Skip to content

Commit 35cbd22

Browse files
committed
Fix idx issue
1 parent 07af049 commit 35cbd22

File tree

14 files changed

+181
-63
lines changed

14 files changed

+181
-63
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ vespertide-core = { path = "crates/vespertide-core", version = "0.1.14" }
1414
vespertide-config = { path = "crates/vespertide-config", version = "0.1.14" }
1515
vespertide-loader = { path = "crates/vespertide-loader", version = "0.1.14" }
1616
vespertide-macro = { path = "crates/vespertide-macro", version = "0.1.14" }
17+
vespertide-naming = { path = "crates/vespertide-naming", version = "0.1.14" }
1718
vespertide-planner = { path = "crates/vespertide-planner", version = "0.1.14" }
1819
vespertide-query = { path = "crates/vespertide-query", version = "0.1.14" }
1920
vespertide-exporter = { path = "crates/vespertide-exporter", version = "0.1.14" }

crates/vespertide-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ description = "Data models for tables, columns, constraints, indexes, and migrat
1212
serde = { version = "1", features = ["derive"] }
1313
schemars = { version = "1.1" }
1414
thiserror = "2"
15+
vespertide-naming = { workspace = true }
1516

1617
[dev-dependencies]
1718
rstest = "0.26"

crates/vespertide-core/src/schema/table.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ impl TableDef {
269269
.insert(col.name.clone());
270270
}
271271
StrOrBoolOrArray::Bool(true) => {
272-
// Auto-generated index name
273-
let index_name = format!("idx_{}_{}", self.name, col.name);
272+
// Auto-generated index name using vespertide-naming
273+
let index_name =
274+
vespertide_naming::build_index_name(&self.name, &[col.name.clone()], None);
274275

275276
// Check for duplicate (auto-generated names are unique per column, so this shouldn't happen)
276277
// But we check anyway for consistency - only check inline definitions
@@ -542,7 +543,7 @@ mod tests {
542543

543544
let normalized = table.normalize().unwrap();
544545
assert_eq!(normalized.indexes.len(), 1);
545-
assert_eq!(normalized.indexes[0].name, "idx_users_name");
546+
assert_eq!(normalized.indexes[0].name, "ix_users__name");
546547
assert_eq!(normalized.indexes[0].columns, vec!["name".to_string()]);
547548
assert!(!normalized.indexes[0].unique);
548549
}
@@ -719,7 +720,7 @@ mod tests {
719720
let idx_col4 = normalized
720721
.indexes
721722
.iter()
722-
.find(|i| i.name == "idx_test_col4")
723+
.find(|i| i.name == "ix_test__col4")
723724
.unwrap();
724725
assert_eq!(idx_col4.columns, vec!["col4".to_string()]);
725726
}
@@ -1189,7 +1190,7 @@ mod tests {
11891190
column_name,
11901191
}) = result
11911192
{
1192-
assert!(index_name.contains("idx_test"));
1193+
assert!(index_name.contains("ix_test"));
11931194
assert!(index_name.contains("col1"));
11941195
assert_eq!(column_name, "col1");
11951196
} else {

crates/vespertide-exporter/src/seaorm/snapshots/vespertide_exporter__seaorm__tests__render_entity_snapshots@params_pk_and_fk_together.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ pub struct Model {
2929

3030

3131
// Index definitions (SeaORM uses Statement builders externally)
32-
// idx_article_user_article_id on [article_id] unique=false
33-
// idx_article_user_user_id on [user_id] unique=false
32+
// ix_article_user__article_id on [article_id] unique=false
33+
// ix_article_user__user_id on [user_id] unique=false
3434
impl ActiveModelBehavior for ActiveModel {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "vespertide-naming"
3+
version = "0.1.14"
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
homepage.workspace = true
8+
documentation.workspace = true
9+
description = "Naming conventions and helpers for vespertide database schema management"
10+
11+
[dependencies]
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! Naming conventions and helpers for vespertide database schema management.
2+
//!
3+
//! This crate provides consistent naming functions for database objects like
4+
//! indexes, constraints, and foreign keys. It has no dependencies and can be
5+
//! used by any other vespertide crate.
6+
7+
/// Generate index name from table name, columns, and optional user-provided key.
8+
/// Always includes table name to avoid conflicts across tables.
9+
/// Uses double underscore to separate table name from the rest.
10+
/// Format: ix_{table}__{key} or ix_{table}__{col1}_{col2}...
11+
pub fn build_index_name(table: &str, columns: &[String], key: Option<&str>) -> String {
12+
match key {
13+
Some(k) => format!("ix_{}__{}", table, k),
14+
None => format!("ix_{}__{}", table, columns.join("_")),
15+
}
16+
}
17+
18+
/// Generate unique constraint name from table name, columns, and optional user-provided key.
19+
/// Always includes table name to avoid conflicts across tables.
20+
/// Uses double underscore to separate table name from the rest.
21+
/// Format: uq_{table}__{key} or uq_{table}__{col1}_{col2}...
22+
pub fn build_unique_constraint_name(table: &str, columns: &[String], key: Option<&str>) -> String {
23+
match key {
24+
Some(k) => format!("uq_{}__{}", table, k),
25+
None => format!("uq_{}__{}", table, columns.join("_")),
26+
}
27+
}
28+
29+
/// Generate foreign key constraint name from table name, columns, and optional user-provided key.
30+
/// Always includes table name to avoid conflicts across tables.
31+
/// Uses double underscore to separate table name from the rest.
32+
/// Format: fk_{table}__{key} or fk_{table}__{col1}_{col2}...
33+
pub fn build_foreign_key_name(table: &str, columns: &[String], key: Option<&str>) -> String {
34+
match key {
35+
Some(k) => format!("fk_{}__{}", table, k),
36+
None => format!("fk_{}__{}", table, columns.join("_")),
37+
}
38+
}
39+
40+
/// Generate CHECK constraint name for SQLite enum column.
41+
/// Uses double underscore to separate table name from the rest.
42+
/// Format: chk_{table}__{column}
43+
pub fn build_check_constraint_name(table: &str, column: &str) -> String {
44+
format!("chk_{}__{}", table, column)
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
51+
#[test]
52+
fn test_build_index_name_with_key() {
53+
assert_eq!(
54+
build_index_name("users", &["email".into()], Some("email_idx")),
55+
"ix_users__email_idx"
56+
);
57+
}
58+
59+
#[test]
60+
fn test_build_index_name_without_key() {
61+
assert_eq!(
62+
build_index_name("users", &["email".into()], None),
63+
"ix_users__email"
64+
);
65+
}
66+
67+
#[test]
68+
fn test_build_index_name_multiple_columns() {
69+
assert_eq!(
70+
build_index_name("users", &["first_name".into(), "last_name".into()], None),
71+
"ix_users__first_name_last_name"
72+
);
73+
}
74+
75+
#[test]
76+
fn test_build_unique_constraint_name_with_key() {
77+
assert_eq!(
78+
build_unique_constraint_name("users", &["email".into()], Some("email_unique")),
79+
"uq_users__email_unique"
80+
);
81+
}
82+
83+
#[test]
84+
fn test_build_unique_constraint_name_without_key() {
85+
assert_eq!(
86+
build_unique_constraint_name("users", &["email".into()], None),
87+
"uq_users__email"
88+
);
89+
}
90+
91+
#[test]
92+
fn test_build_foreign_key_name_with_key() {
93+
assert_eq!(
94+
build_foreign_key_name("posts", &["user_id".into()], Some("fk_user")),
95+
"fk_posts__fk_user"
96+
);
97+
}
98+
99+
#[test]
100+
fn test_build_foreign_key_name_without_key() {
101+
assert_eq!(
102+
build_foreign_key_name("posts", &["user_id".into()], None),
103+
"fk_posts__user_id"
104+
);
105+
}
106+
107+
#[test]
108+
fn test_build_check_constraint_name() {
109+
assert_eq!(
110+
build_check_constraint_name("users", "status"),
111+
"chk_users__status"
112+
);
113+
}
114+
}

crates/vespertide-planner/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ description = "Replays applied migrations to rebuild a baseline, then diffs agai
1010

1111
[dependencies]
1212
vespertide-core = { workspace = true }
13+
vespertide-naming = { workspace = true }
1314
thiserror = "2"
1415

1516
[dev-dependencies]

crates/vespertide-planner/src/apply.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,18 @@ pub fn apply_action(
116116
tbl.indexes.retain(|i| i.name != *name);
117117

118118
// Also clear inline index on column if index name matches the auto-generated pattern
119-
// Pattern: idx_{table}_{column} for Bool(true) or the name itself for Str(name)
120-
let prefix = format!("idx_{}_", table);
119+
// Pattern: ix_{table}__{column} for Bool(true) or the name itself for Str(name)
120+
// Check if this index name was auto-generated for a single column
121+
for col in &mut tbl.columns {
122+
let auto_name =
123+
vespertide_naming::build_index_name(table, &[col.name.clone()], None);
124+
if *name == auto_name {
125+
col.index = None;
126+
break;
127+
}
128+
}
129+
// Legacy prefix check for backwards compatibility
130+
let prefix = format!("ix_{}__", table);
121131
if let Some(col_name) = name.strip_prefix(&prefix) {
122132
// This is an auto-generated index name - clear the inline index on that column
123133
if let Some(col) = tbl.columns.iter_mut().find(|c| c.name == col_name) {
@@ -772,22 +782,22 @@ mod tests {
772782
// Tests for RemoveIndex clearing inline index on columns
773783
#[test]
774784
fn remove_index_clears_inline_index_bool() {
775-
// Column with inline index: true creates idx_{table}_{column} pattern
785+
// Column with inline index: true creates ix_{table}__{column} pattern
776786
let mut col_with_index = col("email", ColumnType::Simple(SimpleColumnType::Text));
777787
col_with_index.index = Some(vespertide_core::StrOrBoolOrArray::Bool(true));
778788

779789
let mut schema = vec![table(
780790
"users",
781791
vec![col_with_index],
782792
vec![],
783-
vec![idx("idx_users_email", vec!["email"], false)],
793+
vec![idx("ix_users__email", vec!["email"], false)],
784794
)];
785795

786796
apply_action(
787797
&mut schema,
788798
&MigrationAction::RemoveIndex {
789799
table: "users".into(),
790-
name: "idx_users_email".into(),
800+
name: "ix_users__email".into(),
791801
},
792802
)
793803
.unwrap();

crates/vespertide-planner/src/diff.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ mod tests {
452452
],
453453
vec![],
454454
vec![IndexDef {
455-
name: "idx_users_name".into(),
455+
name: "ix_users__name".into(),
456456
columns: vec!["name".into()],
457457
unique: false,
458458
}],
@@ -466,7 +466,7 @@ mod tests {
466466
MigrationAction::AddIndex {
467467
table: "users".into(),
468468
index: IndexDef {
469-
name: "idx_users_name".into(),
469+
name: "ix_users__name".into(),
470470
columns: vec!["name".into()],
471471
unique: false,
472472
},
@@ -866,7 +866,7 @@ mod tests {
866866
MigrationAction::CreateTable { .. }
867867
));
868868
if let MigrationAction::AddIndex { index, .. } = &plan.actions[1] {
869-
assert_eq!(index.name, "idx_users_name");
869+
assert_eq!(index.name, "ix_users__name");
870870
assert_eq!(index.columns, vec!["name".to_string()]);
871871
} else {
872872
panic!("Expected AddIndex action");
@@ -937,7 +937,7 @@ mod tests {
937937
assert_eq!(plan.actions.len(), 1);
938938
if let MigrationAction::AddIndex { table, index } = &plan.actions[0] {
939939
assert_eq!(table, "users");
940-
assert_eq!(index.name, "idx_users_name");
940+
assert_eq!(index.name, "ix_users__name");
941941
assert_eq!(index.columns, vec!["name".to_string()]);
942942
} else {
943943
panic!("Expected AddIndex action, got {:?}", plan.actions[0]);

0 commit comments

Comments
 (0)