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}