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