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