Skip to content

Commit bf9f1e9

Browse files
committed
manual schema management
1 parent af052b0 commit bf9f1e9

File tree

5 files changed

+140
-42
lines changed

5 files changed

+140
-42
lines changed

packages/powersync_core/lib/src/database/native/native_powersync_database.dart

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class PowerSyncDatabaseImpl
4444
@override
4545
SqliteDatabase database;
4646

47+
@override
48+
bool manualSchemaManagement;
49+
4750
@override
4851
@protected
4952
late Future<void> isInitialized;
@@ -76,15 +79,21 @@ class PowerSyncDatabaseImpl
7679
required String path,
7780
int maxReaders = SqliteDatabase.defaultMaxReaders,
7881
Logger? logger,
82+
bool manualSchemaManagement = false,
7983
@Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
8084
// ignore: deprecated_member_use_from_same_package
8185
SqliteConnectionSetup? sqliteSetup}) {
8286
// ignore: deprecated_member_use_from_same_package
8387
DefaultSqliteOpenFactory factory =
8488
// ignore: deprecated_member_use_from_same_package
8589
PowerSyncOpenFactory(path: path, sqliteSetup: sqliteSetup);
86-
return PowerSyncDatabaseImpl.withFactory(factory,
87-
schema: schema, maxReaders: maxReaders, logger: logger);
90+
return PowerSyncDatabaseImpl.withFactory(
91+
factory,
92+
schema: schema,
93+
maxReaders: maxReaders,
94+
logger: logger,
95+
manualSchemaManagement: manualSchemaManagement,
96+
);
8897
}
8998

9099
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -96,22 +105,32 @@ class PowerSyncDatabaseImpl
96105
///
97106
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
98107
factory PowerSyncDatabaseImpl.withFactory(
99-
DefaultSqliteOpenFactory openFactory,
100-
{required Schema schema,
101-
int maxReaders = SqliteDatabase.defaultMaxReaders,
102-
Logger? logger}) {
108+
DefaultSqliteOpenFactory openFactory, {
109+
required Schema schema,
110+
int maxReaders = SqliteDatabase.defaultMaxReaders,
111+
Logger? logger,
112+
bool manualSchemaManagement = false,
113+
}) {
103114
final db = SqliteDatabase.withFactory(openFactory, maxReaders: maxReaders);
104115
return PowerSyncDatabaseImpl.withDatabase(
105-
schema: schema, database: db, logger: logger);
116+
schema: schema,
117+
database: db,
118+
logger: logger,
119+
manualSchemaManagement: manualSchemaManagement,
120+
);
106121
}
107122

108123
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
109124
///
110125
/// Migrations are run on the database when this constructor is called.
111126
///
112127
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.s
113-
PowerSyncDatabaseImpl.withDatabase(
114-
{required this.schema, required this.database, Logger? logger}) {
128+
PowerSyncDatabaseImpl.withDatabase({
129+
required this.schema,
130+
required this.database,
131+
Logger? logger,
132+
this.manualSchemaManagement = false,
133+
}) {
115134
this.logger = logger ?? autoLogger;
116135
isInitialized = baseInit();
117136
}

packages/powersync_core/lib/src/database/powersync_database.dart

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,23 @@ abstract class PowerSyncDatabase
3232
/// A maximum of [maxReaders] concurrent read transactions are allowed.
3333
///
3434
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
35-
factory PowerSyncDatabase(
36-
{required Schema schema,
37-
required String path,
38-
Logger? logger,
39-
@Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
40-
// ignore: deprecated_member_use_from_same_package
41-
SqliteConnectionSetup? sqliteSetup}) {
35+
factory PowerSyncDatabase({
36+
required Schema schema,
37+
required String path,
38+
Logger? logger,
39+
bool manualSchemaManagement = false,
40+
@Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
41+
// ignore: deprecated_member_use_from_same_package
42+
SqliteConnectionSetup? sqliteSetup,
43+
}) {
4244
return PowerSyncDatabaseImpl(
43-
schema: schema,
44-
path: path,
45-
logger: logger,
46-
// ignore: deprecated_member_use_from_same_package
47-
sqliteSetup: sqliteSetup);
45+
schema: schema,
46+
path: path,
47+
manualSchemaManagement: manualSchemaManagement,
48+
logger: logger,
49+
// ignore: deprecated_member_use_from_same_package
50+
sqliteSetup: sqliteSetup,
51+
);
4852
}
4953

5054
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -55,24 +59,38 @@ abstract class PowerSyncDatabase
5559
/// Subclass [PowerSyncOpenFactory] to add custom logic to this process.
5660
///
5761
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
58-
factory PowerSyncDatabase.withFactory(DefaultSqliteOpenFactory openFactory,
59-
{required Schema schema,
60-
int maxReaders = SqliteDatabase.defaultMaxReaders,
61-
Logger? logger}) {
62-
return PowerSyncDatabaseImpl.withFactory(openFactory,
63-
schema: schema, maxReaders: maxReaders, logger: logger);
62+
factory PowerSyncDatabase.withFactory(
63+
DefaultSqliteOpenFactory openFactory, {
64+
required Schema schema,
65+
int maxReaders = SqliteDatabase.defaultMaxReaders,
66+
bool manualSchemaManagement = false,
67+
Logger? logger,
68+
}) {
69+
return PowerSyncDatabaseImpl.withFactory(
70+
openFactory,
71+
schema: schema,
72+
maxReaders: maxReaders,
73+
manualSchemaManagement: manualSchemaManagement,
74+
logger: logger,
75+
);
6476
}
6577

6678
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
6779
///
6880
/// Migrations are run on the database when this constructor is called.
6981
///
7082
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
71-
factory PowerSyncDatabase.withDatabase(
72-
{required Schema schema,
73-
required SqliteDatabase database,
74-
Logger? loggers}) {
83+
factory PowerSyncDatabase.withDatabase({
84+
required Schema schema,
85+
required SqliteDatabase database,
86+
bool manualSchemaManagement = false,
87+
Logger? logger,
88+
}) {
7589
return PowerSyncDatabaseImpl.withDatabase(
76-
schema: schema, database: database, logger: loggers);
90+
schema: schema,
91+
database: database,
92+
manualSchemaManagement: manualSchemaManagement,
93+
logger: logger,
94+
);
7795
}
7896
}

packages/powersync_core/lib/src/database/powersync_database_impl_stub.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class PowerSyncDatabaseImpl
3232
@override
3333
SqliteDatabase get database => throw UnimplementedError();
3434

35+
@override
36+
bool get manualSchemaManagement => throw UnimplementedError();
37+
3538
@override
3639
Future<void> get isInitialized => throw UnimplementedError();
3740

@@ -53,6 +56,7 @@ class PowerSyncDatabaseImpl
5356
{required Schema schema,
5457
required String path,
5558
int maxReaders = SqliteDatabase.defaultMaxReaders,
59+
bool manualSchemaManagement = false,
5660
Logger? logger,
5761
@Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
5862
// ignore: deprecated_member_use_from_same_package
@@ -72,6 +76,7 @@ class PowerSyncDatabaseImpl
7276
DefaultSqliteOpenFactory openFactory, {
7377
required Schema schema,
7478
int maxReaders = SqliteDatabase.defaultMaxReaders,
79+
bool manualSchemaManagement = false,
7580
Logger? logger,
7681
}) {
7782
throw UnimplementedError();
@@ -82,10 +87,12 @@ class PowerSyncDatabaseImpl
8287
/// Migrations are run on the database when this constructor is called.
8388
///
8489
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.s
85-
factory PowerSyncDatabaseImpl.withDatabase(
86-
{required Schema schema,
87-
required SqliteDatabase database,
88-
Logger? logger}) {
90+
factory PowerSyncDatabaseImpl.withDatabase({
91+
required Schema schema,
92+
required SqliteDatabase database,
93+
bool manualSchemaManagement = false,
94+
Logger? logger,
95+
}) {
8996
throw UnimplementedError();
9097
}
9198

packages/powersync_core/lib/src/database/powersync_db_mixin.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
3838
/// Use [attachedLogger] to propagate logs to [Logger.root] for custom logging.
3939
Logger get logger;
4040

41+
bool get manualSchemaManagement;
42+
43+
bool _manualSchemaManagementCompleted = false;
44+
4145
@Deprecated("This field is unused, pass params to connect() instead")
4246
Map<String, dynamic>? clientParams;
4347

@@ -110,10 +114,36 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
110114
statusStream = statusStreamController.stream;
111115
updates = powerSyncUpdateNotifications(database.updates);
112116

117+
_manualSchemaManagementCompleted = false;
118+
113119
await database.initialize();
114120
await _checkVersion();
115121
await database.execute('SELECT powersync_init()');
116-
await updateSchema(schema);
122+
123+
if (!manualSchemaManagement) {
124+
// Create the internal db schema
125+
await updateSchema(schema);
126+
await _afterSchemaReady();
127+
}
128+
}
129+
130+
Future<void> markSchemaAsReady() async {
131+
await isInitialized;
132+
_manualSchemaManagementCompleted = true;
133+
134+
await _afterSchemaReady();
135+
}
136+
137+
void _assertSchemaIsReady() {
138+
if (!manualSchemaManagement || _manualSchemaManagementCompleted) {
139+
return;
140+
}
141+
142+
throw AssertionError(
143+
'In manual schema management mode, you need to mark the powersync database as ready');
144+
}
145+
146+
Future<void> _afterSchemaReady() async {
117147
await _updateHasSynced();
118148
}
119149

@@ -289,6 +319,8 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
289319
// the lock for the connection.
290320
await initialize();
291321

322+
_assertSchemaIsReady();
323+
292324
final resolvedOptions = ResolvedSyncOptions.resolve(
293325
options,
294326
crudThrottleTime: crudThrottleTime,
@@ -452,13 +484,15 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
452484
/// Get an unique id for this client.
453485
/// This id is only reset when the database is deleted.
454486
Future<String> getClientId() async {
487+
_assertSchemaIsReady(); // TODO(skilldevs): Needed?
455488
final row = await get('SELECT powersync_client_id() as client_id');
456489
return row['client_id'] as String;
457490
}
458491

459492
/// Get upload queue size estimate and count.
460493
Future<UploadQueueStats> getUploadQueueStats(
461494
{bool includeSize = false}) async {
495+
_assertSchemaIsReady();
462496
if (includeSize) {
463497
final row = await getOptional(
464498
'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud');
@@ -486,6 +520,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
486520
/// data by transaction. One batch may contain data from multiple transactions,
487521
/// and a single transaction may be split over multiple batches.
488522
Future<CrudBatch?> getCrudBatch({int limit = 100}) async {
523+
_assertSchemaIsReady();
489524
final rows = await getAll(
490525
'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?',
491526
[limit + 1]);
@@ -532,6 +567,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
532567
/// Unlike [getCrudBatch], this only returns data from a single transaction at a time.
533568
/// All data for the transaction is loaded into memory.
534569
Future<CrudTransaction?> getNextCrudTransaction() async {
570+
_assertSchemaIsReady();
535571
return await readTransaction((tx) async {
536572
final first = await tx.getOptional(
537573
'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1');

packages/powersync_core/lib/src/database/web/web_powersync_database.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class PowerSyncDatabaseImpl
3838
@override
3939
SqliteDatabase database;
4040

41+
@override
42+
bool manualSchemaManagement;
43+
4144
@override
4245
@protected
4346
late Future<void> isInitialized;
@@ -69,14 +72,20 @@ class PowerSyncDatabaseImpl
6972
{required Schema schema,
7073
required String path,
7174
int maxReaders = SqliteDatabase.defaultMaxReaders,
75+
bool manualSchemaManagement = false,
7276
Logger? logger,
7377
@Deprecated("Use [PowerSyncDatabase.withFactory] instead.")
7478
// ignore: deprecated_member_use_from_same_package
7579
SqliteConnectionSetup? sqliteSetup}) {
7680
// ignore: deprecated_member_use_from_same_package
7781
DefaultSqliteOpenFactory factory = PowerSyncOpenFactory(path: path);
78-
return PowerSyncDatabaseImpl.withFactory(factory,
79-
maxReaders: maxReaders, logger: logger, schema: schema);
82+
return PowerSyncDatabaseImpl.withFactory(
83+
factory,
84+
maxReaders: maxReaders,
85+
logger: logger,
86+
schema: schema,
87+
manualSchemaManagement: manualSchemaManagement,
88+
);
8089
}
8190

8291
/// Open a [PowerSyncDatabase] with a [PowerSyncOpenFactory].
@@ -91,19 +100,28 @@ class PowerSyncDatabaseImpl
91100
DefaultSqliteOpenFactory openFactory,
92101
{required Schema schema,
93102
int maxReaders = SqliteDatabase.defaultMaxReaders,
103+
bool manualSchemaManagement = false,
94104
Logger? logger}) {
95105
final db = SqliteDatabase.withFactory(openFactory, maxReaders: 1);
96106
return PowerSyncDatabaseImpl.withDatabase(
97-
schema: schema, logger: logger, database: db);
107+
schema: schema,
108+
manualSchemaManagement: manualSchemaManagement,
109+
logger: logger,
110+
database: db,
111+
);
98112
}
99113

100114
/// Open a PowerSyncDatabase on an existing [SqliteDatabase].
101115
///
102116
/// Migrations are run on the database when this constructor is called.
103117
///
104118
/// [logger] defaults to [autoLogger], which logs to the console in debug builds.
105-
PowerSyncDatabaseImpl.withDatabase(
106-
{required this.schema, required this.database, Logger? logger}) {
119+
PowerSyncDatabaseImpl.withDatabase({
120+
required this.schema,
121+
required this.database,
122+
this.manualSchemaManagement = false,
123+
Logger? logger,
124+
}) {
107125
if (logger != null) {
108126
this.logger = logger;
109127
} else {

0 commit comments

Comments
 (0)