Skip to content

Commit a91be36

Browse files
Add primary keys to procedural views in C# (#5246)
# Description of Changes Adds primary keys to procedural views in C#. See for #5111 for the equivalent feature in rust and C# as well as a more detailed description. # API and ABI breaking changes None # Expected complexity level and risk 3 # Testing - [x] Equivalent tests as were added in #5111 for rust and typescript
1 parent 4c8ce71 commit a91be36

25 files changed

Lines changed: 1347 additions & 71 deletions

crates/bindings-csharp/BSATN.Codegen/Type.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,56 @@ public enum TypeKind
455455
Sum,
456456
}
457457

458+
public static class SpacetimeDbFieldDiscovery
459+
{
460+
private static readonly string[] SpacetimeDbRowAttributeNames =
461+
[
462+
"SpacetimeDB.TypeAttribute",
463+
"SpacetimeDB.TableAttribute",
464+
];
465+
466+
public static IEnumerable<IFieldSymbol> GetFieldsDeclaredInAnnotatedPartial(
467+
TypeDeclarationSyntax typeSyntax,
468+
INamedTypeSymbol type
469+
) =>
470+
typeSyntax
471+
.Members.OfType<FieldDeclarationSyntax>()
472+
.SelectMany(f => f.Declaration.Variables)
473+
.Select(v => type.GetMembers(v.Identifier.Text).OfType<IFieldSymbol>().Single())
474+
.Where(f => !f.IsStatic);
475+
476+
public static IFieldSymbol? FindSpacetimeDbField(ITypeSymbol rowType, string fieldName)
477+
{
478+
if (rowType is not INamedTypeSymbol namedRowType)
479+
{
480+
return null;
481+
}
482+
483+
foreach (var typeSyntax in GetAnnotatedPartialDeclarations(namedRowType))
484+
{
485+
var field = GetFieldsDeclaredInAnnotatedPartial(typeSyntax, namedRowType)
486+
.FirstOrDefault(field => field.Name == fieldName);
487+
if (field is not null)
488+
{
489+
return field;
490+
}
491+
}
492+
493+
return null;
494+
}
495+
496+
private static IEnumerable<TypeDeclarationSyntax> GetAnnotatedPartialDeclarations(
497+
INamedTypeSymbol type
498+
) =>
499+
type.GetAttributes()
500+
.Where(attr => SpacetimeDbRowAttributeNames.Contains(attr.AttributeClass?.ToString()))
501+
.Select(attr => attr.ApplicationSyntaxReference?.GetSyntax())
502+
.OfType<AttributeSyntax>()
503+
.Select(attr => attr.FirstAncestorOrSelf<TypeDeclarationSyntax>())
504+
.OfType<TypeDeclarationSyntax>()
505+
.Distinct();
506+
}
507+
458508
public abstract record BaseTypeDeclaration<M>
459509
where M : MemberDeclaration, IEquatable<M>
460510
{
@@ -498,11 +548,10 @@ public BaseTypeDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter
498548
//
499549
// To achieve this, we need to walk over the annotated type syntax node, collect the field names,
500550
// and look up the resolved field symbols only for those fields.
501-
var fields = typeSyntax
502-
.Members.OfType<FieldDeclarationSyntax>()
503-
.SelectMany(f => f.Declaration.Variables)
504-
.Select(v => type.GetMembers(v.Identifier.Text).OfType<IFieldSymbol>().Single())
505-
.Where(f => !f.IsStatic);
551+
var fields = SpacetimeDbFieldDiscovery.GetFieldsDeclaredInAnnotatedPartial(
552+
typeSyntax,
553+
type
554+
);
506555

507556
// Check if type implements generic `SpacetimeDB.TaggedEnum<Variants>` and, if so, extract the `Variants` type.
508557
if (type.BaseType?.OriginalDefinition.ToString() == "SpacetimeDB.TaggedEnum<Variants>")

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,42 @@ public partial struct Player
498498
public Identity Identity;
499499
}
500500

501+
[SpacetimeDB.Type]
502+
public partial struct ViewPrimaryKeyRenamedRow
503+
{
504+
public Identity RenamedIdentity;
505+
}
506+
507+
[SpacetimeDB.Type]
508+
public partial class ViewPrimaryKeyPartialRow
509+
{
510+
public Identity DeclaredIdentity;
511+
}
512+
513+
public partial class ViewPrimaryKeyPartialRow
514+
{
515+
public Identity ExtraPartialIdentity;
516+
}
517+
518+
[SpacetimeDB.Type]
519+
public partial struct NonEquatableViewPrimaryKey
520+
{
521+
public uint Value;
522+
}
523+
524+
[SpacetimeDB.Type]
525+
public partial struct NonEquatableViewPrimaryKeyRow
526+
{
527+
public NonEquatableViewPrimaryKey Identity;
528+
}
529+
501530
public struct NotSpacetimeType { }
502531

503532
public partial class Module
504533
{
534+
[SpacetimeDB.Settings]
535+
public const CaseConversionPolicy CASE_CONVERSION_POLICY = CaseConversionPolicy.SnakeCase;
536+
505537
#pragma warning disable STDB_UNSTABLE // Enable ClientVisibilityFilter
506538

507539
// Invalid: not public static readonly
@@ -592,6 +624,54 @@ ViewContext ctx
592624
return ctx.Db.TestIndexIssues.TestUnexpectedColumns.Filter(0);
593625
}
594626

627+
// Invalid: PrimaryKey must refer to a field in the returned row type.
628+
[SpacetimeDB.View(
629+
Accessor = "view_primary_key_missing_column",
630+
Public = true,
631+
PrimaryKey = "MissingIdentity"
632+
)]
633+
public static List<Player> ViewPrimaryKeyMissingColumn(ViewContext ctx)
634+
{
635+
return new List<Player>();
636+
}
637+
638+
// Invalid: PrimaryKey must use the C# source field name, not the canonicalized name.
639+
[SpacetimeDB.View(
640+
Accessor = "view_primary_key_uses_wrong_source_name",
641+
Public = true,
642+
PrimaryKey = "renamed_identity"
643+
)]
644+
public static List<ViewPrimaryKeyRenamedRow> ViewPrimaryKeyUsesWrongSourceName(ViewContext ctx)
645+
{
646+
return new List<ViewPrimaryKeyRenamedRow>();
647+
}
648+
649+
// Invalid: PrimaryKey must refer to a field declared in the SpacetimeDB.Type partial.
650+
[SpacetimeDB.View(
651+
Accessor = "view_primary_key_uses_non_bsatn_partial_field",
652+
Public = true,
653+
PrimaryKey = "ExtraPartialIdentity"
654+
)]
655+
public static List<ViewPrimaryKeyPartialRow> ViewPrimaryKeyUsesNonBsatnPartialField(
656+
ViewContext ctx
657+
)
658+
{
659+
return new List<ViewPrimaryKeyPartialRow>();
660+
}
661+
662+
// Invalid: PrimaryKey column type must match table primary-key validation.
663+
[SpacetimeDB.View(
664+
Accessor = "view_primary_key_non_equatable_column",
665+
Public = true,
666+
PrimaryKey = "Identity"
667+
)]
668+
public static List<NonEquatableViewPrimaryKeyRow> ViewPrimaryKeyNonEquatableColumn(
669+
ViewContext ctx
670+
)
671+
{
672+
return new List<NonEquatableViewPrimaryKeyRow>();
673+
}
674+
595675
// Invalid: Returns type that is not a SpacetimeType
596676
[SpacetimeDB.View(Accessor = "view_def_returns_not_a_spacetime_type", Public = true)]
597677
public static NotSpacetimeType? ViewDefReturnsNotASpacetimeType(AnonymousViewContext ctx)

0 commit comments

Comments
 (0)