From d062576316a208725e3c7f22f355766a0241aa2f Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Mon, 15 Dec 2025 01:46:56 +0900 Subject: [PATCH 1/2] Impl fk with pk --- crates/vespertide-cli/src/commands/export.rs | 13 ++- .../vespertide-core/src/schema/reference.rs | 1 + crates/vespertide-exporter/src/seaorm/mod.rs | 90 +++++++++++++++++++ ...y_snapshots@params_pk_and_fk_together.snap | 29 ++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 crates/vespertide-exporter/src/seaorm/snapshots/vespertide_exporter__seaorm__tests__render_entity_snapshots@params_pk_and_fk_together.snap diff --git a/crates/vespertide-cli/src/commands/export.rs b/crates/vespertide-cli/src/commands/export.rs index c3c8d28f..9900492e 100644 --- a/crates/vespertide-cli/src/commands/export.rs +++ b/crates/vespertide-cli/src/commands/export.rs @@ -32,6 +32,17 @@ pub fn cmd_export(orm: OrmArg, export_dir: Option) -> Result<()> { let config = load_config()?; let models = load_models_recursive(config.models_dir()).context("load models recursively")?; + // Normalize tables to convert inline constraints (primary_key, foreign_key, etc.) to table-level constraints + let normalized_models: Vec<(TableDef, PathBuf)> = models + .into_iter() + .map(|(table, rel_path)| { + table + .normalize() + .map_err(|e| anyhow::anyhow!("Failed to normalize table '{}': {}", table.name, e)) + .map(|normalized| (normalized, rel_path)) + }) + .collect::, _>>()?; + let target_root = resolve_export_dir(export_dir, &config); if !target_root.exists() { fs::create_dir_all(&target_root) @@ -40,7 +51,7 @@ pub fn cmd_export(orm: OrmArg, export_dir: Option) -> Result<()> { let orm_kind: Orm = orm.into(); - for (table, rel_path) in &models { + for (table, rel_path) in &normalized_models { let code = render_entity(orm_kind, table).map_err(|e| anyhow::anyhow!(e))?; let out_path = build_output_path(&target_root, rel_path, orm_kind); if let Some(parent) = out_path.parent() { diff --git a/crates/vespertide-core/src/schema/reference.rs b/crates/vespertide-core/src/schema/reference.rs index 5dcdda3d..c1f51c28 100644 --- a/crates/vespertide-core/src/schema/reference.rs +++ b/crates/vespertide-core/src/schema/reference.rs @@ -2,6 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum ReferenceAction { Cascade, Restrict, diff --git a/crates/vespertide-exporter/src/seaorm/mod.rs b/crates/vespertide-exporter/src/seaorm/mod.rs index fd27b8fe..e95d9b13 100644 --- a/crates/vespertide-exporter/src/seaorm/mod.rs +++ b/crates/vespertide-exporter/src/seaorm/mod.rs @@ -466,6 +466,96 @@ mod tests { constraints: vec![], indexes: vec![], })] + #[case("pk_and_fk_together", { + use vespertide_core::schema::foreign_key::{ForeignKeyDef, ForeignKeySyntax}; + use vespertide_core::schema::reference::ReferenceAction; + let mut table = TableDef { + name: "article_user".into(), + columns: vec![ + ColumnDef { + name: "article_id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Uuid), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: Some(vespertide_core::StrOrBoolOrArray::Bool(true)), + foreign_key: Some(ForeignKeySyntax::Object(ForeignKeyDef { + ref_table: "article".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: None, + })), + }, + ColumnDef { + name: "user_id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Uuid), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: Some(vespertide_core::StrOrBoolOrArray::Bool(true)), + foreign_key: Some(ForeignKeySyntax::Object(ForeignKeyDef { + ref_table: "user".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: None, + })), + }, + ColumnDef { + name: "author_order".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: Some("1".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "role".into(), + r#type: ColumnType::Complex(vespertide_core::ComplexColumnType::Varchar { length: 20 }), + nullable: false, + default: Some("'contributor'".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "is_lead".into(), + r#type: ColumnType::Simple(SimpleColumnType::Boolean), + nullable: false, + default: Some("false".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "created_at".into(), + r#type: ColumnType::Simple(SimpleColumnType::Timestamptz), + nullable: false, + default: Some("now()".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + ], + constraints: vec![], + indexes: vec![], + }; + // Normalize to convert inline constraints to table-level + table = table.normalize().unwrap(); + table + })] fn render_entity_snapshots(#[case] name: &str, #[case] table: TableDef) { let rendered = render_entity(&table); with_settings!({ snapshot_suffix => format!("params_{}", name) }, { diff --git a/crates/vespertide-exporter/src/seaorm/snapshots/vespertide_exporter__seaorm__tests__render_entity_snapshots@params_pk_and_fk_together.snap b/crates/vespertide-exporter/src/seaorm/snapshots/vespertide_exporter__seaorm__tests__render_entity_snapshots@params_pk_and_fk_together.snap new file mode 100644 index 00000000..c11c4500 --- /dev/null +++ b/crates/vespertide-exporter/src/seaorm/snapshots/vespertide_exporter__seaorm__tests__render_entity_snapshots@params_pk_and_fk_together.snap @@ -0,0 +1,29 @@ +--- +source: crates/vespertide-exporter/src/seaorm/mod.rs +expression: rendered +--- +use sea_orm::entity::prelude::*; + +#[sea_orm::model] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "article_user")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub article_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub user_id: Uuid, + pub author_order: i32, + pub role: String, + pub is_lead: bool, + pub created_at: DateTimeWithTimeZone, + #[sea_orm(belongs_to, from = "article_id", to = "id")] + pub article: HasOne, + #[sea_orm(belongs_to, from = "user_id", to = "id")] + pub user: HasOne, +} + + +// Index definitions (SeaORM uses Statement builders externally) +// idx_article_user_article_id on [article_id] unique=false +// idx_article_user_user_id on [user_id] unique=false +impl ActiveModelBehavior for ActiveModel {} From 015a2753a924cc8a4fdac58a3fc83525d3547753 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Mon, 15 Dec 2025 01:47:12 +0900 Subject: [PATCH 2/2] Impl fk with pk --- .changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json diff --git a/.changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json b/.changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json new file mode 100644 index 00000000..1fe6f76e --- /dev/null +++ b/.changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json @@ -0,0 +1 @@ +{"changes":{"crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch"},"note":"Implement fk with pk","date":"2025-12-14T16:47:09.610964300Z"} \ No newline at end of file