Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit d3d9872

Browse files
committed
Add Normalize option in PostgreSQLDialectProvider to create tables without quotes
1 parent 6430aad commit d3d9872

File tree

3 files changed

+222
-22
lines changed

3 files changed

+222
-22
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using NUnit.Framework;
7+
using ServiceStack.OrmLite.Tests;
8+
using ServiceStack.OrmLite.Tests.UseCase;
9+
using ServiceStack.Text;
10+
using Customer = ServiceStack.OrmLite.Tests.Customer;
11+
using Order = ServiceStack.OrmLite.Tests.Order;
12+
13+
namespace ServiceStack.OrmLite.PostgreSQL.Tests
14+
{
15+
class NormalizeTests
16+
: OrmLiteTestBase
17+
{
18+
public NormalizeTests() : base(Dialect.PostgreSql) { }
19+
20+
[Test]
21+
public void Can_create_and_populate_tables_without_quotes()
22+
{
23+
using (var db = OpenDbConnection())
24+
{
25+
PostgreSqlDialectProvider.Instance.Normalize = true;
26+
27+
CustomerOrdersUseCase.DropTables(db); //Has conflicting 'Order' table
28+
db.DropTable<Order>();
29+
db.DropTable<CustomerAddress>();
30+
db.DropTable<Customer>();
31+
32+
db.CreateTable<Customer>();
33+
db.CreateTable<CustomerAddress>();
34+
db.CreateTable<Order>();
35+
36+
db.GetLastSql().Print();
37+
38+
var customer = new Customer
39+
{
40+
Name = "Customer 1",
41+
PrimaryAddress = new CustomerAddress
42+
{
43+
AddressLine1 = "1 Humpty Street",
44+
City = "Humpty Doo",
45+
State = "Northern Territory",
46+
Country = "Australia"
47+
},
48+
Orders = new[] {
49+
new Order { LineItem = "Line 1", Qty = 1, Cost = 1.99m },
50+
new Order { LineItem = "Line 2", Qty = 2, Cost = 2.99m },
51+
}.ToList(),
52+
};
53+
54+
db.Save(customer, references: true);
55+
db.GetLastSql().Print();
56+
57+
var dbCustomer = db.SingleById<Customer>(customer.Id);
58+
Assert.That(dbCustomer.Name, Is.EqualTo(customer.Name));
59+
60+
var address = db.Single<CustomerAddress>(x => x.CustomerId == customer.Id && x.Id == customer.PrimaryAddress.Id);
61+
Assert.That(address.Country, Is.EqualTo("Australia"));
62+
63+
var orders = db.Select<Order>(x => x.CustomerId == customer.Id);
64+
var totalQty = orders.Sum(x => x.Qty);
65+
Assert.That(totalQty, Is.EqualTo(3));
66+
67+
PostgreSqlDialectProvider.Instance.Normalize = false;
68+
}
69+
}
70+
}
71+
}

src/ServiceStack.OrmLite.PostgreSQL/PostgreSQLDialectProvider.cs

Lines changed: 140 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,106 @@ public PostgreSqlDialectProvider()
6262
};
6363
}
6464

65+
private bool normalize;
66+
public bool Normalize
67+
{
68+
get => normalize;
69+
set
70+
{
71+
normalize = !value;
72+
NamingStrategy = value
73+
? new OrmLiteNamingStrategyBase()
74+
: new PostgreSqlNamingStrategy();
75+
}
76+
}
77+
78+
//https://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html
79+
public static HashSet<string> ReservedWords = new HashSet<string>(new[]
80+
{
81+
"ALL",
82+
"ANALYSE",
83+
"ANALYZE",
84+
"AND",
85+
"ANY",
86+
"AS",
87+
"ASC",
88+
"AUTHORIZATION",
89+
"BETWEEN",
90+
"BINARY",
91+
"BOTH",
92+
"CASE",
93+
"CAST",
94+
"CHECK",
95+
"COLLATE",
96+
"COLUMN",
97+
"CONSTRAINT",
98+
"CURRENT_DATE",
99+
"CURRENT_TIME",
100+
"CURRENT_TIMESTAMP",
101+
"CURRENT_USER",
102+
"DEFAULT",
103+
"DEFERRABLE",
104+
"DISTINCT",
105+
"DO",
106+
"ELSE",
107+
"END",
108+
"EXCEPT",
109+
"FOR",
110+
"FOREIGN",
111+
"FREEZE",
112+
"FROM",
113+
"FULL",
114+
"HAVING",
115+
"ILIKE",
116+
"IN",
117+
"INITIALLY",
118+
"INNER",
119+
"INTERSECT",
120+
"INTO",
121+
"IS",
122+
"ISNULL",
123+
"JOIN",
124+
"LEADING",
125+
"LEFT",
126+
"LIKE",
127+
"LIMIT",
128+
"LOCALTIME",
129+
"LOCALTIMESTAMP",
130+
"NEW",
131+
"NOT",
132+
"NOTNULL",
133+
"NULL",
134+
"OFF",
135+
"OFFSET",
136+
"OLD",
137+
"ON",
138+
"ONLY",
139+
"OR",
140+
"ORDER",
141+
"OUTER",
142+
"OVERLAPS",
143+
"PLACING",
144+
"PRIMARY",
145+
"REFERENCES",
146+
"RIGHT",
147+
"SELECT",
148+
"SESSION_USER",
149+
"SIMILAR",
150+
"SOME",
151+
"TABLE",
152+
"THEN",
153+
"TO",
154+
"TRAILING",
155+
"TRUE",
156+
"UNION",
157+
"UNIQUE",
158+
"USER",
159+
"USING",
160+
"VERBOSE",
161+
"WHEN",
162+
"WHERE",
163+
}, StringComparer.OrdinalIgnoreCase);
164+
65165
public override string GetColumnDefinition(FieldDefinition fieldDef)
66166
{
67167
if (fieldDef.IsRowVersion)
@@ -160,8 +260,9 @@ public override IDbDataParameter CreateParam()
160260

161261
public override bool DoesTableExist(IDbCommand dbCmd, string tableName, string schema = null)
162262
{
163-
var sql = "SELECT COUNT(*) FROM pg_class WHERE relname = {0}"
164-
.SqlFmt(tableName);
263+
var sql = !Normalize
264+
? "SELECT COUNT(*) FROM pg_class WHERE relname = {0}".SqlFmt(tableName)
265+
: "SELECT COUNT(*) FROM pg_class WHERE lower(relname) = {0}".SqlFmt(tableName.ToLower());
165266

166267
var conn = dbCmd.Connection;
167268
if (conn != null)
@@ -172,8 +273,9 @@ public override bool DoesTableExist(IDbCommand dbCmd, string tableName, string s
172273

173274
// If a search path (schema) is specified, and there is only one, then assume the CREATE TABLE directive should apply to that schema.
174275
if (!string.IsNullOrEmpty(schema) && !schema.Contains(","))
175-
sql = "SELECT COUNT(*) FROM pg_class JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace WHERE relname = {0} AND nspname = {1}"
176-
.SqlFmt(tableName, schema);
276+
sql = !Normalize
277+
? "SELECT COUNT(*) FROM pg_class JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace WHERE relname = {0} AND nspname = {1}".SqlFmt(tableName, schema)
278+
: "SELECT COUNT(*) FROM pg_class JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace WHERE lower(relname) = {0} AND lower(nspname) = {1}".SqlFmt(tableName.ToLower(), schema.ToLower());
177279
}
178280

179281
var result = dbCmd.ExecLongScalar(sql);
@@ -183,11 +285,19 @@ public override bool DoesTableExist(IDbCommand dbCmd, string tableName, string s
183285

184286
public override bool DoesColumnExist(IDbConnection db, string columnName, string tableName, string schema = null)
185287
{
186-
var sql = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @tableName AND COLUMN_NAME = @columnName"
187-
.SqlFmt(tableName, columnName);
288+
var sql = !Normalize
289+
? "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @tableName AND COLUMN_NAME = @columnName".SqlFmt(tableName, columnName)
290+
: "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE lower(TABLE_NAME) = @tableName AND lower(COLUMN_NAME) = @columnName".SqlFmt(tableName.ToLower(), columnName.ToLower());
188291

189292
if (schema != null)
190-
sql += " AND TABLE_SCHEMA = @schema";
293+
{
294+
sql += !Normalize
295+
? " AND TABLE_SCHEMA = @schema"
296+
: " AND lower(TABLE_SCHEMA) = @schema";
297+
298+
if (!Normalize)
299+
schema = schema.ToLower();
300+
}
191301

192302
var result = db.SqlScalar<long>(sql, new { tableName, columnName, schema });
193303

@@ -217,14 +327,31 @@ public override string ToExecuteProcedureStatement(object objWithProperties)
217327
return sql;
218328
}
219329

330+
public override string GetQuotedTableName(string tableName, string schema = null)
331+
{
332+
return !Normalize || ReservedWords.Contains(tableName) || (schema != null && ReservedWords.Contains(schema))
333+
? base.GetQuotedTableName(tableName, schema)
334+
: schema != null
335+
? schema + "." + tableName
336+
: tableName;
337+
}
338+
339+
public override string GetQuotedName(string name)
340+
{
341+
return !Normalize || ReservedWords.Contains(name)
342+
? base.GetQuotedName(name)
343+
: name;
344+
}
345+
220346
public override string GetQuotedTableName(ModelDefinition modelDef)
221347
{
222348
if (!modelDef.IsInSchema)
223-
{
224349
return base.GetQuotedTableName(modelDef);
225-
}
350+
if (!Normalize && !ReservedWords.Contains(modelDef.ModelName) && !ReservedWords.Contains(modelDef.Schema))
351+
return modelDef.Schema + "." + base.NamingStrategy.GetTableName(modelDef.ModelName);
352+
226353
string escapedSchema = modelDef.Schema.Replace(".", "\".\"");
227-
return string.Format("\"{0}\".\"{1}\"", escapedSchema, base.NamingStrategy.GetTableName(modelDef.ModelName));
354+
return $"\"{escapedSchema}\".\"{base.NamingStrategy.GetTableName(modelDef.ModelName)}\"";
228355
}
229356

230357
public override string GetLastInsertIdSqlSuffix<T>()
@@ -236,7 +363,9 @@ public override string GetLastInsertIdSqlSuffix<T>()
236363
{
237364
var modelDef = GetModel(typeof(T));
238365
var pkName = NamingStrategy.GetColumnName(modelDef.PrimaryKey.FieldName);
239-
return $" RETURNING \"{pkName}\"";
366+
return !Normalize
367+
? $" RETURNING \"{pkName}\""
368+
: " RETURNING " + pkName;
240369
}
241370

242371
return "; " + SelectIdentitySql;

tests/ServiceStack.OrmLite.Tests/Issues/JoinAliasIntIssue.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Team
1414
public int? TeamLeaderId { get; set; }
1515
}
1616

17-
class TeamUser
17+
class Teamuser
1818
{
1919
public int Id { get; set; }
2020

@@ -28,7 +28,7 @@ public void Can_create_query_with_int_JoinAlias()
2828
{
2929
using (var db = OpenDbConnection())
3030
{
31-
db.DropAndCreateTable<TeamUser>();
31+
db.DropAndCreateTable<Teamuser>();
3232
db.DropAndCreateTable<Team>();
3333

3434
db.InsertAll(new[] {
@@ -41,13 +41,13 @@ public void Can_create_query_with_int_JoinAlias()
4141

4242
db.InsertAll(new[]
4343
{
44-
new TeamUser
44+
new Teamuser
4545
{
4646
Id = 1,
4747
Name = "User 1",
4848
TeamId = 1
4949
},
50-
new TeamUser
50+
new Teamuser
5151
{
5252
Id = 2,
5353
Name = "User 2",
@@ -60,13 +60,13 @@ public void Can_create_query_with_int_JoinAlias()
6060
where: x => x.Id == 1);
6161

6262
var q = db.From<Team>();
63-
q.Join<TeamUser>((t, u) => t.Id == u.TeamId, db.JoinAlias("TeamUser"));
64-
q.Join<TeamUser>((t, u) => t.TeamLeaderId == u.Id, db.JoinAlias("Leader"));
65-
q.Where<Team, TeamUser>((t, u) => t.Id == Sql.JoinAlias(u.TeamId, "Leader"));
66-
q.Where<TeamUser>(u => Sql.JoinAlias(u.Id, "Leader") == 1);
67-
q.Where<Team, TeamUser>((t, u) => Sql.JoinAlias(t.Id, q.DialectProvider.GetQuotedTableName(ModelDefinition<Team>.Definition)) == Sql.JoinAlias(u.TeamId, "Leader")); // Workaround, but only works for fields, not constants
68-
q.Where<Team, TeamUser>((user, leader) => Sql.JoinAlias(user.Id, "TeamUser") < Sql.JoinAlias(leader.Id, "Leader"));
69-
q.Select<Team, TeamUser, TeamUser>((t, u, l) => new
63+
q.Join<Teamuser>((t, u) => t.Id == u.TeamId, db.JoinAlias("Teamuser"));
64+
q.Join<Teamuser>((t, u) => t.TeamLeaderId == u.Id, db.JoinAlias("Leader"));
65+
q.Where<Team, Teamuser>((t, u) => t.Id == Sql.JoinAlias(u.TeamId, "Leader"));
66+
q.Where<Teamuser>(u => Sql.JoinAlias(u.Id, "Leader") == 1);
67+
q.Where<Team, Teamuser>((t, u) => Sql.JoinAlias(t.Id, q.DialectProvider.GetQuotedTableName(ModelDefinition<Team>.Definition)) == Sql.JoinAlias(u.TeamId, "Leader")); // Workaround, but only works for fields, not constants
68+
q.Where<Team, Teamuser>((user, leader) => Sql.JoinAlias(user.Id, "Teamuser") < Sql.JoinAlias(leader.Id, "Leader"));
69+
q.Select<Team, Teamuser, Teamuser>((t, u, l) => new
7070
{
7171
TeamName = Sql.As(t.Name, "TeamName"),
7272
UserName = Sql.As(u.Name, "UserName"),

0 commit comments

Comments
 (0)