Conversation
- Added support for parsing TableRelation syntax in AlParser. - Updated DBMLColumn to include References for foreign key lookups. - Enhanced schema post-processing to handle missing referenced tables and fields. - Created documentation for schema post-processing steps and conventions. - Added tests for TableRelation parsing and handling in various scenarios.
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end support for AL TableRelation parsing and DBML relationship emission, including schema post-processing to handle missing/partial referenced tables and to keep output consistent when base tables are absent (common with tableextension-only inputs).
Changes:
- Parse
TableRelationfrom AL fields intoDBMLColumn.References(withUnknownFieldsentinel when the referenced field isn’t explicit). - Extend schema post-processing to remove empty tables, create stub tables for missing referenced targets, and add missing referenced fields to existing partial tables.
- Add/extend tests and fixtures to cover relationship parsing + post-processing behavior; add documentation for parsing and post-processing.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/DBMLWriter/SchemaPostProcessor.cs | Adds new post-processing steps for empty tables, stub table creation, and filling missing referenced fields. |
| src/AL2DBML.Parser/AlParser.cs | Extracts TableRelation into DBMLColumn.References and wires it through table parsing. |
| src/AL2DBML.Tests/Writer/WriterTests.cs | Adds writer-level tests validating stub creation, empty table removal, and unknown-field resolution. |
| src/AL2DBML.Tests/Parser/TableParserTests.cs | Adds parser tests for supported/unsupported TableRelation syntaxes (quoted/unquoted/where/conditional). |
| src/AL2DBML.Tests/Fixtures/Tables/PurchaseHeader.al | Updates fixture to include TableRelation examples used by parser tests. |
| src/AL2DBML.Tests/Fixtures/Tables/Customer.al | Updates fixture to include TableRelation example used by parser tests. |
| docs/SCHEMA_POST_PROCESSING.md | New doc describing schema post-processing pipeline and relationship-resolution rules. |
| docs/AL_PARSING.md | Documents TableRelation parsing and the UnknownField sentinel behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var explicitFields = group | ||
| .Where(f => !string.IsNullOrEmpty(f.References![1]) && !string.Equals(f.References![1], Unknown, StringComparison.OrdinalIgnoreCase)) | ||
| .DistinctBy(f => f.References![1], StringComparer.OrdinalIgnoreCase) | ||
| .Select(f => new DBMLColumn { Name = f.References![1], Type = f.Type, IsPrimaryKey = true }) | ||
| .ToList(); | ||
|
|
There was a problem hiding this comment.
CreateStubsForMissingReferencedTables marks every distinct explicitly referenced field as IsPrimaryKey = true. If multiple explicit field names reference the same missing table, this produces a composite-PK stub and also prevents ResolveUnknownFieldReferences from resolving UnknownField references to that table (since singlePkByTable only tracks tables with exactly one PK). Consider only marking the stub field as PK when there is exactly one explicit referenced field (otherwise add fields without pk, or choose a single best candidate).
| var explicitFields = group | |
| .Where(f => !string.IsNullOrEmpty(f.References![1]) && !string.Equals(f.References![1], Unknown, StringComparison.OrdinalIgnoreCase)) | |
| .DistinctBy(f => f.References![1], StringComparer.OrdinalIgnoreCase) | |
| .Select(f => new DBMLColumn { Name = f.References![1], Type = f.Type, IsPrimaryKey = true }) | |
| .ToList(); | |
| var explicitReferencedFields = group | |
| .Where(f => !string.IsNullOrEmpty(f.References![1]) && !string.Equals(f.References![1], Unknown, StringComparison.OrdinalIgnoreCase)) | |
| .DistinctBy(f => f.References![1], StringComparer.OrdinalIgnoreCase) | |
| .ToList(); | |
| var hasSingleExplicitReferencedField = explicitReferencedFields.Count == 1; | |
| var explicitFields = explicitReferencedFields | |
| .Select(f => new DBMLColumn | |
| { | |
| Name = f.References![1], | |
| Type = f.Type, | |
| IsPrimaryKey = hasSingleExplicitReferencedField | |
| }) | |
| .ToList(); |
| | `TableRelation = "Salesperson/Purchaser"."Code";` | `["Salesperson/Purchaser", "Code"]` | | ||
| | `TableRelation = "Salesperson/Purchaser".Code;` | `["Salesperson/Purchaser", "Code"]` | | ||
|
|
||
| When no field is specified, the sentinel value `"UnknownField"` is used. This is resolved at post-processing time (see ARCHITECTURE.md — Schema post-processing). |
There was a problem hiding this comment.
This link reference appears to be incorrect: ARCHITECTURE.md does not contain a “Schema post-processing” section. Consider updating the reference to SCHEMA_POST_PROCESSING.md (or adding an explicit section/link target in ARCHITECTURE.md).
| When no field is specified, the sentinel value `"UnknownField"` is used. This is resolved at post-processing time (see ARCHITECTURE.md — Schema post-processing). | |
| When no field is specified, the sentinel value `"UnknownField"` is used. This is resolved at post-processing time (see `SCHEMA_POST_PROCESSING.md`). |
| **3. CreateStubsForMissingReferencedTables** | ||
| For each `TableRelation` reference pointing to a table absent from the schema, a stub table is created: | ||
| - If the reference includes an explicit field name: the stub contains that field as PK. | ||
| - If only `UnknownField` references exist: the stub contains an `UnknownField` column as PK (typed from the referencing field). The relation is kept as-is in the output — the schema is technically incomplete but the reference is not broken. | ||
|
|
There was a problem hiding this comment.
The doc says the stub table contains “that field” as PK, but the current implementation can add multiple distinct explicitly referenced fields for the same missing table (and marks them all as PK). Consider clarifying the behavior for multiple explicit references (single vs multiple stub columns, and PK marking rules) so the documentation matches the code.
Description
Closes
closes #49
closes #50