diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index e08d53deb5f..e6f09ceb225 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -1,6 +1,7 @@ // Note: the generated code depends on APIs and interfaces from crates/bindings-csharp/BSATN.Runtime. use super::util::fmt_fn; +use std::collections::BTreeSet; use std::fmt::{self, Write}; use std::ops::Deref; @@ -14,7 +15,7 @@ use crate::{indent_scope, OutputFile}; use convert_case::{Case, Casing}; use spacetimedb_lib::sats::layout::PrimitiveType; use spacetimedb_primitives::ColId; -use spacetimedb_schema::def::{ModuleDef, TableDef, TypeDef}; +use spacetimedb_schema::def::{BTreeAlgorithm, IndexAlgorithm, ModuleDef, TableDef, TypeDef}; use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::schema::TableSchema; use spacetimedb_schema::type_for_generate::{ @@ -473,6 +474,20 @@ const REDUCER_EVENTS: &str = r#" Error += callback; return this; } + + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } /// /// Subscribe to the following SQL queries. @@ -670,6 +685,81 @@ impl Lang for Csharp<'_> { writeln!(output, "public readonly {csharp_table_class_name} {csharp_table_name};"); }); + // Emit top-level Cols/IxCols helpers for the typed query builder. + writeln!(output); + + let cols_owner_name = table.name.deref().to_case(Case::Pascal); + let row_type = type_ref_name(module, table.product_type_ref); + let product_type = module.typespace_for_generate()[table.product_type_ref] + .as_product() + .unwrap(); + + let mut ix_col_positions: BTreeSet = BTreeSet::new(); + for idx in iter_indexes(table) { + if let IndexAlgorithm::BTree(BTreeAlgorithm { columns }) = &idx.algorithm { + for col_pos in columns.iter() { + ix_col_positions.insert(col_pos.idx()); + } + } + } + + writeln!(output, "public sealed class {cols_owner_name}Cols"); + indented_block(&mut output, |output| { + for (field_name, field_type) in &product_type.elements { + let prop = field_name.deref().to_case(Case::Pascal); + let ty = ty_fmt(module, field_type); + writeln!( + output, + "public global::SpacetimeDB.Col<{row_type}, {ty}> {prop} {{ get; }}" + ); + } + writeln!(output); + writeln!(output, "public {cols_owner_name}Cols(string tableName)"); + indented_block(output, |output| { + for (field_name, field_type) in &product_type.elements { + let prop = field_name.deref().to_case(Case::Pascal); + let ty = ty_fmt(module, field_type); + let col_name = field_name.deref(); + writeln!( + output, + "{prop} = new global::SpacetimeDB.Col<{row_type}, {ty}>(tableName, \"{col_name}\");" + ); + } + }); + }); + writeln!(output); + + writeln!(output, "public sealed class {cols_owner_name}IxCols"); + indented_block(&mut output, |output| { + for (i, (field_name, field_type)) in product_type.elements.iter().enumerate() { + if !ix_col_positions.contains(&i) { + continue; + } + let prop = field_name.deref().to_case(Case::Pascal); + let ty = ty_fmt(module, field_type); + writeln!( + output, + "public global::SpacetimeDB.IxCol<{row_type}, {ty}> {prop} {{ get; }}" + ); + } + writeln!(output); + writeln!(output, "public {cols_owner_name}IxCols(string tableName)"); + indented_block(output, |output| { + for (i, (field_name, field_type)) in product_type.elements.iter().enumerate() { + if !ix_col_positions.contains(&i) { + continue; + } + let prop = field_name.deref().to_case(Case::Pascal); + let ty = ty_fmt(module, field_type); + let col_name = field_name.deref(); + writeln!( + output, + "{prop} = new global::SpacetimeDB.IxCol<{row_type}, {ty}>(tableName, \"{col_name}\");" + ); + } + }); + }); + OutputFile { filename: format!("Tables/{}.g.cs", table.name.deref().to_case(Case::Pascal)), code: output.into_inner(), @@ -961,6 +1051,80 @@ impl Lang for Csharp<'_> { writeln!(output, "{REDUCER_EVENTS}"); + writeln!(output, "public sealed class QueryBuilder"); + indented_block(&mut output, |output| { + writeln!(output, "public From From {{ get; }} = new();"); + }); + writeln!(output); + + writeln!(output, "public sealed class From"); + indented_block(&mut output, |output| { + for (table_name, product_type_ref) in iter_table_names_and_types(module) { + let method_name = table_name.deref().to_case(Case::Pascal); + let row_type = type_ref_name(module, product_type_ref); + let table_name_lit = format!("{:?}", table_name.deref()); + writeln!( + output, + "public global::SpacetimeDB.Table<{row_type}, {method_name}Cols, {method_name}IxCols> {method_name}() => new({table_name_lit}, new {method_name}Cols({table_name_lit}), new {method_name}IxCols({table_name_lit}));" + ); + } + }); + writeln!(output); + + writeln!(output, "public sealed class TypedSubscriptionBuilder"); + indented_block(&mut output, |output| { + writeln!(output, "private readonly IDbConnection conn;"); + writeln!(output, "private Action? Applied;"); + writeln!(output, "private Action? Error;"); + writeln!(output, "private readonly List querySqls = new();"); + writeln!(output); + + writeln!( + output, + "internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error)" + ); + indented_block(output, |output| { + writeln!(output, "this.conn = conn;"); + writeln!(output, "Applied = applied;"); + writeln!(output, "Error = error;"); + }); + writeln!(output); + + writeln!( + output, + "public TypedSubscriptionBuilder OnApplied(Action callback)" + ); + indented_block(output, |output| { + writeln!(output, "Applied += callback;"); + writeln!(output, "return this;"); + }); + writeln!(output); + + writeln!( + output, + "public TypedSubscriptionBuilder OnError(Action callback)" + ); + indented_block(output, |output| { + writeln!(output, "Error += callback;"); + writeln!(output, "return this;"); + }); + writeln!(output); + + writeln!(output, "public TypedSubscriptionBuilder AddQuery(Func> build)"); + indented_block(output, |output| { + writeln!(output, "var qb = new QueryBuilder();"); + writeln!(output, "querySqls.Add(build(qb).ToSql());"); + writeln!(output, "return this;"); + }); + writeln!(output); + + writeln!( + output, + "public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray());" + ); + }); + writeln!(output); + writeln!(output, "public abstract partial class Reducer"); indented_block(&mut output, |output| { // Prevent instantiation of this class from outside. diff --git a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap index f5c0f73934a..67e2f096be1 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap @@ -1,5 +1,6 @@ --- source: crates/codegen/tests/codegen.rs +assertion_line: 37 expression: outfiles --- "Procedures/GetMySchemaViaHttp.g.cs" = ''' @@ -1754,6 +1755,20 @@ namespace SpacetimeDB Error += callback; return this; } + + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } /// /// Subscribe to the following SQL queries. @@ -1817,6 +1832,65 @@ namespace SpacetimeDB { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table HasSpecialStuff() => new("has_special_stuff", new HasSpecialStuffCols("has_special_stuff"), new HasSpecialStuffIxCols("has_special_stuff")); + public global::SpacetimeDB.Table LoggedOutPlayer() => new("logged_out_player", new LoggedOutPlayerCols("logged_out_player"), new LoggedOutPlayerIxCols("logged_out_player")); + public global::SpacetimeDB.Table MyPlayer() => new("my_player", new MyPlayerCols("my_player"), new MyPlayerIxCols("my_player")); + public global::SpacetimeDB.Table Person() => new("person", new PersonCols("person"), new PersonIxCols("person")); + public global::SpacetimeDB.Table PkMultiIdentity() => new("pk_multi_identity", new PkMultiIdentityCols("pk_multi_identity"), new PkMultiIdentityIxCols("pk_multi_identity")); + public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player")); + public global::SpacetimeDB.Table Points() => new("points", new PointsCols("points"), new PointsIxCols("points")); + public global::SpacetimeDB.Table PrivateTable() => new("private_table", new PrivateTableCols("private_table"), new PrivateTableIxCols("private_table")); + public global::SpacetimeDB.Table RepeatingTestArg() => new("repeating_test_arg", new RepeatingTestArgCols("repeating_test_arg"), new RepeatingTestArgIxCols("repeating_test_arg")); + public global::SpacetimeDB.Table TableToRemove() => new("table_to_remove", new TableToRemoveCols("table_to_remove"), new TableToRemoveIxCols("table_to_remove")); + public global::SpacetimeDB.Table TestA() => new("test_a", new TestACols("test_a"), new TestAIxCols("test_a")); + public global::SpacetimeDB.Table TestD() => new("test_d", new TestDCols("test_d"), new TestDIxCols("test_d")); + public global::SpacetimeDB.Table TestE() => new("test_e", new TestECols("test_e"), new TestEIxCols("test_e")); + public global::SpacetimeDB.Table TestF() => new("test_f", new TestFCols("test_f"), new TestFIxCols("test_f")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).Sql); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } @@ -1937,6 +2011,26 @@ namespace SpacetimeDB public readonly HasSpecialStuffHandle HasSpecialStuff; } + + public sealed class HasSpecialStuffCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col ConnectionId { get; } + + public HasSpecialStuffCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + ConnectionId = new global::SpacetimeDB.Col(tableName, "connection_id"); + } + } + + public sealed class HasSpecialStuffIxCols + { + + public HasSpecialStuffIxCols(string tableName) + { + } + } } ''' "Tables/LoggedOutPlayer.g.cs" = ''' @@ -1998,6 +2092,34 @@ namespace SpacetimeDB public readonly LoggedOutPlayerHandle LoggedOutPlayer; } + + public sealed class LoggedOutPlayerCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Name { get; } + + public LoggedOutPlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class LoggedOutPlayerIxCols + { + public global::SpacetimeDB.IxCol Identity { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + public global::SpacetimeDB.IxCol Name { get; } + + public LoggedOutPlayerIxCols(string tableName) + { + Identity = new global::SpacetimeDB.IxCol(tableName, "identity"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + Name = new global::SpacetimeDB.IxCol(tableName, "name"); + } + } } ''' "Tables/MyPlayer.g.cs" = ''' @@ -2027,6 +2149,28 @@ namespace SpacetimeDB public readonly MyPlayerHandle MyPlayer; } + + public sealed class MyPlayerCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Name { get; } + + public MyPlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class MyPlayerIxCols + { + + public MyPlayerIxCols(string tableName) + { + } + } } ''' "Tables/Person.g.cs" = ''' @@ -2078,6 +2222,32 @@ namespace SpacetimeDB public readonly PersonHandle Person; } + + public sealed class PersonCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col Age { get; } + + public PersonCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + Age = new global::SpacetimeDB.Col(tableName, "age"); + } + } + + public sealed class PersonIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Age { get; } + + public PersonIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "id"); + Age = new global::SpacetimeDB.IxCol(tableName, "age"); + } + } } ''' "Tables/PkMultiIdentity.g.cs" = ''' @@ -2129,6 +2299,30 @@ namespace SpacetimeDB public readonly PkMultiIdentityHandle PkMultiIdentity; } + + public sealed class PkMultiIdentityCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Other { get; } + + public PkMultiIdentityCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "id"); + Other = new global::SpacetimeDB.Col(tableName, "other"); + } + } + + public sealed class PkMultiIdentityIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Other { get; } + + public PkMultiIdentityIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "id"); + Other = new global::SpacetimeDB.IxCol(tableName, "other"); + } + } } ''' "Tables/Player.g.cs" = ''' @@ -2190,6 +2384,34 @@ namespace SpacetimeDB public readonly PlayerHandle Player; } + + public sealed class PlayerCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Name { get; } + + public PlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class PlayerIxCols + { + public global::SpacetimeDB.IxCol Identity { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + public global::SpacetimeDB.IxCol Name { get; } + + public PlayerIxCols(string tableName) + { + Identity = new global::SpacetimeDB.IxCol(tableName, "identity"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + Name = new global::SpacetimeDB.IxCol(tableName, "name"); + } + } } ''' "Tables/Points.g.cs" = ''' @@ -2229,6 +2451,30 @@ namespace SpacetimeDB public readonly PointsHandle Points; } + + public sealed class PointsCols + { + public global::SpacetimeDB.Col X { get; } + public global::SpacetimeDB.Col Y { get; } + + public PointsCols(string tableName) + { + X = new global::SpacetimeDB.Col(tableName, "x"); + Y = new global::SpacetimeDB.Col(tableName, "y"); + } + } + + public sealed class PointsIxCols + { + public global::SpacetimeDB.IxCol X { get; } + public global::SpacetimeDB.IxCol Y { get; } + + public PointsIxCols(string tableName) + { + X = new global::SpacetimeDB.IxCol(tableName, "x"); + Y = new global::SpacetimeDB.IxCol(tableName, "y"); + } + } } ''' "Tables/PrivateTable.g.cs" = ''' @@ -2258,6 +2504,24 @@ namespace SpacetimeDB public readonly PrivateTableHandle PrivateTable; } + + public sealed class PrivateTableCols + { + public global::SpacetimeDB.Col Name { get; } + + public PrivateTableCols(string tableName) + { + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class PrivateTableIxCols + { + + public PrivateTableIxCols(string tableName) + { + } + } } ''' "Tables/RepeatingTestArg.g.cs" = ''' @@ -2299,6 +2563,30 @@ namespace SpacetimeDB public readonly RepeatingTestArgHandle RepeatingTestArg; } + + public sealed class RepeatingTestArgCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + public global::SpacetimeDB.Col PrevTime { get; } + + public RepeatingTestArgCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + PrevTime = new global::SpacetimeDB.Col(tableName, "prev_time"); + } + } + + public sealed class RepeatingTestArgIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public RepeatingTestArgIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } ''' "Tables/TableToRemove.g.cs" = ''' @@ -2328,6 +2616,24 @@ namespace SpacetimeDB public readonly TableToRemoveHandle TableToRemove; } + + public sealed class TableToRemoveCols + { + public global::SpacetimeDB.Col Id { get; } + + public TableToRemoveCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "id"); + } + } + + public sealed class TableToRemoveIxCols + { + + public TableToRemoveIxCols(string tableName) + { + } + } } ''' "Tables/TestA.g.cs" = ''' @@ -2367,6 +2673,30 @@ namespace SpacetimeDB public readonly TestAHandle TestA; } + + public sealed class TestACols + { + public global::SpacetimeDB.Col X { get; } + public global::SpacetimeDB.Col Y { get; } + public global::SpacetimeDB.Col Z { get; } + + public TestACols(string tableName) + { + X = new global::SpacetimeDB.Col(tableName, "x"); + Y = new global::SpacetimeDB.Col(tableName, "y"); + Z = new global::SpacetimeDB.Col(tableName, "z"); + } + } + + public sealed class TestAIxCols + { + public global::SpacetimeDB.IxCol X { get; } + + public TestAIxCols(string tableName) + { + X = new global::SpacetimeDB.IxCol(tableName, "x"); + } + } } ''' "Tables/TestD.g.cs" = ''' @@ -2396,6 +2726,24 @@ namespace SpacetimeDB public readonly TestDHandle TestD; } + + public sealed class TestDCols + { + public global::SpacetimeDB.Col TestC { get; } + + public TestDCols(string tableName) + { + TestC = new global::SpacetimeDB.Col(tableName, "test_c"); + } + } + + public sealed class TestDIxCols + { + + public TestDIxCols(string tableName) + { + } + } } ''' "Tables/TestE.g.cs" = ''' @@ -2447,6 +2795,30 @@ namespace SpacetimeDB public readonly TestEHandle TestE; } + + public sealed class TestECols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + + public TestECols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class TestEIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Name { get; } + + public TestEIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "id"); + Name = new global::SpacetimeDB.IxCol(tableName, "name"); + } + } } ''' "Tables/TestF.g.cs" = ''' @@ -2476,6 +2848,24 @@ namespace SpacetimeDB public readonly TestFHandle TestF; } + + public sealed class TestFCols + { + public global::SpacetimeDB.Col Field { get; } + + public TestFCols(string tableName) + { + Field = new global::SpacetimeDB.Col(tableName, "field"); + } + } + + public sealed class TestFIxCols + { + + public TestFIxCols(string tableName) + { + } + } } ''' "Types/Baz.g.cs" = ''' diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs index f8210f1cfb5..b51e7eee1d9 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.8.0 (commit a64e8764427700df864f347417437ee0881c1226). +// This was generated using spacetimedb cli version 1.11.2 (commit 97aa69de8942102a6ea0b50dfadea3cd15e44f50). #nullable enable @@ -509,6 +509,20 @@ Action callback return this; } + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } + /// /// Subscribe to the following SQL queries. /// @@ -572,6 +586,64 @@ string[] querySqls { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table Circle() => new("circle", new CircleCols("circle"), new CircleIxCols("circle")); + public global::SpacetimeDB.Table CircleDecayTimer() => new("circle_decay_timer", new CircleDecayTimerCols("circle_decay_timer"), new CircleDecayTimerIxCols("circle_decay_timer")); + public global::SpacetimeDB.Table CircleRecombineTimer() => new("circle_recombine_timer", new CircleRecombineTimerCols("circle_recombine_timer"), new CircleRecombineTimerIxCols("circle_recombine_timer")); + public global::SpacetimeDB.Table Config() => new("config", new ConfigCols("config"), new ConfigIxCols("config")); + public global::SpacetimeDB.Table ConsumeEntityTimer() => new("consume_entity_timer", new ConsumeEntityTimerCols("consume_entity_timer"), new ConsumeEntityTimerIxCols("consume_entity_timer")); + public global::SpacetimeDB.Table Entity() => new("entity", new EntityCols("entity"), new EntityIxCols("entity")); + public global::SpacetimeDB.Table Food() => new("food", new FoodCols("food"), new FoodIxCols("food")); + public global::SpacetimeDB.Table LoggedOutCircle() => new("logged_out_circle", new LoggedOutCircleCols("logged_out_circle"), new LoggedOutCircleIxCols("logged_out_circle")); + public global::SpacetimeDB.Table LoggedOutEntity() => new("logged_out_entity", new LoggedOutEntityCols("logged_out_entity"), new LoggedOutEntityIxCols("logged_out_entity")); + public global::SpacetimeDB.Table LoggedOutPlayer() => new("logged_out_player", new LoggedOutPlayerCols("logged_out_player"), new LoggedOutPlayerIxCols("logged_out_player")); + public global::SpacetimeDB.Table MoveAllPlayersTimer() => new("move_all_players_timer", new MoveAllPlayersTimerCols("move_all_players_timer"), new MoveAllPlayersTimerIxCols("move_all_players_timer")); + public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player")); + public global::SpacetimeDB.Table SpawnFoodTimer() => new("spawn_food_timer", new SpawnFoodTimerCols("spawn_food_timer"), new SpawnFoodTimerIxCols("spawn_food_timer")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).Sql); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs index bc664fb15fc..8ae958b6c7a 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs @@ -46,4 +46,34 @@ internal CircleHandle(DbConnection conn) : base(conn) public readonly CircleHandle Circle; } + + public sealed class CircleCols + { + public global::SpacetimeDB.Col EntityId { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Direction { get; } + public global::SpacetimeDB.Col Speed { get; } + public global::SpacetimeDB.Col LastSplitTime { get; } + + public CircleCols(string tableName) + { + EntityId = new global::SpacetimeDB.Col(tableName, "entity_id"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Direction = new global::SpacetimeDB.Col(tableName, "direction"); + Speed = new global::SpacetimeDB.Col(tableName, "speed"); + LastSplitTime = new global::SpacetimeDB.Col(tableName, "last_split_time"); + } + } + + public sealed class CircleIxCols + { + public global::SpacetimeDB.IxCol EntityId { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + + public CircleIxCols(string tableName) + { + EntityId = new global::SpacetimeDB.IxCol(tableName, "entity_id"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleDecayTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleDecayTimer.g.cs index db8fe6a8373..09cd0b1053b 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleDecayTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleDecayTimer.g.cs @@ -36,4 +36,26 @@ internal CircleDecayTimerHandle(DbConnection conn) : base(conn) public readonly CircleDecayTimerHandle CircleDecayTimer; } + + public sealed class CircleDecayTimerCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + + public CircleDecayTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + } + } + + public sealed class CircleDecayTimerIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public CircleDecayTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleRecombineTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleRecombineTimer.g.cs index aa64283f0fd..5bb48e1918b 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleRecombineTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/CircleRecombineTimer.g.cs @@ -36,4 +36,28 @@ internal CircleRecombineTimerHandle(DbConnection conn) : base(conn) public readonly CircleRecombineTimerHandle CircleRecombineTimer; } + + public sealed class CircleRecombineTimerCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + public global::SpacetimeDB.Col PlayerId { get; } + + public CircleRecombineTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + } + } + + public sealed class CircleRecombineTimerIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public CircleRecombineTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs index 9c24535cf38..d6340962dee 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs @@ -36,4 +36,26 @@ internal ConfigHandle(DbConnection conn) : base(conn) public readonly ConfigHandle Config; } + + public sealed class ConfigCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col WorldSize { get; } + + public ConfigCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "id"); + WorldSize = new global::SpacetimeDB.Col(tableName, "world_size"); + } + } + + public sealed class ConfigIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + + public ConfigIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/ConsumeEntityTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/ConsumeEntityTimer.g.cs index 335a6674868..991112b45b6 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/ConsumeEntityTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/ConsumeEntityTimer.g.cs @@ -36,4 +36,30 @@ internal ConsumeEntityTimerHandle(DbConnection conn) : base(conn) public readonly ConsumeEntityTimerHandle ConsumeEntityTimer; } + + public sealed class ConsumeEntityTimerCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + public global::SpacetimeDB.Col ConsumedEntityId { get; } + public global::SpacetimeDB.Col ConsumerEntityId { get; } + + public ConsumeEntityTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + ConsumedEntityId = new global::SpacetimeDB.Col(tableName, "consumed_entity_id"); + ConsumerEntityId = new global::SpacetimeDB.Col(tableName, "consumer_entity_id"); + } + } + + public sealed class ConsumeEntityTimerIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public ConsumeEntityTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs index c2ca1e53672..e15bec6af06 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs @@ -36,4 +36,28 @@ internal EntityHandle(DbConnection conn) : base(conn) public readonly EntityHandle Entity; } + + public sealed class EntityCols + { + public global::SpacetimeDB.Col EntityId { get; } + public global::SpacetimeDB.Col Position { get; } + public global::SpacetimeDB.Col Mass { get; } + + public EntityCols(string tableName) + { + EntityId = new global::SpacetimeDB.Col(tableName, "entity_id"); + Position = new global::SpacetimeDB.Col(tableName, "position"); + Mass = new global::SpacetimeDB.Col(tableName, "mass"); + } + } + + public sealed class EntityIxCols + { + public global::SpacetimeDB.IxCol EntityId { get; } + + public EntityIxCols(string tableName) + { + EntityId = new global::SpacetimeDB.IxCol(tableName, "entity_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs index d36b66ed744..7b62f84d523 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs @@ -36,4 +36,24 @@ internal FoodHandle(DbConnection conn) : base(conn) public readonly FoodHandle Food; } + + public sealed class FoodCols + { + public global::SpacetimeDB.Col EntityId { get; } + + public FoodCols(string tableName) + { + EntityId = new global::SpacetimeDB.Col(tableName, "entity_id"); + } + } + + public sealed class FoodIxCols + { + public global::SpacetimeDB.IxCol EntityId { get; } + + public FoodIxCols(string tableName) + { + EntityId = new global::SpacetimeDB.IxCol(tableName, "entity_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs index 5d3d6fa6f60..4c56218c271 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs @@ -46,4 +46,34 @@ internal LoggedOutCircleHandle(DbConnection conn) : base(conn) public readonly LoggedOutCircleHandle LoggedOutCircle; } + + public sealed class LoggedOutCircleCols + { + public global::SpacetimeDB.Col EntityId { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Direction { get; } + public global::SpacetimeDB.Col Speed { get; } + public global::SpacetimeDB.Col LastSplitTime { get; } + + public LoggedOutCircleCols(string tableName) + { + EntityId = new global::SpacetimeDB.Col(tableName, "entity_id"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Direction = new global::SpacetimeDB.Col(tableName, "direction"); + Speed = new global::SpacetimeDB.Col(tableName, "speed"); + LastSplitTime = new global::SpacetimeDB.Col(tableName, "last_split_time"); + } + } + + public sealed class LoggedOutCircleIxCols + { + public global::SpacetimeDB.IxCol EntityId { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + + public LoggedOutCircleIxCols(string tableName) + { + EntityId = new global::SpacetimeDB.IxCol(tableName, "entity_id"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs index fe22ef0eea3..00eec8e2b12 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs @@ -36,4 +36,28 @@ internal LoggedOutEntityHandle(DbConnection conn) : base(conn) public readonly LoggedOutEntityHandle LoggedOutEntity; } + + public sealed class LoggedOutEntityCols + { + public global::SpacetimeDB.Col EntityId { get; } + public global::SpacetimeDB.Col Position { get; } + public global::SpacetimeDB.Col Mass { get; } + + public LoggedOutEntityCols(string tableName) + { + EntityId = new global::SpacetimeDB.Col(tableName, "entity_id"); + Position = new global::SpacetimeDB.Col(tableName, "position"); + Mass = new global::SpacetimeDB.Col(tableName, "mass"); + } + } + + public sealed class LoggedOutEntityIxCols + { + public global::SpacetimeDB.IxCol EntityId { get; } + + public LoggedOutEntityIxCols(string tableName) + { + EntityId = new global::SpacetimeDB.IxCol(tableName, "entity_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs index eb4590f2264..035f671053e 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs @@ -46,4 +46,30 @@ internal LoggedOutPlayerHandle(DbConnection conn) : base(conn) public readonly LoggedOutPlayerHandle LoggedOutPlayer; } + + public sealed class LoggedOutPlayerCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Name { get; } + + public LoggedOutPlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class LoggedOutPlayerIxCols + { + public global::SpacetimeDB.IxCol Identity { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + + public LoggedOutPlayerIxCols(string tableName) + { + Identity = new global::SpacetimeDB.IxCol(tableName, "identity"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/MoveAllPlayersTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/MoveAllPlayersTimer.g.cs index 105dd8e2a14..b84ba4c72ef 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/MoveAllPlayersTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/MoveAllPlayersTimer.g.cs @@ -36,4 +36,26 @@ internal MoveAllPlayersTimerHandle(DbConnection conn) : base(conn) public readonly MoveAllPlayersTimerHandle MoveAllPlayersTimer; } + + public sealed class MoveAllPlayersTimerCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + + public MoveAllPlayersTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + } + } + + public sealed class MoveAllPlayersTimerIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public MoveAllPlayersTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs index e4db13e2ad9..9250cf376e0 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs @@ -46,4 +46,30 @@ internal PlayerHandle(DbConnection conn) : base(conn) public readonly PlayerHandle Player; } + + public sealed class PlayerCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Name { get; } + + public PlayerCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "identity"); + PlayerId = new global::SpacetimeDB.Col(tableName, "player_id"); + Name = new global::SpacetimeDB.Col(tableName, "name"); + } + } + + public sealed class PlayerIxCols + { + public global::SpacetimeDB.IxCol Identity { get; } + public global::SpacetimeDB.IxCol PlayerId { get; } + + public PlayerIxCols(string tableName) + { + Identity = new global::SpacetimeDB.IxCol(tableName, "identity"); + PlayerId = new global::SpacetimeDB.IxCol(tableName, "player_id"); + } + } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/SpawnFoodTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/SpawnFoodTimer.g.cs index 98d97340c04..5a8c134a48b 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/SpawnFoodTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/SpawnFoodTimer.g.cs @@ -36,4 +36,26 @@ internal SpawnFoodTimerHandle(DbConnection conn) : base(conn) public readonly SpawnFoodTimerHandle SpawnFoodTimer; } + + public sealed class SpawnFoodTimerCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + + public SpawnFoodTimerCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + } + } + + public sealed class SpawnFoodTimerIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public SpawnFoodTimerIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index 1c646c9d78b..32a441412a8 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -60,19 +60,19 @@ void OnConnected(DbConnection conn, Identity identity, string authToken) throw err; } ) - .Subscribe([ - "SELECT * FROM example_data", - "SELECT * FROM my_player", - "SELECT * FROM my_account", - "SELECT * FROM my_account_missing", - "SELECT * FROM players_at_level_one", - "SELECT * FROM my_table", - "SELECT * FROM null_string_nonnullable", - "SELECT * FROM null_string_nullable", - "SELECT * FROM my_log", - "SELECT * FROM Admins", - "SELECT * FROM nullable_vec_view", - ]); + .AddQuery(qb => qb.From.ExampleData().Build()) + .AddQuery(qb => qb.From.MyPlayer().Build()) + .AddQuery(qb => qb.From.MyAccount().Build()) + .AddQuery(qb => qb.From.MyAccountMissing().Build()) + .AddQuery(qb => qb.From.PlayersAtLevelOne().Build()) + .AddQuery(qb => qb.From.MyTable().Build()) + .AddQuery(qb => qb.From.NullStringNonnullable().Build()) + .AddQuery(qb => qb.From.NullStringNullable().Build()) + .AddQuery(qb => qb.From.MyLog().Build()) + .AddQuery(qb => qb.From.Admins().Build()) + .AddQuery(qb => qb.From.NullableVecView().Build()) + .AddQuery(qb => qb.From.WhereTest().Where(c => c.Value.Gt(10))) + .Subscribe(); // If testing against Rust, the indexed parameter will need to be changed to: ulong indexed conn.Reducers.OnAdd += (ReducerEventContext ctx, uint id, uint indexed) => @@ -241,10 +241,24 @@ void ValidateReducerErrorDoesNotContainStackTrace(Exception exception) Debug.Assert(!exception.Message.Contains(" at "), "Reducer error message should not contain stack trace"); } +void ValidateWhereSubscription(IRemoteDbContext conn) +{ + Log.Debug("Checking typed WHERE subscription..."); + Debug.Assert(conn.Db.WhereTest != null, "conn.Db.WhereTest != null"); + + var rows = conn.Db.WhereTest.Iter().ToList(); + Debug.Assert(rows.Count == 2, $"Expected 2 where_test rows, got {rows.Count}"); + Debug.Assert(rows.All(r => r.Value > 10), "Expected all where_test.Value > 10"); + Debug.Assert(rows.Any(r => r.Id == 2 && r.Name == "high"), "Expected where_test row id=2 name=high"); + Debug.Assert(rows.Any(r => r.Id == 3 && r.Name == "alsohigh"), "Expected where_test row id=3 name=alsohigh"); +} + void OnSubscriptionApplied(SubscriptionEventContext context) { applied = true; + ValidateWhereSubscription(context); + // Do some operations that alter row state; // we will check that everything is in sync in the callbacks for these reducer calls. Log.Debug("Calling Add"); diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/InsertWhereTest.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/InsertWhereTest.g.cs new file mode 100644 index 00000000000..e420952d43b --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/InsertWhereTest.g.cs @@ -0,0 +1,85 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteReducers : RemoteBase + { + public delegate void InsertWhereTestHandler(ReducerEventContext ctx, uint id, uint value, string name); + public event InsertWhereTestHandler? OnInsertWhereTest; + + public void InsertWhereTest(uint id, uint value, string name) + { + conn.InternalCallReducer(new Reducer.InsertWhereTest(id, value, name), this.SetCallReducerFlags.InsertWhereTestFlags); + } + + public bool InvokeInsertWhereTest(ReducerEventContext ctx, Reducer.InsertWhereTest args) + { + if (OnInsertWhereTest == null) + { + if (InternalOnUnhandledReducerError != null) + { + switch (ctx.Event.Status) + { + case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; + case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; + } + } + return false; + } + OnInsertWhereTest( + ctx, + args.Id, + args.Value, + args.Name + ); + return true; + } + } + + public abstract partial class Reducer + { + [SpacetimeDB.Type] + [DataContract] + public sealed partial class InsertWhereTest : Reducer, IReducerArgs + { + [DataMember(Name = "id")] + public uint Id; + [DataMember(Name = "value")] + public uint Value; + [DataMember(Name = "name")] + public string Name; + + public InsertWhereTest( + uint Id, + uint Value, + string Name + ) + { + this.Id = Id; + this.Value = Value; + this.Name = Name; + } + + public InsertWhereTest() + { + this.Name = ""; + } + + string IReducerArgs.ReducerName => "InsertWhereTest"; + } + } + + public sealed partial class SetReducerFlags + { + internal CallReducerFlags InsertWhereTestFlags; + public void InsertWhereTest(CallReducerFlags flags) => InsertWhereTestFlags = flags; + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs index 7a6f628f1dc..4fa6cc6983c 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.11.1 (commit 8dd18f078fed83a2a3946e7bc037b17f103b8026). +// This was generated using spacetimedb cli version 1.11.3 (commit 214a46c8fac0d98c96d03017993ba757b7962124). #nullable enable @@ -45,6 +45,7 @@ public RemoteTables(DbConnection conn) AddTable(PlayerLevel = new(conn)); AddTable(PlayersAtLevelOne = new(conn)); AddTable(RetryLog = new(conn)); + AddTable(WhereTest = new(conn)); } } @@ -513,6 +514,20 @@ Action callback return this; } + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } + /// /// Subscribe to the following SQL queries. /// @@ -576,6 +591,69 @@ string[] querySqls { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table Admins() => new("Admins", new AdminsCols("Admins"), new AdminsIxCols("Admins")); + public global::SpacetimeDB.Table User() => new("User", new UserCols("User"), new UserIxCols("User")); + public global::SpacetimeDB.Table Account() => new("account", new AccountCols("account"), new AccountIxCols("account")); + public global::SpacetimeDB.Table ExampleData() => new("example_data", new ExampleDataCols("example_data"), new ExampleDataIxCols("example_data")); + public global::SpacetimeDB.Table MyAccount() => new("my_account", new MyAccountCols("my_account"), new MyAccountIxCols("my_account")); + public global::SpacetimeDB.Table MyAccountMissing() => new("my_account_missing", new MyAccountMissingCols("my_account_missing"), new MyAccountMissingIxCols("my_account_missing")); + public global::SpacetimeDB.Table MyLog() => new("my_log", new MyLogCols("my_log"), new MyLogIxCols("my_log")); + public global::SpacetimeDB.Table MyPlayer() => new("my_player", new MyPlayerCols("my_player"), new MyPlayerIxCols("my_player")); + public global::SpacetimeDB.Table MyTable() => new("my_table", new MyTableCols("my_table"), new MyTableIxCols("my_table")); + public global::SpacetimeDB.Table NullStringNonnullable() => new("null_string_nonnullable", new NullStringNonnullableCols("null_string_nonnullable"), new NullStringNonnullableIxCols("null_string_nonnullable")); + public global::SpacetimeDB.Table NullStringNullable() => new("null_string_nullable", new NullStringNullableCols("null_string_nullable"), new NullStringNullableIxCols("null_string_nullable")); + public global::SpacetimeDB.Table NullableVec() => new("nullable_vec", new NullableVecCols("nullable_vec"), new NullableVecIxCols("nullable_vec")); + public global::SpacetimeDB.Table NullableVecView() => new("nullable_vec_view", new NullableVecViewCols("nullable_vec_view"), new NullableVecViewIxCols("nullable_vec_view")); + public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player")); + public global::SpacetimeDB.Table PlayerLevel() => new("player_level", new PlayerLevelCols("player_level"), new PlayerLevelIxCols("player_level")); + public global::SpacetimeDB.Table PlayersAtLevelOne() => new("players_at_level_one", new PlayersAtLevelOneCols("players_at_level_one"), new PlayersAtLevelOneIxCols("players_at_level_one")); + public global::SpacetimeDB.Table RetryLog() => new("retry_log", new RetryLogCols("retry_log"), new RetryLogIxCols("retry_log")); + public global::SpacetimeDB.Table WhereTest() => new("where_test", new WhereTestCols("where_test"), new WhereTestIxCols("where_test")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).ToSql()); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } @@ -612,6 +690,7 @@ protected override Reducer ToReducer(TransactionUpdate update) "InsertNullStringIntoNonNullable" => BSATNHelpers.Decode(encodedArgs), "InsertNullStringIntoNullable" => BSATNHelpers.Decode(encodedArgs), "InsertResult" => BSATNHelpers.Decode(encodedArgs), + "InsertWhereTest" => BSATNHelpers.Decode(encodedArgs), "SetNullableVec" => BSATNHelpers.Decode(encodedArgs), "ThrowError" => BSATNHelpers.Decode(encodedArgs), "" => throw new SpacetimeDBEmptyReducerNameException("Reducer name is empty"), @@ -646,6 +725,7 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer) Reducer.InsertNullStringIntoNonNullable args => Reducers.InvokeInsertNullStringIntoNonNullable(eventContext, args), Reducer.InsertNullStringIntoNullable args => Reducers.InvokeInsertNullStringIntoNullable(eventContext, args), Reducer.InsertResult args => Reducers.InvokeInsertResult(eventContext, args), + Reducer.InsertWhereTest args => Reducers.InvokeInsertWhereTest(eventContext, args), Reducer.SetNullableVec args => Reducers.InvokeSetNullableVec(eventContext, args), Reducer.ThrowError args => Reducers.InvokeThrowError(eventContext, args), _ => throw new ArgumentOutOfRangeException("Reducer", $"Unknown reducer {reducer}") diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Account.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Account.g.cs index 6c6e6e38fb9..b8fa9b2b30d 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Account.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Account.g.cs @@ -46,4 +46,30 @@ internal AccountHandle(DbConnection conn) : base(conn) public readonly AccountHandle Account; } + + public sealed class AccountCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + + public AccountCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class AccountIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Identity { get; } + + public AccountIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + Identity = new global::SpacetimeDB.IxCol(tableName, "Identity"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Admins.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Admins.g.cs index ba6b2243e23..cb8c81539cf 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Admins.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Admins.g.cs @@ -24,4 +24,26 @@ internal AdminsHandle(DbConnection conn) : base(conn) public readonly AdminsHandle Admins; } + + public sealed class AdminsCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col IsAdmin { get; } + + public AdminsCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + IsAdmin = new global::SpacetimeDB.Col(tableName, "IsAdmin"); + } + } + + public sealed class AdminsIxCols + { + + public AdminsIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ExampleData.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ExampleData.g.cs index f46d19756a2..2b6f5026dd4 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ExampleData.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ExampleData.g.cs @@ -46,4 +46,28 @@ internal ExampleDataHandle(DbConnection conn) : base(conn) public readonly ExampleDataHandle ExampleData; } + + public sealed class ExampleDataCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Indexed { get; } + + public ExampleDataCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Indexed = new global::SpacetimeDB.Col(tableName, "Indexed"); + } + } + + public sealed class ExampleDataIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Indexed { get; } + + public ExampleDataIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + Indexed = new global::SpacetimeDB.IxCol(tableName, "Indexed"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccount.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccount.g.cs index c442807347a..4ce71bf5870 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccount.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccount.g.cs @@ -24,4 +24,26 @@ internal MyAccountHandle(DbConnection conn) : base(conn) public readonly MyAccountHandle MyAccount; } + + public sealed class MyAccountCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + + public MyAccountCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class MyAccountIxCols + { + + public MyAccountIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccountMissing.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccountMissing.g.cs index 71870aa5811..b23f00b02ff 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccountMissing.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyAccountMissing.g.cs @@ -24,4 +24,26 @@ internal MyAccountMissingHandle(DbConnection conn) : base(conn) public readonly MyAccountMissingHandle MyAccountMissing; } + + public sealed class MyAccountMissingCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + + public MyAccountMissingCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class MyAccountMissingIxCols + { + + public MyAccountMissingIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyLog.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyLog.g.cs index 50a9e8a2224..bb4adac6b12 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyLog.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyLog.g.cs @@ -24,4 +24,22 @@ internal MyLogHandle(DbConnection conn) : base(conn) public readonly MyLogHandle MyLog; } + + public sealed class MyLogCols + { + public global::SpacetimeDB.Col> Msg { get; } + + public MyLogCols(string tableName) + { + Msg = new global::SpacetimeDB.Col>(tableName, "msg"); + } + } + + public sealed class MyLogIxCols + { + + public MyLogIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs index 199a1dfba7f..f7d52c30fd4 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs @@ -24,4 +24,26 @@ internal MyPlayerHandle(DbConnection conn) : base(conn) public readonly MyPlayerHandle MyPlayer; } + + public sealed class MyPlayerCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + + public MyPlayerCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class MyPlayerIxCols + { + + public MyPlayerIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyTable.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyTable.g.cs index e8703966257..2180e1dc793 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyTable.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyTable.g.cs @@ -24,4 +24,22 @@ internal MyTableHandle(DbConnection conn) : base(conn) public readonly MyTableHandle MyTable; } + + public sealed class MyTableCols + { + public global::SpacetimeDB.Col Field { get; } + + public MyTableCols(string tableName) + { + Field = new global::SpacetimeDB.Col(tableName, "Field"); + } + } + + public sealed class MyTableIxCols + { + + public MyTableIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNonnullable.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNonnullable.g.cs index 954fe97086d..afa78ee5000 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNonnullable.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNonnullable.g.cs @@ -36,4 +36,26 @@ internal NullStringNonnullableHandle(DbConnection conn) : base(conn) public readonly NullStringNonnullableHandle NullStringNonnullable; } + + public sealed class NullStringNonnullableCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + + public NullStringNonnullableCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class NullStringNonnullableIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + + public NullStringNonnullableIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNullable.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNullable.g.cs index 09955d87443..6898fee34fd 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNullable.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullStringNullable.g.cs @@ -36,4 +36,26 @@ internal NullStringNullableHandle(DbConnection conn) : base(conn) public readonly NullStringNullableHandle NullStringNullable; } + + public sealed class NullStringNullableCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + + public NullStringNullableCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class NullStringNullableIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + + public NullStringNullableIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVec.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVec.g.cs index 38d6df60a8f..39936a5469f 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVec.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVec.g.cs @@ -36,4 +36,26 @@ internal NullableVecHandle(DbConnection conn) : base(conn) public readonly NullableVecHandle NullableVec; } + + public sealed class NullableVecCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Pos { get; } + + public NullableVecCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Pos = new global::SpacetimeDB.Col(tableName, "Pos"); + } + } + + public sealed class NullableVecIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + + public NullableVecIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVecView.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVecView.g.cs index 2e88cf670f9..b5638aafe78 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVecView.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/NullableVecView.g.cs @@ -24,4 +24,24 @@ internal NullableVecViewHandle(DbConnection conn) : base(conn) public readonly NullableVecViewHandle NullableVecView; } + + public sealed class NullableVecViewCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Pos { get; } + + public NullableVecViewCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Pos = new global::SpacetimeDB.Col(tableName, "Pos"); + } + } + + public sealed class NullableVecViewIxCols + { + + public NullableVecViewIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs index ce769f4e611..776a7706424 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs @@ -46,4 +46,30 @@ internal PlayerHandle(DbConnection conn) : base(conn) public readonly PlayerHandle Player; } + + public sealed class PlayerCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + + public PlayerCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class PlayerIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Identity { get; } + + public PlayerIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + Identity = new global::SpacetimeDB.IxCol(tableName, "Identity"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs index e4da71c07ca..014980367c9 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs @@ -44,4 +44,28 @@ internal PlayerLevelHandle(DbConnection conn) : base(conn) public readonly PlayerLevelHandle PlayerLevel; } + + public sealed class PlayerLevelCols + { + public global::SpacetimeDB.Col PlayerId { get; } + public global::SpacetimeDB.Col Level { get; } + + public PlayerLevelCols(string tableName) + { + PlayerId = new global::SpacetimeDB.Col(tableName, "PlayerId"); + Level = new global::SpacetimeDB.Col(tableName, "Level"); + } + } + + public sealed class PlayerLevelIxCols + { + public global::SpacetimeDB.IxCol PlayerId { get; } + public global::SpacetimeDB.IxCol Level { get; } + + public PlayerLevelIxCols(string tableName) + { + PlayerId = new global::SpacetimeDB.IxCol(tableName, "PlayerId"); + Level = new global::SpacetimeDB.IxCol(tableName, "Level"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersAtLevelOne.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersAtLevelOne.g.cs index 56b58ad0a6c..d0ae3ad6b97 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersAtLevelOne.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersAtLevelOne.g.cs @@ -24,4 +24,28 @@ internal PlayersAtLevelOneHandle(DbConnection conn) : base(conn) public readonly PlayersAtLevelOneHandle PlayersAtLevelOne; } + + public sealed class PlayersAtLevelOneCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col Level { get; } + + public PlayersAtLevelOneCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + Level = new global::SpacetimeDB.Col(tableName, "Level"); + } + } + + public sealed class PlayersAtLevelOneIxCols + { + + public PlayersAtLevelOneIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/RetryLog.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/RetryLog.g.cs index f67e0bea4b2..0821bfe9c5e 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/RetryLog.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/RetryLog.g.cs @@ -36,4 +36,26 @@ internal RetryLogHandle(DbConnection conn) : base(conn) public readonly RetryLogHandle RetryLog; } + + public sealed class RetryLogCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Attempts { get; } + + public RetryLogCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Attempts = new global::SpacetimeDB.Col(tableName, "Attempts"); + } + } + + public sealed class RetryLogIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + + public RetryLogIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/User.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/User.g.cs index b3d66a97cde..614d79c363a 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/User.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/User.g.cs @@ -46,4 +46,30 @@ internal UserHandle(DbConnection conn) : base(conn) public readonly UserHandle User; } + + public sealed class UserCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col IsAdmin { get; } + + public UserCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + IsAdmin = new global::SpacetimeDB.Col(tableName, "IsAdmin"); + } + } + + public sealed class UserIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol IsAdmin { get; } + + public UserIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + IsAdmin = new global::SpacetimeDB.IxCol(tableName, "IsAdmin"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTest.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTest.g.cs new file mode 100644 index 00000000000..7d2188ca12c --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTest.g.cs @@ -0,0 +1,75 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.BSATN; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteTables + { + public sealed class WhereTestHandle : RemoteTableHandle + { + protected override string RemoteTableName => "where_test"; + + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override uint GetKey(WhereTest row) => row.Id; + + public IdUniqueIndex(WhereTestHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + + public sealed class ValueIndex : BTreeIndexBase + { + protected override uint GetKey(WhereTest row) => row.Value; + + public ValueIndex(WhereTestHandle table) : base(table) { } + } + + public readonly ValueIndex Value; + + internal WhereTestHandle(DbConnection conn) : base(conn) + { + Id = new(this); + Value = new(this); + } + + protected override object GetPrimaryKey(WhereTest row) => row.Id; + } + + public readonly WhereTestHandle WhereTest; + } + + public sealed class WhereTestCols + { + public global::SpacetimeDB.Col Id { get; } + public global::SpacetimeDB.Col Value { get; } + public global::SpacetimeDB.Col Name { get; } + + public WhereTestCols(string tableName) + { + Id = new global::SpacetimeDB.Col(tableName, "Id"); + Value = new global::SpacetimeDB.Col(tableName, "Value"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + } + } + + public sealed class WhereTestIxCols + { + public global::SpacetimeDB.IxCol Id { get; } + public global::SpacetimeDB.IxCol Value { get; } + + public WhereTestIxCols(string tableName) + { + Id = new global::SpacetimeDB.IxCol(tableName, "Id"); + Value = new global::SpacetimeDB.IxCol(tableName, "Value"); + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/WhereTest.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/WhereTest.g.cs new file mode 100644 index 00000000000..0a5e8c3368f --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/WhereTest.g.cs @@ -0,0 +1,39 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class WhereTest + { + [DataMember(Name = "Id")] + public uint Id; + [DataMember(Name = "Value")] + public uint Value; + [DataMember(Name = "Name")] + public string Name; + + public WhereTest( + uint Id, + uint Value, + string Name + ) + { + this.Id = Id; + this.Value = Value; + this.Name = Name; + } + + public WhereTest() + { + this.Name = ""; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs index d4ff499730f..8eab9eb3391 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.11.1 (commit 26474240db517aff2f24b1b6b0aff90b0e3b9758). +// This was generated using spacetimedb cli version 1.11.3 (commit 214a46c8fac0d98c96d03017993ba757b7962124). #nullable enable @@ -500,6 +500,20 @@ Action callback return this; } + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } + /// /// Subscribe to the following SQL queries. /// @@ -563,6 +577,55 @@ string[] querySqls { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table MyTable() => new("my_table", new MyTableCols("my_table"), new MyTableIxCols("my_table")); + public global::SpacetimeDB.Table PkUuid() => new("pk_uuid", new PkUuidCols("pk_uuid"), new PkUuidIxCols("pk_uuid")); + public global::SpacetimeDB.Table ProcInsertsInto() => new("proc_inserts_into", new ProcInsertsIntoCols("proc_inserts_into"), new ProcInsertsIntoIxCols("proc_inserts_into")); + public global::SpacetimeDB.Table ScheduledProcTable() => new("scheduled_proc_table", new ScheduledProcTableCols("scheduled_proc_table"), new ScheduledProcTableIxCols("scheduled_proc_table")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).ToSql()); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/MyTable.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/MyTable.g.cs index e8703966257..a2c31ceb058 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/MyTable.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/MyTable.g.cs @@ -24,4 +24,22 @@ internal MyTableHandle(DbConnection conn) : base(conn) public readonly MyTableHandle MyTable; } + + public sealed class MyTableCols + { + public global::SpacetimeDB.Col Field { get; } + + public MyTableCols(string tableName) + { + Field = new global::SpacetimeDB.Col(tableName, "field"); + } + } + + public sealed class MyTableIxCols + { + + public MyTableIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/PkUuid.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/PkUuid.g.cs index 867b2c7e1ea..32c9d6c416c 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/PkUuid.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/PkUuid.g.cs @@ -24,4 +24,24 @@ internal PkUuidHandle(DbConnection conn) : base(conn) public readonly PkUuidHandle PkUuid; } + + public sealed class PkUuidCols + { + public global::SpacetimeDB.Col U { get; } + public global::SpacetimeDB.Col Data { get; } + + public PkUuidCols(string tableName) + { + U = new global::SpacetimeDB.Col(tableName, "u"); + Data = new global::SpacetimeDB.Col(tableName, "data"); + } + } + + public sealed class PkUuidIxCols + { + + public PkUuidIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ProcInsertsInto.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ProcInsertsInto.g.cs index f37c1a31822..e70f58c6af1 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ProcInsertsInto.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ProcInsertsInto.g.cs @@ -24,4 +24,28 @@ internal ProcInsertsIntoHandle(DbConnection conn) : base(conn) public readonly ProcInsertsIntoHandle ProcInsertsInto; } + + public sealed class ProcInsertsIntoCols + { + public global::SpacetimeDB.Col ReducerTs { get; } + public global::SpacetimeDB.Col ProcedureTs { get; } + public global::SpacetimeDB.Col X { get; } + public global::SpacetimeDB.Col Y { get; } + + public ProcInsertsIntoCols(string tableName) + { + ReducerTs = new global::SpacetimeDB.Col(tableName, "reducer_ts"); + ProcedureTs = new global::SpacetimeDB.Col(tableName, "procedure_ts"); + X = new global::SpacetimeDB.Col(tableName, "x"); + Y = new global::SpacetimeDB.Col(tableName, "y"); + } + } + + public sealed class ProcInsertsIntoIxCols + { + + public ProcInsertsIntoIxCols(string tableName) + { + } + } } diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ScheduledProcTable.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ScheduledProcTable.g.cs index 2d57c643f1c..67f120a2902 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ScheduledProcTable.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Tables/ScheduledProcTable.g.cs @@ -36,4 +36,32 @@ internal ScheduledProcTableHandle(DbConnection conn) : base(conn) public readonly ScheduledProcTableHandle ScheduledProcTable; } + + public sealed class ScheduledProcTableCols + { + public global::SpacetimeDB.Col ScheduledId { get; } + public global::SpacetimeDB.Col ScheduledAt { get; } + public global::SpacetimeDB.Col ReducerTs { get; } + public global::SpacetimeDB.Col X { get; } + public global::SpacetimeDB.Col Y { get; } + + public ScheduledProcTableCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.Col(tableName, "scheduled_id"); + ScheduledAt = new global::SpacetimeDB.Col(tableName, "scheduled_at"); + ReducerTs = new global::SpacetimeDB.Col(tableName, "reducer_ts"); + X = new global::SpacetimeDB.Col(tableName, "x"); + Y = new global::SpacetimeDB.Col(tableName, "y"); + } + } + + public sealed class ScheduledProcTableIxCols + { + public global::SpacetimeDB.IxCol ScheduledId { get; } + + public ScheduledProcTableIxCols(string tableName) + { + ScheduledId = new global::SpacetimeDB.IxCol(tableName, "scheduled_id"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs index 5e9e56dff57..d5eb058bf81 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.11.1 (commit 41eec04ea6150114247ff4ae7cbd7a68b1144bd5). +// This was generated using spacetimedb cli version 1.11.3 (commit 214a46c8fac0d98c96d03017993ba757b7962124). #nullable enable @@ -497,6 +497,20 @@ Action callback return this; } + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } + /// /// Subscribe to the following SQL queries. /// @@ -560,6 +574,52 @@ string[] querySqls { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table ExampleData() => new("ExampleData", new ExampleDataCols("ExampleData"), new ExampleDataIxCols("ExampleData")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).ToSql()); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/Tables/ExampleData.g.cs b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/Tables/ExampleData.g.cs index 0ed621d0d69..ae1c33d5b08 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/Tables/ExampleData.g.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/Tables/ExampleData.g.cs @@ -36,4 +36,58 @@ internal ExampleDataHandle(DbConnection conn) : base(conn) public readonly ExampleDataHandle ExampleData; } + + public sealed class ExampleDataCols + { + public global::SpacetimeDB.Col Primary { get; } + public global::SpacetimeDB.Col TestPass { get; } + public global::SpacetimeDB.Col DefaultString { get; } + public global::SpacetimeDB.Col DefaultBool { get; } + public global::SpacetimeDB.Col DefaultI8 { get; } + public global::SpacetimeDB.Col DefaultU8 { get; } + public global::SpacetimeDB.Col DefaultI16 { get; } + public global::SpacetimeDB.Col DefaultU16 { get; } + public global::SpacetimeDB.Col DefaultI32 { get; } + public global::SpacetimeDB.Col DefaultU32 { get; } + public global::SpacetimeDB.Col DefaultI64 { get; } + public global::SpacetimeDB.Col DefaultU64 { get; } + public global::SpacetimeDB.Col DefaultHex { get; } + public global::SpacetimeDB.Col DefaultBin { get; } + public global::SpacetimeDB.Col DefaultF32 { get; } + public global::SpacetimeDB.Col DefaultF64 { get; } + public global::SpacetimeDB.Col DefaultEnum { get; } + public global::SpacetimeDB.Col DefaultNull { get; } + + public ExampleDataCols(string tableName) + { + Primary = new global::SpacetimeDB.Col(tableName, "Primary"); + TestPass = new global::SpacetimeDB.Col(tableName, "TestPass"); + 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.Col(tableName, "DefaultNull"); + } + } + + public sealed class ExampleDataIxCols + { + public global::SpacetimeDB.IxCol Primary { get; } + + public ExampleDataIxCols(string tableName) + { + Primary = new global::SpacetimeDB.IxCol(tableName, "Primary"); + } + } } diff --git a/sdks/csharp/examples~/regression-tests/server/Lib.cs b/sdks/csharp/examples~/regression-tests/server/Lib.cs index 5adc5d46a30..bb63e977af4 100644 --- a/sdks/csharp/examples~/regression-tests/server/Lib.cs +++ b/sdks/csharp/examples~/regression-tests/server/Lib.cs @@ -44,6 +44,19 @@ public partial struct MyTable { public ReturnStruct Field; } + + [SpacetimeDB.Table(Name = "where_test", Public = true)] + public partial struct WhereTest + { + [SpacetimeDB.PrimaryKey] + public uint Id; + + [SpacetimeDB.Index.BTree] + public uint Value; + + public string Name; + } + [SpacetimeDB.Table(Name = "example_data", Public = true)] public partial struct ExampleData { @@ -277,6 +290,12 @@ public static void InsertNullStringIntoNullable(ReducerContext ctx) ctx.Db.null_string_nullable.Insert(new NullStringNullable { Name = null }); } + [SpacetimeDB.Reducer] + public static void InsertWhereTest(ReducerContext ctx, uint id, uint value, string name) + { + ctx.Db.where_test.Insert(new WhereTest { Id = id, Value = value, Name = name }); + } + [Reducer(ReducerKind.ClientConnected)] public static void ClientConnected(ReducerContext ctx) { @@ -326,6 +345,19 @@ public static void ClientConnected(ReducerContext ctx) { ctx.Db.User.Insert(new User { Id = ctx.NewUuidV7(), Name = Name, IsAdmin = IsAdmin }); } + + if (ctx.Db.where_test.Id.Find(1) is null) + { + ctx.Db.where_test.Insert(new WhereTest { Id = 1, Value = 5, Name = "low" }); + } + if (ctx.Db.where_test.Id.Find(2) is null) + { + ctx.Db.where_test.Insert(new WhereTest { Id = 2, Value = 15, Name = "high" }); + } + if (ctx.Db.where_test.Id.Find(3) is null) + { + ctx.Db.where_test.Insert(new WhereTest { Id = 3, Value = 15, Name = "alsohigh" }); + } } [SpacetimeDB.Procedure] diff --git a/sdks/csharp/src/QueryBuilder.cs b/sdks/csharp/src/QueryBuilder.cs new file mode 100644 index 00000000000..712d7db0944 --- /dev/null +++ b/sdks/csharp/src/QueryBuilder.cs @@ -0,0 +1,189 @@ +using System; +using System.Globalization; + +#nullable enable + +namespace SpacetimeDB +{ + 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 BoolExpr Not() => new($"NOT ({Sql})"); + + public override string ToString() => Sql; + } + + public readonly struct Col + { + 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(TValue value) + { + if (value is null) + { + return IsNull(); + } + + return new BoolExpr($"{RefSql} = {SqlFormat.FormatLiteral(value)}"); + } + + public BoolExpr Neq(TValue value) + { + if (value is null) + { + return IsNotNull(); + } + + return new BoolExpr($"{RefSql} <> {SqlFormat.FormatLiteral(value)}"); + } + + public BoolExpr Lt(TValue value) => new($"{RefSql} < {SqlFormat.FormatLiteral(value)}"); + public BoolExpr Lte(TValue value) => new($"{RefSql} <= {SqlFormat.FormatLiteral(value)}"); + public BoolExpr Gt(TValue value) => new($"{RefSql} > {SqlFormat.FormatLiteral(value)}"); + public BoolExpr Gte(TValue value) => new($"{RefSql} >= {SqlFormat.FormatLiteral(value)}"); + + public BoolExpr IsNull() => new($"{RefSql} IS NULL"); + public BoolExpr IsNotNull() => new($"{RefSql} IS NOT NULL"); + + public override string ToString() => RefSql; + } + + public readonly struct IxCol + { + 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(TValue value) + { + if (value is null) + { + return new BoolExpr($"{RefSql} IS NULL"); + } + + return new BoolExpr($"{RefSql} = {SqlFormat.FormatLiteral(value)}"); + } + + public BoolExpr Neq(TValue value) + { + if (value is null) + { + return new BoolExpr($"{RefSql} IS NOT NULL"); + } + + return new BoolExpr($"{RefSql} <> {SqlFormat.FormatLiteral(value)}"); + } + + public override string ToString() => RefSql; + } + + public sealed class Table + { + private readonly string tableName; + + public TCols Cols { get; } + public TIxCols IxCols { get; } + + public Table(string tableName, TCols cols, TIxCols ixCols) + { + this.tableName = tableName; + Cols = cols; + IxCols = ixCols; + } + + public string ToSql() => $"SELECT * FROM {SqlFormat.QuoteIdent(tableName)}"; + + public Query Build() => new(ToSql()); + + public Query Where(Func> predicate) => Where(predicate(Cols)); + + public Query Where(BoolExpr predicate) => new($"{ToSql()} WHERE {predicate.Sql}"); + } + + 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 FormatLiteral(object? value) + { + if (value is null) + { + return "NULL"; + } + + if (value is string s) + { + return $"'{EscapeString(s)}'"; + } + + if (value is bool b) + { + return b ? "TRUE" : "FALSE"; + } + + if (value is char c) + { + return $"'{EscapeString(c.ToString())}'"; + } + + var t = value.GetType(); + if (t.IsEnum) + { + return Convert.ToInt64(value, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture); + } + + if (value is IFormattable f) + { + return f.ToString(null, CultureInfo.InvariantCulture) ?? "NULL"; + } + + return $"'{EscapeString(value.ToString() ?? string.Empty)}'"; + } + } +} + +#nullable disable diff --git a/sdks/csharp/src/QueryBuilder.cs.meta b/sdks/csharp/src/QueryBuilder.cs.meta new file mode 100644 index 00000000000..dd7dc143a71 --- /dev/null +++ b/sdks/csharp/src/QueryBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4bf3c9475d6fc904abe3ab8398d52e97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/sdks/csharp/tests~/QueryBuilderTests.cs b/sdks/csharp/tests~/QueryBuilderTests.cs new file mode 100644 index 00000000000..db13dafbaad --- /dev/null +++ b/sdks/csharp/tests~/QueryBuilderTests.cs @@ -0,0 +1,182 @@ +namespace SpacetimeDB.Tests; + +using System; +using Xunit; + +public sealed class QueryBuilderTests +{ + private sealed class Row { } + + private enum Color : long + { + Red = 1, + } + + private sealed class RowCols + { + public Col Name { get; } + public Col Weird { get; } + public Col Age { get; } + public Col IsAdmin { get; } + public Col Color { get; } + + public RowCols(string tableName) + { + Name = new Col(tableName, "Name"); + Weird = new Col(tableName, "we\"ird"); + Age = new Col(tableName, "Age"); + IsAdmin = new Col(tableName, "IsAdmin"); + Color = new Col(tableName, "Color"); + } + } + + private sealed class RowIxCols + { + public IxCol Name { get; } + + public RowIxCols(string tableName) + { + Name = new IxCol(tableName, "Name"); + } + } + + private static Table MakeTable(string tableName) => + new(tableName, new RowCols(tableName), new RowIxCols(tableName)); + + [Fact] + public void All_QuotesTableName() + { + var table = MakeTable("My\"Table"); + Assert.Equal("SELECT * FROM \"My\"\"Table\"", table.Build().Sql); + } + + [Fact] + public void Where_Eq_String_EscapesSingleQuote() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Name.Eq("O'Reilly")).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"Name\" = 'O''Reilly'", sql); + } + + [Fact] + public void Where_Eq_Null_UsesIsNull() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Name.Eq(null!)).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"Name\" IS NULL", sql); + } + + [Fact] + public void Where_Neq_Null_UsesIsNotNull() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Name.Neq(null!)).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"Name\" IS NOT NULL", sql); + } + + [Fact] + public void Where_Gt_Int_FormatsInvariant() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Age.Gt(123)).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"Age\" > 123", sql); + } + + [Fact] + public void Where_Eq_Bool_FormatsAsTrueFalse() + { + var table = MakeTable("T"); + Assert.Equal( + "SELECT * FROM \"T\" WHERE \"T\".\"IsAdmin\" = TRUE", + table.Where(c => c.IsAdmin.Eq(true)).Sql + ); + Assert.Equal( + "SELECT * FROM \"T\" WHERE \"T\".\"IsAdmin\" = FALSE", + table.Where(c => c.IsAdmin.Eq(false)).Sql + ); + } + + [Fact] + public void Where_Eq_Enum_FormatsAsInt64() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Color.Eq(Color.Red)).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"Color\" = 1", sql); + } + + [Fact] + public void BoolExpr_AndOrNot_AddsParens() + { + var table = MakeTable("T"); + var expr = table.Cols.Age.Gt(1).And(table.Cols.Name.Neq("x")).Or(table.Cols.IsAdmin.Eq(true)).Not(); + + Assert.Equal( + "NOT (((\"T\".\"Age\" > 1) AND (\"T\".\"Name\" <> 'x')) OR (\"T\".\"IsAdmin\" = TRUE))", + expr.Sql + ); + } + + [Fact] + public void QuoteIdent_EscapesDoubleQuotesInColumnName() + { + var table = MakeTable("T"); + var sql = table.Where(c => c.Weird.Eq("x")).Sql; + Assert.Equal("SELECT * FROM \"T\" WHERE \"T\".\"we\"\"ird\" = 'x'", sql); + } + + [Fact] + public void FormatLiteral_SpacetimeDbTypes_AreQuoted() + { + var table = MakeTable("T"); + + var identity = Identity.FromHexString(new string('0', 64)); + Assert.Equal( + $"SELECT * FROM \"T\" WHERE \"T\".\"Name\" = '{identity}'", + table.Where(new Col("T", "Name").Eq(identity)).Sql + ); + + var connId = ConnectionId.FromHexString(new string('0', 31) + "1") ?? throw new InvalidOperationException(); + Assert.Equal( + $"SELECT * FROM \"T\" WHERE \"T\".\"Name\" = '{connId}'", + table.Where(new Col("T", "Name").Eq(connId)).Sql + ); + + var uuid = Uuid.Parse("00000000-0000-0000-0000-000000000000"); + Assert.Equal( + $"SELECT * FROM \"T\" WHERE \"T\".\"Name\" = '{uuid}'", + table.Where(new Col("T", "Name").Eq(uuid)).Sql + ); + + var u128 = new U128(upper: 0, lower: 5); + Assert.Equal( + $"SELECT * FROM \"T\" WHERE \"T\".\"Name\" = '{u128}'", + table.Where(new Col("T", "Name").Eq(u128)).Sql + ); + } + + [Fact] + public void IxCol_EqNeq_HasNullSemantics() + { + var table = MakeTable("T"); + + Assert.Equal( + "\"T\".\"Name\" = 'x'", + table.IxCols.Name.Eq("x").Sql + ); + + Assert.Equal( + "\"T\".\"Name\" IS NULL", + table.IxCols.Name.Eq(null!).Sql + ); + + Assert.Equal( + "\"T\".\"Name\" <> 'x'", + table.IxCols.Name.Neq("x").Sql + ); + + Assert.Equal( + "\"T\".\"Name\" IS NOT NULL", + table.IxCols.Name.Neq(null!).Sql + ); + } +} diff --git a/templates/quickstart-chat-c-sharp/module_bindings/SpacetimeDBClient.g.cs b/templates/quickstart-chat-c-sharp/module_bindings/SpacetimeDBClient.g.cs index 1e25d609f69..eaca76ff731 100644 --- a/templates/quickstart-chat-c-sharp/module_bindings/SpacetimeDBClient.g.cs +++ b/templates/quickstart-chat-c-sharp/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.8.0 (commit 6cbedef7ca453ea48801d9192779fcee7a465a10). +// This was generated using spacetimedb cli version 1.11.2 (commit 97aa69de8942102a6ea0b50dfadea3cd15e44f50). #nullable enable @@ -498,6 +498,20 @@ Action callback return this; } + /// + /// Add a typed query to this subscription. + /// + /// This is the entry point for building subscriptions without writing SQL by hand. + /// Once a typed query is added, only typed queries may follow (SQL and typed queries cannot be mixed). + /// + public TypedSubscriptionBuilder AddQuery( + Func> build + ) + { + var typed = new TypedSubscriptionBuilder(conn, Applied, Error); + return typed.AddQuery(build); + } + /// /// Subscribe to the following SQL queries. /// @@ -561,6 +575,53 @@ string[] querySqls { } } + public sealed class QueryBuilder + { + public From From { get; } = new(); + } + + public sealed class From + { + public global::SpacetimeDB.Table Message() => new("message", new MessageCols("message"), new MessageIxCols("message")); + public global::SpacetimeDB.Table User() => new("user", new UserCols("user"), new UserIxCols("user")); + } + + public sealed class TypedSubscriptionBuilder + { + private readonly IDbConnection conn; + private Action? Applied; + private Action? Error; + private readonly List querySqls = new(); + + internal TypedSubscriptionBuilder(IDbConnection conn, Action? applied, Action? error) + { + this.conn = conn; + Applied = applied; + Error = error; + } + + public TypedSubscriptionBuilder OnApplied(Action callback) + { + Applied += callback; + return this; + } + + public TypedSubscriptionBuilder OnError(Action callback) + { + Error += callback; + return this; + } + + public TypedSubscriptionBuilder AddQuery(Func> build) + { + var qb = new QueryBuilder(); + querySqls.Add(build(qb).Sql); + return this; + } + + public SubscriptionHandle Subscribe() => new(conn, Applied, Error, querySqls.ToArray()); + } + public abstract partial class Reducer { private Reducer() { } diff --git a/templates/quickstart-chat-c-sharp/module_bindings/Tables/Message.g.cs b/templates/quickstart-chat-c-sharp/module_bindings/Tables/Message.g.cs index a0f160c8578..e66dab667b9 100644 --- a/templates/quickstart-chat-c-sharp/module_bindings/Tables/Message.g.cs +++ b/templates/quickstart-chat-c-sharp/module_bindings/Tables/Message.g.cs @@ -24,4 +24,26 @@ internal MessageHandle(DbConnection conn) : base(conn) public readonly MessageHandle Message; } + + public sealed class MessageCols + { + public global::SpacetimeDB.Col Sender { get; } + public global::SpacetimeDB.Col Sent { get; } + public global::SpacetimeDB.Col Text { get; } + + public MessageCols(string tableName) + { + Sender = new global::SpacetimeDB.Col(tableName, "Sender"); + Sent = new global::SpacetimeDB.Col(tableName, "Sent"); + Text = new global::SpacetimeDB.Col(tableName, "Text"); + } + } + + public sealed class MessageIxCols + { + + public MessageIxCols(string tableName) + { + } + } } diff --git a/templates/quickstart-chat-c-sharp/module_bindings/Tables/User.g.cs b/templates/quickstart-chat-c-sharp/module_bindings/Tables/User.g.cs index eed7485e116..46e353d6178 100644 --- a/templates/quickstart-chat-c-sharp/module_bindings/Tables/User.g.cs +++ b/templates/quickstart-chat-c-sharp/module_bindings/Tables/User.g.cs @@ -36,4 +36,28 @@ internal UserHandle(DbConnection conn) : base(conn) public readonly UserHandle User; } + + public sealed class UserCols + { + public global::SpacetimeDB.Col Identity { get; } + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col Online { get; } + + public UserCols(string tableName) + { + Identity = new global::SpacetimeDB.Col(tableName, "Identity"); + Name = new global::SpacetimeDB.Col(tableName, "Name"); + Online = new global::SpacetimeDB.Col(tableName, "Online"); + } + } + + public sealed class UserIxCols + { + public global::SpacetimeDB.IxCol Identity { get; } + + public UserIxCols(string tableName) + { + Identity = new global::SpacetimeDB.IxCol(tableName, "Identity"); + } + } }