Skip to content

Commit d0d6aa2

Browse files
authored
Merge pull request #81 from powersync-ja/serde-schema-decoding
Use serde to read tables and indexes from schema
2 parents 3dde911 + e3f03fd commit d0d6aa2

File tree

8 files changed

+260
-272
lines changed

8 files changed

+260
-272
lines changed

Cargo.lock

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

crates/core/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ num-traits = { version = "0.2.15", default-features = false }
1919
num-derive = "0.3"
2020
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
2121
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
22-
streaming-iterator = { version = "0.1.9", default-features = false, features = ["alloc"] }
2322
const_format = "0.2.34"
2423

2524
[dependencies.uuid]

crates/core/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,9 @@ impl From<serde_json::Error> for SQLiteError {
6666
SQLiteError(ResultCode::ABORT, Some(value.to_string()))
6767
}
6868
}
69+
70+
impl From<core::fmt::Error> for SQLiteError {
71+
fn from(value: core::fmt::Error) -> Self {
72+
SQLiteError(ResultCode::INTERNAL, Some(format!("{}", value)))
73+
}
74+
}

crates/core/src/fix_data.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ fn remove_duplicate_key_encoding(key: &str) -> Option<String> {
117117
}
118118

119119
fn powersync_remove_duplicate_key_encoding_impl(
120-
ctx: *mut sqlite::context,
120+
_ctx: *mut sqlite::context,
121121
args: &[*mut sqlite::value],
122122
) -> Result<Option<String>, SQLiteError> {
123123
let arg = args.get(0).ok_or(ResultCode::MISUSE)?;

crates/core/src/schema/management.rs

Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
extern crate alloc;
22

3-
use alloc::format;
43
use alloc::string::String;
54
use alloc::vec::Vec;
5+
use alloc::{format, vec};
66
use core::ffi::c_int;
77

88
use sqlite::{Connection, ResultCode, Value};
@@ -14,6 +14,8 @@ use crate::ext::ExtendedDatabase;
1414
use crate::util::{quote_identifier, quote_json_path};
1515
use crate::{create_auto_tx_function, create_sqlite_text_fn};
1616

17+
use super::Schema;
18+
1719
fn update_tables(db: *mut sqlite::sqlite3, schema: &str) -> Result<(), SQLiteError> {
1820
{
1921
// In a block so that the statement is finalized before dropping tables
@@ -138,87 +140,83 @@ SELECT name, internal_name, local_only FROM powersync_tables WHERE name NOT IN (
138140

139141
fn update_indexes(db: *mut sqlite::sqlite3, schema: &str) -> Result<(), SQLiteError> {
140142
let mut statements: Vec<String> = alloc::vec![];
143+
let schema = serde_json::from_str::<Schema>(schema)?;
144+
let mut expected_index_names: Vec<String> = vec![];
141145

142146
{
143147
// In a block so that the statement is finalized before dropping indexes
144148
// language=SQLite
145-
let statement = db.prepare_v2("\
146-
SELECT
147-
powersync_internal_table_name(tables.value) as table_name,
148-
(powersync_internal_table_name(tables.value) || '__' || json_extract(indexes.value, '$.name')) as index_name,
149-
json_extract(indexes.value, '$.columns') as index_columns,
150-
ifnull(sqlite_master.sql, '') as sql
151-
FROM json_each(json_extract(?, '$.tables')) tables
152-
CROSS JOIN json_each(json_extract(tables.value, '$.indexes')) indexes
153-
LEFT JOIN sqlite_master ON sqlite_master.name = index_name AND sqlite_master.type = 'index'
154-
").into_db_result(db)?;
155-
statement.bind_text(1, schema, sqlite::Destructor::STATIC)?;
149+
let find_index =
150+
db.prepare_v2("SELECT sql FROM sqlite_master WHERE name = ? AND type = 'index'")?;
156151

157-
while statement.step().into_db_result(db)? == ResultCode::ROW {
158-
let table_name = statement.column_text(0)?;
159-
let index_name = statement.column_text(1)?;
160-
let columns = statement.column_text(2)?;
161-
let existing_sql = statement.column_text(3)?;
162-
163-
// language=SQLite
164-
let stmt2 = db.prepare_v2("select json_extract(e.value, '$.name') as name, json_extract(e.value, '$.type') as type, json_extract(e.value, '$.ascending') as ascending from json_each(?) e")?;
165-
stmt2.bind_text(1, columns, sqlite::Destructor::STATIC)?;
166-
167-
let mut column_values: Vec<String> = alloc::vec![];
168-
while stmt2.step()? == ResultCode::ROW {
169-
let name = stmt2.column_text(0)?;
170-
let type_name = stmt2.column_text(1)?;
171-
let ascending = stmt2.column_int(2) != 0;
172-
173-
if ascending {
174-
let value = format!(
152+
for table in &schema.tables {
153+
let table_name = table.internal_name();
154+
155+
for index in &table.indexes {
156+
let index_name = format!("{}__{}", table_name, &index.name);
157+
158+
let existing_sql = {
159+
find_index.reset()?;
160+
find_index.bind_text(1, &index_name, sqlite::Destructor::STATIC)?;
161+
162+
let result = if let ResultCode::ROW = find_index.step()? {
163+
Some(find_index.column_text(0)?)
164+
} else {
165+
None
166+
};
167+
168+
result
169+
};
170+
171+
let mut column_values: Vec<String> = alloc::vec![];
172+
for indexed_column in &index.columns {
173+
let mut value = format!(
175174
"CAST(json_extract(data, {:}) as {:})",
176-
quote_json_path(name),
177-
type_name
178-
);
179-
column_values.push(value);
180-
} else {
181-
let value = format!(
182-
"CAST(json_extract(data, {:}) as {:}) DESC",
183-
quote_json_path(name),
184-
type_name
175+
quote_json_path(&indexed_column.name),
176+
&indexed_column.type_name
185177
);
178+
179+
if !indexed_column.ascending {
180+
value += " DESC";
181+
}
182+
186183
column_values.push(value);
187184
}
188-
}
189185

190-
let sql = format!(
191-
"CREATE INDEX {} ON {}({})",
192-
quote_identifier(index_name),
193-
quote_identifier(table_name),
194-
column_values.join(", ")
195-
);
196-
if existing_sql == "" {
197-
statements.push(sql);
198-
} else if existing_sql != sql {
199-
statements.push(format!("DROP INDEX {}", quote_identifier(index_name)));
200-
statements.push(sql);
186+
let sql = format!(
187+
"CREATE INDEX {} ON {}({})",
188+
quote_identifier(&index_name),
189+
quote_identifier(&table_name),
190+
column_values.join(", ")
191+
);
192+
193+
if existing_sql.is_none() {
194+
statements.push(sql);
195+
} else if existing_sql != Some(&sql) {
196+
statements.push(format!("DROP INDEX {}", quote_identifier(&index_name)));
197+
statements.push(sql);
198+
}
199+
200+
expected_index_names.push(index_name);
201201
}
202202
}
203203

204204
// In a block so that the statement is finalized before dropping indexes
205205
// language=SQLite
206-
let statement = db.prepare_v2("\
207-
WITH schema_indexes AS (
208-
SELECT
209-
powersync_internal_table_name(tables.value) as table_name,
210-
(powersync_internal_table_name(tables.value) || '__' || json_extract(indexes.value, '$.name')) as index_name
211-
FROM json_each(json_extract(?1, '$.tables')) tables
212-
CROSS JOIN json_each(json_extract(tables.value, '$.indexes')) indexes
213-
)
206+
let statement = db
207+
.prepare_v2(
208+
"\
214209
SELECT
215210
sqlite_master.name as index_name
216211
FROM sqlite_master
217212
WHERE sqlite_master.type = 'index'
218213
AND sqlite_master.name GLOB 'ps_data_*'
219-
AND sqlite_master.name NOT IN (SELECT index_name FROM schema_indexes)
220-
").into_db_result(db)?;
221-
statement.bind_text(1, schema, sqlite::Destructor::STATIC)?;
214+
AND sqlite_master.name NOT IN (SELECT value FROM json_each(?))
215+
",
216+
)
217+
.into_db_result(db)?;
218+
let json_names = serde_json::to_string(&expected_index_names)?;
219+
statement.bind_text(1, &json_names, sqlite::Destructor::STATIC)?;
222220

223221
while statement.step()? == ResultCode::ROW {
224222
let name = statement.column_text(0)?;

crates/core/src/schema/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
mod management;
22
mod table_info;
33

4+
use alloc::vec::Vec;
5+
use serde::Deserialize;
46
use sqlite::ResultCode;
57
use sqlite_nostd as sqlite;
6-
pub use table_info::{
7-
ColumnInfo, ColumnNameAndTypeStatement, DiffIncludeOld, TableInfo, TableInfoFlags,
8-
};
8+
pub use table_info::{Column, DiffIncludeOld, Table, TableInfoFlags};
9+
10+
#[derive(Deserialize)]
11+
pub struct Schema {
12+
tables: Vec<table_info::Table>,
13+
}
914

1015
pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
1116
management::register(db)

0 commit comments

Comments
 (0)