Skip to content

Commit 3335c16

Browse files
committed
Fix index creation.
1 parent c007190 commit 3335c16

File tree

2 files changed

+166
-17
lines changed

2 files changed

+166
-17
lines changed

packages/powersync/lib/src/schema_logic.dart

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,33 @@ void _createTablesAndIndexes(sqlite.Database db, Schema schema) {
239239
"SELECT name, sql FROM sqlite_master WHERE type='index' AND name GLOB 'ps_data_*'");
240240

241241
final Set<String> remainingTables = {};
242-
final Map<String, String> remainingIndexes = {};
242+
final Map<String, String> indexesToDrop = {};
243+
final List<String> createIndexes = [];
243244
for (final row in existingTableRows) {
244245
remainingTables.add(row['name'] as String);
245246
}
246247
for (final row in existingIndexRows) {
247-
remainingIndexes[row['name'] as String] = row['sql'] as String;
248+
indexesToDrop[row['name'] as String] = row['sql'] as String;
249+
}
250+
251+
for (final table in schema.tables) {
252+
for (final index in table.indexes) {
253+
final fullName = index.fullName(table);
254+
final sql = index.toSqlDefinition(table);
255+
if (indexesToDrop.containsKey(fullName)) {
256+
final existingSql = indexesToDrop[fullName];
257+
if (existingSql == sql) {
258+
// No change (don't drop)
259+
indexesToDrop.remove(fullName);
260+
} else {
261+
// Drop and create
262+
createIndexes.add(sql);
263+
}
264+
} else {
265+
// New index - create
266+
createIndexes.add(sql);
267+
}
268+
}
248269
}
249270

250271
for (final table in schema.tables) {
@@ -274,26 +295,16 @@ void _createTablesAndIndexes(sqlite.Database db, Schema schema) {
274295
FROM ps_untyped
275296
WHERE type = ?""", [table.name]);
276297
}
277-
278-
for (final index in table.indexes) {
279-
final fullName = index.fullName(table);
280-
final sql = index.toSqlDefinition(table);
281-
if (remainingIndexes.containsKey(fullName)) {
282-
final existingSql = remainingIndexes[fullName];
283-
if (existingSql == sql) {
284-
continue;
285-
} else {
286-
db.execute('DROP INDEX ${quoteIdentifier(fullName)}');
287-
}
288-
}
289-
db.execute(sql);
290-
}
291298
}
292299

293-
for (final indexName in remainingIndexes.keys) {
300+
for (final indexName in indexesToDrop.keys) {
294301
db.execute('DROP INDEX ${quoteIdentifier(indexName)}');
295302
}
296303

304+
for (final sql in createIndexes) {
305+
db.execute(sql);
306+
}
307+
297308
for (final tableName in remainingTables) {
298309
final typeMatch = RegExp("^ps_data__(.+)\$").firstMatch(tableName);
299310
if (typeMatch != null) {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import 'package:powersync/powersync.dart';
2+
import 'package:test/test.dart';
3+
4+
import 'util.dart';
5+
6+
const testId = "2290de4f-0488-4e50-abed-f8e8eb1d0b42";
7+
final schema = Schema([
8+
Table('assets', [
9+
Column.text('created_at'),
10+
Column.text('make'),
11+
Column.text('model'),
12+
Column.text('serial_number'),
13+
Column.integer('quantity'),
14+
Column.text('user_id'),
15+
Column.real('weight'),
16+
Column.text('description'),
17+
], indexes: [
18+
Index('makemodel', [IndexedColumn('make'), IndexedColumn('model')])
19+
]),
20+
Table('customers', [Column.text('name'), Column.text('email')]),
21+
Table.insertOnly('logs', [Column.text('level'), Column.text('content')]),
22+
Table.localOnly('credentials', [Column.text('key'), Column.text('value')]),
23+
]);
24+
25+
void main() {
26+
group('Schema Tests', () {
27+
late String path;
28+
29+
setUp(() async {
30+
path = dbPath();
31+
await cleanDb(path: path);
32+
});
33+
34+
test('Schema versioning', () async {
35+
// Test that powersync_replace_schema() is a no-op when the schema is not
36+
// modified.
37+
38+
final powersync = await setupPowerSync(path: path, schema: schema);
39+
40+
final versionBefore = await powersync.get('PRAGMA schema_version');
41+
await powersync.updateSchema(schema);
42+
final versionAfter = await powersync.get('PRAGMA schema_version');
43+
44+
// No change
45+
expect(versionAfter['schema_version'],
46+
equals(versionBefore['schema_version']));
47+
48+
final schema2 = Schema([
49+
Table('assets', [
50+
Column.text('created_at'),
51+
Column.text('make'),
52+
Column.text('model'),
53+
Column.text('serial_number'),
54+
Column.integer('quantity'),
55+
Column.text('user_id'),
56+
Column.real('weights'),
57+
Column.text('description'),
58+
], indexes: [
59+
Index('makemodel', [IndexedColumn('make'), IndexedColumn('model')])
60+
]),
61+
Table('customers', [Column.text('name'), Column.text('email')]),
62+
Table.insertOnly(
63+
'logs', [Column.text('level'), Column.text('content')]),
64+
Table.localOnly(
65+
'credentials', [Column.text('key'), Column.text('value')]),
66+
]);
67+
68+
await powersync.updateSchema(schema2);
69+
70+
final versionAfter2 = await powersync.get('PRAGMA schema_version');
71+
72+
// Updated
73+
expect(versionAfter2['schema_version'],
74+
greaterThan(versionAfter['schema_version']));
75+
76+
final schema3 = Schema([
77+
Table('assets', [
78+
Column.text('created_at'),
79+
Column.text('make'),
80+
Column.text('model'),
81+
Column.text('serial_number'),
82+
Column.integer('quantity'),
83+
Column.text('user_id'),
84+
Column.real('weights'),
85+
Column.text('description'),
86+
], indexes: [
87+
Index('makemodel',
88+
[IndexedColumn('make'), IndexedColumn.descending('model')])
89+
]),
90+
Table('customers', [Column.text('name'), Column.text('email')]),
91+
Table.insertOnly(
92+
'logs', [Column.text('level'), Column.text('content')]),
93+
Table.localOnly(
94+
'credentials', [Column.text('key'), Column.text('value')]),
95+
]);
96+
97+
await powersync.updateSchema(schema3);
98+
99+
final versionAfter3 = await powersync.get('PRAGMA schema_version');
100+
101+
// Updated again (index)
102+
expect(versionAfter3['schema_version'],
103+
greaterThan(versionAfter2['schema_version']));
104+
});
105+
106+
test('Indexing', () async {
107+
final powersync = await setupPowerSync(path: path, schema: schema);
108+
109+
final results = await powersync.execute(
110+
'EXPLAIN QUERY PLAN SELECT * FROM assets WHERE make = ?', ['test']);
111+
112+
expect(results[0]['detail'],
113+
contains('USING INDEX ps_data__assets__makemodel'));
114+
115+
// Now drop the index
116+
final schema2 = Schema([
117+
Table('assets', [
118+
Column.text('created_at'),
119+
Column.text('make'),
120+
Column.text('model'),
121+
Column.text('serial_number'),
122+
Column.integer('quantity'),
123+
Column.text('user_id'),
124+
Column.real('weight'),
125+
Column.text('description'),
126+
], indexes: []),
127+
]);
128+
await powersync.updateSchema(schema2);
129+
130+
// Execute instead of getAll so that we don't get a cached query plan
131+
// from a different connection
132+
final results2 = await powersync.execute(
133+
'EXPLAIN QUERY PLAN SELECT * FROM assets WHERE make = ?', ['test']);
134+
135+
expect(results2[0]['detail'], contains('SCAN'));
136+
});
137+
});
138+
}

0 commit comments

Comments
 (0)