Skip to content

Commit

Permalink
feat: support type checking for queries
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-herlemont committed Oct 5, 2024
1 parent af48118 commit f3523f9
Show file tree
Hide file tree
Showing 40 changed files with 1,729 additions and 234 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
60 changes: 53 additions & 7 deletions native_db_macro/src/keys.rs
Original file line number Diff line number Diff line change
@@ -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<O: ToTokenStream> {
pub(super) struct_name: StructName,
field_name: Option<Ident>,
function_name: Option<Ident>,
pub(crate) field_type: Option<String>,
pub(crate) options: O,
}

Expand All @@ -31,8 +34,43 @@ impl<O: ToTokenStream> ToTokenStream for KeyDefinition<O> {
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<u32>";
// 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
)
}
}
}
Expand Down Expand Up @@ -96,11 +134,17 @@ impl<O: ToTokenStream> KeyDefinition<O> {
}
}

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,
}
}
Expand All @@ -117,6 +161,7 @@ impl<O: ToTokenStream> KeyDefinition<O> {
struct_name: table_name,
field_name: None,
function_name: None,
field_type: None,
options: O::default(),
}
}
Expand All @@ -139,7 +184,8 @@ impl<O: ToTokenStream> KeyDefinition<O> {
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()
// }
}
88 changes: 55 additions & 33 deletions native_db_macro/src/model_attributes.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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::<syn::Token![->]>()?;

// 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<KeyOptions> =
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::<syn::Token![->]>()?;

// 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::<syn::Token![,]>()?;
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!(
Expand All @@ -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| {
Expand All @@ -100,6 +121,7 @@ impl ModelAttributes {
.ident
.clone()
.expect("Parsed field expected to have an ident for secondary_key"),
field_type,
secondary_options,
));
}
Expand Down
1 change: 1 addition & 0 deletions native_db_macro/src/native_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),*
}
Expand Down
2 changes: 1 addition & 1 deletion native_db_macro/src/struct_name.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::Ident;

#[derive(Clone)]
#[derive(Clone, Debug)]
pub(crate) struct StructName(Ident);

impl StructName {
Expand Down
8 changes: 8 additions & 0 deletions src/db_type/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
got_types: Vec<String>,
operation: String,
},

#[error("Watch event error")]
WatchEventError(#[from] watch::WatchEventError),

Expand Down
Loading

0 comments on commit f3523f9

Please sign in to comment.