Skip to content

Commit 8138f21

Browse files
authored
Merge pull request #4 from powersync-ja/chore/alpha-polish
chore: Schema validation and other small cleanup items
2 parents 0a10e61 + 9536037 commit 8138f21

File tree

7 files changed

+102
-16
lines changed

7 files changed

+102
-16
lines changed

PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs

+23-4
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ public async Task UpdateSchema(Schema schema)
265265

266266
try
267267
{
268-
// schema.Validate();
268+
schema.Validate();
269269
}
270270
catch (Exception ex)
271271
{
@@ -356,12 +356,19 @@ public async Task Disconnect()
356356
syncStreamStatusCts?.Cancel();
357357
}
358358

359-
public async Task DisconnectAndClear()
359+
/// <summary>
360+
/// Disconnect and clear the database.
361+
/// Use this when logging out.
362+
/// The database can still be queried after this is called, but the tables
363+
/// would be empty.
364+
///
365+
/// To preserve data in local-only tables, set clearLocal to false.
366+
/// </summary>
367+
public async Task DisconnectAndClear(bool clearLocal = true)
360368
{
361369
await Disconnect();
362370
await WaitForReady();
363371

364-
bool clearLocal = true;
365372

366373
await Database.WriteTransaction(async tx =>
367374
{
@@ -373,11 +380,23 @@ await Database.WriteTransaction(async tx =>
373380
Emit(new PowerSyncDBEvent { StatusChanged = CurrentStatus });
374381
}
375382

383+
/// <summary>
384+
/// Close the database, releasing resources.
385+
///
386+
/// Also disconnects any active connection.
387+
///
388+
/// Once close is called, this connection cannot be used again - a new one
389+
/// must be constructed.
390+
/// </summary>
376391
public new async Task Close()
377392
{
378-
base.Close();
379393
await WaitForReady();
380394

395+
if (Closed) return;
396+
397+
398+
await Disconnect();
399+
base.Close();
381400
syncStreamImplementation?.Close();
382401
BucketStorageAdapter?.Close();
383402

PowerSync/PowerSync.Common/DB/Schema/Schema.cs

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ public class Schema(Dictionary<string, Table> tables)
77
{
88
private readonly Dictionary<string, Table> Tables = tables;
99

10+
public void Validate()
11+
{
12+
foreach (var kvp in Tables)
13+
{
14+
var tableName = kvp.Key;
15+
var table = kvp.Value;
16+
17+
if (Table.InvalidSQLCharacters.IsMatch(tableName))
18+
{
19+
throw new Exception($"Invalid characters in table name: {tableName}");
20+
}
21+
22+
table.Validate();
23+
}
24+
}
25+
1026
public string ToJSON()
1127
{
1228
var jsonObject = new

PowerSync/PowerSync.Common/DB/Schema/Table.cs

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace PowerSync.Common.DB.Schema;
22

3+
using System.Text.RegularExpressions;
34
using Newtonsoft.Json;
45

56
public class TableOptions(
@@ -19,7 +20,10 @@ public class TableOptions(
1920

2021
public class Table
2122
{
22-
protected TableOptions Options { get; set; }
23+
public static readonly Regex InvalidSQLCharacters = new Regex(@"[""'%,.#\s\[\]]", RegexOptions.Compiled);
24+
25+
26+
protected TableOptions Options { get; init; } = null!;
2327

2428
public Dictionary<string, ColumnType> Columns;
2529
public Dictionary<string, List<string>> Indexes;
@@ -48,6 +52,53 @@ [.. kv.Value.Select(name =>
4852
Indexes = Options?.Indexes ?? [];
4953
}
5054

55+
public void Validate()
56+
{
57+
if (!string.IsNullOrWhiteSpace(Options.ViewName) && InvalidSQLCharacters.IsMatch(Options.ViewName!))
58+
{
59+
throw new Exception($"Invalid characters in view name: {Options.ViewName}");
60+
}
61+
62+
if (Columns.Count > Column.MAX_AMOUNT_OF_COLUMNS)
63+
{
64+
throw new Exception($"Table has too many columns. The maximum number of columns is {Column.MAX_AMOUNT_OF_COLUMNS}.");
65+
}
66+
67+
var columnNames = new HashSet<string> { "id" };
68+
69+
foreach (var columnName in Columns.Keys)
70+
{
71+
if (columnName == "id")
72+
{
73+
throw new Exception("An id column is automatically added, custom id columns are not supported");
74+
}
75+
76+
if (InvalidSQLCharacters.IsMatch(columnName))
77+
{
78+
throw new Exception($"Invalid characters in column name: {columnName}");
79+
}
80+
81+
columnNames.Add(columnName);
82+
}
83+
84+
foreach (var (indexName, indexColumns) in Indexes)
85+
{
86+
87+
if (InvalidSQLCharacters.IsMatch(indexName))
88+
{
89+
throw new Exception($"Invalid characters in index name: {indexName}");
90+
}
91+
92+
foreach (var indexColumn in indexColumns)
93+
{
94+
if (!columnNames.Contains(indexColumn))
95+
{
96+
throw new Exception($"Column {indexColumn} not found for index {indexName}");
97+
}
98+
}
99+
}
100+
}
101+
51102
public string ToJSON(string Name = "")
52103
{
53104
var jsonObject = new

Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTransactionTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public async Task InitializeAsync()
1212
db = new PowerSyncDatabase(new PowerSyncDatabaseOptions
1313
{
1414
Database = new SQLOpenOptions { DbFilename = "powersyncDataBaseTransactions.db" },
15-
Schema = TestSchema.appSchema,
15+
Schema = TestSchema.AppSchema,
1616
});
1717
await db.Init();
1818
}

Tests/PowerSync/PowerSync.Common.Tests/Client/Sync/BucketStorageTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public async Task InitializeAsync()
7070
db = new PowerSyncDatabase(new PowerSyncDatabaseOptions
7171
{
7272
Database = new SQLOpenOptions { DbFilename = "powersync.db" },
73-
Schema = TestSchema.appSchema,
73+
Schema = TestSchema.AppSchema,
7474
});
7575
await db.Init();
7676
bucketStorage = new SqliteBucketStorage(db.Database, createLogger());
@@ -496,7 +496,7 @@ await Assert.ThrowsAsync<SqliteException>(async () =>
496496
powersync = new PowerSyncDatabase(new PowerSyncDatabaseOptions
497497
{
498498
Database = new SQLOpenOptions { DbFilename = dbName },
499-
Schema = TestSchema.appSchema,
499+
Schema = TestSchema.AppSchema,
500500
});
501501
await powersync.Init();
502502

@@ -515,7 +515,7 @@ public async Task ShouldRemoveTypes()
515515
var powersync = new PowerSyncDatabase(new PowerSyncDatabaseOptions
516516
{
517517
Database = new SQLOpenOptions { DbFilename = dbName },
518-
Schema = TestSchema.appSchema,
518+
Schema = TestSchema.AppSchema,
519519
});
520520

521521
await powersync.Init();
@@ -557,7 +557,7 @@ await Assert.ThrowsAsync<SqliteException>(async () =>
557557
powersync = new PowerSyncDatabase(new PowerSyncDatabaseOptions
558558
{
559559
Database = new SQLOpenOptions { DbFilename = dbName },
560-
Schema = TestSchema.appSchema,
560+
Schema = TestSchema.AppSchema,
561561
});
562562
await powersync.Init();
563563

Tests/PowerSync/PowerSync.Common.Tests/Client/Sync/CRUDTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public async Task InitializeAsync()
2020
db = new PowerSyncDatabase(new PowerSyncDatabaseOptions
2121
{
2222
Database = new SQLOpenOptions { DbFilename = dbName },
23-
Schema = TestSchema.appSchema,
23+
Schema = TestSchema.AppSchema,
2424
});
2525
await db.Init();
2626
}

Tests/PowerSync/PowerSync.Common.Tests/TestSchema.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace PowerSync.Common.Tests;
44

55
public class TestSchema
66
{
7-
public static Table assets = new Table(new Dictionary<string, ColumnType>
7+
public static readonly Table Assets = new Table(new Dictionary<string, ColumnType>
88
{
99
{ "created_at", ColumnType.TEXT },
1010
{ "make", ColumnType.TEXT },
@@ -19,15 +19,15 @@ public class TestSchema
1919
Indexes = new Dictionary<string, List<string>> { { "makemodel", new List<string> { "make", "model" } } }
2020
});
2121

22-
public static Table customers = new Table(new Dictionary<string, ColumnType>
22+
public static readonly Table Customers = new Table(new Dictionary<string, ColumnType>
2323
{
2424
{ "name", ColumnType.TEXT },
2525
{ "email", ColumnType.TEXT }
2626
});
2727

28-
public static Schema appSchema = new Schema(new Dictionary<string, Table>
28+
public static readonly Schema AppSchema = new Schema(new Dictionary<string, Table>
2929
{
30-
{ "assets", assets },
31-
{ "customers", customers }
30+
{ "assets", Assets },
31+
{ "customers", Customers }
3232
});
3333
}

0 commit comments

Comments
 (0)