Skip to content

Conversation

@SamuelMarks
Copy link
Contributor

No description provided.

@SamuelMarks
Copy link
Contributor Author

@Wulf Something weird is happening on my latest commit:

if update_struct.has_code() {
    ret_buffer.push('\n');
    ret_buffer.push_str(update_struct.code());
    if config.options.default_impl {
        ret_buffer.push('\n');
        ret_buffer.push_str(
            build_default_impl_fn(
                &format!("Update{struct_name}"),
                &update_struct.table.columns,
            )
            .as_str(),
        );
    }
    ret_buffer.push('\n');
}

The update_struct.table.columns has column names which are only present in the other structs; but not the update one. Which table should I use?

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests (see simple_table_sqlite as a example), aside from that some minor style improvements.

@hasezoey
Copy link
Collaborator

hasezoey commented Nov 28, 2024

btw, we dont have a CONTRIBUTING currently, but the commit style is angular/conventional-ish, see the commit history for examples.

@SamuelMarks
Copy link
Contributor Author

Good suggestions. Yeah I still need to add tests, but it should be ready now (aside from that).

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better, but still the tests are missing (at least one for being enabled with most common types).

Also consider using r# and multiple lines instead of \n for the format! (like other code in the file), example:

dsync/src/code.rs

Lines 497 to 515 in 2797966

r##"
/// Insert a new row into `{table_name}` with a given [`{create_struct_identifier}`]
pub{async_keyword} fn create(db: &mut ConnectionType, item: &{create_struct_identifier}) -> diesel::QueryResult<Self> {{
use {schema_path}{table_name}::dsl::*;
diesel::insert_into({table_name}).values(item).get_result::<Self>(db){await_keyword}
}}
"##
));
} else {
buffer.push_str(&format!(
r##"
/// Insert a new row into `{table_name}` with all default values
pub{async_keyword} fn create(db: &mut ConnectionType) -> diesel::QueryResult<Self> {{
use {schema_path}{table_name}::dsl::*;
diesel::insert_into({table_name}).default_values().get_result::<Self>(db){await_keyword}
}}
"##

PS: also existing test output needs to be updated if it is meant to change. (though to my understanding it shouldnt change as long as default_impl is false, which it is by default)

@SamuelMarks
Copy link
Contributor Author

@hasezoey Hmm maybe I'm misusing something, generated_columns = [] always in generate_for_table of src/code.rs. The rest of the code works well though.

let generated_columns = table_options.get_autogenerated_columns();
println!("generated_columns = {generated_columns:#?}");

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks mostly OK, but most importantly, tests need to be updated and new tests added for this new option.

Hmm maybe I'm misusing something, generated_columns = [] always in generate_for_table of src/code.rs. The rest of the code works well though.

I dont know how you are using it, but that will always be empty unless you specify the generated columns in TableOptions::autogenerated_columns / --autogenerated-columns on the CLI (MainOptions).

Comment on lines +765 to +783
fn default_for_type(typ: &str) -> &'static str {
match typ {
"i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "i128" | "u128" | "isize"
| "usize" => "0",
"f32" | "f64" => "0.0",
// https://doc.rust-lang.org/std/primitive.bool.html#method.default
"bool" => "false",
"String" => "String::new()",
"&str" | "&'static str" => "\"\"",
"Cow<str>" => "Cow::Owned(String::new())",
_ => {
if typ.starts_with("Option<") {
"None"
} else {
"Default::default()"
}
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldnt this basically just return Default::default() instead of manually mapping the defaults?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could, but throughout your codebase—for example—you are using more specific calls over Default::default. It makes the code more readable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont recall that we do this, so i had a quick skim again and couldnt find anything related that we are doing this for Defaults. Do you have a example of where we do this?

Note that i am talking about the generated code not our code style.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here for example https://github.com/Wulf/dsync/blob/2797966/src/global.rs#L356-L368

impl Default for GenerationConfigOpts<'_> {
    fn default() -> Self {
        Self {
            table_options: HashMap::default(),
            default_table_options: Default::default(),
            schema_path: String::from(DEFAULT_SCHEMA_PATH),
            model_path: String::from(DEFAULT_MODEL_PATH),
            once_common_structs: false,
            once_connection_type: false,
            readonly_prefixes: Vec::default(),
            readonly_suffixes: Vec::default(),
        }
    }
}

That could easily be rewritten:

impl Default for GenerationConfigOpts<'_> {
    fn default() -> Self {
        Self {
            table_options: Default::default(),
            default_table_options: Default::default(),
            schema_path: String::from(DEFAULT_SCHEMA_PATH),
            model_path: String::from(DEFAULT_MODEL_PATH),
            once_common_structs: Default::default(),
            once_connection_type: Default::default(),
            readonly_prefixes: Default::default(),
            readonly_suffixes: Default::default(),
        }
    }
}

…but is it more readable that way? - My code makes the generated code roughly as readable as what we write by hand.

Copy link
Collaborator

@hasezoey hasezoey Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could easily be rewritten:

Yes that could be written as such and i dont think we enforce either style there and either is fine (unless specifically needed).
But like i said, i am talking about generated code not about our code style of dsync itself. This example is not generated code.

Also now that i think about it, why is it implemented manually if a derive could just be added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes a derive could be written from this and it would mostly work. Not sure if it'd handle the edge cases, like timezone specific starts and whatnot.

If you just want to take the derive approach, then merge my other PR and close this one.

Personally, I like how explicit this one is, and it futureproofs the tz and other more interesting use-cases that can be inferred from db schema but not necessarily other parts of the codebase.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you just want to take the derive approach, then merge my other PR and close this one.

Yes that could be done, but i also see the point of dsync generating the schema once and users manually modifying it afterward, so i this PR still has its use. Just was curious what your use-case for this PR not doing a simple derive was.
Still though, i think from a maintenance point of view Default::default() for all the fields would be the best idea, unless there is a type that does not have that or, as you already said, to customize some types (like timezones).
This means that if there should ever be a change for the default impl, we dont have to also change it, and i think a user would expect to actually see ::default instead of the raw type. (at least i would expect it there)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh interesting, so you expect people to modify the code after it's generated? - I mean, I do, but that's just because you lack the features I need (and I'm sending you PRs to fix).

How about a compromise: String::default(), Vec::default(), etc.? - And for the scalars / literals, actual literals:

  --> src/main.rs:18:29
   |
18 |     const S: &'static str = Default::default();
   |                             ^^^^^^^^^^^^^^^^^^
   |
   = note: calls in constants are limited to constant functions, tuple structs and tuple variants
error[E0015]: cannot call non-const associated function `<u8 as Default>::default` in constants
  --> src/main.rs:19:20
   |
19 |     const uu: u8 = Default::default();
   |                    ^^^^^^^^^^^^^^^^^^

That way developers will still see default function calls everywhere but the odd false literal; with a fallback to Default::default() if it's not a builtin case I've manually pattern matched.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh interesting, so you expect people to modify the code after it's generated?

Yes for similar cases as yours where dsync can generate the boilerplate, but can be customized afterward - if necessary.

How about a compromise: String::default(), Vec::default(), etc.?

Sure, but personally, i would expect the same behavior as if using rust-analyzer's "expand derive macro", which practically takes the generated code from the derive and dumps it after the struct, and the Default derive macro uses field: Default::default().

In any case, it is fine as it is now and can be changed in a follow-up PR.

And for the scalars / literals, actual literals:

I dont see how the error messages are relevant for this case. Those Error messages are explicitly for const values.
Playground example

src/code.rs Outdated
Comment on lines 842 to 851
if config.options.default_impl {
ret_buffer.push('\n');
ret_buffer.push_str(
build_default_impl_fn(
&StructType::format(&StructType::Create, &struct_name),
create_struct.table.columns.iter().filter(not_generated),
)
.as_str(),
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should arguably be in Struct::render.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious to see what that would look like

@SamuelMarks
Copy link
Contributor Author

Code looks mostly OK, but most importantly, tests need to be updated and new tests added for this new option.

Hmm maybe I'm misusing something, generated_columns = [] always in generate_for_table of src/code.rs. The rest of the code works well though.

I dont know how you are using it, but that will always be empty unless you specify the generated columns in TableOptions::autogenerated_columns / --autogenerated-columns on the CLI (MainOptions).

Yeah I am using those though. Maybe I'll try constructing a test so you can see.

@SamuelMarks
Copy link
Contributor Author

@hasezoey Darn… trying to get Default for Update to work and it's not working:

Attempt 0:

build_default_impl_fn(
                    &StructType::format(&StructType::Update, &struct_name),
                    update_struct
                        .table
                        .columns
                        .iter()

Attempt 1:

build_default_impl_fn(
                    &StructType::format(&StructType::Update, &struct_name),
                    update_struct
                        .fields()

Both attempts give the wrong fields, basically:

/// Update Struct for a row in table `users` for [`Users`]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::AsChangeset, PartialEq)]
#[diesel(table_name=users)]
pub struct UpdateUsers {
    /// Field representing column `password_hash`
    pub password_hash: Option<String>,
    /// Field representing column `role`
    pub role: Option<String>,
    /// Field representing column `created_at`
    pub created_at: Option<chrono::NaiveDateTime>,
}

impl Default for UpdateUsers {
  fn default() -> Self {
    Self {
        username: String::new(),
        password_hash: String::new(),
        role: String::new()
    }
  }
}

FWIW: The other default is correct

/// Create Struct for a row in table `users` for [`Users`]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::Insertable)]
#[diesel(table_name=users)]
pub struct CreateUsers {
    /// Field representing column `username`
    pub username: String,
    /// Field representing column `password_hash`
    pub password_hash: String,
    /// Field representing column `role`
    pub role: String,
}

impl Default for CreateUsers {
  fn default() -> Self {
    Self {
        username: String::new(),
        password_hash: String::new(),
        role: String::new()
    }
  }
}

So clearly the columns are not being properly set for this struct. How do I get the correctly filtered columns for the given table?

@hasezoey
Copy link
Collaborator

hasezoey commented Sep 7, 2025

Darn… trying to get Default for Update to work and it's not working:

I dont know what you are testing against as you still did not provide a test case with this new option, and with my slight refactor and previous suggestion, i dont see any problem, see the attached patch.

Patch that moves the generation into Struct::render and adds a test

To apply this patch put it into file "DIFF.patch" and run git am <DIFF.patch

Alternatively, i think i could also push this change directly into this PR if wanted.

From f9a3c50f2f2b09b2928c64152f7090df53bd9836 Mon Sep 17 00:00:00 2001
From: hasezoey <[email protected]>
Date: Sun, 7 Sep 2025 13:45:50 +0200
Subject: [PATCH] refactor(code): move "default_impl" code to "Struct::render"

And add test for it
---
 src/code.rs                                 | 118 ++++-----------
 test/default_impl/Cargo.toml                |  18 +++
 test/default_impl/lib.rs                    |   6 +
 test/default_impl/models/mod.rs             |   1 +
 test/default_impl/models/todos/generated.rs | 151 ++++++++++++++++++++
 test/default_impl/models/todos/mod.rs       |   2 +
 test/default_impl/schema.rs                 |  16 +++
 test/default_impl/test.sh                   |   8 ++
 8 files changed, 233 insertions(+), 87 deletions(-)
 create mode 100644 test/default_impl/Cargo.toml
 create mode 100644 test/default_impl/lib.rs
 create mode 100644 test/default_impl/models/mod.rs
 create mode 100644 test/default_impl/models/todos/generated.rs
 create mode 100644 test/default_impl/models/todos/mod.rs
 create mode 100644 test/default_impl/schema.rs
 create mode 100755 test/default_impl/test.sh

diff --git a/src/code.rs b/src/code.rs
index f469c87..56f41b6 100644
--- a/src/code.rs
+++ b/src/code.rs
@@ -228,7 +228,7 @@ impl<'a> Struct<'a> {
                     derives_vec.push(derives::PARTIALEQ);
                 }
 
-                /*if !self.config.options.default_impl*/ {
+                if !self.config.options.default_impl {
                     derives_vec.push(derives::DEFAULT);
                 }
             }
@@ -299,7 +299,7 @@ impl<'a> Struct<'a> {
             .collect::<Vec<String>>()
             .join(" ");
 
-        let fields = self.fields();
+        let mut fields = self.fields();
 
         if fields.is_empty() {
             self.has_fields = Some(false);
@@ -332,18 +332,18 @@ impl<'a> Struct<'a> {
         };
 
         let mut lines = Vec::with_capacity(fields.len());
-        for mut f in fields.into_iter() {
+        for f in fields.iter_mut() {
             let field_name = &f.name;
 
             if f.base_type == "String" {
                 f.base_type = match self.ty {
-                    StructType::Read => f.base_type,
+                    StructType::Read => f.base_type.clone(),
                     StructType::Update => self.opts.get_update_str_type().as_str().to_string(),
                     StructType::Create => self.opts.get_create_str_type().as_str().to_string(),
                 }
             } else if f.base_type == "Vec<u8>" {
                 f.base_type = match self.ty {
-                    StructType::Read => f.base_type,
+                    StructType::Read => f.base_type.clone(),
                     StructType::Update => self.opts.get_update_bytes_type().as_str().to_string(),
                     StructType::Create => self.opts.get_create_bytes_type().as_str().to_string(),
                 }
@@ -382,7 +382,7 @@ impl<'a> Struct<'a> {
             ),
         };
 
-        let struct_code = formatdoc!(
+        let mut struct_code = formatdoc!(
             r#"
             {doccomment}
             {tsync_attr}{derive_attr}
@@ -409,6 +409,14 @@ impl<'a> Struct<'a> {
             lines = lines.join("\n"),
         );
 
+        if self.config.options.default_impl {
+            struct_code.push('\n');
+            struct_code.push_str(&build_default_impl_fn(
+                &ty.format(&table.struct_name),
+                &fields,
+            ));
+        }
+
         self.has_fields = Some(true);
         self.rendered_code = Some(struct_code);
     }
@@ -784,51 +792,40 @@ fn default_for_type(typ: &str) -> &'static str {
     }
 }
 
-struct NameTypNullable<'a> {
-    name: String,
-    typ: &'a str,
-    nullable: bool,
-}
-
 /// Generate default (insides of the `impl Default for StructName { fn default() -> Self {} }`)
-fn build_default_impl_fn<'a>(
-    struct_name: &str,
-    column_name_type_nullable: impl Iterator<Item = NameTypNullable<'a>>,
-) -> String {
-    let fields_to_defaults = column_name_type_nullable
+fn build_default_impl_fn<'a>(struct_name: &str, fields: &[StructField]) -> String {
+    let fields: Vec<String> = fields
+        .iter()
         .map(|name_typ_nullable| {
             format!(
-                "        {name}: {typ_default}",
+                "{name}: {typ_default},",
                 name = name_typ_nullable.name,
-                typ_default = if name_typ_nullable.nullable {
+                typ_default = if name_typ_nullable.is_optional {
                     "None"
                 } else {
-                    default_for_type(name_typ_nullable.typ)
+                    default_for_type(&name_typ_nullable.base_type)
                 }
             )
         })
-        .collect::<Vec<String>>()
-        .join(",\n");
+        .collect();
     formatdoc!(
-        r#"impl Default for {struct_name} {{
-  fn default() -> Self {{
-    Self {{
-{fields_to_defaults}
-    }}
-  }}
-}}"#
+        r#"
+        impl Default for {struct_name} {{
+            fn default() -> Self {{
+                Self {{
+                    {fields}
+                }}
+            }}
+        }}
+        "#,
+        fields = fields.join("\n            ")
     )
 }
 
 /// Generate a full file for a given diesel table
 pub fn generate_for_table(table: &ParsedTableMacro, config: &GenerationConfig) -> String {
     // early to ensure the table options are set for the current table
-    let struct_name = &table.struct_name;
     let table_options = config.table(&table.name.to_string());
-    let generated_columns = table_options.get_autogenerated_columns();
-    let not_generated = |col: &&ParsedColumnMacro| -> bool {
-        !generated_columns.contains(&col.column_name.as_str())
-    };
 
     let mut ret_buffer = format!("{FILE_SIGNATURE}\n\n");
 
@@ -844,25 +841,6 @@ pub fn generate_for_table(table: &ParsedTableMacro, config: &GenerationConfig) -
     if create_struct.has_code() {
         ret_buffer.push('\n');
         ret_buffer.push_str(create_struct.code());
-        if config.options.default_impl {
-            ret_buffer.push('\n');
-            ret_buffer.push_str(
-                build_default_impl_fn(
-                    &StructType::format(&StructType::Create, &struct_name),
-                    create_struct
-                        .table
-                        .columns
-                        .iter()
-                        .filter(not_generated)
-                        .map(|col| NameTypNullable {
-                            name: col.column_name.to_string(),
-                            typ: col.ty.as_str(),
-                            nullable: col.is_nullable,
-                        }),
-                )
-                .as_str(),
-            );
-        }
     }
 
     let update_struct = Struct::new(StructType::Update, table, config);
@@ -870,25 +848,6 @@ pub fn generate_for_table(table: &ParsedTableMacro, config: &GenerationConfig) -
     if update_struct.has_code() {
         ret_buffer.push('\n');
         ret_buffer.push_str(update_struct.code());
-        /*if config.options.default_impl {
-            ret_buffer.push('\n');
-            ret_buffer.push_str(
-                build_default_impl_fn(
-                    &StructType::format(&StructType::Update, &struct_name),
-                    update_struct
-                        .table
-                        .columns
-                        .iter()
-                        .filter(not_generated)
-                        .map(|col| NameTypNullable {
-                            name: col.column_name.to_string(),
-                            typ: col.ty.as_str(),
-                            nullable: col.is_nullable,
-                        }),
-                )
-                .as_str(),
-            );
-        }*/
     }
 
     // third, push functions - if enabled
@@ -897,20 +856,5 @@ pub fn generate_for_table(table: &ParsedTableMacro, config: &GenerationConfig) -
         ret_buffer.push_str(build_table_fns(table, config, create_struct, update_struct).as_str());
     }
 
-    if config.options.default_impl {
-        ret_buffer.push('\n');
-        ret_buffer.push_str(
-            build_default_impl_fn(
-                &struct_name,
-                table.columns.iter().map(|col| NameTypNullable {
-                    name: col.name.to_string().to_owned(),
-                    typ: col.ty.as_str(),
-                    nullable: col.is_nullable,
-                }),
-            )
-            .as_str(),
-        );
-    }
-
     ret_buffer
 }
diff --git a/test/default_impl/Cargo.toml b/test/default_impl/Cargo.toml
new file mode 100644
index 0000000..c8b0b90
--- /dev/null
+++ b/test/default_impl/Cargo.toml
@@ -0,0 +1,18 @@
+[lib]
+path = "lib.rs"
+
+[package]
+name = "default_impl"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+diesel = { version = "*", default-features = false, features = [
+    "sqlite",
+    "r2d2",
+    "chrono",
+    "returning_clauses_for_sqlite_3_35",
+] }
+r2d2.workspace = true
+chrono.workspace = true
+serde.workspace = true
diff --git a/test/default_impl/lib.rs b/test/default_impl/lib.rs
new file mode 100644
index 0000000..fdea3f5
--- /dev/null
+++ b/test/default_impl/lib.rs
@@ -0,0 +1,6 @@
+pub mod models;
+pub mod schema;
+
+pub mod diesel {
+    pub use diesel::*;
+}
diff --git a/test/default_impl/models/mod.rs b/test/default_impl/models/mod.rs
new file mode 100644
index 0000000..015a6a2
--- /dev/null
+++ b/test/default_impl/models/mod.rs
@@ -0,0 +1 @@
+pub mod todos;
diff --git a/test/default_impl/models/todos/generated.rs b/test/default_impl/models/todos/generated.rs
new file mode 100644
index 0000000..aac3981
--- /dev/null
+++ b/test/default_impl/models/todos/generated.rs
@@ -0,0 +1,151 @@
+/* @generated and managed by dsync */
+
+#[allow(unused)]
+use crate::diesel::*;
+use crate::schema::*;
+
+pub type ConnectionType = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::sqlite::SqliteConnection>>;
+
+/// Struct representing a row in table `todos`
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::Queryable, diesel::Selectable, diesel::QueryableByName, PartialEq, diesel::Identifiable)]
+#[diesel(table_name=todos, primary_key(id))]
+pub struct Todos {
+    /// Field representing column `id`
+    pub id: i32,
+    /// Field representing column `text`
+    pub text: String,
+    /// Field representing column `completed`
+    pub completed: bool,
+    /// Field representing column `type`
+    pub type_: String,
+    /// Field representing column `smallint`
+    pub smallint: i16,
+    /// Field representing column `bigint`
+    pub bigint: i64,
+    /// Field representing column `created_at`
+    pub created_at: chrono::NaiveDateTime,
+    /// Field representing column `updated_at`
+    pub updated_at: chrono::NaiveDateTime,
+}
+
+impl Default for Todos {
+    fn default() -> Self {
+        Self {
+            id: 0,
+            text: String::new(),
+            completed: false,
+            type_: String::new(),
+            smallint: 0,
+            bigint: 0,
+            created_at: Default::default(),
+            updated_at: Default::default(),
+        }
+    }
+}
+
+/// Create Struct for a row in table `todos` for [`Todos`]
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::Insertable)]
+#[diesel(table_name=todos)]
+pub struct CreateTodos {
+    /// Field representing column `text`
+    pub text: String,
+    /// Field representing column `completed`
+    pub completed: bool,
+    /// Field representing column `type`
+    pub type_: String,
+    /// Field representing column `smallint`
+    pub smallint: i16,
+    /// Field representing column `bigint`
+    pub bigint: i64,
+}
+
+impl Default for CreateTodos {
+    fn default() -> Self {
+        Self {
+            text: String::new(),
+            completed: false,
+            type_: String::new(),
+            smallint: 0,
+            bigint: 0,
+        }
+    }
+}
+
+/// Update Struct for a row in table `todos` for [`Todos`]
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::AsChangeset, PartialEq)]
+#[diesel(table_name=todos)]
+pub struct UpdateTodos {
+    /// Field representing column `text`
+    pub text: Option<String>,
+    /// Field representing column `completed`
+    pub completed: Option<bool>,
+    /// Field representing column `type`
+    pub type_: Option<String>,
+    /// Field representing column `smallint`
+    pub smallint: Option<i16>,
+    /// Field representing column `bigint`
+    pub bigint: Option<i64>,
+    /// Field representing column `created_at`
+    pub created_at: Option<chrono::NaiveDateTime>,
+    /// Field representing column `updated_at`
+    pub updated_at: Option<chrono::NaiveDateTime>,
+}
+
+impl Default for UpdateTodos {
+    fn default() -> Self {
+        Self {
+            text: String::new(),
+            completed: false,
+            type_: String::new(),
+            smallint: 0,
+            bigint: 0,
+            created_at: Default::default(),
+            updated_at: Default::default(),
+        }
+    }
+}
+
+/// Result of a `.paginate` function
+#[derive(Debug, serde::Serialize)]
+pub struct PaginationResult<T> {
+    /// Resulting items that are from the current page
+    pub items: Vec<T>,
+    /// The count of total items there are
+    pub total_items: i64,
+    /// Current page, 0-based index
+    pub page: i64,
+    /// Size of a page
+    pub page_size: i64,
+    /// Number of total possible pages, given the `page_size` and `total_items`
+    pub num_pages: i64,
+}
+
+impl Todos {
+    /// Insert a new row into `todos` with a given [`CreateTodos`]
+    pub fn create(db: &mut ConnectionType, item: &CreateTodos) -> diesel::QueryResult<Self> {
+        use crate::schema::todos::dsl::*;
+
+        diesel::insert_into(todos).values(item).get_result::<Self>(db)
+    }
+
+    /// Get a row from `todos`, identified by the primary key
+    pub fn read(db: &mut ConnectionType, param_id: i32) -> diesel::QueryResult<Self> {
+        use crate::schema::todos::dsl::*;
+
+        todos.filter(id.eq(param_id)).first::<Self>(db)
+    }
+
+    /// Update a row in `todos`, identified by the primary key with [`UpdateTodos`]
+    pub fn update(db: &mut ConnectionType, param_id: i32, item: &UpdateTodos) -> diesel::QueryResult<Self> {
+        use crate::schema::todos::dsl::*;
+
+        diesel::update(todos.filter(id.eq(param_id))).set(item).get_result(db)
+    }
+
+    /// Delete a row in `todos`, identified by the primary key
+    pub fn delete(db: &mut ConnectionType, param_id: i32) -> diesel::QueryResult<usize> {
+        use crate::schema::todos::dsl::*;
+
+        diesel::delete(todos.filter(id.eq(param_id))).execute(db)
+    }
+}
diff --git a/test/default_impl/models/todos/mod.rs b/test/default_impl/models/todos/mod.rs
new file mode 100644
index 0000000..a5bb9b9
--- /dev/null
+++ b/test/default_impl/models/todos/mod.rs
@@ -0,0 +1,2 @@
+pub use generated::*;
+pub mod generated;
diff --git a/test/default_impl/schema.rs b/test/default_impl/schema.rs
new file mode 100644
index 0000000..c91a5c4
--- /dev/null
+++ b/test/default_impl/schema.rs
@@ -0,0 +1,16 @@
+diesel::table! {
+    todos (id) {
+        id -> Int4,
+        // unsigned -> Unsigned<Integer>,
+        // unsigned_nullable -> Nullable<Unsigned<Integer>>,
+        text -> Text,
+        completed -> Bool,
+        #[sql_name = "type"]
+        #[max_length = 255]
+        type_ -> Varchar,
+        smallint -> Int2,
+        bigint -> Int8,
+        created_at -> Timestamp,
+        updated_at -> Timestamp,
+    }
+}
diff --git a/test/default_impl/test.sh b/test/default_impl/test.sh
new file mode 100755
index 0000000..7c33aaf
--- /dev/null
+++ b/test/default_impl/test.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+
+cd $SCRIPT_DIR
+
+cargo run --manifest-path ../../Cargo.toml -- \
+-i schema.rs -o models -g id -g created_at -g updated_at --default-impl -c "diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::sqlite::SqliteConnection>>"
-- 
2.51.0

@hasezoey hasezoey added the enhancement New feature or request label Sep 7, 2025
@SamuelMarks
Copy link
Contributor Author

@Wulf Thanks. Made an edit to handle the Update properly, what do you think now, can we get this merged in? - Then the other PR?

Then I've got some big ideas to try, but likely beyond dsync scope…

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good to me now, but the tests still need to be updated.
Might i recommend, for reviewability, to put the PartialEq change in a different PR?

Also consider adding a CHANGELOG entry (it is not automated yet as semantic-release only works with 1.0.0+ versions)

Comment on lines +94 to +106
impl Default for UpdateTodos {
fn default() -> Self {
Self {
text: String::new(),
completed: false,
type_: String::new(),
smallint: 0,
bigint: 0,
created_at: Default::default(),
updated_at: Default::default(),
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests need to be updated.

@SamuelMarks
Copy link
Contributor Author

@hasezoey Can you help this get over the line?

Then the other PR? - Then I want to send a new PR to implement From<CreateStruct> for UpdateStruct

@hasezoey
Copy link
Collaborator

Can you help this get over the line?

I dont know what to help you with aside from repeating what i said earlier in this PR (also applies to your other PR):

the tests still need to be updated.

In case you had not seen or done it before in this project, to update the tests you need to follow these steps (relative to the project root):

  • run ./test/test_all.sh
    • on error, fix the error, then re-run until no error (compile test of the tests)
  • review the changes generated
  • commit the changes
  • push the changes
  • review the CI result

Also due to the amount of changes the PartialEq will generate, i would like to have that as a separate PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants