From 301b109e8eaf0b8e32282bc26b0fdabeeea4d1e1 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Wed, 28 Jan 2026 17:37:59 -0800 Subject: [PATCH] Initial add of C# module Typed Query Builder --- .../ExtraCompilationErrors.verified.txt | 207 +++ .../diag/snapshots/Module#FFI.verified.cs | 626 ++++++++- .../Codegen.Tests/fixtures/server/Lib.cs | 8 +- .../server/snapshots/Module#FFI.verified.cs | 531 ++++++- crates/bindings-csharp/Codegen/Module.cs | 201 ++- .../bindings-csharp/Runtime/QueryBuilder.cs | 1218 +++++++++++++++++ 6 files changed, 2773 insertions(+), 18 deletions(-) create mode 100644 crates/bindings-csharp/Runtime/QueryBuilder.cs diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt index df6c2725686..eab68c570e4 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt @@ -298,6 +298,144 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI ] } }, + {/* + public global::SpacetimeDB.Table TestDuplicateTableName() => + new("TestDuplicateTableName", new TestDuplicateTableNameCols("TestDuplicateTableName"), new TestDuplicateTableNameIxCols("TestDuplicateTableName")); + ^^^^^^^^^^^^^^^^^^^^^^^^^^ +} +*/ + Message: The call is ambiguous between the following methods or properties: 'TestDuplicateTableNameCols.TestDuplicateTableNameCols(string)' and 'TestDuplicateTableNameCols.TestDuplicateTableNameCols(string)', + Severity: Error, + Descriptor: { + Id: CS0121, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0121), + MessageFormat: The call is ambiguous between the following methods or properties: '{0}' and '{1}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + public global::SpacetimeDB.Table TestDuplicateTableName() => + new("TestDuplicateTableName", new TestDuplicateTableNameCols("TestDuplicateTableName"), new TestDuplicateTableNameIxCols("TestDuplicateTableName")); + ^^^^^^^^^^^^^^^^^^^^^^^^^^ +} +*/ + Message: The call is ambiguous between the following methods or properties: 'TestDuplicateTableNameCols.TestDuplicateTableNameCols(string)' and 'TestDuplicateTableNameCols.TestDuplicateTableNameCols(string)', + Severity: Error, + Descriptor: { + Id: CS0121, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0121), + MessageFormat: The call is ambiguous between the following methods or properties: '{0}' and '{1}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + public global::SpacetimeDB.Table TestDuplicateTableName() => + new("TestDuplicateTableName", new TestDuplicateTableNameCols("TestDuplicateTableName"), new TestDuplicateTableNameIxCols("TestDuplicateTableName")); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} +*/ + Message: The call is ambiguous between the following methods or properties: 'TestDuplicateTableNameIxCols.TestDuplicateTableNameIxCols(string)' and 'TestDuplicateTableNameIxCols.TestDuplicateTableNameIxCols(string)', + Severity: Error, + Descriptor: { + Id: CS0121, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0121), + MessageFormat: The call is ambiguous between the following methods or properties: '{0}' and '{1}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + public global::SpacetimeDB.Table TestDuplicateTableName() => + new("TestDuplicateTableName", new TestDuplicateTableNameCols("TestDuplicateTableName"), new TestDuplicateTableNameIxCols("TestDuplicateTableName")); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} +*/ + Message: The call is ambiguous between the following methods or properties: 'TestDuplicateTableNameIxCols.TestDuplicateTableNameIxCols(string)' and 'TestDuplicateTableNameIxCols.TestDuplicateTableNameIxCols(string)', + Severity: Error, + Descriptor: { + Id: CS0121, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0121), + MessageFormat: The call is ambiguous between the following methods or properties: '{0}' and '{1}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* +} +public readonly struct TestDuplicateTableNameCols + ^^^^^^^^^^^^^^^^^^^^^^^^^^ +{ +*/ + Message: The namespace 'SpacetimeDB' already contains a definition for 'TestDuplicateTableNameCols', + Severity: Error, + Descriptor: { + Id: CS0101, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0101), + MessageFormat: The namespace '{1}' already contains a definition for '{0}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + +public readonly struct TestDuplicateTableNameIxCols + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +{ +*/ + Message: The namespace 'SpacetimeDB' already contains a definition for 'TestDuplicateTableNameIxCols', + Severity: Error, + Descriptor: { + Id: CS0101, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0101), + MessageFormat: The namespace '{1}' already contains a definition for '{0}', + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, {/* Params: [], ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType(registrar) @@ -391,6 +529,75 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI } }, {/* +{ + public global::SpacetimeDB.Table TestDuplicateTableName() => + ^^^^^^^^^^^^^^^^^^^^^^ + new("TestDuplicateTableName", new TestDuplicateTableNameCols("TestDuplicateTableName"), new TestDuplicateTableNameIxCols("TestDuplicateTableName")); +*/ + Message: Type 'QueryBuilder' already defines a member called 'TestDuplicateTableName' with the same parameter types, + Severity: Error, + Descriptor: { + Id: CS0111, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0111), + MessageFormat: Type '{1}' already defines a member called '{0}' with the same parameter types, + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + + internal TestDuplicateTableNameCols(string tableName) + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + { +*/ + Message: Type 'TestDuplicateTableNameCols' already defines a member called 'TestDuplicateTableNameCols' with the same parameter types, + Severity: Error, + Descriptor: { + Id: CS0111, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0111), + MessageFormat: Type '{1}' already defines a member called '{0}' with the same parameter types, + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + + internal TestDuplicateTableNameIxCols(string tableName) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + { +*/ + Message: Type 'TestDuplicateTableNameIxCols' already defines a member called 'TestDuplicateTableNameIxCols' with the same parameter types, + Severity: Error, + Descriptor: { + Id: CS0111, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0111), + MessageFormat: Type '{1}' already defines a member called '{0}' with the same parameter types, + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* partial struct TestTypeParams : System.IEquatable, SpacetimeDB.BSATN.IStructuralReadWrite { ^^^^^^^^^^^^^^ diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index 9064e3f7b3c..0fcaa83e79d 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -1,4 +1,4 @@ -//HintName: FFI.cs +//HintName: FFI.cs // #nullable enable // The runtime already defines SpacetimeDB.Internal.LocalReadOnly in Runtime\Internal\Module.cs as an empty partial type. @@ -15,6 +15,626 @@ namespace SpacetimeDB { + public readonly struct TestDuplicateTableNameCols + { + internal TestDuplicateTableNameCols(string tableName) { } + } + + public readonly struct TestDuplicateTableNameIxCols + { + internal TestDuplicateTableNameIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::InAnotherNamespace.TestDuplicateTableName, + TestDuplicateTableNameCols, + TestDuplicateTableNameIxCols + > TestDuplicateTableName() => + new( + "TestDuplicateTableName", + new TestDuplicateTableNameCols("TestDuplicateTableName"), + new TestDuplicateTableNameIxCols("TestDuplicateTableName") + ); + } + + public readonly struct PlayerCols + { + public readonly global::SpacetimeDB.Col Identity; + + internal PlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col( + tableName, + "Identity" + ); + } + } + + public readonly struct PlayerIxCols + { + internal PlayerIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table Player() => + new("Player", new PlayerCols("Player"), new PlayerIxCols("Player")); + } + + public readonly struct TestAutoIncNotIntegerCols + { + public readonly global::SpacetimeDB.Col AutoIncField; + public readonly global::SpacetimeDB.Col< + global::TestAutoIncNotInteger, + string + > IdentityField; + + internal TestAutoIncNotIntegerCols(string tableName) + { + AutoIncField = new global::SpacetimeDB.Col( + tableName, + "AutoIncField" + ); + IdentityField = new global::SpacetimeDB.Col( + tableName, + "IdentityField" + ); + } + } + + public readonly struct TestAutoIncNotIntegerIxCols + { + internal TestAutoIncNotIntegerIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestAutoIncNotInteger, + TestAutoIncNotIntegerCols, + TestAutoIncNotIntegerIxCols + > TestAutoIncNotInteger() => + new( + "TestAutoIncNotInteger", + new TestAutoIncNotIntegerCols("TestAutoIncNotInteger"), + new TestAutoIncNotIntegerIxCols("TestAutoIncNotInteger") + ); + } + + public readonly struct TestDefaultFieldValuesCols + { + public readonly global::SpacetimeDB.NullableCol< + global::TestDefaultFieldValues, + int + > UniqueField; + public readonly global::SpacetimeDB.Col< + global::TestDefaultFieldValues, + string + > DefaultString; + public readonly global::SpacetimeDB.Col DefaultBool; + public readonly global::SpacetimeDB.Col DefaultI8; + public readonly global::SpacetimeDB.Col DefaultU8; + public readonly global::SpacetimeDB.Col DefaultI16; + public readonly global::SpacetimeDB.Col DefaultU16; + public readonly global::SpacetimeDB.Col DefaultI32; + public readonly global::SpacetimeDB.Col DefaultU32; + public readonly global::SpacetimeDB.Col DefaultI64; + public readonly global::SpacetimeDB.Col DefaultU64; + public readonly global::SpacetimeDB.Col DefaultHex; + public readonly global::SpacetimeDB.Col DefaultBin; + public readonly global::SpacetimeDB.Col DefaultF32; + public readonly global::SpacetimeDB.Col DefaultF64; + public readonly global::SpacetimeDB.Col DefaultEnum; + public readonly global::SpacetimeDB.NullableCol< + global::TestDefaultFieldValues, + MyStruct + > DefaultNull; + + internal TestDefaultFieldValuesCols(string tableName) + { + UniqueField = new global::SpacetimeDB.NullableCol( + tableName, + "UniqueField" + ); + DefaultString = new global::SpacetimeDB.Col( + tableName, + "DefaultString" + ); + DefaultBool = new global::SpacetimeDB.Col( + tableName, + "DefaultBool" + ); + DefaultI8 = new global::SpacetimeDB.Col( + tableName, + "DefaultI8" + ); + DefaultU8 = new global::SpacetimeDB.Col( + tableName, + "DefaultU8" + ); + DefaultI16 = new global::SpacetimeDB.Col( + tableName, + "DefaultI16" + ); + DefaultU16 = new global::SpacetimeDB.Col( + tableName, + "DefaultU16" + ); + DefaultI32 = new global::SpacetimeDB.Col( + tableName, + "DefaultI32" + ); + DefaultU32 = new global::SpacetimeDB.Col( + tableName, + "DefaultU32" + ); + DefaultI64 = new global::SpacetimeDB.Col( + tableName, + "DefaultI64" + ); + DefaultU64 = new global::SpacetimeDB.Col( + tableName, + "DefaultU64" + ); + DefaultHex = new global::SpacetimeDB.Col( + tableName, + "DefaultHex" + ); + DefaultBin = new global::SpacetimeDB.Col( + tableName, + "DefaultBin" + ); + DefaultF32 = new global::SpacetimeDB.Col( + tableName, + "DefaultF32" + ); + DefaultF64 = new global::SpacetimeDB.Col( + tableName, + "DefaultF64" + ); + DefaultEnum = new global::SpacetimeDB.Col( + tableName, + "DefaultEnum" + ); + DefaultNull = new global::SpacetimeDB.NullableCol< + global::TestDefaultFieldValues, + MyStruct + >(tableName, "DefaultNull"); + } + } + + public readonly struct TestDefaultFieldValuesIxCols + { + internal TestDefaultFieldValuesIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestDefaultFieldValues, + TestDefaultFieldValuesCols, + TestDefaultFieldValuesIxCols + > TestDefaultFieldValues() => + new( + "TestDefaultFieldValues", + new TestDefaultFieldValuesCols("TestDefaultFieldValues"), + new TestDefaultFieldValuesIxCols("TestDefaultFieldValues") + ); + } + + public readonly struct TestDuplicateTableNameCols + { + internal TestDuplicateTableNameCols(string tableName) { } + } + + public readonly struct TestDuplicateTableNameIxCols + { + internal TestDuplicateTableNameIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestDuplicateTableName, + TestDuplicateTableNameCols, + TestDuplicateTableNameIxCols + > TestDuplicateTableName() => + new( + "TestDuplicateTableName", + new TestDuplicateTableNameCols("TestDuplicateTableName"), + new TestDuplicateTableNameIxCols("TestDuplicateTableName") + ); + } + + public readonly struct TestIndexIssuesCols + { + public readonly global::SpacetimeDB.Col SelfIndexingColumn; + + internal TestIndexIssuesCols(string tableName) + { + SelfIndexingColumn = new global::SpacetimeDB.Col( + tableName, + "SelfIndexingColumn" + ); + } + } + + public readonly struct TestIndexIssuesIxCols + { + public readonly global::SpacetimeDB.IxCol SelfIndexingColumn; + + internal TestIndexIssuesIxCols(string tableName) + { + SelfIndexingColumn = new global::SpacetimeDB.IxCol( + tableName, + "SelfIndexingColumn" + ); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestIndexIssues, + TestIndexIssuesCols, + TestIndexIssuesIxCols + > TestIndexIssues() => + new( + "TestIndexIssues", + new TestIndexIssuesCols("TestIndexIssues"), + new TestIndexIssuesIxCols("TestIndexIssues") + ); + } + + public readonly struct TestScheduleWithoutPrimaryKeyCols + { + public readonly global::SpacetimeDB.Col IdWrongType; + public readonly global::SpacetimeDB.Col IdCorrectType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + int + > ScheduleAtWrongType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + > ScheduleAtCorrectType; + + internal TestScheduleWithoutPrimaryKeyCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.Col( + tableName, + "IdWrongType" + ); + IdCorrectType = new global::SpacetimeDB.Col( + tableName, + "IdCorrectType" + ); + ScheduleAtWrongType = new global::SpacetimeDB.Col( + tableName, + "ScheduleAtWrongType" + ); + ScheduleAtCorrectType = new global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduleAtCorrectType"); + } + } + + public readonly struct TestScheduleWithoutPrimaryKeyIxCols + { + internal TestScheduleWithoutPrimaryKeyIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestScheduleIssues, + TestScheduleWithoutPrimaryKeyCols, + TestScheduleWithoutPrimaryKeyIxCols + > TestScheduleWithoutPrimaryKey() => + new( + "TestScheduleWithoutPrimaryKey", + new TestScheduleWithoutPrimaryKeyCols("TestScheduleWithoutPrimaryKey"), + new TestScheduleWithoutPrimaryKeyIxCols("TestScheduleWithoutPrimaryKey") + ); + } + + public readonly struct TestScheduleWithWrongPrimaryKeyTypeCols + { + public readonly global::SpacetimeDB.Col IdWrongType; + public readonly global::SpacetimeDB.Col IdCorrectType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + int + > ScheduleAtWrongType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + > ScheduleAtCorrectType; + + internal TestScheduleWithWrongPrimaryKeyTypeCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.Col( + tableName, + "IdWrongType" + ); + IdCorrectType = new global::SpacetimeDB.Col( + tableName, + "IdCorrectType" + ); + ScheduleAtWrongType = new global::SpacetimeDB.Col( + tableName, + "ScheduleAtWrongType" + ); + ScheduleAtCorrectType = new global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduleAtCorrectType"); + } + } + + public readonly struct TestScheduleWithWrongPrimaryKeyTypeIxCols + { + public readonly global::SpacetimeDB.IxCol IdWrongType; + + internal TestScheduleWithWrongPrimaryKeyTypeIxCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.IxCol( + tableName, + "IdWrongType" + ); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestScheduleIssues, + TestScheduleWithWrongPrimaryKeyTypeCols, + TestScheduleWithWrongPrimaryKeyTypeIxCols + > TestScheduleWithWrongPrimaryKeyType() => + new( + "TestScheduleWithWrongPrimaryKeyType", + new TestScheduleWithWrongPrimaryKeyTypeCols("TestScheduleWithWrongPrimaryKeyType"), + new TestScheduleWithWrongPrimaryKeyTypeIxCols("TestScheduleWithWrongPrimaryKeyType") + ); + } + + public readonly struct TestScheduleWithoutScheduleAtCols + { + public readonly global::SpacetimeDB.Col IdWrongType; + public readonly global::SpacetimeDB.Col IdCorrectType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + int + > ScheduleAtWrongType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + > ScheduleAtCorrectType; + + internal TestScheduleWithoutScheduleAtCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.Col( + tableName, + "IdWrongType" + ); + IdCorrectType = new global::SpacetimeDB.Col( + tableName, + "IdCorrectType" + ); + ScheduleAtWrongType = new global::SpacetimeDB.Col( + tableName, + "ScheduleAtWrongType" + ); + ScheduleAtCorrectType = new global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduleAtCorrectType"); + } + } + + public readonly struct TestScheduleWithoutScheduleAtIxCols + { + public readonly global::SpacetimeDB.IxCol IdCorrectType; + + internal TestScheduleWithoutScheduleAtIxCols(string tableName) + { + IdCorrectType = new global::SpacetimeDB.IxCol( + tableName, + "IdCorrectType" + ); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestScheduleIssues, + TestScheduleWithoutScheduleAtCols, + TestScheduleWithoutScheduleAtIxCols + > TestScheduleWithoutScheduleAt() => + new( + "TestScheduleWithoutScheduleAt", + new TestScheduleWithoutScheduleAtCols("TestScheduleWithoutScheduleAt"), + new TestScheduleWithoutScheduleAtIxCols("TestScheduleWithoutScheduleAt") + ); + } + + public readonly struct TestScheduleWithWrongScheduleAtTypeCols + { + public readonly global::SpacetimeDB.Col IdWrongType; + public readonly global::SpacetimeDB.Col IdCorrectType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + int + > ScheduleAtWrongType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + > ScheduleAtCorrectType; + + internal TestScheduleWithWrongScheduleAtTypeCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.Col( + tableName, + "IdWrongType" + ); + IdCorrectType = new global::SpacetimeDB.Col( + tableName, + "IdCorrectType" + ); + ScheduleAtWrongType = new global::SpacetimeDB.Col( + tableName, + "ScheduleAtWrongType" + ); + ScheduleAtCorrectType = new global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduleAtCorrectType"); + } + } + + public readonly struct TestScheduleWithWrongScheduleAtTypeIxCols + { + public readonly global::SpacetimeDB.IxCol IdCorrectType; + + internal TestScheduleWithWrongScheduleAtTypeIxCols(string tableName) + { + IdCorrectType = new global::SpacetimeDB.IxCol( + tableName, + "IdCorrectType" + ); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestScheduleIssues, + TestScheduleWithWrongScheduleAtTypeCols, + TestScheduleWithWrongScheduleAtTypeIxCols + > TestScheduleWithWrongScheduleAtType() => + new( + "TestScheduleWithWrongScheduleAtType", + new TestScheduleWithWrongScheduleAtTypeCols("TestScheduleWithWrongScheduleAtType"), + new TestScheduleWithWrongScheduleAtTypeIxCols("TestScheduleWithWrongScheduleAtType") + ); + } + + public readonly struct TestScheduleWithMissingScheduleAtFieldCols + { + public readonly global::SpacetimeDB.Col IdWrongType; + public readonly global::SpacetimeDB.Col IdCorrectType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + int + > ScheduleAtWrongType; + public readonly global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + > ScheduleAtCorrectType; + + internal TestScheduleWithMissingScheduleAtFieldCols(string tableName) + { + IdWrongType = new global::SpacetimeDB.Col( + tableName, + "IdWrongType" + ); + IdCorrectType = new global::SpacetimeDB.Col( + tableName, + "IdCorrectType" + ); + ScheduleAtWrongType = new global::SpacetimeDB.Col( + tableName, + "ScheduleAtWrongType" + ); + ScheduleAtCorrectType = new global::SpacetimeDB.Col< + global::TestScheduleIssues, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduleAtCorrectType"); + } + } + + public readonly struct TestScheduleWithMissingScheduleAtFieldIxCols + { + internal TestScheduleWithMissingScheduleAtFieldIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestScheduleIssues, + TestScheduleWithMissingScheduleAtFieldCols, + TestScheduleWithMissingScheduleAtFieldIxCols + > TestScheduleWithMissingScheduleAtField() => + new( + "TestScheduleWithMissingScheduleAtField", + new TestScheduleWithMissingScheduleAtFieldCols( + "TestScheduleWithMissingScheduleAtField" + ), + new TestScheduleWithMissingScheduleAtFieldIxCols( + "TestScheduleWithMissingScheduleAtField" + ) + ); + } + + public readonly struct TestUniqueNotEquatableCols + { + public readonly global::SpacetimeDB.NullableCol< + global::TestUniqueNotEquatable, + int + > UniqueField; + public readonly global::SpacetimeDB.Col< + global::TestUniqueNotEquatable, + TestEnumWithExplicitValues + > PrimaryKeyField; + + internal TestUniqueNotEquatableCols(string tableName) + { + UniqueField = new global::SpacetimeDB.NullableCol( + tableName, + "UniqueField" + ); + PrimaryKeyField = new global::SpacetimeDB.Col< + global::TestUniqueNotEquatable, + TestEnumWithExplicitValues + >(tableName, "PrimaryKeyField"); + } + } + + public readonly struct TestUniqueNotEquatableIxCols + { + public readonly global::SpacetimeDB.IxCol< + global::TestUniqueNotEquatable, + TestEnumWithExplicitValues + > PrimaryKeyField; + + internal TestUniqueNotEquatableIxCols(string tableName) + { + PrimaryKeyField = new global::SpacetimeDB.IxCol< + global::TestUniqueNotEquatable, + TestEnumWithExplicitValues + >(tableName, "PrimaryKeyField"); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::TestUniqueNotEquatable, + TestUniqueNotEquatableCols, + TestUniqueNotEquatableIxCols + > TestUniqueNotEquatable() => + new( + "TestUniqueNotEquatable", + new TestUniqueNotEquatableCols("TestUniqueNotEquatable"), + new TestUniqueNotEquatableIxCols("TestUniqueNotEquatable") + ); + } + public sealed record ReducerContext : DbContext, Internal.IReducerContext { public readonly Identity Sender; @@ -211,6 +831,8 @@ public sealed record ViewContext : DbContext, Internal.I { public Identity Sender { get; } + public QueryBuilder From => default; + internal ViewContext(Identity sender, Internal.LocalReadOnly db) : base(db) { @@ -222,6 +844,8 @@ public sealed record AnonymousViewContext : DbContext, Internal.IAnonymousViewContext { + public QueryBuilder From => default; + internal AnonymousViewContext(Internal.LocalReadOnly db) : base(db) { } } diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/server/Lib.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/server/Lib.cs index 11884f16af3..0367d4b4b85 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/server/Lib.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/server/Lib.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using SpacetimeDB; #pragma warning disable CA1050 // Declare types in namespaces - this is a test fixture, no need for a namespace. @@ -280,6 +280,12 @@ public class Module return (PublicTable?)ctx.Db.PublicTable.Id.Find(0); } + [SpacetimeDB.View(Name = "public_table_query", Public = true)] + public static Query PublicTableQuery(ViewContext ctx) + { + return ctx.From.PublicTable().Where(cols => cols.Id.Eq(SpacetimeDB.SqlLit.Int(0))).Build(); + } + [SpacetimeDB.View(Name = "find_public_table__by_identity", Public = true)] public static PublicTable? FindPublicTableByIdentity(AnonymousViewContext ctx) { diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs index 11c5b86ea18..1cecd68c34f 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs @@ -1,4 +1,4 @@ -//HintName: FFI.cs +//HintName: FFI.cs // #nullable enable // The runtime already defines SpacetimeDB.Internal.LocalReadOnly in Runtime\Internal\Module.cs as an empty partial type. @@ -15,6 +15,486 @@ namespace SpacetimeDB { + internal readonly struct BTreeMultiColumnCols + { + public readonly global::SpacetimeDB.Col X; + public readonly global::SpacetimeDB.Col Y; + public readonly global::SpacetimeDB.Col Z; + + internal BTreeMultiColumnCols(string tableName) + { + X = new global::SpacetimeDB.Col(tableName, "X"); + Y = new global::SpacetimeDB.Col(tableName, "Y"); + Z = new global::SpacetimeDB.Col(tableName, "Z"); + } + } + + internal readonly struct BTreeMultiColumnIxCols + { + public readonly global::SpacetimeDB.IxCol X; + public readonly global::SpacetimeDB.IxCol Y; + public readonly global::SpacetimeDB.IxCol Z; + + internal BTreeMultiColumnIxCols(string tableName) + { + X = new global::SpacetimeDB.IxCol(tableName, "X"); + Y = new global::SpacetimeDB.IxCol(tableName, "Y"); + Z = new global::SpacetimeDB.IxCol(tableName, "Z"); + } + } + + public readonly partial struct QueryBuilder + { + internal global::SpacetimeDB.Table< + global::BTreeMultiColumn, + BTreeMultiColumnCols, + BTreeMultiColumnIxCols + > BTreeMultiColumn() => + new( + "BTreeMultiColumn", + new BTreeMultiColumnCols("BTreeMultiColumn"), + new BTreeMultiColumnIxCols("BTreeMultiColumn") + ); + } + + internal readonly struct BTreeViewsCols + { + public readonly global::SpacetimeDB.Col Id; + public readonly global::SpacetimeDB.Col X; + public readonly global::SpacetimeDB.Col Y; + public readonly global::SpacetimeDB.Col Faction; + + internal BTreeViewsCols(string tableName) + { + Id = new global::SpacetimeDB.Col( + tableName, + "Id" + ); + X = new global::SpacetimeDB.Col(tableName, "X"); + Y = new global::SpacetimeDB.Col(tableName, "Y"); + Faction = new global::SpacetimeDB.Col(tableName, "Faction"); + } + } + + internal readonly struct BTreeViewsIxCols + { + public readonly global::SpacetimeDB.IxCol Id; + public readonly global::SpacetimeDB.IxCol X; + public readonly global::SpacetimeDB.IxCol Y; + public readonly global::SpacetimeDB.IxCol Faction; + + internal BTreeViewsIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol( + tableName, + "Id" + ); + X = new global::SpacetimeDB.IxCol(tableName, "X"); + Y = new global::SpacetimeDB.IxCol(tableName, "Y"); + Faction = new global::SpacetimeDB.IxCol( + tableName, + "Faction" + ); + } + } + + public readonly partial struct QueryBuilder + { + internal global::SpacetimeDB.Table< + global::BTreeViews, + BTreeViewsCols, + BTreeViewsIxCols + > BTreeViews() => + new("BTreeViews", new BTreeViewsCols("BTreeViews"), new BTreeViewsIxCols("BTreeViews")); + } + + public readonly struct MultiTable1Cols + { + public readonly global::SpacetimeDB.Col Name; + public readonly global::SpacetimeDB.Col Foo; + public readonly global::SpacetimeDB.Col Bar; + + internal MultiTable1Cols(string tableName) + { + Name = new global::SpacetimeDB.Col(tableName, "Name"); + Foo = new global::SpacetimeDB.Col(tableName, "Foo"); + Bar = new global::SpacetimeDB.Col(tableName, "Bar"); + } + } + + public readonly struct MultiTable1IxCols + { + public readonly global::SpacetimeDB.IxCol Name; + public readonly global::SpacetimeDB.IxCol Foo; + + internal MultiTable1IxCols(string tableName) + { + Name = new global::SpacetimeDB.IxCol(tableName, "Name"); + Foo = new global::SpacetimeDB.IxCol(tableName, "Foo"); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::MultiTableRow, + MultiTable1Cols, + MultiTable1IxCols + > MultiTable1() => + new( + "MultiTable1", + new MultiTable1Cols("MultiTable1"), + new MultiTable1IxCols("MultiTable1") + ); + } + + public readonly struct MultiTable2Cols + { + public readonly global::SpacetimeDB.Col Name; + public readonly global::SpacetimeDB.Col Foo; + public readonly global::SpacetimeDB.Col Bar; + + internal MultiTable2Cols(string tableName) + { + Name = new global::SpacetimeDB.Col(tableName, "Name"); + Foo = new global::SpacetimeDB.Col(tableName, "Foo"); + Bar = new global::SpacetimeDB.Col(tableName, "Bar"); + } + } + + public readonly struct MultiTable2IxCols + { + internal MultiTable2IxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::MultiTableRow, + MultiTable2Cols, + MultiTable2IxCols + > MultiTable2() => + new( + "MultiTable2", + new MultiTable2Cols("MultiTable2"), + new MultiTable2IxCols("MultiTable2") + ); + } + + public readonly struct PrivateTableCols + { + internal PrivateTableCols(string tableName) { } + } + + public readonly struct PrivateTableIxCols + { + internal PrivateTableIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::PrivateTable, + PrivateTableCols, + PrivateTableIxCols + > PrivateTable() => + new( + "PrivateTable", + new PrivateTableCols("PrivateTable"), + new PrivateTableIxCols("PrivateTable") + ); + } + + public readonly struct PublicTableCols + { + public readonly global::SpacetimeDB.Col Id; + public readonly global::SpacetimeDB.Col ByteField; + public readonly global::SpacetimeDB.Col UshortField; + public readonly global::SpacetimeDB.Col UintField; + public readonly global::SpacetimeDB.Col UlongField; + public readonly global::SpacetimeDB.Col UInt128Field; + public readonly global::SpacetimeDB.Col U128Field; + public readonly global::SpacetimeDB.Col U256Field; + public readonly global::SpacetimeDB.Col SbyteField; + public readonly global::SpacetimeDB.Col ShortField; + public readonly global::SpacetimeDB.Col IntField; + public readonly global::SpacetimeDB.Col LongField; + public readonly global::SpacetimeDB.Col Int128Field; + public readonly global::SpacetimeDB.Col I128Field; + public readonly global::SpacetimeDB.Col I256Field; + public readonly global::SpacetimeDB.Col BoolField; + public readonly global::SpacetimeDB.Col FloatField; + public readonly global::SpacetimeDB.Col DoubleField; + public readonly global::SpacetimeDB.Col StringField; + public readonly global::SpacetimeDB.Col< + global::PublicTable, + SpacetimeDB.Identity + > IdentityField; + public readonly global::SpacetimeDB.Col< + global::PublicTable, + SpacetimeDB.ConnectionId + > ConnectionIdField; + public readonly global::SpacetimeDB.Col< + global::PublicTable, + CustomStruct + > CustomStructField; + public readonly global::SpacetimeDB.Col CustomClassField; + public readonly global::SpacetimeDB.Col CustomEnumField; + public readonly global::SpacetimeDB.Col< + global::PublicTable, + CustomTaggedEnum + > CustomTaggedEnumField; + public readonly global::SpacetimeDB.Col< + global::PublicTable, + System.Collections.Generic.List + > ListField; + public readonly global::SpacetimeDB.NullableCol< + global::PublicTable, + int + > NullableValueField; + public readonly global::SpacetimeDB.NullableCol< + global::PublicTable, + string + > NullableReferenceField; + + internal PublicTableCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + ByteField = new global::SpacetimeDB.Col( + tableName, + "ByteField" + ); + UshortField = new global::SpacetimeDB.Col( + tableName, + "UshortField" + ); + UintField = new global::SpacetimeDB.Col( + tableName, + "UintField" + ); + UlongField = new global::SpacetimeDB.Col( + tableName, + "UlongField" + ); + UInt128Field = new global::SpacetimeDB.Col( + tableName, + "UInt128Field" + ); + U128Field = new global::SpacetimeDB.Col( + tableName, + "U128Field" + ); + U256Field = new global::SpacetimeDB.Col( + tableName, + "U256Field" + ); + SbyteField = new global::SpacetimeDB.Col( + tableName, + "SbyteField" + ); + ShortField = new global::SpacetimeDB.Col( + tableName, + "ShortField" + ); + IntField = new global::SpacetimeDB.Col(tableName, "IntField"); + LongField = new global::SpacetimeDB.Col( + tableName, + "LongField" + ); + Int128Field = new global::SpacetimeDB.Col( + tableName, + "Int128Field" + ); + I128Field = new global::SpacetimeDB.Col( + tableName, + "I128Field" + ); + I256Field = new global::SpacetimeDB.Col( + tableName, + "I256Field" + ); + BoolField = new global::SpacetimeDB.Col( + tableName, + "BoolField" + ); + FloatField = new global::SpacetimeDB.Col( + tableName, + "FloatField" + ); + DoubleField = new global::SpacetimeDB.Col( + tableName, + "DoubleField" + ); + StringField = new global::SpacetimeDB.Col( + tableName, + "StringField" + ); + IdentityField = new global::SpacetimeDB.Col( + tableName, + "IdentityField" + ); + ConnectionIdField = new global::SpacetimeDB.Col< + global::PublicTable, + SpacetimeDB.ConnectionId + >(tableName, "ConnectionIdField"); + CustomStructField = new global::SpacetimeDB.Col( + tableName, + "CustomStructField" + ); + CustomClassField = new global::SpacetimeDB.Col( + tableName, + "CustomClassField" + ); + CustomEnumField = new global::SpacetimeDB.Col( + tableName, + "CustomEnumField" + ); + CustomTaggedEnumField = new global::SpacetimeDB.Col< + global::PublicTable, + CustomTaggedEnum + >(tableName, "CustomTaggedEnumField"); + ListField = new global::SpacetimeDB.Col< + global::PublicTable, + System.Collections.Generic.List + >(tableName, "ListField"); + NullableValueField = new global::SpacetimeDB.NullableCol( + tableName, + "NullableValueField" + ); + NullableReferenceField = new global::SpacetimeDB.NullableCol< + global::PublicTable, + string + >(tableName, "NullableReferenceField"); + } + } + + public readonly struct PublicTableIxCols + { + public readonly global::SpacetimeDB.IxCol Id; + + internal PublicTableIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::PublicTable, + PublicTableCols, + PublicTableIxCols + > PublicTable() => + new( + "PublicTable", + new PublicTableCols("PublicTable"), + new PublicTableIxCols("PublicTable") + ); + } + + internal readonly struct RegressionMultipleUniqueIndexesHadSameNameCols + { + public readonly global::SpacetimeDB.Col< + global::RegressionMultipleUniqueIndexesHadSameName, + uint + > Unique1; + public readonly global::SpacetimeDB.Col< + global::RegressionMultipleUniqueIndexesHadSameName, + uint + > Unique2; + + internal RegressionMultipleUniqueIndexesHadSameNameCols(string tableName) + { + Unique1 = new global::SpacetimeDB.Col< + global::RegressionMultipleUniqueIndexesHadSameName, + uint + >(tableName, "Unique1"); + Unique2 = new global::SpacetimeDB.Col< + global::RegressionMultipleUniqueIndexesHadSameName, + uint + >(tableName, "Unique2"); + } + } + + internal readonly struct RegressionMultipleUniqueIndexesHadSameNameIxCols + { + internal RegressionMultipleUniqueIndexesHadSameNameIxCols(string tableName) { } + } + + public readonly partial struct QueryBuilder + { + internal global::SpacetimeDB.Table< + global::RegressionMultipleUniqueIndexesHadSameName, + RegressionMultipleUniqueIndexesHadSameNameCols, + RegressionMultipleUniqueIndexesHadSameNameIxCols + > RegressionMultipleUniqueIndexesHadSameName() => + new( + "RegressionMultipleUniqueIndexesHadSameName", + new RegressionMultipleUniqueIndexesHadSameNameCols( + "RegressionMultipleUniqueIndexesHadSameName" + ), + new RegressionMultipleUniqueIndexesHadSameNameIxCols( + "RegressionMultipleUniqueIndexesHadSameName" + ) + ); + } + + public readonly struct SendMessageTimerCols + { + public readonly global::SpacetimeDB.Col ScheduledId; + public readonly global::SpacetimeDB.Col< + global::Timers.SendMessageTimer, + SpacetimeDB.ScheduleAt + > ScheduledAt; + public readonly global::SpacetimeDB.Col Text; + + internal SendMessageTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col( + tableName, + "ScheduledId" + ); + ScheduledAt = new global::SpacetimeDB.Col< + global::Timers.SendMessageTimer, + SpacetimeDB.ScheduleAt + >(tableName, "ScheduledAt"); + Text = new global::SpacetimeDB.Col( + tableName, + "Text" + ); + } + } + + public readonly struct SendMessageTimerIxCols + { + public readonly global::SpacetimeDB.IxCol< + global::Timers.SendMessageTimer, + ulong + > ScheduledId; + + internal SendMessageTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol( + tableName, + "ScheduledId" + ); + } + } + + public readonly partial struct QueryBuilder + { + public global::SpacetimeDB.Table< + global::Timers.SendMessageTimer, + SendMessageTimerCols, + SendMessageTimerIxCols + > SendMessageTimer() => + new( + "SendMessageTimer", + new SendMessageTimerCols("SendMessageTimer"), + new SendMessageTimerIxCols("SendMessageTimer") + ); + } + public sealed record ReducerContext : DbContext, Internal.IReducerContext { public readonly Identity Sender; @@ -201,6 +681,8 @@ public sealed record ViewContext : DbContext, Internal.I { public Identity Sender { get; } + public QueryBuilder From => default; + internal ViewContext(Identity sender, Internal.LocalReadOnly db) : base(db) { @@ -212,6 +694,8 @@ public sealed record AnonymousViewContext : DbContext, Internal.IAnonymousViewContext { + public QueryBuilder From => default; + internal AnonymousViewContext(Internal.LocalReadOnly db) : base(db) { } } @@ -1132,6 +1616,48 @@ internal ScheduledIdUniqueIndex() } } +sealed class public_table_queryViewDispatcher : global::SpacetimeDB.Internal.IView +{ + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV9( + Name: "public_table_query", + Index: 0, + IsPublic: true, + IsAnonymous: false, + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption< + PublicTable, + PublicTable.BSATN + >().GetAlgebraicType(registrar) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IViewContext ctx + ) + { + try + { + var returnValue = Module.PublicTableQuery((SpacetimeDB.ViewContext)ctx); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RawSql( + returnValue.ToSql() + ); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error("Error in view 'public_table_query': " + e); + throw; + } + } +} + sealed class public_table_viewViewDispatcher : global::SpacetimeDB.Internal.IView { public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( @@ -1139,7 +1665,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( Name: "public_table_view", - Index: 0, + Index: 1, IsPublic: true, IsAnonymous: false, Params: [], @@ -1748,6 +2274,7 @@ public static void Main() // IMPORTANT: The order in which we register views matters. // It must correspond to the order in which we call `GenerateDispatcherClass`. // See the comment on `GenerateDispatcherClass` for more explanation. + SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterAnonymousView(); diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index 93c44cf0c26..b7074cca3d5 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -805,6 +805,122 @@ public IEnumerable GenerateReadOnlyAccessors() } } + public IEnumerable GenerateQueryBuilderMembers() + { + if (Kind is TypeKind.Sum) + { + yield break; + } + + var vis = SyntaxFacts.GetText(Visibility); + var globalRowName = $"global::{FullName}"; + + foreach (var accessor in TableAccessors) + { + var tableName = accessor.Name; + var colsTypeName = $"{accessor.Name}Cols"; + var ixColsTypeName = $"{accessor.Name}IxCols"; + + string ColDecl(ColumnDeclaration col) + { + var typeName = col.Type.Name; + var isNullable = typeName.EndsWith("?", StringComparison.Ordinal); + var valueTypeName = isNullable ? typeName[..^1] : typeName; + var colType = isNullable + ? "global::SpacetimeDB.NullableCol" + : "global::SpacetimeDB.Col"; + return $"public readonly {colType}<{globalRowName}, {valueTypeName}> {col.Name};"; + } + + string ColInit(ColumnDeclaration col) + { + var typeName = col.Type.Name; + var isNullable = typeName.EndsWith("?", StringComparison.Ordinal); + var valueTypeName = isNullable ? typeName[..^1] : typeName; + var colType = isNullable + ? "global::SpacetimeDB.NullableCol" + : "global::SpacetimeDB.Col"; + return $"{col.Name} = new {colType}<{globalRowName}, {valueTypeName}>(tableName, \"{col.Name}\");"; + } + + var colsDecls = string.Join("\n ", Members.Select(ColDecl)); + var colsInits = string.Join("\n ", Members.Select(ColInit)); + + var ixPositions = new global::System.Collections.Generic.HashSet(); + foreach (var c in GetConstraints(accessor, ColumnAttrs.PrimaryKey | ColumnAttrs.Unique)) + { + ixPositions.Add(c.Pos); + } + + foreach (var ix in GetIndexes(accessor)) + { + foreach (var colRef in ix.Columns.Array) + { + ixPositions.Add(colRef.Index); + } + } + + var ixMembers = Members + .Select((m, i) => (m, i)) + .Where(pair => ixPositions.Contains(pair.i)) + .Select(pair => pair.m) + .ToArray(); + + string IxColDecl(ColumnDeclaration col) + { + var typeName = col.Type.Name; + var isNullable = typeName.EndsWith("?", StringComparison.Ordinal); + var valueTypeName = isNullable ? typeName[..^1] : typeName; + var colType = isNullable + ? "global::SpacetimeDB.NullableIxCol" + : "global::SpacetimeDB.IxCol"; + return $"public readonly {colType}<{globalRowName}, {valueTypeName}> {col.Name};"; + } + + string IxColInit(ColumnDeclaration col) + { + var typeName = col.Type.Name; + var isNullable = typeName.EndsWith("?", StringComparison.Ordinal); + var valueTypeName = isNullable ? typeName[..^1] : typeName; + var colType = isNullable + ? "global::SpacetimeDB.NullableIxCol" + : "global::SpacetimeDB.IxCol"; + return $"{col.Name} = new {colType}<{globalRowName}, {valueTypeName}>(tableName, \"{col.Name}\");"; + } + + var ixColsDecls = string.Join("\n ", ixMembers.Select(IxColDecl)); + var ixColsInits = string.Join("\n ", ixMembers.Select(IxColInit)); + + yield return $$""" + {{vis}} readonly struct {{colsTypeName}} + { + {{colsDecls}} + + internal {{colsTypeName}}(string tableName) + { + {{colsInits}} + } + } + + {{vis}} readonly struct {{ixColsTypeName}} + { + {{ixColsDecls}} + + internal {{ixColsTypeName}}(string tableName) + { + {{ixColsInits}} + } + } + + public readonly partial struct QueryBuilder + { + {{vis}} global::SpacetimeDB.Table<{{globalRowName}}, {{colsTypeName}}, {{ixColsTypeName}}> {{accessor.Name}}() => + new("{{tableName}}", new {{colsTypeName}}("{{tableName}}"), new {{ixColsTypeName}}("{{tableName}}")); + } + """; + } + } + /// /// Represents a default value for a table field, used during table creation. /// @@ -929,6 +1045,7 @@ record ViewDeclaration public readonly string FullName; public readonly bool IsAnonymous; public readonly bool IsPublic; + public readonly bool ReturnsQuery; public readonly TypeUse ReturnType; public readonly EquatableArray Parameters; public readonly Scope Scope; @@ -961,7 +1078,30 @@ public ViewDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter dia FullName = SymbolToName(method); IsPublic = attr.Public; IsAnonymous = isAnonymousContext; - ReturnType = TypeUse.Parse(method, method.ReturnType, diag); + + ReturnsQuery = false; + if ( + method.ReturnType is INamedTypeSymbol + { + Name: "Query", + ContainingNamespace: { Name: "SpacetimeDB" }, + TypeArguments: [var queryRowType] + } + ) + { + ReturnsQuery = true; + var rowType = TypeUse.Parse(method, queryRowType, diag); + var optType = queryRowType.IsValueType + ? "SpacetimeDB.BSATN.ValueOption" + : "SpacetimeDB.BSATN.RefOption"; + var opt = $"{optType}<{rowType.Name}, {rowType.BSATNName}>"; + // Match Rust semantics: Query is described as Option. + ReturnType = new ReferenceUse(opt, opt); + } + else + { + ReturnType = TypeUse.Parse(method, method.ReturnType, diag); + } Scope = new Scope(methodSyntax.Parent as MemberDeclarationSyntax); if (method.Parameters.Length == 0) @@ -1035,18 +1175,29 @@ public string GenerateDispatcherClass(uint index) var isOption = ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.ValueOption") || ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.RefOption"); - var writeOutput = isOption - ? $$$""" - var listSerializer = {{{ReturnType.BSATNName}}}.GetListSerializer(); - var listValue = ModuleRegistration.ToListOrEmpty(returnValue); - var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); - var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); - using var output = new System.IO.MemoryStream(); - using var writer = new System.IO.BinaryWriter(output); - headerRW.Write(writer, header); - listSerializer.Write(writer, listValue); - return output.ToArray(); - """ + + var writeOutput = + ReturnsQuery + ? $$$""" + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RawSql(returnValue.ToSql()); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + return output.ToArray(); + """ + : isOption + ? $$$""" + var listSerializer = {{{ReturnType.BSATNName}}}.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + listSerializer.Write(writer, listValue); + return output.ToArray(); + """ : $$$""" {{{ReturnType.BSATNName}}} returnRW = new(); var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); @@ -1572,6 +1723,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) v => v.FullName ); + var tableDecls = CollectDistinct( + "TableDecl", + context, + tables, + t => t.FullName, + t => t.FullName + ); + var reducers = context .SyntaxProvider.ForAttributeWithMetadataName( fullyQualifiedMetadataName: typeof(ReducerAttribute).FullName, @@ -1697,6 +1856,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Once the compilation is complete, the generated code will be used to create tables and reducers in the database context.RegisterSourceOutput( tableAccessors + .Combine(tableDecls) .Combine(addReducers) .Combine(addProcedures) .Combine(readOnlyAccessors) @@ -1708,13 +1868,21 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var ( ( ( - (((tableAccessors, addReducers), addProcedures), readOnlyAccessors), + ( + (((tableAccessors, tableDecls), addReducers), addProcedures), + readOnlyAccessors + ), views ), rlsFilters ), columnDefaultValues ) = tuple; + + var queryBuilderMembers = string.Join( + "\n", + tableDecls.Array.SelectMany(t => t.GenerateQueryBuilderMembers()) + ); // Don't generate the FFI boilerplate if there are no tables or reducers. if ( tableAccessors.Array.IsEmpty @@ -1742,6 +1910,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) using TxContext = SpacetimeDB.Internal.TxContext; namespace SpacetimeDB { + {{queryBuilderMembers}} public sealed record ReducerContext : DbContext, Internal.IReducerContext { public readonly Identity Sender; public readonly ConnectionId? ConnectionId; @@ -1898,6 +2067,8 @@ public sealed class Local : global::SpacetimeDB.LocalBase { public sealed record ViewContext : DbContext, Internal.IViewContext { public Identity Sender { get; } + + public QueryBuilder From => default; internal ViewContext(Identity sender, Internal.LocalReadOnly db) : base(db) @@ -1908,6 +2079,8 @@ internal ViewContext(Identity sender, Internal.LocalReadOnly db) public sealed record AnonymousViewContext : DbContext, Internal.IAnonymousViewContext { + public QueryBuilder From => default; + internal AnonymousViewContext(Internal.LocalReadOnly db) : base(db) { } } diff --git a/crates/bindings-csharp/Runtime/QueryBuilder.cs b/crates/bindings-csharp/Runtime/QueryBuilder.cs new file mode 100644 index 00000000000..f87fc03095d --- /dev/null +++ b/crates/bindings-csharp/Runtime/QueryBuilder.cs @@ -0,0 +1,1218 @@ +#nullable enable + +namespace SpacetimeDB; + +using System; +using System.Globalization; + +public readonly struct SqlLiteral +{ + internal string Sql { get; } + + internal SqlLiteral(string sql) + { + Sql = sql; + } + + public override string ToString() => Sql; +} + +public static class SqlLit +{ + public static SqlLiteral String(ReadOnlySpan value) => + new(SqlFormat.FormatStringLiteral(value)); + + public static SqlLiteral Bool(bool value) => new(value ? "TRUE" : "FALSE"); + + public static SqlLiteral Int(sbyte value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(byte value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(short value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(ushort value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(int value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(uint value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(long value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(ulong value) => + new(value.ToString(CultureInfo.InvariantCulture)); + + public static SqlLiteral Int(U128 value) => new(value.ToString()); + + public static SqlLiteral Identity(Identity value) => + new(SqlFormat.FormatHexLiteral(value.ToString())); + + public static SqlLiteral ConnectionId(ConnectionId value) => + new(SqlFormat.FormatHexLiteral(value.ToString())); + + public static SqlLiteral Uuid(Uuid value) => + new(SqlFormat.FormatHexLiteral(value.ToString())); +} + +public readonly partial struct QueryBuilder { } + +public readonly struct Query +{ + public string Sql { get; } + + public Query(string sql) + { + Sql = sql; + } + + public string ToSql() => Sql; + + public override string ToString() => Sql; +} + +public readonly struct BoolExpr +{ + public string Sql { get; } + + public BoolExpr(string sql) + { + Sql = sql; + } + + public BoolExpr And(BoolExpr other) => new($"({Sql} AND {other.Sql})"); + + public BoolExpr Or(BoolExpr other) => new($"({Sql} OR {other.Sql})"); + + public override string ToString() => Sql; +} + +public readonly struct IxJoinEq +{ + internal string LeftRefSql { get; } + internal string RightRefSql { get; } + + internal IxJoinEq(string leftRefSql, string rightRefSql) + { + LeftRefSql = leftRefSql; + RightRefSql = rightRefSql; + } +} + +public readonly struct Col + where TValue : notnull +{ + private readonly string tableName; + private readonly string columnName; + + public Col(string tableName, string columnName) + { + this.tableName = tableName; + this.columnName = columnName; + } + + internal string RefSql => + $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; + + public BoolExpr Eq(SqlLiteral value) => new($"({RefSql} = {value.Sql})"); + + public BoolExpr Eq(Col other) => new($"({RefSql} = {other.RefSql})"); + + public BoolExpr Neq(SqlLiteral value) => new($"({RefSql} <> {value.Sql})"); + + public BoolExpr Neq(Col other) => new($"({RefSql} <> {other.RefSql})"); + + public BoolExpr Lt(SqlLiteral value) => new($"({RefSql} < {value.Sql})"); + + public BoolExpr Lte(SqlLiteral value) => new($"({RefSql} <= {value.Sql})"); + + public BoolExpr Gt(SqlLiteral value) => new($"({RefSql} > {value.Sql})"); + + public BoolExpr Gte(SqlLiteral value) => new($"({RefSql} >= {value.Sql})"); + + public BoolExpr Lt(NullableCol other) => + new($"({RefSql} < {other.RefSql})"); + + public BoolExpr Lte(NullableCol other) => + new($"({RefSql} <= {other.RefSql})"); + + public BoolExpr Gt(NullableCol other) => + new($"({RefSql} > {other.RefSql})"); + + public BoolExpr Gte(NullableCol other) => + new($"({RefSql} >= {other.RefSql})"); + + public BoolExpr Lt(Col other) => new($"({RefSql} < {other.RefSql})"); + + public BoolExpr Lte(Col other) => new($"({RefSql} <= {other.RefSql})"); + + public BoolExpr Gt(Col other) => new($"({RefSql} > {other.RefSql})"); + + public BoolExpr Gte(Col other) => new($"({RefSql} >= {other.RefSql})"); + + public override string ToString() => RefSql; +} + +public readonly struct NullableCol + where TValue : notnull +{ + private readonly string tableName; + private readonly string columnName; + + public NullableCol(string tableName, string columnName) + { + this.tableName = tableName; + this.columnName = columnName; + } + + internal string RefSql => + $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; + + public BoolExpr Eq(SqlLiteral value) => new($"({RefSql} = {value.Sql})"); + + public BoolExpr Eq(NullableCol other) => + new($"({RefSql} = {other.RefSql})"); + + public BoolExpr Eq(Col other) => new($"({RefSql} = {other.RefSql})"); + + public IxJoinEq Eq(IxCol other) => + new(RefSql, other.RefSql); + + public BoolExpr Neq(SqlLiteral value) => new($"({RefSql} <> {value.Sql})"); + + public BoolExpr Neq(NullableCol other) => + new($"({RefSql} <> {other.RefSql})"); + + public BoolExpr Lt(SqlLiteral value) => new($"({RefSql} < {value.Sql})"); + + public BoolExpr Lte(SqlLiteral value) => new($"({RefSql} <= {value.Sql})"); + + public BoolExpr Gt(SqlLiteral value) => new($"({RefSql} > {value.Sql})"); + + public BoolExpr Gte(SqlLiteral value) => new($"({RefSql} >= {value.Sql})"); + + public override string ToString() => RefSql; +} + +public readonly struct IxCol + where TValue : notnull +{ + private readonly string tableName; + private readonly string columnName; + + public IxCol(string tableName, string columnName) + { + this.tableName = tableName; + this.columnName = columnName; + } + + internal string RefSql => + $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; + + public BoolExpr Eq(SqlLiteral value) => new($"({RefSql} = {value.Sql})"); + + public IxJoinEq Eq(IxCol other) => + new(RefSql, other.RefSql); + + public BoolExpr Neq(SqlLiteral value) => new($"({RefSql} <> {value.Sql})"); + + public override string ToString() => RefSql; +} + +public readonly struct NullableIxCol + where TValue : notnull +{ + private readonly string tableName; + private readonly string columnName; + + public NullableIxCol(string tableName, string columnName) + { + this.tableName = tableName; + this.columnName = columnName; + } + + internal string RefSql => + $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; + + public BoolExpr Eq(SqlLiteral value) => new($"({RefSql} = {value.Sql})"); + + public IxJoinEq Eq(NullableIxCol other) => + new(RefSql, other.RefSql); + + public BoolExpr Neq(SqlLiteral value) => new($"({RefSql} <> {value.Sql})"); + + public override string ToString() => RefSql; +} + +public sealed class Table +{ + private readonly string tableName; + private readonly TCols cols; + private readonly TIxCols ixCols; + + public Table(string tableName, TCols cols, TIxCols ixCols) + { + this.tableName = tableName; + this.cols = cols; + this.ixCols = ixCols; + } + + internal string TableRefSql => SqlFormat.QuoteIdent(tableName); + + internal TCols Cols => cols; + + internal TIxCols IxCols => ixCols; + + public string ToSql() => $"SELECT * FROM {SqlFormat.QuoteIdent(tableName)}"; + + public Query Build() => new(ToSql()); + + public FromWhere Where(Func> predicate) => + new(this, predicate(cols)); + + public FromWhere Where(Func> predicate) => + new(this, predicate(cols, ixCols)); + + public FromWhere Filter(Func> predicate) => + Where(predicate); + + public FromWhere Filter(Func> predicate) => + Where(predicate); + + public LeftSemiJoin LeftSemijoin< + TRightRow, + TRightCols, + TRightIxCols + >( + Table right, + Func> on + ) => new(this, right, on(ixCols, right.ixCols), whereExpr: null); + + public RightSemiJoin RightSemijoin< + TRightRow, + TRightCols, + TRightIxCols + >( + Table right, + Func> on + ) => new(this, right, on(ixCols, right.ixCols), leftWhereExpr: null); +} + +public sealed class FromWhere +{ + private readonly Table table; + private readonly BoolExpr expr; + + internal FromWhere(Table table, BoolExpr expr) + { + this.table = table; + this.expr = expr; + } + + public FromWhere Where(Func> predicate) => + new(table, expr.And(predicate(table.Cols))); + + public FromWhere Where(Func> predicate) => + new(table, expr.And(predicate(table.Cols, table.IxCols))); + + public FromWhere Filter(Func> predicate) => + Where(predicate); + + public FromWhere Filter(Func> predicate) => + Where(predicate); + + public Query Build() => new($"{table.ToSql()} WHERE {expr.Sql}"); + + public LeftSemiJoin LeftSemijoin< + TRightRow, + TRightCols, + TRightIxCols + >( + Table right, + Func> on + ) => new(table, right, on(table.IxCols, right.IxCols), expr); + + public RightSemiJoin RightSemijoin< + TRightRow, + TRightCols, + TRightIxCols + >( + Table right, + Func> on + ) => new(table, right, on(table.IxCols, right.IxCols), expr); +} + +public sealed class LeftSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols +> +{ + private readonly Table left; + private readonly Table right; + private readonly string leftJoinRefSql; + private readonly string rightJoinRefSql; + private readonly BoolExpr? whereExpr; + + internal LeftSemiJoin( + Table left, + Table right, + IxJoinEq join, + BoolExpr? whereExpr + ) + { + this.left = left; + this.right = right; + leftJoinRefSql = join.LeftRefSql; + rightJoinRefSql = join.RightRefSql; + this.whereExpr = whereExpr; + } + + public LeftSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Where(Func> predicate) + { + var extra = predicate(left.Cols); + BoolExpr? next = whereExpr.HasValue ? whereExpr.Value.And(extra) : extra; + return new( + left, + right, + new IxJoinEq(leftJoinRefSql, rightJoinRefSql), + next + ); + } + + public LeftSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Where(Func> predicate) + { + var extra = predicate(left.Cols, left.IxCols); + BoolExpr? next = whereExpr.HasValue ? whereExpr.Value.And(extra) : extra; + return new( + left, + right, + new IxJoinEq(leftJoinRefSql, rightJoinRefSql), + next + ); + } + + public LeftSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Filter(Func> predicate) => Where(predicate); + + public LeftSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Filter(Func> predicate) => Where(predicate); + + public Query Build() + { + var whereClause = whereExpr.HasValue ? $" WHERE {whereExpr.Value.Sql}" : string.Empty; + return new( + $"SELECT {left.TableRefSql}.* FROM {left.TableRefSql} JOIN {right.TableRefSql} ON {leftJoinRefSql} = {rightJoinRefSql}{whereClause}" + ); + } +} + +public sealed class RightSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols +> +{ + private readonly Table left; + private readonly Table right; + private readonly string leftJoinRefSql; + private readonly string rightJoinRefSql; + private readonly BoolExpr? leftWhereExpr; + private readonly BoolExpr? rightWhereExpr; + + internal RightSemiJoin( + Table left, + Table right, + IxJoinEq join, + BoolExpr? leftWhereExpr, + BoolExpr? rightWhereExpr + ) + { + this.left = left; + this.right = right; + leftJoinRefSql = join.LeftRefSql; + rightJoinRefSql = join.RightRefSql; + this.leftWhereExpr = leftWhereExpr; + this.rightWhereExpr = rightWhereExpr; + } + + internal RightSemiJoin( + Table left, + Table right, + IxJoinEq join, + BoolExpr? leftWhereExpr + ) + : this(left, right, join, leftWhereExpr, rightWhereExpr: null) { } + + public RightSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Where(Func> predicate) + { + var extra = predicate(right.Cols); + BoolExpr? nextRight = rightWhereExpr.HasValue + ? rightWhereExpr.Value.And(extra) + : extra; + return new( + left, + right, + new IxJoinEq(leftJoinRefSql, rightJoinRefSql), + leftWhereExpr, + nextRight + ); + } + + public RightSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Where(Func> predicate) + { + var extra = predicate(right.Cols, right.IxCols); + BoolExpr? nextRight = rightWhereExpr.HasValue + ? rightWhereExpr.Value.And(extra) + : extra; + return new( + left, + right, + new IxJoinEq(leftJoinRefSql, rightJoinRefSql), + leftWhereExpr, + nextRight + ); + } + + public RightSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Filter(Func> predicate) => Where(predicate); + + public RightSemiJoin< + TLeftRow, + TLeftCols, + TLeftIxCols, + TRightRow, + TRightCols, + TRightIxCols + > Filter(Func> predicate) => Where(predicate); + + public Query Build() + { + var whereClause = string.Empty; + + if (leftWhereExpr.HasValue && rightWhereExpr.HasValue) + { + whereClause = $" WHERE {leftWhereExpr.Value.Sql} AND {rightWhereExpr.Value.Sql}"; + } + else if (leftWhereExpr.HasValue) + { + whereClause = $" WHERE {leftWhereExpr.Value.Sql}"; + } + else if (rightWhereExpr.HasValue) + { + whereClause = $" WHERE {rightWhereExpr.Value.Sql}"; + } + + return new( + $"SELECT {right.TableRefSql}.* FROM {left.TableRefSql} JOIN {right.TableRefSql} ON {leftJoinRefSql} = {rightJoinRefSql}{whereClause}" + ); + } +} + +public static class QueryBuilderExtensions +{ + public static BoolExpr Eq(this Col col, ReadOnlySpan value) => + col.Eq(SqlLit.String(value)); + + public static BoolExpr Neq(this Col col, ReadOnlySpan value) => + col.Neq(SqlLit.String(value)); + + public static BoolExpr Lt(this Col col, ReadOnlySpan value) => + col.Lt(SqlLit.String(value)); + + public static BoolExpr Lte(this Col col, ReadOnlySpan value) => + col.Lte(SqlLit.String(value)); + + public static BoolExpr Gt(this Col col, ReadOnlySpan value) => + col.Gt(SqlLit.String(value)); + + public static BoolExpr Gte(this Col col, ReadOnlySpan value) => + col.Gte(SqlLit.String(value)); + + public static BoolExpr Eq( + this NullableCol col, + ReadOnlySpan value + ) => col.Eq(SqlLit.String(value)); + + public static BoolExpr Neq( + this NullableCol col, + ReadOnlySpan value + ) => col.Neq(SqlLit.String(value)); + + public static BoolExpr Lt( + this NullableCol col, + ReadOnlySpan value + ) => col.Lt(SqlLit.String(value)); + + public static BoolExpr Lte( + this NullableCol col, + ReadOnlySpan value + ) => col.Lte(SqlLit.String(value)); + + public static BoolExpr Gt( + this NullableCol col, + ReadOnlySpan value + ) => col.Gt(SqlLit.String(value)); + + public static BoolExpr Gte( + this NullableCol col, + ReadOnlySpan value + ) => col.Gte(SqlLit.String(value)); + + public static BoolExpr Eq(this Col col, bool value) => + col.Eq(SqlLit.Bool(value)); + + public static BoolExpr Neq(this Col col, bool value) => + col.Neq(SqlLit.Bool(value)); + + public static BoolExpr Eq(this NullableCol col, bool value) => + col.Eq(SqlLit.Bool(value)); + + public static BoolExpr Neq(this NullableCol col, bool value) => + col.Neq(SqlLit.Bool(value)); + + public static BoolExpr Eq(this Col col, sbyte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, sbyte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, sbyte value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, sbyte value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, sbyte value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, sbyte value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, sbyte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, sbyte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, sbyte value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, sbyte value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, sbyte value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, sbyte value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, byte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, byte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, byte value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, byte value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, byte value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, byte value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, byte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, byte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, byte value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, byte value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, byte value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, byte value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, short value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, short value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, short value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, short value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, short value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, short value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, short value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, short value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, short value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, short value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, short value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, short value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, ushort value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, ushort value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, ushort value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, ushort value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, ushort value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, ushort value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, ushort value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, ushort value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, ushort value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, ushort value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, ushort value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, ushort value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, int value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, int value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, int value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, int value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, int value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, int value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, int value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, int value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, int value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, int value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, int value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, int value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, uint value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, uint value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, uint value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, uint value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, uint value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, uint value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, uint value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, uint value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, uint value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, uint value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, uint value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, uint value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, long value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, long value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, long value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, long value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, long value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, long value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, long value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, long value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, long value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, long value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, long value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, long value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, ulong value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, ulong value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, ulong value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, ulong value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, ulong value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, ulong value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, ulong value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, ulong value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, ulong value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, ulong value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, ulong value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, ulong value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, U128 value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this Col col, U128 value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this Col col, U128 value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this Col col, U128 value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this Col col, U128 value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this Col col, U128 value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableCol col, U128 value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableCol col, U128 value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Lt(this NullableCol col, U128 value) => + col.Lt(SqlLit.Int(value)); + + public static BoolExpr Lte(this NullableCol col, U128 value) => + col.Lte(SqlLit.Int(value)); + + public static BoolExpr Gt(this NullableCol col, U128 value) => + col.Gt(SqlLit.Int(value)); + + public static BoolExpr Gte(this NullableCol col, U128 value) => + col.Gte(SqlLit.Int(value)); + + public static BoolExpr Eq(this Col col, Identity value) => + col.Eq(SqlLit.Identity(value)); + + public static BoolExpr Neq(this Col col, Identity value) => + col.Neq(SqlLit.Identity(value)); + + public static BoolExpr Eq(this NullableCol col, Identity value) => + col.Eq(SqlLit.Identity(value)); + + public static BoolExpr Neq(this NullableCol col, Identity value) => + col.Neq(SqlLit.Identity(value)); + + public static BoolExpr Eq(this Col col, ConnectionId value) => + col.Eq(SqlLit.ConnectionId(value)); + + public static BoolExpr Neq(this Col col, ConnectionId value) => + col.Neq(SqlLit.ConnectionId(value)); + + public static BoolExpr Eq( + this NullableCol col, + ConnectionId value + ) => col.Eq(SqlLit.ConnectionId(value)); + + public static BoolExpr Neq( + this NullableCol col, + ConnectionId value + ) => col.Neq(SqlLit.ConnectionId(value)); + + public static BoolExpr Eq(this Col col, Uuid value) => + col.Eq(SqlLit.Uuid(value)); + + public static BoolExpr Neq(this Col col, Uuid value) => + col.Neq(SqlLit.Uuid(value)); + + public static BoolExpr Eq(this NullableCol col, Uuid value) => + col.Eq(SqlLit.Uuid(value)); + + public static BoolExpr Neq(this NullableCol col, Uuid value) => + col.Neq(SqlLit.Uuid(value)); + + public static BoolExpr Eq(this IxCol col, ReadOnlySpan value) => + col.Eq(SqlLit.String(value)); + + public static BoolExpr Neq( + this IxCol col, + ReadOnlySpan value + ) => col.Neq(SqlLit.String(value)); + + public static BoolExpr Eq( + this NullableIxCol col, + ReadOnlySpan value + ) => col.Eq(SqlLit.String(value)); + + public static BoolExpr Neq( + this NullableIxCol col, + ReadOnlySpan value + ) => col.Neq(SqlLit.String(value)); + + public static BoolExpr Eq(this IxCol col, bool value) => + col.Eq(SqlLit.Bool(value)); + + public static BoolExpr Neq(this IxCol col, bool value) => + col.Neq(SqlLit.Bool(value)); + + public static BoolExpr Eq(this NullableIxCol col, bool value) => + col.Eq(SqlLit.Bool(value)); + + public static BoolExpr Neq(this NullableIxCol col, bool value) => + col.Neq(SqlLit.Bool(value)); + + public static BoolExpr Eq(this IxCol col, sbyte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, sbyte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, sbyte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, sbyte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, byte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, byte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, byte value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, byte value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, short value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, short value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, short value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, short value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, ushort value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, ushort value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, ushort value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, ushort value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, int value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, int value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, int value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, int value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, uint value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, uint value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, uint value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, uint value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, long value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, long value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, long value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, long value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, ulong value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, ulong value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, ulong value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, ulong value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, U128 value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this IxCol col, U128 value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this NullableIxCol col, U128 value) => + col.Eq(SqlLit.Int(value)); + + public static BoolExpr Neq(this NullableIxCol col, U128 value) => + col.Neq(SqlLit.Int(value)); + + public static BoolExpr Eq(this IxCol col, Identity value) => + col.Eq(SqlLit.Identity(value)); + + public static BoolExpr Neq(this IxCol col, Identity value) => + col.Neq(SqlLit.Identity(value)); + + public static BoolExpr Eq(this NullableIxCol col, Identity value) => + col.Eq(SqlLit.Identity(value)); + + public static BoolExpr Neq( + this NullableIxCol col, + Identity value + ) => col.Neq(SqlLit.Identity(value)); + + public static BoolExpr Eq(this IxCol col, ConnectionId value) => + col.Eq(SqlLit.ConnectionId(value)); + + public static BoolExpr Neq( + this IxCol col, + ConnectionId value + ) => col.Neq(SqlLit.ConnectionId(value)); + + public static BoolExpr Eq( + this NullableIxCol col, + ConnectionId value + ) => col.Eq(SqlLit.ConnectionId(value)); + + public static BoolExpr Neq( + this NullableIxCol col, + ConnectionId value + ) => col.Neq(SqlLit.ConnectionId(value)); + + public static BoolExpr Eq(this IxCol col, Uuid value) => + col.Eq(SqlLit.Uuid(value)); + + public static BoolExpr Neq(this IxCol col, Uuid value) => + col.Neq(SqlLit.Uuid(value)); + + public static BoolExpr Eq(this NullableIxCol col, Uuid value) => + col.Eq(SqlLit.Uuid(value)); + + public static BoolExpr Neq(this NullableIxCol col, Uuid value) => + col.Neq(SqlLit.Uuid(value)); +} + +internal static class SqlFormat +{ + public static string QuoteIdent(string ident) + { + ident ??= string.Empty; + return $"\"{ident.Replace("\"", "\"\"")}\""; + } + + private static string EscapeString(string s) => s.Replace("'", "''"); + + public static string FormatStringLiteral(ReadOnlySpan value) => + $"'{EscapeString(value.ToString())}'"; + + public static string FormatHexLiteral(string hex) + { + if (hex is null) + { + throw new ArgumentNullException(nameof(hex)); + } + + var s = hex; + if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + s = s.Substring(2); + } + + s = s.Replace("-", string.Empty); + + for (var i = 0; i < s.Length; i++) + { + var c = s[i]; + var isHex = c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F'; + if (!isHex) + { + throw new ArgumentOutOfRangeException(nameof(hex), $"Invalid hex character '{c}'."); + } + } + + return $"0x{s}"; + } +}