Skip to content

Commit 00cea74

Browse files
authored
Schema factory syntax (#37)
1 parent 063395d commit 00cea74

17 files changed

Lines changed: 344 additions & 235 deletions

File tree

PowerSync/PowerSync.Common/CHANGELOG.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# PowerSync.Common Changelog
22

33
## 0.0.8-alpha.1
4+
5+
- Updated the syntax for defining the app schema to use a factory pattern.
46
- Add support for [sync streams](https://docs.powersync.com/sync/streams/overview).
57
- Return an `IDisposable` from `PowerSync.Watch`, allowing for easier cancellation of watched queries.
68
- Replaced the old JSON-based method of extracting type information from queries with using Dapper internally for queries, improving memory usage and execution time for querying.
@@ -12,10 +14,11 @@ Console.WriteLine($"Asset ID: {asset.id}");
1214
```
1315

1416
## 0.0.7-alpha.1
17+
1518
- Added fallback to check the application's root directory for the PowerSync extension - fixing compatibility with WPF/WAP, .NET Framework <= 4.8, and other platforms that flatten DLLs into the base folder.
1619
- Added `ExecuteBatch()` implementation.
1720
- Added `GetUploadQueueStats()` to `PowerSyncDatabase`.
18-
- Altered query methods, `Execute()`, `GetAll()`, `GetOptional()`, `Get()`, `Watch()`, to support null parameters in their parameters list, for example:
21+
- Altered query methods, `Execute()`, `GetAll()`, `GetOptional()`, `Get()`, `Watch()`, to support null parameters in their parameters list, for example:
1922

2023
```csharp
2124
db.Execute(
@@ -25,6 +28,7 @@ db.Execute(
2528
```
2629

2730
## 0.0.6-alpha.1
31+
2832
- Updated to the latest version (0.4.10) of the core extension.
2933
- Dropping support for the legacy C# sync implementation.
3034
- Add `trackPreviousValues` option on `TableOptions` which sets `CrudEntry.PreviousValues` to previous values on updates.
@@ -49,12 +53,15 @@ db.Connect(connector, new PowerSync.Common.Client.Sync.Stream.PowerSyncConnectio
4953
```
5054

5155
## 0.0.5-alpha.1
56+
5257
- Using the latest version (0.4.9) of the core extension, it introduces support for the Rust Sync implementation and also makes it the default - users can still opt out and use the legacy C# sync implementation as option when calling `connect()`.
5358

5459
## 0.0.4-alpha.1
55-
- Fixed MAUI issues related to extension loading when installing package outside of the monorepo.
60+
61+
- Fixed MAUI issues related to extension loading when installing package outside of the monorepo.
5662

5763
## 0.0.3-alpha.1
64+
5865
- Minor changes to accommodate PowerSync.MAUI package extension.
5966

6067
## 0.0.2-alpha.2
@@ -69,8 +76,9 @@ db.Connect(connector, new PowerSync.Common.Client.Sync.Stream.PowerSyncConnectio
6976
- Introduce package. Support for Desktop .NET use cases.
7077

7178
### Platform Runtime Support Added
72-
* linux-arm64
73-
* linux-x64
74-
* osx-arm64
75-
* osx-x64
76-
* wind-x64
79+
80+
- linux-arm64
81+
- linux-x64
82+
- osx-arm64
83+
- osx-x64
84+
- wind-x64

PowerSync/PowerSync.Common/Client/ConnectionManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public class ConnectionManager : EventStream<ConnectionManagerEvent>
131131
/// </summary>
132132
protected Action? SyncDisposer;
133133

134-
Func<IPowerSyncBackendConnector, CreateSyncImplementationOptions, Task<ConnectionManagerSyncImplementationResult>> CreateSyncImplementation;
134+
readonly Func<IPowerSyncBackendConnector, CreateSyncImplementationOptions, Task<ConnectionManagerSyncImplementationResult>> CreateSyncImplementation;
135135

136136
protected ILogger Logger;
137137

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

3-
using Newtonsoft.Json;
4-
53
public enum ColumnType
64
{
7-
TEXT,
8-
INTEGER,
9-
REAL
5+
Text,
6+
Integer,
7+
Real
108
}
119

12-
public class ColumnOptions(string Name, ColumnType? Type)
10+
class ColumnJSONOptions(string Name, ColumnType? Type)
1311
{
1412
public string Name { get; set; } = Name;
1513
public ColumnType? Type { get; set; } = Type;
1614
}
1715

18-
public class Column(ColumnOptions options)
16+
class ColumnJSON(ColumnJSONOptions options)
1917
{
20-
public const int MAX_AMOUNT_OF_COLUMNS = 1999;
21-
2218
public string Name { get; set; } = options.Name;
2319

24-
public ColumnType Type { get; set; } = options.Type ?? ColumnType.TEXT;
20+
public ColumnType Type { get; set; } = options.Type ?? ColumnType.Text;
2521

26-
public string ToJSON()
22+
public object ToJSONObject()
2723
{
28-
return JsonConvert.SerializeObject(new
24+
return new
2925
{
3026
name = Name,
3127
type = Type.ToString()
32-
});
28+
};
3329
}
3430
}

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

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace PowerSync.Common.DB.Schema;
2+
3+
class IndexJSONOptions(string name, IndexedColumnJSON[]? columns = null)
4+
{
5+
public string Name { get; set; } = name;
6+
public IndexedColumnJSON[]? Columns { get; set; } = columns ?? [];
7+
}
8+
9+
class IndexJSON(IndexJSONOptions options)
10+
{
11+
public string Name { get; set; } = options.Name;
12+
13+
public IndexedColumnJSON[] Columns => options.Columns ?? [];
14+
15+
public object ToJSONObject(Table table)
16+
{
17+
return new
18+
{
19+
name = Name,
20+
columns = Columns.Select(column => column.ToJSON(table)).ToList()
21+
};
22+
}
23+
}

PowerSync/PowerSync.Common/DB/Schema/IndexedColumn.cs renamed to PowerSync/PowerSync.Common/DB/Schema/IndexedColumnJSON.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ namespace PowerSync.Common.DB.Schema;
22

33
using Newtonsoft.Json;
44

5-
public class IndexColumnOptions(string Name, bool Ascending = true)
5+
class IndexedColumnJSONOptions(string Name, bool Ascending = true)
66
{
77
public string Name { get; set; } = Name;
88
public bool Ascending { get; set; } = Ascending;
99
}
1010

11-
public class IndexedColumn(IndexColumnOptions options)
11+
class IndexedColumnJSON(IndexedColumnJSONOptions options)
1212
{
1313
protected string Name { get; set; } = options.Name;
1414

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

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace PowerSync.Common.DB.Schema;
2+
3+
public class SchemaFactory
4+
{
5+
private readonly List<Table> _tables;
6+
7+
public SchemaFactory(params Table[] tables)
8+
{
9+
_tables = tables.ToList();
10+
}
11+
12+
public SchemaFactory(params TableFactory[] tableFactories)
13+
{
14+
_tables = tableFactories.Select((f) => f.Create()).ToList();
15+
}
16+
17+
public Schema Create()
18+
{
19+
Dictionary<string, Table> tableMap = new();
20+
foreach (Table table in _tables)
21+
{
22+
tableMap[table.Name] = table;
23+
}
24+
return new Schema(tableMap);
25+
}
26+
}

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

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

3+
using System.Collections.Generic;
34
using System.Text.RegularExpressions;
45

56
using Newtonsoft.Json;
@@ -62,54 +63,61 @@ public class TrackPreviousOptions
6263

6364
public class Table
6465
{
65-
public static readonly Regex InvalidSQLCharacters = new Regex(@"[""'%,.#\s\[\]]", RegexOptions.Compiled);
66+
public const int MAX_AMOUNT_OF_COLUMNS = 1999;
6667

68+
public static readonly Regex InvalidSQLCharacters = new Regex(@"[""'%,.#\s\[\]]", RegexOptions.Compiled);
6769

70+
public string Name { get; init; } = null!;
6871
protected TableOptions Options { get; init; } = null!;
72+
public IReadOnlyDictionary<string, ColumnType> Columns { get; init; }
73+
public IReadOnlyDictionary<string, List<string>> Indexes { get; init; }
6974

70-
public Dictionary<string, ColumnType> Columns;
71-
public Dictionary<string, List<string>> Indexes;
72-
73-
private readonly List<Column> ConvertedColumns;
74-
private readonly List<Index> ConvertedIndexes;
75+
private readonly ColumnJSON[] ColumnsJSON;
76+
private readonly IndexJSON[] IndexesJSON;
7577

76-
public Table(Dictionary<string, ColumnType> columns, TableOptions? options = null)
78+
public Table(string name, Dictionary<string, ColumnType> columns, TableOptions? options = null)
7779
{
78-
ConvertedColumns = [.. columns.Select(kv => new Column(new ColumnOptions(kv.Key, kv.Value)))];
79-
80-
ConvertedIndexes =
81-
[
82-
.. (Options?.Indexes ?? [])
83-
.Select(kv =>
84-
new Index(new IndexOptions(
85-
kv.Key,
86-
[
87-
.. kv.Value.Select(name =>
88-
new IndexedColumn(new IndexColumnOptions(
89-
name.Replace("-", ""), !name.StartsWith("-")))
90-
)
91-
]
80+
ColumnsJSON =
81+
columns
82+
.Select(kvp => new ColumnJSON(new ColumnJSONOptions(kvp.Key, kvp.Value)))
83+
.ToArray();
84+
85+
IndexesJSON =
86+
(Options?.Indexes ?? [])
87+
.Select(kvp =>
88+
new IndexJSON(new IndexJSONOptions(
89+
kvp.Key,
90+
kvp.Value.Select(name =>
91+
new IndexedColumnJSON(new IndexedColumnJSONOptions(
92+
name.Replace("-", ""), !name.StartsWith("-")))
93+
).ToArray()
9294
))
9395
)
94-
];
96+
.ToArray();
9597

9698
Options = options ?? new TableOptions();
9799

100+
Name = name;
98101
Columns = columns;
99102
Indexes = Options?.Indexes ?? [];
100103
}
101104

102105
public void Validate()
103106
{
107+
if (string.IsNullOrWhiteSpace(Name))
108+
{
109+
throw new Exception($"Table name is required.");
110+
}
111+
104112
if (!string.IsNullOrWhiteSpace(Options.ViewName) && InvalidSQLCharacters.IsMatch(Options.ViewName!))
105113
{
106114
throw new Exception($"Invalid characters in view name: {Options.ViewName}");
107115
}
108116

109-
if (Columns.Count > Column.MAX_AMOUNT_OF_COLUMNS)
117+
if (Columns.Count > MAX_AMOUNT_OF_COLUMNS)
110118
{
111119
throw new Exception(
112-
$"Table has too many columns. The maximum number of columns is {Column.MAX_AMOUNT_OF_COLUMNS}.");
120+
$"Table has too many columns. The maximum number of columns is {MAX_AMOUNT_OF_COLUMNS}.");
113121
}
114122

115123
if (Options.TrackMetadata && Options.LocalOnly)
@@ -168,8 +176,8 @@ public string ToJSON(string Name = "")
168176
view_name = Options.ViewName ?? Name,
169177
local_only = Options.LocalOnly,
170178
insert_only = Options.InsertOnly,
171-
columns = ConvertedColumns.Select(c => JsonConvert.DeserializeObject<object>(c.ToJSON())).ToList(),
172-
indexes = ConvertedIndexes.Select(e => JsonConvert.DeserializeObject<object>(e.ToJSON(this))).ToList(),
179+
columns = ColumnsJSON.Select(c => c.ToJSONObject()).ToList(),
180+
indexes = IndexesJSON.Select(i => i.ToJSONObject(this)).ToList(),
173181

174182
include_metadata = Options.TrackMetadata,
175183
ignore_empty_update = Options.IgnoreEmptyUpdates,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
namespace PowerSync.Common.DB.Schema;
2+
3+
using System.Collections;
4+
5+
public class TableFactory()
6+
{
7+
public ColumnMap Columns { get; set; } = new();
8+
public IndexMap Indexes { get; set; } = new();
9+
10+
public string Name { get; set; } = null!;
11+
public bool LocalOnly { get; set; } = false;
12+
public bool InsertOnly { get; set; } = false;
13+
string? ViewName { get; set; }
14+
bool? TrackMetadata { get; set; }
15+
TrackPreviousOptions? TrackPreviousValues { get; set; }
16+
bool? IgnoreEmptyUpdates { get; set; }
17+
18+
public Table Create()
19+
{
20+
if (string.IsNullOrWhiteSpace(Name))
21+
{
22+
throw new Exception("Table name is required.");
23+
}
24+
TableOptions options = new(
25+
indexes: Indexes.Indexes,
26+
localOnly: LocalOnly,
27+
insertOnly: InsertOnly,
28+
viewName: ViewName,
29+
trackMetadata: TrackMetadata,
30+
trackPreviousValues: TrackPreviousValues,
31+
ignoreEmptyUpdates: IgnoreEmptyUpdates
32+
);
33+
return new Table(Name, Columns.Columns, options);
34+
}
35+
}
36+
37+
public class ColumnMap : IEnumerable
38+
{
39+
public Dictionary<string, ColumnType> Columns { get; } = new();
40+
41+
public void Add(string key, ColumnType value) => Columns.Add(key, value);
42+
43+
public ColumnType this[string name] { set { Columns[name] = value; } }
44+
public IEnumerator GetEnumerator() => Columns.GetEnumerator();
45+
}
46+
47+
public class IndexMap : IEnumerable
48+
{
49+
public Dictionary<string, List<string>> Indexes { get; } = new();
50+
51+
public void Add(string key, List<string> value) => Indexes.Add(key, value);
52+
53+
public List<string> this[string name] { set { Indexes[name] = value; } }
54+
public IEnumerator GetEnumerator() => Indexes.GetEnumerator();
55+
}
56+

0 commit comments

Comments
 (0)