| /* |
| * Copyright (c) 2008-2013 Eike Stepper (Berlin, Germany) 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: |
| * Eike Stepper - initial API and implementation |
| * Stefan Winkler - Bug 289445 |
| */ |
| package org.eclipse.net4j.spi.db; |
| |
| import org.eclipse.net4j.db.DBException; |
| import org.eclipse.net4j.db.DBType; |
| import org.eclipse.net4j.db.DBUtil; |
| import org.eclipse.net4j.db.IDBAdapter; |
| import org.eclipse.net4j.db.IDBConnectionProvider; |
| import org.eclipse.net4j.db.ddl.IDBField; |
| import org.eclipse.net4j.db.ddl.IDBIndex; |
| import org.eclipse.net4j.db.ddl.IDBSchema; |
| import org.eclipse.net4j.db.ddl.IDBTable; |
| import org.eclipse.net4j.db.ddl.delta.IDBDelta.ChangeKind; |
| import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor; |
| import org.eclipse.net4j.db.ddl.delta.IDBIndexDelta; |
| import org.eclipse.net4j.db.ddl.delta.IDBSchemaDelta; |
| import org.eclipse.net4j.db.ddl.delta.IDBTableDelta; |
| import org.eclipse.net4j.internal.db.bundle.OM; |
| import org.eclipse.net4j.internal.db.ddl.DBField; |
| import org.eclipse.net4j.util.CheckUtil; |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| |
| import javax.sql.DataSource; |
| |
| import java.io.IOException; |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.Driver; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.ResultSetMetaData; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * A useful base class for implementing custom {@link IDBAdapter DB adapters}. |
| * |
| * @author Eike Stepper |
| */ |
| public abstract class DBAdapter implements IDBAdapter |
| { |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_SQL, DBAdapter.class); |
| |
| private static final String[] SQL92_RESERVED_WORDS = { "ABSOLUTE", "ACTION", "ADD", "AFTER", "ALL", "ALLOCATE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ |
| "ALTER", "AND", "ANY", "ARE", "ARRAY", "AS", "ASC", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "AUTHORIZATION", "AVG", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BIT_LENGTH", "BLOB", "BOOLEAN", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "BOTH", "BREADTH", "BY", "CALL", "CALLED", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "CHARACTER_LENGTH", "CHAR_LENGTH", "CHECK", "CLOB", "CLOSE", "COALESCE", "COLLATE", "COLLATION", "COLUMN", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "COMMIT", "CONDITION", "CONNECT", "CONNECTION", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONTAINS", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "CONTINUE", "CONVERT", "CORRESPONDING", "COUNT", "CREATE", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", "DATA", "DATE", "DAY", "DEALLOCATE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DEPTH", "DEREF", "DESC", "DESCRIBE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "DESCRIPTOR", "DETERMINISTIC", "DIAGNOSTICS", "DISCONNECT", "DISTINCT", "DO", "DOMAIN", "DOUBLE", "DROP", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "DYNAMIC", "EACH", "ELEMENT", "ELSE", "ELSEIF", "END", "EQUALS", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "EXECUTE", "EXISTS", "EXIT", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FILTER", "FIRST", "FLOAT", "FOR", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "FOREIGN", "FOUND", "FREE", "FROM", "FULL", "FUNCTION", "GENERAL", "GET", "GLOBAL", "GO", "GOTO", "GRANT", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "GROUP", "GROUPING", "HANDLER", "HAVING", "HOLD", "HOUR", "IDENTITY", "IF", "IMMEDIATE", "IN", "INDICATOR", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "INITIALLY", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ |
| "INTO", "IS", "ISOLATION", "ITERATE", "JOIN", "KEY", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEADING", "LEAVE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "LEFT", "LEVEL", "LIKE", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "LOOP", "LOWER", "MAP", "MATCH", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MODIFIES", "MODULE", "MONTH", "MULTISET", "NAMES", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NULLIF", "NUMERIC", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "OBJECT", "OCTET_LENGTH", "OF", "OLD", "ON", "ONLY", "OPEN", "OPTION", "OR", "ORDER", "ORDINALITY", "OUT", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "OUTER", "OUTPUT", "OVER", "OVERLAPS", "PAD", "PARAMETER", "PARTIAL", "PARTITION", "PATH", "POSITION", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ |
| "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC", "RANGE", "READ", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ |
| "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "RELATIVE", "RELEASE", "REPEAT", "RESIGNAL", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ |
| "RESTRICT", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "ROWS", "SAVEPOINT", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SELECT", "SENSITIVE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ |
| "SESSION", "SESSION_USER", "SET", "SETS", "SIGNAL", "SIMILAR", "SIZE", "SMALLINT", "SOME", "SPACE", "SPECIFIC", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "SPECIFICTYPE", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "STATIC", "SUBMULTISET", "SUBSTRING", "SUM", "SYMMETRIC", "SYSTEM", "SYSTEM_USER", "TABLE", "TABLESAMPLE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "TEMPORARY", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSACTION", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "UNDER", "UNDO", "UNION", "UNIQUE", "UNKNOWN", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ |
| "UNNEST", "UNTIL", "UPDATE", "UPPER", "USAGE", "USER", "USING", "VALUE", "VALUES", "VARCHAR", "VARYING", "VIEW", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| "WHEN", "WHENEVER", "WHERE", "WHILE", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRITE", "YEAR", "ZONE" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ |
| |
| private String name; |
| |
| private String version; |
| |
| private Set<String> reservedWords; |
| |
| public DBAdapter(String name, String version) |
| { |
| this.name = name; |
| this.version = version; |
| } |
| |
| public String getName() |
| { |
| return name; |
| } |
| |
| public String getVersion() |
| { |
| return version; |
| } |
| |
| /** |
| * @since 4.2 |
| * @deprecated As of 4.2 no longer supported because of IP issues for external build dependencies (the vendor driver libs). |
| */ |
| @Deprecated |
| public Driver getJDBCDriver() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * @since 4.2 |
| * @deprecated As of 4.2 no longer supported because of IP issues for external build dependencies (the vendor driver libs). |
| */ |
| @Deprecated |
| public DataSource createJDBCDataSource() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public IDBConnectionProvider createConnectionProvider(DataSource dataSource) |
| { |
| return DBUtil.createConnectionProvider(dataSource); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public IDBSchema readSchema(Connection connection, String name) |
| { |
| return DBUtil.readSchema(this, connection, name); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public void readSchema(Connection connection, IDBSchema schema) |
| { |
| boolean isTrackConstruction = DBField.isTrackConstruction(); |
| try |
| { |
| DBField.trackConstruction(false); |
| String schemaName = schema.getName(); |
| |
| DatabaseMetaData metaData = connection.getMetaData(); |
| Set<String> schemaNames = DBUtil.getAllSchemaNames(metaData); |
| if (!schemaNames.contains(schemaName)) |
| { |
| schemaName = null; |
| } |
| |
| ResultSet tables = readTables(connection, metaData, schemaName); |
| while (tables.next()) |
| { |
| String tableName = tables.getString(3); |
| |
| IDBTable table = schema.addTable(tableName); |
| readFields(connection, table); |
| readIndices(connection, metaData, table, schemaName); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| DBField.trackConstruction(isTrackConstruction); |
| } |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| protected ResultSet readTables(Connection connection, DatabaseMetaData metaData, String schemaName) |
| throws SQLException |
| { |
| return metaData.getTables(null, schemaName, null, new String[] { "TABLE" }); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void readFields(Connection connection, IDBTable table) throws SQLException |
| { |
| Statement statement = null; |
| ResultSet resultSet = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| statement.setMaxRows(1); |
| resultSet = statement.executeQuery("SELECT * FROM " + table); |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| |
| for (int i = 0; i < metaData.getColumnCount(); i++) |
| { |
| int column = i + 1; |
| String name = metaData.getColumnName(column); |
| if (name == null) |
| { |
| // Bug 405924: Just to be sure in case this happens with Oracle. |
| continue; |
| } |
| |
| DBType type = DBType.getTypeByCode(metaData.getColumnType(column)); |
| int precision = metaData.getPrecision(column); |
| int scale = metaData.getScale(column); |
| boolean notNull = metaData.isNullable(column) == ResultSetMetaData.columnNoNulls; |
| |
| table.addField(name, type, precision, scale, notNull); |
| } |
| } |
| finally |
| { |
| DBUtil.close(resultSet); |
| DBUtil.close(statement); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void readIndices(Connection connection, DatabaseMetaData metaData, IDBTable table, String schemaName) |
| throws SQLException |
| { |
| String tableName = table.getName(); |
| |
| ResultSet primaryKeys = metaData.getPrimaryKeys(null, schemaName, tableName); |
| readIndices(connection, primaryKeys, table, 6, 0, 4, 5); |
| |
| ResultSet indexInfo = metaData.getIndexInfo(null, schemaName, tableName, false, false); |
| readIndices(connection, indexInfo, table, 6, 4, 9, 8); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void readIndices(Connection connection, ResultSet resultSet, IDBTable table, int indexNameColumn, |
| int indexTypeColumn, int fieldNameColumn, int fieldPositionColumn) throws SQLException |
| { |
| try |
| { |
| String indexName = null; |
| IDBIndex.Type indexType = null; |
| List<FieldInfo> fieldInfos = new ArrayList<FieldInfo>(); |
| |
| while (resultSet.next()) |
| { |
| String name = resultSet.getString(indexNameColumn); |
| if (name == null) |
| { |
| // Bug 405924: It seems that this can happen with Oracle. |
| continue; |
| } |
| |
| if (indexName != null && !indexName.equals(name)) |
| { |
| addIndex(connection, table, indexName, indexType, fieldInfos); |
| fieldInfos.clear(); |
| } |
| |
| indexName = name; |
| |
| if (indexTypeColumn == 0) |
| { |
| indexType = IDBIndex.Type.PRIMARY_KEY; |
| } |
| else |
| { |
| boolean nonUnique = resultSet.getBoolean(indexTypeColumn); |
| indexType = nonUnique ? IDBIndex.Type.NON_UNIQUE : IDBIndex.Type.UNIQUE; |
| } |
| |
| FieldInfo fieldInfo = new FieldInfo(); |
| fieldInfo.name = resultSet.getString(fieldNameColumn); |
| fieldInfo.position = resultSet.getShort(fieldPositionColumn); |
| fieldInfos.add(fieldInfo); |
| } |
| |
| if (indexName != null) |
| { |
| addIndex(connection, table, indexName, indexType, fieldInfos); |
| } |
| } |
| finally |
| { |
| DBUtil.close(resultSet); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void addIndex(Connection connection, IDBTable table, String name, IDBIndex.Type type, |
| List<FieldInfo> fieldInfos) |
| { |
| IDBField[] fields = new IDBField[fieldInfos.size()]; |
| |
| Collections.sort(fieldInfos); |
| for (int i = 0; i < fieldInfos.size(); i++) |
| { |
| FieldInfo fieldInfo = fieldInfos.get(i); |
| IDBField field = table.getField(fieldInfo.name); |
| if (field == null) |
| { |
| throw new IllegalStateException("Field not found: " + fieldInfo.name); |
| } |
| |
| fields[i] = field; |
| } |
| |
| if (!isPrimaryKeyShadow(connection, table, name, type, fields)) |
| { |
| table.addIndex(name, type, fields); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected boolean isPrimaryKeyShadow(Connection connection, IDBTable table, String name, IDBIndex.Type type, |
| IDBField[] fields) |
| { |
| if (type != IDBIndex.Type.UNIQUE) |
| { |
| return false; |
| } |
| |
| IDBIndex primaryKey = table.getPrimaryKeyIndex(); |
| if (primaryKey == null) |
| { |
| return false; |
| } |
| |
| IDBField[] primaryKeyFields = primaryKey.getFields(); |
| return Arrays.equals(primaryKeyFields, fields); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public void updateSchema(final Connection connection, final IDBSchema schema, IDBSchemaDelta delta) throws DBException |
| { |
| // Apply delta to in-memory representation of the schema |
| delta.applyTo(schema); |
| |
| // Call DDL methods to update the database schema |
| IDBDeltaVisitor schemaUpdater = new IDBDeltaVisitor.Default() |
| { |
| @Override |
| public void visit(IDBTableDelta delta) |
| { |
| IDBTable table = delta.getSchemaElement(schema); |
| ChangeKind changeKind = delta.getChangeKind(); |
| switch (changeKind) |
| { |
| case ADD: |
| createTable(connection, table, delta); |
| break; |
| |
| case REMOVE: |
| dropTable(connection, table, delta); |
| break; |
| |
| case CHANGE: |
| alterTable(connection, table, delta); |
| break; |
| |
| default: |
| throw new IllegalStateException("Illegal change kind: " + changeKind); |
| } |
| } |
| |
| @Override |
| public void visit(IDBIndexDelta delta) |
| { |
| IDBIndex element = delta.getSchemaElement(schema); |
| ChangeKind changeKind = delta.getChangeKind(); |
| switch (changeKind) |
| { |
| case ADD: |
| createIndex(connection, element, delta); |
| break; |
| |
| case REMOVE: |
| dropIndex(connection, element, delta); |
| break; |
| |
| case CHANGE: |
| dropIndex(connection, element, delta); |
| createIndex(connection, element, delta); |
| break; |
| |
| default: |
| throw new IllegalStateException("Illegal change kind: " + changeKind); |
| } |
| } |
| }; |
| |
| delta.accept(schemaUpdater); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void createTable(Connection connection, IDBTable table, IDBTableDelta delta) |
| { |
| CheckUtil.checkArg(delta.getChangeKind() == ChangeKind.ADD, "Not added: " + delta.getName()); |
| |
| StringBuilder builder = new StringBuilder(); |
| builder.append("CREATE TABLE "); //$NON-NLS-1$ |
| builder.append(delta.getName()); |
| builder.append(" ("); //$NON-NLS-1$ |
| appendFieldDefs(builder, table, createFieldDefinitions(table)); |
| builder.append(")"); //$NON-NLS-1$ |
| |
| DBUtil.execute(connection, builder); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void dropTable(Connection connection, IDBTable table, IDBTableDelta delta) |
| { |
| String sql = getDropTableSQL(table); |
| DBUtil.execute(connection, sql); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void alterTable(Connection connection, IDBTable table, IDBTableDelta delta) |
| { |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void createIndex(Connection connection, IDBIndex index, IDBIndexDelta delta) |
| { |
| StringBuilder builder = new StringBuilder(); |
| if (index.getType() == IDBIndex.Type.PRIMARY_KEY) |
| { |
| createPrimaryKey(index, builder); |
| } |
| else |
| { |
| createIndex(index, builder); |
| } |
| |
| createIndexFields(index, builder); |
| DBUtil.execute(connection, builder); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void createPrimaryKey(IDBIndex index, StringBuilder builder) |
| { |
| builder.append("ALTER TABLE "); //$NON-NLS-1$ |
| builder.append(index.getTable()); |
| builder.append(" ADD CONSTRAINT "); //$NON-NLS-1$ |
| builder.append(index); |
| builder.append(" PRIMARY KEY"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void createIndex(IDBIndex index, StringBuilder builder) |
| { |
| builder.append("CREATE "); //$NON-NLS-1$ |
| if (index.getType() == IDBIndex.Type.UNIQUE) |
| { |
| builder.append("UNIQUE "); //$NON-NLS-1$ |
| } |
| |
| builder.append("INDEX "); //$NON-NLS-1$ |
| builder.append(index); |
| builder.append(" ON "); //$NON-NLS-1$ |
| builder.append(index.getTable()); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void createIndexFields(IDBIndex index, StringBuilder builder) |
| { |
| builder.append(" ("); //$NON-NLS-1$ |
| |
| IDBField[] fields = index.getFields(); |
| for (int i = 0; i < fields.length; i++) |
| { |
| if (i != 0) |
| { |
| builder.append(", "); //$NON-NLS-1$ |
| } |
| |
| addIndexField(builder, fields[i]); |
| } |
| |
| builder.append(")"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void dropIndex(Connection connection, IDBIndex index, IDBIndexDelta delta) |
| { |
| StringBuilder builder = new StringBuilder(); |
| if (index.getType() == IDBIndex.Type.PRIMARY_KEY) |
| { |
| dropPrimaryKey(index, builder); |
| } |
| else |
| { |
| dropIndex(index, builder); |
| } |
| |
| DBUtil.execute(connection, builder); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void dropPrimaryKey(IDBIndex index, StringBuilder builder) |
| { |
| builder.append("ALTER TABLE "); //$NON-NLS-1$ |
| builder.append(index.getTable()); |
| builder.append(" DROP CONSTRAINT "); //$NON-NLS-1$ |
| builder.append(index); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void dropIndex(IDBIndex index, StringBuilder builder) |
| { |
| } |
| |
| public Set<IDBTable> createTables(Iterable<? extends IDBTable> tables, Connection connection) throws DBException |
| { |
| Set<IDBTable> createdTables = new HashSet<IDBTable>(); |
| |
| for (IDBTable table : tables) |
| { |
| Statement statement = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| if (createTable(table, statement)) |
| { |
| createdTables.add(table); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| DBUtil.close(statement); |
| } |
| } |
| |
| return createdTables; |
| } |
| |
| public boolean createTable(IDBTable table, Statement statement) throws DBException |
| { |
| boolean created = true; |
| |
| try |
| { |
| doCreateTable(table, statement); |
| } |
| catch (SQLException ex) |
| { |
| created = false; |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace("-- " + ex.getMessage()); //$NON-NLS-1$ |
| } |
| } |
| |
| validateTable(table, statement); |
| return created; |
| } |
| |
| public Collection<IDBTable> dropTables(Iterable<? extends IDBTable> tables, Connection connection) throws DBException |
| { |
| List<IDBTable> droppedTables = new ArrayList<IDBTable>(); |
| |
| for (IDBTable table : tables) |
| { |
| Statement statement = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| if (dropTable(table, statement)) |
| { |
| droppedTables.add(table); |
| } |
| } |
| catch (SQLException ex) |
| { |
| OM.LOG.error(ex); |
| } |
| finally |
| { |
| DBUtil.close(statement); |
| } |
| } |
| |
| return droppedTables; |
| } |
| |
| public boolean dropTable(IDBTable table, Statement statement) |
| { |
| try |
| { |
| String sql = getDropTableSQL(table); |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(sql); |
| } |
| |
| statement.execute(sql); |
| return true; |
| } |
| catch (SQLException ex) |
| { |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(ex.getMessage()); |
| } |
| |
| return false; |
| } |
| } |
| |
| protected String getDropTableSQL(IDBTable table) |
| { |
| return "DROP TABLE " + table; //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public int getMaxTableNameLength() |
| { |
| // Ansi SQL 92 default value |
| return 128; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public int getMaxFieldNameLength() |
| { |
| // Ansi SQL 92 default value |
| return 128; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public int getFieldLength(DBType type) |
| { |
| return getDefaultDBLength(type); |
| } |
| |
| public boolean isTypeIndexable(DBType type) |
| { |
| switch (type) |
| { |
| case CLOB: |
| case BLOB: |
| case LONGVARCHAR: |
| case LONGVARBINARY: |
| case VARBINARY: |
| case BINARY: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return getName() + "-" + getVersion(); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public String convertString(PreparedStatement preparedStatement, int parameterIndex, String value) |
| { |
| return value; |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public String convertString(ResultSet resultSet, int columnIndex, String value) |
| { |
| return value; |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public String convertString(ResultSet resultSet, String columnLabel, String value) |
| { |
| return value; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected void doCreateTable(IDBTable table, Statement statement) throws SQLException |
| { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("CREATE TABLE "); //$NON-NLS-1$ |
| builder.append(table); |
| builder.append(" ("); //$NON-NLS-1$ |
| appendFieldDefs(builder, table, createFieldDefinitions(table)); |
| String constraints = createConstraints(table); |
| if (constraints != null) |
| { |
| builder.append(", "); //$NON-NLS-1$ |
| builder.append(constraints); |
| } |
| |
| builder.append(")"); //$NON-NLS-1$ |
| String sql = builder.toString(); |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(sql); |
| } |
| |
| statement.execute(sql); |
| |
| IDBIndex[] indices = table.getIndices(); |
| for (int i = 0; i < indices.length; i++) |
| { |
| createIndex(indices[i], statement, i); |
| } |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected void createIndex(IDBIndex index, Statement statement, int num) throws SQLException |
| { |
| IDBTable table = index.getTable(); |
| StringBuilder builder = new StringBuilder(); |
| builder.append("CREATE "); //$NON-NLS-1$ |
| if (index.getType() == IDBIndex.Type.UNIQUE || index.getType() == IDBIndex.Type.PRIMARY_KEY) |
| { |
| builder.append("UNIQUE "); //$NON-NLS-1$ |
| } |
| |
| builder.append("INDEX "); //$NON-NLS-1$ |
| builder.append(index); |
| builder.append(" ON "); //$NON-NLS-1$ |
| builder.append(table); |
| createIndexFields(index, builder); |
| String sql = builder.toString(); |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(sql); |
| } |
| |
| statement.execute(sql); |
| } |
| |
| protected void addIndexField(StringBuilder builder, IDBField field) |
| { |
| builder.append(field); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected String createConstraints(IDBTable table) |
| { |
| return null; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected String createFieldDefinition(IDBField field) |
| { |
| return getTypeName(field) + (field.isNotNull() ? " NOT NULL" : ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| // protected String getTypeName(DBType type) |
| // { |
| // new DBField(null,null) |
| // return getTypeName(field); |
| // } |
| |
| protected String getTypeName(IDBField field) |
| { |
| DBType type = field.getType(); |
| switch (type) |
| { |
| case BOOLEAN: |
| case BIT: |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case FLOAT: |
| case REAL: |
| case DOUBLE: |
| case DATE: |
| case TIME: |
| case TIMESTAMP: |
| case LONGVARCHAR: |
| case LONGVARBINARY: |
| case BLOB: |
| case CLOB: |
| return type.toString(); |
| |
| case CHAR: |
| case VARCHAR: |
| case BINARY: |
| case VARBINARY: |
| return type.toString() + field.formatPrecision(); |
| |
| case NUMERIC: |
| case DECIMAL: |
| return type.toString() + field.formatPrecisionAndScale(); |
| } |
| |
| throw new IllegalArgumentException("Unknown type: " + type); //$NON-NLS-1$ |
| } |
| |
| public String[] getSQL92ReservedWords() |
| { |
| return SQL92_RESERVED_WORDS; |
| } |
| |
| public boolean isReservedWord(String word) |
| { |
| if (reservedWords == null) |
| { |
| reservedWords = new HashSet<String>(); |
| for (String reservedWord : getReservedWords()) |
| { |
| reservedWords.add(reservedWord.toUpperCase()); |
| } |
| } |
| |
| word = word.toUpperCase(); |
| return reservedWords.contains(word); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected void validateTable(IDBTable table, Statement statement) throws DBException |
| { |
| int maxRows = 1; |
| |
| try |
| { |
| maxRows = statement.getMaxRows(); |
| statement.setMaxRows(1); |
| |
| String sql = null; |
| |
| try |
| { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("SELECT "); //$NON-NLS-1$ |
| appendFieldNames(builder, table); |
| builder.append(" FROM "); //$NON-NLS-1$ |
| builder.append(table); |
| sql = builder.toString(); |
| |
| if (TRACER.isEnabled()) |
| { |
| TRACER.format("{0}", sql); //$NON-NLS-1$ |
| } |
| |
| ResultSet resultSet = statement.executeQuery(sql); |
| |
| try |
| { |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| int columnCount = metaData.getColumnCount(); |
| if (columnCount != table.getFieldCount()) |
| { |
| throw new DBException("DBTable " + table + " has " + columnCount + " columns instead of " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| + table.getFieldCount()); |
| } |
| } |
| finally |
| { |
| DBUtil.close(resultSet); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException("Problem with table " + table, ex, sql); |
| } |
| finally |
| { |
| if (maxRows != 1) |
| { |
| statement.setMaxRows(maxRows); |
| } |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected String[] createFieldDefinitions(IDBTable table) |
| { |
| IDBField[] fields = table.getFields(); |
| int fieldCount = fields.length; |
| |
| String[] result = new String[fieldCount]; |
| for (int i = 0; i < fieldCount; i++) |
| { |
| IDBField field = fields[i]; |
| result[i] = createFieldDefinition(field); |
| } |
| |
| return result; |
| } |
| |
| public void appendFieldNames(Appendable appendable, IDBTable table) |
| { |
| try |
| { |
| IDBField[] fields = table.getFields(); |
| for (int i = 0; i < fields.length; i++) |
| { |
| IDBField field = fields[i]; |
| if (i != 0) |
| { |
| appendable.append(", "); //$NON-NLS-1$ |
| } |
| |
| String fieldName = field.getName(); |
| appendable.append(fieldName); |
| } |
| } |
| catch (IOException canNotHappen) |
| { |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void appendFieldDefs(Appendable appendable, IDBTable table, String[] defs) |
| { |
| try |
| { |
| IDBField[] fields = table.getFields(); |
| for (int i = 0; i < fields.length; i++) |
| { |
| IDBField field = fields[i]; |
| if (i != 0) |
| { |
| appendable.append(", "); //$NON-NLS-1$ |
| } |
| |
| String fieldName = field.getName(); |
| appendable.append(fieldName); |
| appendable.append(" "); //$NON-NLS-1$ |
| appendable.append(defs[i]); |
| } |
| } |
| catch (IOException canNotHappen) |
| { |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public DBType adaptType(DBType type) |
| { |
| return type; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public boolean isValidFirstChar(char ch) |
| { |
| return true; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public boolean isDuplicateKeyException(SQLException ex) |
| { |
| String sqlState = ex.getSQLState(); |
| return "23001".equals(sqlState); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public boolean isTableNotFoundException(SQLException ex) |
| { |
| String sqlState = ex.getSQLState(); |
| return "42S02".equals(sqlState); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public boolean isColumnNotFoundException(SQLException ex) |
| { |
| String sqlState = ex.getSQLState(); |
| return "42S22".equals(sqlState); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public String sqlRenameField(IDBField field, String oldName) |
| { |
| return "ALTER TABLE " + field.getTable() + " RENAME COLUMN " + oldName + " TO " + field; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public String sqlModifyField(IDBField field) |
| { |
| String tableName = field.getTable().getName(); |
| String fieldName = field.getName(); |
| |
| String definition = createFieldDefinition(field); |
| return sqlModifyField(tableName, fieldName, definition); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected String sqlModifyField(String tableName, String fieldName, String definition) |
| { |
| return "ALTER TABLE " + tableName + " ALTER COLUMN " + fieldName + " " + definition; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public String format(PreparedStatement stmt) |
| { |
| return stmt.toString(); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public String format(ResultSet resultSet) |
| { |
| try |
| { |
| StringBuilder builder = new StringBuilder(); |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| int columnCount = metaData.getColumnCount(); |
| for (int i = 0; i < columnCount; i++) |
| { |
| if (i != 0) |
| { |
| builder.append(", "); |
| } |
| |
| builder.append(metaData.getColumnName(i + 1).toLowerCase()); |
| builder.append("="); |
| builder.append(resultSet.getObject(i + 1)); |
| } |
| |
| return builder.toString(); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static int getDefaultDBLength(DBType type) |
| { |
| return type == DBType.VARCHAR ? 32672 : IDBField.DEFAULT; |
| } |
| |
| /** |
| * @since 4.2 |
| * @author Eike Stepper |
| */ |
| protected static final class FieldInfo implements Comparable<FieldInfo> |
| { |
| public String name; |
| |
| public int position; |
| |
| public int compareTo(FieldInfo o) |
| { |
| return position - o.position; |
| } |
| } |
| } |