Skip to content
Merged

180 #181

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/entity-derive-impl/src/entity/insertable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn generate(entity: &EntityDef) -> TokenStream {
let field_defs = entity
.all_fields()
.iter()
.filter(|f| f.embed.is_none())
.filter(|f| f.embed.is_none() && !f.storage.is_auto)
.map(|f| {
let name = f.name();
let ty = f.ty();
Expand Down
6 changes: 3 additions & 3 deletions crates/entity-derive-impl/src/entity/sql/postgres/bulk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Context<'_> {
insertable_name,
create_dto,
table,
columns_str,
insert_columns_str,
placeholders_str,
entity,
returning,
Expand All @@ -85,15 +85,15 @@ impl Context<'_> {
let insert_row = if matches!(returning, ReturningMode::Full) {
quote! {
let row: #row_name = sqlx::query_as(
concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
)
#(#bindings)*
.fetch_one(&mut *tx).await #constraint_map_err?;
let entity = #entity_name::from(row);
}
} else {
quote! {
sqlx::query(concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ")"))
sqlx::query(concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ")"))
#(#bindings)*
.execute(&mut *tx).await #constraint_map_err?;
}
Expand Down
19 changes: 16 additions & 3 deletions crates/entity-derive-impl/src/entity/sql/postgres/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,17 @@ pub struct Context<'a> {
/// Primary key field type.
pub id_type: &'a syn::Type,

/// Comma-separated column names for SELECT/INSERT.
/// Comma-separated column names for SELECT (all columns).
pub columns_str: String,

/// Comma-separated placeholders for INSERT ($1, $2, ...).
/// Comma-separated column names for INSERT.
///
/// Excludes `#[auto]` fields so database defaults
/// (`DEFAULT now()`, sequences, triggers) apply; persisted values
/// come back via RETURNING.
pub insert_columns_str: String,

/// Comma-separated placeholders matching [`Self::insert_columns_str`].
pub placeholders_str: String,

/// Whether soft delete is enabled.
Expand All @@ -88,6 +95,11 @@ impl<'a> Context<'a> {
pub fn new(entity: &'a EntityDef) -> Self {
let id_field = entity.id_field();
let fields = entity.column_fields();
let insert_fields: Vec<&crate::entity::parse::FieldDef> = fields
.iter()
.copied()
.filter(|f| !f.storage.is_auto)
.collect();
let dialect = entity.dialect;

Self {
Expand All @@ -103,7 +115,8 @@ impl<'a> Context<'a> {
id_name: id_field.name(),
id_type: id_field.ty(),
columns_str: join_columns(&fields),
placeholders_str: dialect.placeholders(fields.len()),
insert_columns_str: join_columns(&insert_fields),
placeholders_str: dialect.placeholders(insert_fields.len()),
soft_delete: entity.is_soft_delete(),
returning: entity.returning.clone(),
streams: entity.has_streams(),
Expand Down
54 changes: 49 additions & 5 deletions crates/entity-derive-impl/src/entity/sql/postgres/crud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl Context<'_> {
insertable_name,
create_dto,
table,
columns_str,
insert_columns_str,
placeholders_str,
entity,
returning,
Expand Down Expand Up @@ -173,7 +173,7 @@ impl Context<'_> {
let entity = #entity_name::from(dto);
let insertable = #insertable_name::from(&entity);
let row: #row_name = sqlx::query_as(
concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
)
#(#bindings)*
.fetch_one(#executor).await #constraint_map_err?;
Expand All @@ -193,7 +193,7 @@ impl Context<'_> {
#tx_open
let entity = #entity_name::from(dto);
let insertable = #insertable_name::from(&entity);
sqlx::query(concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ") RETURNING ", stringify!(#id_name)))
sqlx::query(concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ") RETURNING ", stringify!(#id_name)))
#(#bindings)*
.execute(#executor).await #constraint_map_err?;
#outbox_created
Expand All @@ -210,7 +210,7 @@ impl Context<'_> {
#tx_open
let entity = #entity_name::from(dto);
let insertable = #insertable_name::from(&entity);
sqlx::query(concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ")"))
sqlx::query(concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ")"))
#(#bindings)*
.execute(#executor).await #constraint_map_err?;
#outbox_created
Expand All @@ -228,7 +228,7 @@ impl Context<'_> {
#tx_open
let entity = #entity_name::from(dto);
let insertable = #insertable_name::from(&entity);
sqlx::query(::sqlx::AssertSqlSafe(format!("INSERT INTO {} ({}) VALUES ({}) RETURNING {}", #table, #columns_str, #placeholders_str, #returning_cols)))
sqlx::query(::sqlx::AssertSqlSafe(format!("INSERT INTO {} ({}) VALUES ({}) RETURNING {}", #table, #insert_columns_str, #placeholders_str, #returning_cols)))
#(#bindings)*
.execute(#executor).await #constraint_map_err?;
#outbox_created
Expand Down Expand Up @@ -648,3 +648,47 @@ mod version_tests {
assert!(!code.contains("expected_version"));
}
}

#[cfg(test)]
mod auto_fields_tests {
use quote::quote;
use syn::DeriveInput;

use super::super::context::Context;
use crate::entity::parse::EntityDef;

fn timestamped_entity() -> EntityDef {
let input: DeriveInput = syn::parse_quote! {
#[entity(table = "posts")]
pub struct Post {
#[id]
pub id: uuid::Uuid,
#[field(create, update, response)]
pub title: String,
#[field(response)]
#[auto]
pub created_at: chrono::DateTime<chrono::Utc>,
}
};
EntityDef::from_derive_input(&input).unwrap()
}

#[test]
fn insert_columns_exclude_auto_fields() {
let entity = timestamped_entity();
let ctx = Context::new(&entity);
assert_eq!(ctx.insert_columns_str, "id, title");
assert_eq!(ctx.placeholders_str, "$1, $2");
assert_eq!(ctx.columns_str, "id, title, created_at");
}

#[test]
fn create_inserts_without_auto_columns() {
let entity = timestamped_entity();
let ctx = Context::new(&entity);
let code = ctx.create_method().to_string();
assert!(code.contains("\"id, title\""));
assert!(!code.contains("created_at"));
let _ = quote!();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn join_columns(fields: &[&FieldDef]) -> String {
pub fn insert_bindings(fields: &[FieldDef]) -> Vec<TokenStream> {
fields
.iter()
.filter(|f| f.embed.is_none())
.filter(|f| f.embed.is_none() && !f.storage.is_auto)
.map(|f| {
let name = f.name();
quote! { .bind(insertable.#name) }
Expand Down
4 changes: 2 additions & 2 deletions crates/entity-derive-impl/src/entity/sql/postgres/save.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl Context<'_> {
let row_name = &self.row_name;
let insertable_name = &self.insertable_name;
let table = &self.table;
let columns_str = &self.columns_str;
let insert_columns_str = &self.insert_columns_str;
let placeholders_str = &self.placeholders_str;
let bindings = super::helpers::insert_bindings(self.entity.all_fields());
let error_type = self.entity.error_type();
Expand All @@ -91,7 +91,7 @@ impl Context<'_> {
let mut entity: #entity_name = new.into();
let insertable = #insertable_name::from(&entity);
let row: #row_name = sqlx::query_as(
concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
)
#(#bindings)*
.fetch_one(&mut *tx).await?;
Expand Down
4 changes: 2 additions & 2 deletions crates/entity-derive-impl/src/entity/sql/postgres/upsert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl Context<'_> {
.into_iter()
.filter(|f| {
let col = f.name_str();
!FieldDef::is_id(f) && !conflict.contains(&col)
!FieldDef::is_id(f) && !f.storage.is_auto && !conflict.contains(&col)
})
.map(|f| {
let col = f.name_str();
Expand All @@ -147,7 +147,7 @@ impl Context<'_> {

format!(
"INSERT INTO {} ({}) VALUES ({}) ON CONFLICT ({}) {} RETURNING *",
self.table, self.columns_str, self.placeholders_str, target, action_sql
self.table, self.insert_columns_str, self.placeholders_str, target, action_sql
)
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/entity-derive-impl/src/entity/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn generate_repo_adapter(entity: &EntityDef) -> TokenStream {
let create_dto = &ctx.create_dto;
let update_dto = &ctx.update_dto;
let table = &ctx.table;
let insert_columns_str = &ctx.insert_columns_str;
let columns_str = &ctx.columns_str;
let placeholders_str = &ctx.placeholders_str;
let id_name = ctx.id_name;
Expand Down Expand Up @@ -95,7 +96,7 @@ fn generate_repo_adapter(entity: &EntityDef) -> TokenStream {
let entity = #entity_name::from(dto);
let insertable = #insertable_name::from(&entity);
let row: #row_name = sqlx::query_as(
concat!("INSERT INTO ", #table, " (", #columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
concat!("INSERT INTO ", #table, " (", #insert_columns_str, ") VALUES (", #placeholders_str, ") RETURNING *")
)
#(#bindings)*
.fetch_one(&mut **self.tx).await?;
Expand Down
4 changes: 2 additions & 2 deletions crates/entity-derive-impl/src/utils/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn assigns(fields: &[FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.filter(|f| f.embed.is_none())
.filter(|f| f.embed.is_none() && !f.storage.is_auto)
.map(|f: &FieldDef| {
let name = f.name();
if let Some((parent, sub)) = &f.embed_origin {
Expand All @@ -63,7 +63,7 @@ pub fn assigns_clone(fields: &[FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.filter(|f| f.embed.is_none())
.filter(|f| f.embed.is_none() && !f.storage.is_auto)
.map(|f: &FieldDef| {
let name = f.name();
if let Some((parent, sub)) = &f.embed_origin {
Expand Down
Loading