Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQL Server provider functionality to handle reserved keywords as table and column names #144

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading