diff --git a/src/Weasel.Core/DbObjectName.cs b/src/Weasel.Core/DbObjectName.cs index c57273ca..512b297a 100644 --- a/src/Weasel.Core/DbObjectName.cs +++ b/src/Weasel.Core/DbObjectName.cs @@ -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"); @@ -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) diff --git a/src/Weasel.SqlServer.Tests/Tables/creating_tables_in_database.cs b/src/Weasel.SqlServer.Tests/Tables/creating_tables_in_database.cs index 5ca666e8..771a6725 100644 --- a/src/Weasel.SqlServer.Tests/Tables/creating_tables_in_database.cs +++ b/src/Weasel.SqlServer.Tests/Tables/creating_tables_in_database.cs @@ -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("id").AsPrimaryKey(); + table.AddColumn("first_name"); + table.AddColumn("last_name"); + table.AddColumn("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(); + } } diff --git a/src/Weasel.SqlServer/Canonicalization.cs b/src/Weasel.SqlServer/Canonicalization.cs index 36eb48bb..684930ff 100644 --- a/src/Weasel.SqlServer/Canonicalization.cs +++ b/src/Weasel.SqlServer/Canonicalization.cs @@ -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; } } diff --git a/src/Weasel.SqlServer/SchemaUtils.cs b/src/Weasel.SqlServer/SchemaUtils.cs new file mode 100644 index 00000000..7ff6f9a2 --- /dev/null +++ b/src/Weasel.SqlServer/SchemaUtils.cs @@ -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" + ]; +} diff --git a/src/Weasel.SqlServer/SqlServerObjectName.cs b/src/Weasel.SqlServer/SqlServerObjectName.cs index 5828f723..9b7d58a3 100644 --- a/src/Weasel.SqlServer/SqlServerObjectName.cs +++ b/src/Weasel.SqlServer/SqlServerObjectName.cs @@ -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().ToQualifiedName(schema, name)) { diff --git a/src/Weasel.SqlServer/Tables/Table.cs b/src/Weasel.SqlServer/Tables/Table.cs index f4337bed..cd8deb28 100644 --- a/src/Weasel.SqlServer/Tables/Table.cs +++ b/src/Weasel.SqlServer/Tables/Table.cs @@ -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); @@ -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()) diff --git a/src/Weasel.SqlServer/Tables/TableColumn.cs b/src/Weasel.SqlServer/Tables/TableColumn.cs index cf217b1e..413af854 100644 --- a/src/Weasel.SqlServer/Tables/TableColumn.cs +++ b/src/Weasel.SqlServer/Tables/TableColumn.cs @@ -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() { @@ -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()