diff --git a/src/Weasel.Postgresql.Tests/Tables/creating_tables_in_database.cs b/src/Weasel.Postgresql.Tests/Tables/creating_tables_in_database.cs index 197e4371..3cc6a895 100644 --- a/src/Weasel.Postgresql.Tests/Tables/creating_tables_in_database.cs +++ b/src/Weasel.Postgresql.Tests/Tables/creating_tables_in_database.cs @@ -210,4 +210,27 @@ bool withDistinctNulls (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.Postgresql/PostgresqlObjectName.cs b/src/Weasel.Postgresql/PostgresqlObjectName.cs index 67389ab5..06e94218 100644 --- a/src/Weasel.Postgresql/PostgresqlObjectName.cs +++ b/src/Weasel.Postgresql/PostgresqlObjectName.cs @@ -4,6 +4,8 @@ namespace Weasel.Postgresql; public class PostgresqlObjectName: DbObjectName { + protected override string QuotedQualifiedName => $"{SchemaUtils.QuoteName(Schema)}.{SchemaUtils.QuoteName(Name)}"; + public PostgresqlObjectName(string schema, string name) : base(schema, name, PostgresqlProvider.Instance.ToQualifiedName(schema, name)) { diff --git a/src/Weasel.Postgresql/SchemaUtils.cs b/src/Weasel.Postgresql/SchemaUtils.cs index 6693e098..09d3d8d2 100644 --- a/src/Weasel.Postgresql/SchemaUtils.cs +++ b/src/Weasel.Postgresql/SchemaUtils.cs @@ -43,5 +43,25 @@ private static async Task dropSchema(string connectionString, string schem throw; } } + + public static string QuoteName(string name) + { + return ReservedKeywords.Contains(name, StringComparer.InvariantCultureIgnoreCase) ? $"\"{name}\"" : name; + } + + private static readonly string[] ReservedKeywords = + [ + "ALL", "ANALYSE", "ANALYZE", "AND", "ANY", "ARRAY", "AS", "ASC", "ASYMMETRIC", "AUTHORIZATION", + "BINARY", "BOTH", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "CONCURRENTLY", "CONSTRAINT", + "CREATE", "CROSS", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_SCHEMA", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT", "DEFERRABLE", "DESC", "DISTINCT", + "DO", "ELSE", "END", "EXCEPT", "FALSE", "FETCH", "FOR", "FOREIGN", "FREEZE", "FROM", "FULL", + "GRANT", "GROUP", "HAVING", "ILIKE", "IN", "INITIALLY", "INNER", "INTERSECT", "INTO", "IS", + "ISNULL", "JOIN", "LATERAL", "LEADING", "LEFT", "LIKE", "LIMIT", "LOCALTIME", "LOCALTIMESTAMP", + "NATURAL", "NOT", "NOTNULL", "NULL", "OFFSET", "ON", "ONLY", "OR", "ORDER", "OUTER", "OVERLAPS", + "PLACING", "PRIMARY", "REFERENCES", "RETURNING", "RIGHT", "SELECT", "SESSION_USER", "SIMILAR", + "SOME", "SYMMETRIC", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION", "UNIQUE", "USER", + "USING", "VARIADIC", "VERBOSE", "WHEN", "WHERE", "WINDOW", "WITH" + ]; } diff --git a/src/Weasel.Postgresql/Tables/Table.cs b/src/Weasel.Postgresql/Tables/Table.cs index 9342149c..5c3e6e0a 100644 --- a/src/Weasel.Postgresql/Tables/Table.cs +++ b/src/Weasel.Postgresql/Tables/Table.cs @@ -75,11 +75,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.Postgresql/Tables/TableColumn.cs b/src/Weasel.Postgresql/Tables/TableColumn.cs index b035dfd5..7dc7efb2 100644 --- a/src/Weasel.Postgresql/Tables/TableColumn.cs +++ b/src/Weasel.Postgresql/Tables/TableColumn.cs @@ -41,6 +41,8 @@ public TableColumn(string name, string type) public string Name { get; } + public string QuotedName => SchemaUtils.QuoteName(Name); + public string RawType() { return Type.Split('(')[0].Trim(); @@ -59,7 +61,7 @@ public string Declaration() protected bool Equals(TableColumn other) { - return string.Equals(Name, other.Name) && + return string.Equals(QuotedName, other.QuotedName) && string.Equals(PostgresqlProvider.Instance.ConvertSynonyms(RawType()), PostgresqlProvider.Instance.ConvertSynonyms(other.RawType())); } @@ -97,8 +99,8 @@ public string ToDeclaration() var declaration = Declaration(); return declaration.IsEmpty() - ? $"{Name} {Type}" - : $"{Name} {Type} {declaration}"; + ? $"{QuotedName} {Type}" + : $"{QuotedName} {Type} {declaration}"; } public override string ToString() @@ -109,12 +111,12 @@ public override string ToString() public virtual string AlterColumnTypeSql(Table table, TableColumn changeActual) { - return $"alter table {table.Identifier} alter column {Name.PadRight(Name.Length)} type {Type};"; + return $"alter table {table.Identifier} alter column {QuotedName.PadRight(QuotedName.Length)} type {Type};"; } public string DropColumnSql(Table table) { - return $"alter table {table.Identifier} drop column {Name};"; + return $"alter table {table.Identifier} drop column {QuotedName};"; }