Skip to content

Commit 80177fe

Browse files
committed
Support down_migrations.
1 parent 1504864 commit 80177fe

File tree

1 file changed

+81
-26
lines changed

1 file changed

+81
-26
lines changed

crates/core/src/view_admin.rs

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
extern crate alloc;
22

33
use alloc::format;
4-
use alloc::string::String;
4+
use alloc::string::{String, ToString};
5+
use alloc::vec::Vec;
56
use core::ffi::c_int;
67
use core::slice;
78

89
use sqlite::{ResultCode, Value};
910
use sqlite_nostd as sqlite;
1011
use sqlite_nostd::{Connection, Context};
1112

12-
use crate::{create_auto_tx_function, create_sqlite_text_fn};
1313
use crate::error::{PSResult, SQLiteError};
1414
use crate::util::quote_identifier;
15+
use crate::{create_auto_tx_function, create_sqlite_text_fn};
1516

1617
fn powersync_drop_view_impl(
1718
ctx: *mut sqlite::context,
@@ -62,7 +63,9 @@ fn powersync_internal_table_name_impl(
6263
let local_db = ctx.db_handle();
6364

6465
// language=SQLite
65-
let stmt1 = local_db.prepare_v2("SELECT json_extract(?1, '$.name') as name, ifnull(json_extract(?1, '$.local_only'), 0)")?;
66+
let stmt1 = local_db.prepare_v2(
67+
"SELECT json_extract(?1, '$.name') as name, ifnull(json_extract(?1, '$.local_only'), 0)",
68+
)?;
6669
stmt1.bind_text(1, schema, sqlite::Destructor::STATIC)?;
6770

6871
let step_result = stmt1.step()?;
@@ -115,26 +118,80 @@ fn powersync_init_impl(
115118
let local_db = ctx.db_handle();
116119

117120
// language=SQLite
118-
local_db.exec_safe("\
119-
CREATE TABLE IF NOT EXISTS ps_migration(id INTEGER PRIMARY KEY, down_migrations TEXT)")?;
121+
local_db.exec_safe(
122+
"\
123+
CREATE TABLE IF NOT EXISTS ps_migration(id INTEGER PRIMARY KEY, down_migrations TEXT)",
124+
)?;
120125

121126
// language=SQLite
122-
let stmt = local_db.prepare_v2("SELECT ifnull(max(id), 0) as version FROM ps_migration")?;
123-
let rc = stmt.step()?;
127+
let current_version_stmt =
128+
local_db.prepare_v2("SELECT ifnull(max(id), 0) as version FROM ps_migration")?;
129+
let rc = current_version_stmt.step()?;
124130
if rc != ResultCode::ROW {
125131
return Err(SQLiteError::from(ResultCode::ABORT));
126132
}
127133

128-
let version = stmt.column_int(0)?;
129-
130-
if version > 2 {
131-
// We persist down migrations, but don't support running them yet
132-
return Err(SQLiteError(ResultCode::MISUSE, Some(String::from("Downgrade not supported"))));
134+
const CODE_VERSION: i32 = 2;
135+
136+
let mut current_version = current_version_stmt.column_int(0)?;
137+
138+
while current_version > CODE_VERSION {
139+
// Run down migrations.
140+
// This is rare, we don't worry about optimizing this.
141+
142+
current_version_stmt.reset()?;
143+
144+
let down_migrations_stmt = local_db.prepare_v2("select e.value ->> 'sql' as sql from (select id, down_migrations from ps_migration where id > ?1 order by id desc limit 1) m, json_each(m.down_migrations) e")?;
145+
down_migrations_stmt.bind_int(1, CODE_VERSION);
146+
147+
let mut down_sql: Vec<String> = alloc::vec![];
148+
149+
while down_migrations_stmt.step()? == ResultCode::ROW {
150+
let sql = down_migrations_stmt.column_text(0)?;
151+
down_sql.push(sql.to_string());
152+
}
153+
154+
for sql in down_sql {
155+
let rs = local_db.exec_safe(&sql);
156+
if let Err(code) = rs {
157+
return Err(SQLiteError(
158+
code,
159+
Some(format!(
160+
"Down migration failed for {:} {:}",
161+
current_version, sql
162+
)),
163+
));
164+
}
165+
}
166+
167+
// Refresh the version
168+
current_version_stmt.reset()?;
169+
let rc = current_version_stmt.step()?;
170+
if rc != ResultCode::ROW {
171+
return Err(SQLiteError(
172+
rc,
173+
Some("Down migration failed - could not get version".to_string()),
174+
));
175+
}
176+
let new_version = current_version_stmt.column_int(0)?;
177+
if new_version >= current_version {
178+
// Database down from version $currentVersion to $version failed - version not updated after dow migration
179+
return Err(SQLiteError(
180+
ResultCode::ABORT,
181+
Some(format!(
182+
"Down migration failed - version not updated from {:}",
183+
current_version
184+
)),
185+
));
186+
}
187+
current_version = new_version;
133188
}
134189

135-
if version < 1 {
190+
if current_version < 1 {
136191
// language=SQLite
137-
local_db.exec_safe("
192+
local_db
193+
.exec_safe(
194+
"
138195
CREATE TABLE ps_oplog(
139196
bucket TEXT NOT NULL,
140197
op_id INTEGER NOT NULL,
@@ -164,31 +221,28 @@ CREATE TABLE ps_untyped(type TEXT NOT NULL, id TEXT NOT NULL, data TEXT, PRIMARY
164221
CREATE TABLE ps_crud (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT);
165222
166223
INSERT INTO ps_migration(id, down_migrations) VALUES(1, NULL);
167-
").into_db_result(local_db)?;
224+
",
225+
)
226+
.into_db_result(local_db)?;
168227
}
169228

170-
if version < 2 {
229+
if current_version < 2 {
171230
// language=SQLite
172231
local_db.exec_safe("\
173232
CREATE TABLE ps_tx(id INTEGER PRIMARY KEY NOT NULL, current_tx INTEGER, next_tx INTEGER);
174233
INSERT INTO ps_tx(id, current_tx, next_tx) VALUES(1, NULL, 1);
175234
176235
ALTER TABLE ps_crud ADD COLUMN tx_id INTEGER;
177236
178-
INSERT INTO ps_migration(id, down_migrations) VALUES(2, json_array(json_object('sql', 'DELETE FROM ps_migrations WHERE id >= 2', 'params', json_array()), json_object('sql', 'DROP TABLE ps_tx', 'params', json_array()), json_object('sql', 'ALTER TABLE ps_crud DROP COLUMN tx_id', 'params', json_array())));
237+
INSERT INTO ps_migration(id, down_migrations) VALUES(2, json_array(json_object('sql', 'DELETE FROM ps_migration WHERE id >= 2', 'params', json_array()), json_object('sql', 'DROP TABLE ps_tx', 'params', json_array()), json_object('sql', 'ALTER TABLE ps_crud DROP COLUMN tx_id', 'params', json_array())));
179238
").into_db_result(local_db)?;
180239
}
181240

182241
Ok(String::from(""))
183242
}
184243

185-
186244
create_auto_tx_function!(powersync_init_tx, powersync_init_impl);
187-
create_sqlite_text_fn!(
188-
powersync_init,
189-
powersync_init_tx,
190-
"powersync_init"
191-
);
245+
create_sqlite_text_fn!(powersync_init, powersync_init_tx, "powersync_init");
192246

193247
pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
194248
// This entire module is just making it easier to edit sqlite_master using queries.
@@ -259,7 +313,6 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
259313
None,
260314
)?;
261315

262-
263316
db.create_function_v2(
264317
"powersync_internal_table_name",
265318
1,
@@ -324,14 +377,16 @@ BEGIN
324377
END;")?;
325378

326379
// language=SQLite
327-
db.exec_safe("\
380+
db.exec_safe(
381+
"\
328382
CREATE TEMP VIEW powersync_tables(name, internal_name, local_only)
329383
AS SELECT
330384
powersync_external_table_name(name) as name,
331385
name as internal_name,
332386
name GLOB 'ps_data_local__*' as local_only
333387
FROM sqlite_master
334-
WHERE type = 'table' AND name GLOB 'ps_data_*';")?;
388+
WHERE type = 'table' AND name GLOB 'ps_data_*';",
389+
)?;
335390

336391
Ok(())
337392
}

0 commit comments

Comments
 (0)