diff --git a/server/csharp/MetaObjects.Codegen.Tests/ScalarArrayCodegenTests.cs b/server/csharp/MetaObjects.Codegen.Tests/ScalarArrayCodegenTests.cs index ba498bb0f..863cbea08 100644 --- a/server/csharp/MetaObjects.Codegen.Tests/ScalarArrayCodegenTests.cs +++ b/server/csharp/MetaObjects.Codegen.Tests/ScalarArrayCodegenTests.cs @@ -48,6 +48,19 @@ public class ScalarArrayCodegenTests ]}} """; + // An entity with a field.uuid isArray:true — array-ness is derived, not via + // the removed dbColumnType:uuid_array value (Phase 1 slim-and-derive). + private const string UuidArrayModel = """ + { "metadata.root": { "package": "acme", "children": [ + { "object.entity": { "name": "Widget", "children": [ + { "source.rdb": { "@table": "widgets" } }, + { "field.long": { "name": "id" } }, + { "field.uuid": { "name": "refs", "isArray": true } }, + { "identity.primary": { "@fields": "id" } } + ]}} + ]}} + """; + // An entity with both a scalar enum (no isArray) and an enum-array, so we can // assert the scalar path is unchanged (regression guard). private const string MixedEnumModel = """ @@ -188,6 +201,38 @@ public void Enum_array_field_does_not_emit_has_conversion_without_element_type_i Assert.DoesNotContain("Statuses).HasConversion()", dbCtx); } + // ------------------------------------------------------------------------- + // field.uuid isArray — Phase 1 slim-and-derive: array-ness via isArray:true, + // not the removed dbColumnType:uuid_array value. + // ScalarFor("uuid") == "Guid", so the existing scalar-array path handles this. + // ------------------------------------------------------------------------- + + [Fact] + public void Uuid_array_field_emits_ICollection_Guid_property() + { + var ctx = Ctx(Load(UuidArrayModel)); + var src = Assert.Single(new EntityGenerator().Generate(ctx)).Content; + + // field.uuid isArray:true → ICollection with List initializer. + Assert.Contains("public ICollection Refs { get; set; } = new List();", src); + // Guard: the scalar form must NOT appear. + Assert.DoesNotContain("public Guid? Refs", src); + Assert.DoesNotContain("public Guid Refs", src); + } + + [Fact] + public void Uuid_array_field_emits_PrimitiveCollection_in_dbcontext() + { + var ctx = Ctx(Load(UuidArrayModel)); + var dbCtx = Assert.Single(new DbContextGenerator().Generate(ctx)).Content; + + // EF Core 8 primitive collection API for the uuid array (derived, not via + // the removed dbColumnType:uuid_array value). + Assert.Contains( + "modelBuilder.Entity().PrimitiveCollection(x => x.Refs);", + dbCtx); + } + // ------------------------------------------------------------------------- // Compile check — generated scalar-array entity must be valid C# // ------------------------------------------------------------------------- @@ -208,6 +253,14 @@ public void Generated_enum_array_entity_compiles() AssertCompiles(src, "enumarray"); } + [Fact] + public void Generated_uuid_array_entity_compiles() + { + var ctx = Ctx(Load(UuidArrayModel)); + var src = Assert.Single(new EntityGenerator().Generate(ctx)).Content; + AssertCompiles(src, "uuidarray"); + } + // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- diff --git a/server/csharp/MetaObjects.Conformance.Tests/R6Plan2LoaderTests.cs b/server/csharp/MetaObjects.Conformance.Tests/R6Plan2LoaderTests.cs index 51a63f1ae..0d8077ec7 100644 --- a/server/csharp/MetaObjects.Conformance.Tests/R6Plan2LoaderTests.cs +++ b/server/csharp/MetaObjects.Conformance.Tests/R6Plan2LoaderTests.cs @@ -134,6 +134,26 @@ public void DbColumnType_unknown_value_is_ERR_BAD_ATTR_VALUE() Assert.Contains("allowed", err.Message); } + // ------------------------------------------------------------------------ + // Phase 1 slim-and-derive: uuid_array + text_array are removed values. + // They now fall through Rule 1 (unknown value) → ERR_BAD_ATTR_VALUE. + // Array-ness is derived from field.uuid/field.string isArray:true instead. + // ------------------------------------------------------------------------ + + [Theory] + [InlineData("uuid_array")] + [InlineData("text_array")] + public void DbColumnType_removed_array_values_are_ERR_BAD_ATTR_VALUE(string removedValue) + { + var json = Pairing(FIELD_SUBTYPE_STRING, removedValue); + var res = LoadJson(json); + var err = res.Errors.FirstOrDefault(e => + e.Code == ErrorCode.ERR_BAD_ATTR_VALUE && e.Message.Contains("dbColumnType")); + Assert.NotNull(err); + Assert.Contains(removedValue, err!.Message); + Assert.Contains("allowed", err.Message); + } + // ------------------------------------------------------------------------ // Plan 2b — own-only: an INHERITED @dbColumnType is not re-validated against // the inheriting field's subtype (mirrors the field.enum own-only policy). diff --git a/server/csharp/MetaObjects/Loader/ValidationPasses.cs b/server/csharp/MetaObjects/Loader/ValidationPasses.cs index ca0bc0213..d4cba2519 100644 --- a/server/csharp/MetaObjects/Loader/ValidationPasses.cs +++ b/server/csharp/MetaObjects/Loader/ValidationPasses.cs @@ -1854,8 +1854,7 @@ private static void WalkDbColumnType(MetaData node, List errors) // Rule 2: legal (subtype × value) pairing. var requiredSubType = value switch { - DB_COLUMN_TYPE_UUID or DB_COLUMN_TYPE_JSONB - or DB_COLUMN_TYPE_UUID_ARRAY or DB_COLUMN_TYPE_TEXT_ARRAY => FIELD_SUBTYPE_STRING, + DB_COLUMN_TYPE_UUID or DB_COLUMN_TYPE_JSONB => FIELD_SUBTYPE_STRING, DB_COLUMN_TYPE_TIMESTAMP_TZ => FIELD_SUBTYPE_TIMESTAMP, _ => null, // unreachable (Rule 1) }; diff --git a/server/csharp/MetaObjects/Persistence/Db/DbConstants.cs b/server/csharp/MetaObjects/Persistence/Db/DbConstants.cs index 6148bd99b..368e17fe1 100644 --- a/server/csharp/MetaObjects/Persistence/Db/DbConstants.cs +++ b/server/csharp/MetaObjects/Persistence/Db/DbConstants.cs @@ -51,6 +51,8 @@ public static class DbConstants /// Physical DB column-type override on a field (@dbColumnType). Closed value /// set: / / /// . + /// Array-ness is derived from isArray: true on the field, not from a separate + /// uuid_array/text_array value (removed in Phase 1 of the slim-and-derive pass). /// public const string FIELD_ATTR_DB_COLUMN_TYPE = "dbColumnType"; @@ -60,10 +62,6 @@ public static class DbConstants public const string DB_COLUMN_TYPE_JSONB = "jsonb"; /// @dbColumnType: timestamp_with_tztimestamp with time zone column (legal on field.timestamp). public const string DB_COLUMN_TYPE_TIMESTAMP_TZ = "timestamp_with_tz"; - /// @dbColumnType: uuid_array — native Postgres uuid[] array column (legal on field.string). - public const string DB_COLUMN_TYPE_UUID_ARRAY = "uuid_array"; - /// @dbColumnType: text_array — native Postgres text[] array column (legal on field.string). - public const string DB_COLUMN_TYPE_TEXT_ARRAY = "text_array"; /// The closed set of legal @dbColumnType values. public static readonly IReadOnlyList VALID_DB_COLUMN_TYPES = new[] @@ -71,7 +69,5 @@ public static class DbConstants DB_COLUMN_TYPE_UUID, DB_COLUMN_TYPE_JSONB, DB_COLUMN_TYPE_TIMESTAMP_TZ, - DB_COLUMN_TYPE_UUID_ARRAY, - DB_COLUMN_TYPE_TEXT_ARRAY, }; }