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 }