Skip to content

Commit

Permalink
Update TableSchema & system tables to resemble ABI V9 (#1697)
Browse files Browse the repository at this point in the history
  • Loading branch information
kazimuth authored and lcodes committed Sep 30, 2024
1 parent 80075b1 commit 794b1b8
Show file tree
Hide file tree
Showing 5 changed files with 913 additions and 1,202 deletions.
243 changes: 119 additions & 124 deletions crates/cli/src/subcommands/generate/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use std::ops::Deref;

use convert_case::{Case, Casing};
use spacetimedb_lib::sats::{AlgebraicType, AlgebraicTypeRef, ArrayType, ProductType, SumType};
use spacetimedb_lib::{ReducerDef, TableDesc};
use spacetimedb_lib::ReducerDef;
use spacetimedb_primitives::ColList;
use spacetimedb_schema::schema::TableSchema;

use super::code_indenter::CodeIndenter;
use super::{GenCtx, GenItem};
use super::{GenCtx, GenItem, TableDescHack};

fn scalar_or_string_name(b: &AlgebraicType) -> Option<&str> {
Some(match b {
Expand Down Expand Up @@ -279,17 +279,13 @@ pub fn autogen_csharp_tuple(ctx: &GenCtx, name: &str, tuple: &ProductType, names
}

#[allow(deprecated)]
pub fn autogen_csharp_table(ctx: &GenCtx, table: &TableDesc, namespace: &str) -> String {
pub fn autogen_csharp_table(ctx: &GenCtx, table: &TableDescHack, namespace: &str) -> String {
let tuple = ctx.typespace[table.data].as_product().unwrap();
autogen_csharp_product_table_common(
ctx,
csharp_typename(ctx, table.data),
tuple,
Some(
TableSchema::from_def(0.into(), table.schema.clone())
.validated()
.expect("Failed to generate table due to validation errors"),
),
Some(table.schema.clone()),
namespace,
)
}
Expand All @@ -316,8 +312,8 @@ fn autogen_csharp_product_table_common(
if let Some(schema) = &schema {
write!(
output,
" : SpacetimeDB.{parent}<{name}, {namespace}.EventContext>",
parent = if schema.pk().is_some() {
" : SpacetimeDB.{parent}<{name}, {namespace}.ReducerEvent>",
parent = if schema.primary_key.is_some() {
"DatabaseTableWithPrimaryKey"
} else {
"DatabaseTable"
Expand Down Expand Up @@ -383,7 +379,7 @@ fn autogen_csharp_product_table_common(

// If this is a table, we want to generate event accessor and indexes
if let Some(schema) = &schema {
let constraints = schema.column_constraints();
let constraints = schema.backcompat_column_constraints();
let mut unique_indexes = Vec::new();
// Declare custom index dictionaries
for col in schema.columns() {
Expand Down Expand Up @@ -450,7 +446,7 @@ fn autogen_csharp_access_funcs_for_struct(
_table_name: &str,
schema: &TableSchema,
) {
let constraints = schema.column_constraints();
let constraints = schema.backcompat_column_constraints();
for col in schema.columns() {
let is_unique = constraints[&ColList::new(col.col_pos)].has_unique();

Expand Down Expand Up @@ -520,6 +516,7 @@ fn autogen_csharp_access_funcs_for_struct(

pub fn autogen_csharp_reducer(ctx: &GenCtx, reducer: &ReducerDef, namespace: &str) -> String {
let func_name = &*reducer.name;
// let reducer_pascal_name = func_name.to_case(Case::Pascal);
let func_name_pascal_case = func_name.to_case(Case::Pascal);

let mut output = CsharpAutogen::new(namespace, &[]);
Expand All @@ -531,46 +528,101 @@ pub fn autogen_csharp_reducer(ctx: &GenCtx, reducer: &ReducerDef, namespace: &st
"public partial class {func_name_pascal_case}ArgsStruct : IReducerArgs"
);

let mut func_params: String = String::new();
let mut field_inits: String = String::new();

indented_block(&mut output, |output| {
writeln!(
output,
"ReducerType IReducerArgs.ReducerType => ReducerType.{func_name_pascal_case};"
);
writeln!(output, "string IReducerArgsBase.ReducerName => \"{func_name}\";");
writeln!(output, "bool IReducerArgs.InvokeHandler(EventContext ctx) => ctx.Reducers.Invoke{func_name_pascal_case}(ctx, this);");
writeln!(output, "bool IReducerArgs.InvokeHandler(ReducerEvent reducerEvent) => Reducer.On{func_name_pascal_case}(reducerEvent, this);");
if !reducer.args.is_empty() {
writeln!(output);
}
for arg in reducer.args.iter() {
for (arg_i, arg) in reducer.args.iter().enumerate() {
let name = arg
.name
.as_deref()
.unwrap_or_else(|| panic!("reducer args should have names: {func_name}"));
let arg_type_str = ty_fmt(ctx, &arg.algebraic_type, namespace);
let arg_name = name.to_case(Case::Camel);
let field_name = name.to_case(Case::Pascal);
let arg_type_str = ty_fmt(ctx, &arg.algebraic_type, namespace);

if arg_i != 0 {
func_params.push_str(", ");
field_inits.push_str(", ");
}
write!(output, "public {arg_type_str} {field_name}");
// Skip default initializer if it's the same as the implicit default.
if let Some(default) = default_init(ctx, &arg.algebraic_type) {
write!(output, " = {default}");
}
writeln!(output, ";");
write!(func_params, "{arg_type_str} {arg_name}").unwrap();
write!(field_inits, "{field_name} = {arg_name}").unwrap();
}
});

writeln!(output);

writeln!(output, "public static partial class Reducer");
indented_block(&mut output, |output| {
let delegate_separator = if !reducer.args.is_empty() { ", " } else { "" };
writeln!(
output,
"public delegate void {func_name_pascal_case}Handler(ReducerEvent reducerEvent{delegate_separator}{func_params});"
);
writeln!(
output,
"public static event {func_name_pascal_case}Handler? On{func_name_pascal_case}Event;"
);

writeln!(output);

writeln!(output, "public static void {func_name_pascal_case}({func_params})");
indented_block(output, |output| {
writeln!(
output,
"SpacetimeDBClient.instance.InternalCallReducer(new {func_name_pascal_case}ArgsStruct {{ {field_inits} }});"
);
});
writeln!(output);

writeln!(
output,
"public static bool On{func_name_pascal_case}(ReducerEvent reducerEvent, {func_name_pascal_case}ArgsStruct args)"
);
indented_block(output, |output| {
writeln!(output, "if (On{func_name_pascal_case}Event == null) return false;");
writeln!(output, "On{func_name_pascal_case}Event(");
// Write out arguments one per line
{
indent_scope!(output);
write!(output, "reducerEvent");
for (i, arg) in reducer.args.iter().enumerate() {
writeln!(output, ",");
let arg_name = arg
.name
.as_deref()
.map_or_else(|| format!("Arg{i}"), |name| name.to_case(Case::Pascal));
write!(output, "args.{arg_name}");
}
writeln!(output);
}
writeln!(output, ");");
writeln!(output, "return true;");
});
});
writeln!(output);

output.into_inner()
}

pub fn autogen_csharp_globals(ctx: &GenCtx, items: &[GenItem], namespace: &str) -> Vec<(String, String)> {
let mut results = Vec::new();

let tables = items
.iter()
.filter_map(|i| {
if let GenItem::Table(table) = i {
Some(table)
}
else {
None
}
});

let reducers: Vec<&ReducerDef> = items
.iter()
.filter_map(|i| {
Expand All @@ -588,111 +640,58 @@ pub fn autogen_csharp_globals(ctx: &GenCtx, items: &[GenItem], namespace: &str)

let mut output = CsharpAutogen::new(namespace, &["SpacetimeDB.ClientApi"]);

writeln!(output, "public interface IReducerArgs : IReducerArgsBase");
indented_block(&mut output, |output| {
writeln!(output, "bool InvokeHandler(EventContext ctx);");
});
writeln!(output);

writeln!(output, "public sealed class RemoteTables");
writeln!(output, "public enum ReducerType");
indented_block(&mut output, |output| {
for table in tables {
let name = &table.schema.table_name;
writeln!(output, "public readonly RemoteTableHandle<EventContext, {}> {} = new();", name, name);
writeln!(output, "None,");
for reducer_name in &reducer_names {
writeln!(output, "{reducer_name},");
}
});
writeln!(output);

writeln!(output, "public sealed class RemoteReducers : RemoteBase<DbConnection>");
writeln!(output, "public interface IReducerArgs : IReducerArgsBase");
indented_block(&mut output, |output| {
writeln!(output, "internal RemoteReducers(DbConnection conn) : base(conn) {{}}");
writeln!(output);

for reducer in &reducers {
let func_name = &*reducer.name;
let func_name_pascal_case = func_name.to_case(Case::Pascal);
let delegate_separator = if !reducer.args.is_empty() { ", " } else { "" };

let mut func_params: String = String::new();
let mut field_inits: String = String::new();

for (arg_i, arg) in reducer.args.iter().enumerate() {
if arg_i != 0 {
func_params.push_str(", ");
field_inits.push_str(", ");
}

let name = arg
.name
.as_deref()
.unwrap_or_else(|| panic!("reducer args should have names: {func_name}"));
let arg_type_str = ty_fmt(ctx, &arg.algebraic_type, namespace);
let arg_name = name.to_case(Case::Camel);
let field_name = name.to_case(Case::Pascal);

write!(func_params, "{arg_type_str} {arg_name}").unwrap();
write!(field_inits, "{field_name} = {arg_name}").unwrap();
}

writeln!(
output,
"public delegate void {func_name_pascal_case}Handler(EventContext ctx{delegate_separator}{func_params});"
);
writeln!(
output,
"public event {func_name_pascal_case}Handler? On{func_name_pascal_case};"
);
writeln!(output);


writeln!(output, "public void {func_name_pascal_case}({func_params})");
indented_block(output, |output| {
writeln!(
output,
"conn.InternalCallReducer(new {func_name_pascal_case}ArgsStruct {{ {field_inits} }});"
);
});
writeln!(output);

writeln!(
output,
"public bool Invoke{func_name_pascal_case}(EventContext ctx, {func_name_pascal_case}ArgsStruct args)"
);
indented_block(output, |output| {
writeln!(output, "if (On{func_name_pascal_case} == null) return false;");
writeln!(output, "On{func_name_pascal_case}(");
// Write out arguments one per line
{
indent_scope!(output);
write!(output, "ctx");
for (i, arg) in reducer.args.iter().enumerate() {
writeln!(output, ",");
let arg_name = arg
.name
.as_deref()
.map_or_else(|| format!("Arg{i}"), |name| name.to_case(Case::Pascal));
write!(output, "args.{arg_name}");
}
writeln!(output);
}
writeln!(output, ");");
writeln!(output, "return true;");
});
}
writeln!(output, "ReducerType ReducerType {{ get; }}");
writeln!(output, "bool InvokeHandler(ReducerEvent reducerEvent);");
});
writeln!(output);

writeln!(output, "public partial class EventContext : EventContextBase<RemoteTables, RemoteReducers>");
writeln!(output, "public partial class ReducerEvent : ReducerEventBase");
indented_block(&mut output, |output| {
writeln!(output, "public IReducerArgs? Args {{ get; }}");
writeln!(output);
writeln!(output, "public string ReducerName => Args?.ReducerName ?? \"<none>\";");
writeln!(output);
writeln!(
output,
"public EventContext(DbConnection conn, TransactionUpdate update, IReducerArgs? args) : base(conn.RemoteTables, conn.RemoteReducers, update) => Args = args;"
r#"[Obsolete("ReducerType is deprecated, please match directly on type of .Args instead.")]"#
);
writeln!(
output,
"public ReducerType Reducer => Args?.ReducerType ?? ReducerType.None;"
);
writeln!(output);
writeln!(
output,
"public ReducerEvent(IReducerArgs? args) : base() => Args = args;"
);
writeln!(
output,
"public ReducerEvent(TransactionUpdate update, IReducerArgs? args) : base(update) => Args = args;"
);
writeln!(output);
// Properties for reducer args
for reducer_name in &reducer_names {
writeln!(
output,
r#"[Obsolete("Accessors that implicitly cast `Args` are deprecated, please match `Args` against the desired type explicitly instead.")]"#
);
writeln!(
output,
"public {reducer_name}ArgsStruct {reducer_name}Args => ({reducer_name}ArgsStruct)Args!;"
);
}
writeln!(output);
// Event handlers.
writeln!(
output,
Expand All @@ -703,18 +702,11 @@ pub fn autogen_csharp_globals(ctx: &GenCtx, items: &[GenItem], namespace: &str)

writeln!(
output,
"public class DbConnection : DbConnectionBase<DbConnection, EventContext>"
"public class SpacetimeDBClient : SpacetimeDBClientBase<ReducerEvent>"
);
indented_block(&mut output, |output| {
writeln!(output, "public readonly RemoteTables RemoteTables = new();");
writeln!(output, "public readonly RemoteReducers RemoteReducers;");
writeln!(output);

writeln!(output, "public DbConnection()");
writeln!(output, "protected SpacetimeDBClient()");
indented_block(output, |output| {
writeln!(output, "RemoteReducers = new(this);");
writeln!(output);

for item in items {
if let GenItem::Table(table) = item {
writeln!(
Expand All @@ -727,9 +719,12 @@ pub fn autogen_csharp_globals(ctx: &GenCtx, items: &[GenItem], namespace: &str)
});
writeln!(output);

writeln!(output, "public static readonly SpacetimeDBClient instance = new();");
writeln!(output);

writeln!(
output,
"protected override EventContext ReducerEventFromDbEvent(TransactionUpdate update)"
"protected override ReducerEvent ReducerEventFromDbEvent(TransactionUpdate update)"
);
indented_block(output, |output| {
writeln!(output, "var encodedArgs = update.ReducerCall.Args;");
Expand All @@ -753,7 +748,7 @@ pub fn autogen_csharp_globals(ctx: &GenCtx, items: &[GenItem], namespace: &str)
);
}
writeln!(output, "}};");
writeln!(output, "return new EventContext(this, update, args);");
writeln!(output, "return new ReducerEvent(update, args);");
});
});

Expand Down
Loading

0 comments on commit 794b1b8

Please sign in to comment.