-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from Servant-Software-LLC/7-add-alter-table-add…
…drop-column-support Basic implementation of ALTER TABLE ADD/DROP COLUMN.
- Loading branch information
Showing
10 changed files
with
543 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.