Skip to content

Commit 759f522

Browse files
[C#] Module bindings for Typed Query Builder (#4159)
# Description of Changes This is the implementation of the Typed Query Builder for C# modules outlined in #3750. This requires the changes from #4146 to be in place before it can properly function. This aligns the C# typed query builder SQL formatter with the Rust implementation: * Supports `And/Or` with same grouping styles as Rust. * All comparison operators (`Eq/Neq/Lt/…`) wrap expressions in parentheses so chained predicates produce byte-for-byte identical SQL. # API and ABI breaking changes Not breaking. Existing modules keep producing the same SQL semantics; only redundant parentheses were added to match the Rust formatter. # Expected complexity level and risk 2 — localized string-format updates plus parity harness tweaks. Low functional risk; largest concern is ensuring every comparison path gained parentheses. # Testing - [X] Tested against a local test harness validating output from these changes match current Rust behavior. --------- Signed-off-by: Ryan <r.ekhoff@clockworklabs.io> Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
1 parent bb8fd49 commit 759f522

19 files changed

Lines changed: 3278 additions & 820 deletions

File tree

crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs

Lines changed: 1216 additions & 0 deletions
Large diffs are not rendered by default.

crates/bindings-csharp/Codegen.Tests/Tests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public static async Task<Fixture> Compile(string name)
2626
return new(projectDir, (CSharpCompilation)compilation!);
2727
}
2828

29+
public Task Verify(string fileName, object target) =>
30+
Verifier.Verify(target).UseDirectory($"{projectDir}/snapshots").UseFileName(fileName);
31+
2932
private static CSharpGeneratorDriver CreateDriver(
3033
IIncrementalGenerator generator,
3134
LanguageVersion languageVersion
@@ -42,9 +45,6 @@ LanguageVersion languageVersion
4245
);
4346
}
4447

45-
public Task Verify(string fileName, object target) =>
46-
Verifier.Verify(target).UseDirectory($"{projectDir}/snapshots").UseFileName(fileName);
47-
4848
private async Task<IEnumerable<SyntaxTree>> RunAndCheckGenerator(
4949
IIncrementalGenerator generator
5050
)

crates/bindings-csharp/Codegen.Tests/fixtures/client/Lib.cs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Runtime.InteropServices;
45
using SpacetimeDB;
56

@@ -39,6 +40,9 @@ public enum CustomEnum
3940
namespace System.Runtime.CompilerServices
4041
{
4142
internal static class IsExternalInit { } // https://stackoverflow.com/a/64749403/1484415
43+
44+
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
45+
internal sealed class ModuleInitializerAttribute : Attribute { }
4246
}
4347

4448
[SpacetimeDB.Type]
@@ -48,6 +52,7 @@ public partial record CustomTaggedEnum
4852
[SpacetimeDB.Type]
4953
public partial struct PublicTable
5054
{
55+
public int Id;
5156
public byte ByteField;
5257
public ushort UshortField;
5358
public uint UintField;
@@ -74,3 +79,98 @@ public partial struct PublicTable
7479
public int? NullableValueField;
7580
public string? NullableReferenceField;
7681
}
82+
83+
internal static class PublicTableViewRegressions
84+
{
85+
[global::System.Runtime.CompilerServices.ModuleInitializer]
86+
internal static void Initialize()
87+
{
88+
ValidatePublicTableQuerySql();
89+
ValidatePublicTableViewSql();
90+
ValidateFindPublicTableByIdentitySql();
91+
}
92+
93+
private sealed class PublicTableCols
94+
{
95+
public Col<PublicTable, int> Id { get; }
96+
97+
public PublicTableCols(string tableName)
98+
{
99+
Id = new Col<PublicTable, int>(tableName, "Id");
100+
}
101+
}
102+
103+
private sealed class PublicTableIxCols
104+
{
105+
public IxCol<PublicTable, int> Id { get; }
106+
107+
public PublicTableIxCols(string tableName)
108+
{
109+
Id = new IxCol<PublicTable, int>(tableName, "Id");
110+
}
111+
}
112+
113+
private static Table<PublicTable, PublicTableCols, PublicTableIxCols> MakeTable()
114+
{
115+
const string tableName = "PublicTable";
116+
return new Table<PublicTable, PublicTableCols, PublicTableIxCols>(
117+
tableName,
118+
new PublicTableCols(tableName),
119+
new PublicTableIxCols(tableName)
120+
);
121+
}
122+
123+
private static string BuildPublicTableQuerySql() =>
124+
MakeTable().Where(cols => cols.Id.Eq(0)).Build().Sql;
125+
126+
private static string BuildPublicTableViewSql()
127+
{
128+
var cols = new PublicTableCols("PublicTable");
129+
return MakeTable().Where(_ => cols.Id.Eq(0)).Build().Sql;
130+
}
131+
132+
private static string BuildFindPublicTableByIdentitySql()
133+
{
134+
var table = MakeTable();
135+
return table.Where(cols => cols.Id.Eq(0)).Build().Sql;
136+
}
137+
138+
/// <summary>
139+
/// Mirrors Module.PublicTableQuery server view to ensure the generated SQL stays in sync.
140+
/// </summary>
141+
[Conditional("DEBUG")]
142+
public static void ValidatePublicTableQuerySql()
143+
{
144+
var sql = BuildPublicTableQuerySql();
145+
Debug.Assert(
146+
sql == "SELECT * FROM \"PublicTable\" WHERE (\"PublicTable\".\"Id\" = 0)",
147+
$"Unexpected SQL produced for public_table_query: {sql}"
148+
);
149+
}
150+
151+
/// <summary>
152+
/// Mirrors Module.PublicTableByIdentity (public_table_view) returning Option<PublicTable>.
153+
/// </summary>
154+
[Conditional("DEBUG")]
155+
public static void ValidatePublicTableViewSql()
156+
{
157+
var sql = BuildPublicTableViewSql();
158+
Debug.Assert(
159+
sql == "SELECT * FROM \"PublicTable\" WHERE (\"PublicTable\".\"Id\" = 0)",
160+
$"Unexpected SQL produced for public_table_view: {sql}"
161+
);
162+
}
163+
164+
/// <summary>
165+
/// Mirrors Module.FindPublicTableByIdentity anonymous view.
166+
/// </summary>
167+
[Conditional("DEBUG")]
168+
public static void ValidateFindPublicTableByIdentitySql()
169+
{
170+
var sql = BuildFindPublicTableByIdentitySql();
171+
Debug.Assert(
172+
sql == "SELECT * FROM \"PublicTable\" WHERE (\"PublicTable\".\"Id\" = 0)",
173+
$"Unexpected SQL produced for find_public_table__by_identity: {sql}"
174+
);
175+
}
176+
}

crates/bindings-csharp/Codegen.Tests/fixtures/client/snapshots/Type#PublicTable.verified.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ partial struct PublicTable : System.IEquatable<PublicTable>, SpacetimeDB.BSATN.I
66
{
77
public void ReadFields(System.IO.BinaryReader reader)
88
{
9+
Id = BSATN.IdRW.Read(reader);
910
ByteField = BSATN.ByteFieldRW.Read(reader);
1011
UshortField = BSATN.UshortFieldRW.Read(reader);
1112
UintField = BSATN.UintFieldRW.Read(reader);
@@ -35,6 +36,7 @@ public void ReadFields(System.IO.BinaryReader reader)
3536

3637
public void WriteFields(System.IO.BinaryWriter writer)
3738
{
39+
BSATN.IdRW.Write(writer, Id);
3840
BSATN.ByteFieldRW.Write(writer, ByteField);
3941
BSATN.UshortFieldRW.Write(writer, UshortField);
4042
BSATN.UintFieldRW.Write(writer, UintField);
@@ -68,10 +70,11 @@ object SpacetimeDB.BSATN.IStructuralReadWrite.GetSerializer()
6870
}
6971

7072
public override string ToString() =>
71-
$"PublicTable {{ ByteField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ByteField)}, UshortField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UshortField)}, UintField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UintField)}, UlongField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UlongField)}, U128Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(U128Field)}, U256Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(U256Field)}, SbyteField = {SpacetimeDB.BSATN.StringUtil.GenericToString(SbyteField)}, ShortField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ShortField)}, IntField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IntField)}, LongField = {SpacetimeDB.BSATN.StringUtil.GenericToString(LongField)}, I128Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(I128Field)}, I256Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(I256Field)}, BoolField = {SpacetimeDB.BSATN.StringUtil.GenericToString(BoolField)}, FloatField = {SpacetimeDB.BSATN.StringUtil.GenericToString(FloatField)}, DoubleField = {SpacetimeDB.BSATN.StringUtil.GenericToString(DoubleField)}, StringField = {SpacetimeDB.BSATN.StringUtil.GenericToString(StringField)}, IdentityField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IdentityField)}, ConnectionIdField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ConnectionIdField)}, CustomStructField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomStructField)}, CustomClassField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomClassField)}, CustomEnumField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomEnumField)}, CustomTaggedEnumField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomTaggedEnumField)}, ListField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ListField)}, NullableValueField = {SpacetimeDB.BSATN.StringUtil.GenericToString(NullableValueField)}, NullableReferenceField = {SpacetimeDB.BSATN.StringUtil.GenericToString(NullableReferenceField)} }}";
73+
$"PublicTable {{ Id = {SpacetimeDB.BSATN.StringUtil.GenericToString(Id)}, ByteField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ByteField)}, UshortField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UshortField)}, UintField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UintField)}, UlongField = {SpacetimeDB.BSATN.StringUtil.GenericToString(UlongField)}, U128Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(U128Field)}, U256Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(U256Field)}, SbyteField = {SpacetimeDB.BSATN.StringUtil.GenericToString(SbyteField)}, ShortField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ShortField)}, IntField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IntField)}, LongField = {SpacetimeDB.BSATN.StringUtil.GenericToString(LongField)}, I128Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(I128Field)}, I256Field = {SpacetimeDB.BSATN.StringUtil.GenericToString(I256Field)}, BoolField = {SpacetimeDB.BSATN.StringUtil.GenericToString(BoolField)}, FloatField = {SpacetimeDB.BSATN.StringUtil.GenericToString(FloatField)}, DoubleField = {SpacetimeDB.BSATN.StringUtil.GenericToString(DoubleField)}, StringField = {SpacetimeDB.BSATN.StringUtil.GenericToString(StringField)}, IdentityField = {SpacetimeDB.BSATN.StringUtil.GenericToString(IdentityField)}, ConnectionIdField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ConnectionIdField)}, CustomStructField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomStructField)}, CustomClassField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomClassField)}, CustomEnumField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomEnumField)}, CustomTaggedEnumField = {SpacetimeDB.BSATN.StringUtil.GenericToString(CustomTaggedEnumField)}, ListField = {SpacetimeDB.BSATN.StringUtil.GenericToString(ListField)}, NullableValueField = {SpacetimeDB.BSATN.StringUtil.GenericToString(NullableValueField)}, NullableReferenceField = {SpacetimeDB.BSATN.StringUtil.GenericToString(NullableReferenceField)} }}";
7274

7375
public readonly partial struct BSATN : SpacetimeDB.BSATN.IReadWrite<PublicTable>
7476
{
77+
internal static readonly SpacetimeDB.BSATN.I32 IdRW = new();
7578
internal static readonly SpacetimeDB.BSATN.U8 ByteFieldRW = new();
7679
internal static readonly SpacetimeDB.BSATN.U16 UshortFieldRW = new();
7780
internal static readonly SpacetimeDB.BSATN.U32 UintFieldRW = new();
@@ -123,6 +126,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar
123126
registrar.RegisterType<PublicTable>(_ => new SpacetimeDB.BSATN.AlgebraicType.Product(
124127
new SpacetimeDB.BSATN.AggregateElement[]
125128
{
129+
new("Id", IdRW.GetAlgebraicType(registrar)),
126130
new("ByteField", ByteFieldRW.GetAlgebraicType(registrar)),
127131
new("UshortField", UshortFieldRW.GetAlgebraicType(registrar)),
128132
new("UintField", UintFieldRW.GetAlgebraicType(registrar)),
@@ -164,6 +168,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar
164168

165169
public override int GetHashCode()
166170
{
171+
var ___hashId = Id.GetHashCode();
167172
var ___hashByteField = ByteField.GetHashCode();
168173
var ___hashUshortField = UshortField.GetHashCode();
169174
var ___hashUintField = UintField.GetHashCode();
@@ -202,7 +207,8 @@ public override int GetHashCode()
202207
var ___hashNullableValueField = NullableValueField.GetHashCode();
203208
var ___hashNullableReferenceField =
204209
NullableReferenceField == null ? 0 : NullableReferenceField.GetHashCode();
205-
return ___hashByteField
210+
return ___hashId
211+
^ ___hashByteField
206212
^ ___hashUshortField
207213
^ ___hashUintField
208214
^ ___hashUlongField
@@ -232,6 +238,7 @@ public override int GetHashCode()
232238
#nullable enable
233239
public bool Equals(PublicTable that)
234240
{
241+
var ___eqId = this.Id.Equals(that.Id);
235242
var ___eqByteField = this.ByteField.Equals(that.ByteField);
236243
var ___eqUshortField = this.UshortField.Equals(that.UshortField);
237244
var ___eqUintField = this.UintField.Equals(that.UintField);
@@ -291,7 +298,8 @@ public bool Equals(PublicTable that)
291298
this.NullableReferenceField == null
292299
? that.NullableReferenceField == null
293300
: this.NullableReferenceField.Equals(that.NullableReferenceField);
294-
return ___eqByteField
301+
return ___eqId
302+
&& ___eqByteField
295303
&& ___eqUshortField
296304
&& ___eqUintField
297305
&& ___eqUlongField

0 commit comments

Comments
 (0)