package com.eas.client.sqldrivers; import com.eas.client.ClientConstants; import com.eas.client.SQLUtils; import com.eas.client.changes.JdbcChangeValue; import com.eas.client.dataflow.StatementsGenerator; import com.eas.client.metadata.DbTableIndexSpec; import com.eas.client.metadata.JdbcField; import com.eas.client.metadata.ForeignKeySpec; import com.eas.client.metadata.PrimaryKeySpec; import com.eas.client.settings.SettingsConstants; import com.eas.client.sqldrivers.resolvers.TypesResolver; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Wrapper; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author mg */ public abstract class SqlDriver implements StatementsGenerator.GeometryConverter { protected static class TwinString { private final String left; private final String right; public TwinString(String aLeft, String aRight) { left = aLeft; right = aRight; } public String getLeft() { return left; } public String getRight() { return right; } } // error codes protected static final String EAS_TABLE_ALREADY_EXISTS = "EAS_TABLE_ALREADY_EXISTS"; protected static final String EAS_TABLE_DOESNT_EXISTS = "EAS_TABLE_DOESNT_EXISTS"; // misc protected static final String EAS_SQL_SCRIPT_DELIMITER = "#GO"; public static final String DROP_FIELD_SQL_PREFIX = "alter table %s drop column "; public static final String ADD_FIELD_SQL_PREFIX = "alter table %s add "; public static final String PKEY_NAME_SUFFIX = "_pk"; public SqlDriver() { super(); } /** * Adds tables, foreign keys etc. to a database for in database users space * * @param aConnection * @throws Exception */ public void initializeUsersSpace(Connection aConnection) throws Exception { if (!checkUsersSpaceInitialized(aConnection)) { String scriptText = readUsersSpaceInitScriptResource(); Logger.getLogger(SqlDriver.class.getName()).log(Level.INFO, "About to initialize in-database users space."); applyScript(scriptText, aConnection); } } /** * Adds tables, foreign keys etc. to a database for database versioning * * @param aConnection * @throws Exception */ public void initializeVersion(Connection aConnection) throws Exception { if (!checkVersionInitialized(aConnection)) { String scriptText = readVersionInitScriptResource(); Logger.getLogger(SqlDriver.class.getName()).log(Level.INFO, "About to initialize database versioning."); applyScript(scriptText, aConnection); } } /** * * * The database supports deferrable constraints to enable constrains check * on transaction commit. * * @return true if constraints is deferrable */ public abstract boolean isConstraintsDeferrable(); /** * * * Gets type resolver to convert SQL types to JDBC types and vice-versa. * * @return TypesResolver instance */ public abstract TypesResolver getTypesResolver(); /** * * Gets in database users space initial script location and file name. * * @return */ public abstract String getUsersSpaceInitResourceName(); /** * * Gets database versioning initial script location and file name. * * @return */ public abstract String getVersionInitResourceName(); /** * * * Sets current schema for current session. * * @param aConnection JDBC connection * @param aSchema Schema name * @throws Exception in the case of operation failure */ public abstract void applyContextToConnection(Connection aConnection, String aSchema) throws Exception; /** * Gets current schema for connection * * @param aConnection JDBC connection * @return Schema name * @throws Exception in the case of operation failure */ public String getConnectionContext(Connection aConnection) throws Exception { try (PreparedStatement stmt = aConnection.prepareStatement(getSql4GetConnectionContext())) { ResultSet rs = stmt.executeQuery(); rs.next(); return rs.getString(1); } } public abstract String getSql4GetConnectionContext(); /** * Returns sql text for create new schema. * * @param aSchemaName schema name * @param aPassword owner password, required for some databases (Oracle) * @return Sql text. */ public abstract String getSql4CreateSchema(String aSchemaName, String aPassword); /** * * * Returns sql clause array to set column's comment. Eeach sql clause from * array executed consequentially * * @param aOwnerName Schema name * @param aTableName Table name * @param aFieldName Column name * @param aDescription Comment * @return Sql texts array */ public abstract String[] getSql4CreateColumnComment(String aOwnerName, String aTableName, String aFieldName, String aDescription); /** * * * Returns sql clause to set table's comment. * * @param aOwnerName Schema name * @param aTableName Table name * @param aDescription Comment * @return Sql text */ public abstract String getSql4CreateTableComment(String aOwnerName, String aTableName, String aDescription); /** * * * Gets sql clause for dropping the table. * * @param aSchemaName Schema name * @param aTableName Table name * @return sql text */ public abstract String getSql4DropTable(String aSchemaName, String aTableName); /** * * * Gets sql clause for dropping the index on the table. * * @param aSchemaName Schema name * @param aTableName Table name * @param aIndexName Index name * @return sql text */ public abstract String getSql4DropIndex(String aSchemaName, String aTableName, String aIndexName); /** * * * Gets sql clause for dropping the foreign key constraint. * * @param aSchemaName Schema name * @param aFk Foreign key specification object * @return Sql text */ public abstract String getSql4DropFkConstraint(String aSchemaName, ForeignKeySpec aFk); /** * * * Gets sql clause for creating the primary key. * * @param aSchemaName Schema name * @param listPk Primary key columns specifications list * @return Sql text */ public abstract String[] getSql4CreatePkConstraint(String aSchemaName, List<PrimaryKeySpec> listPk); /** * * * Gets sql clause for dropping the primary key. * * @param aSchemaName Schema name * @param aPk Primary key specification * @return Sql text */ public abstract String getSql4DropPkConstraint(String aSchemaName, PrimaryKeySpec aPk); /** * * * Gets sql clause for creating the foreign key constraint. * * @param aSchemaName Schema name * @param aFk Foreign key specification * @return Sql text */ public abstract String getSql4CreateFkConstraint(String aSchemaName, ForeignKeySpec aFk); /** * * * Gets sql clause for creating the foreign key constraint. * * @param aSchemaName Schema name * @param listFk Foreign key columns specifications list * @return Sql text */ public abstract String getSql4CreateFkConstraint(String aSchemaName, List<ForeignKeySpec> listFk); /** * * * Gets sql clause for creating the index * * @param aSchemaName Schema name * @param aTableName Table name * @param aIndex Index specification * @return Sql text */ public abstract String getSql4CreateIndex(String aSchemaName, String aTableName, DbTableIndexSpec aIndex); /** * * * Gets sql clause for creating an empty table. * * @param aSchemaName Schema name * @param aTableName Table name * @param aPkFieldName Column name for primary key * @return Sql text */ public abstract String getSql4EmptyTableCreation(String aSchemaName, String aTableName, String aPkFieldName); /** * * * Gets specific exception message. * * @param ex Exception * @return Exception message */ public abstract String parseException(Exception ex); /** * Generates Sql string fragment for field definition, according to specific * features of particular database. If it meets any strange type, such * java.sql.Types.OTHER or java.sql.Types.STRUCT, it uses the field's type * name. * * @param aField A field information to deal with. * @return Sql string for field definition */ public abstract String getSql4FieldDefinition(JdbcField aField); /** * Generates Sql string to modify a field, according to specific features of * particular database. If it meats any strange type, such * java.sql.Types.OTHER or java.sql.Types.STRUCT, it uses the field's type * name. * * @param aSchemaName Schema name * @param aTableName Name of the table with that field * @param aField A field information * @return Sql array string for field modification. */ public abstract String[] getSqls4AddingField(String aSchemaName, String aTableName, JdbcField aField); /** * Generates sql texts array for dropping a field. Sql clauses from array * will execute consequentially * * @param aSchemaName Schema name * @param aTableName Name of a table the field to dropped from. * @param aFieldName Field name to drop * @return Sql string generted. */ public String[] getSql4DroppingField(String aSchemaName, String aTableName, String aFieldName) { String fullTableName = wrapNameIfRequired(aTableName); if (aSchemaName != null && !aSchemaName.isEmpty()) { fullTableName = wrapNameIfRequired(aSchemaName) + "." + fullTableName; } return new String[]{ String.format(DROP_FIELD_SQL_PREFIX, fullTableName) + wrapNameIfRequired(aFieldName) }; } /** * Generates Sql string to modify a field, according to specific features of * particular database. If it meats any strange type, such * java.sql.Types.OTHER or java.sql.Types.STRUCT, it uses the field's type * name. * * @param aSchemaName Schema name * @param aTableName Name of the table with that field * @param aOldFieldMd A field information to migrate from. * @param aNewFieldMd A field information to migrate to. * @return Sql array string for field modification. */ public abstract String[] getSqls4ModifyingField(String aSchemaName, String aTableName, JdbcField aOldFieldMd, JdbcField aNewFieldMd); /** * * * Generates Sql string to rename a field, according to specific features of * particular database. * * @param aSchemaName Schema name * @param aTableName Table name * @param aOldFieldName Old column name * @param aNewFieldMd New field * @return Sql array string for field modification. */ public abstract String[] getSqls4RenamingField(String aSchemaName, String aTableName, String aOldFieldName, JdbcField aNewFieldMd); public static void applyScript(String scriptText, Connection aConnection) throws Exception { String[] commandsTexts = scriptText.split(EAS_SQL_SCRIPT_DELIMITER); if (commandsTexts != null) { boolean autoCommit = aConnection.getAutoCommit(); try { aConnection.setAutoCommit(false); try (Statement stmt = aConnection.createStatement()) { for (String commandText : commandsTexts) { String queryText = commandText; queryText = queryText.replace('\r', ' '); queryText = queryText.replace('\n', ' '); if (!queryText.isEmpty()) { try { stmt.execute(queryText); aConnection.commit(); } catch (Exception ex) { aConnection.rollback(); Logger.getLogger(SqlDriver.class.getName()).log(Level.WARNING, "Error applying SQL script. {0}", ex.getMessage()); } } } } } finally { aConnection.setAutoCommit(autoCommit); } } } private boolean checkUsersSpaceInitialized(Connection aConnection) { try { try (PreparedStatement stmt = aConnection.prepareStatement(String.format(SQLUtils.SQL_MAX_COMMON_BY_FIELD, ClientConstants.F_USR_NAME, ClientConstants.F_USR_NAME, ClientConstants.T_MTD_USERS))) { ResultSet res = stmt.executeQuery(); res.close(); } return true; } catch (SQLException ex) { try { aConnection.rollback(); } catch (SQLException ex1) { Logger.getLogger(SqlDriver.class.getName()).log(Level.SEVERE, null, ex1); } Logger.getLogger(SqlDriver.class.getName()).log(Level.WARNING, "In database users space seems to be uninitialized. {0}", ex.getMessage()); } return false; } private boolean checkVersionInitialized(Connection aConnection) { try { try (PreparedStatement stmt = aConnection.prepareStatement(String.format(SQLUtils.SQL_MAX_COMMON_BY_FIELD, ClientConstants.F_VERSION_VALUE, ClientConstants.F_VERSION_VALUE, ClientConstants.T_MTD_VERSION))) { ResultSet res = stmt.executeQuery(); res.close(); } return true; } catch (SQLException ex) { try { aConnection.rollback(); } catch (SQLException ex1) { Logger.getLogger(SqlDriver.class.getName()).log(Level.SEVERE, null, ex1); } Logger.getLogger(SqlDriver.class.getName()).log(Level.WARNING, "Database vertioning seems to be uninitialized. {0}", ex.getMessage()); } return false; } private String readUsersSpaceInitScriptResource() throws IOException { String resName = getUsersSpaceInitResourceName(); return readScriptResource(resName); } private String readVersionInitScriptResource() throws IOException { String resName = getVersionInitResourceName(); return readScriptResource(resName); } protected String readScriptResource(String resName) throws IOException { try (InputStream is = SqlDriver.class.getResourceAsStream(resName)) { byte[] data = new byte[is.available()]; is.read(data); return new String(data, SettingsConstants.COMMON_ENCODING); } } public String makeFullName(String aSchemaName, String aName) { String name = wrapNameIfRequired(aName); if (aSchemaName != null && !aSchemaName.isEmpty()) { name = wrapNameIfRequired(aSchemaName) + "." + name; } return name; } protected String constructIn(Set<String> strings) { StringBuilder sb = new StringBuilder(); String delimiter = ""; for (String l : strings) { sb.append(delimiter).append("'").append(l.replaceAll("'", "''")).append("'"); delimiter = ", "; } return sb.toString(); } @Override public abstract JdbcChangeValue convertGeometry(String aValue, Connection aConnection) throws SQLException; public abstract String readGeometry(Wrapper aRs, int aColumnIndex, Connection aConnection) throws SQLException; abstract public TwinString[] getCharsForWrap(); abstract public char[] getRestrictedChars(); abstract public boolean isHadWrapped(String aName); protected boolean isHaveLowerCase(String aValue) { if (aValue != null) { for (char c : aValue.toCharArray()) { if (Character.isLowerCase(c)) { return true; } } } return false; } protected boolean isHaveUpperCase(String aValue) { if (aValue != null) { for (char c : aValue.toCharArray()) { if (Character.isUpperCase(c)) { return true; } } } return false; } /** * * Wrapping names containing restricted symbols. * * @param aName Name to wrap * @return Wrapped text */ public String wrapNameIfRequired(String aName) { return wrapName(aName, isRequiredWrap(aName)); } public String wrapName(String aName, boolean requiredOnly) { if (aName != null && !aName.isEmpty() && !isWrappedName(aName) && requiredOnly) { TwinString[] twinsWrap = getCharsForWrap(); if (twinsWrap != null && twinsWrap.length > 0) { String wrapL = twinsWrap[0].getLeft(); String wrapR = twinsWrap[0].getRight(); StringBuilder sb = new StringBuilder(); sb.append(wrapL); if (wrapL.length() == 1) { if (wrapL.equals(wrapR)) { sb.append(aName.replaceAll(wrapL, wrapL + wrapL)); } else { sb.append(aName.replaceAll(wrapL, wrapL + wrapL).replaceAll(wrapR, wrapR + wrapR)); } } else { sb.append(aName); } sb.append(wrapR); return sb.toString(); } } return aName; } public String unwrapName(String aName) { int wrapLength = getWrapLength(aName); if (wrapLength > 0) { int length = aName.length(); String left = aName.substring(0, wrapLength); String right = aName.substring(length - wrapLength); if (left.equals(right)) { return aName.substring(wrapLength, length - wrapLength).replaceAll(left + right, left); } return aName.substring(wrapLength, length - wrapLength); } return aName; } public abstract boolean is(String aDialect); public boolean isWrappedName(String aName) { return getWrapLength(aName) > 0; } public int getWrapLength(String aName) { if (aName != null && !aName.isEmpty()) { TwinString[] twins = getCharsForWrap(); if (twins != null) { for (TwinString twin : twins) { String left = twin.getLeft(); String right = twin.getRight(); if (aName.startsWith(left) && aName.endsWith(right)) { return left.length(); } } } } return 0; } public boolean isRequiredWrap(String aName) { if (aName != null && !aName.isEmpty()) { char[] restricted = getRestrictedChars(); assert restricted != null; for (char c : aName.toCharArray()) { for (char rC : restricted) { if (c == rC) { return true; } } } } return false; } public String generatePkName(String aTableName, String aSuffix) { int wrapLength = getWrapLength(aTableName); StringBuilder sb = new StringBuilder(); sb.append(aTableName.substring(0, aTableName.length() - wrapLength)); sb.append(aSuffix); sb.append(aTableName.substring(aTableName.length() - wrapLength)); return sb.toString(); } }