Skip to content
Merged
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
1 change: 1 addition & 0 deletions .changepacks/changepack_log_xIyk1IGTLmUTH4QC65hmf.json
Original file line number Diff line number Diff line change
@@ -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"}
13 changes: 12 additions & 1 deletion crates/vespertide-cli/src/commands/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ pub fn cmd_export(orm: OrmArg, export_dir: Option<PathBuf>) -> 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::<Result<Vec<_>, _>>()?;

let target_root = resolve_export_dir(export_dir, &config);
if !target_root.exists() {
fs::create_dir_all(&target_root)
Expand All @@ -40,7 +51,7 @@ pub fn cmd_export(orm: OrmArg, export_dir: Option<PathBuf>) -> 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() {
Expand Down
1 change: 1 addition & 0 deletions crates/vespertide-core/src/schema/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 90 additions & 0 deletions crates/vespertide-exporter/src/seaorm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }, {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<super::article::Entity>,
#[sea_orm(belongs_to, from = "user_id", to = "id")]
pub user: HasOne<super::user::Entity>,
}


// 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 {}