Skip to content

Commit 1468ce2

Browse files
committed
Added new schema options (tracking metadata/previous data).
1 parent 8de793d commit 1468ce2

File tree

4 files changed

+133
-64
lines changed

4 files changed

+133
-64
lines changed

PowerSync/PowerSync.Common/Client/Sync/Bucket/SqliteBucketStorage.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,9 @@ record ControlResult(string? r);
273273

274274
public async Task<string> Control(string op, object? payload = null)
275275
{
276-
Console.WriteLine("Calling control on extension "+ op + " - ");
277276
return await db.WriteTransaction(async tx =>
278277
{
279278
var result = await tx.Get<ControlResult>("SELECT powersync_control(?, ?) AS r", [op, payload ?? ""]);
280-
Console.WriteLine("completed op: " + op + " - " + JsonConvert.SerializeObject(result));
281279
return result.r!;
282280
});
283281
}

PowerSync/PowerSync.Common/Client/Sync/Stream/StreamingSyncImplementation.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,7 @@ protected async Task StreamingSync(CancellationToken? signal, PowerSyncConnectio
280280
{
281281
break;
282282
}
283-
Console.WriteLine("XXXX starting");
284283
await StreamingSyncIteration(nestedCts.Token, options);
285-
Console.WriteLine("XXXX ending");
286284
// Continue immediately
287285
}
288286
catch (Exception ex)
@@ -363,8 +361,6 @@ private async Task SyncIteration(CancellationToken? signal, RequiredPowerSyncCon
363361

364362
async Task Connect(EstablishSyncStream instruction)
365363
{
366-
Console.WriteLine("----- We got het here again" + nestedCts.Token.IsCancellationRequested);
367-
Console.WriteLine("-----" + JsonConvert.SerializeObject(instruction.Request));
368364
var syncOptions = new SyncStreamOptions
369365
{
370366
Path = "/sync/stream",
@@ -386,7 +382,6 @@ async Task Connect(EstablishSyncStream instruction)
386382
logger.LogDebug("Parsing line for rust sync stream {message}", "xx");
387383
await Control("line_text", line);
388384
}
389-
Console.WriteLine("Done");
390385
}
391386

392387
async Task Stop()
@@ -481,16 +476,10 @@ void HandleInstruction(Instruction instruction)
481476
try
482477
{
483478
notifyCompletedUploads = () => { Task.Run(async () => await Control("completed_upload")); };
484-
logger.LogError("START");
485479
await Control("start", JsonConvert.SerializeObject(resolvedOptions.Params));
486480
if (receivingLines != null)
487481
{
488482
await receivingLines;
489-
logger.LogError("Done waiting");
490-
}
491-
else
492-
{
493-
Console.WriteLine("No receiving lines task was started, this should not happen.");
494483
}
495484
}
496485
finally

PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,62 @@ namespace PowerSync.Common.DB.Crud;
55

66
public enum UpdateType
77
{
8-
[JsonProperty("PUT")]
9-
PUT,
8+
[JsonProperty("PUT")] PUT,
109

11-
[JsonProperty("PATCH")]
12-
PATCH,
10+
[JsonProperty("PATCH")] PATCH,
1311

14-
[JsonProperty("DELETE")]
15-
DELETE
12+
[JsonProperty("DELETE")] DELETE
1613
}
1714

1815
public class CrudEntryJSON
1916
{
20-
[JsonProperty("id")]
21-
public string Id { get; set; } = null!;
17+
[JsonProperty("id")] public string Id { get; set; } = null!;
2218

23-
[JsonProperty("data")]
24-
public string Data { get; set; } = null!;
25-
26-
[JsonProperty("tx_id")]
27-
public long? TransactionId { get; set; }
19+
[JsonProperty("data")] public string Data { get; set; } = null!;
20+
21+
[JsonProperty("tx_id")] public long? TransactionId { get; set; }
2822
}
2923

3024
public class CrudEntryDataJSON
3125
{
32-
[JsonProperty("data")]
33-
public Dictionary<string, object>? Data { get; set; }
34-
35-
[JsonProperty("op")]
36-
public UpdateType Op { get; set; }
37-
38-
[JsonProperty("type")]
39-
public string Type { get; set; } = null!;
40-
41-
[JsonProperty("id")]
42-
public string Id { get; set; } = null!;
26+
[JsonProperty("data")] public Dictionary<string, object>? Data { get; set; }
27+
28+
[JsonProperty("old")] public Dictionary<string, string?>? Old { get; set; }
29+
30+
[JsonProperty("op")] public UpdateType Op { get; set; }
31+
32+
[JsonProperty("type")] public string Type { get; set; } = null!;
33+
34+
[JsonProperty("id")] public string Id { get; set; } = null!;
35+
36+
[JsonProperty("metadata")] public string? Metadata { get; set; }
4337
}
4438

4539
public class CrudEntryOutputJSON
4640
{
47-
[JsonProperty("op_id")]
48-
public int OpId { get; set; }
41+
[JsonProperty("op_id")] public int OpId { get; set; }
4942

50-
[JsonProperty("op")]
51-
public UpdateType Op { get; set; }
43+
[JsonProperty("op")] public UpdateType Op { get; set; }
5244

53-
[JsonProperty("type")]
54-
public string Type { get; set; } = null!;
45+
[JsonProperty("type")] public string Type { get; set; } = null!;
5546

56-
[JsonProperty("id")]
57-
public string Id { get; set; } = null!;
47+
[JsonProperty("id")] public string Id { get; set; } = null!;
5848

59-
[JsonProperty("tx_id")]
60-
public long? TransactionId { get; set; }
49+
[JsonProperty("tx_id")] public long? TransactionId { get; set; }
6150

62-
[JsonProperty("data")]
63-
public Dictionary<string, object>? Data { get; set; }
51+
[JsonProperty("data")] public Dictionary<string, object>? Data { get; set; }
6452
}
6553

66-
public class CrudEntry(int clientId, UpdateType op, string table, string id, long? transactionId = null, Dictionary<string, object>? opData = null)
54+
public class CrudEntry(
55+
int clientId,
56+
UpdateType op,
57+
string table,
58+
string id,
59+
long? transactionId = null,
60+
Dictionary<string, object>? opData = null,
61+
Dictionary<String, String?>? previousValues = null,
62+
string? metadata = null
63+
)
6764
{
6865
public int ClientId { get; private set; } = clientId;
6966
public string Id { get; private set; } = id;
@@ -72,6 +69,19 @@ public class CrudEntry(int clientId, UpdateType op, string table, string id, lon
7269
public string Table { get; private set; } = table;
7370
public long? TransactionId { get; private set; } = transactionId;
7471

72+
/// <summary>
73+
/// Previous values before this change.
74+
/// </summary>
75+
public Dictionary<String, String?>? PreviousValues { get; private set; } = previousValues;
76+
77+
/// <summary>
78+
/// Client-side metadata attached with this write.
79+
///
80+
/// This field is only available when the `trackMetadata` option was set to `true` when creating a table
81+
/// and the insert or update statement set the `_metadata` column.
82+
/// </summary>
83+
public string? Metadata { get; private set; } = metadata;
84+
7585
public static CrudEntry FromRow(CrudEntryJSON dbRow)
7686
{
7787
var data = JsonConvert.DeserializeObject<CrudEntryDataJSON>(dbRow.Data)
@@ -83,7 +93,9 @@ public static CrudEntry FromRow(CrudEntryJSON dbRow)
8393
data.Type,
8494
data.Id,
8595
dbRow.TransactionId,
86-
data.Data
96+
data.Data,
97+
data.Old,
98+
data.Metadata
8799
);
88100
}
89101

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

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,57 @@ public class TableOptions(
77
Dictionary<string, List<string>>? indexes = null,
88
bool? localOnly = null,
99
bool? insertOnly = null,
10-
string? viewName = null)
10+
string? viewName = null,
11+
bool? trackMetadata = null,
12+
TrackPreviousOptions? trackPreviousOptions = null,
13+
bool? ignoreEmptyUpdates = null
14+
)
1115
{
1216
public Dictionary<string, List<string>> Indexes { get; set; } = indexes ?? [];
1317

14-
public bool LocalOnly { get; set; } = localOnly ?? false;
18+
public bool LocalOnly { get; } = localOnly ?? false;
1519

16-
public bool InsertOnly { get; set; } = insertOnly ?? false;
20+
public bool InsertOnly { get; } = insertOnly ?? false;
1721

18-
public string? ViewName { get; set; } = viewName;
22+
public string? ViewName { get; } = viewName;
23+
24+
/// <summary>
25+
/// Whether to add a hidden `_metadata` column that will be enabled for updates to attach custom
26+
/// information about writes that will be reported through [CrudEntry.metadata].
27+
/// </summary>
28+
public bool TrackMetadata { get; } = trackMetadata ?? false;
29+
30+
/// <summary>
31+
/// When set to a non-null value, track old values of columns
32+
/// </summary>
33+
public TrackPreviousOptions? TrackPreviousOptions { get; } = trackPreviousOptions ?? null;
34+
35+
/// <summary>
36+
/// Whether an `UPDATE` statement that doesn't change any values should be ignored when creating
37+
/// CRUD entries.
38+
/// </summary>
39+
public bool IgnoreEmptyUpdates { get; } = ignoreEmptyUpdates ?? false;
40+
}
41+
42+
/// <summary>
43+
/// Whether to include previous column values when PowerSync tracks local changes.
44+
/// Including old values may be helpful for some backend connector implementations,
45+
/// which is why it can be enabled on a per-table or per-column basis.
46+
/// </summary>
47+
public class TrackPreviousOptions
48+
{
49+
/// <summary>
50+
/// When defined, a list of column names for which old values should be tracked.
51+
/// </summary>
52+
[JsonProperty("columns")]
53+
public List<string>? Columns { get; set; }
54+
55+
/// <summary>
56+
/// Whether to only include old values when they were changed by an update, instead of always
57+
/// including all old values,
58+
/// </summary>
59+
[JsonProperty("onlyWhenChanged")]
60+
public bool? OnlyWhenChanged { get; set; }
1961
}
2062

2163
public class Table
@@ -35,16 +77,21 @@ public Table(Dictionary<string, ColumnType> columns, TableOptions? options = nul
3577
{
3678
ConvertedColumns = [.. columns.Select(kv => new Column(new ColumnOptions(kv.Key, kv.Value)))];
3779

38-
ConvertedIndexes = [.. (Options?.Indexes ?? [])
80+
ConvertedIndexes =
81+
[
82+
.. (Options?.Indexes ?? [])
3983
.Select(kv =>
4084
new Index(new IndexOptions(
4185
kv.Key,
42-
[.. kv.Value.Select(name =>
43-
new IndexedColumn(new IndexColumnOptions(
44-
name.Replace("-", ""), !name.StartsWith("-")))
45-
)]
86+
[
87+
.. kv.Value.Select(name =>
88+
new IndexedColumn(new IndexColumnOptions(
89+
name.Replace("-", ""), !name.StartsWith("-")))
90+
)
91+
]
4692
))
47-
)];
93+
)
94+
];
4895

4996
Options = options ?? new TableOptions();
5097

@@ -61,7 +108,18 @@ public void Validate()
61108

62109
if (Columns.Count > Column.MAX_AMOUNT_OF_COLUMNS)
63110
{
64-
throw new Exception($"Table has too many columns. The maximum number of columns is {Column.MAX_AMOUNT_OF_COLUMNS}.");
111+
throw new Exception(
112+
$"Table has too many columns. The maximum number of columns is {Column.MAX_AMOUNT_OF_COLUMNS}.");
113+
}
114+
115+
if (Options.TrackMetadata && Options.LocalOnly)
116+
{
117+
throw new Exception("Can't include metadata for local-only tables.");
118+
}
119+
120+
if (Options.TrackPreviousOptions != null && Options.LocalOnly)
121+
{
122+
throw new Exception("Can't include old values for local-only tables.");
65123
}
66124

67125
var columnNames = new HashSet<string> { "id" };
@@ -103,15 +161,27 @@ public void Validate()
103161

104162
public string ToJSON(string Name = "")
105163
{
164+
var trackPrevious = Options.TrackPreviousOptions;
165+
106166
var jsonObject = new
107167
{
108168
view_name = Options.ViewName ?? Name,
109169
local_only = Options.LocalOnly,
110170
insert_only = Options.InsertOnly,
111171
columns = ConvertedColumns.Select(c => JsonConvert.DeserializeObject<object>(c.ToJSON())).ToList(),
112-
indexes = ConvertedIndexes.Select(e => JsonConvert.DeserializeObject<object>(e.ToJSON(this))).ToList()
172+
indexes = ConvertedIndexes.Select(e => JsonConvert.DeserializeObject<object>(e.ToJSON(this))).ToList(),
173+
174+
include_metadata = Options.TrackMetadata,
175+
ignore_empty_update = Options.IgnoreEmptyUpdates,
176+
include_old = (object)(trackPrevious switch
177+
{
178+
null => false,
179+
{ Columns: null } => true,
180+
{ Columns: var cols } => cols
181+
}),
182+
include_old_only_when_changed = trackPrevious?.OnlyWhenChanged ?? false
113183
};
114184

115185
return JsonConvert.SerializeObject(jsonObject);
116186
}
117-
}
187+
}

0 commit comments

Comments
 (0)