From 03db0d4e30c6307b483228f29492c3061b6bf957 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 00:42:39 +0000 Subject: [PATCH 1/5] Bump coverlet.collector from 6.0.0 to 6.0.2 Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.0 to 6.0.2. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.0...v6.0.2) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- DbSeeder.Tests/DbSeeder.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DbSeeder.Tests/DbSeeder.Tests.csproj b/DbSeeder.Tests/DbSeeder.Tests.csproj index 085bddd..ae2545b 100644 --- a/DbSeeder.Tests/DbSeeder.Tests.csproj +++ b/DbSeeder.Tests/DbSeeder.Tests.csproj @@ -10,7 +10,7 @@ - + From aa22fbc1d73e2d1bd6e2fd2f597f96db307ddfe7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 00:43:01 +0000 Subject: [PATCH 2/5] Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.9.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- DbSeeder.Tests/DbSeeder.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DbSeeder.Tests/DbSeeder.Tests.csproj b/DbSeeder.Tests/DbSeeder.Tests.csproj index 085bddd..5c59226 100644 --- a/DbSeeder.Tests/DbSeeder.Tests.csproj +++ b/DbSeeder.Tests/DbSeeder.Tests.csproj @@ -11,7 +11,7 @@ - + From ba4491cc335dd534b2d34b332e7e5f6af3cabfd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 20:55:16 +0000 Subject: [PATCH 3/5] Bump xunit from 2.5.3 to 2.7.1 Bumps [xunit](https://github.com/xunit/xunit) from 2.5.3 to 2.7.1. - [Commits](https://github.com/xunit/xunit/compare/2.5.3...2.7.1) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- DbSeeder.Tests/DbSeeder.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DbSeeder.Tests/DbSeeder.Tests.csproj b/DbSeeder.Tests/DbSeeder.Tests.csproj index 5c59226..b48125e 100644 --- a/DbSeeder.Tests/DbSeeder.Tests.csproj +++ b/DbSeeder.Tests/DbSeeder.Tests.csproj @@ -12,7 +12,7 @@ - + From f1d1193ffd052daf46cf1cd90c7293e20e10531d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 20:56:09 +0000 Subject: [PATCH 4/5] Bump xunit.runner.visualstudio from 2.5.3 to 2.5.8 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.5.3 to 2.5.8. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/compare/2.5.3...2.5.8) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- DbSeeder.Tests/DbSeeder.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DbSeeder.Tests/DbSeeder.Tests.csproj b/DbSeeder.Tests/DbSeeder.Tests.csproj index c2aa7f2..f69633f 100644 --- a/DbSeeder.Tests/DbSeeder.Tests.csproj +++ b/DbSeeder.Tests/DbSeeder.Tests.csproj @@ -13,7 +13,7 @@ - + From 8d1994dd5cd7242cb69aff5843a485bda86e944e Mon Sep 17 00:00:00 2001 From: Bardin08 Date: Fri, 26 Apr 2024 00:54:14 +0300 Subject: [PATCH 5/5] Implement `FOREIGN KEY` parsing --- DbSeeder/DbSeeder.csproj | 4 + DbSeeder/Program.cs | 4 +- DbSeeder/Schema/SqlSchemaBuilder.cs | 3 + DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs | 158 +++++++++++++++--- .../SyntaxTree/ForeignKeyConstraintBuilder.cs | 119 +++++++++++++ .../SyntaxTree/SyntaxTreeNodeType.cs | 10 +- .../Internal/TreeStructureValidator.cs | 15 +- 7 files changed, 285 insertions(+), 28 deletions(-) create mode 100644 DbSeeder/SqlParser/SyntaxTree/ForeignKeyConstraintBuilder.cs diff --git a/DbSeeder/DbSeeder.csproj b/DbSeeder/DbSeeder.csproj index 2f4fc77..8f67ab3 100644 --- a/DbSeeder/DbSeeder.csproj +++ b/DbSeeder/DbSeeder.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/DbSeeder/Program.cs b/DbSeeder/Program.cs index 855ea14..43d28e0 100644 --- a/DbSeeder/Program.cs +++ b/DbSeeder/Program.cs @@ -13,10 +13,10 @@ private static void Main() """ CREATE TABLE users ( - id INT AUTO_INCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(122) NOT NULL UNIQUE, profile_id INT, - PRIMARY KEY (id) + FOREIGN KEY (profile_id) REFERENCES profiles(id) ); CREATE TABLE profiles diff --git a/DbSeeder/Schema/SqlSchemaBuilder.cs b/DbSeeder/Schema/SqlSchemaBuilder.cs index 470c867..03effa8 100644 --- a/DbSeeder/Schema/SqlSchemaBuilder.cs +++ b/DbSeeder/Schema/SqlSchemaBuilder.cs @@ -96,6 +96,9 @@ private Column GetColumn(SyntaxTreeNode columnNode) case SyntaxTreeNodeType.ColumnConstraint: constraints.Add(node.Value); break; + case SyntaxTreeNodeType.ForeignKeyDefinition: + // TODO: Implement processing FK constraint correctly + break; } } diff --git a/DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs b/DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs index b9575a6..f2ccfbe 100644 --- a/DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs +++ b/DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs @@ -2,23 +2,54 @@ namespace DbSeeder.SqlParser.SyntaxTree; public class AstBuilder(List tokens) { + private ForeignKeyConstraintBuilder? _fkBuilder; + + + private readonly SyntaxTreeNode _root = new(SyntaxTreeNodeType.Root, "SQL_Script", null!); + + private SyntaxTreeNode? _localRoot; + public SyntaxTreeNode BuildSyntaxTree() { - var root = new SyntaxTreeNode(SyntaxTreeNodeType.Root, "SQL_Script", null!); - var localRoot = root; + _localRoot = _root; + _fkBuilder = new ForeignKeyConstraintBuilder(); foreach (var token in tokens) { - HandleToken(token, root, ref localRoot); + HandleToken(token, ref _localRoot); } - return root; + return _root; } - private void HandleToken(SqlToken token, SyntaxTreeNode root, ref SyntaxTreeNode? localRoot) + private void HandleToken(SqlToken token, ref SyntaxTreeNode? localRoot) { ArgumentNullException.ThrowIfNull(token); + if (localRoot.Type is SyntaxTreeNodeType.KeyDefinition) + { + HandleKeyConstraint(token, ref localRoot); + return; + } + else if (localRoot.Type is SyntaxTreeNodeType.ForeignKeyDefinition) + { + _fkBuilder!.Handle(token, ref localRoot); + + if (_fkBuilder.IsComplete) + { + // we have to move FK from the cols' definition to specific col which owns this FK + var fkDefinition = _localRoot!.Children.Single(n => n!.Type is SyntaxTreeNodeType.ForeignKeyDefinition); + var fkOwner = fkDefinition!.Children.First(); // According to the FK subtree struct owner is the first child of the FK root + + var fkOwnerColRoot = localRoot.Children.Single(c => + c!.Value.Equals(fkOwner!.Value, StringComparison.OrdinalIgnoreCase)); + + localRoot.Children.Remove(fkDefinition); + fkOwnerColRoot!.Children.Add(fkDefinition); + } + return; + } + switch (token.Type) { case SqlTokenType.Keyword: @@ -31,7 +62,7 @@ private void HandleToken(SqlToken token, SyntaxTreeNode root, ref SyntaxTreeNode HandleNumber(token, ref localRoot!); break; case SqlTokenType.Punctuation: - HandlePunctuation(token, root, ref localRoot!); + HandlePunctuation(token, ref localRoot!); break; case SqlTokenType.StringLiteral: case SqlTokenType.Operator: @@ -90,8 +121,10 @@ private void HandleIdentifier(SqlToken token, ref SyntaxTreeNode localRoot) } else { - BuildConstraint(token, ref localRoot); + // Here can be another constraints created with a CONSTRAINT keyword, but they're not supported yet + BuildKeyConstraint(token, ref localRoot); } + break; case SyntaxTreeNodeType.Column: AddNode(SyntaxTreeNodeType.ColumnDataType, token.Value, ref localRoot!); @@ -102,11 +135,58 @@ private void HandleIdentifier(SqlToken token, ref SyntaxTreeNode localRoot) case SyntaxTreeNodeType.ColumnConstraint: HandleColumnConstraintIdentifier(token, ref localRoot); break; + case SyntaxTreeNodeType.KeyDefinition: + HandleKeyConstraint(token, ref localRoot); + break; + case SyntaxTreeNodeType.ForeignKeyDefinition: + HandleForeignKeyToken(token); + break; + case SyntaxTreeNodeType.PrimaryKeyDefinition: + HandlePrimaryKeyToken(token, ref localRoot); + break; default: throw new NotImplementedException($"Unhandled localRoot type for identifier: {localRoot.Type}"); } } + private void HandlePrimaryKeyToken(SqlToken token, ref SyntaxTreeNode localRoot) + { + throw new NotImplementedException(); + } + + private void HandleForeignKeyToken(SqlToken token) + { + _fkBuilder!.Handle(token, ref _localRoot!); + } + + private void HandleKeyConstraint(SqlToken token, ref SyntaxTreeNode localRoot) + { + if (localRoot.Value.Contains("FOREIGN", StringComparison.OrdinalIgnoreCase)) + { + _fkBuilder!.Handle(token, ref localRoot); + } + + // if (token.Value.Equals("KEY", StringComparison.OrdinalIgnoreCase) && + // !localRoot.Value.Contains("KEY", StringComparison.OrdinalIgnoreCase)) + // { + // var isPrimaryKey = localRoot.Value.Contains("PRIMARY", StringComparison.OrdinalIgnoreCase); + // var nodeType = isPrimaryKey + // ? SyntaxTreeNodeType.PrimaryKeyDefinition + // : SyntaxTreeNodeType.ForeignKeyDefinition; + // var nodeValue = string.Concat(localRoot.Value.Trim(), " ", token.Value); + // var newNode = new SyntaxTreeNode(nodeType, nodeValue, localRoot.Parent); + // newNode.Children.AddRange(localRoot.Children); + // + // var tempNode = localRoot; + // localRoot = newNode; + // + // // Update parent with a new sub node + // var parent = localRoot.Parent!; + // parent.Children.Remove(tempNode); + // parent.Children.Add(newNode); + // } + } + private bool IsConstraint(SqlToken token) { var isConstraintKeyWord = token.Value.Equals("constraint", StringComparison.OrdinalIgnoreCase); @@ -116,14 +196,10 @@ private bool IsConstraint(SqlToken token) return isConstraintKeyWord || isIdentifier; } - private void BuildConstraint(SqlToken token, ref SyntaxTreeNode localRoot) + private void BuildKeyConstraint(SqlToken token, ref SyntaxTreeNode localRoot) { - Console.WriteLine($"Detected Constraint: {token.Value}"); - - AddNode(SyntaxTreeNodeType.ColumnConstraint, token.Value, ref localRoot!); - - - + // We temporarily add this constraint to the cols' root + AddNode(SyntaxTreeNodeType.KeyDefinition, token.Value, ref localRoot!); } private void HandleColumnConstraintIdentifier(SqlToken token, ref SyntaxTreeNode localRoot) @@ -137,14 +213,34 @@ private void HandleColumnConstraintIdentifier(SqlToken token, ref SyntaxTreeNode else if (localRoot.Parent?.Type is SyntaxTreeNodeType.TableColumns) { // PRIMARY KEY (id). If we're here, we are looking at the col name. - var colsContainerNode = localRoot.Parent; - colsContainerNode.Children.Remove(localRoot); - var constraintOwner = colsContainerNode.Children.First(x => x!.Value.Equals(token.Value))!; + if (localRoot.Value.Equals("FOREIGN KEY", StringComparison.OrdinalIgnoreCase)) + { + // This is a FOREIGN KEY CONSTRAINT, so we have to handle it appropriately. + // FOREIGN KEY definition statement looks like: + // + // FOREIGN KEY id REFERENCES profiles(user_id) + + if (token.Type is SqlTokenType.Identifier) + { + localRoot.AddChild(new SyntaxTreeNode(SyntaxTreeNodeType.KeyColumnIdentifier, token.Value, + localRoot)); + } + + Console.WriteLine("We are parsing FK constraint"); + } + + else if (localRoot.Value.Equals("PRIMARY KEY", StringComparison.OrdinalIgnoreCase)) + { + var colsContainerNode = localRoot.Parent; + + colsContainerNode.Children.Remove(localRoot); + var constraintOwner = colsContainerNode.Children.First(x => x!.Value.Equals(token.Value))!; - var temp = new SyntaxTreeNode(localRoot.Type, localRoot.Value, constraintOwner); - localRoot = temp; - constraintOwner.Children.Add(temp); + var temp = new SyntaxTreeNode(localRoot.Type, localRoot.Value, constraintOwner); + localRoot = temp; + constraintOwner.Children.Add(temp); + } } else { @@ -174,21 +270,22 @@ private void AddColumnConstraintNode(SqlToken token, ref SyntaxTreeNode localRoo #region Punctuation - private void HandlePunctuation(SqlToken token, SyntaxTreeNode root, ref SyntaxTreeNode localRoot) + private void HandlePunctuation(SqlToken token, ref SyntaxTreeNode localRoot) { - ArgumentNullException.ThrowIfNull(root); + ArgumentNullException.ThrowIfNull(_root); ArgumentNullException.ThrowIfNull(localRoot); var isStatementClosingBracket = token.Value.Equals(")") && localRoot.Type is SyntaxTreeNodeType.TableColumns; if (token.Value.Equals(";") || isStatementClosingBracket) { // Reset to root on statement end - localRoot = root; + localRoot = _root; } else { switch (localRoot.Type) { + // TODO: Add Key Token Type Constraint case SyntaxTreeNodeType.TableRoot: HandleTableRootPunctuation(ref localRoot); break; @@ -201,6 +298,11 @@ private void HandlePunctuation(SqlToken token, SyntaxTreeNode root, ref SyntaxTr // Move back to the direct parent (likely ColumnDataType) localRoot = localRoot.Parent!; break; + case SyntaxTreeNodeType.KeyDefinition: + case SyntaxTreeNodeType.ForeignKeyDefinition: + case SyntaxTreeNodeType.PrimaryKeyDefinition: + HandleKeyConstraintPunctuation(token, ref localRoot); + break; case SyntaxTreeNodeType.Root: case SyntaxTreeNodeType.CreateStatement: case SyntaxTreeNodeType.CreateTable: @@ -211,6 +313,14 @@ private void HandlePunctuation(SqlToken token, SyntaxTreeNode root, ref SyntaxTr } } + private void HandleKeyConstraintPunctuation(SqlToken token, ref SyntaxTreeNode localRoot) + { + if (token.Value.Equals("(")) + { + // just skip + } + } + private void HandleTableRootPunctuation(ref SyntaxTreeNode localRoot) { ArgumentNullException.ThrowIfNull(localRoot); @@ -276,7 +386,7 @@ private void HandleNumber(SqlToken token, ref SyntaxTreeNode localRoot) } #endregion - + private void AddNode(SyntaxTreeNodeType type, string value, ref SyntaxTreeNode? currentRootNode) { var newNode = new SyntaxTreeNode(type, value, currentRootNode); diff --git a/DbSeeder/SqlParser/SyntaxTree/ForeignKeyConstraintBuilder.cs b/DbSeeder/SqlParser/SyntaxTree/ForeignKeyConstraintBuilder.cs new file mode 100644 index 0000000..d09ebce --- /dev/null +++ b/DbSeeder/SqlParser/SyntaxTree/ForeignKeyConstraintBuilder.cs @@ -0,0 +1,119 @@ +namespace DbSeeder.SqlParser.SyntaxTree; + +public class ForeignKeyConstraintBuilder +{ + private SyntaxTreeNode? _constraintLocalRoot; + private SyntaxTreeNode? _lastAddedNode; + + private bool _referenceKeywordMet; + public bool IsComplete { get; private set; } + + // FOREIGN KEY subtree structure: + // FOREIGN KEY -- ForeignKeyDefinition + // profile_id -- KeyColumnIdentifier + // profiles -- KeyReferencedTable + // id -- KeyReferencedColumn + public void Handle(SqlToken? token, ref SyntaxTreeNode treeLocalRoot) + { + ArgumentNullException.ThrowIfNull(token); + + if (_constraintLocalRoot is null) + { + // This is a first node and here we have to create a ForeignKeyDefinition node + CreateConstraintRoot(ref treeLocalRoot); + } + else if (token.Type is SqlTokenType.Punctuation) + { + HandlePunctuationToken(token, ref treeLocalRoot); + } + + else if (token.Type is SqlTokenType.Identifier) + { + HandleIdentifierToken(token); + } + + else if (token.Type is SqlTokenType.Keyword + && token.Value.Equals("REFERENCES", StringComparison.OrdinalIgnoreCase)) + { + _referenceKeywordMet = true; + } + } + + private void HandleIdentifierToken(SqlToken token) + { + ArgumentNullException.ThrowIfNull(_lastAddedNode); + ArgumentNullException.ThrowIfNull(_constraintLocalRoot); + + if (token.Value.Equals("REFERENCES", StringComparison.OrdinalIgnoreCase)) + { + _referenceKeywordMet = true; + return; + } + + if (_lastAddedNode.Type is SyntaxTreeNodeType.ForeignKeyDefinition) + { + // This is a pointer to the constraint owner column + var node = new SyntaxTreeNode( + SyntaxTreeNodeType.KeyColumnIdentifier, + token.Value, + _constraintLocalRoot); + + _constraintLocalRoot.Children.Add(node); + _lastAddedNode = node; + } + else if (_lastAddedNode.Type is SyntaxTreeNodeType.KeyColumnIdentifier && + _referenceKeywordMet) + { + var node = new SyntaxTreeNode( + SyntaxTreeNodeType.KeyReferencedTable, + token.Value, + _constraintLocalRoot); + + _constraintLocalRoot.Children.Add(node); + _lastAddedNode = node; + } + else if (_lastAddedNode.Type is SyntaxTreeNodeType.KeyReferencedTable) + { + var node = new SyntaxTreeNode( + SyntaxTreeNodeType.KeyReferencedColumn, + token.Value, + _constraintLocalRoot); + + _constraintLocalRoot.Children.Add(node); + _lastAddedNode = node; + } + } + + private void HandlePunctuationToken(SqlToken token, ref SyntaxTreeNode treeLocalRoot) + { + ArgumentNullException.ThrowIfNull(_lastAddedNode); + + if (_lastAddedNode.Type == SyntaxTreeNodeType.KeyReferencedColumn && + token.Value is ")") + { + // FOREIGN KEY statement is complete + IsComplete = true; + + // move a pointer back to the cols + // then this one should be moved to the appropriate column, + // but this should be managed by the ast builder + treeLocalRoot = _constraintLocalRoot!.Parent!; + } + } + + private void CreateConstraintRoot(ref SyntaxTreeNode treeLocalRoot) + { + const string nodeValue = "FOREIGN KEY"; + _constraintLocalRoot = new SyntaxTreeNode( + SyntaxTreeNodeType.ForeignKeyDefinition, nodeValue, treeLocalRoot.Parent); + + var tempNode = treeLocalRoot; + treeLocalRoot = _constraintLocalRoot; + + var parent = treeLocalRoot.Parent!; + parent.Children.Remove(tempNode); + parent.Children.Add(_constraintLocalRoot); + + _lastAddedNode = _constraintLocalRoot; + } +} diff --git a/DbSeeder/SqlParser/SyntaxTree/SyntaxTreeNodeType.cs b/DbSeeder/SqlParser/SyntaxTree/SyntaxTreeNodeType.cs index 4393fe6..2677aee 100644 --- a/DbSeeder/SqlParser/SyntaxTree/SyntaxTreeNodeType.cs +++ b/DbSeeder/SqlParser/SyntaxTree/SyntaxTreeNodeType.cs @@ -14,5 +14,13 @@ public enum SyntaxTreeNodeType ColumnDataType, ColumnConstraint, - DataTypeConstraint + DataTypeConstraint, + + KeyDefinition, + KeyReferencedTable, + KeyColumnIdentifier, + KeyReferencedColumn, + + PrimaryKeyDefinition, + ForeignKeyDefinition } diff --git a/DbSeeder/SqlParser/SyntaxTree/Validation/Internal/TreeStructureValidator.cs b/DbSeeder/SqlParser/SyntaxTree/Validation/Internal/TreeStructureValidator.cs index 93648fe..d9782d9 100644 --- a/DbSeeder/SqlParser/SyntaxTree/Validation/Internal/TreeStructureValidator.cs +++ b/DbSeeder/SqlParser/SyntaxTree/Validation/Internal/TreeStructureValidator.cs @@ -11,10 +11,23 @@ public class TreeStructureValidator : INodeValidator { SyntaxTreeNodeType.CreateTable, [SyntaxTreeNodeType.TableRoot] }, { SyntaxTreeNodeType.TableRoot, [SyntaxTreeNodeType.TableColumns] }, { SyntaxTreeNodeType.TableColumns, [SyntaxTreeNodeType.Column] }, - { SyntaxTreeNodeType.Column, [SyntaxTreeNodeType.ColumnDataType, SyntaxTreeNodeType.ColumnConstraint] }, + { SyntaxTreeNodeType.Column, [ + SyntaxTreeNodeType.ColumnDataType, + SyntaxTreeNodeType.ColumnConstraint, + SyntaxTreeNodeType.ForeignKeyDefinition] }, { SyntaxTreeNodeType.ColumnDataType, [SyntaxTreeNodeType.DataTypeConstraint] }, { SyntaxTreeNodeType.DataTypeConstraint, [] }, { SyntaxTreeNodeType.ColumnConstraint, [] }, + { + SyntaxTreeNodeType.ForeignKeyDefinition, [ + SyntaxTreeNodeType.KeyColumnIdentifier, + SyntaxTreeNodeType.KeyReferencedTable, + SyntaxTreeNodeType.KeyReferencedColumn + ] + }, + { SyntaxTreeNodeType.KeyColumnIdentifier, [] }, + { SyntaxTreeNodeType.KeyReferencedTable, [] }, + { SyntaxTreeNodeType.KeyReferencedColumn, [] } }; public void Validate(ValidationContext validationContext, SyntaxTreeNode node)