@syncular/server 0.15.11 → 0.15.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/d1-storage.js +10 -2
- package/dist/postgres-storage.js +15 -2
- package/dist/relational-rows.d.ts +8 -5
- package/dist/relational-rows.js +14 -6
- package/dist/sqlite-storage.js +10 -3
- package/package.json +2 -2
- package/src/d1-storage.ts +20 -2
- package/src/postgres-storage.ts +26 -2
- package/src/relational-rows.ts +15 -5
- package/src/sqlite-storage.ts +20 -3
package/dist/d1-storage.js
CHANGED
|
@@ -309,15 +309,23 @@ export class D1ServerStorage {
|
|
|
309
309
|
const layouts = parseLayouts(marker?.layouts);
|
|
310
310
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
311
311
|
const existing = new Map();
|
|
312
|
+
const existingIndexes = new Map();
|
|
312
313
|
for (const table of schema.tables.values()) {
|
|
314
|
+
const escapedTableName = table.name.replaceAll('"', '""');
|
|
313
315
|
const { results } = await this.#db
|
|
314
|
-
.prepare(`PRAGMA table_info("${
|
|
316
|
+
.prepare(`PRAGMA table_info("${escapedTableName}")`)
|
|
315
317
|
.all();
|
|
316
318
|
if (results.length > 0) {
|
|
317
319
|
existing.set(table.name, new Set(results.map((c) => c.name)));
|
|
320
|
+
const indexes = await this.#db
|
|
321
|
+
.prepare(`PRAGMA index_list("${escapedTableName}")`)
|
|
322
|
+
.all();
|
|
323
|
+
existingIndexes.set(table.name, new Set(indexes.results
|
|
324
|
+
.filter((index) => index.origin === 'c')
|
|
325
|
+
.map((index) => index.name)));
|
|
318
326
|
}
|
|
319
327
|
}
|
|
320
|
-
for (const statement of schemaDdl(schema, existing, 'sqlite')) {
|
|
328
|
+
for (const statement of schemaDdl(schema, existing, 'sqlite', existingIndexes)) {
|
|
321
329
|
await this.#db.exec(`${statement.replace(/\s+/g, ' ')};`);
|
|
322
330
|
}
|
|
323
331
|
// Migration rewrite: payload re-encode on layout change and/or
|
package/dist/postgres-storage.js
CHANGED
|
@@ -449,7 +449,7 @@ export class PostgresServerStorage {
|
|
|
449
449
|
}
|
|
450
450
|
if (stored === undefined || stored < schema.version) {
|
|
451
451
|
// Introspect existing app tables, apply the migration subset
|
|
452
|
-
// (CREATE TABLE / ADD COLUMN /
|
|
452
|
+
// (CREATE TABLE / ADD COLUMN / rebuild indexes), then rewrite stored
|
|
453
453
|
// rows (payload re-encode for layout changes and/or projection
|
|
454
454
|
// backfill for flipped-on materialization) — all inside one
|
|
455
455
|
// transaction (Postgres DDL is transactional — a failed bump leaves
|
|
@@ -460,11 +460,24 @@ export class PostgresServerStorage {
|
|
|
460
460
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
461
461
|
await this.#exec.transaction(async (client) => {
|
|
462
462
|
const existing = new Map();
|
|
463
|
+
const existingIndexes = new Map();
|
|
463
464
|
for (const table of schema.tables.values()) {
|
|
464
465
|
const { rows } = await client.query(`SELECT column_name FROM information_schema.columns
|
|
465
466
|
WHERE table_schema = current_schema() AND table_name = $1`, [table.name]);
|
|
466
467
|
if (rows.length > 0) {
|
|
467
468
|
existing.set(table.name, new Set(rows.map((r) => r.column_name)));
|
|
469
|
+
const indexes = await client.query(`SELECT index_class.relname AS index_name
|
|
470
|
+
FROM pg_catalog.pg_class AS table_class
|
|
471
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
472
|
+
ON namespace.oid = table_class.relnamespace
|
|
473
|
+
JOIN pg_catalog.pg_index AS index_meta
|
|
474
|
+
ON index_meta.indrelid = table_class.oid
|
|
475
|
+
JOIN pg_catalog.pg_class AS index_class
|
|
476
|
+
ON index_class.oid = index_meta.indexrelid
|
|
477
|
+
WHERE namespace.nspname = current_schema()
|
|
478
|
+
AND table_class.relname = $1
|
|
479
|
+
AND NOT index_meta.indisprimary`, [table.name]);
|
|
480
|
+
existingIndexes.set(table.name, new Set(indexes.rows.map((index) => index.index_name)));
|
|
468
481
|
}
|
|
469
482
|
}
|
|
470
483
|
for (const tableName of retiredTables) {
|
|
@@ -473,7 +486,7 @@ export class PostgresServerStorage {
|
|
|
473
486
|
]);
|
|
474
487
|
await client.query(dropTableDdl(tableName));
|
|
475
488
|
}
|
|
476
|
-
for (const statement of schemaDdl(schema, existing, 'postgres')) {
|
|
489
|
+
for (const statement of schemaDdl(schema, existing, 'postgres', existingIndexes)) {
|
|
477
490
|
await client.query(statement);
|
|
478
491
|
}
|
|
479
492
|
for (const table of schema.tables.values()) {
|
|
@@ -76,6 +76,8 @@ export declare function addColumnDdl(table: CompiledTable, existingColumns: Read
|
|
|
76
76
|
* as it is client-side).
|
|
77
77
|
*/
|
|
78
78
|
export declare function createIndexDdl(table: CompiledTable): string[];
|
|
79
|
+
/** Idempotent removal of one Syncular-owned relational projection index. */
|
|
80
|
+
export declare function dropIndexDdl(indexName: string): string;
|
|
79
81
|
/**
|
|
80
82
|
* Convert one decoded row value to its SQL bind value.
|
|
81
83
|
* - boolean → 0/1 on sqlite (INTEGER affinity), native on postgres;
|
|
@@ -229,9 +231,10 @@ export declare function rewritePlan(table: CompiledTable, oldLayout: readonly St
|
|
|
229
231
|
backfill: boolean;
|
|
230
232
|
};
|
|
231
233
|
/**
|
|
232
|
-
* Every DDL statement to bring a database from
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
234
|
+
* Every DDL statement to bring a database from the introspected relational
|
|
235
|
+
* projections to `schema`: CREATE TABLE for absent tables, ADD COLUMN for
|
|
236
|
+
* missing columns, and rebuild declared secondary indexes. Rebuilding during
|
|
237
|
+
* a version bump supports DROP INDEX and same-name index replacement without
|
|
238
|
+
* requiring historical index definitions in the stored column-layout marker.
|
|
236
239
|
*/
|
|
237
|
-
export declare function schemaDdl(schema: CompiledSchema, existingColumnsByTable: ReadonlyMap<string, ReadonlySet<string>>, dialect: RelationalDialect): string[];
|
|
240
|
+
export declare function schemaDdl(schema: CompiledSchema, existingColumnsByTable: ReadonlyMap<string, ReadonlySet<string>>, dialect: RelationalDialect, existingIndexesByTable?: ReadonlyMap<string, ReadonlySet<string>>): string[];
|
package/dist/relational-rows.js
CHANGED
|
@@ -162,6 +162,10 @@ export function createIndexDdl(table) {
|
|
|
162
162
|
return `CREATE ${unique}INDEX IF NOT EXISTS ${quoteIdent(index.name)} ON ${quoteIdent(table.name)} (${columns})`;
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
+
/** Idempotent removal of one Syncular-owned relational projection index. */
|
|
166
|
+
export function dropIndexDdl(indexName) {
|
|
167
|
+
return `DROP INDEX IF EXISTS ${quoteIdent(indexName)}`;
|
|
168
|
+
}
|
|
165
169
|
/**
|
|
166
170
|
* Convert one decoded row value to its SQL bind value.
|
|
167
171
|
* - boolean → 0/1 on sqlite (INTEGER affinity), native on postgres;
|
|
@@ -399,7 +403,7 @@ export function dropTableDdl(tableName) {
|
|
|
399
403
|
*/
|
|
400
404
|
export function assertAppendOnlyMigration(tableName, oldLayout, table) {
|
|
401
405
|
if (oldLayout.length > table.columns.length) {
|
|
402
|
-
throw new Error(`table ${JSON.stringify(tableName)}: the schema removed columns — only
|
|
406
|
+
throw new Error(`table ${JSON.stringify(tableName)}: the schema removed columns — only appending nullable columns is supported; indexes may be created, replaced, or dropped independently`);
|
|
403
407
|
}
|
|
404
408
|
for (let i = 0; i < oldLayout.length; i++) {
|
|
405
409
|
const before = oldLayout[i];
|
|
@@ -503,12 +507,13 @@ export function rewritePlan(table, oldLayout, physicalColumnsBefore) {
|
|
|
503
507
|
return { migrate, backfill };
|
|
504
508
|
}
|
|
505
509
|
/**
|
|
506
|
-
* Every DDL statement to bring a database from
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
+
* Every DDL statement to bring a database from the introspected relational
|
|
511
|
+
* projections to `schema`: CREATE TABLE for absent tables, ADD COLUMN for
|
|
512
|
+
* missing columns, and rebuild declared secondary indexes. Rebuilding during
|
|
513
|
+
* a version bump supports DROP INDEX and same-name index replacement without
|
|
514
|
+
* requiring historical index definitions in the stored column-layout marker.
|
|
510
515
|
*/
|
|
511
|
-
export function schemaDdl(schema, existingColumnsByTable, dialect) {
|
|
516
|
+
export function schemaDdl(schema, existingColumnsByTable, dialect, existingIndexesByTable = new Map()) {
|
|
512
517
|
const out = [];
|
|
513
518
|
for (const table of schema.tables.values()) {
|
|
514
519
|
const existing = existingColumnsByTable.get(table.name);
|
|
@@ -516,6 +521,9 @@ export function schemaDdl(schema, existingColumnsByTable, dialect) {
|
|
|
516
521
|
out.push(createTableDdl(table, dialect));
|
|
517
522
|
}
|
|
518
523
|
else {
|
|
524
|
+
for (const indexName of existingIndexesByTable.get(table.name) ?? []) {
|
|
525
|
+
out.push(dropIndexDdl(indexName));
|
|
526
|
+
}
|
|
519
527
|
out.push(...addColumnDdl(table, existing, dialect));
|
|
520
528
|
}
|
|
521
529
|
out.push(...createIndexDdl(table));
|
package/dist/sqlite-storage.js
CHANGED
|
@@ -138,19 +138,26 @@ export class SqliteServerStorage {
|
|
|
138
138
|
}
|
|
139
139
|
if (marker === null || marker.schema_version < schema.version) {
|
|
140
140
|
// Introspect existing app tables, then apply the migration subset
|
|
141
|
-
// (CREATE TABLE / ADD COLUMN /
|
|
141
|
+
// (CREATE TABLE / ADD COLUMN / rebuild indexes) to reach `schema`, then
|
|
142
142
|
// rewrite stored rows (payload re-encode for layout changes, and/or
|
|
143
143
|
// projection backfill for flipped-on materialization). One
|
|
144
144
|
// transaction: a failed bump leaves no half-state.
|
|
145
145
|
const layouts = parseLayouts(marker?.layouts);
|
|
146
146
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
147
147
|
const existing = new Map();
|
|
148
|
+
const existingIndexes = new Map();
|
|
148
149
|
for (const table of schema.tables.values()) {
|
|
150
|
+
const escapedTableName = table.name.replaceAll('"', '""');
|
|
149
151
|
const columns = this.db
|
|
150
|
-
.query(`PRAGMA table_info("${
|
|
152
|
+
.query(`PRAGMA table_info("${escapedTableName}")`)
|
|
151
153
|
.all();
|
|
152
154
|
if (columns.length > 0) {
|
|
153
155
|
existing.set(table.name, new Set(columns.map((c) => c.name)));
|
|
156
|
+
const indexes = this.db
|
|
157
|
+
.query(`PRAGMA index_list("${escapedTableName}")`)
|
|
158
|
+
.all()
|
|
159
|
+
.filter((index) => index.origin === 'c');
|
|
160
|
+
existingIndexes.set(table.name, new Set(indexes.map((index) => index.name)));
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
this.db.exec('BEGIN IMMEDIATE');
|
|
@@ -161,7 +168,7 @@ export class SqliteServerStorage {
|
|
|
161
168
|
.run(tableName);
|
|
162
169
|
this.db.exec(dropTableDdl(tableName));
|
|
163
170
|
}
|
|
164
|
-
for (const statement of schemaDdl(schema, existing, 'sqlite')) {
|
|
171
|
+
for (const statement of schemaDdl(schema, existing, 'sqlite', existingIndexes)) {
|
|
165
172
|
this.db.exec(statement);
|
|
166
173
|
}
|
|
167
174
|
for (const table of schema.tables.values()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/server",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.13",
|
|
4
4
|
"description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"!dist/**/*.test.d.ts"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@syncular/core": "0.15.
|
|
56
|
+
"@syncular/core": "0.15.13"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@electric-sql/pglite": "^0.5.4"
|
package/src/d1-storage.ts
CHANGED
|
@@ -493,15 +493,33 @@ export class D1ServerStorage implements ServerStorage {
|
|
|
493
493
|
const layouts = parseLayouts(marker?.layouts);
|
|
494
494
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
495
495
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
496
|
+
const existingIndexes = new Map<string, ReadonlySet<string>>();
|
|
496
497
|
for (const table of schema.tables.values()) {
|
|
498
|
+
const escapedTableName = table.name.replaceAll('"', '""');
|
|
497
499
|
const { results } = await this.#db
|
|
498
|
-
.prepare(`PRAGMA table_info("${
|
|
500
|
+
.prepare(`PRAGMA table_info("${escapedTableName}")`)
|
|
499
501
|
.all<{ name: string }>();
|
|
500
502
|
if (results.length > 0) {
|
|
501
503
|
existing.set(table.name, new Set(results.map((c) => c.name)));
|
|
504
|
+
const indexes = await this.#db
|
|
505
|
+
.prepare(`PRAGMA index_list("${escapedTableName}")`)
|
|
506
|
+
.all<{ name: string; origin: string }>();
|
|
507
|
+
existingIndexes.set(
|
|
508
|
+
table.name,
|
|
509
|
+
new Set(
|
|
510
|
+
indexes.results
|
|
511
|
+
.filter((index) => index.origin === 'c')
|
|
512
|
+
.map((index) => index.name),
|
|
513
|
+
),
|
|
514
|
+
);
|
|
502
515
|
}
|
|
503
516
|
}
|
|
504
|
-
for (const statement of schemaDdl(
|
|
517
|
+
for (const statement of schemaDdl(
|
|
518
|
+
schema,
|
|
519
|
+
existing,
|
|
520
|
+
'sqlite',
|
|
521
|
+
existingIndexes,
|
|
522
|
+
)) {
|
|
505
523
|
await this.#db.exec(`${statement.replace(/\s+/g, ' ')};`);
|
|
506
524
|
}
|
|
507
525
|
// Migration rewrite: payload re-encode on layout change and/or
|
package/src/postgres-storage.ts
CHANGED
|
@@ -713,7 +713,7 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
713
713
|
}
|
|
714
714
|
if (stored === undefined || stored < schema.version) {
|
|
715
715
|
// Introspect existing app tables, apply the migration subset
|
|
716
|
-
// (CREATE TABLE / ADD COLUMN /
|
|
716
|
+
// (CREATE TABLE / ADD COLUMN / rebuild indexes), then rewrite stored
|
|
717
717
|
// rows (payload re-encode for layout changes and/or projection
|
|
718
718
|
// backfill for flipped-on materialization) — all inside one
|
|
719
719
|
// transaction (Postgres DDL is transactional — a failed bump leaves
|
|
@@ -726,6 +726,7 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
726
726
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
727
727
|
await this.#exec.transaction(async (client) => {
|
|
728
728
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
729
|
+
const existingIndexes = new Map<string, ReadonlySet<string>>();
|
|
729
730
|
for (const table of schema.tables.values()) {
|
|
730
731
|
const { rows } = await client.query<{ column_name: string }>(
|
|
731
732
|
`SELECT column_name FROM information_schema.columns
|
|
@@ -734,6 +735,24 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
734
735
|
);
|
|
735
736
|
if (rows.length > 0) {
|
|
736
737
|
existing.set(table.name, new Set(rows.map((r) => r.column_name)));
|
|
738
|
+
const indexes = await client.query<{ index_name: string }>(
|
|
739
|
+
`SELECT index_class.relname AS index_name
|
|
740
|
+
FROM pg_catalog.pg_class AS table_class
|
|
741
|
+
JOIN pg_catalog.pg_namespace AS namespace
|
|
742
|
+
ON namespace.oid = table_class.relnamespace
|
|
743
|
+
JOIN pg_catalog.pg_index AS index_meta
|
|
744
|
+
ON index_meta.indrelid = table_class.oid
|
|
745
|
+
JOIN pg_catalog.pg_class AS index_class
|
|
746
|
+
ON index_class.oid = index_meta.indexrelid
|
|
747
|
+
WHERE namespace.nspname = current_schema()
|
|
748
|
+
AND table_class.relname = $1
|
|
749
|
+
AND NOT index_meta.indisprimary`,
|
|
750
|
+
[table.name],
|
|
751
|
+
);
|
|
752
|
+
existingIndexes.set(
|
|
753
|
+
table.name,
|
|
754
|
+
new Set(indexes.rows.map((index) => index.index_name)),
|
|
755
|
+
);
|
|
737
756
|
}
|
|
738
757
|
}
|
|
739
758
|
for (const tableName of retiredTables) {
|
|
@@ -742,7 +761,12 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
742
761
|
]);
|
|
743
762
|
await client.query(dropTableDdl(tableName));
|
|
744
763
|
}
|
|
745
|
-
for (const statement of schemaDdl(
|
|
764
|
+
for (const statement of schemaDdl(
|
|
765
|
+
schema,
|
|
766
|
+
existing,
|
|
767
|
+
'postgres',
|
|
768
|
+
existingIndexes,
|
|
769
|
+
)) {
|
|
746
770
|
await client.query(statement);
|
|
747
771
|
}
|
|
748
772
|
for (const table of schema.tables.values()) {
|
package/src/relational-rows.ts
CHANGED
|
@@ -188,6 +188,11 @@ export function createIndexDdl(table: CompiledTable): string[] {
|
|
|
188
188
|
});
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/** Idempotent removal of one Syncular-owned relational projection index. */
|
|
192
|
+
export function dropIndexDdl(indexName: string): string {
|
|
193
|
+
return `DROP INDEX IF EXISTS ${quoteIdent(indexName)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
191
196
|
/**
|
|
192
197
|
* Convert one decoded row value to its SQL bind value.
|
|
193
198
|
* - boolean → 0/1 on sqlite (INTEGER affinity), native on postgres;
|
|
@@ -481,7 +486,7 @@ export function assertAppendOnlyMigration(
|
|
|
481
486
|
): boolean {
|
|
482
487
|
if (oldLayout.length > table.columns.length) {
|
|
483
488
|
throw new Error(
|
|
484
|
-
`table ${JSON.stringify(tableName)}: the schema removed columns — only
|
|
489
|
+
`table ${JSON.stringify(tableName)}: the schema removed columns — only appending nullable columns is supported; indexes may be created, replaced, or dropped independently`,
|
|
485
490
|
);
|
|
486
491
|
}
|
|
487
492
|
for (let i = 0; i < oldLayout.length; i++) {
|
|
@@ -624,15 +629,17 @@ export function rewritePlan(
|
|
|
624
629
|
}
|
|
625
630
|
|
|
626
631
|
/**
|
|
627
|
-
* Every DDL statement to bring a database from
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
632
|
+
* Every DDL statement to bring a database from the introspected relational
|
|
633
|
+
* projections to `schema`: CREATE TABLE for absent tables, ADD COLUMN for
|
|
634
|
+
* missing columns, and rebuild declared secondary indexes. Rebuilding during
|
|
635
|
+
* a version bump supports DROP INDEX and same-name index replacement without
|
|
636
|
+
* requiring historical index definitions in the stored column-layout marker.
|
|
631
637
|
*/
|
|
632
638
|
export function schemaDdl(
|
|
633
639
|
schema: CompiledSchema,
|
|
634
640
|
existingColumnsByTable: ReadonlyMap<string, ReadonlySet<string>>,
|
|
635
641
|
dialect: RelationalDialect,
|
|
642
|
+
existingIndexesByTable: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
636
643
|
): string[] {
|
|
637
644
|
const out: string[] = [];
|
|
638
645
|
for (const table of schema.tables.values()) {
|
|
@@ -640,6 +647,9 @@ export function schemaDdl(
|
|
|
640
647
|
if (existing === undefined) {
|
|
641
648
|
out.push(createTableDdl(table, dialect));
|
|
642
649
|
} else {
|
|
650
|
+
for (const indexName of existingIndexesByTable.get(table.name) ?? []) {
|
|
651
|
+
out.push(dropIndexDdl(indexName));
|
|
652
|
+
}
|
|
643
653
|
out.push(...addColumnDdl(table, existing, dialect));
|
|
644
654
|
}
|
|
645
655
|
out.push(...createIndexDdl(table));
|
package/src/sqlite-storage.ts
CHANGED
|
@@ -270,21 +270,33 @@ export class SqliteServerStorage implements ServerStorage {
|
|
|
270
270
|
}
|
|
271
271
|
if (marker === null || marker.schema_version < schema.version) {
|
|
272
272
|
// Introspect existing app tables, then apply the migration subset
|
|
273
|
-
// (CREATE TABLE / ADD COLUMN /
|
|
273
|
+
// (CREATE TABLE / ADD COLUMN / rebuild indexes) to reach `schema`, then
|
|
274
274
|
// rewrite stored rows (payload re-encode for layout changes, and/or
|
|
275
275
|
// projection backfill for flipped-on materialization). One
|
|
276
276
|
// transaction: a failed bump leaves no half-state.
|
|
277
277
|
const layouts = parseLayouts(marker?.layouts);
|
|
278
278
|
const retiredTables = retiredTableNames(schema, layouts);
|
|
279
279
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
280
|
+
const existingIndexes = new Map<string, ReadonlySet<string>>();
|
|
280
281
|
for (const table of schema.tables.values()) {
|
|
282
|
+
const escapedTableName = table.name.replaceAll('"', '""');
|
|
281
283
|
const columns = this.db
|
|
282
284
|
.query<{ name: string }, []>(
|
|
283
|
-
`PRAGMA table_info("${
|
|
285
|
+
`PRAGMA table_info("${escapedTableName}")`,
|
|
284
286
|
)
|
|
285
287
|
.all();
|
|
286
288
|
if (columns.length > 0) {
|
|
287
289
|
existing.set(table.name, new Set(columns.map((c) => c.name)));
|
|
290
|
+
const indexes = this.db
|
|
291
|
+
.query<{ name: string; origin: string }, []>(
|
|
292
|
+
`PRAGMA index_list("${escapedTableName}")`,
|
|
293
|
+
)
|
|
294
|
+
.all()
|
|
295
|
+
.filter((index) => index.origin === 'c');
|
|
296
|
+
existingIndexes.set(
|
|
297
|
+
table.name,
|
|
298
|
+
new Set(indexes.map((index) => index.name)),
|
|
299
|
+
);
|
|
288
300
|
}
|
|
289
301
|
}
|
|
290
302
|
this.db.exec('BEGIN IMMEDIATE');
|
|
@@ -295,7 +307,12 @@ export class SqliteServerStorage implements ServerStorage {
|
|
|
295
307
|
.run(tableName);
|
|
296
308
|
this.db.exec(dropTableDdl(tableName));
|
|
297
309
|
}
|
|
298
|
-
for (const statement of schemaDdl(
|
|
310
|
+
for (const statement of schemaDdl(
|
|
311
|
+
schema,
|
|
312
|
+
existing,
|
|
313
|
+
'sqlite',
|
|
314
|
+
existingIndexes,
|
|
315
|
+
)) {
|
|
299
316
|
this.db.exec(statement);
|
|
300
317
|
}
|
|
301
318
|
for (const table of schema.tables.values()) {
|