Skip to content

50 feat parse tables with relationship#51

Merged
OGR-67 merged 2 commits into
devfrom
50-feat-parse-tables-with-relationship
Apr 3, 2026
Merged

50 feat parse tables with relationship#51
OGR-67 merged 2 commits into
devfrom
50-feat-parse-tables-with-relationship

Conversation

@OGR-67

@OGR-67 OGR-67 commented Apr 3, 2026

Copy link
Copy Markdown
Owner
  • 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.

Description

Closes

closes #50 #49

OGR-67 and others added 2 commits March 22, 2026 20:37
- 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.
Copilot AI review requested due to automatic review settings April 3, 2026 08:14
@OGR-67 OGR-67 merged commit 65d5b79 into dev Apr 3, 2026
3 checks passed
@OGR-67 OGR-67 deleted the 50-feat-parse-tables-with-relationship branch April 3, 2026 08:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds end-to-end support for AL TableRelation parsing so relationships can be emitted in the generated DBML, including schema post-processing to keep references valid even when referenced tables/fields are missing (common with tableextension-only inputs).

Changes:

  • Parse TableRelation from field bodies and store it in DBMLColumn.References ([tableName, fieldName] with "UnknownField" sentinel when unspecified).
  • Extend schema post-processing to (1) drop empty tables, (2) create stub tables for missing referenced tables, and (3) add missing referenced fields to existing tables before resolving "UnknownField" references.
  • Add/extend tests, fixtures, and documentation for the new relationship parsing + post-processing pipeline.

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/AL2DBML.Parser/AlParser.cs Extracts TableRelation into DBMLColumn.References during field parsing.
src/DBMLWriter/SchemaPostProcessor.cs Adds pipeline steps to remove empty tables, create stubs for missing referenced tables, and add missing referenced fields.
src/AL2DBML.Tests/Parser/TableParserTests.cs Adds tests asserting TableRelation parsing across supported/unsupported syntaxes.
src/AL2DBML.Tests/Writer/WriterTests.cs Adds tests for new schema post-processing behaviors affecting output DBML.
src/AL2DBML.Tests/Fixtures/Tables/PurchaseHeader.al Adds TableRelation examples used by parser tests.
src/AL2DBML.Tests/Fixtures/Tables/Customer.al Adds TableRelation example used by parser tests.
docs/SCHEMA_POST_PROCESSING.md Documents the schema post-processing pipeline and conventions (incl. "UnknownField").
docs/AL_PARSING.md Documents TableRelation parsing behavior, supported syntaxes, and limitations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +89 to +93

var stubFields = explicitFields.Count > 0
? explicitFields
: [new DBMLColumn { Name = Unknown, Type = group.First().Type, IsPrimaryKey = true }];

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateStubsForMissingReferencedTables: when at least one explicit referenced field exists, the stub omits UnknownField entirely. If the same missing table has any UnknownField references that cannot be resolved later (e.g., the stub ends up with multiple [pk] fields so it won’t be in singlePkByTable), the generated DBML can contain ref: > <Table>.UnknownField pointing to a column that doesn’t exist in the stub table. Consider ensuring the stub always contains an UnknownField column when there are any unresolved/unknown references for that table (or avoid emitting unresolved UnknownField refs).

Suggested change
var stubFields = explicitFields.Count > 0
? explicitFields
: [new DBMLColumn { Name = Unknown, Type = group.First().Type, IsPrimaryKey = true }];
var hasUnknownReferences = group.Any(f => string.IsNullOrEmpty(f.References![1]) || string.Equals(f.References![1], Unknown, StringComparison.OrdinalIgnoreCase));
var stubFields = explicitFields.Count > 0
? explicitFields
: [new DBMLColumn { Name = Unknown, Type = group.First().Type, IsPrimaryKey = true }];
if (hasUnknownReferences && explicitFields.Count > 0
&& !stubFields.Any(f => string.Equals(f.Name, Unknown, StringComparison.OrdinalIgnoreCase)))
{
stubFields.Add(new DBMLColumn { Name = Unknown, Type = group.First().Type, IsPrimaryKey = false });
}

Copilot uses AI. Check for mistakes.
&& !string.IsNullOrEmpty(refs[0])
&& !string.IsNullOrEmpty(refs[1])
&& !string.Equals(refs[1], Unknown, StringComparison.OrdinalIgnoreCase)
&& tablesByName.ContainsKey(refs[0]));

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitRefs is a deferred LINQ query over schema.Tables.SelectMany(t => t.Fields). The subsequent loop mutates targetTable.Fields (inserts new columns), which can invalidate the underlying List<T> enumerator if the referenced table is the same table currently being enumerated (self-referencing TableRelation), causing InvalidOperationException: Collection was modified. Materialize explicitRefs (e.g., ToList()) before the loop, or collect pending field additions and apply them after enumeration.

Suggested change
&& tablesByName.ContainsKey(refs[0]));
&& tablesByName.ContainsKey(refs[0]))
.ToList();

Copilot uses AI. Check for mistakes.
Comment thread docs/AL_PARSING.md
| `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).

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This references “ARCHITECTURE.md — Schema post-processing”, but docs/ARCHITECTURE.md currently doesn’t contain a matching section/header (and the detailed write-up is in SCHEMA_POST_PROCESSING.md). Update the link/wording to point at the correct document/anchor or add the referenced section to ARCHITECTURE.md.

Suggested change
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`).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants