Skip to content

Commit

Permalink
Merge pull request #16 from Bardin08/vbardin/implement-sql-schema-parser
Browse files Browse the repository at this point in the history
SQL Schema construction
  • Loading branch information
Bardin08 authored Apr 25, 2024
2 parents 6b20b96 + 67086ed commit f545927
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 22 deletions.
12 changes: 6 additions & 6 deletions DbSeeder/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ private static void Main()
{
const string sqlScript =
"""
CREATE TABLE users
CREATE TABLE profiles
(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(122) NOT NULL UNIQUE,
profile_id INT,
FOREIGN KEY (profile_id) REFERENCES profiles(id)
nickname VARCHAR(122) NOT NULL UNIQUE
);

CREATE TABLE profiles
CREATE TABLE users
(
id INT AUTO_INCREMENT PRIMARY KEY,
nickname VARCHAR(122) NOT NULL UNIQUE
name VARCHAR(122) NOT NULL UNIQUE,
profile_id INT,
FOREIGN KEY (profile_id) REFERENCES profiles(id)
);
""";
var lexer = new SqlLexer(sqlScript);
Expand Down
47 changes: 42 additions & 5 deletions DbSeeder/Schema/Column.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
namespace DbSeeder.Schema;

public class Column(string name, string dataType, string dataTypeConstraint, string[] constraints)
public record Column
{
public string Name { get; } = name;
public string DataType { get; } = dataType;
public string DataTypeConstraint { get; set; } = dataTypeConstraint;
public string[] Constraints { get; set; } = constraints;
public Column(string name,
string dataType,
string dataTypeConstraint,
string[] constraints)
{
Name = name;
DataType = dataType;
DataTypeConstraint = dataTypeConstraint;

constraints = constraints.Select(c => c.ToLower()).ToArray();
IsAutoIncrement = constraints.Contains("auto_increment", StringComparer.OrdinalIgnoreCase);
IsNotNull = constraints.Contains("not null", StringComparer.OrdinalIgnoreCase);
IsUnique = constraints.Contains("unique", StringComparer.OrdinalIgnoreCase);
IsPrimaryKey = constraints.Contains("primary key", StringComparer.OrdinalIgnoreCase);

var fkConstraint = constraints.FirstOrDefault(
c => c.StartsWith("foreign key", StringComparison.OrdinalIgnoreCase));
IsForeignKey = fkConstraint is not null;

if (IsForeignKey)
{
var refTableAndCol = fkConstraint![12..].Split("|");

var refTableName = refTableAndCol[0];
var refColumnName = refTableAndCol[1];

ForeignKeyRef = new ForeignKeyRef(refTableName, refColumnName);
}
}

public string Name { get; }
public string DataType { get; }
public string DataTypeConstraint { get; }

public bool IsAutoIncrement { get; }
public bool IsPrimaryKey { get; }
public bool IsForeignKey { get; }
public bool IsNotNull { get; }
public bool IsUnique { get; }

public ForeignKeyRef? ForeignKeyRef { get; }
}
7 changes: 7 additions & 0 deletions DbSeeder/Schema/ForeignKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace DbSeeder.Schema;

public record ForeignKey(
Table Table,
Column Column,
Table RefTable,
Column RefColumn);
5 changes: 5 additions & 0 deletions DbSeeder/Schema/ForeignKeyRef.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace DbSeeder.Schema;

public record ForeignKeyRef(
string TableName,
string ColumnName);
5 changes: 5 additions & 0 deletions DbSeeder/Schema/PrimaryKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace DbSeeder.Schema;

public record PrimaryKey(
Table Table,
Column Column);
8 changes: 5 additions & 3 deletions DbSeeder/Schema/SqlSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
public class SqlSchema
{
private readonly List<Table> _tables = [];

public IReadOnlyList<Table> Tables => _tables;

public void AddTable(Table table)
{
_tables.Add(table);
}
=> _tables.Add(table);

public Table? GetTableByName(string tableName)
=> _tables.FirstOrDefault(t => t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase));
}
11 changes: 5 additions & 6 deletions DbSeeder/Schema/SqlSchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,19 @@ private void TraverseCreateStatement(SyntaxTreeNode createStatementNode)
private Table CreateTable(SyntaxTreeNode tableRoot)
{
var tableName = tableRoot.Value;
var table = new Table(tableName);
var table = new Table(tableName, _sqlSchema!);

// Actually, now we expect that table root contains only cols, but in the future more sub-nodes can be added,
// so we have to be sure that we're working exactly with a node that contains cols.
var colsRoot = tableRoot.Children.First(x => x?.Type is SyntaxTreeNodeType.TableColumns);
var cols = ParseColumns(colsRoot!);
table.Columns.AddRange(cols);
table.AddColumns(cols);

return table;
}

private IEnumerable<Column> ParseColumns(SyntaxTreeNode colsRoot)
{
return colsRoot.Children.Select(GetColumn!).ToList();
}
=> colsRoot.Children.Select(GetColumn!).ToList();

private Column GetColumn(SyntaxTreeNode columnNode)
{
Expand Down Expand Up @@ -97,7 +95,8 @@ private Column GetColumn(SyntaxTreeNode columnNode)
constraints.Add(node.Value);
break;
case SyntaxTreeNodeType.ForeignKeyDefinition:
// TODO: Implement processing FK constraint correctly
var fkConstraint = $"foreign key {node.Children[1]?.Value}|{node.Children[2]?.Value}";
constraints.Add(fkConstraint);
break;
}
}
Expand Down
71 changes: 69 additions & 2 deletions DbSeeder/Schema/Table.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,74 @@
namespace DbSeeder.Schema;

public class Table(string name)
public class Table(string name, SqlSchema schema)
{
private readonly List<Column> _columns = [];
private readonly List<ForeignKey> _foreignKeys = [];

// There are some rules how to deal if PK is not defined
// ex: find the first attribute with NOT NULL & UNIQUE constraints
public PrimaryKey? PrimaryKey { get; private set; }
public string Name { get; } = name;
public List<Column> Columns { get; } = [];
public IReadOnlyList<Column> Columns => _columns;
public IReadOnlyList<ForeignKey> ForeignKeys => _foreignKeys;

public void AddColumns(IEnumerable<Column> columns)
{
foreach (var column in columns)
{
AddColumn(column);
}
}

private void AddColumn(Column column)
{
ArgumentNullException.ThrowIfNull(column);
_columns.Add(column);

if (column.IsPrimaryKey)
{
SetTablePrimaryKey(column);
}
else if (column.IsForeignKey)
{
SetTableForeignKey(column);
}
}

private void SetTablePrimaryKey(Column column)
{
if (PrimaryKey != null)
{
throw new ArgumentException("Table can not contains more that one PRIMARY KEY");
}

PrimaryKey = new PrimaryKey(this, column);
}

private void SetTableForeignKey(Column column)
{
ArgumentNullException.ThrowIfNull(column.ForeignKeyRef);

var refTable = schema.GetTableByName(column.ForeignKeyRef.TableName);
if (refTable is null)
{
throw new InvalidOperationException($"Referenced table {column.ForeignKeyRef.TableName} is not exists " +
$"in current schema. Validate the order of the create statements, " +
$"it's matter");
}

var refColumn = refTable.GetColumnByName(column.ForeignKeyRef.ColumnName);
if (refColumn is null)
{
throw new InvalidOperationException($"Referenced table {column.ForeignKeyRef.TableName} is not exists " +
$"in current schema. Validate the order of the create statements, " +
$"it's matter");
}

var fk = new ForeignKey(this, column, refTable, refColumn);
_foreignKeys.Add(fk);
}

private Column? GetColumnByName(string columnName)
=> Columns.FirstOrDefault(c => c.Name.Equals(columnName, StringComparison.Ordinal));
}

0 comments on commit f545927

Please sign in to comment.