@@ -14,7 +14,7 @@ use sqlite::{Connection, ResultCode, Value};
1414
1515use crate :: error:: { PSResult , PowerSyncError } ;
1616use crate :: ext:: ExtendedDatabase ;
17- use crate :: schema:: inspection:: ExistingView ;
17+ use crate :: schema:: inspection:: { ExistingTable , ExistingView } ;
1818use crate :: state:: DatabaseState ;
1919use crate :: util:: { quote_identifier, quote_json_path} ;
2020use crate :: views:: {
@@ -25,112 +25,62 @@ use crate::{create_auto_tx_function, create_sqlite_text_fn};
2525
2626use super :: Schema ;
2727
28- fn update_tables ( db : * mut sqlite:: sqlite3 , schema : & str ) -> Result < ( ) , PowerSyncError > {
29- {
30- // In a block so that the statement is finalized before dropping tables
31- // language=SQLite
32- let statement = db
33- . prepare_v2 (
34- "\
35- SELECT
36- json_extract(json_each.value, '$.name') as name,
37- powersync_internal_table_name(json_each.value) as internal_name,
38- ifnull(json_extract(json_each.value, '$.local_only'), 0) as local_only
39- FROM json_each(json_extract(?, '$.tables'))
40- WHERE name NOT IN (SELECT name FROM powersync_tables)" ,
41- )
42- . into_db_result ( db) ?;
43- statement. bind_text ( 1 , schema, sqlite:: Destructor :: STATIC ) ?;
44-
45- while statement. step ( ) . into_db_result ( db) ? == ResultCode :: ROW {
46- let name = statement. column_text ( 0 ) ?;
47- let internal_name = statement. column_text ( 1 ) ?;
48- let local_only = statement. column_int ( 2 ) != 0 ;
49-
50- db. exec_safe ( & format ! (
51- "CREATE TABLE {:}(id TEXT PRIMARY KEY NOT NULL, data TEXT)" ,
52- quote_identifier( internal_name)
53- ) )
54- . into_db_result ( db) ?;
28+ fn update_tables ( db : * mut sqlite:: sqlite3 , schema : & Schema ) -> Result < ( ) , PowerSyncError > {
29+ let existing_tables = ExistingTable :: list ( db) ?;
30+ let mut existing_tables = {
31+ let mut map = BTreeMap :: new ( ) ;
32+ for table in & existing_tables {
33+ map. insert ( & * table. name , table) ;
34+ }
35+ map
36+ } ;
5537
56- if !local_only {
57- // MOVE data if any
58- db. exec_text (
59- & format ! (
60- "INSERT INTO {:}(id, data)
61- SELECT id, data
62- FROM ps_untyped
63- WHERE type = ?" ,
64- quote_identifier( internal_name)
65- ) ,
66- name,
67- )
38+ {
39+ // In a block so that all statements are finalized before dropping tables.
40+ for table in & schema. tables {
41+ if let Some ( _) = existing_tables. remove ( & * table. name ) {
42+ // This table exists already, nothing to do.
43+ // TODO: Handle switch between local only <-> regular tables?
44+ } else {
45+ // New table.
46+ let quoted_internal_name = quote_identifier ( & table. internal_name ( ) ) ;
47+
48+ db. exec_safe ( & format ! (
49+ "CREATE TABLE {:}(id TEXT PRIMARY KEY NOT NULL, data TEXT)" ,
50+ quoted_internal_name
51+ ) )
6852 . into_db_result ( db) ?;
6953
70- // language=SQLite
71- db. exec_text (
72- "DELETE
73- FROM ps_untyped
74- WHERE type = ?" ,
75- name,
76- ) ?;
77- }
78-
79- if !local_only {
80- // MOVE data if any
81- db. exec_text (
82- & format ! (
83- "INSERT INTO {:}(id, data)
54+ if !table. local_only ( ) {
55+ // MOVE data if any
56+ db. exec_text (
57+ & format ! (
58+ "INSERT INTO {:}(id, data)
8459 SELECT id, data
8560 FROM ps_untyped
8661 WHERE type = ?" ,
87- quote_identifier( internal_name)
88- ) ,
89- name,
90- )
91- . into_db_result ( db) ?;
92-
93- // language=SQLite
94- db. exec_text (
95- "DELETE
96- FROM ps_untyped
97- WHERE type = ?" ,
98- name,
99- ) ?;
62+ quoted_internal_name
63+ ) ,
64+ & table. name ,
65+ )
66+ . into_db_result ( db) ?;
67+
68+ // language=SQLite
69+ db. exec_text ( "DELETE FROM ps_untyped WHERE type = ?" , & table. name ) ?;
70+ }
10071 }
10172 }
102- }
103-
104- let mut tables_to_drop: Vec < String > = alloc:: vec![ ] ;
105-
106- {
107- // In a block so that the statement is finalized before dropping tables
108- // language=SQLite
109- let statement = db
110- . prepare_v2 (
111- "\
112- SELECT name, internal_name, local_only FROM powersync_tables WHERE name NOT IN (
113- SELECT json_extract(json_each.value, '$.name')
114- FROM json_each(json_extract(?, '$.tables'))
115- )" ,
116- )
117- . into_db_result ( db) ?;
118- statement. bind_text ( 1 , schema, sqlite:: Destructor :: STATIC ) ?;
119-
120- while statement. step ( ) ? == ResultCode :: ROW {
121- let name = statement. column_text ( 0 ) ?;
122- let internal_name = statement. column_text ( 1 ) ?;
123- let local_only = statement. column_int ( 2 ) != 0 ;
124-
125- tables_to_drop. push ( String :: from ( internal_name) ) ;
12673
127- if !local_only {
74+ // Remaining tables need to be dropped. But first, we want to move their contents to
75+ // ps_untyped.
76+ for remaining in existing_tables. values ( ) {
77+ if !remaining. local_only {
12878 db. exec_text (
12979 & format ! (
13080 "INSERT INTO ps_untyped(type, id, data) SELECT ?, id, data FROM {:}" ,
131- quote_identifier( internal_name)
81+ quote_identifier( & remaining . internal_name)
13282 ) ,
133- name,
83+ & remaining . name ,
13484 )
13585 . into_db_result ( db) ?;
13686 }
@@ -139,8 +89,8 @@ SELECT name, internal_name, local_only FROM powersync_tables WHERE name NOT IN (
13989
14090 // We cannot have any open queries on sqlite_master at the point that we drop tables, otherwise
14191 // we get "table is locked" errors.
142- for internal_name in tables_to_drop {
143- let q = format ! ( "DROP TABLE {:}" , quote_identifier( & internal_name) ) ;
92+ for remaining in existing_tables . values ( ) {
93+ let q = format ! ( "DROP TABLE {:}" , quote_identifier( & remaining . internal_name) ) ;
14494 db. exec_safe ( & q) . into_db_result ( db) ?;
14595 }
14696
@@ -305,7 +255,7 @@ fn powersync_replace_schema_impl(
305255 // language=SQLite
306256 db. exec_safe ( "SELECT powersync_init()" ) . into_db_result ( db) ?;
307257
308- update_tables ( db, schema ) ?;
258+ update_tables ( db, & parsed_schema ) ?;
309259 update_indexes ( db, & parsed_schema) ?;
310260 update_views ( db, & parsed_schema) ?;
311261
0 commit comments