Skip to content

Commit c600362

Browse files
authored
Merge pull request #289 from powersync-ja/update-sqlite-core
powersync-sqlite-core v0.2.1
2 parents 6ace8bf + 447f979 commit c600362

File tree

38 files changed

+22084
-16611
lines changed

38 files changed

+22084
-16611
lines changed

.changeset/brown-eggs-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Improve performance of MOVE and REMOVE operations.

.changeset/cuddly-paws-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/react-native': minor
3+
---
4+
5+
Use react-native-quick-sqlite 1.3.0 / powersync-sqlite-core 0.2.1.

.changeset/popular-phones-knock.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': patch
3+
---
4+
5+
Always cast `target_op` (write checkpoint) to ensure it's an integer.

.changeset/slow-lizards-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Add custom x-user-agent header and client_id parameter to requests.

.changeset/small-pants-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': minor
3+
---
4+
5+
Use wa-sqlite 0.3.0 / powersync-sqlite-core 0.2.0.

.changeset/soft-mice-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Emit update notifications on `disconnectAndClear()`.

.changeset/tiny-worms-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': patch
3+
---
4+
5+
Validate that the powersync-sqlite-core version number is in a compatible range of ^0.2.0.

.changeset/two-walls-nail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Persist lastSyncedAt timestamp.

demos/angular-supabase-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@angular/platform-browser-dynamic": "^18.1.1",
2222
"@angular/router": "^18.1.1",
2323
"@angular/service-worker": "^18.1.1",
24-
"@journeyapps/wa-sqlite": "^0.2.0",
24+
"@journeyapps/wa-sqlite": "^0.3.0",
2525
"@powersync/web": "workspace:*",
2626
"@supabase/supabase-js": "^2.44.4",
2727
"rxjs": "~7.8.1",

demos/django-react-native-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"dependencies": {
1111
"@azure/core-asynciterator-polyfill": "^1.0.2",
1212
"@expo/vector-icons": "^14.0.0",
13-
"@journeyapps/react-native-quick-sqlite": "^1.1.7",
13+
"@journeyapps/react-native-quick-sqlite": "^1.3.0",
1414
"@powersync/common": "workspace:*",
1515
"@powersync/react": "workspace:*",
1616
"@powersync/react-native": "workspace:*",

demos/example-capacitor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@capacitor/core": "latest",
2424
"@capacitor/ios": "^6.0.0",
2525
"@capacitor/splash-screen": "latest",
26-
"@journeyapps/wa-sqlite": "^0.2.0",
26+
"@journeyapps/wa-sqlite": "^0.3.0",
2727
"@powersync/react": "workspace:*",
2828
"@powersync/web": "workspace:*",
2929
"js-logger": "^1.6.1",

demos/example-electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"dependencies": {
2222
"@emotion/react": "^11.13.0",
2323
"@emotion/styled": "^11.13.0",
24-
"@journeyapps/wa-sqlite": "~0.2.0",
24+
"@journeyapps/wa-sqlite": "^0.3.0",
2525
"@mui/icons-material": "^5.15.16",
2626
"@mui/material": "^5.15.16",
2727
"@mui/x-data-grid": "^6.19.11",

demos/example-nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@emotion/react": "^11.11.4",
1515
"@emotion/styled": "^11.11.5",
1616
"@fontsource/roboto": "^5.0.13",
17-
"@journeyapps/wa-sqlite": "~0.2.0",
17+
"@journeyapps/wa-sqlite": "^0.3.0",
1818
"@lexical/react": "^0.15.0",
1919
"@mui/icons-material": "^5.15.18",
2020
"@mui/material": "^5.15.18",

demos/react-multi-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"test:build": "pnpm build"
1111
},
1212
"dependencies": {
13-
"@journeyapps/wa-sqlite": "~0.2.0",
13+
"@journeyapps/wa-sqlite": "^0.3.0",
1414
"@powersync/react": "workspace:*",
1515
"@powersync/web": "workspace:*",
1616
"@supabase/supabase-js": "^2.43.1",

demos/react-native-supabase-group-chat/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"dependencies": {
2222
"@azure/core-asynciterator-polyfill": "^1.0.2",
2323
"@faker-js/faker": "8.3.1",
24-
"@journeyapps/react-native-quick-sqlite": "^1.1.7",
24+
"@journeyapps/react-native-quick-sqlite": "^1.3.0",
2525
"@powersync/common": "workspace:*",
2626
"@powersync/react": "workspace:*",
2727
"@powersync/react-native": "workspace:*",

demos/react-native-supabase-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"dependencies": {
1111
"@azure/core-asynciterator-polyfill": "^1.0.2",
1212
"@expo/vector-icons": "^14.0.0",
13-
"@journeyapps/react-native-quick-sqlite": "^1.1.7",
13+
"@journeyapps/react-native-quick-sqlite": "^1.3.0",
1414
"@powersync/attachments": "workspace:*",
1515
"@powersync/common": "workspace:*",
1616
"@powersync/react": "workspace:*",

demos/react-supabase-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@powersync/web": "workspace:*",
1414
"@emotion/react": "11.11.4",
1515
"@emotion/styled": "11.11.5",
16-
"@journeyapps/wa-sqlite": "~0.2.0",
16+
"@journeyapps/wa-sqlite": "^0.3.0",
1717
"@mui/icons-material": "^5.15.12",
1818
"@mui/material": "^5.15.12",
1919
"@mui/x-data-grid": "^6.19.6",

demos/yjs-react-supabase-text-collab/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@fontsource/roboto": "^5.0.12",
1515
"@powersync/react": "workspace:*",
1616
"@powersync/web": "workspace:*",
17-
"@journeyapps/wa-sqlite": "~0.1.1",
17+
"@journeyapps/wa-sqlite": "^0.3.0",
1818
"@lexical/react": "^0.11.3",
1919
"@mui/icons-material": "^5.15.12",
2020
"@mui/material": "^5.15.12",

packages/common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { Schema } from '../db/schema/Schema';
1616
import { BaseObserver } from '../utils/BaseObserver';
1717
import { ControlledExecutor } from '../utils/ControlledExecutor';
1818
import { mutexRunExclusive } from '../utils/mutex';
19-
import { quoteIdentifier } from '../utils/strings';
2019
import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory';
2120
import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector';
2221
import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter';
@@ -292,21 +291,47 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
292291
protected async initialize() {
293292
await this._initialize();
294293
await this.bucketStorageAdapter.init();
295-
const version = await this.database.execute('SELECT powersync_rs_version()');
296-
this.sdkVersion = version.rows?.item(0)['powersync_rs_version()'] ?? '';
294+
await this._loadVersion();
297295
await this.updateSchema(this.options.schema);
298296
await this.updateHasSynced();
299297
await this.database.execute('PRAGMA RECURSIVE_TRIGGERS=TRUE');
300298
this.ready = true;
301299
this.iterateListeners((cb) => cb.initialized?.());
302300
}
303301

302+
private async _loadVersion() {
303+
try {
304+
const { version } = await this.database.get<{ version: string }>('SELECT powersync_rs_version() as version');
305+
this.sdkVersion = version;
306+
} catch (e) {
307+
throw new Error(`The powersync extension is not loaded correctly. Details: ${e.message}`);
308+
}
309+
let versionInts: number[];
310+
try {
311+
versionInts = this.sdkVersion!.split(/[.\/]/)
312+
.slice(0, 3)
313+
.map((n) => parseInt(n));
314+
} catch (e) {
315+
throw new Error(
316+
`Unsupported powersync extension version. Need ^0.2.0, got: ${this.sdkVersion}. Details: ${e.message}`
317+
);
318+
}
319+
320+
// Validate ^0.2.0
321+
if (versionInts[0] != 0 || versionInts[1] != 2 || versionInts[2] < 0) {
322+
throw new Error(`Unsupported powersync extension version. Need ^0.2.0, got: ${this.sdkVersion}`);
323+
}
324+
}
325+
304326
protected async updateHasSynced() {
305-
const result = await this.database.getOptional('SELECT 1 FROM ps_buckets WHERE last_applied_op > 0 LIMIT 1');
306-
const hasSynced = !!result;
327+
const result = await this.database.get<{ synced_at: string | null }>(
328+
'SELECT powersync_last_synced_at() as synced_at'
329+
);
330+
const hasSynced = result.synced_at != null;
331+
const syncedAt = result.synced_at != null ? new Date(result.synced_at! + 'Z') : undefined;
307332

308333
if (hasSynced != this.currentStatus.hasSynced) {
309-
this.currentStatus = new SyncStatus({ ...this.currentStatus.toJSON(), hasSynced });
334+
this.currentStatus = new SyncStatus({ ...this.currentStatus.toJSON(), hasSynced, lastSyncedAt: syncedAt });
310335
this.iterateListeners((l) => l.statusChanged?.(this.currentStatus));
311336
}
312337
}
@@ -400,26 +425,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
400425

401426
// TODO DB name, verify this is necessary with extension
402427
await this.database.writeTransaction(async (tx) => {
403-
await tx.execute(`DELETE FROM ${PSInternalTable.OPLOG}`);
404-
await tx.execute(`DELETE FROM ${PSInternalTable.CRUD}`);
405-
await tx.execute(`DELETE FROM ${PSInternalTable.BUCKETS}`);
406-
await tx.execute(`DELETE FROM ${PSInternalTable.UNTYPED}`);
407-
408-
const tableGlob = clearLocal ? 'ps_data_*' : 'ps_data__*';
409-
410-
const existingTableRows = await tx.execute(
411-
`
412-
SELECT name FROM sqlite_master WHERE type='table' AND name GLOB ?
413-
`,
414-
[tableGlob]
415-
);
416-
417-
if (!existingTableRows.rows?.length) {
418-
return;
419-
}
420-
for (const row of existingTableRows.rows._array) {
421-
await tx.execute(`DELETE FROM ${quoteIdentifier(row.name)} WHERE 1`);
422-
}
428+
await tx.execute('SELECT powersync_clear(?)', [clearLocal ? 1 : 0]);
423429
});
424430

425431
// The data has been deleted - reset the sync status
@@ -553,6 +559,15 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
553559
});
554560
}
555561

562+
/**
563+
* Get an unique client id for this database.
564+
*
565+
* The id is not reset when the database is cleared, only when the database is deleted.
566+
*/
567+
async getClientId(): Promise<string> {
568+
return this.bucketStorageAdapter.getClientId();
569+
}
570+
556571
private async handleCrudCheckpoint(lastClientId: number, writeCheckpoint?: string) {
557572
return this.writeTransaction(async (tx) => {
558573
await tx.execute(`DELETE FROM ${PSInternalTable.CRUD} WHERE id <= ?`, [lastClientId]);

packages/common/src/client/sync/bucket/BucketStorageAdapter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,9 @@ export interface BucketStorageAdapter extends BaseObserver<BucketStorageListener
7979
forceCompact(): Promise<void>;
8080

8181
getMaxOpId(): string;
82+
83+
/**
84+
* Get an unique client id.
85+
*/
86+
getClientId(): Promise<string>;
8287
}

packages/common/src/client/sync/bucket/SqliteBucketStorage.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
2323
private pendingBucketDeletes: boolean;
2424
private _hasCompletedSync: boolean;
2525
private updateListener: () => void;
26+
private _clientId?: Promise<string>;
2627

2728
/**
2829
* Count up, and do a compact on startup.
@@ -62,9 +63,22 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
6263
this.updateListener?.();
6364
}
6465

66+
async _getClientId() {
67+
const row = await this.db.get<{ client_id: string }>('SELECT powersync_client_id() as client_id');
68+
return row['client_id'];
69+
}
70+
71+
getClientId() {
72+
if (this._clientId == null) {
73+
this._clientId = this._getClientId();
74+
}
75+
return this._clientId!;
76+
}
77+
6578
getMaxOpId() {
6679
return MAX_OP_ID;
6780
}
81+
6882
/**
6983
* Reset any caches.
7084
*/
@@ -103,9 +117,7 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
103117
*/
104118
private async deleteBucket(bucket: string) {
105119
await this.writeTransaction(async (tx) => {
106-
await tx.execute(
107-
'INSERT INTO powersync_operations(op, data) VALUES(?, ?)',
108-
['delete_bucket', bucket]);
120+
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', ['delete_bucket', bucket]);
109121
});
110122

111123
this.logger.debug('done deleting bucket');
@@ -116,8 +128,8 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
116128
if (this._hasCompletedSync) {
117129
return true;
118130
}
119-
const r = await this.db.execute(`SELECT name, last_applied_op FROM ps_buckets WHERE last_applied_op > 0 LIMIT 1`);
120-
const completed = !!r.rows?.length;
131+
const r = await this.db.get<{ synced_at: string | null }>(`SELECT powersync_last_synced_at() as synced_at`);
132+
const completed = r.synced_at != null;
121133
if (completed) {
122134
this._hasCompletedSync = true;
123135
}
@@ -219,12 +231,7 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
219231
private async deletePendingBuckets() {
220232
if (this.pendingBucketDeletes !== false) {
221233
await this.writeTransaction(async (tx) => {
222-
await tx.execute(
223-
'DELETE FROM ps_oplog WHERE bucket IN (SELECT name FROM ps_buckets WHERE pending_delete = 1 AND last_applied_op = last_op AND last_op >= target_op)'
224-
);
225-
await tx.execute(
226-
'DELETE FROM ps_buckets WHERE pending_delete = 1 AND last_applied_op = last_op AND last_op >= target_op'
227-
);
234+
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES (?, ?)', ['delete_pending_buckets', '']);
228235
});
229236
// Executed once after start-up, and again when there are pending deletes.
230237
this.pendingBucketDeletes = false;
@@ -284,7 +291,9 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
284291
return false;
285292
}
286293

287-
const response = await tx.execute("UPDATE ps_buckets SET target_op = ? WHERE name='$local'", [opId]);
294+
const response = await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [
295+
opId
296+
]);
288297
this.logger.debug(['[updateLocalTarget] Response from updating target_op ', JSON.stringify(response)]);
289298
return true;
290299
});
@@ -333,10 +342,14 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
333342
if (writeCheckpoint) {
334343
const crudResult = await tx.execute('SELECT 1 FROM ps_crud LIMIT 1');
335344
if (crudResult.rows?.length) {
336-
await tx.execute("UPDATE ps_buckets SET target_op = ? WHERE name='$local'", [writeCheckpoint]);
345+
await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [
346+
writeCheckpoint
347+
]);
337348
}
338349
} else {
339-
await tx.execute("UPDATE ps_buckets SET target_op = ? WHERE name='$local'", [this.getMaxOpId()]);
350+
await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [
351+
this.getMaxOpId()
352+
]);
340353
}
341354
});
342355
}

0 commit comments

Comments
 (0)