Skip to content

Commit 2333282

Browse files
authored
Merge table cache into table handle (#139)
## Description of Changes Merges cache into the table handle as suggested on the original PR + hides most table methods that shouldn't be part of the stable API. Few remaining methods will need a codegen change to be available only to subclasses, so for now that's out of scope. Same for merging ClientCache into RemoteTables - we shouldn't need a separate collection, and instead could autogenerate a switch expression over table name. ## API - [ ] This is an API breaking change to the SDK *If the API is breaking, please state below what will break* ## Requires SpacetimeDB PRs *List any PRs here that are required for this SDK change to work*
1 parent 4d36ae3 commit 2333282

File tree

3 files changed

+87
-105
lines changed

3 files changed

+87
-105
lines changed

src/ClientCache.cs

Lines changed: 7 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,74 +6,22 @@
66

77
namespace SpacetimeDB
88
{
9+
// TODO: merge this into `RemoteTables`.
10+
// It should just provide auto-generated `GetTable` and `GetTables` methods.
911
public class ClientCache
1012
{
11-
public interface ITableCache : IEnumerable<KeyValuePair<byte[], IDatabaseRow>>
12-
{
13-
Type ClientTableType { get; }
14-
bool InsertEntry(byte[] rowBytes, IDatabaseRow value);
15-
bool DeleteEntry(byte[] rowBytes);
16-
IDatabaseRow DecodeValue(byte[] bytes);
17-
IRemoteTableHandle Handle { get; }
18-
}
19-
20-
public class TableCache<Row> : ITableCache
21-
where Row : IDatabaseRow, new()
22-
{
23-
public TableCache(IRemoteTableHandle handle) => Handle = handle;
24-
25-
public IRemoteTableHandle Handle { get; init; }
26-
27-
public Type ClientTableType => typeof(Row);
28-
29-
public readonly Dictionary<byte[], Row> Entries = new(ByteArrayComparer.Instance);
30-
31-
/// <summary>
32-
/// Inserts the value into the table. There can be no existing value with the provided BSATN bytes.
33-
/// </summary>
34-
/// <param name="rowBytes">The BSATN encoded bytes of the row to retrieve.</param>
35-
/// <param name="value">The parsed row encoded by the <paramref>rowBytes</paramref>.</param>
36-
/// <returns>True if the row was inserted, false if the row wasn't inserted because it was a duplicate.</returns>
37-
public bool InsertEntry(byte[] rowBytes, IDatabaseRow value) => Entries.TryAdd(rowBytes, (Row)value);
38-
39-
/// <summary>
40-
/// Deletes a value from the table.
41-
/// </summary>
42-
/// <param name="rowBytes">The BSATN encoded bytes of the row to remove.</param>
43-
/// <returns>True if and only if the value was previously resident and has been deleted.</returns>
44-
public bool DeleteEntry(byte[] rowBytes)
45-
{
46-
if (Entries.Remove(rowBytes))
47-
{
48-
return true;
49-
}
50-
51-
Log.Warn("Deleting value that we don't have (no cached value available)");
52-
return false;
53-
}
54-
55-
// The function to use for decoding a type value.
56-
public IDatabaseRow DecodeValue(byte[] bytes) => BSATNHelpers.Decode<Row>(bytes);
57-
58-
public IEnumerator<KeyValuePair<byte[], IDatabaseRow>> GetEnumerator() => Entries.Select(kv => new KeyValuePair<byte[], IDatabaseRow>(kv.Key, kv.Value)).GetEnumerator();
59-
60-
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
61-
}
62-
63-
private readonly Dictionary<string, ITableCache> tables = new();
13+
private readonly Dictionary<string, IRemoteTableHandle> tables = new();
6414

65-
public void AddTable<Row>(string name, IRemoteTableHandle handle)
15+
public void AddTable<Row>(string name, IRemoteTableHandle table)
6616
where Row : IDatabaseRow, new()
6717
{
68-
var cache = new TableCache<Row>(handle);
69-
handle.SetCache(cache);
70-
if (!tables.TryAdd(name, cache))
18+
if (!tables.TryAdd(name, table))
7119
{
7220
Log.Error($"Table with name already exists: {name}");
7321
}
7422
}
7523

76-
public ITableCache? GetTable(string name)
24+
public IRemoteTableHandle? GetTable(string name)
7725
{
7826
if (tables.TryGetValue(name, out var table))
7927
{
@@ -84,6 +32,6 @@ public void AddTable<Row>(string name, IRemoteTableHandle handle)
8432
return null;
8533
}
8634

87-
public IEnumerable<ITableCache> GetTables() => tables.Values;
35+
public IEnumerable<IRemoteTableHandle> GetTables() => tables.Values;
8836
}
8937
}

src/SpacetimeDBClient.cs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ public abstract class DbConnectionBase<DbConnection, Reducer> : IDbConnection
8787
{
8888
public static DbConnectionBuilder<DbConnection, Reducer> Builder() => new();
8989

90-
struct DbValue
90+
readonly struct DbValue
9191
{
92-
public IDatabaseRow value;
93-
public byte[] bytes;
92+
public readonly IDatabaseRow value;
93+
public readonly byte[] bytes;
9494

9595
public DbValue(IDatabaseRow value, byte[] bytes)
9696
{
@@ -101,7 +101,7 @@ public DbValue(IDatabaseRow value, byte[] bytes)
101101

102102
struct DbOp
103103
{
104-
public ClientCache.ITableCache table;
104+
public IRemoteTableHandle table;
105105
public DbValue? delete;
106106
public DbValue? insert;
107107
}
@@ -193,12 +193,14 @@ struct PreProcessedMessage
193193
private readonly CancellationTokenSource _preProcessCancellationTokenSource = new();
194194
private CancellationToken _preProcessCancellationToken => _preProcessCancellationTokenSource.Token;
195195

196-
static DbValue Decode(ClientCache.ITableCache table, EncodedValue value) => value switch
196+
static DbValue Decode(IRemoteTableHandle table, EncodedValue value, out object? primaryKey)
197197
{
198-
EncodedValue.Binary(var bin) => new DbValue(table.DecodeValue(bin), bin),
199-
EncodedValue.Text(var text) => throw new InvalidOperationException("JavaScript messages aren't supported."),
200-
_ => throw new InvalidOperationException(),
201-
};
198+
// We expect only binary messages here; let type cast exception take care of any others.
199+
var bin = ((EncodedValue.Binary)value).Binary_;
200+
var obj = table.DecodeValue(bin);
201+
primaryKey = table.GetPrimaryKey(obj);
202+
return new(obj, bin);
203+
}
202204

203205
private static readonly Status Committed = new Status.Committed(default);
204206
private static readonly Status OutOfEnergy = new Status.OutOfEnergy(default);
@@ -337,8 +339,7 @@ HashSet<byte[]> GetInsertHashSet(System.Type tableType, int tableSize)
337339

338340
foreach (var row in update.Inserts)
339341
{
340-
var op = new DbOp { table = table, insert = Decode(table, row) };
341-
var pk = table.Handle.GetPrimaryKey(op.insert.Value.value);
342+
var op = new DbOp { table = table, insert = Decode(table, row, out var pk) };
342343
if (pk != null)
343344
{
344345
// Compound key that we use for lookup.
@@ -373,8 +374,7 @@ HashSet<byte[]> GetInsertHashSet(System.Type tableType, int tableSize)
373374

374375
foreach (var row in update.Deletes)
375376
{
376-
var op = new DbOp { table = table, delete = Decode(table, row) };
377-
var pk = table.Handle.GetPrimaryKey(op.delete.Value.value);
377+
var op = new DbOp { table = table, delete = Decode(table, row, out var pk) };
378378
if (pk != null)
379379
{
380380
// Compound key that we use for lookup.
@@ -454,7 +454,7 @@ ProcessedMessage CalculateStateDiff(PreProcessedMessage preProcessedMessage)
454454
continue;
455455
}
456456

457-
foreach (var (rowBytes, oldValue) in table.Where(kv => !hashSet.Contains(kv.Key)))
457+
foreach (var (rowBytes, oldValue) in table.IterEntries().Where(kv => !hashSet.Contains(kv.Key)))
458458
{
459459
processed.dbOps.Add(new DbOp
460460
{
@@ -523,7 +523,7 @@ private void OnMessageProcessCompleteUpdate(IEventContext eventContext, List<DbO
523523
{
524524
try
525525
{
526-
update.table.Handle.InvokeBeforeDelete(eventContext, oldValue);
526+
update.table.InvokeBeforeDelete(eventContext, oldValue);
527527
}
528528
catch (Exception e)
529529
{
@@ -542,7 +542,7 @@ private void OnMessageProcessCompleteUpdate(IEventContext eventContext, List<DbO
542542
{
543543
if (update.table.DeleteEntry(delete.bytes))
544544
{
545-
update.table.Handle.InternalInvokeValueDeleted(delete.value);
545+
update.table.InternalInvokeValueDeleted(delete.value);
546546
}
547547
else
548548
{
@@ -555,7 +555,7 @@ private void OnMessageProcessCompleteUpdate(IEventContext eventContext, List<DbO
555555
{
556556
if (update.table.InsertEntry(insert.bytes, insert.value))
557557
{
558-
update.table.Handle.InternalInvokeValueInserted(insert.value);
558+
update.table.InternalInvokeValueInserted(insert.value);
559559
}
560560
else
561561
{
@@ -573,17 +573,15 @@ private void OnMessageProcessCompleteUpdate(IEventContext eventContext, List<DbO
573573
switch (dbOp)
574574
{
575575
case { insert: { value: var newValue }, delete: { value: var oldValue } }:
576-
{
577-
dbOp.table.Handle.InvokeUpdate(eventContext, oldValue, newValue);
578-
break;
579-
}
576+
dbOp.table.InvokeUpdate(eventContext, oldValue, newValue);
577+
break;
580578

581579
case { insert: { value: var newValue } }:
582-
dbOp.table.Handle.InvokeInsert(eventContext, newValue);
580+
dbOp.table.InvokeInsert(eventContext, newValue);
583581
break;
584582

585583
case { delete: { value: var oldValue } }:
586-
dbOp.table.Handle.InvokeDelete(eventContext, oldValue);
584+
dbOp.table.InvokeDelete(eventContext, oldValue);
587585
break;
588586
}
589587
}

src/Table.cs

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,53 +20,89 @@ protected RemoteBase(DbConnection conn)
2020

2121
public interface IRemoteTableHandle
2222
{
23-
void SetCache(ClientCache.ITableCache cache);
24-
23+
// These methods need to be overridden by autogen.
2524
object? GetPrimaryKey(IDatabaseRow row);
26-
2725
void InternalInvokeValueInserted(IDatabaseRow row);
2826
void InternalInvokeValueDeleted(IDatabaseRow row);
29-
void InvokeInsert(IEventContext context, IDatabaseRow row);
30-
void InvokeDelete(IEventContext context, IDatabaseRow row);
31-
void InvokeBeforeDelete(IEventContext context, IDatabaseRow row);
32-
void InvokeUpdate(IEventContext context, IDatabaseRow oldRow, IDatabaseRow newRow);
27+
28+
// These are provided by RemoteTableHandle.
29+
internal Type ClientTableType { get; }
30+
internal IEnumerable<KeyValuePair<byte[], IDatabaseRow>> IterEntries();
31+
internal bool InsertEntry(byte[] rowBytes, IDatabaseRow value);
32+
internal bool DeleteEntry(byte[] rowBytes);
33+
internal IDatabaseRow DecodeValue(byte[] bytes);
34+
35+
internal void InvokeInsert(IEventContext context, IDatabaseRow row);
36+
internal void InvokeDelete(IEventContext context, IDatabaseRow row);
37+
internal void InvokeBeforeDelete(IEventContext context, IDatabaseRow row);
38+
internal void InvokeUpdate(IEventContext context, IDatabaseRow oldRow, IDatabaseRow newRow);
3339
}
3440

3541
public abstract class RemoteTableHandle<EventContext, Row> : IRemoteTableHandle
3642
where EventContext : class, IEventContext
3743
where Row : IDatabaseRow, new()
3844
{
39-
public void SetCache(ClientCache.ITableCache cache) => Cache = (ClientCache.TableCache<Row>)cache;
45+
// These methods need to be overridden by autogen.
46+
public virtual object? GetPrimaryKey(IDatabaseRow row) => null;
47+
public virtual void InternalInvokeValueInserted(IDatabaseRow row) { }
48+
public virtual void InternalInvokeValueDeleted(IDatabaseRow row) { }
49+
50+
// These are provided by RemoteTableHandle.
51+
Type IRemoteTableHandle.ClientTableType => typeof(Row);
52+
53+
private readonly Dictionary<byte[], Row> Entries = new(Internal.ByteArrayComparer.Instance);
54+
55+
IEnumerable<KeyValuePair<byte[], IDatabaseRow>> IRemoteTableHandle.IterEntries() =>
56+
Entries.Select(kv => new KeyValuePair<byte[], IDatabaseRow>(kv.Key, kv.Value));
57+
58+
/// <summary>
59+
/// Inserts the value into the table. There can be no existing value with the provided BSATN bytes.
60+
/// </summary>
61+
/// <param name="rowBytes">The BSATN encoded bytes of the row to retrieve.</param>
62+
/// <param name="value">The parsed row encoded by the <paramref>rowBytes</paramref>.</param>
63+
/// <returns>True if the row was inserted, false if the row wasn't inserted because it was a duplicate.</returns>
64+
bool IRemoteTableHandle.InsertEntry(byte[] rowBytes, IDatabaseRow value) => Entries.TryAdd(rowBytes, (Row)value);
4065

41-
internal ClientCache.TableCache<Row>? Cache;
66+
/// <summary>
67+
/// Deletes a value from the table.
68+
/// </summary>
69+
/// <param name="rowBytes">The BSATN encoded bytes of the row to remove.</param>
70+
/// <returns>True if and only if the value was previously resident and has been deleted.</returns>
71+
bool IRemoteTableHandle.DeleteEntry(byte[] rowBytes)
72+
{
73+
if (Entries.Remove(rowBytes))
74+
{
75+
return true;
76+
}
77+
78+
Log.Warn("Deleting value that we don't have (no cached value available)");
79+
return false;
80+
}
81+
82+
// The function to use for decoding a type value.
83+
IDatabaseRow IRemoteTableHandle.DecodeValue(byte[] bytes) => BSATNHelpers.Decode<Row>(bytes);
4284

4385
public event Action<EventContext, Row>? OnInsert;
4486
public event Action<EventContext, Row>? OnDelete;
4587
public event Action<EventContext, Row>? OnBeforeDelete;
4688
public event Action<EventContext, Row, Row>? OnUpdate;
4789

48-
public virtual object? GetPrimaryKey(IDatabaseRow row) => null;
49-
50-
public virtual void InternalInvokeValueInserted(IDatabaseRow row) { }
51-
52-
public virtual void InternalInvokeValueDeleted(IDatabaseRow row) { }
53-
54-
public int Count => Cache!.Entries.Count;
90+
public int Count => Entries.Count;
5591

56-
public IEnumerable<Row> Iter() => Cache!.Entries.Values;
92+
public IEnumerable<Row> Iter() => Entries.Values;
5793

58-
public IEnumerable<Row> Query(Func<Row, bool> filter) => Iter().Where(filter);
94+
protected IEnumerable<Row> Query(Func<Row, bool> filter) => Iter().Where(filter);
5995

60-
public void InvokeInsert(IEventContext context, IDatabaseRow row) =>
96+
void IRemoteTableHandle.InvokeInsert(IEventContext context, IDatabaseRow row) =>
6197
OnInsert?.Invoke((EventContext)context, (Row)row);
6298

63-
public void InvokeDelete(IEventContext context, IDatabaseRow row) =>
99+
void IRemoteTableHandle.InvokeDelete(IEventContext context, IDatabaseRow row) =>
64100
OnDelete?.Invoke((EventContext)context, (Row)row);
65101

66-
public void InvokeBeforeDelete(IEventContext context, IDatabaseRow row) =>
102+
void IRemoteTableHandle.InvokeBeforeDelete(IEventContext context, IDatabaseRow row) =>
67103
OnBeforeDelete?.Invoke((EventContext)context, (Row)row);
68104

69-
public void InvokeUpdate(IEventContext context, IDatabaseRow oldRow, IDatabaseRow newRow) =>
105+
void IRemoteTableHandle.InvokeUpdate(IEventContext context, IDatabaseRow oldRow, IDatabaseRow newRow) =>
70106
OnUpdate?.Invoke((EventContext)context, (Row)oldRow, (Row)newRow);
71107
}
72-
}
108+
}

0 commit comments

Comments
 (0)