Skip to content

Commit

Permalink
Add functionality to handle reserved keywords as table and column names
Browse files Browse the repository at this point in the history
- Add functionality to handle reserved keywords as table and column names
- Cleanup Canonicalization
- Add unit tests for table with name and column having a reserved keyword
  • Loading branch information
mysticmind authored and jeremydmiller committed Oct 8, 2024
1 parent 75fc65e commit 16c70c4
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 24 deletions.
4 changes: 3 additions & 1 deletion src/Weasel.Core/DbObjectName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ protected DbObjectName(string schema, string name, string qualifiedName)
public string Name { get; }
public string QualifiedName { get; }

protected virtual string QuotedQualifiedName => QualifiedName;

public DbObjectName ToTempCopyTable()
{
return new DbObjectName(Schema, Name + "_temp");
Expand All @@ -38,7 +40,7 @@ public static DbObjectName Parse(IDatabaseProvider provider, string schemaName,

public override string ToString()
{
return QualifiedName;
return QuotedQualifiedName;
}

protected bool Equals(DbObjectName other)
Expand Down
23 changes: 23 additions & 0 deletions src/Weasel.SqlServer.Tests/Tables/creating_tables_in_database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,27 @@ public async Task create_tables_with_indexes_and_included_columns()
(await table.ExistsInDatabaseAsync(theConnection))
.ShouldBeTrue();
}

[Fact]
public async Task create_table_with_name_and_column_using_reserved_keyword()
{
await theConnection.OpenAsync();

await theConnection.ResetSchemaAsync("tables");

var table = new Table("order");
table.AddColumn<int>("id").AsPrimaryKey();
table.AddColumn<string>("first_name");
table.AddColumn<string>("last_name");
table.AddColumn<int>("order");

await CreateSchemaObjectInDatabase(table);

(await table.ExistsInDatabaseAsync(theConnection))
.ShouldBeTrue();

await theConnection.CreateCommand(
"insert into [order] (id, first_name, last_name, [order]) values (1, 'Elton', 'John',1)")
.ExecuteNonQueryAsync();
}
}
21 changes: 3 additions & 18 deletions src/Weasel.SqlServer/Canonicalization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,9 @@ public static string CanonicizeSql(this string sql)
.Replace("Boolean", "boolean")
.Replace("bool,", "boolean,")
.Replace("int[]", "integer[]")
.Replace("numeric", "decimal").TrimEnd(';').TrimEnd();
.Replace("numeric", "decimal")
.TrimEnd(';').TrimEnd();


if (replaced.ContainsIgnoreCase("PLV8"))
{
replaced = replaced
.Replace("LANGUAGE plv8 IMMUTABLE STRICT AS $function$", "AS $$");

const string languagePlv8ImmutableStrict = "$$ LANGUAGE plv8 IMMUTABLE STRICT";
const string functionMarker = "$function$";
if (replaced.EndsWith(functionMarker))
{
replaced = replaced.Substring(0, replaced.LastIndexOf(functionMarker)) +
languagePlv8ImmutableStrict;
}
}

return replaced
.Replace(" ", " ").TrimEnd().TrimEnd(';');
return replaced;
}
}
199 changes: 199 additions & 0 deletions src/Weasel.SqlServer/SchemaUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
namespace Weasel.SqlServer;

public static class SchemaUtils
{
public static string QuoteName(string name)
{
return ReservedKeywords.Contains(name, StringComparer.InvariantCultureIgnoreCase) ? $"[{name}]" : name;
}

private static readonly string[] ReservedKeywords =
[
"ADD",
"EXTERNAL",
"PROCEDURE",
"ALL",
"FETCH",
"PUBLIC",
"ALTER",
"FILE",
"RAISERROR",
"AND",
"FILLFACTOR",
"READ",
"ANY",
"FOR",
"READTEXT",
"AS",
"FOREIGN",
"RECONFIGURE",
"ASC",
"FREETEXT",
"REFERENCES",
"AUTHORIZATION",
"FREETEXTTABLE",
"REPLICATION",
"BACKUP",
"FROM",
"RESTORE",
"BEGIN",
"FULL",
"RESTRICT",
"BETWEEN",
"FUNCTION",
"RETURN",
"BREAK",
"GOTO",
"REVERT",
"BROWSE",
"GRANT",
"REVOKE",
"BULK",
"GROUP",
"RIGHT",
"BY",
"HAVING",
"ROLLBACK",
"CASCADE",
"HOLDLOCK",
"ROWCOUNT",
"CASE",
"IDENTITY",
"ROWGUIDCOL",
"CHECK",
"IDENTITY_INSERT",
"RULE",
"CHECKPOINT",
"IDENTITYCOL",
"SAVE",
"CLOSE",
"IF",
"SCHEMA",
"CLUSTERED",
"IN",
"SECURITYAUDIT",
"COALESCE",
"INDEX",
"SELECT",
"COLLATE",
"INNER",
"SEMANTICKEYPHRASETABLE",
"COLUMN",
"INSERT",
"SEMANTICSIMILARITYDETAILSTABLE",
"COMMIT",
"INTERSECT",
"SEMANTICSIMILARITYTABLE",
"COMPUTE",
"INTO",
"SESSION_USER",
"CONSTRAINT",
"IS",
"SET",
"CONTAINS",
"JOIN",
"SETUSER",
"CONTAINSTABLE",
"KEY",
"SHUTDOWN",
"CONTINUE",
"KILL",
"SOME",
"CONVERT",
"LEFT",
"STATISTICS",
"CREATE",
"LIKE",
"SYSTEM_USER",
"CROSS",
"LINENO",
"TABLE",
"CURRENT",
"LOAD",
"TABLESAMPLE",
"CURRENT_DATE",
"MERGE",
"TEXTSIZE",
"CURRENT_TIME",
"NATIONAL",
"THEN",
"CURRENT_TIMESTAMP",
"NOCHECK",
"TO",
"CURRENT_USER",
"NONCLUSTERED",
"TOP",
"CURSOR",
"NOT",
"TRAN",
"DATABASE",
"NULL",
"TRANSACTION",
"DBCC",
"NULLIF",
"TRIGGER",
"DEALLOCATE",
"OF",
"TRUNCATE",
"DECLARE",
"OFF",
"TRY_CONVERT",
"DEFAULT",
"OFFSETS",
"TSEQUAL",
"DELETE",
"ON",
"UNION",
"DENY",
"OPEN",
"UNIQUE",
"DESC",
"OPENDATASOURCE",
"UNPIVOT",
"DISK",
"OPENQUERY",
"UPDATE",
"DISTINCT",
"OPENROWSET",
"UPDATETEXT",
"DISTRIBUTED",
"OPENXML",
"USE",
"DOUBLE",
"OPTION",
"USER",
"DROP",
"OR",
"VALUES",
"DUMP",
"ORDER",
"VARYING",
"ELSE",
"OUTER",
"VIEW",
"END",
"OVER",
"WAITFOR",
"ERRLVL",
"PERCENT",
"WHEN",
"ESCAPE",
"PIVOT",
"WHERE",
"EXCEPT",
"PLAN",
"WHILE",
"EXEC",
"PRECISION",
"WITH",
"EXECUTE",
"PRIMARY",
"WITHIN GROUP",
"EXISTS",
"PRINT",
"WRITETEXT",
"EXIT",
"PROC",
"RANK"
];
}
2 changes: 2 additions & 0 deletions src/Weasel.SqlServer/SqlServerObjectName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Weasel.SqlServer;

public class SqlServerObjectName: DbObjectName
{
protected override string QuotedQualifiedName => $"{SchemaUtils.QuoteName(Schema)}.{SchemaUtils.QuoteName(Name)}";

public SqlServerObjectName(string schema, string name)
: base(schema, name, SqlServerProvider.Instance.As<IDatabaseProvider>().ToQualifiedName(schema, name))
{
Expand Down
6 changes: 3 additions & 3 deletions src/Weasel.SqlServer/Tables/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void WriteCreateStatement(Migrator migrator, TextWriter writer)
writer.WriteLine("SELECT {0} = {1} + 'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id)) + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'",
sqlVariableName, sqlVariableName);
writer.WriteLine("FROM sys.foreign_keys AS fk");
writer.WriteLine("WHERE fk.referenced_object_id = OBJECT_ID('{0}');", Identifier.QualifiedName);
writer.WriteLine("WHERE fk.referenced_object_id = OBJECT_ID('{0}');", Identifier);
writer.WriteLine("EXEC sp_executesql {0};", sqlVariableName);

writer.WriteLine("DROP TABLE IF EXISTS {0};", Identifier);
Expand All @@ -83,11 +83,11 @@ public void WriteCreateStatement(Migrator migrator, TextWriter writer)

if (migrator.Formatting == SqlFormatting.Pretty)
{
var columnLength = Columns.Max(x => x.Name.Length) + 4;
var columnLength = Columns.Max(x => x.QuotedName.Length) + 4;
var typeLength = Columns.Max(x => x.Type.Length) + 4;

var lines = Columns.Select(column =>
$" {column.Name.PadRight(columnLength)}{column.Type.PadRight(typeLength)}{column.Declaration()}")
$" {column.QuotedName.PadRight(columnLength)}{column.Type.PadRight(typeLength)}{column.Declaration()}")
.ToList();

if (PrimaryKeyColumns.Any())
Expand Down
5 changes: 3 additions & 2 deletions src/Weasel.SqlServer/Tables/TableColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public TableColumn(string name, string type)
public bool IsAutoNumber { get; set; }

public string Name { get; }
public string QuotedName => SchemaUtils.QuoteName(Name);

public string RawType()
{
Expand Down Expand Up @@ -103,8 +104,8 @@ public string ToDeclaration()
var declaration = Declaration();

return declaration.IsEmpty()
? $"{Name} {Type}"
: $"{Name} {Type} {declaration}";
? $"{QuotedName} {Type}"
: $"{QuotedName} {Type} {declaration}";
}

public override string ToString()
Expand Down

0 comments on commit 16c70c4

Please sign in to comment.