diff --git a/flyway-community-db-support-archetype/pom.xml b/flyway-community-db-support-archetype/pom.xml index 1ededac..0d673e1 100644 --- a/flyway-community-db-support-archetype/pom.xml +++ b/flyway-community-db-support-archetype/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.flywaydb flyway-community-db-support-archetype - 10.13.0 + 10.14.0 maven-archetype Archetype - flyway-community-db-support-archetype diff --git a/flyway-database-clickhouse/pom.xml b/flyway-database-clickhouse/pom.xml index d250498..cb30199 100644 --- a/flyway-database-clickhouse/pom.xml +++ b/flyway-database-clickhouse/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-clickhouse diff --git a/flyway-database-databricks/pom.xml b/flyway-database-databricks/pom.xml index 1056b86..3f92628 100644 --- a/flyway-database-databricks/pom.xml +++ b/flyway-database-databricks/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-databricks diff --git a/flyway-database-db2zOS/pom.xml b/flyway-database-db2zOS/pom.xml new file mode 100644 index 0000000..134e9bf --- /dev/null +++ b/flyway-database-db2zOS/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + org.flywaydb + flyway-community-db-support + 10.14.0 + + + flyway-database-db2zOS + ${project.artifactId} + + + + ${project.groupId} + flyway-core + + + org.projectlombok + lombok + provided + + + + + + + src/main/resources + true + + + + + maven-resources-plugin + + + maven-jar-plugin + + + + diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/DB2ZDatabaseExtension.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/DB2ZDatabaseExtension.java new file mode 100644 index 0000000..b2be151 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/DB2ZDatabaseExtension.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.flywaydb.community.database.db2z.DB2ZConfigurationExtension; +import org.flywaydb.core.api.FlywayException; +import org.flywaydb.core.extensibility.PluginMetadata; +import org.flywaydb.core.internal.util.FileUtils; + +public class DB2ZDatabaseExtension implements PluginMetadata { + + public String getDescription() { + return "Community-contributed DB2/zOS database support extension " + readVersion() + " by Redgate"; + } + + public static String readVersion() { + try { + return FileUtils.copyToString( + DB2ZConfigurationExtension.class.getClassLoader().getResourceAsStream("org/flywaydb/community/database/db2z/version.txt"), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new FlywayException("Unable to read extension version: " + e.getMessage(), e); + } + } + +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZCallProcedureParsedStatement.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZCallProcedureParsedStatement.java new file mode 100644 index 0000000..e8b7997 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZCallProcedureParsedStatement.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Pattern; +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; +import org.flywaydb.core.internal.jdbc.Result; +import org.flywaydb.core.internal.jdbc.Results; +import org.flywaydb.core.internal.sqlscript.Delimiter; +import org.flywaydb.core.internal.sqlscript.ParsedSqlStatement; +import org.flywaydb.core.internal.sqlscript.SqlScriptExecutor; + + +/** + * A DB2Z CALL PROCEDURE statement. + */ +public class DB2ZCallProcedureParsedStatement extends ParsedSqlStatement { + + private final String procedureName; + private final Object[] parms; + + private static final Pattern DB2Z_DSNUTILU_PROCNAME = Pattern.compile( + "\"?SYSPROC\"?\\.\"?DSNUTILU\"?", Pattern.CASE_INSENSITIVE); + /** + * Creates a new DB2Z CALL PROCEDURE statement. + */ + public DB2ZCallProcedureParsedStatement(int pos, int line, int col, String sql, Delimiter delimiter, + boolean canExecuteInTransaction, boolean batchable, + String procedureName, Object[] parms) { + super(pos, line, col, sql, delimiter, canExecuteInTransaction, batchable); + this.procedureName = procedureName; + this.parms = parms; + } + + @Override + public Results execute(JdbcTemplate jdbcTemplate, SqlScriptExecutor sqlScriptExecutor, Configuration config) { + Results results; + String callStmt = "CALL " + procedureName + "("; + for(int i=0; i < parms.length; i++) { + callStmt += (i > 0 ? ", ?" : "?"); + } + callStmt += ")"; + + results = ((DB2ZJdbcTemplate)jdbcTemplate).executeCallableStatement(callStmt, parms); + + //For SYSPROC.DSNUTILU invocations, check last result row to detect any error + if(DB2Z_DSNUTILU_PROCNAME.matcher(procedureName).matches()) { + List resultList = results.getResults(); + if(resultList.size() > 0) { + Result result = resultList.get(0); + if(result != null) { + List> resultData = result.getData(); + if(resultData != null && resultData.size() > 0) { + List lastResultRow = resultData.get(resultData.size()-1); + if(lastResultRow != null && lastResultRow.size() > 0 ) { + String lastMessage = lastResultRow.get(lastResultRow.size()-1); + if(lastMessage != null && ( + lastMessage.contains("DSNUGBAC - UTILITY EXECUTION TERMINATED, HIGHEST RETURN CODE=") || + lastMessage.contains("DSNUGBAC - UTILITY BATCH MEMORY EXECUTION ABENDED"))) { + String message = "DSNUTILU TERMINATED WITH OUTPUT:\n"; + for(List row : resultData) { + message += row.get(row.size()-1) + "\n"; + } + results.setException(new SQLException(message)); + } + } + } + } + } + } + + return results; + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConfigurationExtension.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConfigurationExtension.java new file mode 100644 index 0000000..6349f77 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConfigurationExtension.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.util.Map; +import lombok.Data; +import org.flywaydb.core.extensibility.ConfigurationExtension; + +@Data +public class DB2ZConfigurationExtension implements ConfigurationExtension { + private static final String DATABASE_NAME = "flyway.db2z.databaseName"; + private static final String SQL_ID = "flyway.db2z.sqlId"; + + /** + * The database name for DB2 on z/OS (required for DB2 on z/OS) + */ + private String databaseName = ""; + /** + * The SQLID for DB2 on z/OS (does not necessarily match with schema) + */ + private String sqlId = ""; + + + @Override + public String getNamespace() { + return "db2z"; + } + + @Override + public void extractParametersFromConfiguration(Map configuration) { + databaseName = configuration.getOrDefault(DATABASE_NAME, databaseName); + sqlId = configuration.getOrDefault(SQL_ID, sqlId); + configuration.remove(DATABASE_NAME); + configuration.remove(SQL_ID); + } + + @Override + public String getConfigurationParameterFromEnvironmentVariable(String environmentVariable) { + if ("FLYWAY_DB2Z_DATABASE_NAME".equals(environmentVariable)) { + return DATABASE_NAME; + } + if ("FLYWAY_DB2Z_SQL_ID".equals(environmentVariable)) { + return SQL_ID; + } + return null; + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConnection.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConnection.java new file mode 100644 index 0000000..29d2d3b --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZConnection.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import lombok.CustomLog; +import org.flywaydb.core.internal.database.base.Connection; +import org.flywaydb.core.internal.database.base.Schema; +import org.flywaydb.core.internal.exception.FlywaySqlException; + +/** + * DB2 connection. + */ +@CustomLog +public class DB2ZConnection extends Connection { + DB2ZConnection(DB2ZDatabase database, java.sql.Connection connection) { + super(database, connection); + this.jdbcTemplate = new DB2ZJdbcTemplate(connection, database.getDatabaseType()); + } + + @Override + protected String getCurrentSchemaNameOrSearchPath() throws SQLException { + return jdbcTemplate.queryForString("select current_schema from sysibm.sysdummy1"); + } + + @Override + public void changeCurrentSchemaTo(Schema schema) { + try { + if (!schema.exists()) { + return; + } + doChangeCurrentSchemaOrSearchPathTo(schema.getName()); + } catch (SQLException e) { + String sqlId = (database.getSqlId() == "") ? schema.getName() : database.getSqlId(); + LOG.info("SET CURRENT SQLID = '" + sqlId + "'"); + LOG.info("SET SCHEMA " + database.quote(schema.getName())); + throw new FlywaySqlException("Error setting current sqlid and/or schema", e); + } + } + + @Override + public void doChangeCurrentSchemaOrSearchPathTo(String schema) throws SQLException { + // Maybe sqlid not same as schema name and entered as config property + String sqlId = (database.getSqlId() == "") ? schema : database.getSqlId(); + jdbcTemplate.execute("SET CURRENT SQLID = '" + sqlId + "'"); + jdbcTemplate.execute("SET SCHEMA " + database.quote(schema)); + } + + @Override + public Schema getSchema(String name) { + return new DB2ZSchema(jdbcTemplate, database, name); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabase.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabase.java new file mode 100644 index 0000000..8337262 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabase.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.Connection; +import java.sql.SQLException; +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.extensibility.Tier; +import org.flywaydb.core.internal.database.base.Database; +import org.flywaydb.core.internal.database.base.Table; +import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory; +import org.flywaydb.core.internal.jdbc.StatementInterceptor; + +/** + * DB2 database. + */ +public class DB2ZDatabase extends Database { + private String name; + + /** + * Creates a new instance. + * + * @param configuration The Flyway configuration. + */ + public DB2ZDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) { + super(configuration, jdbcConnectionFactory, statementInterceptor); + DB2ZConfigurationExtension configurationExtension = configuration.getPluginRegister().getPlugin(DB2ZConfigurationExtension.class); + name = configurationExtension.getDatabaseName(); + } + + @Override + protected DB2ZConnection doGetConnection(Connection connection) { + return new DB2ZConnection(this, connection); + } + + public String getName() { + return name; + } + + public String getSqlId() { + DB2ZConfigurationExtension configurationExtension = configuration.getPluginRegister().getPlugin(DB2ZConfigurationExtension.class); + return configurationExtension.getSqlId(); + } + + @Override + public void ensureSupported(Configuration configuration) { + ensureDatabaseIsRecentEnough("10.0"); + + ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition("11.0", Tier.PREMIUM, configuration); + + recommendFlywayUpgradeIfNecessary("12.1"); + } + + @Override + public String getRawCreateScript(Table table, boolean baseline) { + String tableSpaceName = "SFLYWAY"; + String configurationTablespaceName = configuration.getTablespace(); + if(configurationTablespaceName != null) { + tableSpaceName = configurationTablespaceName; + } + // Maybe sqlid not same as schema name and entered as config property + String sqlId = (this.getSqlId() == "") ? table.getSchema().getName() : this.getSqlId(); + + return "SET CURRENT SQLID = '" + sqlId + "';\n" + + "SET CURRENT SCHEMA = '" + table.getSchema().getName() + "';\n" + + "CREATE TABLESPACE " + tableSpaceName + " IN \"" + name + "\" MAXPARTITIONS 1 LOCKSIZE ROW CLOSE YES COMPRESS YES;\n" + + "CREATE TABLE " + table + " (\n" + + " \"installed_rank\" INT NOT NULL,\n" + + " \"version\" VARCHAR(50),\n" + + " \"description\" VARCHAR(200) NOT NULL,\n" + + " \"type\" VARCHAR(20) NOT NULL,\n" + + " \"script\" VARCHAR(1000) NOT NULL,\n" + + " \"checksum\" INT,\n" + + " \"installed_by\" VARCHAR(100) NOT NULL,\n" + + " \"installed_on\" TIMESTAMP DEFAULT NOT NULL,\n" + + " \"execution_time\" INT NOT NULL,\n" + + " \"success\" SMALLINT NOT NULL,\n" + + " CONSTRAINT \"" + table.getName() + "_s\" CHECK (\"success\" in (0, 1))\n" + + ") IN \"" + name + "\"." + tableSpaceName + ";\n" + + "CREATE UNIQUE INDEX \"" + table.getSchema().getName() + "\".\"" + table.getName() + "_pk_idx\" ON " + table + " (\"installed_rank\");" + + "ALTER TABLE " + table + " ADD CONSTRAINT \"" + table.getName() + "_pk\" PRIMARY KEY (\"installed_rank\");\n" + + "CREATE INDEX \"" + table.getSchema().getName() + "\".\"" + table.getName() + "_s_idx\" ON " + table + " (\"success\");" + + (baseline ? getBaselineStatement(table) + ";\n" : ""); + } + + @Override + public String getSelectStatement(Table table) { + return super.getSelectStatement(table) + // Allow uncommitted reads so info can be invoked while migrate is running + + " WITH UR"; + } + + @Override + protected String doGetCurrentUser() throws SQLException { + return getMainConnection().getJdbcTemplate().queryForString("select USER from sysibm.sysdummy1"); + } + + @Override + public boolean supportsDdlTransactions() { + return true; + } + + @Override + public String getBooleanTrue() { + return "1"; + } + + @Override + public String getBooleanFalse() { + return "0"; + } + + @Override + public String doQuote(String identifier) { + return "\"" + identifier + "\""; + } + + @Override + public boolean catalogIsSchema() { + return false; + } + + @Override + public boolean useSingleConnection() { + return true; + } + +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabaseType.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabaseType.java new file mode 100644 index 0000000..2ae3516 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZDatabaseType.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.Connection; +import java.sql.Types; +import java.util.Properties; +import org.flywaydb.community.database.DB2ZDatabaseExtension; +import org.flywaydb.core.api.ResourceProvider; +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.internal.callback.CallbackExecutor; +import org.flywaydb.core.internal.database.DatabaseType; +import org.flywaydb.core.internal.database.base.BaseDatabaseType; +import org.flywaydb.core.internal.database.base.CommunityDatabaseType; +import org.flywaydb.core.internal.database.base.Database; +import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory; +import org.flywaydb.core.internal.jdbc.StatementInterceptor; +import org.flywaydb.core.internal.parser.Parser; +import org.flywaydb.core.internal.parser.ParsingContext; +import org.flywaydb.core.internal.sqlscript.DefaultSqlScriptExecutor; +import org.flywaydb.core.internal.sqlscript.SqlScriptExecutorFactory; + +public class DB2ZDatabaseType extends BaseDatabaseType implements CommunityDatabaseType { + + public String getName() { + return "DB2 for z/OS"; + } + + @Override + public int getNullType() { + return Types.VARCHAR; + } + + @Override + public boolean handlesJDBCUrl(String url) { + return url.startsWith("jdbc:db2:") || url.startsWith("jdbc:p6spy:db2:"); + } + + @Override + public String getDriverClass(String url, ClassLoader classLoader) { + if (url.startsWith("jdbc:p6spy:db2:")) { + return "com.p6spy.engine.spy.P6SpyDriver"; + } + return "com.ibm.db2.jcc.DB2Driver"; + } + + @Override + public boolean handlesDatabaseProductNameAndVersion(String databaseProductName, String databaseProductVersion, Connection connection) { + return databaseProductName.startsWith("DB2") && databaseProductVersion.startsWith("DSN"); + } + + @Override + public Database createDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) { + return new DB2ZDatabase(configuration, jdbcConnectionFactory, statementInterceptor); + } + + @Override + public Parser createParser(Configuration configuration, ResourceProvider resourceProvider, ParsingContext parsingContext) { + return new DB2ZParser(configuration, parsingContext); + } + + @Override + public void setDefaultConnectionProps(String url, Properties props, ClassLoader classLoader) { + props.put("clientProgramName", APPLICATION_NAME); + props.put("retrieveMessagesFromServerOnGetMessage", "true"); + } + + @Override + public SqlScriptExecutorFactory createSqlScriptExecutorFactory(final JdbcConnectionFactory jdbcConnectionFactory, + final CallbackExecutor callbackExecutor, + final StatementInterceptor statementInterceptor) { + boolean supportsBatch = false; + + + + + final boolean finalSupportsBatch = supportsBatch; + final DatabaseType thisRef = this; + + return (connection, undo, batch, outputQueryResults) -> new DefaultSqlScriptExecutor(new DB2ZJdbcTemplate(connection, thisRef), + callbackExecutor, undo, finalSupportsBatch && batch, outputQueryResults, statementInterceptor); + } + + @Override + public int getPriority() { + // DB2/zOS needs to be checked in advance of DB2 + return 1; + } + + @Override + public String getPluginVersion(Configuration config) { + return DB2ZDatabaseExtension.readVersion(); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZFunction.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZFunction.java new file mode 100644 index 0000000..22b63ee --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZFunction.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import org.flywaydb.core.internal.database.base.Database; +import org.flywaydb.core.internal.database.base.Function; +import org.flywaydb.core.internal.database.base.Schema; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; + +/** + * DB2-specific function. + */ +public class DB2ZFunction extends Function { + /** + * Creates a new Db2 function. + * + * @param jdbcTemplate The Jdbc Template for communicating with the DB. + * @param database The database-specific support. + * @param schema The schema this function lives in. + * @param name The name of the function. + * @param args The arguments of the function. + */ + DB2ZFunction(JdbcTemplate jdbcTemplate, Database database, Schema schema, String name, String... args) { + super(jdbcTemplate, database, schema, name, args); + } + + @Override + protected void doDrop() throws SQLException { + jdbcTemplate.execute("DROP SPECIFIC FUNCTION " + database.quote(schema.getName(), name)); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZJdbcTemplate.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZJdbcTemplate.java new file mode 100644 index 0000000..8982518 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZJdbcTemplate.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import org.flywaydb.core.internal.database.DatabaseType; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; +import org.flywaydb.core.internal.jdbc.JdbcUtils; +import org.flywaydb.core.internal.jdbc.Results; + +public class DB2ZJdbcTemplate extends JdbcTemplate { + + public DB2ZJdbcTemplate(Connection connection, DatabaseType databaseType) { + super(connection, databaseType); + } + + /** + * Executes this callable sql statement using a PreparedStatement. + * + * @param sql The statement to execute. + * @param params The statement parameters. + * @return the results of the execution. + */ + public Results executeCallableStatement(String sql, Object... params) { + Results results = new Results(); + PreparedStatement statement = null; + try { + statement = prepareStatement(sql, params); + boolean hasResults = statement.execute(); + extractResults(results, statement, sql, hasResults); + extractWarnings(results, statement); + } catch (final SQLException e) { + extractErrors(results, e); + } finally { + JdbcUtils.closeStatement(statement); + } + return results; + } + +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZParser.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZParser.java new file mode 100644 index 0000000..4eb8210 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZParser.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.CustomLog; +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.internal.parser.Parser; +import org.flywaydb.core.internal.parser.ParserContext; +import org.flywaydb.core.internal.parser.ParsingContext; +import org.flywaydb.core.internal.parser.PeekingReader; +import org.flywaydb.core.internal.parser.Recorder; +import org.flywaydb.core.internal.parser.StatementType; +import org.flywaydb.core.internal.parser.Token; +import org.flywaydb.core.internal.parser.TokenType; +import org.flywaydb.core.internal.sqlscript.Delimiter; +import org.flywaydb.core.internal.sqlscript.ParsedSqlStatement; + +@CustomLog +public class DB2ZParser extends Parser { + private static final String COMMENT_DIRECTIVE = "--#"; + private static final String SET_TERMINATOR_DIRECTIVE = COMMENT_DIRECTIVE + "SET TERMINATOR "; + + public DB2ZParser(Configuration configuration, ParsingContext parsingContext) { + super(configuration, parsingContext, COMMENT_DIRECTIVE.length()); + } + + // WHILE and FOR both contain DO before the body of the block, so are both handled by the DO keyword + // See https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/sqlref/src/tpc/db2z_sqlplnativeintro.html + private static final List CONTROL_FLOW_KEYWORDS = Arrays.asList("LOOP", "CASE", "DO", "REPEAT", "IF"); + + private static final Pattern CREATE_IF_NOT_EXISTS = Pattern.compile( + ".*CREATE\\s([^\\s]+\\s){0,2}IF\\sNOT\\sEXISTS"); + private static final Pattern DROP_IF_EXISTS = Pattern.compile( + ".*DROP\\s([^\\s]+\\s){0,2}IF\\sEXISTS"); + private static final Pattern STORED_PROCEDURE_CALL = Pattern.compile( + "^CALL"); + private static final StatementType DB2Z_CALL_STATEMENT = new StatementType(); + // Do not assume first line is beginning of CALL statement. Maybe comment or whitelines first... + private static final Pattern DB2Z_CALL_WITH_PARMS_REGEX = Pattern.compile( + "CALL\\s+(?([^\\s]+\\.)?[^\\s]+)(\\(\\s*(?\\S.*)\\s*\\))", Pattern.CASE_INSENSITIVE); + + //Split on comma if that comma has zero, or an even number of quotes ahead + private static final Pattern PARMS_SPLIT_REGEX = Pattern.compile(",(?=(?:[^']*'[^']*')*[^']*$)"); + private static final Pattern STRING_PARM_REGEX = Pattern.compile("'.*'"); + private static final Pattern INTEGER_PARM_REGEX = Pattern.compile("-?\\d+"); + + @Override + protected StatementType detectStatementType(String simplifiedStatement, ParserContext context, PeekingReader reader) { + LOG.debug("detectStatementType: simplifiedStatement=" + simplifiedStatement); + if (STORED_PROCEDURE_CALL.matcher(simplifiedStatement).matches()) { + LOG.debug("detectStatementType: DB2Z CALL statement found" ); + return DB2Z_CALL_STATEMENT; + } + return super.detectStatementType(simplifiedStatement, context, reader); + } + + @Override + protected ParsedSqlStatement createStatement(PeekingReader reader, Recorder recorder, + int statementPos, int statementLine, int statementCol, + int nonCommentPartPos, int nonCommentPartLine, int nonCommentPartCol, + StatementType statementType, boolean canExecuteInTransaction, + Delimiter delimiter, String sql, boolean batchable) throws IOException { + LOG.debug(sql); + if (statementType == DB2Z_CALL_STATEMENT) { + Matcher callMatcher = DB2Z_CALL_WITH_PARMS_REGEX.matcher(sql); + if(callMatcher.find()) { + String procName = callMatcher.group("procname"); + String parmsString = callMatcher.group("args"); + String[] parmStrings = PARMS_SPLIT_REGEX.split(parmsString); + Object[] parms = new Object[parmStrings.length]; + for(int i = 0; i < parmStrings.length; i++) { + String prmTrimmed = parmStrings[i].trim(); + LOG.debug("createStatement: DB2Z CALL with parms: " + procName + " " + prmTrimmed ); + if (STRING_PARM_REGEX.matcher(prmTrimmed).matches()) { + //For string literals, remove the surrounding single quotes and + //de-escape any single quotes inside the string + parms[i] = prmTrimmed.substring(1, prmTrimmed.length() - 1).replace("''", "'"); + } else if (INTEGER_PARM_REGEX.matcher(prmTrimmed).matches()) { + parms[i] = Integer.valueOf(prmTrimmed); + } else if (prmTrimmed.toUpperCase().equals("NULL")) { + parms[i] = null; + } else { + parms[i] = prmTrimmed; + } + } + return new DB2ZCallProcedureParsedStatement(statementPos, statementLine, statementCol, + sql, delimiter, canExecuteInTransaction, batchable, procName, parms); + } + } + LOG.debug("createStatement: DB2Z CALL no parms " + statementType + " " + sql); + return super.createStatement(reader, recorder, statementPos, statementLine, statementCol, + nonCommentPartPos, nonCommentPartLine, nonCommentPartCol, + statementType, canExecuteInTransaction, delimiter, sql, batchable + ); + } + + @Override + protected void adjustBlockDepth(ParserContext context, List tokens, Token keyword, PeekingReader reader) throws IOException { + boolean previousTokenIsKeyword = !tokens.isEmpty() && tokens.get(tokens.size() - 1).getType() == TokenType.KEYWORD; + + int lastKeywordIndex = getLastKeywordIndex(tokens); + String previousKeyword = lastKeywordIndex >= 0 ? tokens.get(lastKeywordIndex).getText() : null; + + lastKeywordIndex = getLastKeywordIndex(tokens, lastKeywordIndex); + String previousPreviousToken = lastKeywordIndex >= 0 ? tokens.get(lastKeywordIndex).getText() : null; + + if ( + // BEGIN increases block depth, exception when used with ROW BEGIN + ("BEGIN".equals(keyword.getText()) && (!"ROW".equals(previousKeyword) || previousPreviousToken == null || "EACH".equals(previousPreviousToken))) + // Control flow keywords increase depth + || CONTROL_FLOW_KEYWORDS.contains(keyword.getText()) + ) { + // But not END IF and END WHILE + if (!previousTokenIsKeyword || !"END".equals(previousKeyword)) { + context.increaseBlockDepth(keyword.getText()); + + } + } else if ( + // END decreases block depth, exception when used with ROW END + ("END".equals(keyword.getText()) && !"ROW".equals(previousKeyword)) + || doTokensMatchPattern(tokens, keyword, CREATE_IF_NOT_EXISTS) + || doTokensMatchPattern(tokens, keyword, DROP_IF_EXISTS)) { + context.decreaseBlockDepth(); + } + } + + @Override + protected void resetDelimiter(ParserContext context) { + // Do not reset delimiter as delimiter changes survive beyond a single statement + } + + @Override + protected boolean isCommentDirective(String peek) { + return peek.startsWith(COMMENT_DIRECTIVE); + } + + @Override + protected Token handleCommentDirective(PeekingReader reader, ParserContext context, int pos, int line, int col) throws IOException { + if (SET_TERMINATOR_DIRECTIVE.equals(reader.peek(SET_TERMINATOR_DIRECTIVE.length()))) { + reader.swallow(SET_TERMINATOR_DIRECTIVE.length()); + String delimiter = reader.readUntilExcluding('\n', '\r'); + return new Token(TokenType.NEW_DELIMITER, pos, line, col, delimiter.trim(), delimiter, context.getParensDepth()); + } + reader.swallowUntilExcluding('\n', '\r'); + return new Token(TokenType.COMMENT, pos, line, col, null, null, context.getParensDepth()); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZSchema.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZSchema.java new file mode 100644 index 0000000..37a3400 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZSchema.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import lombok.CustomLog; +import org.flywaydb.core.internal.database.base.Function; +import org.flywaydb.core.internal.database.base.Schema; +import org.flywaydb.core.internal.database.base.Table; +import org.flywaydb.core.internal.database.base.Type; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; + +/** + * DB2 implementation of Schema. + */ +@CustomLog +public class DB2ZSchema extends Schema { + /** + * Creates a new DB2 schema. + * + * @param jdbcTemplate The Jdbc Template for communicating with the DB. + * @param database The database-specific support. + * @param name The name of the schema. + */ + DB2ZSchema(JdbcTemplate jdbcTemplate, DB2ZDatabase database, String name) { + super(jdbcTemplate, database, name); + } + + @Override + protected boolean doExists() throws SQLException { + /** + * For DB2 on z/OS, a schema is not an object that can be created or dropped and is not listed in the catalog. + * Instead, we do need to check whether the database exists (which is a container for tablespaces and other storage related objects) + */ + return jdbcTemplate.queryForInt("SELECT COUNT(*) FROM sysibm.sysdatabase WHERE name=?", database.getName()) > 0; + } + + @Override + protected boolean doEmpty() throws SQLException { + int objectCount = jdbcTemplate.queryForInt("select count(*) from sysibm.systables where dbname = ? AND creator = ?", database.getName(), name); + objectCount += jdbcTemplate.queryForInt("select count(*) from sysibm.syssequences where schema = ?", name); + objectCount += jdbcTemplate.queryForInt("select count(*) from sysibm.sysindexes where creator = ?", name); + objectCount += jdbcTemplate.queryForInt("select count(*) from sysibm.sysroutines where schema = ?", name); + objectCount += jdbcTemplate.queryForInt("select count(*) from sysibm.systriggers where schema = ?", name); + return objectCount == 0; + } + + @Override + protected void doCreate() throws SQLException { + throw new UnsupportedOperationException("Create Schema - is not supported in db2 on zOS"); + } + + @Override + protected void doDrop() throws SQLException { + throw new UnsupportedOperationException("Drop Schema - is not supported in db2 on zOS"); + } + + @Override + protected void doClean() throws SQLException { + // MQTs are dropped when the backing views or tables are dropped + // Indexes in DB2 are dropped when the corresponding table is dropped + + // drop versioned table link -> not supported for DB2 9.x + List dropVersioningStatements = generateDropVersioningStatement(); + if (!dropVersioningStatements.isEmpty()) { + // Do a explicit drop of MQTs in order to be able to drop the Versioning + for (String dropTableStatement : generateDropStatements("M", "TABLE")) { + jdbcTemplate.execute(dropTableStatement); + } + } + + for (String dropVersioningStatement : dropVersioningStatements) { + jdbcTemplate.execute(dropVersioningStatement); + } + + // diable archiving on table + List disableArchivingStatements = generateDisableArchivingStatement(); + if (!disableArchivingStatements.isEmpty()) { + // Do a explicit drop of MQTs in order to be able to drop the Versioning + for (String dropTableStatement : generateDropStatements("M", "TABLE")) { + jdbcTemplate.execute(dropTableStatement); + } + } + + for (String disableArchivingStatement : disableArchivingStatements) { + jdbcTemplate.execute(disableArchivingStatement); + } + + // views + /* We need to query for all views in schema after each DROP because of a + * specific property in z/OS to DROP dependent nested views (views depending on other views). + */ + List dropStatements = generateDropStatements("V", "VIEW"); + while(dropStatements.size() != 0) { + String dropStatement = dropStatements.get(0); + jdbcTemplate.execute(dropStatement); + dropStatements = generateDropStatements("V", "VIEW"); + } + + // aliases + for (String dropStatement : generateDropStatements("A", "ALIAS")) { + jdbcTemplate.execute(dropStatement); + } + + for (Table table : allTables()) { + table.drop(); + } + + // temporary Tables + for (String dropStatement : generateDropStatements("G", "TABLE")) { + jdbcTemplate.execute(dropStatement); + } + + // explicit tablespace + for (String dropStatement : generateDropStatementsForRegularTablespace()) { + jdbcTemplate.execute(dropStatement); + } + + for (String dropStatement : generateDropStatementsForLobTablespace()) { + jdbcTemplate.execute(dropStatement); + } + + // sequences + for (String dropStatement : generateDropStatementsForSequences()) { + jdbcTemplate.execute(dropStatement); + } + + // procedures + for (String dropStatement : generateDropStatementsForProcedures()) { + jdbcTemplate.execute(dropStatement); + } + + // triggers + for (String dropStatement : generateDropStatementsForTriggers()) { + jdbcTemplate.execute(dropStatement); + } + + for (Function function : allFunctions()) { + function.drop(); + } + + // types. TODO: find out, why generic drop type function is not working at all times with Db2Z + // For now, call the one that is working for sure + for (String dropStatement : generateDropStatementsForTypes()) { + jdbcTemplate.execute(dropStatement); + } + + for (Type type : allTypes()) { + type.drop(); + } + } + + private String getSqlId() { + /** + * Get SQLID. + * When sqlid not set, implicitly use schema name for sqlid + */ + String sqlId = (database.getSqlId() == "") ?name : database.getSqlId(); + return sqlId; + } + + /** + * Generates DROP statements for the procedures in this schema. + * + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatementsForProcedures() throws SQLException { + String dropProcGenQuery = "select rtrim(NAME) from SYSIBM.SYSROUTINES where CAST_FUNCTION = 'N' " + + " and ROUTINETYPE = 'P' and SCHEMA = '" + name + "' and OWNER = '" + this.getSqlId() + "'"; + return buildDropStatements("DROP PROCEDURE", dropProcGenQuery); + } + + /** + * Generates DROP statements for the sequences in this schema. + * + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatementsForSequences() throws SQLException { + String dropSeqGenQuery = "select rtrim(NAME) from SYSIBM.SYSSEQUENCES where SCHEMA = '" + name + + "' and SEQTYPE='S' and OWNER = '" + this.getSqlId() + "'"; + return buildDropStatements("DROP SEQUENCE", dropSeqGenQuery); + } + + /** + * Generates DROP statements for the explicitly-created tablespaces in this database with the schema user as creator. + * + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatementsForRegularTablespace() throws SQLException { + //Only drop explicitly created tablespaces for current database and created under this specific schema authorization ID + //Note that this also drops the related table for partitioned tablespaces. + String dropTablespaceGenQuery = "select rtrim(NAME) FROM SYSIBM.SYSTABLESPACE where IMPLICIT = 'N' AND DBNAME = '" + database.getName() + "' AND CREATOR = '" + this.getSqlId() + "' AND TYPE <> 'O'"; + + List dropStatements = new ArrayList<>(); + List dbObjects = jdbcTemplate.queryForStringList(dropTablespaceGenQuery); + for (String dbObject : dbObjects) { + LOG.debug("DROP TABLESPACE " + database.quote(database.getName(), dbObject)); + dropStatements.add("DROP TABLESPACE " + database.quote(database.getName(), dbObject)); + } + return dropStatements; + } + + private List generateDropStatementsForLobTablespace() throws SQLException { + String dropTablespaceGenQuery = "select rtrim(NAME) FROM SYSIBM.SYSTABLESPACE where IMPLICIT = 'N' AND DBNAME = '" + database.getName() + "' AND CREATOR = '" + this.getSqlId() + "' AND TYPE = 'O'"; + + List dropStatements = new ArrayList<>(); + List dbObjects = jdbcTemplate.queryForStringList(dropTablespaceGenQuery); + for (String dbObject : dbObjects) { + LOG.debug("DROP TABLESPACE " + database.quote(database.getName(), dbObject)); + dropStatements.add("DROP TABLESPACE " + database.quote(database.getName(), dbObject)); + } + return dropStatements; + } + + /** + * Generates DROP statements for this type of table, representing this type of object in this schema. + * + * @param tableType The type of table (Can be T, V, S, ...). + * @param objectType The type of object. + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatements(String tableType, String objectType) throws SQLException { + String dropTablesGenQuery = "select rtrim(NAME) from SYSIBM.SYSTABLES where TYPE='" + tableType + "' and OWNER = '" + this.getSqlId() + "' AND CREATOR = '" + name + "'"; + return buildDropStatements("DROP " + objectType, dropTablesGenQuery); + } + + /** + * Generates DROP statements for the triggers in this schema. + * + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatementsForTriggers() throws SQLException { + String dropTrigGenQuery = "select TRIGNAME from SYSIBM.SYSTRIGGERS where SCHEMA = '" + name + "' and OWNER = '" + this.getSqlId() + "'"; + LOG.debug(dropTrigGenQuery); + return buildDropStatements("DROP TRIGGER", dropTrigGenQuery); + } + + /** + * Generates DROP statements for the types in this schema. + * + * @return The drop statements. + * @throws SQLException when the statements could not be generated. + */ + private List generateDropStatementsForTypes() throws SQLException { + String dropProcGenQuery = "select rtrim(NAME) from SYSIBM.SYSROUTINES where CAST_FUNCTION = 'Y' " + + " and ROUTINETYPE = 'T' and SCHEMA = '" + name + "' and OWNER = '" + this.getSqlId() + "'"; + return buildDropStatements("DROP PROCEDURE", dropProcGenQuery); + } + + /** + * Builds the drop statements for database objects in this schema. + * + * @param dropPrefix The drop command for the database object (e.g. 'drop table'). + * @param query The query to get all present database objects + * @return The statements. + * @throws SQLException when the drop statements could not be built. + */ + private List buildDropStatements(final String dropPrefix, final String query) throws SQLException { + List dropStatements = new ArrayList<>(); + List dbObjects = jdbcTemplate.queryForStringList(query); + for (String dbObject : dbObjects) { + LOG.debug(dropPrefix + " " + database.quote(name, dbObject)); + dropStatements.add(dropPrefix + " " + database.quote(name, dbObject)); + } + return dropStatements; + } + + /** + * @return All tables that have versioning associated with them. + */ + private List generateDropVersioningStatement() throws SQLException { + List dropVersioningStatements = new ArrayList<>(); + Table[] versioningTables = findTables("select rtrim(NAME) from SYSIBM.SYSTABLES where VERSIONING_TABLE <> '' and CREATOR = '" + name + "' and OWNER = '" + this.getSqlId() + "'"); + for (Table table : versioningTables) { + LOG.debug("ALTER TABLE " + table.toString() + " DROP VERSIONING"); + dropVersioningStatements.add("ALTER TABLE " + table.toString() + " DROP VERSIONING"); + } + + return dropVersioningStatements; + } + + /** + * @return All tables that have archiving associated with them. + */ + private List generateDisableArchivingStatement() throws SQLException { + List dropArchivingStatements = new ArrayList<>(); + Table[] archivingTables = findTables("select rtrim(NAME) from SYSIBM.SYSTABLES where ARCHIVING_TABLE <> '' and CREATOR = '" + name + "' and OWNER = '" + this.getSqlId() + "'"); + for (Table table : archivingTables) { + LOG.debug("ALTER TABLE " + table.toString() + " DISABLE ARCHIVE"); + dropArchivingStatements.add("ALTER TABLE " + table.toString() + " DISABLE ARCHIVE"); + } + return dropArchivingStatements; + } + + private DB2ZTable[] findTables(String sqlQuery, String... params) throws SQLException { + List tableNames = jdbcTemplate.queryForStringList(sqlQuery, params); + DB2ZTable[] tables = new DB2ZTable[tableNames.size()]; + for (int i = 0; i < tableNames.size(); i++) { + tables[i] = new DB2ZTable(jdbcTemplate, database, this, tableNames.get(i)); + } + return tables; + } + + @Override + protected DB2ZTable[] doAllTables() throws SQLException { + return findTables("select rtrim(NAME) from SYSIBM.SYSTABLES where TYPE='T' and CREATOR = '" + name + "' and OWNER = '" + this.getSqlId() + "'"); + } + + @Override + protected Function[] doAllFunctions() throws SQLException { + List functionNames = jdbcTemplate.queryForStringList( + "select rtrim(SPECIFICNAME) from SYSIBM.SYSROUTINES where" + // Functions only + + " ROUTINETYPE='F'" + // That aren't system-generated or built-in + + " AND ORIGIN IN (" + + "'E', " // User-defined, external + + "'M', " // Template function + + "'Q', " // SQL-bodied + + "'U')" // User-defined, based on a source + + " and SCHEMA = '" + name + "' and OWNER = '" + this.getSqlId() + "'"); + + List functions = new ArrayList<>(); + for (String functionName : functionNames) { + functions.add(getFunction(functionName)); + } + + return functions.toArray(new Function[0]); + } + + @Override + public Table getTable(String tableName) { + return new DB2ZTable(jdbcTemplate, database, this, tableName); + } + + @Override + protected Type getType(String typeName) { + return new DB2ZType(jdbcTemplate, database, this, typeName); + } + + @Override + public Function getFunction(String functionName, String... args) { + return new DB2ZFunction(jdbcTemplate, database, this, functionName, args); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZTable.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZTable.java new file mode 100644 index 0000000..db36c28 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZTable.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import lombok.CustomLog; +import org.flywaydb.core.api.logging.Log; +import org.flywaydb.core.api.logging.LogFactory; +import org.flywaydb.core.internal.database.base.Table; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; + +/** + * Db2-specific table. + */ +@CustomLog +public class DB2ZTable extends Table { + + /** + * Creates a new Db2 table. + * + * @param jdbcTemplate The Jdbc Template for communicating with the DB. + * @param database The database-specific support. + * @param schema The schema this table lives in. + * @param name The name of the table. + */ + DB2ZTable(JdbcTemplate jdbcTemplate, DB2ZDatabase database, DB2ZSchema schema, String name) { + super(jdbcTemplate, database, schema, name); + } + + @Override + protected void doDrop() throws SQLException { + + String dbName = jdbcTemplate.queryForString("SELECT DBNAME FROM SYSIBM.SYSTABLES WHERE NAME=? AND CREATOR=?", this.getName(), this.getSchema().getName()); + String tableSpaceName = jdbcTemplate.queryForString("SELECT TSNAME FROM SYSIBM.SYSTABLES WHERE NAME=? AND CREATOR=?", this.getName(), this.getSchema().getName()); + //Use sqlid as creator with tablespace. When sqlid is not set, implicitly use schema name for sqlid + String sqlId = (database.getSqlId() == "") ? this.getSchema().getName() : database.getSqlId(); + String implicit = jdbcTemplate.queryForString("SELECT IMPLICIT FROM SYSIBM.SYSTABLESPACE WHERE DBNAME=? AND CREATOR=? AND NAME=?", dbName, sqlId, tableSpaceName); + String tableSpaceType = jdbcTemplate.queryForString("SELECT TYPE FROM SYSIBM.SYSTABLESPACE WHERE DBNAME=? AND CREATOR=? AND NAME=?", dbName, sqlId, tableSpaceName); + + if (implicit == null || implicit.isEmpty()) { + LOG.debug("Nothing to drop because table " + this.getName() + " does exist on tablespace " + tableSpaceName + " but with creator other than " + sqlId); + } else { + if (implicit.equals("N") && (tableSpaceType.equals("G") || tableSpaceType.equals("R"))) { + //Tablespace will be dropped by DB2ZSchema.doClean() + LOG.debug("Table '" + this + "' cannot be dropped directly (tableSpaceName=" + tableSpaceName + ", implicit=" + implicit + ", tableSpaceType=" + tableSpaceType + ")"); + } else { + LOG.debug("Dropping table " + this + " ..."); + jdbcTemplate.execute("DROP TABLE " + this); + } + } + } + + @Override + protected boolean doExists() throws SQLException { + return exists(null, schema, name); + } + + @Override + protected void doLock() throws SQLException { + jdbcTemplate.update("lock table " + this + " in exclusive mode"); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZType.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZType.java new file mode 100644 index 0000000..e7c0610 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/DB2ZType.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flywaydb.community.database.db2z; + +import java.sql.SQLException; +import org.flywaydb.core.internal.database.base.Type; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; + +/** + * Db2-specific type. + */ +public class DB2ZType extends Type { + /** + * Creates a new Db2 type. + * + * @param jdbcTemplate The Jdbc Template for communicating with the DB. + * @param database The database-specific support. + * @param schema The schema this type lives in. + * @param name The name of the type. + */ + DB2ZType(JdbcTemplate jdbcTemplate, DB2ZDatabase database, DB2ZSchema schema, String name) { + super(jdbcTemplate, database, schema, name); + } + + @Override + protected void doDrop() throws SQLException { + jdbcTemplate.execute("DROP TYPE " + database.quote(schema.getName(), name)); + } +} diff --git a/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/package-info.java b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/package-info.java new file mode 100644 index 0000000..43b9a17 --- /dev/null +++ b/flyway-database-db2zOS/src/main/java/org/flywaydb/community/database/db2z/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) Red Gate Software Ltd 2010-2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Private API. No compatibility guarantees provided. + */ +package org.flywaydb.community.database.db2z; diff --git a/flyway-database-db2zOS/src/main/resources/META-INF/services/org.flywaydb.core.extensibility.Plugin b/flyway-database-db2zOS/src/main/resources/META-INF/services/org.flywaydb.core.extensibility.Plugin new file mode 100644 index 0000000..3dd3ba3 --- /dev/null +++ b/flyway-database-db2zOS/src/main/resources/META-INF/services/org.flywaydb.core.extensibility.Plugin @@ -0,0 +1,2 @@ +org.flywaydb.community.database.db2z.DB2ZDatabaseType +org.flywaydb.community.database.db2z.DB2ZConfigurationExtension diff --git a/flyway-database-db2zOS/src/main/resources/org/flywaydb/community/database/db2z/version.txt b/flyway-database-db2zOS/src/main/resources/org/flywaydb/community/database/db2z/version.txt new file mode 100644 index 0000000..1785151 --- /dev/null +++ b/flyway-database-db2zOS/src/main/resources/org/flywaydb/community/database/db2z/version.txt @@ -0,0 +1 @@ +${pom.version} \ No newline at end of file diff --git a/flyway-database-ignite/pom.xml b/flyway-database-ignite/pom.xml index e1788d3..9a1d56f 100644 --- a/flyway-database-ignite/pom.xml +++ b/flyway-database-ignite/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-ignite diff --git a/flyway-database-oceanbase/pom.xml b/flyway-database-oceanbase/pom.xml index 887909c..0ce0bb7 100644 --- a/flyway-database-oceanbase/pom.xml +++ b/flyway-database-oceanbase/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-oceanbase diff --git a/flyway-database-tidb/pom.xml b/flyway-database-tidb/pom.xml index 8b4db54..c669189 100644 --- a/flyway-database-tidb/pom.xml +++ b/flyway-database-tidb/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-tidb diff --git a/flyway-database-yugabytedb/pom.xml b/flyway-database-yugabytedb/pom.xml index 8a907d6..7f1a35f 100644 --- a/flyway-database-yugabytedb/pom.xml +++ b/flyway-database-yugabytedb/pom.xml @@ -23,7 +23,7 @@ org.flywaydb flyway-community-db-support - 10.13.0 + 10.14.0 flyway-database-yugabytedb diff --git a/pom.xml b/pom.xml index 5bdc3db..3f3bb82 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ flyway-community-db-support pom - 10.13.0 + 10.14.0 ${project.artifactId} @@ -39,10 +39,11 @@ flyway-database-oceanbase flyway-database-databricks flyway-community-db-support-archetype + flyway-database-db2zOS - 10.13.0 + 10.14.0