Skip to content

Commit 5c81f21

Browse files
committed
Fix temp_table issue
1 parent ffa14e5 commit 5c81f21

26 files changed

Lines changed: 488 additions & 396 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ local.db
33
settings.local.json
44
coverage
55
lcov.info
6+
.sisyphus

crates/vespertide-planner/src/apply.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,11 @@ pub fn apply_action(
186186
.iter_mut()
187187
.find(|t| t.name == *table)
188188
.ok_or_else(|| PlannerError::TableNotFound(table.clone()))?;
189-
tbl.constraints.push(constraint.clone());
189+
// Skip if an equivalent constraint already exists (e.g. inline index
190+
// was already promoted to table-level by normalize() during AddColumn)
191+
if !tbl.constraints.contains(constraint) {
192+
tbl.constraints.push(constraint.clone());
193+
}
190194
Ok(())
191195
}
192196
MigrationAction::RemoveConstraint { table, constraint } => {

crates/vespertide-planner/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ pub enum PlannerError {
3636
DuplicateEnumValue(String, String, String, i32),
3737
#[error("{0}")]
3838
InvalidEnumDefault(#[from] Box<InvalidEnumDefaultError>),
39+
#[error(
40+
"auto_increment on non-integer column: {0}.{1} (type {2} does not support auto_increment)"
41+
)]
42+
InvalidAutoIncrement(String, String, String),
3943
}
4044

4145
#[derive(Debug, Error)]

crates/vespertide-planner/src/validate.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,44 @@ fn validate_table(
6161
return Err(PlannerError::MissingPrimaryKey(table.name.clone()));
6262
}
6363

64+
// Validate auto_increment columns have integer types
65+
for constraint in &table.constraints {
66+
if let TableConstraint::PrimaryKey {
67+
auto_increment: true,
68+
columns,
69+
} = constraint
70+
{
71+
for col_name in columns {
72+
if let Some(column) = table.columns.iter().find(|c| c.name == *col_name)
73+
&& !column.r#type.supports_auto_increment() {
74+
return Err(PlannerError::InvalidAutoIncrement(
75+
table.name.clone(),
76+
col_name.clone(),
77+
format!("{:?}", column.r#type),
78+
));
79+
}
80+
}
81+
}
82+
}
83+
84+
// Validate auto_increment on inline primary_key definitions
85+
use vespertide_core::schema::primary_key::PrimaryKeySyntax;
86+
for column in &table.columns {
87+
if let Some(pk_syntax) = &column.primary_key {
88+
let has_auto_increment = match pk_syntax {
89+
PrimaryKeySyntax::Bool(_) => false,
90+
PrimaryKeySyntax::Object(pk_def) => pk_def.auto_increment,
91+
};
92+
if has_auto_increment && !column.r#type.supports_auto_increment() {
93+
return Err(PlannerError::InvalidAutoIncrement(
94+
table.name.clone(),
95+
column.name.clone(),
96+
format!("{:?}", column.r#type),
97+
));
98+
}
99+
}
100+
}
101+
64102
// Validate columns (enum types)
65103
for column in &table.columns {
66104
validate_column(column, &table.name)?;
@@ -463,6 +501,7 @@ pub fn find_missing_fill_with(plan: &MigrationPlan) -> Vec<FillWithRequired> {
463501
mod tests {
464502
use super::*;
465503
use rstest::rstest;
504+
use vespertide_core::schema::primary_key::{PrimaryKeyDef, PrimaryKeySyntax};
466505
use vespertide_core::{
467506
ColumnDef, ColumnType, ComplexColumnType, EnumValues, NumValue, SimpleColumnType,
468507
TableConstraint,
@@ -1826,4 +1865,61 @@ mod tests {
18261865
let missing = find_missing_fill_with(&plan);
18271866
assert!(missing.is_empty());
18281867
}
1868+
1869+
#[test]
1870+
fn validate_auto_increment_on_text_column_fails() {
1871+
let table_def = table(
1872+
"users",
1873+
vec![col("id", ColumnType::Simple(SimpleColumnType::Text))],
1874+
vec![TableConstraint::PrimaryKey {
1875+
auto_increment: true,
1876+
columns: vec!["id".into()],
1877+
}],
1878+
);
1879+
1880+
let result = validate_table(&table_def, &std::collections::HashMap::new());
1881+
assert!(result.is_err());
1882+
match result {
1883+
Err(PlannerError::InvalidAutoIncrement(table_name, col_name, _)) => {
1884+
assert_eq!(table_name, "users");
1885+
assert_eq!(col_name, "id");
1886+
}
1887+
_ => panic!("Expected InvalidAutoIncrement error"),
1888+
}
1889+
}
1890+
1891+
#[test]
1892+
fn validate_auto_increment_on_integer_column_succeeds() {
1893+
let table_def = table(
1894+
"users",
1895+
vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
1896+
vec![TableConstraint::PrimaryKey {
1897+
auto_increment: true,
1898+
columns: vec!["id".into()],
1899+
}],
1900+
);
1901+
1902+
let result = validate_table(&table_def, &std::collections::HashMap::new());
1903+
assert!(result.is_ok());
1904+
}
1905+
1906+
#[test]
1907+
fn validate_inline_auto_increment_on_text_column_fails() {
1908+
let mut col_def = col("id", ColumnType::Simple(SimpleColumnType::Text));
1909+
col_def.primary_key = Some(PrimaryKeySyntax::Object(PrimaryKeyDef {
1910+
auto_increment: true,
1911+
}));
1912+
1913+
let table_def = table("users", vec![col_def], vec![]);
1914+
1915+
let result = validate_table(&table_def, &std::collections::HashMap::new());
1916+
assert!(result.is_err());
1917+
match result {
1918+
Err(PlannerError::InvalidAutoIncrement(table_name, col_name, _)) => {
1919+
assert_eq!(table_name, "users");
1920+
assert_eq!(col_name, "id");
1921+
}
1922+
_ => panic!("Expected InvalidAutoIncrement error"),
1923+
}
1924+
}
18291925
}

crates/vespertide-query/src/sql/add_column.rs

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ use sea_query::{Alias, Expr, Query, Table, TableAlterStatement};
22

33
use vespertide_core::{ColumnDef, TableDef};
44

5-
use super::create_table::build_create_table_for_backend;
65
use super::helpers::{
7-
build_create_enum_type_sql, build_schema_statement, build_sea_column_def_with_table,
8-
collect_sqlite_enum_check_clauses, normalize_enum_default, normalize_fill_with,
6+
build_create_enum_type_sql, build_sea_column_def_with_table, build_sqlite_temp_table_create,
7+
normalize_enum_default, normalize_fill_with, recreate_indexes_after_rebuild,
98
};
109
use super::rename_table::build_rename_table;
11-
use super::types::{BuiltQuery, DatabaseBackend, RawSql};
10+
use super::types::{BuiltQuery, DatabaseBackend};
1211
use crate::error::QueryError;
1312

1413
fn build_add_column_alter_for_backend(
@@ -50,32 +49,16 @@ pub fn build_add_column(
5049
new_columns.push(column.clone());
5150

5251
let temp_table = format!("{}_temp", table);
53-
let create_temp = build_create_table_for_backend(
52+
53+
// 1. Create temporary table with all CHECK constraints (enum + explicit)
54+
let create_query = build_sqlite_temp_table_create(
5455
backend,
5556
&temp_table,
57+
table,
5658
&new_columns,
5759
&table_def.constraints,
5860
);
5961

60-
// For SQLite, add CHECK constraints for enum columns
61-
// Use original table name for constraint naming (table will be renamed back)
62-
let enum_check_clauses = collect_sqlite_enum_check_clauses(table, &new_columns);
63-
let create_query = if !enum_check_clauses.is_empty() {
64-
let base_sql = build_schema_statement(&create_temp, *backend);
65-
let mut modified_sql = base_sql;
66-
if let Some(pos) = modified_sql.rfind(')') {
67-
let check_sql = enum_check_clauses.join(", ");
68-
modified_sql.insert_str(pos, &format!(", {}", check_sql));
69-
}
70-
BuiltQuery::Raw(RawSql::per_backend(
71-
modified_sql.clone(),
72-
modified_sql.clone(),
73-
modified_sql,
74-
))
75-
} else {
76-
BuiltQuery::CreateTable(Box::new(create_temp))
77-
};
78-
7962
// Copy existing data, filling new column
8063
let mut select_query = Query::select();
8164
for col in &table_def.columns {
@@ -112,21 +95,8 @@ pub fn build_add_column(
11295
BuiltQuery::DropTable(Box::new(Table::drop().table(Alias::new(table)).to_owned()));
11396
let rename_query = build_rename_table(&temp_table, table);
11497

115-
// Recreate indexes from Index constraints
116-
let mut index_queries = Vec::new();
117-
for constraint in &table_def.constraints {
118-
if let vespertide_core::TableConstraint::Index { name, columns } = constraint {
119-
let index_name =
120-
vespertide_naming::build_index_name(table, columns, name.as_deref());
121-
let mut idx_stmt = sea_query::Index::create();
122-
idx_stmt = idx_stmt.name(&index_name).to_owned();
123-
for col_name in columns {
124-
idx_stmt = idx_stmt.col(Alias::new(col_name)).to_owned();
125-
}
126-
idx_stmt = idx_stmt.table(Alias::new(table)).to_owned();
127-
index_queries.push(BuiltQuery::CreateIndex(Box::new(idx_stmt)));
128-
}
129-
}
98+
// Recreate indexes (both regular and UNIQUE)
99+
let index_queries = recreate_indexes_after_rebuild(table, &table_def.constraints);
130100

131101
let mut stmts = vec![create_query, insert_query, drop_query, rename_query];
132102
stmts.extend(index_queries);

0 commit comments

Comments
 (0)