Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace SysML2.NET.Core
/// (SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/ClassKindRegistry.cs) and are
/// frozen forever: never renumbered, never deleted. The canonical identity of a metaclass
/// remains its name (the API @type value); this enum is the registry interning of it.
/// Values from 1000 upward are the Systems Modeling API Clause 7 data records, versioned
/// through the commit stream like elements.
/// </summary>
[GeneratedCode("SysML2.NET", "latest")]
public enum ClassKind : short
Expand Down Expand Up @@ -911,6 +913,21 @@ public enum ClassKind : short
/// </summary>
WhileLoopActionUsage = 175,

/// <summary>
/// The ExternalData API data record — frozen class_kind id 1001, introduced in sysml-2.0-2026-08.
/// </summary>
ExternalData = 1001,

/// <summary>
/// The ExternalRelationship API data record — frozen class_kind id 1002, introduced in sysml-2.0-2026-08.
/// </summary>
ExternalRelationship = 1002,

/// <summary>
/// The ProjectUsage API data record — frozen class_kind id 1003, introduced in sysml-2.0-2026-08.
/// </summary>
ProjectUsage = 1003,

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ public async Task Verify_that_sql_schema_is_generated()
Assert.That(generatedSchema, Does.Contain("CREATE OR REPLACE FUNCTION sysml2.validate_references_in_commit("));
Assert.That(generatedSchema, Does.Contain("ANALYZE validation_snapshot;"), "the full pass must feed the planner true snapshot cardinality");
Assert.That(generatedSchema, Does.Contain("'wrong-type'"), "the reference validation must type-check via the typed identity");
Assert.That(generatedSchema, Does.Contain("(1003, 'ProjectUsage', false, 1, NULL)"), "the API data records are seeded from the registry's reserved band");
Assert.That(generatedSchema, Does.Contain("UNIQUE (id, project_id)"), "commit must expose the FK target for the usedProject = usedProjectCommit.owningProject check");
Assert.That(generatedSchema, Does.Contain("CREATE TABLE sysml2.external_data_version"));
Assert.That(generatedSchema, Does.Contain("CREATE TABLE sysml2.external_relationship_version"));
Assert.That(generatedSchema, Does.Contain("CREATE TABLE sysml2.project_usage_version"));
Assert.That(generatedSchema, Does.Contain("FOREIGN KEY (used_project_commit, used_project) REFERENCES sysml2.commit (id, project_id)"));
Assert.That(generatedSchema, Does.Contain("CREATE OR REPLACE FUNCTION sysml2.resolve_kind_at_commit("));
Assert.That(generatedSchema, Does.Contain("CREATE OR REPLACE FUNCTION sysml2.get_project_usages_at_commit("));
Assert.That(generatedSchema, Does.Contain("CREATE OR REPLACE FUNCTION sysml2.get_external_relationships_by_element_at_commit("));
Assert.That(generatedSchema, Does.Contain("SELECT 'external_relationship_version'::text, 'external_data_end'::text,"), "the hand-written record reference columns must get validation arms");
Assert.That(generatedSchema, Does.Contain("CASE WHEN ti.class_kind >= 1000 THEN 'wrong-type' ELSE 'dangling' END"), "an Element-typed reference naming an API data record must be reported as wrong-type");
Assert.That(generatedSchema, Does.Not.Contain("CREATE TABLE sysml2.project_usage ("), "project usages are versioned through the commit stream, not stored as a flat PIM row");
Assert.That(generatedSchema, Does.Not.Contain("CREATE TABLE sysml2.property_catalog"), "the property->storage routing lives in generated C#, not in the database");
Assert.That(generatedSchema, Does.Not.Contain("CREATE TABLE sysml2.class_kind_table"), "subtype-table participation lives in generated C#, not in the database");
Assert.That(generatedSchema, Does.Not.Contain("{{"), "no unresolved handlebars expressions may survive generation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public async Task Verify_that_smoke_test_passes_against_generated_schema()
Assert.That(passNotices, Has.Some.StartWith("PASS 2a"), "the derived-state axiom assertion must be present");
Assert.That(passNotices, Has.Some.StartWith("PASS 11a"), "the multi-version registry assertion must be present");
Assert.That(passNotices, Has.Some.StartWith("PASS 13c"), "the incremental reference-validation assertion must be present");
Assert.That(passNotices, Has.Some.StartWith("PASS 16e"), "the ProjectUsageService commit-stream assertions must be present");
Assert.That(passNotices, Has.Some.StartWith("PASS 28e"), "the ExternalRelationshipService commit-stream assertions must be present");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// ------------------------------------------------------------------------------------------------
// <copyright file="SqlSchemaHelpersTestFixture.cs" company="Starion Group S.A.">
//
// Copyright (C) 2022-2026 Starion Group S.A.
// SPDX-License-Identifier: Apache-2.0
//
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace SysML2.NET.CodeGenerator.Tests.HandleBarHelpers
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using NUnit.Framework;

using SysML2.NET.CodeGenerator.Generators.UmlHandleBarsGenerators;
using SysML2.NET.CodeGenerator.HandleBarHelpers;
using SysML2.NET.CodeGenerator.Tests.Generators.UmlHandleBarsGenerators;

using uml4net.xmi.Readers;

[TestFixture]
public class SqlSchemaHelpersTestFixture
{
private HandlebarsPayload payload;

[OneTimeSetUp]
public void OneTimeSetUp()
{
this.payload = PayloadGenerator.Create(GeneratorSetupFixture.XmiReaderResult);
}

[Test]
public void VerifyAssertRegistryInSyncWithModel()
{
var modelVersions = ClassKindRegistry.ModelVersions;
var classKinds = ClassKindRegistry.ClassKinds;

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(null), Throws.TypeOf<ArgumentNullException>());
Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, null, classKinds), Throws.TypeOf<ArgumentNullException>());
Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, null), Throws.TypeOf<ArgumentNullException>());

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload), Throws.Nothing);

IReadOnlyList<ModelVersionRegistration> unreleasedVersions = [.. modelVersions, new(2, "next-release", "SysML:_unreleased")];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, unreleasedVersions, classKinds),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("fingerprint"));

IReadOnlyList<ClassKindRegistration> metaclassInRecordBand = [.. classKinds, new(2001, "StrayMetaclass", false, 1)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, metaclassInRecordBand),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("StrayMetaclass"));

IReadOnlyList<ClassKindRegistration> recordBelowBand = [.. classKinds, new(999, "StrayRecord", false, 1, null, true)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, recordBelowBand),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("reserved for API data records"));

IReadOnlyList<ClassKindRegistration> withoutPartUsage = [.. classKinds.Where(registration => registration.Name != "PartUsage")];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, withoutPartUsage),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("new(176, \"PartUsage\", false, 1),"));

IReadOnlyList<ClassKindRegistration> withRetiredMetaclass = [.. classKinds, new(176, "RetiredMetaclass", false, 1)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, withRetiredMetaclass),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("RetiredMetaclass"));

IReadOnlyList<ClassKindRegistration> withClosedMetaclass = [.. classKinds, new(176, "RetiredMetaclass", false, 1, 1)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, withClosedMetaclass), Throws.Nothing);

IReadOnlyList<ClassKindRegistration> recordNamedLikeMetaclass = [.. classKinds, new(1004, "Package", false, 1, null, true)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, recordNamedLikeMetaclass),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("also declares as metaclasses: Package"));

IReadOnlyList<ClassKindRegistration> driftedAbstractness =
[.. classKinds.Select(registration => registration.Name == "Element" ? registration with { IsAbstract = false } : registration)];

Assert.That(() => SqlSchemaHelpers.AssertRegistryInSyncWithModel(this.payload, modelVersions, driftedAbstractness),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("abstractness of Element"));
}

private sealed class PayloadGenerator : UmlHandleBarsGenerator
{
public static HandlebarsPayload Create(XmiReaderResult xmiReaderResult)
{
return CreateHandlebarsPayload(xmiReaderResult);
}

public override Task GenerateAsync(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
{
throw new NotSupportedException();
}

protected override void RegisterHelpers()
{
}

protected override void RegisterTemplates()
{
}
}
}
}
2 changes: 1 addition & 1 deletion SysML2.NET.CodeGenerator.Tests/Sql/SQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ so the scripts always run against the live generator output, never against the e

| File | Role | Consumed by |
|---|---|---|
| `Sql/schema.smoke.sql` | End-to-end functional scenario on a clean database: create, rename, tombstone, merge, checkpoint, conversion commit, reference validation, tags, project usages, stored queries, diff, deletion ordering and soft delete. Every check raises on failure and emits a `RAISE NOTICE 'PASS …'` on success. | `SqlSchemaSmokeTestFixture` (`[Category("Integration")]`). The fixture counts the `PASS` notices declared in the script and asserts the same number came back, so adding a check to the script never requires touching the fixture. |
| `Sql/schema.smoke.sql` | End-to-end functional scenario on a clean database: create, rename, tombstone, merge, checkpoint, conversion commit, reference validation, tags, project usages and external relationships (the API data records of the commit stream), stored queries, diff, deletion ordering and soft delete. Every check raises on failure and emits a `RAISE NOTICE 'PASS …'` on success. | `SqlSchemaSmokeTestFixture` (`[Category("Integration")]`). The fixture counts the `PASS` notices declared in the script and asserts the same number came back, so adding a check to the script never requires touching the fixture. |
| `Sql/schema.concurrency.setup.sql` | Seeds one project with 1,000 elements, a checkpointed base commit and 16 branches, and installs two test-only functions: `sysml2.bench_try_commit(branch_index, seed)` (one compare-and-swap commit attempt) and `sysml2.bench_read(n)` (one branch-head element read). | `SqlSchemaConcurrencyTestFixture` (`[Category("Integration")]`) runs it once after installing the schema. Also the first step of a manual `pgbench` session. |
| `Sql/schema.concurrency.verify.sql` | Asserts the invariants C1 to C5 the commit protocol promises under contention (no accidental merges, every head walks back to the seed, no lost updates, attempts equal wins plus losses, overlay rows stay on their own branch) and reports the tallies. | `SqlSchemaConcurrencyTestFixture` runs it after every scenario. Also the last step of a manual `pgbench` session. |

Expand Down
Loading
Loading