001package com.avaje.ebean.config.dbplatform; 002 003import com.avaje.ebean.BackgroundExecutor; 004import com.avaje.ebean.Query; 005import com.avaje.ebean.config.PersistBatch; 006import com.avaje.ebean.config.ServerConfig; 007import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler; 008import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import javax.sql.DataSource; 013import java.sql.Connection; 014import java.sql.DatabaseMetaData; 015import java.sql.ResultSet; 016import java.sql.SQLException; 017import java.sql.Types; 018 019/** 020 * Database platform specific settings. 021 */ 022public class DatabasePlatform { 023 024 private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class); 025 026 /** 027 * Behavior used when ending a query only transaction (at read committed isolation level). 028 */ 029 public enum OnQueryOnly { 030 031 /** 032 * Rollback the transaction. 033 */ 034 ROLLBACK, 035 036 /** 037 * Just close the transaction. Valid at READ_COMMITTED isolation and preferred on some Databases 038 * as a performance optimisation. 039 */ 040 CLOSE, 041 042 /** 043 * Commit the transaction 044 */ 045 COMMIT 046 } 047 048 049 /** 050 * Set to true for MySql, no other jdbc drivers need this workaround. 051 */ 052 protected boolean useExtraTransactionOnIterateSecondaryQueries; 053 054 /** 055 * The behaviour used when ending a read only transaction at read committed isolation level. 056 */ 057 protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK; 058 059 /** 060 * The open quote used by quoted identifiers. 061 */ 062 protected String openQuote = "\""; 063 064 /** 065 * The close quote used by quoted identifiers. 066 */ 067 protected String closeQuote = "\""; 068 069 /** 070 * For limit/offset, row_number etc limiting of SQL queries. 071 */ 072 protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter(); 073 074 /** 075 * Limit/offset support for SqlQuery only. 076 */ 077 protected BasicSqlLimiter basicSqlLimiter = new BasicSqlLimitOffset(); 078 079 /** 080 * Mapping of JDBC to Database types. 081 */ 082 protected DbTypeMap dbTypeMap = new DbTypeMap(); 083 084 /** 085 * Default values for DB columns. 086 */ 087 protected DbDefaultValue dbDefaultValue = new DbDefaultValue(); 088 089 /** 090 * Set to true if the DB has native UUID type support. 091 */ 092 protected boolean nativeUuidType; 093 094 /** 095 * Defines DB identity/sequence features. 096 */ 097 protected DbIdentity dbIdentity = new DbIdentity(); 098 099 /** 100 * The history support for this database platform. 101 */ 102 protected DbHistorySupport historySupport; 103 104 /** 105 * The JDBC type to map booleans to (by default). 106 */ 107 protected int booleanDbType = Types.BOOLEAN; 108 109 /** 110 * The JDBC type to map Blob to. 111 */ 112 protected int blobDbType = Types.BLOB; 113 114 /** 115 * The JDBC type to map Clob to. 116 */ 117 protected int clobDbType = Types.CLOB; 118 119 /** 120 * For Oracle treat empty strings as null. 121 */ 122 protected boolean treatEmptyStringsAsNull; 123 124 /** 125 * The database platform name. 126 */ 127 protected String name = "generic"; 128 129 protected String columnAliasPrefix = "c"; 130 131 protected String tableAliasPlaceHolder = "${ta}"; 132 133 /** 134 * Use a BackTick ` at the beginning and end of table or column names that you 135 * want to use quoted identifiers for. The backticks get converted to the 136 * appropriate characters in convertQuotedIdentifiers 137 */ 138 private static final char BACK_TICK = '`'; 139 140 /** 141 * The like clause. Can be overridden to disable default escape character. 142 */ 143 protected String likeClause = "like ?"; 144 145 protected DbEncrypt dbEncrypt; 146 147 protected boolean idInExpandedForm; 148 149 protected boolean selectCountWithAlias; 150 151 /** 152 * If set then use the FORWARD ONLY hint when creating ResultSets for 153 * findIterate() and findVisit(). 154 */ 155 protected boolean forwardOnlyHintOnFindIterate; 156 157 /** 158 * By default we use JDBC batch when cascading (except for SQL Server). 159 */ 160 protected PersistBatch persistBatchOnCascade = PersistBatch.ALL; 161 162 protected PlatformDdl platformDdl; 163 164 /** 165 * The maximum length of table names - used specifically when derived 166 * default table names for intersection tables. 167 */ 168 protected int maxTableNameLength = 60; 169 170 /** 171 * A value of 60 is a reasonable default for all databases except 172 * Oracle (limited to 30) and DB2 (limited to 18). 173 */ 174 protected int maxConstraintNameLength = 60; 175 176 protected boolean supportsNativeIlike; 177 178 /** 179 * Instantiates a new database platform. 180 */ 181 public DatabasePlatform() { 182 } 183 184 /** 185 * Configure UUID Storage etc based on ServerConfig settings. 186 */ 187 public void configure(ServerConfig serverConfig) { 188 dbTypeMap.config(nativeUuidType, serverConfig.getDbUuid()); 189 } 190 191 /** 192 * Return the name of the DatabasePlatform. 193 * <p> 194 * "generic" is returned when no specific database platform has been set or 195 * found. 196 * </p> 197 */ 198 public String getName() { 199 return name; 200 } 201 202 /** 203 * Return true if this database platform supports native ILIKE expression. 204 */ 205 public boolean isSupportsNativeIlike() { 206 return supportsNativeIlike; 207 } 208 209 /** 210 * Return the maximum table name length. 211 * <p> 212 * This is used when deriving names of intersection tables. 213 * </p> 214 */ 215 public int getMaxTableNameLength() { 216 return maxTableNameLength; 217 } 218 219 /** 220 * Return the maximum constraint name allowed for the platform. 221 */ 222 public int getMaxConstraintNameLength() { 223 return maxConstraintNameLength; 224 } 225 226 /** 227 * Return the platform specific DDL. 228 */ 229 public PlatformDdl getPlatformDdl() { 230 return platformDdl; 231 } 232 233 /** 234 * Create and return a DDL handler for generating DDL scripts. 235 */ 236 public DdlHandler createDdlHandler(ServerConfig serverConfig) { 237 return platformDdl.createDdlHandler(serverConfig); 238 } 239 240 /** 241 * Return true if the JDBC driver does not allow additional queries to execute 242 * when a resultSet is being 'streamed' as is the case with findEach() etc. 243 * <p> 244 * Honestly, this is a workaround for a stupid MySql JDBC driver limitation. 245 * </p> 246 */ 247 public boolean useExtraTransactionOnIterateSecondaryQueries() { 248 return useExtraTransactionOnIterateSecondaryQueries; 249 } 250 251 /** 252 * Return a DB Sequence based IdGenerator. 253 * 254 * @param be the BackgroundExecutor that can be used to load the sequence if 255 * desired 256 * @param ds the DataSource 257 * @param seqName the name of the sequence 258 * @param batchSize the number of sequences that should be loaded 259 */ 260 public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) { 261 return null; 262 } 263 264 /** 265 * Return the behaviour to use when ending a read only transaction. 266 */ 267 public OnQueryOnly getOnQueryOnly() { 268 return onQueryOnly; 269 } 270 271 /** 272 * Set the behaviour to use when ending a read only transaction. 273 */ 274 public void setOnQueryOnly(OnQueryOnly onQueryOnly) { 275 this.onQueryOnly = onQueryOnly; 276 } 277 278 /** 279 * Return the DbEncrypt handler for this DB platform. 280 */ 281 public DbEncrypt getDbEncrypt() { 282 return dbEncrypt; 283 } 284 285 /** 286 * Set the DbEncrypt handler for this DB platform. 287 */ 288 public void setDbEncrypt(DbEncrypt dbEncrypt) { 289 this.dbEncrypt = dbEncrypt; 290 } 291 292 /** 293 * Return the history support for this database platform. 294 */ 295 public DbHistorySupport getHistorySupport() { 296 return historySupport; 297 } 298 299 /** 300 * Set the history support for this database platform. 301 */ 302 public void setHistorySupport(DbHistorySupport historySupport) { 303 this.historySupport = historySupport; 304 } 305 306 /** 307 * Return true if the DB supports native UUID. 308 */ 309 public boolean isNativeUuidType() { 310 return nativeUuidType; 311 } 312 313 /** 314 * Return the mapping of JDBC to DB types. 315 * 316 * @return the db type map 317 */ 318 public DbTypeMap getDbTypeMap() { 319 return dbTypeMap; 320 } 321 322 /** 323 * Return the mapping for DB column default values. 324 */ 325 public DbDefaultValue getDbDefaultValue() { 326 return dbDefaultValue; 327 } 328 329 /** 330 * Return the column alias prefix. 331 */ 332 public String getColumnAliasPrefix() { 333 return columnAliasPrefix; 334 } 335 336 /** 337 * Set the column alias prefix. 338 */ 339 public void setColumnAliasPrefix(String columnAliasPrefix) { 340 this.columnAliasPrefix = columnAliasPrefix; 341 } 342 343 /** 344 * Return the table alias placeholder. 345 */ 346 public String getTableAliasPlaceHolder() { 347 return tableAliasPlaceHolder; 348 } 349 350 /** 351 * Set the table alias placeholder. 352 */ 353 public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) { 354 this.tableAliasPlaceHolder = tableAliasPlaceHolder; 355 } 356 357 /** 358 * Return the close quote for quoted identifiers. 359 * 360 * @return the close quote 361 */ 362 public String getCloseQuote() { 363 return closeQuote; 364 } 365 366 /** 367 * Return the open quote for quoted identifiers. 368 * 369 * @return the open quote 370 */ 371 public String getOpenQuote() { 372 return openQuote; 373 } 374 375 /** 376 * Return the JDBC type used to store booleans. 377 * 378 * @return the boolean db type 379 */ 380 public int getBooleanDbType() { 381 return booleanDbType; 382 } 383 384 /** 385 * Return the data type that should be used for Blob. 386 * <p> 387 * This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for 388 * example. 389 * </p> 390 */ 391 public int getBlobDbType() { 392 return blobDbType; 393 } 394 395 /** 396 * Return the data type that should be used for Clob. 397 * <p> 398 * This is typically Types.CLOB but for Postgres is Types.VARCHAR. 399 * </p> 400 */ 401 public int getClobDbType() { 402 return clobDbType; 403 } 404 405 /** 406 * Return true if empty strings should be treated as null. 407 * 408 * @return true, if checks if is treat empty strings as null 409 */ 410 public boolean isTreatEmptyStringsAsNull() { 411 return treatEmptyStringsAsNull; 412 } 413 414 /** 415 * Return true if a compound ID in (...) type expression needs to be in 416 * expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in 417 * ((?,?),(?,?),...); 418 */ 419 public boolean isIdInExpandedForm() { 420 return idInExpandedForm; 421 } 422 423 /** 424 * Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on 425 * findIterate() and findVisit() PreparedStatements. 426 * <p> 427 * This specifically is required for MySql when processing large results. 428 * </p> 429 */ 430 public boolean isForwardOnlyHintOnFindIterate() { 431 return forwardOnlyHintOnFindIterate; 432 } 433 434 /** 435 * Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements. 436 */ 437 public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) { 438 this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate; 439 } 440 441 /** 442 * Return the DB identity/sequence features for this platform. 443 * 444 * @return the db identity 445 */ 446 public DbIdentity getDbIdentity() { 447 return dbIdentity; 448 } 449 450 /** 451 * Return the SqlLimiter used to apply additional sql around a query to limit 452 * its results. 453 * <p> 454 * Basically add the clauses for limit/offset, rownum, row_number(). 455 * </p> 456 * 457 * @return the sql limiter 458 */ 459 public SqlLimiter getSqlLimiter() { 460 return sqlLimiter; 461 } 462 463 /** 464 * Return the BasicSqlLimiter for limit/offset of SqlQuery queries. 465 */ 466 public BasicSqlLimiter getBasicSqlLimiter() { 467 return basicSqlLimiter; 468 } 469 470 /** 471 * Set the DB TRUE literal (from the registered boolean ScalarType) 472 */ 473 public void setDbTrueLiteral(String dbTrueLiteral) { 474 this.dbDefaultValue.setTrue(dbTrueLiteral); 475 } 476 477 /** 478 * Set the DB FALSE literal (from the registered boolean ScalarType) 479 */ 480 public void setDbFalseLiteral(String dbFalseLiteral) { 481 this.dbDefaultValue.setFalse(dbFalseLiteral); 482 } 483 484 /** 485 * Convert backticks to the platform specific open quote and close quote 486 * <p> 487 * Specific plugins may implement this method to cater for platform specific 488 * naming rules. 489 * </p> 490 * 491 * @param dbName the db name 492 * @return the string 493 */ 494 public String convertQuotedIdentifiers(String dbName) { 495 // Ignore null values e.g. schema name or catalog 496 if (dbName != null && !dbName.isEmpty()) { 497 if (dbName.charAt(0) == BACK_TICK) { 498 if (dbName.charAt(dbName.length() - 1) == BACK_TICK) { 499 500 String quotedName = getOpenQuote(); 501 quotedName += dbName.substring(1, dbName.length() - 1); 502 quotedName += getCloseQuote(); 503 504 return quotedName; 505 506 } else { 507 logger.error("Missing backquote on [" + dbName + "]"); 508 } 509 } 510 } 511 return dbName; 512 } 513 514 /** 515 * Set to true if select count against anonymous view requires an alias. 516 */ 517 public boolean isSelectCountWithAlias() { 518 return selectCountWithAlias; 519 } 520 521 public String completeSql(String sql, Query<?> query) { 522 if (Boolean.TRUE.equals(query.isForUpdate())) { 523 sql = withForUpdate(sql); 524 } 525 526 return sql; 527 } 528 529 protected String withForUpdate(String sql) { 530 // silently assume the database does not support the "for update" clause. 531 logger.info("it seems your database does not support the 'for update' clause"); 532 533 return sql; 534 } 535 536 /** 537 * Returns the like clause used by this database platform. 538 * <p> 539 * This may include an escape clause to disable a default escape character. 540 */ 541 public String getLikeClause() { 542 return likeClause; 543 } 544 545 /** 546 * Return the platform default JDBC batch mode for persist cascade. 547 */ 548 public PersistBatch getPersistBatchOnCascade() { 549 return persistBatchOnCascade; 550 } 551 552 /** 553 * Return true if the table exists. 554 */ 555 public boolean tableExists(Connection connection, String catalog, String schema, String table) throws SQLException { 556 557 DatabaseMetaData metaData = connection.getMetaData(); 558 ResultSet tables = metaData.getTables(catalog, schema, table, null); 559 try { 560 return tables.next(); 561 } finally { 562 close(tables); 563 } 564 } 565 566 /** 567 * Close the resultSet. 568 */ 569 protected void close(ResultSet resultSet) { 570 try { 571 resultSet.close(); 572 } catch (SQLException e) { 573 logger.error("Error closing resultSet", e); 574 } 575 } 576}