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