Skip to content

Commit d46700d

Browse files
committed
feat: use query_as non macro when type array was used
1 parent 943dd6b commit d46700d

13 files changed

Lines changed: 362 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/sqlx_gen/src/codegen/composite_gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ mod tests {
130130
is_primary_key: false,
131131
ordinal_position: 0,
132132
schema_name: "public".to_string(),
133+
column_default: None,
133134
}
134135
}
135136

crates/sqlx_gen/src/codegen/crud_gen.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ pub fn generate_crud_from_parsed(
2424
// Pool type (used via full path sqlx::PgPool etc., no import needed)
2525
let pool_type = pool_type_tokens(db_kind);
2626

27-
// When the entity has custom SQL array types (e.g. Vec<ConnectorUsages> from connector_usages[]),
27+
// When the entity has custom SQL types (enums, composites, arrays),
2828
// query_as! macro can't resolve the column type at compile time. Fall back to runtime query_as::<_, T>()
2929
// for queries that return rows. DELETE (no rows returned) can still use macro.
30-
let has_sql_array = entity.fields.iter().any(|f| f.is_sql_array);
31-
let use_macro = query_macro && !has_sql_array;
30+
let has_custom_sql_type = entity.fields.iter().any(|f| f.sql_type.is_some());
31+
let use_macro = query_macro && !has_custom_sql_type;
3232

3333
// Entity import
3434
imports.insert(format!("use {}::{};", entity_module_path, entity.struct_name));
@@ -1288,7 +1288,7 @@ mod tests {
12881288
assert!(!code.contains("query!("));
12891289
}
12901290

1291-
// --- sql_array fallback: macro mode + array field → runtime for SELECT, macro for DELETE ---
1291+
// --- custom sql_type fallback: macro mode + custom type → runtime for SELECT, macro for DELETE ---
12921292

12931293
fn entity_with_sql_array() -> ParsedEntity {
12941294
ParsedEntity {
@@ -1379,4 +1379,57 @@ mod tests {
13791379
// Should NOT contain query_as! macro (only query_as::<_ for runtime)
13801380
assert!(!code.contains("query_as!("));
13811381
}
1382+
1383+
// --- custom enum (non-array) also triggers runtime fallback ---
1384+
1385+
fn entity_with_sql_enum() -> ParsedEntity {
1386+
ParsedEntity {
1387+
struct_name: "Task".to_string(),
1388+
table_name: "tasks".to_string(),
1389+
schema_name: None,
1390+
is_view: false,
1391+
fields: vec![
1392+
ParsedField {
1393+
rust_name: "id".to_string(),
1394+
column_name: "id".to_string(),
1395+
rust_type: "i32".to_string(),
1396+
inner_type: "i32".to_string(),
1397+
is_nullable: false,
1398+
is_primary_key: true,
1399+
sql_type: None,
1400+
is_sql_array: false,
1401+
},
1402+
ParsedField {
1403+
rust_name: "status".to_string(),
1404+
column_name: "status".to_string(),
1405+
rust_type: "TaskStatus".to_string(),
1406+
inner_type: "TaskStatus".to_string(),
1407+
is_nullable: false,
1408+
is_primary_key: false,
1409+
sql_type: Some("task_status".to_string()),
1410+
is_sql_array: false,
1411+
},
1412+
],
1413+
imports: vec![],
1414+
}
1415+
}
1416+
1417+
#[test]
1418+
fn test_sql_enum_macro_uses_runtime() {
1419+
let skip = Methods::all();
1420+
let (tokens, _) = generate_crud_from_parsed(&entity_with_sql_enum(), DatabaseKind::Postgres, "crate::models::task", &skip, true);
1421+
let code = parse_and_format(&tokens);
1422+
// SELECT queries should use runtime query_as, not macro
1423+
assert!(code.contains("query_as::<"));
1424+
assert!(!code.contains("query_as!("));
1425+
}
1426+
1427+
#[test]
1428+
fn test_sql_enum_macro_delete_still_uses_macro() {
1429+
let skip = Methods::all();
1430+
let (tokens, _) = generate_crud_from_parsed(&entity_with_sql_enum(), DatabaseKind::Postgres, "crate::models::task", &skip, true);
1431+
let code = parse_and_format(&tokens);
1432+
// DELETE still uses query! macro
1433+
assert!(code.contains("query!"));
1434+
}
13821435
}

crates/sqlx_gen/src/codegen/domain_gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub fn generate_domain(
3131
is_primary_key: false,
3232
ordinal_position: 0,
3333
schema_name: domain.schema_name.clone(),
34+
column_default: None,
3435
};
3536

3637
let rust_type = typemap::map_column(&fake_col, db_kind, schema_info, type_overrides);

crates/sqlx_gen/src/codegen/enum_gen.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ pub fn generate_enum(
7171
})
7272
.collect();
7373

74+
let default_impl = if let Some(ref default_variant) = enum_info.default_variant {
75+
let variant_pascal = default_variant.to_upper_camel_case();
76+
let variant_ident = format_ident!("{}", variant_pascal);
77+
quote! {
78+
impl Default for #enum_name {
79+
fn default() -> Self {
80+
Self::#variant_ident
81+
}
82+
}
83+
}
84+
} else {
85+
quote! {}
86+
};
87+
7488
let tokens = quote! {
7589
#[doc = #doc]
7690
#[derive(#(#derive_tokens),*)]
@@ -79,6 +93,8 @@ pub fn generate_enum(
7993
pub enum #enum_name {
8094
#(#variants)*
8195
}
96+
97+
#default_impl
8298
};
8399

84100
(tokens, imports)
@@ -94,6 +110,7 @@ mod tests {
94110
schema_name: "public".to_string(),
95111
name: name.to_string(),
96112
variants: variants.into_iter().map(|s| s.to_string()).collect(),
113+
default_variant: None,
97114
}
98115
}
99116

@@ -150,6 +167,7 @@ mod tests {
150167
schema_name: "auth".to_string(),
151168
name: "role".to_string(),
152169
variants: vec!["admin".to_string(), "user".to_string()],
170+
default_variant: None,
153171
};
154172
let (tokens, _) = generate_enum(&e, DatabaseKind::Postgres, &[]);
155173
let code = parse_and_format(&tokens);
@@ -289,4 +307,39 @@ mod tests {
289307
let code = gen(&e, DatabaseKind::Postgres);
290308
assert!(code.contains("pub enum MyEnum"));
291309
}
310+
311+
// --- impl Default ---
312+
313+
#[test]
314+
fn test_default_impl_generated() {
315+
let e = EnumInfo {
316+
schema_name: "public".to_string(),
317+
name: "task_status".to_string(),
318+
variants: vec!["idle".to_string(), "running".to_string(), "done".to_string()],
319+
default_variant: Some("idle".to_string()),
320+
};
321+
let code = gen(&e, DatabaseKind::Postgres);
322+
assert!(code.contains("impl Default for TaskStatus"));
323+
assert!(code.contains("Self::Idle"));
324+
}
325+
326+
#[test]
327+
fn test_no_default_impl_when_none() {
328+
let e = make_enum("status", vec!["active", "inactive"]);
329+
let code = gen(&e, DatabaseKind::Postgres);
330+
assert!(!code.contains("impl Default"));
331+
}
332+
333+
#[test]
334+
fn test_default_impl_snake_case_variant() {
335+
let e = EnumInfo {
336+
schema_name: "public".to_string(),
337+
name: "status".to_string(),
338+
variants: vec!["in_progress".to_string(), "done".to_string()],
339+
default_variant: Some("in_progress".to_string()),
340+
};
341+
let code = gen(&e, DatabaseKind::Postgres);
342+
assert!(code.contains("impl Default for Status"));
343+
assert!(code.contains("Self::InProgress"));
344+
}
292345
}

0 commit comments

Comments
 (0)