Skip to content

Commit

Permalink
Merge pull request #8 from Servant-Software-LLC/7-add-alter-table-add…
Browse files Browse the repository at this point in the history
…drop-column-support

Basic implementation of ALTER TABLE ADD/DROP COLUMN.
  • Loading branch information
DaveRMaltby committed Apr 3, 2024
2 parents afa3b7c + 36d6191 commit 7870255
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 169 deletions.
81 changes: 81 additions & 0 deletions src/Core/AlterStmt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Irony.Parsing;
using SqlBuildingBlocks.Extensions;
using SqlBuildingBlocks.LogicalEntities;
using System.Reflection;

namespace SqlBuildingBlocks;

public class AlterStmt : NonTerminal
{
public static string TermName => MethodBase.GetCurrentMethod().DeclaringType.Name.CamelCase();

public AlterStmt(Grammar grammar, Id id)
: this(grammar, id, new ColumnDef(grammar, id)) { }
public AlterStmt(Grammar grammar, Id id, DataType dataType)
: this(grammar, id, new ColumnDef(grammar, id, dataType)) { }

public AlterStmt(Grammar grammar, Id id, ColumnDef columnDef)
: base(TermName)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
ColumnDef = columnDef ?? throw new ArgumentNullException(nameof(columnDef));

var ALTER = grammar.ToTerm("ALTER");
var TABLE = grammar.ToTerm("TABLE");
var ADD = grammar.ToTerm("ADD");
var DROP = grammar.ToTerm("DROP");
var COLUMN = grammar.ToTerm("COLUMN");
var COLUMN_Optional = new NonTerminal("ColumnOptional", grammar.Empty | COLUMN);
var alterCmd = new NonTerminal("alterCmd");

alterCmd.Rule = ADD + COLUMN_Optional + columnDef
| DROP + COLUMN_Optional + id;

Rule = ALTER + TABLE + id + alterCmd;

grammar.MarkPunctuation("(", ")");

// Mark ALTER, TABLE, and COLUMN as punctuation
grammar.MarkPunctuation("ALTER", "TABLE", "COLUMN");
}

public Id Id { get; }
public ColumnDef ColumnDef { get; }

public virtual SqlAlterTableDefinition Create(ParseTreeNode alterStmt)
{
SqlAlterTableDefinition sqlAlterTableDefinition = new();
Update(alterStmt, sqlAlterTableDefinition);

return sqlAlterTableDefinition;
}

public virtual void Update(ParseTreeNode alterStmt, SqlAlterTableDefinition sqlAlterTableDefinition)
{
if (alterStmt.Term.Name != TermName)
{
var thisMethod = MethodBase.GetCurrentMethod() as MethodInfo;
throw new ArgumentException($"Cannot create building block of type {thisMethod!.ReturnType}. The TermName for node is {alterStmt.Term.Name} which does not match {TermName}", nameof(alterStmt));
}

var id = Id.CreateTable(alterStmt.ChildNodes[0]);
sqlAlterTableDefinition.Table = id;

var alterCmd = alterStmt.ChildNodes[1];

var alterType = alterCmd.ChildNodes[0].FindTokenAndGetText();

if (alterType == "ADD")
{
var constraints = new List<SqlConstraintDefinition>();
var columnDef = ColumnDef.Create(alterCmd.ChildNodes[2], constraints);
if (columnDef != null)
sqlAlterTableDefinition.ColumnsToAdd.Add(new(columnDef, constraints));
}
else if (alterType == "DROP")
{
var columnName = alterCmd.ChildNodes[2].ChildNodes[0].ChildNodes[0].Token.ValueString;
sqlAlterTableDefinition.ColumnsToDrop.Add(columnName);
}
}
}
83 changes: 83 additions & 0 deletions src/Core/ColumnDef.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Irony.Parsing;
using SqlBuildingBlocks.Extensions;
using SqlBuildingBlocks.LogicalEntities;
using System.Reflection;

namespace SqlBuildingBlocks;

public class ColumnDef : NonTerminal
{
public static string TermName => MethodBase.GetCurrentMethod().DeclaringType.Name.CamelCase();

public ColumnDef(Grammar grammar, Id id)
: this(grammar, id, new DataType(grammar))
{

}
public ColumnDef(Grammar grammar, Id id, DataType dataType)
: base(TermName)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
DataType = dataType ?? throw new ArgumentNullException(nameof(dataType));

var NULL = grammar.ToTerm("NULL");
var NOT = grammar.ToTerm("NOT");
var UNIQUE = grammar.ToTerm("UNIQUE");
var KEY = grammar.ToTerm("KEY");
var PRIMARY = grammar.ToTerm("PRIMARY");

var nullSpecOpt = new NonTerminal("nullSpecOpt");
var uniqueOpt = new NonTerminal("uniqueOpt");
var primaryKeyOpt = new NonTerminal("primaryKeyOpt");

//Inline constraints
nullSpecOpt.Rule = NULL | NOT + NULL | grammar.Empty;
uniqueOpt.Rule = UNIQUE | grammar.Empty;
primaryKeyOpt.Rule = PRIMARY + KEY | grammar.Empty;

Rule = id.SimpleId + DataType + nullSpecOpt + uniqueOpt + primaryKeyOpt;
}

public Id Id { get; }

public DataType DataType { get; }

public SqlColumnDefinition? Create(ParseTreeNode definition, IList<SqlConstraintDefinition> constraintDefinitions)
{
if (definition.Term.Name != TermName)
return null;

var columnName = Id.SimpleId.Create(definition.ChildNodes[0]);
var dataTypeName = DataType.Create(definition.ChildNodes[1]);

SqlColumnDefinition sqlColumnDefinition = new(columnName, dataTypeName);

//NULL or NOT NULL
var nullSpecOpt = definition.ChildNodes[2];
if (nullSpecOpt.ChildNodes.Count > 0)
{
sqlColumnDefinition.AllowNulls = nullSpecOpt.ChildNodes.Count == 1;
}

//UNIQUE
var uniqueOpt = definition.ChildNodes[3];
if (uniqueOpt.ChildNodes.Count > 0)
{
var uniqueConstraint = new SqlUniqueConstraint();
uniqueConstraint.Columns.Add(columnName);
constraintDefinitions.Add(new($"UQ_{columnName}", uniqueConstraint));
}

//PRIMARY KEY
var primaryKeyOpt = definition.ChildNodes[4];
if (primaryKeyOpt.ChildNodes.Count > 0)
{
var primaryKeyConstraint = new SqlPrimaryKeyConstraint();
primaryKeyConstraint.Columns.Add(columnName);
constraintDefinitions.Add(new($"PK_{columnName}", primaryKeyConstraint));
}

return sqlColumnDefinition;
}

}
114 changes: 114 additions & 0 deletions src/Core/ConstraintDef.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Irony.Parsing;
using SqlBuildingBlocks.Extensions;
using SqlBuildingBlocks.LogicalEntities;
using System.Reflection;

namespace SqlBuildingBlocks;

public class ConstraintDef : NonTerminal
{
public static string TermName => MethodBase.GetCurrentMethod().DeclaringType.Name.CamelCase();

private KeyTerm PRIMARY;
private SimpleIdList simpleIdList;

public ConstraintDef(Grammar grammar, Id id)
: base(TermName)
{
Id = id ?? throw new ArgumentNullException(nameof(id));

var CONSTRAINT = grammar.ToTerm("CONSTRAINT");
PRIMARY = grammar.ToTerm("PRIMARY");
var KEY = grammar.ToTerm("KEY");
var UNIQUE = grammar.ToTerm("UNIQUE");

var idlistPar = new NonTerminal("idlistPar");
var constraintTypeOpt = new NonTerminal("constraintTypeOpt");

simpleIdList = new SimpleIdList(id.SimpleId, grammar);
var simpleIdListPar = new NonTerminal("simpleIdListPar");

simpleIdListPar.Rule = "(" + simpleIdList + ")";

IdList idList = new(grammar, id);
idlistPar.Rule = "(" + idList + ")";
constraintTypeOpt.Rule = PRIMARY + KEY + simpleIdListPar
| UNIQUE + simpleIdListPar
| "FOREIGN" + KEY + idlistPar + "REFERENCES" + id + idlistPar;


Rule = CONSTRAINT + id.SimpleId + constraintTypeOpt;
}

public Id Id { get; }

public (SqlConstraintDefinition? Constraint, bool Handled) Create(ParseTreeNode definition, IList<SqlColumnDefinition> columns)
{
if (definition.Term.Name != TermName)
return (null, false);

var constraintName = Id.SimpleId.Create(definition.ChildNodes[1]);
var constraintTypeOpt = definition.ChildNodes[2];

switch (constraintTypeOpt.ChildNodes.Count)
{
//Only UNIQUE has 2 child nodes
case 2:
SqlUniqueConstraint sqlUniqueConstraint = new();
var uniqueColumns = simpleIdList.Create(constraintTypeOpt.ChildNodes[1].ChildNodes[0]);
foreach (var column in uniqueColumns)
{
if (!ContainsColumn(column, columns))
throw new Exception($"CREATE TABLE statement does not contain a column definition for {column}, but there is a UNIQUE constraint specifying this column.");

sqlUniqueConstraint.Columns.Add(column);
}
return (new(constraintName, sqlUniqueConstraint), true);

//Could be either PRIMARY KEY or NOT NULL constraints
case 3:
//PRIMARY KEY
if (constraintTypeOpt.ChildNodes[0].Term.Name == PRIMARY.Name)
{
SqlPrimaryKeyConstraint sqlPrimaryKeyConstraint = new();
var primaryKeyColumns = simpleIdList.Create(constraintTypeOpt.ChildNodes[2].ChildNodes[0]);
foreach (var column in primaryKeyColumns)
{
var columnFound = columns.FirstOrDefault(col => col.ColumnName == column);
if (columnFound == null)
throw new Exception($"CREATE TABLE statement does not contain a column definition for {column}, but there is a PRIMARY KEY constraint specifying this column.");

//PRIMARY KEY constraints implicitly make a column NOT NULL
columnFound.AllowNulls = false;

sqlPrimaryKeyConstraint.Columns.Add(column);
}
return (new(constraintName, sqlPrimaryKeyConstraint), true);
}

//NOT NULL
else
{
var notNullColumnName = Id.SimpleId.Create(constraintTypeOpt.ChildNodes[2].ChildNodes[0]);
var column = columns.FirstOrDefault(col => col.ColumnName == notNullColumnName);
if (column == null)
throw new Exception($"CREATE TABLE statement does not contain a column definition for {notNullColumnName}, but there is a NOT NULL constraint specifying this column.");

column.AllowNulls = false;
return (null, true);
}

case 6:
throw new NotImplementedException($"CREATE TABLE has a FOREIGN KEY constraint, but no implementation has been made for the {nameof(CreateTableStmt)}.{nameof(Create)} method. Consider contributing an implementation.");

default:
throw new Exception("CREATE TABLE has an unrecognized CONSTRAINT on it.");
}

throw new NotImplementedException();
}


private static bool ContainsColumn(string column, IList<SqlColumnDefinition> columns) =>
columns.Any(sqlColumn => sqlColumn.ColumnName == column);
}

0 comments on commit 7870255

Please sign in to comment.