Skip to content

Commit 8da7f2d

Browse files
dmealingclaude
andcommitted
feat(csharp): source-v2 codegen + test-fixture cutover to source.rdb / @column
Migrates Codegen + Cli test fixtures from the v1 (source.dbTable/source.dbView with @name, field @dbColumn) vocabulary to the v2 (source.rdb with @table + @kind, field @column) vocabulary. Two test fixtures with the canonical-spec violation `"@isarray": true` corrected to the reserved bare key form `"isArray": true` — the v2 canonical parser now rejects the @-prefixed form (ERR_RESERVED_ATTR), which exposed the latent bug. Threads relationship referential actions into the full-CREATE FK emitter: MetaRelationship.EffectiveOnDelete / EffectiveOnUpdate resolve the explicit attr or fall back to the per-subtype default (composition→cascade, aggregation→set-null, association→restrict; update default cascade). New ReferentialActions.Resolve(entity, target) picks the relationship by @objectref match and returns the action pair; PostgresSchema.ForeignKeyClause appends ON DELETE / ON UPDATE clauses and fails loudly on set-null over a NOT NULL FK column (mirrors Java SetNullNotNullableError). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1727b33 commit 8da7f2d

13 files changed

Lines changed: 172 additions & 33 deletions

File tree

server/csharp/MetaObjects.Cli.Tests/GenCommandTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class GenCommandTests : IDisposable
1313
private const string Metadata = """
1414
{ "metadata.root": { "package": "acme", "children": [
1515
{ "object.entity": { "name": "Subscriber", "children": [
16-
{ "source.dbTable": { "@name": "subscribers" } },
16+
{ "source.rdb": { "@table": "subscribers" } },
1717
{ "field.long": { "name": "id" } },
1818
{ "field.string": { "name": "email", "@required": true } },
1919
{ "identity.primary": { "@fields": "id" } }

server/csharp/MetaObjects.Cli.Tests/MigrateCommandTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class MigrateCommandTests : IDisposable
1313
private const string Metadata = """
1414
{ "metadata.root": { "package": "acme", "children": [
1515
{ "object.entity": { "name": "Subscriber", "children": [
16-
{ "source.dbTable": { "@name": "subscribers" } },
16+
{ "source.rdb": { "@table": "subscribers" } },
1717
{ "field.long": { "name": "id" } },
1818
{ "field.string": { "name": "email", "@required": true } },
1919
{ "identity.primary": { "@fields": "id" } }

server/csharp/MetaObjects.Codegen.Tests/DbContextCompileTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class DbContextCompileTests
4242
{ "field.string": { "name": "city", "@maxLength": 80 } }
4343
]}},
4444
{ "object.entity": { "name": "Order", "children": [
45-
{ "source.dbTable": { "@name": "orders" } },
45+
{ "source.rdb": { "@table": "orders" } },
4646
{ "field.long": { "name": "id" } },
4747
{ "field.enum": { "name": "status", "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
4848
{ "field.enum": { "name": "statuses", "isArray": true, "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
@@ -52,7 +52,7 @@ public class DbContextCompileTests
5252
{ "identity.primary": { "@fields": "id" } }
5353
]}},
5454
{ "object.entity": { "name": "ProgramSummary", "children": [
55-
{ "source.dbView": { "@name": "v_program_summary" } },
55+
{ "source.rdb": { "@kind": "view", "@table": "v_program_summary" } },
5656
{ "field.long": { "name": "id" } },
5757
{ "field.int": { "name": "weekCount" } },
5858
{ "identity.primary": { "@fields": "id" } }

server/csharp/MetaObjects.Codegen.Tests/DemoTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class DemoTests
3030
{ "object.value": { "name": "AuthorBrief", "children": [
3131
{ "field.string": { "name": "displayName" } },
3232
{ "field.int": { "name": "postCount" } },
33-
{ "field.object": { "name": "posts", "@isArray": true, "@objectRef": "PostBrief",
33+
{ "field.object": { "name": "posts", "isArray": true, "@objectRef": "PostBrief",
3434
"children": [ { "origin.collection": { "@via": "Author.posts" } } ] } }
3535
]}},
3636
{ "template.prompt": { "name": "contentStrategyPrompt",

server/csharp/MetaObjects.Codegen.Tests/EntityGeneratorTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class EntityGeneratorTests
1313
private const string Model = """
1414
{ "metadata.root": { "package": "acme", "children": [
1515
{ "object.entity": { "name": "Subscriber", "children": [
16-
{ "source.dbTable": { "@name": "subscribers" } },
16+
{ "source.rdb": { "@table": "subscribers" } },
1717
{ "field.long": { "name": "id" } },
1818
{ "field.string": { "name": "email", "@required": true, "@maxLength": 255 } },
1919
{ "field.boolean": { "name": "subscribed" } },
@@ -113,7 +113,7 @@ public void Routes_generator_emits_crud_endpoints()
113113
private const string EnumModel = """
114114
{ "metadata.root": { "package": "acme", "children": [
115115
{ "object.entity": { "name": "Order", "children": [
116-
{ "source.dbTable": { "@name": "orders" } },
116+
{ "source.rdb": { "@table": "orders" } },
117117
{ "field.long": { "name": "id" } },
118118
{ "field.enum": { "name": "status", "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
119119
{ "identity.primary": { "@fields": "id" } }
@@ -155,7 +155,7 @@ public void Enum_field_property_is_required_when_in_pk()
155155
const string model = """
156156
{ "metadata.root": { "package": "acme", "children": [
157157
{ "object.entity": { "name": "Order", "children": [
158-
{ "source.dbTable": { "@name": "orders" } },
158+
{ "source.rdb": { "@table": "orders" } },
159159
{ "field.enum": { "name": "kind", "@values": ["A", "B"] } },
160160
{ "identity.primary": { "@fields": "kind" } }
161161
]}}
@@ -176,7 +176,7 @@ public void Enum_abstract_extends_uses_super_name_as_enum_type_name()
176176
{ "metadata.root": { "package": "acme", "children": [
177177
{ "field.enum": { "name": "Status", "abstract": true, "@values": ["DRAFT", "PUBLISHED"] } },
178178
{ "object.entity": { "name": "Order", "children": [
179-
{ "source.dbTable": { "@name": "orders" } },
179+
{ "source.rdb": { "@table": "orders" } },
180180
{ "field.long": { "name": "id" } },
181181
{ "field.enum": { "name": "status", "extends": "Status" } },
182182
{ "identity.primary": { "@fields": "id" } }
@@ -242,7 +242,7 @@ public void Two_fields_extending_same_abstract_enum_emit_declaration_exactly_onc
242242
{ "metadata.root": { "package": "acme", "children": [
243243
{ "field.enum": { "name": "OrderStatus", "abstract": true, "@values": ["DRAFT", "PUBLISHED"] } },
244244
{ "object.entity": { "name": "Order", "children": [
245-
{ "source.dbTable": { "@name": "orders" } },
245+
{ "source.rdb": { "@table": "orders" } },
246246
{ "field.long": { "name": "id" } },
247247
{ "field.enum": { "name": "currentStatus", "extends": "OrderStatus" } },
248248
{ "field.enum": { "name": "previousStatus", "extends": "OrderStatus" } },

server/csharp/MetaObjects.Codegen.Tests/ObjectFieldCodegenTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class ObjectFieldCodegenTests
2020
{ "field.string": { "name": "city", "@maxLength": 80 } }
2121
]}},
2222
{ "object.entity": { "name": "Customer", "children": [
23-
{ "source.dbTable": { "@name": "customers" } },
23+
{ "source.rdb": { "@table": "customers" } },
2424
{ "field.long": { "name": "id" } },
2525
{ "field.string": { "name": "name", "@required": true } },
2626
{ "field.object": { "name": "homeAddress", "@objectRef": "Address", "@storage": "flattened" } },

server/csharp/MetaObjects.Codegen.Tests/PayloadCodegenTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class PayloadCodegenTests
2626
{ "object.value": { "name": "AuthorBrief", "children": [
2727
{ "field.string": { "name": "displayName" } },
2828
{ "field.int": { "name": "postCount" } },
29-
{ "field.object": { "name": "posts", "@isArray": true, "@objectRef": "PostBrief",
29+
{ "field.object": { "name": "posts", "isArray": true, "@objectRef": "PostBrief",
3030
"children": [ { "origin.collection": { "@via": "Author.posts" } } ] } }
3131
]}},
3232
{ "template.prompt": { "name": "contentStrategyPrompt",

server/csharp/MetaObjects.Codegen.Tests/PostgresSchemaTests.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ public class PostgresSchemaTests
1818
private const string Model = """
1919
{ "metadata.root": { "package": "acme", "children": [
2020
{ "object.entity": { "name": "Week", "children": [
21-
{ "source.dbTable": { "@name": "weeks" } },
21+
{ "source.rdb": { "@table": "weeks" } },
2222
{ "field.long": { "name": "id" } },
2323
{ "field.long": { "name": "programId" } },
2424
{ "identity.primary": { "@fields": "id" } },
2525
{ "identity.reference": { "name": "fkProgram", "@fields": "programId", "@references": "Program" } },
2626
{ "relationship.association": { "name": "program", "@objectRef": "Program", "@cardinality": "one" } }
2727
]}},
2828
{ "object.entity": { "name": "Tag", "children": [
29-
{ "source.dbTable": { "@name": "tags" } },
29+
{ "source.rdb": { "@table": "tags" } },
3030
{ "field.long": { "name": "id" } },
3131
{ "field.long": { "name": "programId" } },
3232
{ "identity.primary": { "@fields": "id" } },
@@ -37,7 +37,7 @@ public class PostgresSchemaTests
3737
{ "field.string": { "name": "city", "@maxLength": 80 } }
3838
]}},
3939
{ "object.entity": { "name": "Program", "children": [
40-
{ "source.dbTable": { "@name": "programs" } },
40+
{ "source.rdb": { "@table": "programs" } },
4141
{ "field.long": { "name": "id" } },
4242
{ "field.string": { "name": "title", "@required": true, "@maxLength": 200 } },
4343
{ "field.object": { "name": "homeAddress", "@objectRef": "Address", "@storage": "flattened" } },
@@ -47,25 +47,25 @@ public class PostgresSchemaTests
4747
{ "identity.secondary": { "name": "byTitle", "@fields": "title", "@unique": true } }
4848
]}},
4949
{ "object.value": { "name": "ProgramView", "children": [
50-
{ "source.dbView": { "@name": "v_program" } },
50+
{ "source.rdb": { "@kind": "view", "@table": "v_program" } },
5151
{ "field.long": { "name": "id", "children": [ { "origin.passthrough": { "@from": "Program.id" } } ] } },
5252
{ "field.string": { "name": "title", "children": [ { "origin.passthrough": { "@from": "Program.title" } } ] } }
5353
]}},
5454
{ "object.value": { "name": "ProgramStat", "children": [
55-
{ "source.dbView": { "@name": "v_program_stat" } },
55+
{ "source.rdb": { "@kind": "view", "@table": "v_program_stat" } },
5656
{ "field.int": { "name": "weekCount", "children": [
5757
{ "origin.aggregate": { "@agg": "count", "@of": "Week.id", "@via": "Program.weeks" } }
5858
]}}
5959
]}},
6060
{ "object.value": { "name": "WeekDetail", "children": [
61-
{ "source.dbView": { "@name": "v_week_detail" } },
61+
{ "source.rdb": { "@kind": "view", "@table": "v_week_detail" } },
6262
{ "field.long": { "name": "id", "children": [ { "origin.passthrough": { "@from": "Week.id" } } ] } },
6363
{ "field.string": { "name": "programTitle", "children": [
6464
{ "origin.passthrough": { "@from": "Program.title", "@via": "Week.program" } }
6565
]}}
6666
]}},
6767
{ "object.value": { "name": "ProgramWithWeeks", "children": [
68-
{ "source.dbView": { "@name": "v_program_weeks" } },
68+
{ "source.rdb": { "@kind": "view", "@table": "v_program_weeks" } },
6969
{ "field.long": { "name": "id", "children": [ { "origin.passthrough": { "@from": "Program.id" } } ] } },
7070
{ "field.object": { "name": "weeks", "@objectRef": "Week", "children": [
7171
{ "origin.collection": { "@via": "Program.weeks" } }
@@ -122,7 +122,7 @@ public void Composite_pk_and_unique_index_preserve_declared_field_order()
122122
const string m = """
123123
{ "metadata.root": { "package": "acme", "children": [
124124
{ "object.entity": { "name": "Link", "children": [
125-
{ "source.dbTable": { "@name": "links" } },
125+
{ "source.rdb": { "@table": "links" } },
126126
{ "field.long": { "name": "a" } },
127127
{ "field.long": { "name": "b" } },
128128
{ "identity.primary": { "@fields": ["b", "a"] } },
@@ -219,7 +219,7 @@ public void Comment_on_table_emits_from_entity_description()
219219
{ "object.entity": { "name": "Item",
220220
"@description": "A catalog item.",
221221
"children": [
222-
{ "source.dbTable": { "@name": "items" } },
222+
{ "source.rdb": { "@table": "items" } },
223223
{ "field.long": { "name": "id" } },
224224
{ "identity.primary": { "@fields": "id" } }
225225
]
@@ -238,7 +238,7 @@ public void Comment_on_column_emits_from_field_description()
238238
const string m = """
239239
{ "metadata.root": { "package": "acme", "children": [
240240
{ "object.entity": { "name": "Item", "children": [
241-
{ "source.dbTable": { "@name": "items" } },
241+
{ "source.rdb": { "@table": "items" } },
242242
{ "field.long": { "name": "id" } },
243243
{ "field.string": { "name": "sku", "@description": "Stock keeping unit." } },
244244
{ "identity.primary": { "@fields": "id" } }
@@ -258,7 +258,7 @@ public void Comment_on_column_single_quote_in_description_is_escaped()
258258
const string m = """
259259
{ "metadata.root": { "package": "acme", "children": [
260260
{ "object.entity": { "name": "Item", "children": [
261-
{ "source.dbTable": { "@name": "items" } },
261+
{ "source.rdb": { "@table": "items" } },
262262
{ "field.long": { "name": "id" } },
263263
{ "field.string": { "name": "label", "@description": "It's a label." } },
264264
{ "identity.primary": { "@fields": "id" } }
@@ -278,7 +278,7 @@ public void No_description_produces_no_comment_statements()
278278
const string m = """
279279
{ "metadata.root": { "package": "acme", "children": [
280280
{ "object.entity": { "name": "Item", "children": [
281-
{ "source.dbTable": { "@name": "items" } },
281+
{ "source.rdb": { "@table": "items" } },
282282
{ "field.long": { "name": "id" } },
283283
{ "identity.primary": { "@fields": "id" } }
284284
]
@@ -300,7 +300,7 @@ public void Notes_content_NEVER_appears_in_DDL()
300300
"@description": "Public description.",
301301
"@notes": "__DDL_INTERNAL__",
302302
"children": [
303-
{ "source.dbTable": { "@name": "items" } },
303+
{ "source.rdb": { "@table": "items" } },
304304
{ "field.long": { "name": "id" } },
305305
{ "identity.primary": { "@fields": "id" } }
306306
]

server/csharp/MetaObjects.Codegen.Tests/ProjectionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ public class ProjectionTests
1818
private const string Model = """
1919
{ "metadata.root": { "package": "acme", "children": [
2020
{ "object.entity": { "name": "ProgramSummary", "children": [
21-
{ "source.dbView": { "@name": "v_program_summary" } },
21+
{ "source.rdb": { "@kind": "view", "@table": "v_program_summary" } },
2222
{ "field.long": { "name": "id" } },
2323
{ "field.int": { "name": "weekCount" } },
2424
{ "identity.primary": { "@fields": "id" } }
2525
]}},
2626
{ "object.value": { "name": "TagCount", "children": [
27-
{ "source.dbView": { "@name": "v_tag_count" } },
27+
{ "source.rdb": { "@kind": "view", "@table": "v_tag_count" } },
2828
{ "field.string": { "name": "tag" } },
2929
{ "field.int": { "name": "count" } }
3030
]}}

server/csharp/MetaObjects.Codegen.Tests/ScalarArrayCodegenTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class ScalarArrayCodegenTests
3131
private const string ScalarArrayModel = """
3232
{ "metadata.root": { "package": "acme", "children": [
3333
{ "object.entity": { "name": "Product", "children": [
34-
{ "source.dbTable": { "@name": "products" } },
34+
{ "source.rdb": { "@table": "products" } },
3535
{ "field.long": { "name": "id" } },
3636
{ "field.string": { "name": "tags", "isArray": true } },
3737
{ "identity.primary": { "@fields": "id" } }
@@ -43,7 +43,7 @@ public class ScalarArrayCodegenTests
4343
private const string EnumArrayModel = """
4444
{ "metadata.root": { "package": "acme", "children": [
4545
{ "object.entity": { "name": "Order", "children": [
46-
{ "source.dbTable": { "@name": "orders" } },
46+
{ "source.rdb": { "@table": "orders" } },
4747
{ "field.long": { "name": "id" } },
4848
{ "field.enum": { "name": "statuses", "isArray": true, "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
4949
{ "identity.primary": { "@fields": "id" } }
@@ -56,7 +56,7 @@ public class ScalarArrayCodegenTests
5656
private const string MixedEnumModel = """
5757
{ "metadata.root": { "package": "acme", "children": [
5858
{ "object.entity": { "name": "Order", "children": [
59-
{ "source.dbTable": { "@name": "orders" } },
59+
{ "source.rdb": { "@table": "orders" } },
6060
{ "field.long": { "name": "id" } },
6161
{ "field.enum": { "name": "status", "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
6262
{ "field.enum": { "name": "statuses", "isArray": true, "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },

0 commit comments

Comments
 (0)