diff --git a/Cargo.toml b/Cargo.toml index b604158..1b36eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ once_cell = "1.20.1" dinghy-test = "0.7.2" itertools = "0.13" include_dir = "0.7" +paste = "1.0.15" [features] default = [ "upgrade_0_5_x", "upgrade_0_7_x" ] diff --git a/justfile b/justfile index 6d0a96a..f03033b 100644 --- a/justfile +++ b/justfile @@ -79,7 +79,7 @@ bench: expand test_file_name="util": rm -f {{test_file_name}}.expanded.rs; \ - cargo expand --test {{test_file_name}} | save -f --raw src/{{test_file_name}}_expanded.rs + RUSTFLAGS="-Zmacro-backtrace" cargo expand --test {{test_file_name}} | save -f --raw src/{{test_file_name}}_expanded.rs expand_clean: rm -f src/*_expanded.rs \ No newline at end of file diff --git a/native_db_macro/src/keys.rs b/native_db_macro/src/keys.rs index b5a96f9..aa8833a 100644 --- a/native_db_macro/src/keys.rs +++ b/native_db_macro/src/keys.rs @@ -1,14 +1,17 @@ use crate::struct_name::StructName; use crate::ToTokenStream; use quote::quote; +use quote::ToTokens; use std::hash::Hash; -use syn::Ident; +use syn::PathArguments; +use syn::{parse_str, Ident, Type}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct KeyDefinition { pub(super) struct_name: StructName, field_name: Option, function_name: Option, + pub(crate) field_type: Option, pub(crate) options: O, } @@ -31,8 +34,43 @@ impl ToTokenStream for KeyDefinition { let options = self.options.new_to_token_stream(); let struct_name = self.struct_name.ident(); let key_name = self.name(); + let rust_type_name = self + .field_type + .clone() + .expect("KeyDefinition must have a field type"); + + // DEBUG print + // let rust_type_name: &str = "Vec"; + // let type_str = "u32"; + let mut parsed_type: Type = parse_str(&rust_type_name).expect("Failed to parse type"); + + if let Type::Path(ref mut path, ..) = parsed_type { + if let Some(segment) = path.path.segments.last_mut() { + if let PathArguments::AngleBracketed(ref mut args) = segment.arguments { + if args.colon2_token.is_none() { + let new_args = args.clone(); + segment.arguments = PathArguments::None; + + let modified_path: syn::Path = syn::parse_quote! { + #path :: #new_args + }; + + path.path.segments = modified_path.segments; + } + } + } + } + + let parsed_type_token_stream = parsed_type.to_token_stream(); + quote! { - native_db::db_type::KeyDefinition::new(#struct_name::native_model_id(), #struct_name::native_model_version(), #key_name, #options) + native_db::db_type::KeyDefinition::new( + #struct_name::native_model_id(), + #struct_name::native_model_version(), + #key_name, + <#parsed_type_token_stream>::key_names(), + #options + ) } } } @@ -96,11 +134,17 @@ impl KeyDefinition { } } - pub(crate) fn new_field(table_name: StructName, field_name: Ident, options: O) -> Self { + pub(crate) fn new_field( + table_name: StructName, + field_name: Ident, + field_type: String, + options: O, + ) -> Self { Self { struct_name: table_name, field_name: Some(field_name), function_name: None, + field_type: Some(field_type), options, } } @@ -117,6 +161,7 @@ impl KeyDefinition { struct_name: table_name, field_name: None, function_name: None, + field_type: None, options: O::default(), } } @@ -139,7 +184,8 @@ impl KeyDefinition { self.function_name.is_some() } - pub(crate) fn is_empty(&self) -> bool { - self.field_name.is_none() && self.function_name.is_none() - } + // TODO: check why this method is not used + // pub(crate) fn is_empty(&self) -> bool { + // self.field_name.is_none() && self.function_name.is_none() + // } } diff --git a/native_db_macro/src/model_attributes.rs b/native_db_macro/src/model_attributes.rs index 3ae9fb3..a4124ae 100644 --- a/native_db_macro/src/model_attributes.rs +++ b/native_db_macro/src/model_attributes.rs @@ -1,5 +1,7 @@ use crate::keys::{KeyDefinition, KeyOptions}; use crate::struct_name::StructName; +use proc_macro2::TokenStream; +use quote::ToTokens; use std::collections::HashSet; use syn::meta::ParseNestedMeta; use syn::parse::Result; @@ -20,44 +22,56 @@ impl ModelAttributes { pub(crate) fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> { if meta.path.is_ident("primary_key") { let mut key: KeyDefinition<()> = KeyDefinition::new_empty(self.struct_name.clone()); - meta.parse_nested_meta(|meta| { - let ident = meta - .path - .get_ident() - .expect("Expected ident for primary_key"); - if key.is_empty() { - key.set_function_name(ident.clone()); - } else { - panic!( - "Unknown attribute \"{}\" for primary_key", - ident.to_string() - ); - } - Ok(()) - })?; + let content; + syn::parenthesized!(content in meta.input); + + // Parse the identifier + let ident: syn::Ident = content.parse()?; + key.set_function_name(ident); + + // Expect a comma + content.parse::]>()?; + + // Parse the type + let ty: syn::Type = content.parse()?; + let ty_string = ty.to_token_stream().to_string(); + key.field_type = Some(ty_string); + self.primary_key = Some(key); } else if meta.path.is_ident("secondary_key") { let mut key: KeyDefinition = KeyDefinition::new_empty(self.struct_name.clone()); - meta.parse_nested_meta(|meta| { - let ident = meta - .path - .get_ident() - .expect("Expected ident for secondary_key"); - if key.is_empty() { - key.set_function_name(ident.clone()); - } else if meta.path.is_ident("unique") { - key.options.unique = true; - } else if meta.path.is_ident("optional") { - key.options.optional = true; - } else { - panic!( - "Unknown attribute \"{}\" for secondary_key", - ident.to_string() - ); + let content; + syn::parenthesized!(content in meta.input); + + // Parse the identifier + let ident: syn::Ident = content.parse()?; + key.set_function_name(ident); + + // Expect a comma + content.parse::]>()?; + + // Parse the type + let ty: syn::Type = content.parse()?; + let ty_string = ty.to_token_stream().to_string(); + key.field_type = Some(ty_string); + + // Parse optional flags + while !content.is_empty() { + content.parse::()?; + let option: syn::Ident = content.parse()?; + match option.to_string().as_str() { + "unique" => key.options.unique = true, + "optional" => key.options.optional = true, + _ => { + return Err(syn::Error::new_spanned( + option, + "Unknown option for secondary_key, expected 'unique' or 'optional'", + )); + } } - Ok(()) - })?; + } + self.secondary_keys.insert(key); } else { panic!( @@ -71,15 +85,22 @@ impl ModelAttributes { pub(crate) fn parse_field(&mut self, field: &Field) -> Result<()> { for attr in &field.attrs { if attr.path().is_ident("primary_key") { + let mut field_type_token_stream = TokenStream::new(); + field.ty.to_tokens(&mut field_type_token_stream); + let field_type = field_type_token_stream.to_string(); self.primary_key = Some(KeyDefinition::new_field( self.struct_name.clone(), field .ident .clone() .expect("Parsed field expected to have an ident for primary_key"), + field_type, (), )); } else if attr.path().is_ident("secondary_key") { + let mut field_type_token_stream = TokenStream::new(); + field.ty.to_tokens(&mut field_type_token_stream); + let field_type = field_type_token_stream.to_string(); let mut secondary_options = KeyOptions::default(); if let Ok(_) = attr.meta.require_list() { attr.parse_nested_meta(|meta| { @@ -100,6 +121,7 @@ impl ModelAttributes { .ident .clone() .expect("Parsed field expected to have an ident for secondary_key"), + field_type, secondary_options, )); } diff --git a/native_db_macro/src/native_db.rs b/native_db_macro/src/native_db.rs index 9d3d012..c262f30 100644 --- a/native_db_macro/src/native_db.rs +++ b/native_db_macro/src/native_db.rs @@ -56,6 +56,7 @@ pub fn native_db(args: TokenStream, input: TokenStream) -> TokenStream { #native_db_gks } + #[allow(non_camel_case_types)] pub(crate) enum #keys_enum_name { #(#keys_enum),* } diff --git a/native_db_macro/src/struct_name.rs b/native_db_macro/src/struct_name.rs index 685a959..181844d 100644 --- a/native_db_macro/src/struct_name.rs +++ b/native_db_macro/src/struct_name.rs @@ -1,6 +1,6 @@ use proc_macro2::Ident; -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct StructName(Ident); impl StructName { diff --git a/src/db_type/error.rs b/src/db_type/error.rs index d29b8df..41cb2af 100644 --- a/src/db_type/error.rs +++ b/src/db_type/error.rs @@ -71,6 +71,14 @@ pub enum Error { #[error("Duplicate key for \"{key_name}\"")] DuplicateKey { key_name: String }, + #[error("Missmatched key type for \"{key_name}\" expected {expected_types:?} got {got_types:?} during {operation:?}")] + MissmatchedKeyType { + key_name: String, + expected_types: Vec, + got_types: Vec, + operation: String, + }, + #[error("Watch event error")] WatchEventError(#[from] watch::WatchEventError), diff --git a/src/db_type/key/key.rs b/src/db_type/key/key.rs index 1afd3e0..686d572 100644 --- a/src/db_type/key/key.rs +++ b/src/db_type/key/key.rs @@ -35,10 +35,14 @@ impl Key { /// #[derive(Debug, Deserialize, Serialize)] /// struct City(String); /// -/// impl ToKey for &City { -/// fn to_key(&self) -> Key { +/// impl ToKey for City { +/// fn to_key(&self) -> Key { /// Key::new(self.0.as_bytes().to_vec()) /// } +/// +/// fn key_names() -> Vec { +/// vec!["City".to_string()] +/// } /// } /// /// #[derive(Serialize, Deserialize)] @@ -60,10 +64,10 @@ impl Key { /// let r = db.r_transaction()?; /// /// // Get contry by the capital city (primary key) -/// let _us: Option = r.get().primary(&City("Washington, D.C.".to_string()))?; +/// let _us: Option = r.get().primary(City("Washington, D.C.".to_string()))?; /// /// // Get contry by the bigest city (secondary key) -/// let _us: Option = r.get().secondary(ContryKey::bigest_city,&City("New York".to_string()))?; +/// let _us: Option = r.get().secondary(ContryKey::bigest_city, City("New York".to_string()))?; /// Ok(()) /// } /// ``` @@ -80,10 +84,14 @@ impl Key { /// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)] /// struct Uuid(uuid::Uuid); /// -/// impl ToKey for &Uuid { +/// impl ToKey for Uuid { /// fn to_key(&self) -> Key { /// Key::new(self.0.as_bytes().to_vec()) /// } +/// +/// fn key_names() -> Vec { +/// vec!["Uuid".to_string()] +/// } /// } /// /// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] @@ -105,7 +113,7 @@ impl Key { /// rw.commit()?; /// /// let r = db.r_transaction()?; -/// let result_item: Item = r.get().primary(&item.uuid)?.unwrap(); +/// let result_item: Item = r.get().primary(item.uuid.clone())?.unwrap(); /// assert_eq!(result_item.uuid, item.uuid); /// Ok(()) /// } @@ -124,10 +132,14 @@ impl Key { /// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)] /// struct DateTime(chrono::DateTime); /// -/// impl ToKey for &DateTime { +/// impl ToKey for DateTime { /// fn to_key(&self) -> Key { /// Key::new(self.0.timestamp_millis().to_be_bytes().to_vec()) /// } +/// +/// fn key_names() -> Vec { +/// vec!["DateTime".to_string()] +/// } /// } /// /// #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] @@ -165,6 +177,7 @@ impl Key { /// ``` pub trait ToKey: Debug { fn to_key(&self) -> Key; + fn key_names() -> Vec; } // Implement for char @@ -172,13 +185,19 @@ impl ToKey for char { fn to_key(&self) -> Key { Key::new(u32::from(*self).to_be_bytes().to_vec()) } + fn key_names() -> Vec { + vec!["char".to_string()] + } } -// Implement for String -impl ToKey for &String { +// Implement for &String +impl ToKey for String { fn to_key(&self) -> Key { self.as_str().to_key() } + fn key_names() -> Vec { + vec!["String".to_string()] + } } // Implement for &str @@ -186,6 +205,9 @@ impl ToKey for &str { fn to_key(&self) -> Key { Key::new(self.as_bytes().to_vec()) } + fn key_names() -> Vec { + vec!["String".to_string(), "&str".to_string()] + } } impl ToKey for Key { @@ -194,19 +216,9 @@ impl ToKey for Key { fn to_key(&self) -> Key { self.clone() } -} -// Implement for Slice -impl ToKey for &[T] -where - T: ToKey, -{ - fn to_key(&self) -> Key { - let mut data = Vec::new(); - for item in self.iter().as_slice() { - data.extend(item.to_key().0); - } - Key::new(data) + fn key_names() -> Vec { + vec!["Key".to_string()] } } @@ -215,6 +227,9 @@ impl ToKey for () { fn to_key(&self) -> Key { Key::new(Vec::new()) } + fn key_names() -> Vec { + vec!["()".to_string()] + } } // Macro for tuples @@ -229,6 +244,15 @@ macro_rules! impl_inner_key_value_for_tuple { data.extend(self.$i_last.to_key().0); Key::new(data) } + fn key_names() -> Vec { + let mut name = String::new(); + $( + name.push_str(<$t as ToKey>::key_names()[0].as_str()); + name.push_str(", "); + )+ + name.push_str(<$t_last as ToKey>::key_names()[0].as_str()); + vec![format!("({})", name)] + } } } } @@ -322,6 +346,36 @@ where } Key::new(data) } + fn key_names() -> Vec { + let mut names = Vec::new(); + for name in T::key_names() { + names.push(format!("Vec<{}>", name)); + names.push(format!("[{}]", name)); + } + names + } +} + +// Implement for Slice +impl ToKey for &[T] +where + T: ToKey, +{ + fn to_key(&self) -> Key { + let mut data = Vec::new(); + for item in self.iter().as_slice() { + data.extend(item.to_key().0); + } + Key::new(data) + } + fn key_names() -> Vec { + let mut names = Vec::new(); + for name in T::key_names() { + names.push(format!("[{}]", name)); + } + + names + } } // Implement InnerKeyValue for Option where T: InnerKeyValue @@ -335,6 +389,13 @@ where None => Key::new(Vec::new()), } } + fn key_names() -> Vec { + let mut names = Vec::new(); + for name in T::key_names() { + names.push(format!("Option<{}>", name)); + } + names + } } // Macro for implementing InnerKeyValue for u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64 @@ -344,6 +405,9 @@ macro_rules! impl_inner_key_value_for_primitive { fn to_key(&self) -> Key { Key::new(self.to_be_bytes().to_vec()) } + fn key_names() -> Vec { + vec![stringify!($type).to_string()] + } } }; } diff --git a/src/db_type/key/key_definition.rs b/src/db_type/key/key_definition.rs index 68e34c9..0215253 100644 --- a/src/db_type/key/key_definition.rs +++ b/src/db_type/key/key_definition.rs @@ -1,5 +1,6 @@ -use crate::db_type::Key; -use std::hash::Hash; +use crate::db_type::{Error, Result}; +use crate::{db_type::Key, Model, ToKey}; +use std::{hash::Hash, ops::RangeBounds}; pub trait ToKeyDefinition { fn key_definition(&self) -> KeyDefinition; @@ -8,6 +9,7 @@ pub trait ToKeyDefinition { #[derive(Default, Clone, Debug)] pub struct KeyDefinition { pub(crate) unique_table_name: String, + pub(crate) rust_types: Vec, pub(crate) options: O, } @@ -18,10 +20,17 @@ impl ToKeyDefinition for KeyDefinition { } impl KeyDefinition { - pub fn new(model_id: u32, model_version: u32, name: &'static str, options: O) -> Self { + pub fn new( + model_id: u32, + model_version: u32, + name: &'static str, + rust_types: Vec, + options: O, + ) -> Self { let table_name = format!("{}_{}_{}", model_id, model_version, name); Self { options, + rust_types, unique_table_name: table_name, } } @@ -31,17 +40,17 @@ impl KeyDefinition { } } -impl From<&'static str> for KeyDefinition<()> { - fn from(name: &'static str) -> Self { - Self::new(0, 0, name, ()) - } -} +// impl From<&'static str> for KeyDefinition<()> { +// fn from(name: &'static str) -> Self { +// Self::new(0, 0, name, ()) +// } +// } -impl From<&'static str> for KeyDefinition { - fn from(name: &'static str) -> Self { - Self::new(0, 0, name, KeyOptions::default()) - } -} +// impl From<&'static str> for KeyDefinition { +// fn from(name: &'static str) -> Self { +// Self::new(0, 0, name, KeyOptions::default()) +// } +// } impl PartialEq for KeyDefinition { fn eq(&self, other: &Self) -> bool { @@ -72,3 +81,54 @@ pub fn composite_key(secondary_key: &Key, primary_key: &Key) -> Key { secondary_key.extend_with_delimiter(0, primary_key); secondary_key } +fn _check_key_type_from_key_definition(key_definition: &KeyDefinition) -> Result<()> { + if !K::key_names() + .iter() + .any(|name| key_definition.rust_types.contains(name)) + { + return Err(Error::MissmatchedKeyType { + key_name: key_definition.unique_table_name.to_string(), + expected_types: key_definition.rust_types.clone(), + got_types: K::key_names(), + operation: "get".to_string(), + }); + } + Ok(()) +} + +fn _check_key_type(model: &Model) -> Result<()> { + if !K::key_names() + .iter() + .any(|name| model.primary_key.rust_types.contains(name)) + { + return Err(Error::MissmatchedKeyType { + key_name: model.primary_key.unique_table_name.to_string(), + expected_types: model.primary_key.rust_types.clone(), + got_types: K::key_names(), + operation: "get".to_string(), + }); + } + Ok(()) +} + +pub(crate) fn check_key_type(model: &Model, _key: &K) -> Result<()> { + _check_key_type::(model) +} + +pub(crate) fn check_key_type_from_key_definition(key_definition: &KeyDefinition, _key: &K) -> Result<()> { + _check_key_type_from_key_definition::(key_definition) +} + +pub(crate) fn check_range_key_range_bounds( + model: &Model, + _range: &impl RangeBounds, +) -> Result<()> { + _check_key_type::(model) +} + +pub(crate) fn check_range_key_range_bounds_from_key_definition( + key_definition: &KeyDefinition, + _range: &impl RangeBounds, +) -> Result<()> { + _check_key_type_from_key_definition::(key_definition) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 61fc6b2..18c9ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ //! //! > 👉 Unlike the usual database where there is a difference between *schema* and *model*, here, as we can directly use Rust types that are serialized in the database, we do not have the concept of *schema*, only that of the *model*. //! -//! In this section, we will create a simple model. I have chosen a particular organization using Rust modules, which I find to be a best practice. However, it is not mandatory; you can do it as you prefer. +//! In this section, we will create a simple model. I have chosen a particular organization using Rust modules, which I find to be a best practice. However, it is not mandatory; you can do it as you prefer. (see [`define`](crate::Models::define) for more information) //! //! In this example: //! - We create a module `data` which contains **all versions of all models**. @@ -195,7 +195,7 @@ //! //! // Get the person //! let r = db.r_transaction()?; -//! let person: data::Person = r.get().primary(&"Alice".to_string())?.unwrap(); +//! let person: data::Person = r.get().primary("Alice".to_string())?.unwrap(); //! assert_eq!(person.name, "Alice"); //! Ok(()) //! } diff --git a/src/models.rs b/src/models.rs index 643bf86..54311c3 100644 --- a/src/models.rs +++ b/src/models.rs @@ -68,26 +68,34 @@ impl Models { /// Defines a table using the given model. /// - /// Native DB depends of `native_model` to define the model. - /// And `native_model` by default uses [`serde`](https://serde.rs/) to serialize and deserialize the data but - /// you can use any other serialization library see the documentation of [`native_model`](https://github.com/vincent-herlemont/native_model) for more information. - /// So in the example below we import `serde` and we use the `Serialize` and `Deserialize` traits. + /// Native DB depends on `native_model` to define the model. + /// By default, `native_model` uses [`serde`](https://serde.rs/) to serialize and deserialize the data, but + /// you can use any other serialization library. See the documentation of [`native_model`](https://github.com/vincent-herlemont/native_model) for more information. + /// In the examples below, we import `serde` and use the `Serialize` and `Deserialize` traits. /// - /// # Primary key + /// # Primary Key /// - /// The primary key is *strict*, you **must**: - /// - define it. - /// - define only one. + /// The primary key is **mandatory**, and you **must**: + /// - Define it. + /// - Define only one. /// - /// If the primary key is not defined, the compiler will return an error `Primary key is not set`. + /// If the primary key is not defined, the compiler will return an error: `Primary key is not set`. /// - /// You can define with two ways: - /// - `#[primary_key]` on the field - /// - `#[native_db(primary_key())]` on any type `enum`, `struct`, `tuple struct` or `unit struct`. + /// There are two ways to define a primary key: + /// + /// 1. **On a Field**: + /// - Use the `#[primary_key]` attribute on the field that will serve as the primary key. + /// - The type of the field will be used as the primary key type. + /// + /// 2. **With a Custom Method**: + /// - Use the `#[native_db(primary_key( -> ))]` attribute on the type (`enum`, `struct`, `tuple struct`, or `unit struct`). + /// - Implement a method with the given `` that returns the primary key of type ``. + /// - **Important:** You must specify both the method name and the return type using the syntax `primary_key( -> )`. The type must be specified because it is used at runtime to check the query types. /// /// The primary key is **unique**, so you can't have two instances of the model with the same primary key saved in the database. /// - /// ## Define a simple model with a primary key + /// ## Defining a Simple Model with a Primary Key on a Field + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -106,7 +114,12 @@ impl Models { /// models.define::() /// } /// ``` - /// ## Define a model with a method as primary key + /// + /// In this example, we have: + /// - **One primary key** named `id` of type `u64`, defined directly on the field using the `#[primary_key]` attribute. + /// + /// ## Defining a Model with a Method as Primary Key + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -115,33 +128,43 @@ impl Models { /// #[derive(Serialize, Deserialize)] /// #[native_model(id=1, version=1)] /// #[native_db( - /// primary_key(custom_id) + /// primary_key(custom_id -> u32) /// )] /// struct Data(u64); /// /// impl Data { - /// fn custom_id(&self) -> u32 { - /// (self.0 + 1) as u32 - /// } + /// fn custom_id(&self) -> u32 { + /// (self.0 + 1) as u32 + /// } /// } - /// /// ``` /// - /// ## Secondary key + /// In this example, we have: + /// - **One primary key** named `custom_id` of type `u32`, defined using a custom method. The method `custom_id` computes and returns the primary key value. + /// + /// # Secondary Key /// - /// The secondary key is *flexible*, you can: - /// - define it or not. - /// - define one or more. + /// The secondary key is *flexible*, and you can: + /// - Define it or not. + /// - Define one or more. /// - /// You can define with two ways: - /// - `#[secondary_key]` on the field - /// - `#[native_db(secondary_key(, ))]` on any type `enum`, `struct`, `tuple struct` or `unit struct`. + /// There are two ways to define a secondary key: + /// + /// 1. **On a Field**: + /// - Use the `#[secondary_key]` attribute on the field that will serve as a secondary key. + /// - The type of the field will be used as the secondary key type. + /// + /// 2. **With a Custom Method**: + /// - Use the `#[native_db(secondary_key( -> , ))]` attribute on the type. + /// - Implement a method with the given `` that returns the secondary key value of type ``. + /// - **Important:** You must specify both the method name and the return type using the syntax `secondary_key( -> , )`. The type must be specified because it is used at runtime to check the query types. /// /// The secondary key can have two options: /// - [`unique`](#unique) (default: false) /// - [`optional`](#optional) (default: false) /// - /// ## Define a model with a secondary key + /// ## Defining a Model with a Secondary Key on a Field + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -158,7 +181,12 @@ impl Models { /// } /// ``` /// - /// ## Define a model wit a secondary key optional and unique + /// In the above example, we have: + /// - **One primary key** named `id` of type `u64`, defined on the field. + /// - **One secondary key** named `name` of type `String`, defined on the field using the `#[secondary_key]` attribute. + /// + /// ## Defining a Model with an Optional and Unique Secondary Key + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -174,7 +202,12 @@ impl Models { /// name: Option, /// } /// ``` - /// - Note: the secondary key can be `unique` **or** `optional` as well. + /// + /// In the above example, we have: + /// - **One primary key** named `id` of type `u64`, defined on the field. + /// - **One secondary key** named `name` of type `Option`, defined on the field with options `unique` and `optional`. + /// + /// - **Note:** The secondary key can be `unique`, `optional`, or both. /// /// ## Unique /// @@ -184,14 +217,15 @@ impl Models { /// ## Optional /// /// This means that an instance of the model can have a value for the secondary key or not. - /// When`optional` is set the value **must** be an [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html). - /// if the value is not an [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) the compiler will return - /// an error `error[E0282]: type annotations needed: cannot infer type`. - /// - /// Under the hood, the secondary key is stored in a separate redb table. So if the secondary key is optional, + /// When `optional` is set, the value **must** be an [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html). + /// If the value is not an [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html), the compiler will return + /// an error: `error[E0282]: type annotations needed: cannot infer type`. + /// + /// Under the hood, the secondary key is stored in a separate `redb` table. So if the secondary key is optional, /// the value will be stored in the table only if the value is not `None`. /// - /// # Define a model with a secondary key and a custom secondary key optional + /// ## Defining a Model with a Custom Optional Secondary Key + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -200,7 +234,7 @@ impl Models { /// #[derive(Serialize, Deserialize)] /// #[native_model(id=1, version=1)] /// #[native_db( - /// secondary_key(custom_name, optional) + /// secondary_key(custom_name -> Option, optional) /// )] /// struct Data { /// #[primary_key] @@ -220,12 +254,21 @@ impl Models { /// } /// } /// ``` - /// # Define multiple models /// - /// To define multiple models, you **must** use different `id` for each model. If you use the same `id` for two models, - /// the program will panic with the message `The table has the same native model version as the table and it's not allowed`. + /// In the above example, we have: + /// - **One primary key** named `id` of type `u64`, defined on the field. + /// - **One secondary key** named `name` of type `String`, defined on the field. + /// - **One custom secondary key** named `custom_name` of type `Option`, defined using a custom method with the option `optional`. + /// + /// The method `custom_name` returns an `Option` based on some logic involving the `flag` field. + /// + /// # Defining Multiple Models + /// + /// To define multiple models, you **must** use different `id` values for each model. If you use the same `id` for two models, + /// the program will panic with the message: `The table has the same native model version as the table and it's not allowed`. /// /// Example: + /// /// ```rust /// use native_db::*; /// use native_model::{native_model, Model}; @@ -253,6 +296,12 @@ impl Models { /// models.define::() /// } /// ``` + /// + /// In the above example, we have: + /// - We have two models, `Animal` and `Vegetable`. + /// - Both have: + /// - **One primary key** named `name` of type `String`, defined on the field. + /// - Each model has a unique `id` (`id=1` for `Animal`, `id=2` for `Vegetable`), which is necessary to avoid conflicts. pub fn define(&mut self) -> Result<()> { let mut new_model_builder = ModelBuilder { model: T::native_db_model(), diff --git a/src/test_tuple_field_expanded copy.rs b/src/test_tuple_field_expanded copy.rs new file mode 100644 index 0000000..821721f --- /dev/null +++ b/src/test_tuple_field_expanded copy.rs @@ -0,0 +1,457 @@ +### parsed_type: "(String, u32)" +### parsed_type_token_stream: TokenStream [Group { delimiter: Parenthesis, stream: TokenStream [Ident { ident: "String", span: #21 bytes(197..209) }, Punct { ch: ',', spacing: Alone, span: #21 bytes(197..209) }, Ident { ident: "u32", span: #21 bytes(197..209) }], span: #21 bytes(197..209) }] +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use native_db::*; +use native_model::{native_model, Model}; +use serde::{Deserialize, Serialize}; +struct ItemCustomPk { + #[primary_key] + id: (String, u32), +} +impl native_db::db_type::ToInput for ItemCustomPk { + fn native_db_bincode_encode_to_vec(&self) -> native_db::db_type::Result> { + native_db::bincode_encode_to_vec(self) + } + fn native_db_bincode_decode_from_slice( + slice: &[u8], + ) -> native_db::db_type::Result { + Ok(native_db::bincode_decode_from_slice(slice)?.0) + } + fn native_db_model() -> native_db::Model { + let mut secondary_tables_name = std::collections::HashSet::new(); + native_db::Model { + primary_key: native_db::db_type::KeyDefinition::new( + ItemCustomPk::native_model_id(), + ItemCustomPk::native_model_version(), + "id", + <(String, u32)>::key_names(), + (), + ), + secondary_keys: secondary_tables_name, + } + } + fn native_db_primary_key(&self) -> native_db::db_type::Key { + (&self.id).to_key() + } + fn native_db_secondary_keys( + &self, + ) -> std::collections::HashMap< + native_db::db_type::KeyDefinition, + native_db::db_type::KeyEntry, + > { + let mut secondary_tables_name = std::collections::HashMap::new(); + secondary_tables_name + } +} +pub(crate) enum ItemCustomPkKey {} +impl native_db::db_type::ToKeyDefinition +for ItemCustomPkKey { + fn key_definition( + &self, + ) -> native_db::db_type::KeyDefinition { + match self { + _ => { + ::std::rt::begin_panic("Unknown key"); + } + } + } +} +impl native_model::Model for ItemCustomPk { + fn native_model_id() -> u32 { + 1 + } + fn native_model_id_str() -> &'static str { + "1" + } + fn native_model_version() -> u32 { + 1 + } + fn native_model_version_str() -> &'static str { + "1" + } + fn native_model_encode_body( + &self, + ) -> std::result::Result, native_model::EncodeBodyError> { + use native_model::Encode; + native_model::bincode_1_3::Bincode::encode(self) + .map_err(|e| native_model::EncodeBodyError { + msg: { + let res = ::alloc::fmt::format(format_args!("{0}", e)); + res + }, + source: e.into(), + }) + } + fn native_model_encode_downgrade_body( + self, + version: u32, + ) -> native_model::Result> { + if version == Self::native_model_version() { + let result = self.native_model_encode_body()?; + Ok(result) + } else if version < Self::native_model_version() { + Err(native_model::Error::DowngradeNotSupported { + from: version, + to: Self::native_model_version(), + }) + } else { + Err(native_model::Error::DowngradeNotSupported { + from: version, + to: Self::native_model_version(), + }) + } + } + fn native_model_decode_body( + data: Vec, + id: u32, + ) -> std::result::Result { + if id != 1 { + return Err(native_model::DecodeBodyError::MismatchedModelId); + } + use native_model::Decode; + native_model::bincode_1_3::Bincode::decode(data) + .map_err(|e| native_model::DecodeBodyError::DecodeError { + msg: { + let res = ::alloc::fmt::format(format_args!("{0}", e)); + res + }, + source: e.into(), + }) + } + fn native_model_decode_upgrade_body( + data: Vec, + id: u32, + version: u32, + ) -> native_model::Result { + if version == Self::native_model_version() { + let result = Self::native_model_decode_body(data, id)?; + Ok(result) + } else if version < Self::native_model_version() { + Err(native_model::Error::UpgradeNotSupported { + from: version, + to: Self::native_model_version(), + }) + } else { + Err(native_model::Error::UpgradeNotSupported { + from: version, + to: Self::native_model_version(), + }) + } + } +} +#[doc(hidden)] +#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] +const _: () = { + #[allow(unused_extern_crates, clippy::useless_attribute)] + extern crate serde as _serde; + #[automatically_derived] + impl _serde::Serialize for ItemCustomPk { + fn serialize<__S>( + &self, + __serializer: __S, + ) -> _serde::__private::Result<__S::Ok, __S::Error> + where + __S: _serde::Serializer, + { + let mut __serde_state = _serde::Serializer::serialize_struct( + __serializer, + "ItemCustomPk", + false as usize + 1, + )?; + _serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "id", + &self.id, + )?; + _serde::ser::SerializeStruct::end(__serde_state) + } + } +}; +#[doc(hidden)] +#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] +const _: () = { + #[allow(unused_extern_crates, clippy::useless_attribute)] + extern crate serde as _serde; + #[automatically_derived] + impl<'de> _serde::Deserialize<'de> for ItemCustomPk { + fn deserialize<__D>( + __deserializer: __D, + ) -> _serde::__private::Result + where + __D: _serde::Deserializer<'de>, + { + #[allow(non_camel_case_types)] + #[doc(hidden)] + enum __Field { + __field0, + __ignore, + } + #[doc(hidden)] + struct __FieldVisitor; + impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { + type Value = __Field; + fn expecting( + &self, + __formatter: &mut _serde::__private::Formatter, + ) -> _serde::__private::fmt::Result { + _serde::__private::Formatter::write_str( + __formatter, + "field identifier", + ) + } + fn visit_u64<__E>( + self, + __value: u64, + ) -> _serde::__private::Result + where + __E: _serde::de::Error, + { + match __value { + 0u64 => _serde::__private::Ok(__Field::__field0), + _ => _serde::__private::Ok(__Field::__ignore), + } + } + fn visit_str<__E>( + self, + __value: &str, + ) -> _serde::__private::Result + where + __E: _serde::de::Error, + { + match __value { + "id" => _serde::__private::Ok(__Field::__field0), + _ => _serde::__private::Ok(__Field::__ignore), + } + } + fn visit_bytes<__E>( + self, + __value: &[u8], + ) -> _serde::__private::Result + where + __E: _serde::de::Error, + { + match __value { + b"id" => _serde::__private::Ok(__Field::__field0), + _ => _serde::__private::Ok(__Field::__ignore), + } + } + } + impl<'de> _serde::Deserialize<'de> for __Field { + #[inline] + fn deserialize<__D>( + __deserializer: __D, + ) -> _serde::__private::Result + where + __D: _serde::Deserializer<'de>, + { + _serde::Deserializer::deserialize_identifier( + __deserializer, + __FieldVisitor, + ) + } + } + #[doc(hidden)] + struct __Visitor<'de> { + marker: _serde::__private::PhantomData, + lifetime: _serde::__private::PhantomData<&'de ()>, + } + impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> { + type Value = ItemCustomPk; + fn expecting( + &self, + __formatter: &mut _serde::__private::Formatter, + ) -> _serde::__private::fmt::Result { + _serde::__private::Formatter::write_str( + __formatter, + "struct ItemCustomPk", + ) + } + #[inline] + fn visit_seq<__A>( + self, + mut __seq: __A, + ) -> _serde::__private::Result + where + __A: _serde::de::SeqAccess<'de>, + { + let __field0 = match _serde::de::SeqAccess::next_element::< + (String, u32), + >(&mut __seq)? { + _serde::__private::Some(__value) => __value, + _serde::__private::None => { + return _serde::__private::Err( + _serde::de::Error::invalid_length( + 0usize, + &"struct ItemCustomPk with 1 element", + ), + ); + } + }; + _serde::__private::Ok(ItemCustomPk { id: __field0 }) + } + #[inline] + fn visit_map<__A>( + self, + mut __map: __A, + ) -> _serde::__private::Result + where + __A: _serde::de::MapAccess<'de>, + { + let mut __field0: _serde::__private::Option<(String, u32)> = _serde::__private::None; + while let _serde::__private::Some(__key) = _serde::de::MapAccess::next_key::< + __Field, + >(&mut __map)? { + match __key { + __Field::__field0 => { + if _serde::__private::Option::is_some(&__field0) { + return _serde::__private::Err( + <__A::Error as _serde::de::Error>::duplicate_field("id"), + ); + } + __field0 = _serde::__private::Some( + _serde::de::MapAccess::next_value::< + (String, u32), + >(&mut __map)?, + ); + } + _ => { + let _ = _serde::de::MapAccess::next_value::< + _serde::de::IgnoredAny, + >(&mut __map)?; + } + } + } + let __field0 = match __field0 { + _serde::__private::Some(__field0) => __field0, + _serde::__private::None => { + _serde::__private::de::missing_field("id")? + } + }; + _serde::__private::Ok(ItemCustomPk { id: __field0 }) + } + } + #[doc(hidden)] + const FIELDS: &'static [&'static str] = &["id"]; + _serde::Deserializer::deserialize_struct( + __deserializer, + "ItemCustomPk", + FIELDS, + __Visitor { + marker: _serde::__private::PhantomData::, + lifetime: _serde::__private::PhantomData, + }, + ) + } + } +}; +#[automatically_derived] +impl ::core::cmp::Eq for ItemCustomPk { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () { + let _: ::core::cmp::AssertParamIsEq<(String, u32)>; + } +} +#[automatically_derived] +impl ::core::marker::StructuralPartialEq for ItemCustomPk {} +#[automatically_derived] +impl ::core::cmp::PartialEq for ItemCustomPk { + #[inline] + fn eq(&self, other: &ItemCustomPk) -> bool { + self.id == other.id + } +} +#[automatically_derived] +impl ::core::fmt::Debug for ItemCustomPk { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_struct_field1_finish( + f, + "ItemCustomPk", + "id", + &&self.id, + ) + } +} +#[automatically_derived] +impl ::core::clone::Clone for ItemCustomPk { + #[inline] + fn clone(&self) -> ItemCustomPk { + ItemCustomPk { + id: ::core::clone::Clone::clone(&self.id), + } + } +} +extern crate test; +#[cfg(test)] +#[rustc_test_marker = "test_tuple_tokey"] +pub const test_tuple_tokey: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("test_tuple_tokey"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "tests/test_tuple_field.rs", + start_line: 14usize, + start_col: 4usize, + end_line: 14usize, + end_col: 20usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(test_tuple_tokey()), + ), +}; +fn test_tuple_tokey() { + let to_key = ("test".to_string(), 3u32); + let to_key = to_key.to_key(); + match &to_key { + tmp => { + { + ::std::io::_eprint( + format_args!( + "[{0}:{1}:{2}] {3} = {4:#?}\n", + "tests/test_tuple_field.rs", + 17u32, + 5u32, + "&to_key", + &tmp, + ), + ); + }; + tmp + } + }; + let key_names = <(String, u32)>::key_names(); + match &key_names { + tmp => { + { + ::std::io::_eprint( + format_args!( + "[{0}:{1}:{2}] {3} = {4:#?}\n", + "tests/test_tuple_field.rs", + 20u32, + 5u32, + "&key_names", + &tmp, + ), + ); + }; + tmp + } + }; +} +#[rustc_main] +#[coverage(off)] +pub fn main() -> () { + extern crate test; + test::test_main_static(&[&test_tuple_tokey]) +} diff --git a/src/transaction/query/get.rs b/src/transaction/query/get.rs index 12d7db2..d9e0deb 100644 --- a/src/transaction/query/get.rs +++ b/src/transaction/query/get.rs @@ -1,4 +1,6 @@ -use crate::db_type::{KeyOptions, Result, ToInput, ToKey, ToKeyDefinition}; +use crate::db_type::{ + check_key_type, check_key_type_from_key_definition, KeyOptions, Result, ToInput, ToKey, ToKeyDefinition +}; use crate::transaction::internal::private_readable_transaction::PrivateReadableTransaction; use crate::transaction::internal::r_transaction::InternalRTransaction; use crate::transaction::internal::rw_transaction::InternalRwTransaction; @@ -40,6 +42,7 @@ impl RGet<'_, '_> { /// ``` pub fn primary(&self, key: impl ToKey) -> Result> { let model = T::native_db_model(); + check_key_type(&model, &key)?; let result = self.internal.get_by_primary_key(model, key)?; if let Some(value) = result { Ok(Some(value.inner()?)) @@ -90,6 +93,7 @@ impl RGet<'_, '_> { key: impl ToKey, ) -> Result> { let model = T::native_db_model(); + check_key_type_from_key_definition(&key_def.key_definition(), &key)?; let result = self.internal.get_by_secondary_key(model, key_def, key)?; if let Some(value) = result { Ok(Some(value.inner()?)) @@ -109,6 +113,7 @@ impl RwGet<'_, '_> { /// See [`primary`](crate::transaction::query::RGet::primary). pub fn primary(&self, key: impl ToKey) -> Result> { let model = T::native_db_model(); + check_key_type(&model, &key)?; let result = self.internal.get_by_primary_key(model, key)?; if let Some(value) = result { Ok(Some(value.inner()?)) @@ -125,6 +130,7 @@ impl RwGet<'_, '_> { key_def: impl ToKeyDefinition, key: impl ToKey, ) -> Result> { + check_key_type_from_key_definition(&key_def.key_definition(), &key)?; let model = T::native_db_model(); let result = self.internal.get_by_secondary_key(model, key_def, key)?; if let Some(value) = result { diff --git a/src/transaction/query/scan/mod.rs b/src/transaction/query/scan/mod.rs index 83cebe6..4bab98b 100644 --- a/src/transaction/query/scan/mod.rs +++ b/src/transaction/query/scan/mod.rs @@ -48,7 +48,7 @@ impl<'txn> RScan<'_, 'txn> { let primary_table = self.internal.get_primary_table(&model)?; let secondary_key = key_def.key_definition(); let secondary_table = self.internal.get_secondary_table(&model, &secondary_key)?; - let out = SecondaryScan::new(primary_table, secondary_table); + let out = SecondaryScan::new(primary_table, secondary_table, key_def); Ok(out) } } @@ -90,7 +90,7 @@ where let primary_table = self.internal.get_primary_table(&model)?; let secondary_key = key_def.key_definition(); let secondary_table = self.internal.get_secondary_table(&model, &secondary_key)?; - let out = SecondaryScan::new(primary_table, secondary_table); + let out = SecondaryScan::new(primary_table, secondary_table, key_def); Ok(out) } } diff --git a/src/transaction/query/scan/primary_scan.rs b/src/transaction/query/scan/primary_scan.rs index e97d230..f27ea28 100644 --- a/src/transaction/query/scan/primary_scan.rs +++ b/src/transaction/query/scan/primary_scan.rs @@ -1,4 +1,4 @@ -use crate::db_type::ToKey; +use crate::db_type::{check_key_type, check_range_key_range_bounds, ToKey}; use crate::db_type::{unwrap_item, Key, KeyRange, Result, ToInput}; use std::marker::PhantomData; use std::ops::RangeBounds; @@ -91,7 +91,9 @@ where /// Ok(()) /// } /// ``` - pub fn range>(&self, range: R) -> Result> { + pub fn range>(&self, range: R) -> Result> { + let model = T::native_db_model(); + check_range_key_range_bounds(&model, &range)?; let database_inner_key_value_range = KeyRange::new(range); let range = self .primary_table @@ -132,10 +134,9 @@ where /// Ok(()) /// } /// ``` - pub fn start_with<'a>( - &'a self, - start_with: impl ToKey + 'a, - ) -> Result> { + pub fn start_with(&self, start_with: impl ToKey) -> Result> { + let model = T::native_db_model(); + check_key_type(&model, &start_with)?; let start_with = start_with.to_key(); let range = self.primary_table.range::(start_with.clone()..)?; diff --git a/src/transaction/query/scan/secondary_scan.rs b/src/transaction/query/scan/secondary_scan.rs index faff2cf..b19659d 100644 --- a/src/transaction/query/scan/secondary_scan.rs +++ b/src/transaction/query/scan/secondary_scan.rs @@ -1,4 +1,4 @@ -use crate::db_type::ToKey; +use crate::db_type::{check_key_type_from_key_definition, check_range_key_range_bounds_from_key_definition, KeyDefinition, KeyOptions, ToKey, ToKeyDefinition}; use crate::db_type::{unwrap_item, Key, KeyRange, Result, ToInput}; use redb::{self}; use std::marker::PhantomData; @@ -12,6 +12,7 @@ where { pub(crate) primary_table: PrimaryTable, pub(crate) secondary_table: SecondaryTable, + pub(crate) key_def: KeyDefinition, pub(crate) _marker: PhantomData, } @@ -20,10 +21,11 @@ where PrimaryTable: redb::ReadableTable, SecondaryTable: redb::ReadableMultimapTable, { - pub(crate) fn new(primary_table: PrimaryTable, secondary_table: SecondaryTable) -> Self { + pub(crate) fn new(primary_table: PrimaryTable, secondary_table: SecondaryTable, key_def: impl ToKeyDefinition) -> Self { Self { primary_table, secondary_table, + key_def: key_def.key_definition(), _marker: PhantomData::default(), } } @@ -116,10 +118,11 @@ where /// Ok(()) /// } /// ``` - pub fn range>( + pub fn range>( &self, range: R, ) -> Result> { + check_range_key_range_bounds_from_key_definition(&self.key_def, &range)?; let mut primary_keys = vec![]; let database_inner_key_value_range = KeyRange::new(range); for keys in self @@ -174,10 +177,11 @@ where /// Ok(()) /// } /// ``` - pub fn start_with<'a>( - &'a self, - start_with: impl ToKey + 'a, - ) -> Result> { + pub fn start_with( + &self, + start_with: impl ToKey, + ) -> Result> { + check_key_type_from_key_definition(&self.key_def, &start_with)?; let start_with = start_with.to_key(); let mut primary_keys = vec![]; for keys in self.secondary_table.range::(start_with.clone()..)? { @@ -252,34 +256,3 @@ where } } } - -pub struct SecondaryScanIteratorStartWith<'a, PrimaryTable, T> -where - PrimaryTable: redb::ReadableTable, - T: ToInput, -{ - pub(crate) primary_table: &'a PrimaryTable, - pub(crate) primary_keys: IntoIter>, - pub(crate) _marker: PhantomData, -} - -impl<'a, PrimaryTable, T> Iterator for SecondaryScanIteratorStartWith<'a, PrimaryTable, T> -where - PrimaryTable: redb::ReadableTable, - T: ToInput, -{ - type Item = Result; - - fn next(&mut self) -> Option { - match self.primary_keys.next() { - Some(primary_key) => { - if let Ok(value) = self.primary_table.get(primary_key.value()) { - unwrap_item(value) - } else { - None - } - } - _ => None, - } - } -} diff --git a/src/watch/query/get.rs b/src/watch/query/get.rs index 5cd54f2..a9518ab 100644 --- a/src/watch/query/get.rs +++ b/src/watch/query/get.rs @@ -1,4 +1,4 @@ -use crate::db_type::{KeyOptions, Result, ToInput, ToKey, ToKeyDefinition}; +use crate::db_type::{check_key_type, check_key_type_from_key_definition, KeyOptions, Result, ToInput, ToKey, ToKeyDefinition}; use crate::watch; use crate::watch::query::internal; use crate::watch::MpscReceiver; @@ -42,6 +42,8 @@ impl WatchGet<'_, '_> { &self, key: impl ToKey, ) -> Result<(MpscReceiver, u64)> { + let model = T::native_db_model(); + check_key_type(&model, &key)?; self.internal.watch_primary::(key) } @@ -81,6 +83,7 @@ impl WatchGet<'_, '_> { key_def: impl ToKeyDefinition, key: impl ToKey, ) -> Result<(MpscReceiver, u64)> { + check_key_type_from_key_definition(&key_def.key_definition(), &key)?; self.internal.watch_secondary::(&key_def, key) } } diff --git a/src/watch/query/scan.rs b/src/watch/query/scan.rs index 129bf4f..e7e186e 100644 --- a/src/watch/query/scan.rs +++ b/src/watch/query/scan.rs @@ -1,4 +1,6 @@ -use crate::db_type::{KeyDefinition, KeyOptions, Result, ToInput, ToKey, ToKeyDefinition}; +use crate::db_type::{ + check_key_type, check_key_type_from_key_definition, KeyDefinition, KeyOptions, Result, ToInput, ToKey, ToKeyDefinition +}; use crate::watch; use crate::watch::query::internal; use crate::watch::MpscReceiver; @@ -115,6 +117,8 @@ impl WatchScanPrimary<'_, '_> { &self, start_with: impl ToKey, ) -> Result<(MpscReceiver, u64)> { + let model = T::native_db_model(); + check_key_type(&model, &start_with)?; self.internal.watch_primary_start_with::(start_with) } } @@ -203,6 +207,7 @@ impl WatchScanSecondary<'_, '_> { &self, start_with: impl ToKey, ) -> Result<(MpscReceiver, u64)> { + check_key_type_from_key_definition(&self.key_def, &start_with)?; self.internal .watch_secondary_start_with::(&self.key_def, start_with) } diff --git a/tests/check_type/all.rs b/tests/check_type/all.rs new file mode 100644 index 0000000..8fd511c --- /dev/null +++ b/tests/check_type/all.rs @@ -0,0 +1,284 @@ +use itertools::Itertools; +use native_db::db_type::Result; +use native_db::*; +use native_model::{native_model, Model}; +use paste::paste; +use serde::{Deserialize, Serialize}; +use uuid::{uuid, Uuid}; + +macro_rules! create_test { + ($struct_name:ident, $id_type:ty, $id_value:expr, $expected_id_type:ty, $expected_id_value:expr, $non_expected_id_type:ty, $non_expected_id_value:expr) => { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + #[native_model(id = 1, version = 1)] + #[native_db] + #[allow(non_camel_case_types)] + struct $struct_name { + #[primary_key] + id: $id_type, + #[secondary_key(unique)] + sk: $id_type, + } + + #[test] + #[allow(non_camel_case_types)] + fn $struct_name() { + let item = $struct_name { + id: $id_value, + sk: $id_value, + }; + + let mut models = Models::new(); + models.define::<$struct_name>().unwrap(); + let db = Builder::new().create_in_memory(&models).unwrap(); + + let rw = db.rw_transaction().unwrap(); + rw.insert(item.clone()).unwrap(); + rw.commit().unwrap(); + + let r = db.r_transaction().unwrap(); + + // Get primary key + let expected_id: $expected_id_type = $expected_id_value; + let result_item = r.get().primary(expected_id).unwrap().unwrap(); + assert_eq!(item, result_item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + let result_item: Result> = r.get().primary(non_expected_id); + assert!(result_item.is_err()); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + + // Get secondary key + let expected_id: $expected_id_type = $expected_id_value; + paste! { + let result_item = r.get().secondary([<$struct_name Key>]::sk, expected_id).unwrap().unwrap(); + } + assert_eq!(item, result_item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + paste! { + let result_item: Result> = r.get().secondary([<$struct_name Key>]::sk, non_expected_id); + } + assert!(result_item.is_err()); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + + // Scan primary key range + let expected_id: $expected_id_type = $expected_id_value; + paste! { + let result_item: Vec<$struct_name> = r.scan().primary().unwrap().range(expected_id..).unwrap().try_collect().unwrap(); + } + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + paste! { + if let Err(result_item) = r.scan().primary::<$struct_name>().unwrap().range(non_expected_id..) { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("scan primary key range expected error"); + } + } + + // Scan primary key start with + let expected_id: $expected_id_type = $expected_id_value; + paste! { + let result_item: Vec<$struct_name> = r.scan().primary().unwrap().start_with(expected_id).unwrap().try_collect().unwrap(); + } + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + paste! { + if let Err(result_item) = r.scan().primary::<$struct_name>().unwrap().start_with(non_expected_id) { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("scan primary key start with expected error"); + } + } + + // Scan secondary key range + let expected_id: $expected_id_type = $expected_id_value; + paste! { + let result_item: Vec<$struct_name> = r.scan().secondary([<$struct_name Key>]::sk).unwrap().range(expected_id..).unwrap().try_collect().unwrap(); + } + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + paste! { + if let Err(result_item) = r.scan().secondary::<$struct_name>([<$struct_name Key>]::sk).unwrap().range(non_expected_id..) { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("scan secondary key range expected error"); + } + } + + // Scan secondary key start with + let expected_id: $expected_id_type = $expected_id_value; + paste! { + let result_item: Vec<$struct_name> = r.scan().secondary([<$struct_name Key>]::sk).unwrap().start_with(expected_id).unwrap().try_collect().unwrap(); + } + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id: $non_expected_id_type = $non_expected_id_value; + paste! { + if let Err(result_item) = r.scan().secondary::<$struct_name>([<$struct_name Key>]::sk).unwrap().start_with(non_expected_id) { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("scan secondary key start with expected error"); + } + } + } + }; +} + +create_test!(test_u8_u8, u8, 1u8, u8, 1u8, u16, 1u16); +create_test!(test_u16_u16, u16, 1u16, u16, 1u16, u32, 1u32); +create_test!(test_u32_u32, u32, 1u32, u32, 1u32, u128, 1u128); +create_test!(test_u64_u64, u64, 1u64, u64, 1u64, u128, 1u128); +create_test!(test_u128_u128, u128, 1u128, u128, 1u128, u64, 1u64); + +create_test!(test_i8_i8, i8, 1i8, i8, 1i8, i16, 1i16); +create_test!(test_i16_i16, i16, 1i16, i16, 1i16, i32, 1i32); +create_test!(test_i32_i32, i32, 1i32, i32, 1i32, i64, 1i64); +create_test!(test_i64_i64, i64, 1i64, i64, 1i64, i128, 1i128); +create_test!(test_i128_i128, i128, 1i128, i128, 1i128, i64, 1i64); + +create_test!(test_f32_f32, f32, 1.0f32, f32, 1.0f32, f64, 1.0f64); +create_test!(test_f64_f64, f64, 1.0f64, f64, 1.0f64, f32, 1.0f32); + +create_test!(test_char_char, char, 'a', char, 'a', u8, 97u8); + +// Tests for String Types + +create_test!( + test_string_string, + String, + "test".to_string(), + String, + "test".to_string(), + u128, + 1u128 +); + +create_test!( + test_string_str, + String, + "test".to_string(), + &str, + "test", + u128, + 1u128 +); + +// Tests for Compound Types + +create_test!( + test_option_u32_option_u32, + Option, + Some(1u32), + Option, + Some(1u32), + Option, + Some(1u64) +); + +create_test!( + test_array_u32_u32, + Vec, + vec![1, 2, 3], + &'static [u32], + &[1, 2, 3], + &'static [u64], + &[1, 2, 3] +); + +create_test!( + test_vecu8_vecu8, + Vec, + vec![1, 2, 3], + Vec, + vec![1, 2, 3], + u128, + 1u128 +); + +create_test!( + test_tuple_u32_string, + (u32, String), + (1u32, "test".to_string()), + (u32, String), + (1u32, "test".to_string()), + (u32, u32), + (1u32, 2u32) +); + +// Tests for Custom Types + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)] +struct MyUuid(uuid::Uuid); + +impl ToKey for MyUuid { + fn to_key(&self) -> Key { + Key::new(self.0.as_bytes().to_vec()) + } + + fn key_names() -> Vec { + vec!["MyUuid".to_string()] + } +} + +const UUID: Uuid = uuid!("00000000-0000-0000-0000-000000000000"); + +create_test!( + test_myuuid_myuuid, + MyUuid, + MyUuid(UUID), + MyUuid, + MyUuid(UUID), + u128, + 1u128 +); + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)] +enum PkEnum { + A, + B, + C, +} + +impl ToKey for PkEnum { + fn to_key(&self) -> Key { + match self { + PkEnum::A => Key::new(vec![0]), + PkEnum::B => Key::new(vec![1]), + PkEnum::C => Key::new(vec![2]), + } + } + + fn key_names() -> Vec { + vec!["PkEnum".to_string()] + } +} + +create_test!( + test_pkenum_pkenum, + PkEnum, + PkEnum::A, + PkEnum, + PkEnum::A, + u128, + 1u128 +); diff --git a/tests/check_type/mod.rs b/tests/check_type/mod.rs new file mode 100644 index 0000000..05c3d73 --- /dev/null +++ b/tests/check_type/mod.rs @@ -0,0 +1,3 @@ +mod all; +mod struct_custom; +mod struct_simple; \ No newline at end of file diff --git a/tests/check_type/struct_custom.rs b/tests/check_type/struct_custom.rs new file mode 100644 index 0000000..bfb06aa --- /dev/null +++ b/tests/check_type/struct_custom.rs @@ -0,0 +1,94 @@ +use native_db::*; +use native_model::{native_model, Model}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +#[native_model(id = 1, version = 1)] +#[native_db( + primary_key(custom_id -> u32), + // secondary_key(custom_sk -> Vec), + // secondary_key(custom_sk_o -> Option>, optional), + // secondary_key(custom_sk_u -> Vec, unique), + // secondary_key(custom_sk_o_u -> Option>, unique, optional), + secondary_key(custom_sk_no_u -> Option>, unique), +)] +struct ItemCustomPk { + id: u32, + all_sk: Vec, +} + +impl ItemCustomPk { + fn custom_id(&self) -> u32 { + self.id + } + + // fn custom_sk(&self) -> Vec { + // self.all_sk.clone() + // } + + // fn custom_sk_u(&self) -> Vec { + // self.all_sk.clone() + // } + + // fn custom_sk_o(&self) -> Option> { + // Some(self.all_sk.clone()) + // } + + // fn custom_sk_o_u(&self) -> Option> { + // Some(self.all_sk.clone()) + // } + + fn custom_sk_no_u(&self) -> Option> { + Some(self.all_sk.clone()) + } +} + +#[test] +fn test_get_primary_key_custom_pk() { + let item = ItemCustomPk { + id: 1, + all_sk: vec!["1".to_string()], + }; + + let mut models = Models::new(); + models.define::().unwrap(); + let db = Builder::new().create_in_memory(&models).unwrap(); + + let rw = db.rw_transaction().unwrap(); + rw.insert(item.clone()).unwrap(); + rw.commit().unwrap(); + + + // // Get primary key for read transaction for unique + // let r = db.r_transaction().unwrap(); + // let id = vec!["1".to_string()]; + // let result_item = r + // .get() + // .secondary(ItemCustomPkKey::custom_sk_u, id) + // .unwrap() + // .unwrap(); + + // assert_eq!(item, result_item); + + // // Get primary key for read transaction for unique optional + // let r = db.r_transaction().unwrap(); + // let id = vec!["1".to_string()]; + // let result_item = r + // .get() + // .secondary(ItemCustomPkKey::custom_sk_o_u, id) + // .unwrap() + // .unwrap(); + + // assert_eq!(item, result_item); + + // Get primary key for read transaction for unique not optional + let r = db.r_transaction().unwrap(); + let id = Some(vec!["1".to_string()]); + let result_item = r + .get() + .secondary(ItemCustomPkKey::custom_sk_no_u, id) + .unwrap() + .unwrap(); + + assert_eq!(item, result_item); +} diff --git a/tests/check_type/struct_simple.rs b/tests/check_type/struct_simple.rs new file mode 100644 index 0000000..4d2d8d1 --- /dev/null +++ b/tests/check_type/struct_simple.rs @@ -0,0 +1,387 @@ +use std::vec; + +use itertools::Itertools; +use native_db::db_type::Result; +use native_db::*; +use native_model::{native_model, Model}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +#[native_model(id = 1, version = 1)] +#[native_db] +struct ItemCustomPk { + #[primary_key] + id: Vec, + #[secondary_key] + sk: Vec, + #[secondary_key(unique)] + sk_u: Vec, + #[secondary_key(optional)] + sk_o: Option>, + #[secondary_key(unique, optional)] + sk_u_o: Option>, +} + +#[test] +fn test_get_primary_key_custom_pk() { + let item = ItemCustomPk { + id: vec![1], + sk: vec!["test".to_string()], + sk_u: vec!["test".to_string()], + sk_o: Some(vec!["test".to_string()]), + sk_u_o: Some(vec!["test".to_string()]), + }; + + let mut models = Models::new(); + models.define::().unwrap(); + let db = Builder::new().create_in_memory(&models).unwrap(); + + let rw = db.rw_transaction().unwrap(); + rw.insert(item.clone()).unwrap(); + rw.commit().unwrap(); + + // Get primary key for read transaction + let r = db.r_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item: ItemCustomPk = r.get().primary(expected_id).unwrap().unwrap(); + assert_eq!(item, result_item); + let non_expected_id = vec![3]; + let result_item: Result> = r.get().primary(non_expected_id); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + drop(r); + + // Get primary key for write transaction + let rw = db.rw_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item = rw.get().primary(expected_id).unwrap().unwrap(); + assert_eq!(item, result_item); + let non_expected_id = vec![3]; + let result_item: Result> = rw.get().primary(non_expected_id); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + drop(rw); + + // Get secondary key for read transaction + let r = db.r_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result_item = r + .get() + .secondary(ItemCustomPkKey::sk_u, expected_id) + .unwrap() + .unwrap(); + assert_eq!(item, result_item); + let non_expected_id = vec![3]; + let result_item: Result> = + r.get().secondary(ItemCustomPkKey::sk_u, non_expected_id); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + drop(r); + + // Get secondary key for write transaction + let rw = db.rw_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result_item = rw + .get() + .secondary(ItemCustomPkKey::sk_u, expected_id) + .unwrap() + .unwrap(); + assert_eq!(item, result_item); + let non_expected_id = vec![3]; + let result_item: Result> = + rw.get().secondary(ItemCustomPkKey::sk_u, non_expected_id); + assert!(matches!( + result_item.unwrap_err(), + db_type::Error::MissmatchedKeyType { .. } + )); + drop(rw); + + // Scan primary key range for read transaction + let r = db.r_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item: Vec = r + .scan() + .primary() + .unwrap() + .range(expected_id..) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = r + .scan() + .primary::() + .unwrap() + .range(non_expected_id..) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(r); + + // Scan primary key range for write transaction + let rw = db.rw_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item: Vec = rw + .scan() + .primary() + .unwrap() + .range(expected_id..) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = rw + .scan() + .primary::() + .unwrap() + .range(non_expected_id..) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(rw); + + // Scan primary key start with for read transaction + let r = db.r_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item: Vec = r + .scan() + .primary() + .unwrap() + .start_with(expected_id) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = r + .scan() + .primary::() + .unwrap() + .start_with(non_expected_id) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(r); + + // Scan primary key start with for write transaction + let rw = db.rw_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result_item: Vec = rw + .scan() + .primary() + .unwrap() + .start_with(expected_id) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = rw + .scan() + .primary::() + .unwrap() + .start_with(non_expected_id) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(rw); + + // Scan secondary key range for read transaction + let r = db.r_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result_item: Vec = r + .scan() + .secondary(ItemCustomPkKey::sk_u) + .unwrap() + .range(expected_id..) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = r + .scan() + .secondary::(ItemCustomPkKey::sk_u) + .unwrap() + .range(non_expected_id..) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(r); + + // Scan secondary key range for write transaction + let rw = db.rw_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result_item: Vec = rw + .scan() + .secondary::(ItemCustomPkKey::sk_u) + .unwrap() + .range(expected_id..) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = rw + .scan() + .secondary::(ItemCustomPkKey::sk_u) + .unwrap() + .range(non_expected_id..) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(rw); + + // Scan secondary key start with for read transaction + let r = db.r_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result_item: Vec = r + .scan() + .secondary::(ItemCustomPkKey::sk_u) + .unwrap() + .start_with(expected_id) + .unwrap() + .try_collect() + .unwrap(); + assert_eq!(result_item.len(), 1); + assert_eq!(result_item[0], item); + let non_expected_id = vec![3]; + if let Err(result_item) = r + .scan() + .secondary::(ItemCustomPkKey::sk_u) + .unwrap() + .start_with(non_expected_id) + { + assert!(matches!( + result_item, + db_type::Error::MissmatchedKeyType { .. } + )); + } else { + panic!("Expected error"); + } + drop(r); + + // Watch get primary key for read transaction + let r = db.r_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result = db.watch().get().primary::(expected_id); + assert!(result.is_ok()); + let non_expected_id = vec![3]; + if let Err(result) = db.watch().get().primary::(non_expected_id) { + assert!(matches!(result, db_type::Error::MissmatchedKeyType { .. })); + } else { + panic!("Expected error"); + } + drop(r); + + // Watch get secondary key for read transaction + let r = db.r_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result = db + .watch() + .get() + .secondary::(ItemCustomPkKey::sk_u, expected_id); + assert!(result.is_ok()); + let non_expected_id = vec![3]; + if let Err(result) = db + .watch() + .get() + .secondary::(ItemCustomPkKey::sk_u, non_expected_id) + { + assert!(matches!(result, db_type::Error::MissmatchedKeyType { .. })); + } else { + panic!("Expected error"); + } + drop(r); + + // Watch scan primary key start with for read transaction + let r = db.r_transaction().unwrap(); + let expected_id: Vec = vec![1]; + let result = db + .watch() + .scan() + .primary() + .start_with::(expected_id); + assert!(result.is_ok()); + let non_expected_id = vec![3]; + if let Err(result) = db + .watch() + .scan() + .primary() + .start_with::(non_expected_id) + { + assert!(matches!(result, db_type::Error::MissmatchedKeyType { .. })); + } else { + panic!("Expected error"); + } + drop(r); + + // Watch scan secondary key start with for read transaction + let r = db.r_transaction().unwrap(); + let expected_id = vec!["test".to_string()]; + let result = db + .watch() + .scan() + .secondary(ItemCustomPkKey::sk_u) + .start_with::(expected_id); + assert!(result.is_ok()); + let non_expected_id = vec![3]; + if let Err(result) = db + .watch() + .scan() + .secondary(ItemCustomPkKey::sk_u) + .start_with::(non_expected_id) + { + assert!(matches!(result, db_type::Error::MissmatchedKeyType { .. })); + } else { + panic!("Expected error"); + } + drop(r); + + // TODO: watch scan primary key range, because it's not implemented + // TODO: watch scan secondary key range, because it's not implemented +} diff --git a/tests/custom_type/custom.rs b/tests/custom_type/custom.rs index c39dbcc..9f0a0ff 100644 --- a/tests/custom_type/custom.rs +++ b/tests/custom_type/custom.rs @@ -9,10 +9,14 @@ use shortcut_assert_fs::TmpFs; #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] struct City(String); -impl ToKey for &City { +impl ToKey for City { fn to_key(&self) -> Key { Key::new(self.0.as_bytes().to_vec()) } + + fn key_names() -> Vec { + vec!["City".to_string()] + } } // Test genrate fields: @@ -52,7 +56,7 @@ fn insert_item_fields() { let r = db.r_transaction().unwrap(); let result_item = r .get() - .secondary(ItemFieldsKey::city2, &item.city2) + .secondary(ItemFieldsKey::city2, item.city2.clone()) .unwrap() .unwrap(); assert_eq!(item, result_item); @@ -64,10 +68,10 @@ fn insert_item_fields() { #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] #[native_model(id = 1, version = 1)] #[native_db( - primary_key(m_city1), - secondary_key(m_city2, unique), - secondary_key(m_city2_ref, unique), - secondary_key(m_city3, optional) + primary_key(m_city1 -> City), + secondary_key(m_city2 -> City, unique), + secondary_key(m_city2_ref -> City, unique), + secondary_key(m_city3 -> Option, optional), )] struct ItemFunctions { city1: City, @@ -115,7 +119,7 @@ fn test_item_functions() { let r = db.r_transaction().unwrap(); let result_item = r .get() - .secondary(ItemFunctionsKey::m_city2, &item.city2) + .secondary(ItemFunctionsKey::m_city2, item.city2.clone()) .unwrap() .unwrap(); assert_eq!(item, result_item); diff --git a/tests/macro_def/primary_key.rs b/tests/macro_def/primary_key.rs index 78f70cf..e50c020 100644 --- a/tests/macro_def/primary_key.rs +++ b/tests/macro_def/primary_key.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(compute_primary_key))] +#[native_db(primary_key(compute_primary_key -> String))] struct Item { id: u32, name: String, diff --git a/tests/macro_def/secondary_key.rs b/tests/macro_def/secondary_key.rs index d89b213..5bfa067 100644 --- a/tests/macro_def/secondary_key.rs +++ b/tests/macro_def/secondary_key.rs @@ -6,7 +6,10 @@ use std::collections::HashMap; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(compute_primary_key), secondary_key(compute_secondary_key))] +#[native_db( + primary_key(compute_primary_key -> String), + secondary_key(compute_secondary_key -> String), +)] struct ItemSecondary { id: u32, name: String, @@ -39,6 +42,7 @@ fn test_secondary() { 1, 1, "compute_secondary_key", + vec!["String".to_string()], Default::default() )) .unwrap(), @@ -49,8 +53,8 @@ fn test_secondary() { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 2, version = 1)] #[native_db( - primary_key(compute_primary_key), - secondary_key(compute_secondary_key, unique) + primary_key(compute_primary_key -> String), + secondary_key(compute_secondary_key -> String, unique) )] struct ItemSecondaryUnique { id: u32, @@ -84,6 +88,7 @@ fn test_secondary_unique() { 2, 1, "compute_secondary_key", + vec!["String".to_string()], Default::default() )) .unwrap(), @@ -94,8 +99,8 @@ fn test_secondary_unique() { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 2, version = 1)] #[native_db( - primary_key(compute_primary_key), - secondary_key(compute_secondary_key, optional) + primary_key(compute_primary_key -> String), + secondary_key(compute_secondary_key -> Option, optional) )] struct ItemSecondaryOptional { id: u32, @@ -133,6 +138,7 @@ fn test_secondary_optional() { 2, 1, "compute_secondary_key", + vec!["Option".to_string()], Default::default() )) .unwrap(), @@ -148,6 +154,7 @@ fn test_secondary_optional() { 2, 1, "compute_secondary_key", + vec!["Option".to_string()], Default::default() )) .unwrap(), diff --git a/tests/macro_def/secondary_key_attribute.rs b/tests/macro_def/secondary_key_attribute.rs index ff22ff9..7b201e7 100644 --- a/tests/macro_def/secondary_key_attribute.rs +++ b/tests/macro_def/secondary_key_attribute.rs @@ -28,7 +28,7 @@ fn test_secondary() { assert_eq!(secondary_key.len(), 1); assert_eq!( secondary_key - .get(&KeyDefinition::new(1, 1, "name", Default::default())) + .get(&KeyDefinition::new(1, 1, "name", vec!["u32".to_string()], Default::default())) .unwrap(), &KeyEntry::Default("test".to_key()) ); @@ -58,7 +58,7 @@ fn test_secondary_optional() { assert_eq!(secondary_key.len(), 1); assert_eq!( secondary_key - .get(&KeyDefinition::new(2, 1, "name", Default::default())) + .get(&KeyDefinition::new(2, 1, "name", vec!["Option".to_string()], Default::default())) .unwrap(), &KeyEntry::Optional(Some("test".to_key())) ); @@ -68,7 +68,7 @@ fn test_secondary_optional() { assert_eq!(secondary_key.len(), 1); assert_eq!( secondary_key - .get(&KeyDefinition::new(2, 1, "name", Default::default())) + .get(&KeyDefinition::new(2, 1, "name", vec!["Option".to_string()], Default::default())) .unwrap(), &KeyEntry::Optional(None) ); @@ -98,7 +98,7 @@ fn test_secondary_unique() { assert_eq!(secondary_key.len(), 1); assert_eq!( secondary_key - .get(&KeyDefinition::new(3, 1, "name", Default::default())) + .get(&KeyDefinition::new(3, 1, "name", vec!["u32".to_string()], Default::default())) .unwrap(), &KeyEntry::Default("test".to_key()) ); @@ -131,13 +131,13 @@ fn test_secondary_others() { assert_eq!(secondary_key.len(), 2); assert_eq!( secondary_key - .get(&KeyDefinition::new(4, 1, "name", Default::default())) + .get(&KeyDefinition::new(4, 1, "name", vec!["String".to_string()], Default::default())) .unwrap(), &KeyEntry::Default("test".to_key()) ); assert_eq!( secondary_key - .get(&KeyDefinition::new(4, 1, "name2", Default::default())) + .get(&KeyDefinition::new(4, 1, "name2", vec!["String".to_string()], Default::default())) .unwrap(), &KeyEntry::Default("test2".to_key()) ); diff --git a/tests/macro_def/secondary_key_mix.rs b/tests/macro_def/secondary_key_mix.rs index f918a3a..cf66d35 100644 --- a/tests/macro_def/secondary_key_mix.rs +++ b/tests/macro_def/secondary_key_mix.rs @@ -4,10 +4,14 @@ use native_db::*; use native_model::{native_model, Model}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::vec; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(compute_primary_key), secondary_key(compute_secondary_key))] +#[native_db( + primary_key(compute_primary_key -> String), + secondary_key(compute_secondary_key -> String), +)] struct ItemSecondaryMix { id: u32, #[secondary_key(unique)] @@ -40,6 +44,7 @@ fn test_secondary() { 1, 1, "compute_secondary_key", + vec!["String".to_string()], Default::default() )), Some(&KeyEntry::Default("test-1".to_key())) @@ -47,7 +52,7 @@ fn test_secondary() { assert_eq!( secondary_key - .get(&KeyDefinition::new(1, 1, "name", Default::default())) + .get(&KeyDefinition::new(1, 1, "name", vec!["String".to_string()], Default::default())) .unwrap(), &KeyEntry::Default("test".to_key()) ); diff --git a/tests/migrate/only_primary_key.rs b/tests/migrate/only_primary_key.rs index f494c73..93a5c92 100644 --- a/tests/migrate/only_primary_key.rs +++ b/tests/migrate/only_primary_key.rs @@ -5,7 +5,9 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(generate_my_primary_key))] +#[native_db( + primary_key(generate_my_primary_key -> String), +)] struct ItemV1 { id: u32, name: String, @@ -20,7 +22,9 @@ impl ItemV1 { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 2, from = ItemV1)] -#[native_db(primary_key(generate_my_primary_key))] +#[native_db( + primary_key(generate_my_primary_key -> String), +)] struct ItemV2 { id: u64, name: String, diff --git a/tests/migrate/with_multiple_versions.rs b/tests/migrate/with_multiple_versions.rs index 47d5a91..470e710 100644 --- a/tests/migrate/with_multiple_versions.rs +++ b/tests/migrate/with_multiple_versions.rs @@ -86,7 +86,7 @@ fn test_skip_version() { let r_txn = db.r_transaction().unwrap(); - let item: ItemV1 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV1 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV1 { @@ -110,7 +110,7 @@ fn test_skip_version() { rw.commit().unwrap(); let r_txn = db.r_transaction().unwrap(); - let item: ItemV3 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV3 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV3 { @@ -156,7 +156,7 @@ fn test_skip_version_with_data_should_fail() { rw_txn.commit().unwrap(); let r_txn = db.r_transaction().unwrap(); - let item: ItemV1 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV1 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV1 { @@ -165,7 +165,7 @@ fn test_skip_version_with_data_should_fail() { } ); - let item: ItemV2 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV2 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV2 { diff --git a/tests/migrate/with_other_model.rs b/tests/migrate/with_other_model.rs index 1d87159..134ef05 100644 --- a/tests/migrate/with_other_model.rs +++ b/tests/migrate/with_other_model.rs @@ -77,7 +77,7 @@ fn test_migrate() { let r_txn = db.r_transaction().unwrap(); - let item: ItemV1 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV1 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV1 { @@ -101,7 +101,7 @@ fn test_migrate() { rw.commit().unwrap(); let r_txn = db.r_transaction().unwrap(); - let item: ItemV2 = r_txn.get().primary(1).unwrap().unwrap(); + let item: ItemV2 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, ItemV2 { @@ -110,7 +110,7 @@ fn test_migrate() { } ); - let item: Item2 = r_txn.get().primary(1).unwrap().unwrap(); + let item: Item2 = r_txn.get().primary(1u32).unwrap().unwrap(); assert_eq!( item, Item2 { diff --git a/tests/migrate/with_secondary_keys.rs b/tests/migrate/with_secondary_keys.rs index b5dad99..a67f7d6 100644 --- a/tests/migrate/with_secondary_keys.rs +++ b/tests/migrate/with_secondary_keys.rs @@ -8,7 +8,9 @@ use std::convert::TryInto; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(id_key), secondary_key(name_key))] +#[native_db( + primary_key(id_key -> u32), + secondary_key(name_key -> String))] struct ItemV1 { id: u32, name: String, @@ -35,9 +37,9 @@ impl ItemV1 { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 1, version = 2, from = ItemV1)] #[native_db( - primary_key(id_key), - secondary_key(first_name_key, unique), - secondary_key(last_name_key, unique) + primary_key(id_key -> u64), + secondary_key(first_name_key -> String, unique), + secondary_key(last_name_key -> String, unique) )] struct ItemV2 { id: u64, diff --git a/tests/modules.rs b/tests/modules.rs index 0dd9f19..7dce39f 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,3 +1,4 @@ +mod check_type; mod custom_type; mod macro_def; diff --git a/tests/native_model.rs b/tests/native_model.rs index 8f7fb94..18f0158 100644 --- a/tests/native_model.rs +++ b/tests/native_model.rs @@ -22,7 +22,7 @@ impl native_model::Decode for Bincode { #[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, Debug)] #[native_model(id = 1, version = 1, with = Bincode)] -#[native_db(primary_key(compute_primary_key))] +#[native_db(primary_key(compute_primary_key -> Vec))] struct ItemV1 { id: u32, name: String, diff --git a/tests/primary_drain/only_primary_key.rs b/tests/primary_drain/only_primary_key.rs index 5437ef7..1ee1c85 100644 --- a/tests/primary_drain/only_primary_key.rs +++ b/tests/primary_drain/only_primary_key.rs @@ -5,7 +5,9 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(generate_my_primary_key))] +#[native_db( + primary_key(generate_my_primary_key -> Vec) +)] struct Item { id: u32, name: String, diff --git a/tests/primary_drain/with_secondary_keys.rs b/tests/primary_drain/with_secondary_keys.rs index d308ef8..818fa33 100644 --- a/tests/primary_drain/with_secondary_keys.rs +++ b/tests/primary_drain/with_secondary_keys.rs @@ -6,8 +6,8 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 1, version = 1)] #[native_db( - primary_key(generate_my_primary_key), - secondary_key(generate_my_secondary_key, unique) + primary_key(generate_my_primary_key -> u32), + secondary_key(generate_my_secondary_key -> String, unique) )] struct Item { id: u32, diff --git a/tests/query/insert_get_sk.rs b/tests/query/insert_get_sk.rs index 5f2b6dc..ef4209b 100644 --- a/tests/query/insert_get_sk.rs +++ b/tests/query/insert_get_sk.rs @@ -5,7 +5,10 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(pk), secondary_key(gk_1, unique))] +#[native_db( + primary_key(pk -> String), + secondary_key(gk_1 -> String, unique) +)] struct Item { id: u32, name: String, @@ -156,7 +159,7 @@ fn test_insert_optional() { let r = db.r_transaction().unwrap(); let result_item = r .get() - .secondary(ItemOptionalKey::name, "test") + .secondary(ItemOptionalKey::name, Some("test")) .unwrap() .unwrap(); assert_eq!(item_1, result_item); diff --git a/tests/query/upsert_get_sk.rs b/tests/query/upsert_get_sk.rs index 7496b2f..32d141a 100644 --- a/tests/query/upsert_get_sk.rs +++ b/tests/query/upsert_get_sk.rs @@ -5,7 +5,10 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] #[native_model(id = 1, version = 1)] -#[native_db(primary_key(pk), secondary_key(gk_1, unique))] +#[native_db( + primary_key(pk -> String), + secondary_key(gk_1 -> String, unique) +)] struct Item { id: u32, name: String, @@ -156,7 +159,7 @@ fn test_upsert_optional() { let r = db.r_transaction().unwrap(); let result_item = r .get() - .secondary(ItemOptionalKey::name, "test") + .secondary(ItemOptionalKey::name, Some("test")) .unwrap() .unwrap(); assert_eq!(item_1, result_item); diff --git a/tests/scan.rs b/tests/scan.rs index 85119fc..0ac01fc 100644 --- a/tests/scan.rs +++ b/tests/scan.rs @@ -9,9 +9,9 @@ use shortcut_assert_fs::TmpFs; #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 1, version = 1)] #[native_db( - primary_key(generate_my_primary_key), - secondary_key(secondary_key_1, unique), - secondary_key(secondary_key_2, unique) + primary_key(generate_my_primary_key -> u32), + secondary_key(secondary_key_1 -> String, unique), + secondary_key(secondary_key_2 -> String, unique) )] struct Item { id: u32, @@ -169,7 +169,7 @@ fn test_iter_range() { .scan() .primary() .unwrap() - .range(..2_i32) + .range(..2u32) .unwrap() .try_collect() .unwrap(); @@ -183,7 +183,7 @@ fn test_iter_range() { .scan() .primary() .unwrap() - .range(2_i32..) + .range(2u32..) .unwrap() .try_collect() .unwrap(); @@ -201,7 +201,7 @@ fn test_iter_range() { .scan() .primary() .unwrap() - .range(2_i32..3_i32) + .range(2u32..3u32) .unwrap() .try_collect() .unwrap(); @@ -297,7 +297,7 @@ fn test_double_ended_iter_by_key_range() { let r = db.r_transaction().unwrap(); let scan = r.scan().secondary(ItemKey::secondary_key_1).unwrap(); - let iter = scan.range(..b"2".as_slice()).unwrap(); + let iter = scan.range(.."2").unwrap(); let result: Vec = iter.rev().try_collect().unwrap(); assert_eq!(result.len(), 1); @@ -307,7 +307,7 @@ fn test_double_ended_iter_by_key_range() { assert_eq!(obj1.name, "test"); let scan = r.scan().secondary(ItemKey::secondary_key_1).unwrap(); - let iter = scan.range(b"2".as_slice()..).unwrap(); + let iter = scan.range("2"..).unwrap(); let result: Vec = iter.rev().try_collect().unwrap(); assert_eq!(result.len(), 2); @@ -320,7 +320,7 @@ fn test_double_ended_iter_by_key_range() { assert_eq!(obj2.name, "test2"); let scan = r.scan().secondary(ItemKey::secondary_key_1).unwrap(); - let iter = scan.range(b"2".as_slice()..b"3".as_slice()).unwrap(); + let iter = scan.range("2".."3").unwrap(); let result: Vec = iter.rev().try_collect().unwrap(); assert_eq!(result.len(), 1); @@ -332,7 +332,7 @@ fn test_double_ended_iter_by_key_range() { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 2, version = 1)] -#[native_db(primary_key(generate_my_primary_key))] +#[native_db(primary_key(generate_my_primary_key -> String))] struct ItemFlag { name: String, } @@ -400,7 +400,7 @@ fn test_start_with_scenario() { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[native_model(id = 3, version = 1)] -#[native_db(primary_key(generate_my_primary_key), secondary_key(flag, unique))] +#[native_db(primary_key(generate_my_primary_key -> String), secondary_key(flag -> String, unique))] struct ItemIdFlag { id: String, flag: String, @@ -577,7 +577,7 @@ fn test_txn_write_iter_range() { .scan() .primary() .unwrap() - .range(..2_i32.to_be_bytes().as_slice()) + .range(..2u32) .unwrap() .try_collect() .unwrap(); @@ -591,7 +591,7 @@ fn test_txn_write_iter_range() { .scan() .primary() .unwrap() - .range(2_i32.to_be_bytes().as_slice()..) + .range(2u32..) .unwrap() .try_collect() .unwrap(); @@ -609,7 +609,7 @@ fn test_txn_write_iter_range() { .scan() .primary() .unwrap() - .range(2_i32.to_be_bytes().as_slice()..3_i32.to_be_bytes().as_slice()) + .range(2u32..3u32) .unwrap() .try_collect() .unwrap(); @@ -722,7 +722,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::nr) .unwrap() - .range(0..10) + .range(0u32..10u32) .unwrap() .collect::, _>>() .unwrap() @@ -735,7 +735,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::nr) .unwrap() - .range(2..3) + .range(2u32..3u32) .unwrap() .collect::, _>>() .unwrap() @@ -748,7 +748,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::unique_nr) .unwrap() - .range(1..3) + .range(1u32..3u32) .unwrap() .collect::, _>>() .unwrap() @@ -761,7 +761,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::unique_nr) .unwrap() - .range(1..=3) + .range(1u32..=3u32) .unwrap() .collect::, _>>() .unwrap() @@ -774,7 +774,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::unique_nr) .unwrap() - .range(3..=3) + .range(3u32..=3u32) .unwrap() .collect::, _>>() .unwrap() @@ -787,7 +787,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::nr) .unwrap() - .range(2..=3) + .range(2u32..=3u32) .unwrap() .collect::, _>>() .unwrap() @@ -800,7 +800,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::nr) .unwrap() - .range(2..=2) + .range(2u32..=2u32) .unwrap() .collect::, _>>() .unwrap() @@ -813,7 +813,7 @@ fn test_scan_range() { .scan() .secondary(ItemScanRangeKey::nr) .unwrap() - .range(0..=2) + .range(0u32..=2u32) .unwrap() .collect::, _>>() .unwrap() diff --git a/tests/watch/mod.rs b/tests/watch/mod.rs index f994bcb..4f161c4 100644 --- a/tests/watch/mod.rs +++ b/tests/watch/mod.rs @@ -216,7 +216,7 @@ fn watch_one_secondary_key() { let (recv, _) = db .watch() .get() - .secondary::(ItemA1KKey::name, &a.name) + .secondary::(ItemA1KKey::name, a.name.clone()) .unwrap(); let rw = db.rw_transaction().unwrap(); diff --git a/tests/watch/watch_optional.rs b/tests/watch/watch_optional.rs index 72636d9..005381e 100644 --- a/tests/watch/watch_optional.rs +++ b/tests/watch/watch_optional.rs @@ -32,7 +32,7 @@ fn watch_one_secondary_key_some() { let (recv, _) = db .watch() .get() - .secondary::(ItemAOptionalKey::name, "a") + .secondary::(ItemAOptionalKey::name, Some("a")) .unwrap(); let rw = db.rw_transaction().unwrap(); @@ -66,7 +66,7 @@ fn watch_one_secondary_key_none() { let (recv, _) = db .watch() .get() - .secondary::(ItemAOptionalKey::name, "a") + .secondary::(ItemAOptionalKey::name, Some("a")) .unwrap(); let rw = db.rw_transaction().unwrap(); @@ -111,7 +111,7 @@ fn watch_start_with_by_key() { .watch() .scan() .secondary(ItemAOptionalKey::name) - .start_with::("a") + .start_with::(Some("a")) .unwrap(); let rw = db.rw_transaction().unwrap();