/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.eclipse.ecr.core.storage.sql.jdbc.dialect; import java.io.Serializable; import java.net.SocketException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Array; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.ecr.core.storage.StorageException; import org.eclipse.ecr.core.storage.sql.Binary; import org.eclipse.ecr.core.storage.sql.BinaryManager; import org.eclipse.ecr.core.storage.sql.ColumnType; import org.eclipse.ecr.core.storage.sql.Model; import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor; import org.eclipse.ecr.core.storage.sql.jdbc.QueryMaker.QueryMakerException; import org.eclipse.ecr.core.storage.sql.jdbc.db.Column; import org.eclipse.ecr.core.storage.sql.jdbc.db.Database; import org.eclipse.ecr.core.storage.sql.jdbc.db.Join; import org.eclipse.ecr.core.storage.sql.jdbc.db.Table; import org.eclipse.ecr.core.storage.sql.jdbc.dialect.Dialect.FulltextQuery.Op; import org.nuxeo.common.utils.StringUtils; /** * A Dialect encapsulates knowledge about database-specific behavior. * * @author Florent Guillaume */ public abstract class Dialect { public static final class JDBCInfo { public final String string; public final int jdbcType; public JDBCInfo(String string, int jdbcType) { this.string = string; this.jdbcType = jdbcType; } } public static JDBCInfo jdbcInfo(String string, int jdbcType) { return new JDBCInfo(string, jdbcType); } public static JDBCInfo jdbcInfo(String string, int length, int jdbcType) { return new JDBCInfo(String.format(string, Integer.valueOf(length)), jdbcType); } protected final BinaryManager binaryManager; protected final boolean storesUpperCaseIdentifiers; protected final boolean fulltextDisabled; protected final boolean aclOptimizationsEnabled; protected final int readAclMaxSize; /** * Creates a {@code Dialect} by connecting to the datasource to check what * database is used. * * @throws StorageException if a SQL connection problem occurs */ public static Dialect createDialect(Connection connection, BinaryManager binaryManager, RepositoryDescriptor repositoryDescriptor) throws StorageException { DatabaseMetaData metadata; String databaseName; try { metadata = connection.getMetaData(); databaseName = metadata.getDatabaseProductName(); } catch (SQLException e) { throw new StorageException(e); } if ("Apache Derby".equals(databaseName)) { return new DialectDerby(metadata, binaryManager, repositoryDescriptor); } if ("H2".equals(databaseName)) { return new DialectH2(metadata, binaryManager, repositoryDescriptor); } if ("MySQL".equals(databaseName)) { return new DialectMySQL(metadata, binaryManager, repositoryDescriptor); } if ("Oracle".equals(databaseName)) { return new DialectOracle(metadata, binaryManager, repositoryDescriptor); } if ("PostgreSQL".equals(databaseName)) { return new DialectPostgreSQL(metadata, binaryManager, repositoryDescriptor); } if ("Microsoft SQL Server".equals(databaseName)) { return new DialectSQLServer(metadata, binaryManager, repositoryDescriptor); } if ("HSQL Database Engine".equals(databaseName)) { return new DialectHSQLDB(metadata, binaryManager, repositoryDescriptor); } throw new StorageException("Unsupported database: " + databaseName); } public Dialect(DatabaseMetaData metadata, BinaryManager binaryManager, RepositoryDescriptor repositoryDescriptor) throws StorageException { try { storesUpperCaseIdentifiers = metadata.storesUpperCaseIdentifiers(); } catch (SQLException e) { throw new StorageException("An error has occured.", e); } this.binaryManager = binaryManager; if (repositoryDescriptor == null) { fulltextDisabled = true; aclOptimizationsEnabled = false; readAclMaxSize = 0; } else { fulltextDisabled = repositoryDescriptor.fulltextDisabled; aclOptimizationsEnabled = repositoryDescriptor.aclOptimizationsEnabled; readAclMaxSize = repositoryDescriptor.readAclMaxSize; } } public BinaryManager getBinaryManager() { return binaryManager; } /** * Gets the schema to use to query metadata about existing tables. */ public String getConnectionSchema(Connection connection) throws SQLException { return null; } /** * Gets the JDBC type and string from Nuxeo's type abstraction. */ public abstract JDBCInfo getJDBCTypeAndString(ColumnType type); /** * Check mismatches between expected and actual JDBC types read from * database introspection. */ public boolean isAllowedConversion(int expected, int actual, String actualName, int actualSize) { return false; } public abstract void setToPreparedStatement(PreparedStatement ps, int index, Serializable value, Column column) throws SQLException; public static final String ARRAY_SEP = "|"; protected void setToPreparedStatementString(PreparedStatement ps, int index, Serializable value, Column column) throws SQLException { String v; ColumnType type = column.getType(); if (type == ColumnType.BLOBID) { v = ((Binary) value).getDigest(); } else if (type == ColumnType.SYSNAMEARRAY) { // implementation when arrays aren't supported String[] strings = (String[]) value; if (strings == null) { v = null; } else { // use initial and final separator as terminator StringBuilder buf = new StringBuilder(ARRAY_SEP); for (String string : strings) { buf.append(string); buf.append(ARRAY_SEP); } v = buf.toString(); } } else { v = (String) value; } ps.setString(index, v); } protected void setToPreparedStatementTimestamp(PreparedStatement ps, int index, Serializable value, Column column) throws SQLException { Calendar cal = (Calendar) value; Timestamp ts = new Timestamp(cal.getTimeInMillis()); ps.setTimestamp(index, ts, cal); // cal passed for timezone } public abstract Serializable getFromResultSet(ResultSet rs, int index, Column column) throws SQLException; protected Serializable getFromResultSetString(ResultSet rs, int index, Column column) throws SQLException { String string = rs.getString(index); if (string == null) { return null; } ColumnType type = column.getType(); if (type == ColumnType.BLOBID) { return getBinaryManager().getBinary(string); } else if (type == ColumnType.SYSNAMEARRAY) { // implementation when arrays aren't supported // an initial separator is expected if (string.startsWith(ARRAY_SEP)) { string = string.substring(ARRAY_SEP.length()); } // the final separator is dropped as split does not return final // empty strings return string.split(Pattern.quote(ARRAY_SEP)); } else { return string; } } protected Serializable getFromResultSetTimestamp(ResultSet rs, int index, Column column) throws SQLException { Timestamp ts = rs.getTimestamp(index); if (ts == null) { return null; } else { Serializable cal = new GregorianCalendar(); // XXX timezone ((Calendar) cal).setTimeInMillis(ts.getTime()); return cal; } } public boolean storesUpperCaseIdentifiers() { return storesUpperCaseIdentifiers; } public char openQuote() { return '"'; } public char closeQuote() { return '"'; } public String toBooleanValueString(boolean bool) { return bool ? "1" : "0"; } protected int getMaxNameSize() { return 999; } protected int getMaxIndexNameSize() { return 999; } /* * Needs to be deterministic and not change between Nuxeo EP releases. * * Turns "field_with_too_many_chars_for_oracle" into * "FIELD_WITH_TOO_MANY_C_58557BA3". */ protected String makeName(String name, int maxNameSize) { if (name.length() > maxNameSize) { MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e.toString(), e); } byte[] bytes = name.getBytes(); digest.update(bytes, 0, bytes.length); name = name.substring(0, maxNameSize - 1 - 8); name += '_' + toHexString(digest.digest()).substring(0, 8); } name = storesUpperCaseIdentifiers() ? name.toUpperCase() : name.toLowerCase(); name = name.replace(':', '_'); return name; } /* * Used for one-time names (IDX, FK, PK), ok if algorithm changes. * * If too long, keeps 4 chars of the prefix and the full suffix. */ protected String makeName(String prefix, String string, String suffix, int maxNameSize) { String name = prefix + string + suffix; if (name.length() > maxNameSize) { MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e.toString(), e); } byte[] bytes = (prefix + string).getBytes(); digest.update(bytes, 0, bytes.length); name = prefix.substring(0, 4); name += '_' + toHexString(digest.digest()).substring(0, 8); name += suffix; } name = storesUpperCaseIdentifiers() ? name.toUpperCase() : name.toLowerCase(); name = name.replace(':', '_'); return name; } protected static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); public static String toHexString(byte[] bytes) { StringBuilder buf = new StringBuilder(2 * bytes.length); for (byte b : bytes) { buf.append(HEX_DIGITS[(0xF0 & b) >> 4]); buf.append(HEX_DIGITS[0x0F & b]); } return buf.toString(); } public String getTableName(String name) { return makeName(name, getMaxNameSize()); } public String getColumnName(String name) { return makeName(name, getMaxNameSize()); } public String getPrimaryKeyConstraintName(String tableName) { return makeName(tableName, "", "_PK", getMaxNameSize()); } public String getForeignKeyConstraintName(String tableName, String foreignColumnName, String foreignTableName) { return makeName(tableName + '_', foreignColumnName + '_' + foreignTableName, "_FK", getMaxNameSize()); } public String getIndexName(String tableName, List<String> columnNames) { return makeName(qualifyIndexName() ? tableName + '_' : "", StringUtils.join(columnNames, '_'), "_IDX", getMaxIndexNameSize()); } /** * Gets a CREATE INDEX statement for a normal index. */ public String getCreateIndexSql(String indexName, String tableName, List<String> columnNames) { return String.format("CREATE INDEX %s ON %s (%s)", indexName, tableName, StringUtils.join(columnNames, ", ")); } /** * Specifies what columns of the fulltext table have to be indexed. * * @return 0 for none, 1 for the synthetic one, 2 for the individual ones */ public abstract int getFulltextIndexedColumns(); /** * SQL Server supports only one fulltext index. */ public boolean supportsMultipleFulltextIndexes() { return true; } /** * Does the fulltext synthetic column have to be materialized. */ public abstract boolean getMaterializeFulltextSyntheticColumn(); /** * Gets a CREATE INDEX statement for a fulltext index. */ public abstract String getCreateFulltextIndexSql(String indexName, String quotedIndexName, Table table, List<Column> columns, Model model); /** * Structured fulltext query. */ public static class FulltextQuery { public enum Op { OR, AND, WORD, NOTWORD }; public Op op; /** The list of terms, if op is OR or AND */ public List<FulltextQuery> terms; /** The word, if op is WORD or NOTWORD */ public String word; } /** * Analyzes a fulltext query into a generic datastructure that can be used * for each specific database. * <p> * List of terms containing only negative words are suppressed. Otherwise * negative words are put at the end of the lists of terms. */ public static FulltextQuery analyzeFulltextQuery(String query) { return new FulltextQueryAnalyzer().analyze(query); } public static class FulltextQueryAnalyzer { public static final String PLUS = "+"; public static final String MINUS = "-"; public static final String SPACE = " "; public static final char CSPACE = ' '; public static final String DOUBLE_QUOTES = "\""; public FulltextQuery ft = new FulltextQuery(); public List<FulltextQuery> terms = new LinkedList<FulltextQuery>(); public FulltextQuery analyze(String query) { query = query.replaceAll(" +", " ").trim(); if (query.trim().length() == 0) { return null; } ft.op = FulltextQuery.Op.OR; ft.terms = new LinkedList<FulltextQuery>(); // current sequence of ANDed terms boolean wasOr = false; String[] words = StringUtils.split(query, CSPACE, true); for (Iterator<String> it = Arrays.asList(words).iterator(); it.hasNext();) { boolean plus = false; boolean minus = false; String word = it.next(); if (word.startsWith(PLUS)) { plus = true; word = word.substring(1); } else if (word.startsWith(MINUS)) { minus = true; word = word.substring(1); } if (word.startsWith(DOUBLE_QUOTES)) { // read phrase word = word.substring(1); StringBuilder phrase = null; while (true) { boolean end = word.endsWith(DOUBLE_QUOTES); if (end) { word = word.substring(0, word.length() - 1).trim(); } if (word.contains(DOUBLE_QUOTES)) { throw new QueryMakerException( "Invalid fulltext query (double quotes in word): " + query); } if (word.length() != 0) { if (phrase == null) { phrase = new StringBuilder(); } else { phrase.append(CSPACE); } phrase.append(word); } if (end) { break; } if (!it.hasNext()) { throw new QueryMakerException( "Invalid fulltext query (unterminated phrase): " + query); } word = it.next(); } if (phrase == null) { continue; } word = phrase.toString(); } else if (word.equalsIgnoreCase("OR")) { if (wasOr) { throw new QueryMakerException( "Invalid fulltext query (OR OR): " + query); } if (terms.isEmpty()) { throw new QueryMakerException( "Invalid fulltext query (standalone OR): " + query); } wasOr = true; continue; } FulltextQuery w = new FulltextQuery(); if (minus) { if (word.length() == 0) { throw new QueryMakerException( "Invalid fulltext query (standalone -): " + query); } w.op = FulltextQuery.Op.NOTWORD; } else { if (plus) { if (word.length() == 0) { throw new QueryMakerException( "Invalid fulltext query (standalone +): " + query); } } w.op = FulltextQuery.Op.WORD; } if (wasOr) { endAnd(); wasOr = false; } w.word = word; terms.add(w); } if (wasOr) { throw new QueryMakerException( "Invalid fulltext query (final OR): " + query); } // final terms endAnd(); int size = ft.terms.size(); if (size == 0) { // all terms were negative return null; } else if (size == 1) { // simplify when no OR ft = ft.terms.get(0); } return ft; } // add current ANDed terms to global OR protected void endAnd() { // put negative words at the end List<FulltextQuery> pos = new LinkedList<FulltextQuery>(); List<FulltextQuery> neg = new LinkedList<FulltextQuery>(); for (FulltextQuery term : terms) { if (term.op == FulltextQuery.Op.NOTWORD) { neg.add(term); } else { pos.add(term); } } if (!pos.isEmpty()) { terms = pos; terms.addAll(neg); if (terms.size() == 1) { ft.terms.add(terms.get(0)); } else { FulltextQuery a = new FulltextQuery(); a.op = FulltextQuery.Op.AND; a.terms = terms; ft.terms.add(a); } } terms = new LinkedList<FulltextQuery>(); } public static void translate(FulltextQuery ft, StringBuilder buf, String or, String and, String andNot, String phraseQuote) { if (ft.op == Op.AND || ft.op == Op.OR) { buf.append('('); for (int i = 0; i < ft.terms.size(); i++) { FulltextQuery term = ft.terms.get(i); if (i > 0) { buf.append(' '); if (ft.op == Op.OR) { buf.append(or); } else { // Op.AND if (term.op == Op.NOTWORD) { buf.append(andNot); } else { buf.append(and); } } buf.append(' '); } translate(term, buf, or, and, andNot, phraseQuote); } buf.append(')'); return; } else { boolean isPhrase = ft.word.contains(SPACE); if (isPhrase) { buf.append(phraseQuote); } buf.append(ft.word); if (isPhrase) { buf.append(phraseQuote); } } } public static boolean hasPhrase(FulltextQuery ft) { if (ft.op == Op.AND || ft.op == Op.OR) { for (FulltextQuery term : ft.terms) { if (hasPhrase(term)) { return true; } } return false; } else { return ft.word.contains(SPACE); } } } /** * Translate fulltext into a common pattern used by many servers. */ public static String translateFulltext(FulltextQuery ft, String or, String and, String andNot, String phraseQuote) { StringBuilder buf = new StringBuilder(); FulltextQueryAnalyzer.translate(ft, buf, or, and, andNot, phraseQuote); return buf.toString(); } /** * Checks if a fulltext search has a phrase in it. */ public static boolean fulltextHasPhrase(FulltextQuery ft) { return FulltextQueryAnalyzer.hasPhrase(ft); } /** * Get the dialect-specific version of a fulltext query. * * @param query the CMIS-syntax-based fulltext query string * @return the dialect native fulltext query string */ public abstract String getDialectFulltextQuery(String query); /** * Information needed to express fulltext search with scoring. */ public static class FulltextMatchInfo { public List<Join> joins; public String whereExpr; public String whereExprParam; public String scoreExpr; public String scoreExprParam; public String scoreAlias; public Column scoreCol; } /** * Gets the SQL information needed to do a a fulltext match, either with a * direct expression in the WHERE clause, or using a join with an additional * table. */ public abstract FulltextMatchInfo getFulltextScoredMatchInfo( String fulltextQuery, String indexName, int nthMatch, Column mainColumn, Model model, Database database); /** * Gets the SQL fragment to match a mixin type. */ public String getMatchMixinType(Column mixinsColumn, String mixin, boolean positive, String[] returnParam) { returnParam[0] = "%" + ARRAY_SEP + mixin + ARRAY_SEP + "%"; return String.format("%s %s ?", mixinsColumn.getFullQuotedName(), positive ? "LIKE" : "NOT LIKE"); } /** * Indicates if dialect supports paging * * @return true if the dialect supports paging */ public boolean supportsPaging() { return false; } /** * Gets paging clause to be appended at the end of select statement */ public String getPagingClause(long limit, long offset) { throw new UnsupportedOperationException("paging is not supported"); } /** * Gets the type of a fulltext column has known by JDBC. * <p> * This is used for setNull. */ public int getFulltextType() { return Types.CLOB; } /** * Gets the JDBC expression setting a free value for this column type. * <p> * Needed for columns that need an expression around the value being set, * usually for conversion (this is the case for PostgreSQL fulltext * {@code TSVECTOR} columns for instance). * * @param type the column type * @return the expression containing a free variable */ public String getFreeVariableSetterForType(ColumnType type) { return "?"; } public String getNoColumnsInsertString() { return "VALUES ( )"; } public String getNullColumnString() { return ""; } public String getTableTypeString(Table table) { return ""; } public String getAddPrimaryKeyConstraintString(String constraintName) { return String.format(" ADD CONSTRAINT %s PRIMARY KEY ", constraintName); } public String getAddForeignKeyConstraintString(String constraintName, String[] foreignKeys, String referencedTable, String[] primaryKeys, boolean referencesPrimaryKey) { String sql = String.format( " ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s", constraintName, StringUtils.join(foreignKeys, ", "), referencedTable); if (!referencesPrimaryKey) { sql += " (" + StringUtils.join(primaryKeys, ", ") + ')'; } return sql; } public boolean qualifyIndexName() { return true; } public boolean supportsIfExistsBeforeTableName() { return false; } public boolean supportsIfExistsAfterTableName() { return false; } public String getCascadeDropConstraintsString() { return ""; } public boolean supportsCircularCascadeDeleteConstraints() { // false for MS SQL Server return true; } public String getAddColumnString() { return "ADD COLUMN"; } /** * Does the dialect support UPDATE t SET ... FROM t, u WHERE ... ? */ public abstract boolean supportsUpdateFrom(); /** * When doing an UPDATE t SET ... FROM t, u WHERE ..., does the FROM clause * need to repeate the updated table (t). */ public abstract boolean doesUpdateFromRepeatSelf(); /** * When doing a SELECT DISTINCT that uses a ORDER BY, do the keys along * which we order have to be mentioned in the DISTINCT clause? */ public boolean needsOrderByKeysAfterDistinct() { return true; } /** * Whether a derived table (subselect in a FROM statement) needs an alias. */ public boolean needsAliasForDerivedTable() { return false; } /** * Whether a GROUP BY can only be used with the original column name and not * an alias. */ public boolean needsOriginalColumnInGroupBy() { return false; } /** * Whether implicit Oracle joins (instead of explicit ANSI joins) are * needed. */ public boolean needsOracleJoins() { return false; } /** * When using a CLOB field in an expression, is some casting required and * with what pattern? * <p> * Needed for Derby and H2. * * @param inOrderBy {@code true} if the expression is for an ORDER BY column * @return a pattern for String.format with one parameter for the column * name and one for the width, or {@code null} if no cast is * required */ public String getClobCast(boolean inOrderBy) { return null; } /** * Gets the expression to use to check security. * * @param idColumnName the quoted name of the id column to use * @return an SQL expression with two parameters (principals and * permissions) that is true if access is allowed */ public abstract String getSecurityCheckSql(String idColumnName); /** * Checks if the dialect supports an ancestors table. */ public boolean supportsAncestorsTable() { return false; } /** * Gets the expression to use to check tree membership. * * @param idColumnName the quoted name of the id column to use * @return an SQL expression with one parameters for the based id that is * true if the document is under base id */ public abstract String getInTreeSql(String idColumnName); /** * Does the dialect support passing ARRAY values (to stored procedures * mostly). * <p> * If not, we'll simulate them using a string and a separator. * * @return true if ARRAY values are supported */ public boolean supportsArrays() { return false; } /** * Checks if the dialect supports storing arrays of system names (for mixins * for instance). */ public boolean supportsSysNameArray() { return false; } /** * Factory method for creating Array objects, suitable for passing to * {@link PreparedStatement#setArray}. * <p> * (An equivalent method is defined by JDBC4 on the {@link Connection} * class.) * * @param type the SQL type of the elements * @param elements the elements of the array * @param connection the connection * @return an Array holding the elements */ public Array createArrayOf(int type, Object[] elements, Connection connection) throws SQLException { throw new SQLException("Not supported"); } /** * Gets the name of the file containing the SQL statements. */ public abstract String getSQLStatementsFilename(); public abstract String getTestSQLStatementsFilename(); /** * Gets the properties to use with the SQL statements. */ public abstract Map<String, Serializable> getSQLStatementsProperties( Model model, Database database); /** * Checks that clustering is supported. */ public boolean isClusteringSupported() { return false; } /** * Does clustering fetch of invalidations ( * {@link #getClusterGetInvalidations}) need a separate delete for them ( * {@link #getClusterDeleteInvalidations}). */ public boolean isClusteringDeleteNeeded() { return false; } /** * Gets the SQL to send an invalidation to the cluster. * * @return an SQL statement with parameters for: id, fragments, kind */ public String getClusterInsertInvalidations() { return null; } /** * Gets the SQL to query invalidations for this cluster node. * * @return an SQL statement returning a result set */ public String getClusterGetInvalidations() { return null; } /** * Gets the SQL to delete invalidations for this cluster node. * * @return an SQL statement returning a result set */ public String getClusterDeleteInvalidations() { return null; } /** * Does the dialect support ILIKE operator * */ public boolean supportsIlike() { return false; } /** * Does the dialect support an optimized read security checks * */ public boolean supportsReadAcl() { return false; } /** * Does the dialect support SQL-99 WITH common table expressions. */ public boolean supportsWith() { return false; } /** * Does the dialect have an empty string identical to NULL (Oracle). */ public boolean hasNullEmptyString() { return false; } /** * Maximum number of values in a IN (?, ?, ...) statement. * <p> * Beyond this size we'll do the query in several chunks. * <p> * PostgreSQL is limited to 65535 values in a prepared statement. * <p> * Oracle is limited to 1000 expressions in a list (ORA-01795). */ public int getMaximumArgsForIn() { return 400; } /** * Gets the statement to update the read acls * */ public String getUpdateReadAclsSql() { return null; } /** * Gets the statement to rebuild the wall read acls * */ public String getRebuildReadAclsSql() { return null; } /** * Gets the expression to check if access is allowed using read acl the * dialect must suppportsReadAcl * * @param idColumnName the quoted name of the read acl_id column to use * @return an SQL expression with one parameter (principals) that is true if * access is allowed */ public String getReadAclsCheckSql(String idColumnName) { return null; } /** * Called before a table is created, when it's been determined that it * doesn't exist yet. * * @return {@code false} if the table must actually not be created */ public boolean preCreateTable(Connection connection, Table table, Model model, Database database) throws SQLException { return true; } /** * Gets the sql statements to call after a table has been created. * <p> * Used for migrations/upgrades. */ public List<String> getPostCreateTableSqls(Table table, Model model, Database database) { return Collections.emptyList(); } /** * Called after an existing table has been detected in the database. * <p> * Used for migrations/upgrades. */ public void existingTableDetected(Connection connection, Table table, Model model, Database database) throws SQLException { } /** * Checks if an exception received means that the low level connection has * been trashed and must be reset. */ public boolean isConnectionClosedException(Throwable t) { while (t.getCause() != null) { t = t.getCause(); } return t instanceof SocketException; } /** * Let the dialect processes additional statements after tables creation and * conditional statements. Can be used for specific upgrade procedure. * * @param connection */ public void performAdditionalStatements(Connection connection) throws SQLException { } /** * A query that, when executed, will make at least a round-trip to the * server to check that the connection is alive. * <p> * The query should throw an error if the connection is dead. */ public String getValidationQuery() { return "SELECT 1"; } }