Quellcode durchsuchen

Reworked internal Index ergonomics.

Kestrel vor 2 Jahren
Ursprung
Commit
39dd1fff7f
4 geänderte Dateien mit 100 neuen und 31 gelöschten Zeilen
  1. 24 3
      microrm-macros/src/lib.rs
  2. 64 6
      microrm/src/lib.rs
  3. 6 15
      microrm/src/model.rs
  4. 6 7
      microrm/src/model/create.rs

+ 24 - 3
microrm-macros/src/lib.rs

@@ -265,7 +265,8 @@ fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) ->
         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 {
+        impl #microrm_ref::model::Index for #index_struct_name {
+            type IndexedEntity = #index_entity_type_name;
             fn index_name() -> &'static str {
                 #index_sql_name
             }
@@ -279,13 +280,33 @@ fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) ->
     }.into()
 }
 
-/// Marks a struct as able to be directly used in an Entity to correspond to a single database column.
+/// Defines a struct to represent a optionally-unique index on a table.
+///
+/// Suppose the following `Entity` definition is used:
+///
+/// ```rust
+/// #[derive(Entity,Serialize,Deserialize)]
+/// struct SystemUser {
+///     username: String,
+///     hashed_password: String
+/// }
+/// ```
+///
+/// We can now use `make_index!` to define an index on the username field:
+/// ```rust
+/// make_index!(SystemUsernameIndex, SystemUserColumns::Username)
+/// ```
+///
+/// This index can be made unique by adding a `!` prior to the type name, as:
+/// ```rust
+/// make_index!(!SystemUsernameUniqueIndex, SystemUserColumns::Username)
+/// ```
 #[proc_macro]
 pub fn make_index(tokens: TokenStream) -> TokenStream {
     do_make_index(tokens, quote!{ ::microrm })
 }
 
-/// For internal use inside the microrm library.
+/// For internal use inside the microrm library. See `make_index`.
 #[proc_macro]
 pub fn make_index_internal(tokens: TokenStream) -> TokenStream {
     do_make_index(tokens, quote!{ crate })

+ 64 - 6
microrm/src/lib.rs

@@ -13,17 +13,28 @@
 //!
 //! Querying supports a small subset of SQL expressed as type composition.
 //!
-//! A simple example using a SQLite table as an (unindexed) key/value store
+//! A simple example using an SQLite table as an (indexed) key/value store
 //! might look something like this:
-//! ```
-//! use microrm::Entity;
+//! ```rust
+//! use microrm::{Entity,make_index};
 //! #[derive(Debug,Entity,serde::Serialize,serde::Deserialize)]
 //! pub struct KVStore {
 //!     pub key: String,
 //!     pub value: String
 //! }
 //!
-//! let schema = microrm::model::SchemaModel::new().add::<KVStore>();
+//! // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
+//! make_index!(!KVStoreIndex, KVStoreColumns::Key);
+//!
+//! let schema = microrm::model::SchemaModel::new()
+//!     .add::<KVStore>()
+//!     .index::<KVStoreIndex>();
+//!
+//! // dump the schema in case you want to inspect it manually
+//! for create_sql in schema.create() {
+//!     println!("{};", create_sql);
+//! }
+//!
 //! let db = microrm::DB::new_in_memory(schema).unwrap();
 //! let qi = db.query_interface();
 //!
@@ -32,6 +43,7 @@
 //!     value: "a_value".to_string()
 //! });
 //!
+//! // because KVStoreIndex indexes key, this is a logarithmic lookup
 //! let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
 //!
 //! assert_eq!(qr.is_some(), true);
@@ -39,7 +51,12 @@
 //! assert_eq!(qr.as_ref().unwrap().value, "a_value");
 //! ```
 //!
-//! A more interesting
+//! The schema output from the loop is (details subject to change based on internals):
+//! ```sql
+//! CREATE TABLE IF NOT EXISTS "kv_store" (id integer primary key,"key" text,"value" text);
+//! CREATE UNIQUE INDEX "kv_store_index" ON "kv_store" ("key");
+//! ```
+
 
 mod meta;
 pub mod model;
@@ -48,7 +65,7 @@ pub mod query;
 use meta::Metaschema;
 use model::Entity;
 
-pub use microrm_macros::{Entity, Modelable};
+pub use microrm_macros::{Entity, Modelable, make_index};
 pub use query::{QueryInterface, WithID};
 
 // no need to show the re-exports in the documentation
@@ -265,3 +282,44 @@ mod test {
 
     microrm_macros::make_index_internal!(S2ParentIndex, S2Columns::ParentId);
 }
+
+#[cfg(test)]
+mod test2 {
+    use crate::{Entity,make_index};
+    #[derive(Debug,Entity,serde::Serialize,serde::Deserialize)]
+    #[microrm_internal]
+    pub struct KVStore {
+        pub key: String,
+        pub value: String
+    }
+
+    // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
+    microrm_macros::make_index_internal!(!KVStoreIndex, KVStoreColumns::Key);
+
+    #[test]
+    fn dump_test() {
+        let schema = crate::model::SchemaModel::new()
+            .add::<KVStore>()
+            .index::<KVStoreIndex>();
+
+        // dump the schema in case you want to inspect it manually
+        for create_sql in schema.create() {
+            println!("{};", create_sql);
+        }
+
+        let db = crate::DB::new_in_memory(schema).unwrap();
+        let qi = db.query_interface();
+
+        qi.add(&KVStore {
+            key: "a_key".to_string(),
+            value: "a_value".to_string()
+        });
+
+        // because KVStoreIndex indexes key, this is a logarithmic lookup
+        let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
+
+        assert_eq!(qr.is_some(), true);
+        assert_eq!(qr.as_ref().unwrap().key, "a_key");
+        assert_eq!(qr.as_ref().unwrap().value, "a_value");
+    }
+}

+ 6 - 15
microrm/src/model.rs

@@ -96,11 +96,13 @@ pub trait EntityForeignKey<T: EntityColumns> {
 }
 
 /// Trait for an index over a column
-pub trait Index<T: Entity> {
+pub trait Index {
+    type IndexedEntity : Entity;
+
     fn index_name() -> &'static str
     where
         Self: Sized;
-    fn columns() -> &'static [T::Column]
+    fn columns() -> &'static [<<Self as Index>::IndexedEntity as Entity>::Column]
     where
         Self: Sized;
     fn unique() -> bool
@@ -108,17 +110,6 @@ pub trait Index<T: Entity> {
         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 {
@@ -141,10 +132,10 @@ impl SchemaModel {
         self
     }
 
-    pub fn index<T: Entity<Column = C>, C: EntityColumns<Entity = T>, I: Index<T>>(
+    pub fn index<I: Index>(
         mut self,
     ) -> Self {
-        let (drop, create) = create::sql_for_index::<I, _, _>();
+        let (drop, create) = create::sql_for_index::<I>();
         self.drop.push(drop);
         self.create.push(create);
         self

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

@@ -187,20 +187,19 @@ pub fn sql_for_table<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>,
+    I: super::Index
 >() -> (String, String) {
+    use super::Entity;
     (
         format!("DROP INDEX IF EXISTS \"{}\"", I::index_name()),
         format!(
             "CREATE {}INDEX \"{}\" ON \"{}\" ({})",
             if I::unique() { "UNIQUE " } else { "" },
             I::index_name(),
-            T::table_name(),
+            I::IndexedEntity::table_name(),
             I::columns()
                 .iter()
-                .map(|x| format!("\"{}\"", T::name(x.clone())))
+                .map(|x| format!("\"{}\"", I::IndexedEntity::name(x.clone())))
                 .collect::<Vec<_>>()
                 .join(",")
         ),
@@ -320,14 +319,14 @@ mod test {
     #[test]
     fn test_indexes() {
         assert_eq!(
-            super::sql_for_index::<ValueIndex, _, _>(),
+            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, _, _>(),
+            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()