Pārlūkot izejas kodu

Add basic index support.

Kestrel 2 gadi atpakaļ
vecāks
revīzija
c9c3e91c8d

+ 1 - 1
microrm-macros/Cargo.toml

@@ -10,7 +10,7 @@ proc-macro = true
 
 [dependencies]
 # proc_macro = "*"
-syn = { version = "1.0", features = ["derive"] }
+syn = { version = "1.0", features = ["derive", "extra-traits"] }
 quote = "1.0"
 convert_case = "0.5"
 proc-macro2 = "1.0"

+ 72 - 0
microrm-macros/src/lib.rs

@@ -220,3 +220,75 @@ pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
         }
     }.into()
 }
+
+type ColumnList = syn::punctuated::Punctuated::<syn::TypePath, syn::Token![,]>;
+struct MakeIndexParams {
+    unique: Option<syn::Token![!]>,
+    name: syn::Ident,
+    #[allow(dead_code)]
+    comma: syn::Token![,],
+    columns: ColumnList
+}
+
+impl syn::parse::Parse for MakeIndexParams {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        Ok(Self {
+            unique: input.parse()?,
+            name: input.parse()?,
+            comma: input.parse()?,
+            columns: ColumnList::parse_separated_nonempty(input)?
+        })
+    }
+}
+
+fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) -> TokenStream {
+    let input = parse_macro_input!(tokens as MakeIndexParams);
+
+    let index_struct_name = input.name;
+
+    let first_col = input.columns.first().unwrap();
+    let mut column_type_path = first_col.path.clone();
+
+    // remove variant name
+    column_type_path.segments.pop();
+    let last = column_type_path.segments.pop().expect("Full path to EntityColumn variant");
+    column_type_path.segments.push(last.value().clone());
+
+    let index_entity_type_name = format_ident!("{}Entity", index_struct_name);
+    let columns = input.columns.clone().into_iter();
+
+    let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
+
+    let unique = input.unique.is_some();
+
+    quote!{
+        pub struct #index_struct_name {}
+        type #index_entity_type_name = <#column_type_path as #microrm_ref::model::EntityColumns>::Entity;
+
+        impl #microrm_ref::model::Index<#index_entity_type_name> for #index_struct_name {
+            fn index_name() -> &'static str {
+                #index_sql_name
+            }
+            fn columns() -> &'static [#column_type_path] where Self: Sized {
+                &[#(#columns),*]
+            }
+            fn unique() -> bool where Self: Sized {
+                #unique
+            }
+        }
+    }.into()
+}
+
+/// Marks a struct as able to be directly used in an Entity to correspond to a single database column.
+#[proc_macro]
+pub fn make_index(tokens: TokenStream) -> TokenStream {
+    do_make_index(tokens, quote!{ ::microrm })
+}
+
+/// For internal use inside the microrm library.
+#[proc_macro]
+pub fn make_index_internal(tokens: TokenStream) -> TokenStream {
+    do_make_index(tokens, quote!{ crate })
+}
+
+ // , attributes(microrm_internal))]

+ 5 - 1
microrm/src/lib.rs

@@ -25,7 +25,7 @@
 //!
 //! let schema = microrm::model::SchemaModel::new().add::<KVStore>();
 //! let db = microrm::DB::new_in_memory(schema).unwrap();
-//! let qi = microrm::query::QueryInterface::new(&db);
+//! let qi = db.query_interface();
 //!
 //! qi.add(&KVStore {
 //!     key: "a_key".to_string(),
@@ -38,6 +38,8 @@
 //! assert_eq!(qr.as_ref().unwrap().key, "a_key");
 //! assert_eq!(qr.as_ref().unwrap().value, "a_value");
 //! ```
+//!
+//! A more interesting
 
 mod meta;
 pub mod model;
@@ -260,4 +262,6 @@ mod test {
 
         qi.get_one_by_id(child_id).expect("Can't get S2 instance");
     }
+
+    microrm_macros::make_index_internal!(S2ParentIndex, S2Columns::ParentId);
 }

+ 43 - 3
microrm/src/model.rs

@@ -77,12 +77,12 @@ pub trait Entity: 'static + for<'de> serde::Deserialize<'de> + serde::Serialize
 }
 
 /// Trait representing the columns of a database entity
-pub trait EntityColumns: PartialEq + From<usize> + std::hash::Hash + Clone {
+pub trait EntityColumns: 'static + PartialEq + From<usize> + std::hash::Hash + Clone {
     type Entity: Entity;
 }
 
 /// Trait for entity IDs in the database
-pub trait EntityID: std::fmt::Debug + Copy + Modelable {
+pub trait EntityID: 'static + std::fmt::Debug + Copy + Modelable {
     type Entity: Entity;
     fn from_raw_id(raw: i64) -> Self;
     fn raw_id(&self) -> i64;
@@ -95,6 +95,30 @@ pub trait EntityForeignKey<T: EntityColumns> {
     fn foreign_column_name(&self) -> &'static str;
 }
 
+/// Trait for an index over a column
+pub trait Index<T: Entity> {
+    fn index_name() -> &'static str
+    where
+        Self: Sized;
+    fn columns() -> &'static [T::Column]
+    where
+        Self: Sized;
+    fn unique() -> bool
+    where
+        Self: Sized;
+}
+
+/*pub struct Index<T: Entity> {
+    pub(crate) columns: Vec<T::Column>,
+    pub(crate) unique: bool
+}
+
+impl<T: Entity> Index<T> {
+    fn new(columns: Vec<T::Column>, unique: bool) -> Self {
+        Self { columns, unique }
+    }
+}*/
+
 /// How we describe an entire schema
 #[derive(Debug)]
 pub struct SchemaModel {
@@ -110,8 +134,24 @@ impl SchemaModel {
         }
     }
 
+    pub fn entity<E: Entity>(mut self) -> Self {
+        let (drop, create) = create::sql_for_table::<E>();
+        self.drop.push(drop);
+        self.create.push(create);
+        self
+    }
+
+    pub fn index<T: Entity<Column = C>, C: EntityColumns<Entity = T>, I: Index<T>>(
+        mut self,
+    ) -> Self {
+        let (drop, create) = create::sql_for_index::<I, _, _>();
+        self.drop.push(drop);
+        self.create.push(create);
+        self
+    }
+
     pub fn add<E: Entity>(mut self) -> Self {
-        let (drop, create) = create::sql_for::<E>();
+        let (drop, create) = create::sql_for_table::<E>();
         self.drop.push(drop);
         self.create.push(create);
         self

+ 56 - 7
microrm/src/model/create.rs

@@ -132,7 +132,7 @@ impl<'de> serde::de::SeqAccess<'de> for CreateDeserializer<'de> {
     }
 }
 
-pub fn sql_for<T: crate::model::Entity>() -> (String, String) {
+pub fn sql_for_table<T: crate::model::Entity>() -> (String, String) {
     let elength = Rc::new(Cell::new(0));
 
     let mut cd = CreateDeserializer {
@@ -186,6 +186,27 @@ pub fn sql_for<T: crate::model::Entity>() -> (String, String) {
     )
 }
 
+pub fn sql_for_index<
+    I: super::Index<T>,
+    T: super::Entity<Column = C>,
+    C: super::EntityColumns<Entity = T>,
+>() -> (String, String) {
+    (
+        format!("DROP INDEX IF EXISTS \"{}\"", I::index_name()),
+        format!(
+            "CREATE {}INDEX \"{}\" ON \"{}\" ({})",
+            if I::unique() { "UNIQUE " } else { "" },
+            I::index_name(),
+            T::table_name(),
+            I::columns()
+                .iter()
+                .map(|x| format!("\"{}\"", T::name(x.clone())))
+                .collect::<Vec<_>>()
+                .join(",")
+        ),
+    )
+}
+
 #[cfg(test)]
 mod test {
     #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
@@ -207,14 +228,14 @@ mod test {
     #[test]
     fn example_sql_for() {
         assert_eq!(
-            super::sql_for::<Empty>(),
+            super::sql_for_table::<Empty>(),
             (
                 r#"DROP TABLE IF EXISTS "empty""#.to_owned(),
                 r#"CREATE TABLE IF NOT EXISTS "empty" (id integer primary key)"#.to_owned()
             )
         );
         assert_eq!(
-            super::sql_for::<Single>(),
+            super::sql_for_table::<Single>(),
             (
                 r#"DROP TABLE IF EXISTS "single""#.to_owned(),
                 r#"CREATE TABLE IF NOT EXISTS "single" (id integer primary key,"e" integer)"#
@@ -223,7 +244,7 @@ mod test {
         );
 
         assert_eq!(
-            super::sql_for::<Reference>(),
+            super::sql_for_table::<Reference>(),
             (
                 r#"DROP TABLE IF EXISTS "reference""#.to_owned(),
                 r#"CREATE TABLE IF NOT EXISTS "reference" (id integer primary key,"e" integer)"#
@@ -244,7 +265,7 @@ mod test {
     #[test]
     fn unit_newtype_struct() {
         assert_eq!(
-            super::sql_for::<UnitNewtype>(),
+            super::sql_for_table::<UnitNewtype>(),
             (
                 r#"DROP TABLE IF EXISTS "unit_newtype""#.to_owned(),
                 r#"CREATE TABLE IF NOT EXISTS "unit_newtype" (id integer primary key,"newtype" integer)"#
@@ -265,7 +286,7 @@ mod test {
     #[test]
     #[should_panic]
     fn nonunit_newtype_struct() {
-        super::sql_for::<NonUnitNewtype>();
+        super::sql_for_table::<NonUnitNewtype>();
     }
 
     #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
@@ -278,11 +299,39 @@ mod test {
     #[test]
     fn test_foreign_key() {
         assert_eq!(
-            super::sql_for::<Child>(),
+            super::sql_for_table::<Child>(),
             (
                 r#"DROP TABLE IF EXISTS "child""#.to_owned(),
                 r#"CREATE TABLE IF NOT EXISTS "child" (id integer primary key,"parent_id" integer references "single"("id"))"#.to_owned()
             )
         );
     }
+
+    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
+    #[microrm_internal]
+    pub struct KeyValue {
+        key: String,
+        value: String,
+    }
+
+    microrm_macros::make_index_internal!(ValueIndex, KeyValueColumns::Value);
+    microrm_macros::make_index_internal!(!UniqueValueIndex, KeyValueColumns::Value);
+
+    #[test]
+    fn test_indexes() {
+        assert_eq!(
+            super::sql_for_index::<ValueIndex, _, _>(),
+            (
+                r#"DROP INDEX IF EXISTS "value_index""#.to_owned(),
+                r#"CREATE INDEX "value_index" ON "key_value" ("value")"#.to_owned()
+            )
+        );
+        assert_eq!(
+            super::sql_for_index::<UniqueValueIndex, _, _>(),
+            (
+                r#"DROP INDEX IF EXISTS "unique_value_index""#.to_owned(),
+                r#"CREATE UNIQUE INDEX "unique_value_index" ON "key_value" ("value")"#.to_owned()
+            )
+        )
+    }
 }