Repository: Brendonovich/prisma-client-rust (language: rust)

Issue:
`not` field function is not `Option<T>` for optional fields



Discussion / hints:

Fix patch:
diff --git a/cli/src/generator/models/mod.rs b/cli/src/generator/models/mod.rs
index c2189bf1..d2f28de1 100644
--- a/cli/src/generator/models/mod.rs
+++ b/cli/src/generator/models/mod.rs
@@ -8,6 +8,7 @@ mod actions;
 mod create;
 mod include;
 
+use datamodel::dml::FieldArity;
 use prisma_client_rust_sdk::prelude::*;
 
 use std::ops::Deref;
@@ -151,7 +152,7 @@ impl WhereParams {
         self.to_serialized_where.push(match_arm);
     }
 
-    pub fn add_unique_variant(&mut self, field: &dml::Field) {
+    pub fn add_unique_variant(&mut self, field: &dml::Field, args: &GenerateArgs) {
         if matches!(field.arity(), dml::FieldArity::Optional) {
             panic!("add_unique_variant cannot add optional fields. Perhaps you meant add_optional_unique_variant?");
         }
@@ -164,14 +165,17 @@ impl WhereParams {
         self.unique_variants
             .push(quote!(#variant_name(#field_type)));
 
+        let read_filter = args.read_filter(&field.as_scalar_field().unwrap()).unwrap();
+        let filter_enum = format_ident!("{}Filter", &read_filter.name);
+
         self.from_unique_match_arms.push(quote! {
-            UniqueWhereParam::#variant_name(value) => Self::#variant_name(value)
+            UniqueWhereParam::#variant_name(value) => Self::#field_pascal(_prisma::read_filters::#filter_enum::Equals(value))
         });
     }
 
     pub fn add_optional_unique_variant(
         &mut self,
-        field: &dml::Field
+        field: &dml::Field, args: &GenerateArgs
     ) {
         if !matches!(field.arity(), dml::FieldArity::Optional) {
             panic!("add_optional_unique_variant only adds optional fields. Perhaps you meant add_unique_variant?");
@@ -187,8 +191,11 @@ impl WhereParams {
         self.unique_variants
             .push(quote!(#variant_name(#field_base_type)));
 
+        let read_filter = args.read_filter(&field.as_scalar_field().unwrap()).unwrap();
+        let filter_enum = format_ident!("{}Filter", &read_filter.name);
+
         self.from_unique_match_arms.push(quote! {
-            UniqueWhereParam::#variant_name(value) => Self::#variant_name(Some(value))
+            UniqueWhereParam::#variant_name(value) => Self::#field_pascal(_prisma::read_filters::#filter_enum::Equals(Some(value)))
         });
 
         self.from_optional_uniques.push(quote!{
@@ -196,7 +203,7 @@ impl WhereParams {
                 type Arg = Option<#field_base_type>;
                 
                 fn from_arg(arg: Self::Arg) -> Self where Self: Sized {
-                    Self::#variant_name(arg)
+                    Self::#field_pascal(_prisma::read_filters::#filter_enum::Equals(arg))
                 }
             }
             
@@ -385,8 +392,8 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                 let field = fields[0];
                 
                 match field.arity()  {
-                    dml::FieldArity::Optional => model_where_params.add_optional_unique_variant(field),
-                    _ => model_where_params.add_unique_variant(field),
+                    dml::FieldArity::Optional => model_where_params.add_optional_unique_variant(field, args),
+                    _ => model_where_params.add_unique_variant(field, args),
                 }
             } else {
                 let variant_name_string = fields.iter().map(|f| f.name().to_case(Case::Pascal)).collect::<String>();
@@ -411,7 +418,7 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                     
                     variant_data_as_args.push(quote!(#field_name_snake: #field_type));
                     variant_data_as_types.push(field_type);
-                    variant_data_as_prisma_values.push(field.type_prisma_value(&field_name_snake));
+                    variant_data_as_prisma_values.push(field.field_type().to_prisma_value(&field_name_snake, &FieldArity::Required));
                     variant_data_as_destructured.push(field_name_snake);
                 }
 
@@ -631,45 +638,6 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                     });
 
                     let equals_variant_name = format_ident!("{}Equals", &field_name_pascal);
-                    let equals_variant = quote!(#equals_variant_name(#field_type));
-                    let type_as_prisma_value= root_field.type_prisma_value(&format_ident!("value"));
-                    
-                    let type_as_prisma_value = if !root_field.arity().is_optional() {
-                        type_as_prisma_value
-                    } else {
-                        quote!(value.map(|value| #type_as_prisma_value).unwrap_or(#pcr::PrismaValue::Null))
-                    };
-                    
-                    model_where_params.add_variant(
-                        equals_variant.clone(), 
-                        quote! {
-                            Self::#equals_variant_name(value) => (
-                                #field_string,
-                                #pcr::SerializedWhereValue::Value(#type_as_prisma_value)
-                            )
-                        }
-                    );
-                    
-                    // Add equals query functions. Unique/Where enum variants are added in unique/primary key sections earlier on.
-                    field_query_module.add_method(
-                        match (model.field_is_primary(field_string), model.field_is_unique(field_string), field.arity.is_required()) {
-                            (true, _, _) | (_, true, true) => quote! {
-                                pub fn equals<T: From<UniqueWhereParam>>(value: #field_type) -> T {
-                                    UniqueWhereParam::#equals_variant_name(value).into()
-                                }
-                            },
-                            (_, true, false) => quote! {
-                                pub fn equals<A, T: #pcr::FromOptionalUniqueArg<Set, Arg = A>>(value: A) -> T {
-                                    T::from_arg(value)
-                                }
-                            },
-                            (_, _, _) => quote! {
-                                pub fn equals(value: #field_type) -> WhereParam {
-                                    WhereParam::#equals_variant_name(value).into()
-                                }
-                            }
-                        }
-                    );
 
                     // Pagination
                     field_query_module.add_method(quote! {
@@ -678,8 +646,29 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                         }
                     });
 
-                    if let Some(read_type) = args.read_filter(&field) {
-                        let filter_enum = format_ident!("{}Filter", &read_type.name);
+                    if let Some(read_filter) = args.read_filter(&field) {
+                        let filter_enum = format_ident!("{}Filter", &read_filter.name);
+                    
+                        // Add equals query functions. Unique/Where enum variants are added in unique/primary key sections earlier on.
+                        field_query_module.add_method(
+                            match (model.field_is_primary(field_string), model.field_is_unique(field_string), field.arity.is_required()) {
+                                (true, _, _) | (_, true, true) => quote! {
+                                    pub fn equals<T: From<UniqueWhereParam>>(value: #field_type) -> T {
+                                        UniqueWhereParam::#equals_variant_name(value).into()
+                                    }
+                                },
+                                (_, true, false) => quote! {
+                                    pub fn equals<A, T: #pcr::FromOptionalUniqueArg<Set, Arg = A>>(value: A) -> T {
+                                        T::from_arg(value)
+                                    }
+                                },
+                                (_, _, _) => quote! {
+                                    pub fn equals(value: #field_type) -> WhereParam {
+                                        WhereParam::#field_name_pascal(_prisma::read_filters::#filter_enum::Equals(value))
+                                    }
+                                }
+                            }
+                        );
 
                         model_where_params.add_variant(
                             quote!(#field_name_pascal(_prisma::read_filters::#filter_enum)),
@@ -691,7 +680,9 @@ pub fn generate(args: &GenerateArgs, module_path: TokenStream) -> Vec<TokenStrea
                             },
                         );
 
-                        for method in &read_type.methods {
+                        for method in &read_filter.methods {
+                            if method.name == "Equals" { continue; }
+
                             let method_name_snake = format_ident!("{}", method.name.to_case(Case::Snake));
                             let method_name_pascal =
                                 format_ident!("{}", method.name.to_case(Case::Pascal));
diff --git a/cli/src/generator/models/set_params.rs b/cli/src/generator/models/set_params.rs
index f49a6dfc..4d35e8e0 100644
--- a/cli/src/generator/models/set_params.rs
+++ b/cli/src/generator/models/set_params.rs
@@ -57,11 +57,6 @@ fn field_set_params(field: &dml::Field, args: &GenerateArgs) -> Vec<SetParam> {
             let field_type = field.type_tokens(quote!());
 
             let converter = field.type_prisma_value(&format_ident!("value"));
-            let converter = field
-                .arity()
-                .is_optional()
-                .then(|| quote!(value.map(|value| #converter).unwrap_or(#pcr::PrismaValue::Null)))
-                .unwrap_or_else(|| converter);
 
             let set_variant_name = format_ident!("Set{}", &field_name_pascal);
 
@@ -81,7 +76,7 @@ fn field_set_params(field: &dml::Field, args: &GenerateArgs) -> Vec<SetParam> {
                 for method in &write_type.methods {
                     let typ = method.type_tokens(quote!());
                     
-                    let prisma_value_converter = method.base_type.to_prisma_value(&format_ident!("value"), method.is_list);
+                    let prisma_value_converter = method.base_type.to_prisma_value(&format_ident!("value"), &method.arity());
 
                     let variant_name = format_ident!("{}{}", pascal_ident(&method.name), field_name_pascal);
                     
diff --git a/cli/src/generator/read_filters.rs b/cli/src/generator/read_filters.rs
index 263565b0..ba5cceb8 100644
--- a/cli/src/generator/read_filters.rs
+++ b/cli/src/generator/read_filters.rs
@@ -10,10 +10,10 @@ pub fn generate_module(args: &GenerateArgs, prefix: TokenStream) -> TokenStream
 
             let value_as_prisma_value = method
                 .base_type
-                .to_prisma_value(&format_ident!("value"), method.is_list);
+                .to_prisma_value(&format_ident!("value"), &method.arity());
             let typ = method.type_tokens(quote!(#prefix super::));
 
-            (
+            ( 
                 quote!(#variant_name(#typ)),
                 quote! {
                     Self::#variant_name(value) => ::prisma_client_rust::SerializedWhereValue::Object(vec![
diff --git a/sdk/src/args.rs b/sdk/src/args.rs
index a8c96a99..47f6b4af 100644
--- a/sdk/src/args.rs
+++ b/sdk/src/args.rs
@@ -47,19 +47,20 @@ impl GenerateArgs {
             let mut filters = vec![];
 
             for scalar in &scalars {
-                let combinations = vec![
-                    vec![
-                        scalar.to_string() + "ListFilter",
-                        scalar.to_string() + "NullableListFilter",
-                    ],
-                    vec![
-                        scalar.to_string() + "Filter",
-                        scalar.to_string() + "NullableFilter",
-                    ],
+                let scalar = match scalar.as_str() {
+                    "Boolean" => "Bool".to_string(),
+                    n => n.to_string(),
+                };
+
+                let combinations = [
+                    scalar.to_string() + "ListFilter",
+                    scalar.to_string() + "NullableListFilter",
+                    scalar.to_string() + "Filter",
+                    scalar.to_string() + "NullableFilter",
                 ];
 
                 for c in combinations {
-                    let p = match schema.find_input_type(c) {
+                    let p = match schema.find_input_type(&c) {
                         Some(p) => p,
                         None => continue,
                     };
@@ -76,6 +77,9 @@ impl GenerateArgs {
                     if p.name.contains("ListFilter") {
                         s += "List";
                     }
+                    if p.name.contains("Nullable") {
+                        s += "Nullable";
+                    }
 
                     filters.push(Filter {
                         name: s,
@@ -85,36 +89,39 @@ impl GenerateArgs {
             }
 
             for e in &dml.enums {
-                let p = match schema.find_input_type(vec![
+                let combinations = [
                     "Enum".to_string() + &e.name + "Filter",
                     "Enum".to_string() + &e.name + "NullableFilter",
-                ]) {
-                    Some(t) => t,
-                    None => continue,
-                };
+                ];
+
+                for c in combinations {
+                    let p = match schema.find_input_type(&c) {
+                        Some(t) => t,
+                        None => continue,
+                    };
 
-                let mut fields = vec![];
+                    let mut fields = vec![];
 
-                for field in &p.fields {
-                    if let Some(method) = input_field_as_method(field) {
-                        fields.push(method);
+                    for field in &p.fields {
+                        if let Some(method) = input_field_as_method(field) {
+                            fields.push(method);
+                        }
                     }
-                }
 
-                filters.push(Filter {
-                    name: e.name.clone(),
-                    methods: fields,
-                });
+                    filters.push(Filter {
+                        name: e.name.clone(),
+                        methods: fields,
+                    });
+                }
             }
 
             for i in 0..dml.models.len() {
                 let m = &dml.models[i];
-                let p = match schema
-                    .find_input_type(vec![m.name.to_string() + "OrderByRelevanceInput"])
-                {
-                    Some(p) => p,
-                    None => continue,
-                };
+                let p =
+                    match schema.find_input_type(&(m.name.to_string() + "OrderByRelevanceInput")) {
+                        Some(p) => p,
+                        None => continue,
+                    };
 
                 let mut methods = vec![];
 
@@ -145,54 +152,59 @@ impl GenerateArgs {
             let mut filters = vec![];
 
             for scalar in &scalars {
-                let p = match schema.find_input_type(vec![
+                let combinations = [
                     scalar.clone() + "FieldUpdateOperationsInput",
                     "Nullable".to_string() + &scalar + "FieldUpdateOperationsInput",
-                ]) {
-                    Some(p) => p,
-                    None => continue,
-                };
+                ];
 
-                let mut fields = vec![];
+                for c in combinations {
+                    let p = match schema.find_input_type(&c) {
+                        Some(p) => p,
+                        None => continue,
+                    };
 
-                for field in &p.fields {
-                    if field.name == "set" {
-                        continue;
-                    }
+                    let mut fields = vec![];
+
+                    for field in &p.fields {
+                        if field.name == "set" {
+                            continue;
+                        }
 
-                    if let Some((type_name, is_list)) = {
-                        let mut ret = None;
-                        for input_type in &field.input_types {
-                            match input_type.location {
-                                TypeLocation::Scalar if input_type.typ != "Null" => {
-                                    ret = Some((input_type.typ.clone(), input_type.is_list))
+                        if let Some((type_name, is_list)) = {
+                            let mut ret = None;
+                            for input_type in &field.input_types {
+                                match input_type.location {
+                                    TypeLocation::Scalar if input_type.typ != "Null" => {
+                                        ret = Some((input_type.typ.clone(), input_type.is_list))
+                                    }
+                                    _ => {}
                                 }
-                                _ => {}
                             }
+                            ret
+                        } {
+                            fields.push(Method::new(
+                                field.name.to_case(Case::Pascal),
+                                field.name.clone(),
+                                ScalarType::from_str(&type_name)
+                                    .map(|t| FieldType::Scalar(t, None))
+                                    .unwrap_or(FieldType::Enum(type_name)),
+                                is_list,
+                                field.is_nullable,
+                            ));
                         }
-                        ret
-                    } {
-                        fields.push(Method::new(
-                            field.name.to_case(Case::Pascal),
-                            field.name.clone(),
-                            ScalarType::from_str(&type_name)
-                                .map(|t| FieldType::Scalar(t, None))
-                                .unwrap_or(FieldType::Enum(type_name)),
-                            is_list,
-                        ));
                     }
+                    filters.push(Filter {
+                        name: scalar.clone(),
+                        methods: fields,
+                    });
                 }
-                filters.push(Filter {
-                    name: scalar.clone(),
-                    methods: fields,
-                });
             }
 
             for model in &dml.models {
                 for field in &model.fields {
-                    let p = match schema.find_input_type(vec![
-                        model.name.to_string() + "Update" + &field.name() + "Input",
-                    ]) {
+                    let p = match schema.find_input_type(
+                        &(model.name.to_string() + "Update" + &field.name() + "Input"),
+                    ) {
                         Some(p) => p,
                         None => continue,
                     };
@@ -238,6 +250,7 @@ impl GenerateArgs {
                                         None,
                                     ),
                                     is_list,
+                                    field.is_nullable,
                                 ));
                             };
                         }
@@ -287,10 +300,15 @@ impl GenerateArgs {
     pub fn read_filter(&self, field: &ScalarField) -> Option<&Filter> {
         match &field.field_type {
             FieldType::Scalar(typ, _) => {
-                let mut typ = typ.to_string();
+                let mut typ = match typ.to_string().as_str() {
+                    "Boolean" => "Bool".to_string(),
+                    n => n.to_string(),
+                };
 
-                if field.arity.is_list() {
-                    typ += "List";
+                match field.arity {
+                    FieldArity::List => typ += "List",
+                    FieldArity::Optional => typ += "Nullable",
+                    _ => {}
                 }
 
                 self.read_filters.iter().find(|f| f.name == typ)
@@ -316,16 +334,14 @@ impl GenerateArgs {
 }
 
 trait DmmfSchemaExt {
-    fn find_input_type(&self, potential_names: Vec<String>) -> Option<&DmmfInputType>;
+    fn find_input_type(&self, name: &str) -> Option<&DmmfInputType>;
 }
 
 impl DmmfSchemaExt for DmmfSchema {
-    fn find_input_type(&self, potential_names: Vec<String>) -> Option<&DmmfInputType> {
-        let object_types = self.input_object_types.get("prisma").unwrap();
-
-        potential_names
-            .iter()
-            .find_map(|name| object_types.iter().find(|i| &i.name == name))
+    fn find_input_type(&self, name: &str) -> Option<&DmmfInputType> {
+        self.input_object_types
+            .get("prisma")
+            .and_then(|t| t.iter().find(|i| &i.name == &name))
     }
 }
 
@@ -334,25 +350,47 @@ pub struct Method {
     pub name: String,
     pub action: String,
     pub is_list: bool,
+    pub is_nullable: bool,
     pub base_type: FieldType,
 }
 
 impl Method {
-    fn new(name: String, action: String, base_type: FieldType, is_list: bool) -> Self {
+    fn new(
+        name: String,
+        action: String,
+        base_type: FieldType,
+        is_list: bool,
+        is_nullable: bool,
+    ) -> Self {
         Method {
             name,
             action,
             is_list,
+            is_nullable,
             base_type,
         }
     }
 
     pub fn type_tokens(&self, prefix: TokenStream) -> TokenStream {
-        let base_type = &self.base_type.to_tokens(prefix);
+        let base_type = self.base_type.to_tokens(prefix);
+
+        if self.is_list {
+            quote!(Vec<#base_type>)
+        } else if self.is_nullable {
+            quote!(Option<#base_type>)
+        } else {
+            base_type
+        }
+    }
 
-        self.is_list
-            .then(|| quote!(Vec<#base_type>))
-            .unwrap_or(quote!(#base_type))
+    pub fn arity(&self) -> FieldArity {
+        if self.is_list {
+            FieldArity::List
+        } else if self.is_nullable {
+            FieldArity::Optional
+        } else {
+            FieldArity::Required
+        }
     }
 }
 
@@ -364,10 +402,6 @@ pub struct Filter {
 
 /// Gets a method definition from an input field.
 fn input_field_as_method(field: &DmmfInputField) -> Option<Method> {
-    if field.name == "equals" {
-        return None;
-    }
-
     field.input_types.iter().find(|input_type|
         matches!(input_type.location, TypeLocation::Scalar | TypeLocation::EnumTypes if input_type.typ != "Null")
     ).map(|input_type| {
@@ -386,6 +420,7 @@ fn input_field_as_method(field: &DmmfInputField) -> Option<Method> {
                 .map(|t| FieldType::Scalar(t, None))
                 .unwrap_or(FieldType::Enum(type_name)),
             is_list,
+            field.is_nullable,
         )
     })
 }
diff --git a/sdk/src/extensions.rs b/sdk/src/extensions.rs
index 24cb0dc5..a397f5cd 100644
--- a/sdk/src/extensions.rs
+++ b/sdk/src/extensions.rs
@@ -47,8 +47,7 @@ impl FieldExt for Field {
     }
 
     fn type_prisma_value(&self, var: &Ident) -> TokenStream {
-        self.field_type()
-            .to_prisma_value(var, self.arity().is_list())
+        self.field_type().to_prisma_value(var, &self.arity())
     }
 
     fn relation_methods(&self) -> &'static [&'static str] {
@@ -68,7 +67,7 @@ impl FieldExt for Field {
 }
 pub trait FieldTypeExt {
     fn to_tokens(&self, prefix: TokenStream) -> TokenStream;
-    fn to_prisma_value(&self, var: &Ident, is_list: bool) -> TokenStream;
+    fn to_prisma_value(&self, var: &Ident, arity: &FieldArity) -> TokenStream;
 }
 
 impl FieldTypeExt for FieldType {
@@ -87,8 +86,8 @@ impl FieldTypeExt for FieldType {
         }
     }
 
-    fn to_prisma_value(&self, var: &Ident, is_list: bool) -> TokenStream {
-        let scalar_identifier = if is_list {
+    fn to_prisma_value(&self, var: &Ident, arity: &FieldArity) -> TokenStream {
+        let scalar_identifier = if arity.is_list() {
             format_ident!("v")
         } else {
             var.clone()
@@ -102,10 +101,14 @@ impl FieldTypeExt for FieldType {
             typ => unimplemented!("{:?}", typ),
         };
 
-        if is_list {
-            quote!(#v::List(#var.into_iter().map(|v| #scalar_converter).collect()))
-        } else {
-            scalar_converter
+        match arity {
+            FieldArity::List => {
+                quote!(#v::List(#var.into_iter().map(|v| #scalar_converter).collect()))
+            }
+            FieldArity::Optional => {
+                quote!(#var.map(|#var| #scalar_converter).unwrap_or_else(|| #v::Null))
+            }
+            FieldArity::Required => scalar_converter,
         }
     }
 }
diff --git a/sdk/src/keywords.rs b/sdk/src/keywords.rs
index b4b81222..8cad6812 100644
--- a/sdk/src/keywords.rs
+++ b/sdk/src/keywords.rs
@@ -55,7 +55,3 @@ pub const KEYWORDS: &'static [&'static str] = &[
     "'static",
     "dyn",
 ];
-
-pub fn is_reserved_keyword(name: &str) -> bool {
-    KEYWORDS.contains(&name)
-}
\ No newline at end of file
diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs
index dfc82a37..1e4f9579 100644
--- a/sdk/src/utils.rs
+++ b/sdk/src/utils.rs
@@ -1,13 +1,10 @@
 use std::{path::Path, process::Command, sync::Arc};
 
-use convert_case::Case;
 use datamodel::{datamodel_connector::ConnectorCapabilities, dml::Datamodel, Configuration};
 use dmmf::{from_precomputed_parts, DataModelMetaFormat};
 use prisma_models::InternalDataModelBuilder;
 use query_core::schema_builder;
 
-use crate::{args::GenerateArgs, casing::Casing, keywords::is_reserved_keyword, GeneratorError};
-
 pub fn rustfmt(path: &Path) {
     Command::new("rustfmt")
         .arg("--edition=2021")


Test patch: