diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index 17f7373e6a7..21c8fd60ef4 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -82,10 +82,22 @@ public abstract class UniqueIndexBase : IndexBase public UniqueIndexBase(RemoteTableHandle table) { - // Guaranteed to be a valid cast by contract of OnInternalInsert. - table.OnInternalInsert += row => cache.Add(GetKey((Row)row.Row), row); - // Guaranteed to be a valid cast by contract of OnInternalDelete. - table.OnInternalDelete += row => cache.Remove(GetKey((Row)row.Row)); + table.OnInternalInsert += rows => + { + foreach (var preHashed in rows) + { + // Guaranteed to be a valid cast by contract of OnInternalInsert. + cache.Add(GetKey((Row)preHashed.Row), preHashed); + } + }; + table.OnInternalDelete += rows => + { + foreach (var preHashed in rows) + { + // Guaranteed to be a valid cast by contract of OnInternalDelete. + cache.Remove(GetKey((Row)preHashed.Row)); + } + }; } public Row? Find(Column value) => cache.TryGetValue(value, out var row) ? (Row)row.Row : null; @@ -99,44 +111,50 @@ public abstract class BTreeIndexBase : IndexBase public BTreeIndexBase(RemoteTableHandle table) { - table.OnInternalInsert += preHashed => + table.OnInternalInsert += preHashedRows => { - // Guaranteed to be a valid cast by contract of OnInternalInsert. - var row = (Row)preHashed.Row; - var key = GetKey(row); - if (cache.TryGetValue(key, out var rows)) + foreach (var preHashed in preHashedRows) { - rows.Add(preHashed); - // Need to update the parent dictionary: rows is a mutable struct. - // Just updating the local `rows` variable won't update the parent dict. - cache[key] = rows; - } - else - { - rows = new() + // Guaranteed to be a valid cast by contract of OnInternalInsert. + var row = (Row)preHashed.Row; + var key = GetKey(row); + if (cache.TryGetValue(key, out var set)) { - preHashed - }; - cache.Add(key, rows); + set.Add(preHashed); + // Need to update the parent dictionary: `set` is a mutable struct. + // Just updating the local `set` variable won't update the parent dict. + cache[key] = set; + } + else + { + set = new() + { + preHashed + }; + cache.Add(key, set); + } } }; - table.OnInternalDelete += preHashed => + table.OnInternalDelete += preHashedRows => { - // Guaranteed to be a valid cast by contract of OnInternalDelete. - var row = (Row)preHashed.Row; - var key = GetKey(row); - var keyCache = cache[key]; - keyCache.Remove(preHashed); - if (keyCache.Count == 0) + foreach (var preHashed in preHashedRows) { - cache.Remove(key); - } - else - { - // Need to update the parent dictionary: keyCache is a mutable struct. - // Just updating the local `keyCache` variable won't update the parent dict. - cache[key] = keyCache; + // Guaranteed to be a valid cast by contract of OnInternalDelete. + var row = (Row)preHashed.Row; + var key = GetKey(row); + var set = cache[key]; + set.Remove(preHashed); + if (set.Count == 0) + { + cache.Remove(key); + } + else + { + // Need to update the parent dictionary: `set` is a mutable struct. + // Just updating the local `set` variable won't update the parent dict. + cache[key] = set; + } } }; } @@ -155,20 +173,21 @@ public RemoteTableHandle(IDbConnection conn) : base(conn) { } protected virtual object? GetPrimaryKey(Row row) => null; // These events are used by indices to add/remove rows to their dictionaries. - // They can assume the Row stored in the PreHashedRow passed is of the correct type; + // + // They are passed all the modified rows for an update at once: + // this avoids the overhead of invoking handlers per-row. + // (Unfortunately, it's too late to make this sort of change for user callbacks...) + // + // These callbacks can assume the Row stored in the PreHashedRow passed is of the correct type; // the check is done before performing these callbacks. - // TODO: figure out if they can be merged into regular OnInsert / OnDelete. - // I didn't do that because that delays the index updates until after the row is processed. - // In theory, that shouldn't be the issue, but I didn't want to break it right before leaving :) - // - Ingvar - private AbstractEventHandler OnInternalInsertHandler { get; } = new(); - private event Action OnInternalInsert + private AbstractEventHandler> OnInternalInsertHandler { get; } = new(); + private event Action> OnInternalInsert { add => OnInternalInsertHandler.AddListener(value); remove => OnInternalInsertHandler.RemoveListener(value); } - private AbstractEventHandler OnInternalDeleteHandler { get; } = new(); - private event Action OnInternalDelete + private AbstractEventHandler> OnInternalDeleteHandler { get; } = new(); + private event Action> OnInternalDelete { add => OnInternalDeleteHandler.AddListener(value); remove => OnInternalDeleteHandler.RemoveListener(value); @@ -310,6 +329,9 @@ void IRemoteTableHandle.PreApply(IEventContext context, MultiDictionaryDelta scratchInsertBuffer = new(); + List scratchDeleteBuffer = new(); + void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta multiDictionaryDelta) { try @@ -325,36 +347,47 @@ void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta internal struct PreHashedRow {