Skip to content

Commit 54c7a23

Browse files
committed
Use SQLite 3.35 DROP COLUMN
1 parent b5c0f74 commit 54c7a23

File tree

10 files changed

+43
-82
lines changed

10 files changed

+43
-82
lines changed

butane_core/src/db/sqlite.rs

+27-15
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ use crate::{debug, query, Error, Result, SqlType, SqlVal, SqlValRef};
2626
#[cfg(feature = "datetime")]
2727
const SQLITE_DT_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.f";
2828

29+
/// The minimum SQLite version required by this backend.
30+
pub const SQLITE_MIN_VERSION: i32 = 3035000;
31+
2932
/// The name of the sqlite backend.
3033
pub const BACKEND_NAME: &str = "sqlite";
3134
/// The internal row creation order field name.
@@ -113,6 +116,12 @@ pub struct SQLiteConnection {
113116
}
114117
impl SQLiteConnection {
115118
fn open(path: impl AsRef<Path>) -> Result<Self> {
119+
if rusqlite::version_number() < SQLITE_MIN_VERSION {
120+
return Err(Error::IncompatibleSQLite(
121+
rusqlite::version(),
122+
SQLITE_MIN_VERSION,
123+
));
124+
}
116125
#[cfg(feature = "log")]
117126
static INIT_SQLITE_LOGGING: Once = Once::new();
118127

@@ -643,7 +652,7 @@ fn sql_for_op(current: &mut ADB, op: &Operation) -> Result<String> {
643652
Operation::RemoveTable(name) => Ok(drop_table(name)),
644653
Operation::RemoveTableConstraints(_table) => Ok("".to_owned()),
645654
Operation::AddColumn(tbl, col) => add_column(tbl, col),
646-
Operation::RemoveColumn(tbl, name) => Ok(remove_column(current, tbl, name)),
655+
Operation::RemoveColumn(tbl, name) => remove_column(current, tbl, name),
647656
Operation::ChangeColumn(tbl, old, new) => Ok(change_column(current, tbl, old, Some(new))),
648657
}
649658
}
@@ -766,21 +775,24 @@ fn add_column(tbl_name: &str, col: &AColumn) -> Result<String> {
766775
))
767776
}
768777

769-
fn remove_column(current: &mut ADB, tbl_name: &str, name: &str) -> String {
770-
let old = current
778+
fn remove_column(current: &mut ADB, tbl_name: &str, name: &str) -> Result<String> {
779+
let current_clone = current.clone();
780+
let table = current_clone
771781
.get_table(tbl_name)
772-
.and_then(|table| table.column(name))
773-
.cloned();
774-
match old {
775-
Some(col) => change_column(current, tbl_name, &col, None),
776-
None => {
777-
crate::warn!(
778-
"Cannot remove column {} that does not exist from table {}",
779-
name,
780-
tbl_name
781-
);
782-
"".to_string()
783-
}
782+
.ok_or_else(|| Error::TableNotFound(tbl_name.to_string()))?;
783+
let col = table
784+
.column(name)
785+
.ok_or_else(|| Error::ColumnNotFound(tbl_name.to_string(), name.to_string()))?;
786+
// "ALTER TABLE b DROP COLUMN fkey;" fails due to sqlite not being
787+
// able to remove the attached constraint.
788+
if col.reference().is_some() {
789+
Ok(change_column(current, tbl_name, col, None))
790+
} else {
791+
Ok(format!(
792+
"ALTER TABLE {} DROP COLUMN {};",
793+
helper::quote_reserved_word(tbl_name),
794+
helper::quote_reserved_word(name),
795+
))
784796
}
785797
}
786798

butane_core/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@ pub enum Error {
314314
#[cfg(feature = "async-adapter")]
315315
#[error("Crossbeam cannot send/recv, channel disconnected")]
316316
CrossbeamChannel,
317+
#[error("SQLite version {0} is lower than minimum supported {1}")]
318+
IncompatibleSQLite(&'static str, i32),
319+
#[error("Table \"{0}\" not found in schema definitions")]
320+
TableNotFound(String),
321+
#[error("Column \"{0}\".\"{1}\" not found in schema definitions")]
322+
ColumnNotFound(String, String),
317323
}
318324

319325
#[cfg(feature = "sqlite")]

butane_core/tests/adb.rs

-3
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,6 @@ async fn add_table_fkey_delete_column(conn: ConnectionAsync) {
286286
conn.execute("SELECT * from a").await.unwrap();
287287
conn.execute("SELECT * from b").await.unwrap();
288288

289-
// "ALTER TABLE b DROP COLUMN fkey;" fails due to sqlite not being
290-
// able to remove the attached constraint, however the RemoveColumn
291-
// operation already recreates the table, so this works.
292289
let remove_column_op = Operation::RemoveColumn("b".to_owned(), "fkey".to_owned());
293290
let sql = backend
294291
.create_migration_sql(&new, vec![remove_column_op])

butane_core/tests/migration.rs

+4-26
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,7 @@ fn migration_add_field_sqlite() {
182182
migration_add_field(
183183
&mut sqlite_connection(),
184184
"ALTER TABLE Foo ADD COLUMN baz INTEGER NOT NULL DEFAULT 0;",
185-
// The exact details of futzing a DROP COLUMN in sqlite aren't
186-
// important (e.g. the temp table naming is certainly not part
187-
// of the API contract), but the goal here is to ensure we're
188-
// getting sane looking downgrade sql and a test failure if it
189-
// changes. If the change is innocuous, this test should just
190-
// be updated.
191-
"CREATE TABLE Foo__butane_tmp (\"id\" INTEGER NOT NULL PRIMARY KEY,bar TEXT NOT NULL) STRICT;
192-
INSERT INTO Foo__butane_tmp SELECT \"id\", bar FROM Foo;DROP TABLE Foo;
193-
ALTER TABLE Foo__butane_tmp RENAME TO Foo;",
185+
"ALTER TABLE Foo DROP COLUMN baz;",
194186
);
195187
}
196188

@@ -212,9 +204,7 @@ fn migration_add_field_with_default_sqlite() {
212204
&mut sqlite_connection(),
213205
"ALTER TABLE Foo ADD COLUMN baz INTEGER NOT NULL DEFAULT 42;",
214206
// See comments on migration_add_field_sqlite
215-
"CREATE TABLE Foo__butane_tmp (\"id\" INTEGER NOT NULL PRIMARY KEY,bar TEXT NOT NULL) STRICT;
216-
INSERT INTO Foo__butane_tmp SELECT \"id\", bar FROM Foo;
217-
DROP TABLE Foo;ALTER TABLE Foo__butane_tmp RENAME TO Foo;",
207+
"ALTER TABLE Foo DROP COLUMN baz;",
218208
);
219209
}
220210

@@ -279,20 +269,8 @@ fn migration_modify_field_pg() {
279269
fn migration_add_and_remove_field_sqlite() {
280270
migration_add_and_remove_field(
281271
&mut sqlite_connection(),
282-
// The exact details of futzing a DROP COLUMN in sqlite aren't
283-
// important (e.g. the temp table naming is certainly not part
284-
// of the API contract), but the goal here is to ensure we're
285-
// getting sane looking downgrade sql and a test failure if it
286-
// changes. If the change is innocuous, this test should just
287-
// be updated.
288-
"ALTER TABLE Foo ADD COLUMN baz INTEGER NOT NULL DEFAULT 0;
289-
CREATE TABLE Foo__butane_tmp (\"id\" INTEGER NOT NULL PRIMARY KEY,baz INTEGER NOT NULL) STRICT;
290-
INSERT INTO Foo__butane_tmp SELECT \"id\", baz FROM Foo;
291-
DROP TABLE Foo;ALTER TABLE Foo__butane_tmp RENAME TO Foo;",
292-
"ALTER TABLE Foo ADD COLUMN bar TEXT NOT NULL DEFAULT '';
293-
CREATE TABLE Foo__butane_tmp (\"id\" INTEGER NOT NULL PRIMARY KEY,bar TEXT NOT NULL) STRICT;
294-
INSERT INTO Foo__butane_tmp SELECT \"id\", bar FROM Foo;DROP TABLE Foo;
295-
ALTER TABLE Foo__butane_tmp RENAME TO Foo;",
272+
"ALTER TABLE Foo ADD COLUMN baz INTEGER NOT NULL DEFAULT 0;ALTER TABLE Foo DROP COLUMN bar;",
273+
"ALTER TABLE Foo ADD COLUMN bar TEXT NOT NULL DEFAULT '';ALTER TABLE Foo DROP COLUMN baz;",
296274
);
297275
}
298276

Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
CREATE TABLE Post__butane_tmp (
2-
"id" INTEGER NOT NULL PRIMARY KEY,
3-
title TEXT NOT NULL,
4-
body TEXT NOT NULL,
5-
published INTEGER NOT NULL,
6-
blog INTEGER NOT NULL,
7-
byline TEXT
8-
) STRICT;
9-
INSERT INTO Post__butane_tmp SELECT "id", title, body, published, blog, byline FROM Post;
10-
DROP TABLE Post;
11-
ALTER TABLE Post__butane_tmp RENAME TO Post;
1+
ALTER TABLE Post DROP COLUMN likes;

examples/getting_started/src/butane_migrations.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ pub fn get_migrations() -> Result<MemMigrations, butane::Error> {
330330
},
331331
"down": {
332332
"pg": "ALTER TABLE Post DROP COLUMN likes;\n",
333-
"sqlite": "CREATE TABLE Post__butane_tmp (\n\"id\" INTEGER NOT NULL PRIMARY KEY,\ntitle TEXT NOT NULL,\nbody TEXT NOT NULL,\npublished INTEGER NOT NULL,\nblog INTEGER NOT NULL,\nbyline TEXT\n) STRICT;\nINSERT INTO Post__butane_tmp SELECT \"id\", title, body, published, blog, byline FROM Post;\nDROP TABLE Post;\nALTER TABLE Post__butane_tmp RENAME TO Post;\n"
333+
"sqlite": "ALTER TABLE Post DROP COLUMN likes;\n"
334334
}
335335
},
336336
"20240115_023841384_dbconstraints": {
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
CREATE TABLE Post__butane_tmp (
2-
"id" INTEGER NOT NULL PRIMARY KEY,
3-
title TEXT NOT NULL,
4-
body TEXT NOT NULL,
5-
published INTEGER NOT NULL,
6-
blog INTEGER NOT NULL,
7-
byline TEXT
8-
) STRICT;
9-
INSERT INTO Post__butane_tmp SELECT "id", title, body, published, blog, byline FROM Post;
10-
DROP TABLE Post;
11-
ALTER TABLE Post__butane_tmp RENAME TO Post;
1+
ALTER TABLE Post DROP COLUMN likes;

examples/getting_started_async/src/butane_migrations.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ pub fn get_migrations() -> Result<MemMigrations, butane::Error> {
330330
},
331331
"down": {
332332
"pg": "ALTER TABLE Post DROP COLUMN likes;\n",
333-
"sqlite": "CREATE TABLE Post__butane_tmp (\n\"id\" INTEGER NOT NULL PRIMARY KEY,\ntitle TEXT NOT NULL,\nbody TEXT NOT NULL,\npublished INTEGER NOT NULL,\nblog INTEGER NOT NULL,\nbyline TEXT\n) STRICT;\nINSERT INTO Post__butane_tmp SELECT \"id\", title, body, published, blog, byline FROM Post;\nDROP TABLE Post;\nALTER TABLE Post__butane_tmp RENAME TO Post;\n"
333+
"sqlite": "ALTER TABLE Post DROP COLUMN likes;\n"
334334
}
335335
},
336336
"20240115_023841384_dbconstraints": {

examples/newtype/.butane/migrations/20240406_035726416_tags/sqlite_down.sql

+1-13
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,4 @@ FOREIGN KEY (has) REFERENCES "Tag"("tag")
77
CREATE TABLE Tag (
88
"tag" TEXT NOT NULL PRIMARY KEY
99
) STRICT;
10-
CREATE TABLE Post__butane_tmp (
11-
"id" BLOB NOT NULL PRIMARY KEY,
12-
title TEXT NOT NULL,
13-
body TEXT NOT NULL,
14-
published INTEGER NOT NULL,
15-
blog BLOB NOT NULL,
16-
byline TEXT,
17-
likes INTEGER NOT NULL,
18-
FOREIGN KEY (blog) REFERENCES Blog("id")
19-
) STRICT;
20-
INSERT INTO Post__butane_tmp SELECT "id", title, body, published, blog, byline, likes FROM Post;
21-
DROP TABLE Post;
22-
ALTER TABLE Post__butane_tmp RENAME TO Post;
10+
ALTER TABLE Post DROP COLUMN tags;

examples/newtype/src/butane_migrations.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ pub fn get_migrations() -> Result<MemMigrations, butane::Error> {
377377
},
378378
"down": {
379379
"pg": "CREATE TABLE Post_tags_Many (\n\"owner\" BYTEA NOT NULL,\nhas TEXT NOT NULL\n);\nCREATE TABLE \"Tag\" (\n\"tag\" TEXT NOT NULL PRIMARY KEY\n);\nALTER TABLE Post DROP COLUMN tags;\nALTER TABLE Post_tags_Many ADD FOREIGN KEY (\"owner\") REFERENCES Post(\"id\");\nALTER TABLE Post_tags_Many ADD FOREIGN KEY (has) REFERENCES \"Tag\"(\"tag\");\n",
380-
"sqlite": "CREATE TABLE Post_tags_Many (\n\"owner\" BLOB NOT NULL,\nhas TEXT NOT NULL,\nFOREIGN KEY (\"owner\") REFERENCES Post(\"id\")\nFOREIGN KEY (has) REFERENCES \"Tag\"(\"tag\")\n) STRICT;\nCREATE TABLE Tag (\n\"tag\" TEXT NOT NULL PRIMARY KEY\n) STRICT;\nCREATE TABLE Post__butane_tmp (\n\"id\" BLOB NOT NULL PRIMARY KEY,\ntitle TEXT NOT NULL,\nbody TEXT NOT NULL,\npublished INTEGER NOT NULL,\nblog BLOB NOT NULL,\nbyline TEXT,\nlikes INTEGER NOT NULL,\nFOREIGN KEY (blog) REFERENCES Blog(\"id\")\n) STRICT;\nINSERT INTO Post__butane_tmp SELECT \"id\", title, body, published, blog, byline, likes FROM Post;\nDROP TABLE Post;\nALTER TABLE Post__butane_tmp RENAME TO Post;\n"
380+
"sqlite": "CREATE TABLE Post_tags_Many (\n\"owner\" BLOB NOT NULL,\nhas TEXT NOT NULL,\nFOREIGN KEY (\"owner\") REFERENCES Post(\"id\")\nFOREIGN KEY (has) REFERENCES \"Tag\"(\"tag\")\n) STRICT;\nCREATE TABLE Tag (\n\"tag\" TEXT NOT NULL PRIMARY KEY\n) STRICT;\nALTER TABLE Post DROP COLUMN tags;\n"
381381
}
382382
}
383383
},

0 commit comments

Comments
 (0)