1 /**
2  * DStruct - Object-Relation Mapping for D programming language, with interface similar to Hibernate. 
3  * 
4  * Hibernate documentation can be found here:
5  * $(LINK http://hibernate.org/docs)$(BR)
6  * 
7  * Source file dstruct/dialect.d.
8  *
9  * This module contains declaration of Dialect class - base class for implementing RDBMS specific SQL syntax definitions.
10  * 
11  * Copyright: Copyright 2013
12  * License:   $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
13  * Author:   Vadim Lopatin
14  */
15 module dstruct.dialect;
16 
17 import std.stdio;
18 import std..string;
19 
20 import dstruct.metadata;
21 
22 /// Represents a dialect of SQL implemented by a particular RDBMS. -- generated from JavaDocs on org.hibernate.dialect.Dialect
23 abstract class Dialect
24 {
25 
26     // returns string like "BIGINT(20) NOT NULL" or "VARCHAR(255) NULL"
27     string getColumnTypeDefinition(const PropertyInfo pi, const PropertyInfo overrideTypeFrom = null);
28 
29     // returns string like "`columnname` BIGINT(20) NOT NULL" or "VARCHAR(255) NULL"
30     string getColumnDefinition(const PropertyInfo pi)
31     {
32         return quoteIfNeeded(pi.columnName) ~ " " ~ getColumnTypeDefinition(pi);
33     }
34 
35     ///The character specific to this dialect used to close a quoted identifier.
36     char closeQuote() const;
37     ///The character specific to this dialect used to begin a quoted identifier.
38     char openQuote() const;
39     ///Apply dialect-specific quoting (for `quoted` identifier, replace backtick quotes with dialect specific)
40     string quote(string name) const
41     {
42         //Apply dialect-specific quoting.
43         //By default, the incoming value is checked to see if its first character is the back-tick (`). If so, the dialect specific quoting is applied. 
44         if (name.length <= 2 || name[0] != openQuote)
45             return name;
46         return openQuote() ~ name[1 .. $ - 1] ~ closeQuote();
47     }
48 
49     // should return true for identifiers which cannot be used w/o quote (e.g. keywords)
50     bool needQuote(string ident) const
51     {
52         return (toUpper(ident) in keywordList) !is null;
53     }
54 
55     string quoteIfNeeded(string ident) const
56     {
57         if (needQuote(ident))
58             return quote(openQuote ~ ident ~ closeQuote);
59         return quote(ident);
60     }
61 
62     protected int[string] keywordList;
63 
64     protected void addKeywords(string[] keywords)
65     {
66         foreach (s; keywords)
67         {
68             keywordList[s] = 1;
69         }
70     }
71 
72     string getDropIndexSQL(string tableName, string indexName)
73     {
74         return "DROP INDEX " ~ quoteIfNeeded(indexName) ~ " ON " ~ quoteIfNeeded(tableName);
75     }
76 
77     string getDropForeignKeySQL(string tableName, string indexName)
78     {
79         return "ALTER TABLE " ~ quoteIfNeeded(
80                 tableName) ~ " DROP FOREIGN KEY " ~ quoteIfNeeded(indexName);
81     }
82 
83     string getIndexSQL(string tableName, string indexName, string[] columnNames)
84     {
85         return "CREATE INDEX " ~ quoteIfNeeded(indexName) ~ " ON " ~ quoteIfNeeded(
86                 tableName) ~ createFieldListSQL(columnNames);
87     }
88 
89     string getUniqueIndexSQL(string tableName, string indexName, string[] columnNames)
90     {
91         return "CREATE UNIQUE INDEX " ~ quoteIfNeeded(indexName) ~ " ON " ~ quoteIfNeeded(
92                 tableName) ~ createFieldListSQL(columnNames);
93     }
94 
95     string getForeignKeySQL(string tableName, string indexName, string[] columnNames,
96             string referencedTableName, string[] referencedFieldNames)
97     {
98         assert(columnNames.length == referencedFieldNames.length);
99         return "ALTER TABLE " ~ quoteIfNeeded(tableName) ~ " ADD CONSTRAINT " ~ quoteIfNeeded(
100                 indexName) ~ " FOREIGN KEY " ~ createFieldListSQL(
101                 columnNames) ~ " REFERENCES " ~ quoteIfNeeded(
102                 referencedTableName) ~ createFieldListSQL(referencedFieldNames);
103     }
104 
105     string getCheckTableExistsSQL(string tableName)
106     {
107         return "SHOW TABLES LIKE " ~ quoteSqlString(tableName);
108     }
109 
110     string getUniqueIndexItemSQL(string indexName, string[] columnNames)
111     {
112         return "UNIQUE INDEX " ~ quoteIfNeeded(indexName) ~ " " ~ createFieldListSQL(columnNames);
113     }
114 
115     /// for some of RDBMS it's necessary to pass additional clauses in query to get generated value (e.g. in Postgres - " returing id"
116     string appendInsertToFetchGeneratedKey(string query, const EntityInfo entity)
117     {
118         return query;
119     }
120 
121     /// returns comma separated quoted identifier list in () parenthesis
122     string createFieldListSQL(string[] fields)
123     {
124         string res;
125         foreach (s; fields)
126         {
127             if (res.length > 0)
128                 res ~= ", ";
129             res ~= quoteIfNeeded(s);
130         }
131         return "(" ~ res ~ ")";
132     }
133 
134     char getStringQuoteChar()
135     {
136         return '\'';
137     }
138 
139     string quoteSqlString(string s)
140     {
141         string res = "'";
142         foreach (ch; s)
143         {
144             switch (ch)
145             {
146             case '\'':
147                 res ~= "\\\'";
148                 break;
149             case '\"':
150                 res ~= "\\\"";
151                 break;
152             case '\\':
153                 res ~= "\\\\";
154                 break;
155             case '\0':
156                 res ~= "\\n";
157                 break;
158             case '\a':
159                 res ~= "\\a";
160                 break;
161             case '\b':
162                 res ~= "\\b";
163                 break;
164             case '\f':
165                 res ~= "\\f";
166                 break;
167             case '\n':
168                 res ~= "\\n";
169                 break;
170             case '\r':
171                 res ~= "\\r";
172                 break;
173             case '\t':
174                 res ~= "\\t";
175                 break;
176             case '\v':
177                 res ~= "\\v";
178                 break;
179             default:
180                 res ~= ch;
181             }
182         }
183         res ~= "'";
184         //writeln("quoted " ~ s ~ " is " ~ res);
185         return res;
186     }
187 
188     /+
189     ///Provided we supportsInsertSelectIdentity(), then attach the "select identity" clause to the insert statement.
190     string     appendIdentitySelectToInsert(string insertString);
191     ///Some dialects support an alternative means to SELECT FOR UPDATE, whereby a "lock hint" is appends to the table name in the from clause.
192     string     appendLockHint(LockMode mode, string tableName);
193     ///Modifies the given SQL by applying the appropriate updates for the specified lock modes and key columns.
194     string     applyLocksToSql(string sql, LockOptions aliasedLockOptions, Map keyColumnNames);
195     ///Are string comparisons implicitly case insensitive.
196     bool    areStringComparisonsCaseInsensitive();
197     ///Does the LIMIT clause come at the start of the SELECT statement, rather than at the end?
198     bool    bindLimitParametersFirst();
199     ///ANSI SQL defines the LIMIT clause to be in the form LIMIT offset, limit.
200     bool    bindLimitParametersInReverseOrder();
201     ///Build an instance of the SQLExceptionConverter preferred by this dialect for converting SQLExceptions into Hibernate's JDBCException hierarchy.
202     SQLExceptionConverter  buildSQLExceptionConverter();
203     ///Hibernate APIs explicitly state that setFirstResult() should be a zero-based offset.
204     int    convertToFirstRowValue(int zeroBasedFirstResult);
205     ///Create a CaseFragment strategy responsible for handling this dialect's variations in how CASE statements are handled.
206     CaseFragment   createCaseFragment();
207     ///Create a JoinFragment strategy responsible for handling this dialect's variations in how joins are handled.
208     JoinFragment   createOuterJoinFragment();
209     ///For the underlying database, is READ_COMMITTED isolation implemented by forcing readers to wait for write locks to be released?
210     bool    doesReadCommittedCauseWritersToBlockReaders();
211     ///For the underlying database, is REPEATABLE_READ isolation implemented by forcing writers to wait for read locks to be released?
212     bool    doesRepeatableReadCauseReadersToBlockWriters();
213     ///Do we need to drop constraints before dropping tables in this dialect?
214     bool    dropConstraints();
215     ///Do we need to drop the temporary table after use?
216     bool    dropTemporaryTableAfterUse();
217     ///Generally, if there is no limit applied to a Hibernate query we do not apply any limits to the SQL query.
218     bool    forceLimitUsage();
219     ///Is FOR UPDATE OF syntax supported?
220     bool    forUpdateOfColumns();
221     ///Generate a temporary table name given the bas table.
222     string     generateTemporaryTableName(string baseTableName);
223     ///The syntax used to add a column to a table (optional).
224     string     getAddColumnString();
225     ///The syntax used to add a foreign key constraint to a table.
226     string     getAddForeignKeyConstraintString(string constraintName, string[] foreignKey, string referencedTable, string[] primaryKey, bool referencesPrimaryKey);
227     ///The syntax used to add a primary key constraint to a table.
228     string     getAddPrimaryKeyConstraintString(string constraintName);
229     ///Completely optional cascading drop clause
230     string     getCascadeConstraintsString();
231     ///Get the name of the database type appropriate for casting operations (via the CAST() SQL function) for the given Types typecode.
232     string     getCastTypeName(int code);
233     string     getColumnComment(string comment);
234 
235     ///Slight variation on getCreateTableString().
236     string     getCreateMultisetTableString();
237     ///Typically dialects which support sequences can create a sequence with a single command.
238     protected  string   getCreateSequenceString(string sequenceName);
239     ///Overloaded form of getCreateSequenceString(string), additionally taking the initial value and increment size to be applied to the sequence definition.
240     protected  string   getCreateSequenceString(string sequenceName, int initialValue, int incrementSize);
241     ///Deprecated. Use getCreateSequenceString(string, int, int) instead
242     string[]   getCreateSequenceStrings(string sequenceName);
243     ///An optional multi-line form for databases which supportsPooledSequences().
244     string[]   getCreateSequenceStrings(string sequenceName, int initialValue, int incrementSize);
245     ///Command used to create a table.
246     string     getCreateTableString();
247     ///Get any fragments needing to be postfixed to the command for temporary table creation.
248     string     getCreateTemporaryTablePostfix();
249     ///Command used to create a temporary table.
250     string     getCreateTemporaryTableString();
251     ///Get the separator to use for defining cross joins when translating HQL queries.
252     string     getCrossJoinSeparator();
253     ///Retrieve the command used to retrieve the current timestamp from the database.
254     string     getCurrentTimestampSelectString();
255     ///The name of the database-specific SQL function for retrieving the current timestamp.
256     string     getCurrentTimestampSQLFunctionName();
257     ///Retrieve a set of default Hibernate properties for this database.
258     Properties     getDefaultProperties();
259     ///Get an instance of the dialect specified by the current System properties.
260     static Dialect  getDialect();
261     ///Get an instance of the dialect specified by the given properties or by the current System properties.
262     static Dialect  getDialect(Properties props);
263     string     getDropForeignKeyString();
264 
265     ///Typically dialects which support sequences can drop a sequence with a single command.
266     protected  string   getDropSequenceString(string sequenceName);
267     ///The multiline script used to drop a sequence.
268     string[]   getDropSequenceStrings(string sequenceName);
269     ///Command used to drop a temporary table.
270     string     getDropTemporaryTableString();
271     ///Retrieves the FOR UPDATE NOWAIT syntax specific to this dialect.
272     string     getForUpdateNowaitString();
273     ///Get the FOR UPDATE OF column_list NOWAIT fragment appropriate for this dialect given the aliases of the columns to be write locked.
274     string     getForUpdateNowaitString(string aliases);
275     ///Get the string to append to SELECT statements to acquire locks for this dialect.
276     string     getForUpdateString();
277     ///Given a lock mode, determine the appropriate for update fragment to use.
278     string     getForUpdateString(LockMode lockMode);
279     ///Given LockOptions (lockMode, timeout), determine the appropriate for update fragment to use.
280     string     getForUpdateString(LockOptions lockOptions);
281     ///Get the FOR UPDATE OF column_list fragment appropriate for this dialect given the aliases of the columns to be write locked.
282     string     getForUpdateString(string aliases);
283     ///Get the FOR UPDATE OF column_list fragment appropriate for this dialect given the aliases of the columns to be write locked.
284     string     getForUpdateString(string aliases, LockOptions lockOptions);
285     ///Retrieves a map of the dialect's registered functions (functionName => SQLFunction).
286     Map    getFunctions();
287     ///Get the name of the Hibernate Type associated with the given Types typecode.
288     string     getHibernateTypeName(int code);
289     ///Get the name of the Hibernate Type associated with the given Types typecode with the given storage specification parameters.
290     string     getHibernateTypeName(int code, int length, int precision, int scale);
291     ///The syntax used during DDL to define a column as being an IDENTITY.
292     protected  string   getIdentityColumnString();
293     ///The syntax used during DDL to define a column as being an IDENTITY of a particular type.
294     string     getIdentityColumnString(int type);
295     ///The keyword used to insert a generated value into an identity column (or null).
296     string     getIdentityInsertString();
297     ///Get the select command to use to retrieve the last generated IDENTITY value.
298     protected  string   getIdentitySelectString();
299     ///Get the select command to use to retrieve the last generated IDENTITY value for a particular table
300     string     getIdentitySelectString(string table, string column, int type);
301     Set    getKeywords();
302 
303     ///Apply s limit clause to the query.
304     protected  string   getLimitString(string query, bool hasOffset);
305     ///Given a limit and an offset, apply the limit clause to the query.
306     string     getLimitString(string query, int offset, int limit);
307     ///Get a strategy instance which knows how to acquire a database-level lock of the specified mode for this dialect.
308     LockingStrategy    getLockingStrategy(Lockable lockable, LockMode lockMode);
309     ///The name of the SQL function that transforms a string to lowercase
310     string     getLowercaseFunction();
311     ///What is the maximum length Hibernate can use for generated aliases?
312     int    getMaxAliasLength();
313     ///The class (which implements IdentifierGenerator) which acts as this dialects native generation strategy.
314     Class  getNativeIdentifierGeneratorClass();
315     ///The fragment used to insert a row without specifying any column values.
316     string     getNoColumnsInsertString();
317     ///The keyword used to specify a nullable column.
318     string     getNullColumnString();
319     ///Get the select command used retrieve the names of all sequences.
320     string     getQuerySequencesString();
321     ///Get the string to append to SELECT statements to acquire WRITE locks for this dialect.
322     string     getReadLockString(int timeout);
323     ///Given a callable statement previously processed by registerResultSetOutParameter(java.sql.CallableStatement, int), extract the ResultSet from the OUT parameter.
324     ResultSet  getResultSet(CallableStatement statement);
325     ///Given a Types type code, determine an appropriate null value to use in a select clause.
326     string     getSelectClauseNullString(int sqlType);
327     ///Get the command used to select a GUID from the underlying database.
328     string     getSelectGUIDString();
329     ///Generate the select expression fragment that will retrieve the next value of a sequence as part of another (typically DML) statement.
330     string     getSelectSequenceNextValString(string sequenceName);
331     ///Generate the appropriate select statement to to retrieve the next value of a sequence.
332     string     getSequenceNextValString(string sequenceName);
333     string     getTableComment(string comment);
334 
335     string     getTableTypeString();
336 
337     ///Get the name of the database type associated with the given Types typecode.
338     string     getTypeName(int code);
339     ///Get the name of the database type associated with the given Types typecode with the given storage specification parameters.
340     string     getTypeName(int code, int length, int precision, int scale);
341     ViolatedConstraintNameExtracter    getViolatedConstraintNameExtracter();
342 
343     ///Get the string to append to SELECT statements to acquire WRITE locks for this dialect.
344     string     getWriteLockString(int timeout);
345     ///Does this dialect support the ALTER TABLE syntax?
346     bool    hasAlterTable();
347     ///Whether this dialect have an Identity clause added to the data type or a completely separate identity data type
348     bool    hasDataTypeInIdentityColumn();
349     bool    hasSelfReferentialForeignKeyBug();
350 
351     ///Should the value returned by getCurrentTimestampSelectString() be treated as callable.
352     bool    isCurrentTimestampSelectStringCallable();
353     ///If this dialect supports specifying lock timeouts, are those timeouts rendered into the SQL string as parameters.
354     bool    isLockTimeoutParameterized();
355     ///The character specific to this dialect used to begin a quoted identifier.
356     char   openQuote();
357     ///Does the dialect require that temporary table DDL statements occur in isolation from other statements? This would be the case if the creation would cause any current transaction to get committed implicitly.
358     bool    performTemporaryTableDDLInIsolation();
359     ///Do we need to qualify index names with the schema name?
360     bool    qualifyIndexName();
361     ///Subclasses register a type name for the given type code and maximum column length.
362     protected  void     registerColumnType(int code, int capacity, string name);
363     ///Subclasses register a type name for the given type code.
364     protected  void     registerColumnType(int code, string name);
365     protected  void     registerFunction(string name, SQLFunction function);
366 
367     ///Registers a Hibernate Type name for the given Types type code and maximum column length.
368     protected  void     registerHibernateType(int code, int capacity, string name);
369     ///Registers a Hibernate Type name for the given Types type code.
370     protected  void     registerHibernateType(int code, string name);
371     protected  void     registerKeyword(string word);
372 
373     ///Registers an OUT parameter which will be returning a ResultSet.
374     int    registerResultSetOutParameter(CallableStatement statement, int position);
375     ///Does this dialect require that parameters appearing in the SELECT clause be wrapped in cast() calls to tell the db parser the expected type.
376     bool    requiresCastingOfParametersInSelectClause();
377     ///Does this dialect support using a JDBC bind parameter as an argument to a function or procedure call?
378     bool    supportsBindAsCallableArgument();
379     bool    supportsCascadeDelete();
380 
381     ///Does this dialect support definition of cascade delete constraints which can cause circular chains?
382     bool    supportsCircularCascadeDeleteConstraints();
383     ///Does this dialect support column-level check constraints?
384     bool    supportsColumnCheck();
385     bool    supportsCommentOn();
386 
387     ///Does this dialect support a way to retrieve the database's current timestamp value?
388     bool    supportsCurrentTimestampSelection();
389     ///Does this dialect support empty IN lists? For example, is [where XYZ in ()] a supported construct?
390     bool    supportsEmptyInList();
391     ///Does the dialect support an exists statement in the select clause?
392     bool    supportsExistsInSelect();
393     ///Expected LOB usage pattern is such that I can perform an insert via prepared statement with a parameter binding for a LOB value without crazy casting to JDBC driver implementation-specific classes...
394     bool    supportsExpectedLobUsagePattern();
395     ///Does this dialect support identity column key generation?
396     bool    supportsIdentityColumns();
397     bool    supportsIfExistsAfterTableName();
398 
399     bool    supportsIfExistsBeforeTableName();
400 
401     ///Does the dialect support some form of inserting and selecting the generated IDENTITY value all in the same statement.
402     bool    supportsInsertSelectIdentity();
403     ///Does this dialect support some form of limiting query results via a SQL clause?
404     bool    supportsLimit();
405     ///Does this dialect's LIMIT support (if any) additionally support specifying an offset?
406     bool    supportsLimitOffset();
407     ///Does the dialect support propogating changes to LOB values back to the database? Talking about mutating the internal value of the locator as opposed to supplying a new locator instance...
408     bool    supportsLobValueChangePropogation();
409     ///Informational metadata about whether this dialect is known to support specifying timeouts for requested lock acquisitions.
410     bool    supportsLockTimeouts();
411     bool    supportsNotNullUnique();
412 
413     ///Does this dialect support FOR UPDATE in conjunction with outer joined rows?
414     bool    supportsOuterJoinForUpdate();
415     ///Does this dialect support parameters within the SELECT clause of INSERT ...
416     bool    supportsParametersInInsertSelect();
417     ///Does this dialect support "pooled" sequences.
418     bool    supportsPooledSequences();
419     ///Does this dialect support asking the result set its positioning information on forward only cursors.
420     bool    supportsResultSetPositionQueryMethodsOnForwardOnlyCursor();
421     ///Is this dialect known to support what ANSI-SQL terms "row value constructor" syntax; sometimes called tuple syntax.
422     bool    supportsRowValueConstructorSyntax();
423     ///If the dialect supports row values, does it offer such support in IN lists as well? For example, "...
424     bool    supportsRowValueConstructorSyntaxInInList();
425     ///Does this dialect support sequences?
426     bool    supportsSequences();
427     ///Does this dialect support referencing the table being mutated in a subquery.
428     bool    supportsSubqueryOnMutatingTable();
429     ///Are subselects supported as the left-hand-side (LHS) of IN-predicates.
430     bool    supportsSubselectAsInPredicateLHS();
431     ///Does this dialect support table-level check constraints?
432     bool    supportsTableCheck();
433     ///Does this dialect support temporary tables?
434     bool    supportsTemporaryTables();
435     ///Does this dialect support `count(a,b)`?
436     bool    supportsTupleCounts();
437     ///Does this dialect support `count(distinct a,b)`?
438     bool    supportsTupleDistinctCounts();
439     ///Is it supported to materialize a LOB locator outside the transaction in which it was created? Again, part of the trickiness here is the fact that this is largely driver dependent.
440     bool    supportsUnboundedLobLocatorMaterialization();
441     ///Does this dialect support UNION ALL, which is generally a faster variant of UNION?
442     bool    supportsUnionAll();
443     ///Does this dialect support the UNIQUE column syntax?
444     bool    supportsUnique();
445     ///Does this dialect support adding Unique constraints via create and alter table ?
446     bool    supportsUniqueConstraintInCreateAlterTable();
447     ///Does this dialect support bind variables (i.e., prepared statement parameters) for its limit/offset?
448     bool    supportsVariableLimit();
449     ///The SQL literal value to which this database maps bool values.
450     string     toBooleanValueString(bool bool);
451     ///Meant as a means for end users to affect the select strings being sent to the database and perhaps manipulate them in some fashion.
452     string     transformSelectString(string select);
453     ///Should LOBs (both BLOB and CLOB) be bound using stream operations (i.e.
454     bool    useInputStreamToInsertBlob();
455     ///Does the LIMIT clause take a "maximum" row number instead of a total number of returned rows? This is easiest understood via an example.
456     bool    useMaxForLimit();
457 +/
458 }
459 
460 version (USE_MYSQL)
461 {
462 }
463 else version (USE_SQLITE)
464 {
465 }
466 else version (USE_PGSQL)
467 {
468 }
469 else
470 {
471     pragma(msg, "No DB type version definition specified.
472                 Add one or more versions to command line: USE_MYSQL, USE_PGSQL, USE_SQLITE");
473 }