Skip to content

Commit 606d7cc

Browse files
authored
Rename schema Factory classes, make compiled schema classes internal (#40)
1 parent 00cea74 commit 606d7cc

18 files changed

Lines changed: 329 additions & 268 deletions

File tree

PowerSync/PowerSync.Common/CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# PowerSync.Common Changelog
22

3+
## 0.0.9-alpha.1
4+
5+
- _Breaking:_ Further updated schema definition syntax.
6+
- Renamed `Schema` and `Table` to `CompiledSchema` and `CompiledTable` and renamed `SchemaFactory` and `TableFactory` to `Schema` and `Table`.
7+
- Made `CompiledSchema` and `CompiledTable` internal classes.
8+
- These are the last breaking changes to schema definition before entering beta.
9+
10+
```csharp
11+
public static Table Assets = new Table
12+
{
13+
Name = "assets",
14+
Columns =
15+
{
16+
["make"] = ColumnType.Text,
17+
["model"] = ColumnType.Text,
18+
// ...
19+
},
20+
Indexes =
21+
{
22+
["makemodel"] = ["make", "model"],
23+
},
24+
};
25+
26+
public static Table Customers = new Table
27+
{
28+
Name = "customers",
29+
Columns =
30+
{
31+
["name"] = ColumnType.Text,
32+
},
33+
};
34+
35+
public static Schema PowerSyncSchema = new Schema(Assets, Customers);
36+
```
37+
338
## 0.0.8-alpha.1
439

540
- Updated the syntax for defining the app schema to use a factory pattern.

PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public interface IPowerSyncDatabase : IEventStream<PowerSyncDBEvent>
101101
public class PowerSyncDatabase : EventStream<PowerSyncDBEvent>, IPowerSyncDatabase
102102
{
103103
public IDBAdapter Database { get; protected set; }
104-
private Schema schema;
104+
private CompiledSchema schema;
105105

106106
private static readonly int DEFAULT_WATCH_THROTTLE_MS = 30;
107107
private static readonly Regex POWERSYNC_TABLE_MATCH = new Regex(@"(^ps_data__|^ps_data_local__)", RegexOptions.Compiled);
@@ -168,7 +168,7 @@ public PowerSyncDatabase(PowerSyncDatabaseOptions options)
168168
Closed = false;
169169
Ready = false;
170170

171-
schema = options.Schema;
171+
schema = options.Schema.Compile();
172172
SdkVersion = "";
173173

174174
remoteFactory = options.RemoteFactory ?? (connector => new Remote(connector));
@@ -223,7 +223,7 @@ public PowerSyncDatabase(PowerSyncDatabaseOptions options)
223223
}
224224
}, logger: Logger);
225225

226-
IsReadyTask = Initialize();
226+
IsReadyTask = Initialize(options);
227227
}
228228

229229
protected IBucketStorageAdapter generateBucketStorageAdapter()
@@ -307,11 +307,11 @@ public async Task WaitForStatus(Func<SyncStatus, bool> predicate, CancellationTo
307307
await tcs.Task;
308308
}
309309

310-
protected async Task Initialize()
310+
protected async Task Initialize(PowerSyncDatabaseOptions options)
311311
{
312312
await BucketStorageAdapter.Init();
313313
await LoadVersion();
314-
await UpdateSchema(schema);
314+
await UpdateSchema(options.Schema);
315315
await ResolveOfflineSyncStatus();
316316
await Database.Execute("PRAGMA RECURSIVE_TRIGGERS=TRUE");
317317
Ready = true;
@@ -369,22 +369,23 @@ protected async Task ResolveOfflineSyncStatus()
369369
/// </summary>
370370
public async Task UpdateSchema(Schema schema)
371371
{
372+
CompiledSchema compiledSchema = schema.Compile();
372373
if (syncStreamImplementation != null)
373374
{
374375
throw new Exception("Cannot update schema while connected");
375376
}
376377

377378
try
378379
{
379-
schema.Validate();
380+
compiledSchema.Validate();
380381
}
381382
catch (Exception ex)
382383
{
383384
Logger.LogWarning("Schema validation failed. Unexpected behavior could occur: {Exception}", ex);
384385
}
385386

386-
this.schema = schema;
387-
await Database.Execute("SELECT powersync_replace_schema(?)", [schema.ToJSON()]);
387+
this.schema = compiledSchema;
388+
await Database.Execute("SELECT powersync_replace_schema(?)", [compiledSchema.ToJSON()]);
388389
await Database.RefreshSchema();
389390
Emit(new PowerSyncDBEvent { SchemaChanged = schema });
390391
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace PowerSync.Common.DB.Schema;
2+
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
5+
6+
class CompiledSchema(Dictionary<string, CompiledTable> tables)
7+
{
8+
private readonly Dictionary<string, CompiledTable> Tables = tables;
9+
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 (CompiledTable.InvalidSQLCharacters.IsMatch(tableName))
18+
{
19+
throw new Exception($"Invalid characters in table name: {tableName}");
20+
}
21+
22+
table.Validate();
23+
}
24+
}
25+
26+
public string ToJSON()
27+
{
28+
var jsonObject = new
29+
{
30+
tables = Tables.Select(kv =>
31+
{
32+
var json = JObject.Parse(kv.Value.ToJSON(kv.Key));
33+
var orderedJson = new JObject { ["name"] = kv.Key };
34+
orderedJson.Merge(json, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat });
35+
return orderedJson;
36+
}).ToList()
37+
};
38+
39+
return JsonConvert.SerializeObject(jsonObject);
40+
}
41+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
namespace PowerSync.Common.DB.Schema;
2+
3+
using System.Collections.Generic;
4+
using System.Text.RegularExpressions;
5+
6+
using Newtonsoft.Json;
7+
8+
class CompiledTable
9+
{
10+
public static readonly Regex InvalidSQLCharacters = new Regex(@"[""'%,.#\s\[\]]", RegexOptions.Compiled);
11+
12+
public string Name { get; init; } = null!;
13+
protected TableOptions Options { get; init; } = null!;
14+
public IReadOnlyDictionary<string, ColumnType> Columns { get; init; }
15+
public IReadOnlyDictionary<string, List<string>> Indexes { get; init; }
16+
17+
private readonly ColumnJSON[] ColumnsJSON;
18+
private readonly IndexJSON[] IndexesJSON;
19+
20+
public CompiledTable(string name, Dictionary<string, ColumnType> columns, TableOptions options)
21+
{
22+
ColumnsJSON =
23+
columns
24+
.Select(kvp => new ColumnJSON(new ColumnJSONOptions(kvp.Key, kvp.Value)))
25+
.ToArray();
26+
27+
IndexesJSON =
28+
(Options?.Indexes ?? [])
29+
.Select(kvp =>
30+
new IndexJSON(new IndexJSONOptions(
31+
kvp.Key,
32+
kvp.Value.Select(name =>
33+
new IndexedColumnJSON(new IndexedColumnJSONOptions(
34+
name.Replace("-", ""), !name.StartsWith("-")))
35+
).ToArray()
36+
))
37+
)
38+
.ToArray();
39+
40+
Name = name;
41+
Columns = columns;
42+
Options = options;
43+
Indexes = Options?.Indexes ?? [];
44+
}
45+
46+
public void Validate()
47+
{
48+
if (string.IsNullOrWhiteSpace(Name))
49+
{
50+
throw new Exception($"Table name is required.");
51+
}
52+
53+
if (!string.IsNullOrWhiteSpace(Options.ViewName) && InvalidSQLCharacters.IsMatch(Options.ViewName!))
54+
{
55+
throw new Exception($"Invalid characters in view name: {Options.ViewName}");
56+
}
57+
58+
if (Columns.Count > Table.MAX_AMOUNT_OF_COLUMNS)
59+
{
60+
throw new Exception(
61+
$"Table has too many columns. The maximum number of columns is {Table.MAX_AMOUNT_OF_COLUMNS}.");
62+
}
63+
64+
if (Options.TrackMetadata && Options.LocalOnly)
65+
{
66+
throw new Exception("Can't include metadata for local-only tables.");
67+
}
68+
69+
if (Options.TrackPreviousValues != null && Options.LocalOnly)
70+
{
71+
throw new Exception("Can't include old values for local-only tables.");
72+
}
73+
74+
var columnNames = new HashSet<string> { "id" };
75+
76+
foreach (var columnName in Columns.Keys)
77+
{
78+
if (columnName == "id")
79+
{
80+
throw new Exception("An id column is automatically added, custom id columns are not supported");
81+
}
82+
83+
if (InvalidSQLCharacters.IsMatch(columnName))
84+
{
85+
throw new Exception($"Invalid characters in column name: {columnName}");
86+
}
87+
88+
columnNames.Add(columnName);
89+
}
90+
91+
foreach (var kvp in Indexes)
92+
{
93+
var indexName = kvp.Key;
94+
var indexColumns = kvp.Value;
95+
96+
if (InvalidSQLCharacters.IsMatch(indexName))
97+
{
98+
throw new Exception($"Invalid characters in index name: {indexName}");
99+
}
100+
101+
foreach (var indexColumn in indexColumns)
102+
{
103+
if (!columnNames.Contains(indexColumn))
104+
{
105+
throw new Exception($"Column {indexColumn} not found for index {indexName}");
106+
}
107+
}
108+
}
109+
}
110+
111+
public string ToJSON(string Name = "")
112+
{
113+
var trackPrevious = Options.TrackPreviousValues;
114+
115+
var jsonObject = new
116+
{
117+
view_name = Options.ViewName ?? Name,
118+
local_only = Options.LocalOnly,
119+
insert_only = Options.InsertOnly,
120+
columns = ColumnsJSON.Select(c => c.ToJSONObject()).ToList(),
121+
indexes = IndexesJSON.Select(i => i.ToJSONObject(this)).ToList(),
122+
123+
include_metadata = Options.TrackMetadata,
124+
ignore_empty_update = Options.IgnoreEmptyUpdates,
125+
include_old = (object)(trackPrevious switch
126+
{
127+
null => false,
128+
{ Columns: null } => true,
129+
{ Columns: var cols } => cols
130+
}),
131+
include_old_only_when_changed = trackPrevious?.OnlyWhenChanged ?? false
132+
};
133+
134+
return JsonConvert.SerializeObject(jsonObject);
135+
}
136+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class IndexJSON(IndexJSONOptions options)
1212

1313
public IndexedColumnJSON[] Columns => options.Columns ?? [];
1414

15-
public object ToJSONObject(Table table)
15+
public object ToJSONObject(CompiledTable table)
1616
{
1717
return new
1818
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class IndexedColumnJSON(IndexedColumnJSONOptions options)
1414

1515
protected bool Ascending { get; set; } = options.Ascending;
1616

17-
public string ToJSON(Table table)
17+
public string ToJSON(CompiledTable table)
1818
{
1919
var colType = table.Columns.TryGetValue(Name, out var value) ? value : default;
2020

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,22 @@
11
namespace PowerSync.Common.DB.Schema;
22

3-
using Newtonsoft.Json;
4-
using Newtonsoft.Json.Linq;
5-
6-
public class Schema(Dictionary<string, Table> tables)
3+
public class Schema
74
{
8-
private readonly Dictionary<string, Table> Tables = tables;
5+
private readonly List<Table> _tables;
96

10-
public void Validate()
7+
public Schema(params Table[] tables)
118
{
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-
}
9+
_tables = tables.ToList();
2410
}
2511

26-
public string ToJSON()
12+
internal CompiledSchema Compile()
2713
{
28-
var jsonObject = new
14+
Dictionary<string, CompiledTable> tableMap = new();
15+
foreach (Table table in _tables)
2916
{
30-
tables = Tables.Select(kv =>
31-
{
32-
var json = JObject.Parse(kv.Value.ToJSON(kv.Key));
33-
var orderedJson = new JObject { ["name"] = kv.Key };
34-
orderedJson.Merge(json, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat });
35-
return orderedJson;
36-
}).ToList()
37-
};
38-
39-
return JsonConvert.SerializeObject(jsonObject);
17+
var compiled = table.Compile();
18+
tableMap[compiled.Name] = compiled;
19+
}
20+
return new CompiledSchema(tableMap);
4021
}
4122
}

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

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)