Skip to content

Commit

Permalink
Merge branch 'refs/heads/master' into vbardin/implement-sql-schema-pa…
Browse files Browse the repository at this point in the history
…rser
  • Loading branch information
Bardin08 committed Apr 25, 2024
2 parents 2a23f92 + 6b20b96 commit c448766
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 32 deletions.
8 changes: 4 additions & 4 deletions DbSeeder.Tests/DbSeeder.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="xunit" Version="2.5.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
<PackageReference Include="xunit" Version="2.7.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8"/>
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions DbSeeder/DbSeeder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MySql.Data" Version="8.3.0" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions DbSeeder/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions DbSeeder/Schema/SqlSchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ private Column GetColumn(SyntaxTreeNode columnNode)
case SyntaxTreeNodeType.ColumnConstraint:
constraints.Add(node.Value);
break;
case SyntaxTreeNodeType.ForeignKeyDefinition:
// TODO: Implement processing FK constraint correctly

Check warning on line 98 in DbSeeder/Schema/SqlSchemaBuilder.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
break;
}
}

Expand Down
158 changes: 134 additions & 24 deletions DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,54 @@ namespace DbSeeder.SqlParser.SyntaxTree;

public class AstBuilder(List<SqlToken> 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)

Check warning on line 29 in DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.

Check warning on line 29 in DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.

Check warning on line 29 in DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.

Check warning on line 29 in DbSeeder/SqlParser/SyntaxTree/AstBuilder.cs

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.
{
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:
Expand All @@ -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:
Expand Down Expand Up @@ -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!);
Expand All @@ -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);
Expand All @@ -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)
Expand All @@ -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
{
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit c448766

Please sign in to comment.