diff --git a/flyway-database-oceanbase/pom.xml b/flyway-database-oceanbase/pom.xml
index e85d451..8bb0096 100644
--- a/flyway-database-oceanbase/pom.xml
+++ b/flyway-database-oceanbase/pom.xml
@@ -45,6 +45,10 @@
+
+ org.projectlombok
+ lombok
+
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseConnection.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseConnection.java
deleted file mode 100644
index 7d09cd1..0000000
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseConnection.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) Red Gate Software Ltd 2010-2024
- *
- * 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.oceanbase;
-
-import org.flywaydb.database.mysql.MySQLConnection;
-import org.flywaydb.database.mysql.MySQLDatabase;
-
-import java.sql.Connection;
-
-public class OceanBaseConnection extends MySQLConnection {
-
- public OceanBaseConnection(MySQLDatabase database, Connection connection) {
- super(database, connection);
- }
-
- @Override
- protected boolean canUseNamedLockTemplate() {
- return false;
- }
-}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/OceanBaseDatabaseExtension.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseExtension.java
similarity index 60%
rename from flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/OceanBaseDatabaseExtension.java
rename to flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseExtension.java
index 83f87f8..c7db359 100644
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/OceanBaseDatabaseExtension.java
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseExtension.java
@@ -1,3 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
/*
* Copyright (C) Red Gate Software Ltd 2010-2024
*
@@ -13,15 +32,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.flywaydb.community.database;
+package org.flywaydb.community.database.oceanbase;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.extensibility.PluginMetadata;
import org.flywaydb.core.internal.util.FileUtils;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
public class OceanBaseDatabaseExtension implements PluginMetadata {
public String getDescription() {
return "Community-contributed OceanBase database support extension " + readVersion() + " by Redgate";
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseType.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseType.java
index 53a6642..8407e3d 100644
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseType.java
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabaseType.java
@@ -1,3 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
/*
* Copyright (C) Red Gate Software Ltd 2010-2024
*
@@ -15,9 +34,17 @@
*/
package org.flywaydb.community.database.oceanbase;
-import org.flywaydb.community.database.OceanBaseDatabaseExtension;
+import org.flywaydb.community.database.oceanbase.mysql.OceanBaseMysqlModeDatabase;
+import org.flywaydb.community.database.oceanbase.oracle.OceanBaseOracleModeDatabase;
+import org.flywaydb.community.database.oceanbase.oracle.OceanBaseOracleModeParser;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Types;
+
import org.flywaydb.core.api.ResourceProvider;
import org.flywaydb.core.api.configuration.Configuration;
+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;
@@ -25,17 +52,13 @@
import org.flywaydb.core.internal.parser.Parser;
import org.flywaydb.core.internal.parser.ParsingContext;
import org.flywaydb.core.internal.util.ClassUtils;
-import org.flywaydb.database.mysql.MySQLDatabaseType;
import org.flywaydb.database.mysql.MySQLParser;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Types;
-
-public class OceanBaseDatabaseType extends MySQLDatabaseType implements CommunityDatabaseType {
+public class OceanBaseDatabaseType extends BaseDatabaseType implements CommunityDatabaseType {
private static final String OB_JDBC_DRIVER = "com.oceanbase.jdbc.Driver";
private static final String OB_LEGACY_JDBC_DRIVER = "com.alipay.oceanbase.jdbc.Driver";
+ private static JdbcConnectionFactory jdbcConnectionFactory;
public String getName() {
return "OceanBase";
@@ -59,24 +82,19 @@ public boolean handlesJDBCUrl(String url) {
@Override
public String getDriverClass(String url, ClassLoader classLoader) {
- return url.startsWith("jdbc:oceanbase:")?
- OB_JDBC_DRIVER :
- super.getDriverClass(url, classLoader);
+ return url.startsWith("jdbc:oceanbase:") ? OB_JDBC_DRIVER : OceanBaseJdbcUtils.getDriverClass(url, classLoader);
}
@Override
public String getBackupDriverClass(String url, ClassLoader classLoader) {
- return (url.startsWith("jdbc:oceanbase:") && ClassUtils.isPresent(OB_LEGACY_JDBC_DRIVER, classLoader)?
- OB_LEGACY_JDBC_DRIVER :
- super.getBackupDriverClass(url, classLoader));
+ return (url.startsWith("jdbc:oceanbase:") && ClassUtils.isPresent(OB_LEGACY_JDBC_DRIVER, classLoader) ? OB_LEGACY_JDBC_DRIVER : OceanBaseJdbcUtils.getBackupDriverClass(url, classLoader));
}
@Override
public boolean handlesDatabaseProductNameAndVersion(String databaseProductName, String databaseProductVersion, Connection connection) {
- if (!databaseProductName.contains("MySQL") && !databaseProductName.contains("OceanBase")) {
+ if (!databaseProductName.contains("MySQL") && !databaseProductName.contains("Oracle") && !databaseProductName.contains("OceanBase")) {
return false;
}
-
String versionComment;
try {
versionComment = OceanBaseJdbcUtils.getVersionComment(connection);
@@ -88,7 +106,31 @@ public boolean handlesDatabaseProductNameAndVersion(String databaseProductName,
@Override
public Database createDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) {
- return new OceanBaseDatabase(configuration, jdbcConnectionFactory, statementInterceptor);
+ OceanBaseDatabaseType.jdbcConnectionFactory = jdbcConnectionFactory;
+ String compatibleMode = getCompatibleMode(jdbcConnectionFactory);
+ if (isMySQLMode(compatibleMode)) {
+ return new OceanBaseMysqlModeDatabase(configuration, jdbcConnectionFactory, statementInterceptor);
+ } else {
+ return new OceanBaseOracleModeDatabase(configuration, jdbcConnectionFactory, statementInterceptor);
+ }
+ }
+
+ @Override
+ public Parser createParser(Configuration configuration, ResourceProvider resourceProvider, ParsingContext parsingContext) {
+ String compatibleMode = getCompatibleMode(jdbcConnectionFactory);
+ if (isMySQLMode(compatibleMode)) {
+ return new MySQLParser(configuration, parsingContext);
+ } else {
+ return new OceanBaseOracleModeParser(configuration, parsingContext);
+ }
+ }
+
+ private String getCompatibleMode(JdbcConnectionFactory jdbcConnectionFactory) {
+ return OceanBaseJdbcUtils.getCompatibleMode(jdbcConnectionFactory.openConnection());
+ }
+
+ private boolean isMySQLMode(String compatibleMode) {
+ return "MySQL".equalsIgnoreCase(compatibleMode);
}
@Override
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseJdbcUtils.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseJdbcUtils.java
index 85f0752..9cb8c93 100644
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseJdbcUtils.java
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseJdbcUtils.java
@@ -1,3 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
/*
* Copyright (C) Red Gate Software Ltd 2010-2024
*
@@ -15,13 +34,14 @@
*/
package org.flywaydb.community.database.oceanbase;
-import org.flywaydb.core.internal.util.StringUtils;
-
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import org.flywaydb.core.internal.util.ClassUtils;
+import org.flywaydb.core.internal.util.StringUtils;
+
public class OceanBaseJdbcUtils {
public static String getVersionComment(Connection connection) throws SQLException {
@@ -50,4 +70,31 @@ private static String queryVariable(Connection connection, String variable) thro
}
return null;
}
+
+ public static String getCompatibleMode(Connection connection) {
+ String mode;
+ try (Connection conn = connection; Statement statement = conn.createStatement()) {
+ ResultSet rs = statement.executeQuery("SHOW VARIABLES LIKE 'ob_compatibility_mode'");
+ mode = rs.next() ? rs.getString("VALUE") : null;
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to get oceanbase compatible mode", e);
+ }
+ return mode;
+ }
+
+ public static String getDriverClass(String url, ClassLoader classLoader) {
+ if (!url.startsWith("jdbc:p6spy:mysql:") && !url.startsWith("jdbc:p6spy:google:")) {
+ return url.startsWith("jdbc:mysql:") ? "com.mysql.cj.jdbc.Driver" : "com.mysql.jdbc.GoogleDriver";
+ } else {
+ return "com.p6spy.engine.spy.P6SpyDriver";
+ }
+ }
+
+ public static String getBackupDriverClass(String url, ClassLoader classLoader) {
+ if (ClassUtils.isPresent("com.mysql.jdbc.Driver", classLoader)) {
+ return "com.mysql.jdbc.Driver";
+ } else {
+ return ClassUtils.isPresent("org.mariadb.jdbc.Driver", classLoader) && !url.contains("disableMariaDbDriver") ? "org.mariadb.jdbc.Driver" : null;
+ }
+ }
}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeConnection.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeConnection.java
new file mode 100644
index 0000000..8f9b8f7
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeConnection.java
@@ -0,0 +1,52 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+/*
+ * Copyright (C) Red Gate Software Ltd 2010-2024
+ *
+ * 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.oceanbase.mysql;
+
+import java.sql.Connection;
+
+import org.flywaydb.database.mysql.MySQLConnection;
+import org.flywaydb.database.mysql.MySQLDatabase;
+
+public class OceanBaseMysqlModeConnection extends MySQLConnection {
+
+ public OceanBaseMysqlModeConnection(MySQLDatabase database, Connection connection) {
+ super(database, connection);
+ }
+
+ @Override
+ protected boolean canUseNamedLockTemplate() {
+ return false;
+ }
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabase.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeDatabase.java
similarity index 58%
rename from flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabase.java
rename to flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeDatabase.java
index c5f70e1..40d91c1 100644
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/OceanBaseDatabase.java
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/OceanBaseMysqlModeDatabase.java
@@ -1,3 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
/*
* Copyright (C) Red Gate Software Ltd 2010-2024
*
@@ -13,7 +32,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.flywaydb.community.database.oceanbase;
+package org.flywaydb.community.database.oceanbase.mysql;
+
+import org.flywaydb.community.database.oceanbase.OceanBaseJdbcUtils;
+
+import java.sql.Connection;
+import java.sql.SQLException;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationVersion;
@@ -23,18 +47,15 @@
import org.flywaydb.database.mysql.MySQLConnection;
import org.flywaydb.database.mysql.MySQLDatabase;
-import java.sql.Connection;
-import java.sql.SQLException;
-
-public class OceanBaseDatabase extends MySQLDatabase {
+public class OceanBaseMysqlModeDatabase extends MySQLDatabase {
- public OceanBaseDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) {
+ public OceanBaseMysqlModeDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) {
super(configuration, jdbcConnectionFactory, statementInterceptor);
}
@Override
protected MySQLConnection doGetConnection(Connection connection) {
- return new OceanBaseConnection(this, connection);
+ return new OceanBaseMysqlModeConnection(this, connection);
}
@Override
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/package-info.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/package-info.java
new file mode 100644
index 0000000..5174e31
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/mysql/package-info.java
@@ -0,0 +1,38 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+/*
+ * Copyright (C) Red Gate Software Ltd 2010-2024
+ *
+ * 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.
+ */
+/**
+ * Community-supported package. No compatibility guarantees provided.
+ */
+package org.flywaydb.community.database.oceanbase.mysql;
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeConnection.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeConnection.java
new file mode 100644
index 0000000..88b0828
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeConnection.java
@@ -0,0 +1,48 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oracle
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
+
+import java.sql.SQLException;
+
+import org.flywaydb.core.internal.database.base.Connection;
+import org.flywaydb.core.internal.database.base.Schema;
+
+public class OceanBaseOracleModeConnection extends Connection {
+
+ OceanBaseOracleModeConnection(OceanBaseOracleModeDatabase database, java.sql.Connection connection) {
+ super(database, connection);
+ }
+
+ @Override
+ protected String getCurrentSchemaNameOrSearchPath() throws SQLException {
+ return jdbcTemplate.queryForString("SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') FROM DUAL");
+ }
+
+ @Override
+ public void doChangeCurrentSchemaOrSearchPathTo(String schema) throws SQLException {
+ jdbcTemplate.execute("ALTER SESSION SET CURRENT_SCHEMA=" + database.quote(schema));
+ }
+
+ @Override
+ public Schema getSchema(String name) {
+ return new OceanBaseOracleModeSchema(jdbcTemplate, database, name);
+ }
+
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeDatabase.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeDatabase.java
new file mode 100644
index 0000000..83395b8
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeDatabase.java
@@ -0,0 +1,180 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oracle
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
+
+import org.flywaydb.community.database.oceanbase.OceanBaseJdbcUtils;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.api.configuration.Configuration;
+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;
+
+public class OceanBaseOracleModeDatabase extends Database {
+
+ public OceanBaseOracleModeDatabase(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory, StatementInterceptor statementInterceptor) {
+ super(configuration, jdbcConnectionFactory, statementInterceptor);
+ }
+
+ @Override
+ protected OceanBaseOracleModeConnection doGetConnection(Connection connection) {
+ return new OceanBaseOracleModeConnection(this, connection);
+ }
+
+ @Override
+ public void ensureSupported(Configuration configuration) {
+ ensureDatabaseIsRecentEnough("1.4");
+ recommendFlywayUpgradeIfNecessary("5.0");
+ }
+
+ @Override
+ public String getRawCreateScript(Table table, boolean baseline) {
+ String tablespace = configuration.getTablespace() == null ? "" : " TABLESPACE \"" + configuration.getTablespace() + "\"";
+
+ return "CREATE TABLE " + table + " (\n" + " \"installed_rank\" INT NOT NULL,\n" + " \"version\" VARCHAR2(50),\n" + " \"description\" VARCHAR2(200) NOT NULL,\n" + " \"type\" VARCHAR2(20) NOT NULL,\n" + " \"script\" VARCHAR2(1000) NOT NULL,\n" + " \"checksum\" INT,\n" + " \"installed_by\" VARCHAR2(100) NOT NULL,\n" + " \"installed_on\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,\n" + " \"execution_time\" INT NOT NULL,\n" + " \"success\" NUMBER(1) NOT NULL,\n" + " CONSTRAINT \"" + table.getName() + "_pk\" PRIMARY KEY (\"installed_rank\")\n" + ")" + tablespace + ";\n" + (baseline ? getBaselineStatement(table) + ";\n" : "") + "CREATE INDEX \"" + table.getSchema().getName() + "\".\"" + table.getName() + "_s_idx\" ON " + table + " (\"success\");\n";
+ }
+
+ @Override
+ public boolean supportsEmptyMigrationDescription() {
+ // OceanBase Oracle mode will convert the empty string to NULL implicitly, and throw an exception as the column is NOT NULL
+ return false;
+ }
+
+ @Override
+ protected String doGetCurrentUser() throws SQLException {
+ return getMainConnection().getJdbcTemplate().queryForString("SELECT USER FROM DUAL");
+ }
+
+ @Override
+ public boolean supportsDdlTransactions() {
+ return false;
+ }
+
+ @Override
+ public String getBooleanTrue() {
+ return "1";
+ }
+
+ @Override
+ public String getBooleanFalse() {
+ return "0";
+ }
+
+ @Override
+ public boolean catalogIsSchema() {
+ return false;
+ }
+
+ @Override
+ protected MigrationVersion determineVersion() {
+ String versionNumber;
+ try {
+ versionNumber = OceanBaseJdbcUtils.getVersionNumber(rawMainJdbcConnection);
+ } catch (SQLException e) {
+ throw new FlywayException("Failed to get version number", e);
+ }
+ return MigrationVersion.fromVersion(versionNumber);
+ }
+
+ /**
+ * Checks whether the specified query returns rows or not. Wraps the query in EXISTS() SQL function and executes it.
+ * This is more preferable to opening a cursor for the original query, because a query inside EXISTS() is implicitly
+ * optimized to return the first row and because the client never fetches more than 1 row despite the fetch size
+ * value.
+ *
+ * @param query The query to check.
+ * @param params The query parameters.
+ * @return {@code true} if the query returns rows, {@code false} if not.
+ * @throws SQLException when the query execution failed.
+ */
+ boolean queryReturnsRows(String query, String... params) throws SQLException {
+ return getMainConnection().getJdbcTemplate().queryForBoolean("SELECT CASE WHEN EXISTS(" + query + ") THEN 1 ELSE 0 END FROM DUAL", params);
+ }
+
+ /**
+ * Checks whether the specified data dictionary view in the specified system schema is accessible (directly or
+ * through a role) or not.
+ *
+ * @param owner the schema name, unquoted case-sensitive.
+ * @param name the data dictionary view name to check, unquoted case-sensitive.
+ * @return {@code true} if it is accessible, {@code false} if not.
+ * @throws SQLException if the check failed.
+ */
+ private boolean isDataDictViewAccessible(String owner, String name) throws SQLException {
+ return queryReturnsRows("SELECT * FROM ALL_TAB_PRIVS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?" + " AND PRIVILEGE = 'SELECT'", owner, name);
+ }
+
+ /**
+ * Checks whether the specified SYS view is accessible (directly or through a role) or not.
+ *
+ * @param name the data dictionary view name to check, unquoted case-sensitive.
+ * @return {@code true} if it is accessible, {@code false} if not.
+ * @throws SQLException if the check failed.
+ */
+ boolean isDataDictViewAccessible(String name) throws SQLException {
+ return isDataDictViewAccessible("SYS", name);
+ }
+
+ /**
+ * Returns the specified data dictionary view name prefixed with DBA_ or ALL_ depending on its accessibility.
+ *
+ * @param baseName the data dictionary view base name, unquoted case-sensitive, e.g. OBJECTS, TABLES.
+ * @return the full name of the view with the proper prefix.
+ * @throws SQLException if the check failed.
+ */
+ String dbaOrAll(String baseName) throws SQLException {
+ return isDataDictViewAccessible("DBA_" + baseName) ? "DBA_" + baseName : "ALL_" + baseName;
+ }
+
+
+ /**
+ * Checks whether XDB component is available or not.
+ *
+ * @return {@code true} if it is available, {@code false} if not.
+ * @throws SQLException when checking availability of the component failed.
+ */
+ boolean isXmlDbAvailable() throws SQLException {
+ return isDataDictViewAccessible("ALL_XML_TABLES");
+ }
+
+ /**
+ * Returns the list of schemas that were created and are maintained by oceanbase-supplied scripts and must not be
+ * changed in any other way.
+ *
+ * @return the set of system schema names
+ */
+ Set getSystemSchemas() throws SQLException {
+
+ // The list of known default system schemas
+ Set result = new HashSet<>(Arrays.asList("SYS", "LBACSYS", "ORAAUDITOR"));
+
+ result.addAll(getMainConnection().getJdbcTemplate().queryForStringList("SELECT USERNAME FROM ALL_USERS WHERE REGEXP_LIKE(USERNAME, '^(APEX|FLOWS)_\\d+$') OR ORACLE_MAINTAINED = 'Y'"));
+
+ return result;
+ }
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeParser.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeParser.java
new file mode 100644
index 0000000..8430338
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeParser.java
@@ -0,0 +1,350 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oracle
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static org.flywaydb.core.internal.util.FlywayDbWebsiteLinks.ORACLE_DATABASE;
+
+import org.flywaydb.core.api.configuration.Configuration;
+import org.flywaydb.core.internal.parser.*;
+import org.flywaydb.core.internal.sqlscript.Delimiter;
+import org.flywaydb.core.internal.sqlscript.ParsedSqlStatement;
+import org.flywaydb.core.internal.util.StringUtils;
+
+public class OceanBaseOracleModeParser extends Parser {
+ /**
+ * Delimiter of PL/SQL blocks and statements.
+ */
+ private static final Delimiter PLSQL_DELIMITER = new Delimiter("/", true);
+
+ // accessible by ( keywordoptionalidentifier )
+ private static final String ACCESSIBLE_BY_REGEX = "ACCESSIBLE\\sBY\\s\\(?(((FUNCTION|PROCEDURE|PACKAGE|TRIGGER|TYPE)\\s)?[^\\s]\\s?+)*\\)?";
+
+ private static final Pattern PLSQL_TYPE_BODY_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\s(NON)?EDITIONABLE)?\\sTYPE\\sBODY\\s([^\\s]*\\s)?(IS|AS)");
+
+ private static final Pattern PLSQL_PACKAGE_BODY_REGEX = Pattern.compile("^CREATE(\\s*OR\\s*REPLACE)?(\\s*(NON)?EDITIONABLE)?\\s*PACKAGE\\s*BODY\\s*([^\\s]*\\s)?(IS|AS)");
+ private static final StatementType PLSQL_PACKAGE_BODY_STATEMENT = new StatementType();
+
+ private static final Pattern PLSQL_PACKAGE_DEFINITION_REGEX = Pattern.compile("^CREATE(\\s*OR\\s*REPLACE)?(\\s*(NON)?EDITIONABLE)?\\s*PACKAGE\\s([^\\s*]*\\s*)?(AUTHID\\s*[^\\s*]*\\s*|" + ACCESSIBLE_BY_REGEX + ")*(IS|AS)");
+
+ private static final Pattern PLSQL_VIEW_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?((\\sNO)?\\sFORCE)?(\\s(NON)?EDITIONABLE)?\\sVIEW\\s([^\\s]*\\s)?AS\\sWITH\\s(PROCEDURE|FUNCTION)");
+ private static final StatementType PLSQL_VIEW_STATEMENT = new StatementType();
+
+ private static final Pattern PLSQL_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\s(NON)?EDITIONABLE)?\\s(FUNCTION(\\s\\S*)|PROCEDURE|TYPE|TRIGGER)");
+ private static final Pattern DECLARE_BEGIN_REGEX = Pattern.compile("^DECLARE|BEGIN|WITH");
+ private static final StatementType PLSQL_STATEMENT = new StatementType();
+
+ private static final Pattern JAVA_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\sAND\\s(RESOLVE|COMPILE))?(\\sNOFORCE)?\\sJAVA\\s(SOURCE|RESOURCE|CLASS)");
+ private static final StatementType PLSQL_JAVA_STATEMENT = new StatementType();
+
+ private static final Pattern PLSQL_PACKAGE_BODY_WRAPPED_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\s(NON)?EDITIONABLE)?\\sPACKAGE\\sBODY(\\s\\S*)?\\sWRAPPED(\\s\\S*)*");
+ private static final Pattern PLSQL_PACKAGE_DEFINITION_WRAPPED_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\s(NON)?EDITIONABLE)?\\sPACKAGE(\\s\\S*)?\\sWRAPPED(\\s\\S*)*");
+ private static final Pattern PLSQL_WRAPPED_REGEX = Pattern.compile("^CREATE(\\sOR\\sREPLACE)?(\\s(NON)?EDITIONABLE)?\\s(FUNCTION|PROCEDURE|TYPE)(\\s\\S*)?\\sWRAPPED(\\s\\S*)*");
+
+ private static final StatementType PLSQL_WRAPPED_STATEMENT = new StatementType();
+ private int initialWrappedBlockDepth = -1;
+
+ private static Pattern toRegex(String... commands) {
+ return Pattern.compile(toRegexPattern(commands));
+ }
+
+ private static String toRegexPattern(String... commands) {
+ return "^(" + StringUtils.arrayToDelimitedString("|", commands) + ")";
+ }
+
+
+ public OceanBaseOracleModeParser(Configuration configuration, ParsingContext parsingContext) {
+ super(configuration, parsingContext, 3);
+ }
+
+ @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 {
+ if (PLSQL_VIEW_STATEMENT == statementType) {
+ sql = sql.trim();
+ // Strip extra semicolon to avoid issues with WITH statements containing PL/SQL
+ if (sql.endsWith(";")) {
+ sql = sql.substring(0, sql.length() - 1);
+ }
+ }
+
+ return super.createStatement(reader, recorder, statementPos, statementLine, statementCol, nonCommentPartPos, nonCommentPartLine, nonCommentPartCol, statementType, canExecuteInTransaction, delimiter, sql, batchable);
+ }
+
+ @Override
+ protected StatementType detectStatementType(String simplifiedStatement, ParserContext context, PeekingReader reader) {
+ if (PLSQL_PACKAGE_BODY_WRAPPED_REGEX.matcher(simplifiedStatement).matches() || PLSQL_PACKAGE_DEFINITION_WRAPPED_REGEX.matcher(simplifiedStatement).matches() || PLSQL_WRAPPED_REGEX.matcher(simplifiedStatement).matches()) {
+ if (initialWrappedBlockDepth == -1) {
+ initialWrappedBlockDepth = context.getBlockDepth();
+ }
+ return PLSQL_WRAPPED_STATEMENT;
+ }
+
+ if (PLSQL_PACKAGE_BODY_REGEX.matcher(simplifiedStatement).matches()) {
+ return PLSQL_PACKAGE_BODY_STATEMENT;
+ }
+
+ if (PLSQL_REGEX.matcher(simplifiedStatement).matches() || PLSQL_PACKAGE_DEFINITION_REGEX.matcher(simplifiedStatement).matches() || DECLARE_BEGIN_REGEX.matcher(simplifiedStatement).matches()) {
+ try {
+ String wrappedKeyword = " WRAPPED";
+ if (!reader.peek(wrappedKeyword.length()).equalsIgnoreCase(wrappedKeyword)) {
+ return PLSQL_STATEMENT;
+ }
+ } catch (IOException e) {
+ return PLSQL_STATEMENT;
+ }
+ }
+
+ if (JAVA_REGEX.matcher(simplifiedStatement).matches()) {
+ return PLSQL_JAVA_STATEMENT;
+ }
+
+ if (PLSQL_VIEW_REGEX.matcher(simplifiedStatement).matches()) {
+ return PLSQL_VIEW_STATEMENT;
+ }
+
+
+ return super.detectStatementType(simplifiedStatement, context, reader);
+ }
+
+ @Override
+ protected boolean shouldDiscard(Token token, boolean nonCommentPartSeen) {
+ // Discard dangling PL/SQL '/' delimiters
+ return ("/".equals(token.getText()) && !nonCommentPartSeen) || super.shouldDiscard(token, nonCommentPartSeen);
+ }
+
+ @Override
+ protected void adjustDelimiter(ParserContext context, StatementType statementType) {
+ if (statementType == PLSQL_STATEMENT || statementType == PLSQL_VIEW_STATEMENT || statementType == PLSQL_JAVA_STATEMENT || statementType == PLSQL_PACKAGE_BODY_STATEMENT) {
+ context.setDelimiter(PLSQL_DELIMITER);
+
+
+ } else {
+ context.setDelimiter(Delimiter.SEMICOLON);
+ }
+ }
+
+ @Override
+ protected boolean shouldAdjustBlockDepth(ParserContext context, List tokens, Token token) {
+ // Package bodies can have an unbalanced BEGIN without END in the initialisation section.
+ TokenType tokenType = token.getType();
+ if (context.getStatementType() == PLSQL_PACKAGE_BODY_STATEMENT && (TokenType.EOF == tokenType || TokenType.DELIMITER == tokenType)) {
+ return true;
+ }
+
+ // Handle wrapped SQL on these token types to ensure it gets treated as one block
+ if (context.getStatementType() == PLSQL_WRAPPED_STATEMENT && (TokenType.EOF == tokenType || TokenType.DELIMITER == tokenType)) {
+ return true;
+ }
+
+ // In Oracle, symbols { } affect the block depth in embedded Java code
+ if (token.getType() == TokenType.SYMBOL && context.getStatementType() == PLSQL_JAVA_STATEMENT) {
+ return true;
+ }
+ final Token previousToken = getPreviousToken(tokens, token.getParensDepth());
+ if (previousToken != null && "CASE".equals(token.getText()) && "FROM".equals(previousToken.getText())) {
+ return false;
+ }
+
+ return super.shouldAdjustBlockDepth(context, tokens, token);
+ }
+
+ // These words increase the block depth - unless preceded by END (in which case the END will decrease the block depth)
+ private static final List CONTROL_FLOW_KEYWORDS = Arrays.asList("IF", "LOOP", "CASE");
+
+ @Override
+ protected void adjustBlockDepth(ParserContext context, List tokens, Token keyword, PeekingReader reader) {
+ TokenType tokenType = keyword.getType();
+ String keywordText = keyword.getText();
+ int parensDepth = keyword.getParensDepth();
+
+ if (lastTokenIs(tokens, parensDepth, "GOTO")) {
+ return;
+ }
+
+ if (context.getStatementType() == PLSQL_WRAPPED_STATEMENT) {
+ // ensure wrapped SQL has an increased block depth so it gets treated as one statement
+ if (context.getBlockDepth() == initialWrappedBlockDepth) {
+ context.increaseBlockDepth("WRAPPED");
+ }
+ // decrease block depth at the end to step out of a wrapped SQL block
+ if ((TokenType.EOF == tokenType || (TokenType.DELIMITER == tokenType && "/".equals(keywordText))) && context.getBlockDepth() > 0) {
+ context.decreaseBlockDepth();
+ }
+ // return early as we don't need to parse the contents of wrapped SQL - it's all one statement anyways
+ return;
+ } else {
+ // decrease block depth when wrapped SQL ends to step out of wrapped SQL block
+ if (context.getBlockDepth() > initialWrappedBlockDepth && context.getBlockInitiator().equals("WRAPPED")) {
+ initialWrappedBlockDepth = -1;
+ context.decreaseBlockDepth();
+ }
+ }
+
+ // In embedded Java code we judge the end of a class definition by the depth of braces.
+ // We ignore normal SQL keywords as Java code can contain arbitrary identifiers.
+ if (context.getStatementType() == PLSQL_JAVA_STATEMENT) {
+ if ("{".equals(keywordText)) {
+ context.increaseBlockDepth("PLSQL_JAVA_STATEMENT");
+ } else if ("}".equals(keywordText)) {
+ context.decreaseBlockDepth();
+ }
+ return;
+ }
+
+ if ("BEGIN".equals(keywordText) || (CONTROL_FLOW_KEYWORDS.contains(keywordText) && !precedingEndAttachesToThisKeyword(tokens, parensDepth, context, keyword)) || ("TRIGGER".equals(keywordText) && lastTokenIs(tokens, parensDepth, "COMPOUND")) || (context.getBlockDepth() == 0 && (doTokensMatchPattern(tokens, keyword, PLSQL_PACKAGE_BODY_REGEX) || doTokensMatchPattern(tokens, keyword, PLSQL_PACKAGE_DEFINITION_REGEX) || doTokensMatchPattern(tokens, keyword, PLSQL_TYPE_BODY_REGEX)))) {
+ context.increaseBlockDepth(keywordText);
+ } else if ("END".equals(keywordText)) {
+ context.decreaseBlockDepth();
+ }
+
+ // Package bodies can have an unbalanced BEGIN without END in the initialisation section. This allows us
+ // to exit the package even though we are still at block depth 1 due to the BEGIN.
+ if (context.getStatementType() == PLSQL_PACKAGE_BODY_STATEMENT && (TokenType.EOF == tokenType || TokenType.DELIMITER == tokenType) && context.getBlockDepth() == 1) {
+ context.decreaseBlockDepth();
+ }
+ }
+
+ private boolean precedingEndAttachesToThisKeyword(List tokens, int parensDepth, ParserContext context, Token keyword) {
+ // Normally IF, LOOP and CASE all pair up with END IF, END LOOP, END CASE
+ // However, CASE ... END is valid in expressions, so in code such as
+ // FOR i IN 1 .. CASE WHEN foo THEN 5 ELSE 6 END
+ // LOOP
+ // ...
+ // END LOOP
+ // the first END does *not* attach to the subsequent LOOP. The same is possible with $IF ... $END constructions
+ return lastTokenIs(tokens, parensDepth, "END") && lastTokenIsOnLine(tokens, parensDepth, keyword.getLine()) && keyword.getText().equals(context.getLastClosedBlockInitiator());
+ }
+
+ @Override
+ protected boolean doTokensMatchPattern(List previousTokens, Token current, Pattern regex) {
+ if (regex == PLSQL_PACKAGE_DEFINITION_REGEX && previousTokens.stream().anyMatch(t -> t.getType() == TokenType.KEYWORD && t.getText().equalsIgnoreCase("ACCESSIBLE"))) {
+ ArrayList tokenStrings = new ArrayList<>();
+ tokenStrings.add(current.getText());
+
+ for (int i = previousTokens.size() - 1; i >= 0; i--) {
+ Token prevToken = previousTokens.get(i);
+ if (prevToken.getType() == TokenType.KEYWORD) {
+ tokenStrings.add(prevToken.getText());
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (int i = tokenStrings.size() - 1; i >= 0; i--) {
+ builder.append(tokenStrings.get(i));
+ if (i != 0) {
+ builder.append(" ");
+ }
+ }
+
+ return regex.matcher(builder.toString()).matches() || super.doTokensMatchPattern(previousTokens, current, regex);
+ }
+
+ return super.doTokensMatchPattern(previousTokens, current, regex);
+ }
+
+ @Override
+ protected boolean isDelimiter(String peek, ParserContext context, int col, int colIgnoringWhitespace) {
+ Delimiter delimiter = context.getDelimiter();
+
+ if (peek.startsWith(delimiter.getEscape() + delimiter.getDelimiter())) {
+ return true;
+ }
+
+ if (delimiter.shouldBeAloneOnLine()) {
+ // Only consider alone-on-line delimiters (such as "/" for PL/SQL) if
+ // it's the first character on the line
+ return colIgnoringWhitespace == 1 && peek.trim().equals(delimiter.getDelimiter());
+ } else {
+ if (colIgnoringWhitespace == 1 && "/".equals(peek.trim())) {
+ return true;
+ }
+ }
+
+ return super.isDelimiter(peek, context, col, colIgnoringWhitespace);
+ }
+
+ @Override
+ protected Token handleMultilineComment(PeekingReader reader, ParserContext context, int pos, int line, int col) throws IOException {
+ reader.swallow("/*".length());
+ String text = reader.readUntilExcluding("*/");
+ reader.swallow("*/".length());
+ return new Token(TokenType.COMMENT, pos, line, col, text, text, context.getParensDepth());
+ }
+
+ @Override
+ protected Token handleDelimiter(PeekingReader reader, ParserContext context, int pos, int line, int col) throws IOException {
+
+
+ if (reader.peek('/')) {
+ reader.swallow(1);
+ return new Token(TokenType.DELIMITER, pos, line, col, "/", "/", context.getParensDepth());
+ }
+
+ return super.handleDelimiter(reader, context, pos, line, col);
+ }
+
+ @Override
+ protected boolean isAlternativeStringLiteral(String peek) {
+ if (peek.length() < 3) {
+ return false;
+ }
+ // Oracle's quoted-literal syntax is introduced by q (case-insensitive) followed by a literal surrounded by
+ // any of !!, [], {}, (), <> provided the selected pair do not appear in the literal string; the others may do.
+ char firstChar = peek.charAt(0);
+ return (firstChar == 'q' || firstChar == 'Q') && peek.charAt(1) == '\'';
+ }
+
+ @Override
+ protected Token handleAlternativeStringLiteral(PeekingReader reader, ParserContext context, int pos, int line, int col) throws IOException {
+ reader.swallow(2);
+ String closeQuote = computeAlternativeCloseQuote((char) reader.read());
+ reader.swallowUntilExcluding(closeQuote);
+ reader.swallow(closeQuote.length());
+ return new Token(TokenType.STRING, pos, line, col, null, null, context.getParensDepth());
+ }
+
+ private String computeAlternativeCloseQuote(char specialChar) {
+ switch (specialChar) {
+ case '!':
+ return "!'";
+ case '[':
+ return "]'";
+ case '(':
+ return ")'";
+ case '{':
+ return "}'";
+ case '<':
+ return ">'";
+ default:
+ return specialChar + "'";
+ }
+ }
+
+
+ @Override
+ protected String getAdditionalParsingErrorInfo() {
+ return "For Oracle-specific information about syntax and limitations, see " + ORACLE_DATABASE + ". ";
+ }
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeSchema.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeSchema.java
new file mode 100644
index 0000000..d9ecced
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeSchema.java
@@ -0,0 +1,549 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oracle
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
+
+import org.flywaydb.community.database.oceanbase.OceanBaseJdbcUtils;
+
+import java.sql.SQLException;
+import java.util.*;
+
+import lombok.CustomLog;
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.internal.database.base.Schema;
+import org.flywaydb.core.internal.database.base.Table;
+import org.flywaydb.core.internal.jdbc.JdbcTemplate;
+import org.flywaydb.core.internal.util.StringUtils;
+
+/**
+ * OceanBase implementation of Schema.
+ */
+@CustomLog
+public class OceanBaseOracleModeSchema extends Schema {
+ /**
+ * Creates a new OceanBase Oracle mode schema.
+ *
+ * @param jdbcTemplate The Jdbc Template for communicating with the DB.
+ * @param database The database-specific support.
+ * @param name The name of the schema.
+ */
+ OceanBaseOracleModeSchema(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, String name) {
+ super(jdbcTemplate, database, name);
+ }
+
+ private boolean greaterThanCurrentVersion(String versionNumber) {
+ boolean isGreat = false;
+ try {
+ String versionStr = OceanBaseJdbcUtils.getVersionNumber(this.jdbcTemplate.getConnection());
+ assert versionStr != null;
+ String[] split = versionStr.split("\\.");
+ int curMajorVersion = Integer.parseInt(split[0]);
+ int curMinorVersion = Integer.parseInt(split[1]);
+
+ String[] versionSplit = versionNumber.split("\\.");
+ int majorVersion = Integer.parseInt(versionSplit[0]);
+ int minorVersion = Integer.parseInt(versionSplit[1]);
+
+ if (curMajorVersion >= majorVersion && curMinorVersion >= minorVersion) {
+ isGreat = true;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Fail to Compare version numbers.", e);
+ }
+ return isGreat;
+ }
+
+ /**
+ * Checks whether the schema is system.
+ *
+ * @return {@code true} if it is system, {@code false} if not.
+ */
+ public boolean isSystem() throws SQLException {
+ return database.getSystemSchemas().contains(name);
+ }
+
+ /**
+ * Checks whether this schema is default for the current user.
+ *
+ * @return {@code true} if it is default, {@code false} if not.
+ */
+ boolean isDefaultSchemaForUser() throws SQLException {
+ return name.equals(database.doGetCurrentUser());
+ }
+
+ @Override
+ protected boolean doExists() throws SQLException {
+ return database.queryReturnsRows("SELECT * FROM ALL_USERS WHERE USERNAME = ?", name);
+ }
+
+ @Override
+ protected boolean doEmpty() throws SQLException {
+ return !ObjectType.supportedTypesExist(jdbcTemplate, database, this);
+ }
+
+ @Override
+ protected void doCreate() throws SQLException {
+ jdbcTemplate.execute("CREATE USER " + database.quote(name) + " IDENTIFIED BY " + database.quote("FFllyywwaayy00!!"));
+ jdbcTemplate.execute("GRANT RESOURCE TO " + database.quote(name));
+ jdbcTemplate.execute("GRANT UNLIMITED TABLESPACE TO " + database.quote(name));
+ }
+
+ @Override
+ protected void doDrop() throws SQLException {
+ jdbcTemplate.execute("DROP USER " + database.quote(name) + " CASCADE");
+ }
+
+ @Override
+ protected void doClean() throws SQLException {
+ if (isSystem()) {
+ throw new FlywayException("Clean not supported on OceanBase Oracle mode for system schema " + database.quote(name) + "! " + "It must not be changed in any way except by running an OceanBase-supplied script!");
+ }
+
+ // Get existing object types in the schema.
+ Set objectTypeNames = ObjectType.getObjectTypeNames(jdbcTemplate, database, this);
+
+ // Define the list of types to process, order is important.
+ List objectTypesToClean = Arrays.asList(
+ // Types to drop.
+ ObjectType.TRIGGER, ObjectType.FILE_WATCHER, ObjectType.SCHEDULER_CHAIN, ObjectType.SCHEDULER_JOB, ObjectType.SCHEDULER_PROGRAM, ObjectType.SCHEDULE, ObjectType.RULE_SET, ObjectType.RULE, ObjectType.EVALUATION_CONTEXT, ObjectType.FILE_GROUP, ObjectType.REWRITE_EQUIVALENCE, ObjectType.SQL_TRANSLATION_PROFILE, ObjectType.MATERIALIZED_VIEW, ObjectType.MATERIALIZED_VIEW_LOG, ObjectType.VIEW, ObjectType.DOMAIN_INDEX, ObjectType.DOMAIN_INDEX_TYPE, ObjectType.TABLE, ObjectType.INDEX, ObjectType.CLUSTER, ObjectType.SEQUENCE, ObjectType.OPERATOR, ObjectType.FUNCTION, ObjectType.PROCEDURE, ObjectType.PACKAGE, ObjectType.PACKAGE_BODY, ObjectType.CONTEXT, ObjectType.LIBRARY, ObjectType.TYPE, ObjectType.SYNONYM, ObjectType.JAVA_SOURCE, ObjectType.JAVA_CLASS, ObjectType.JAVA_RESOURCE,
+
+ // Object types with sensitive information (passwords), skip intentionally, print warning if found.
+ ObjectType.DATABASE_LINK, ObjectType.CREDENTIAL,
+
+ // Unsupported types, print warning if found
+ ObjectType.DATABASE_DESTINATION, ObjectType.SCHEDULER_GROUP, ObjectType.CUBE, ObjectType.CUBE_DIMENSION, ObjectType.CUBE_BUILD_PROCESS, ObjectType.MEASURE_FOLDER,
+
+ // Undocumented types, print warning if found
+ ObjectType.ASSEMBLY, ObjectType.JAVA_DATA);
+
+ for (ObjectType objectType : objectTypesToClean) {
+ if (objectTypeNames.contains(objectType.getName())) {
+ LOG.debug("Cleaning objects of type " + objectType + " ...");
+ objectType.dropObjects(jdbcTemplate, database, this);
+ }
+ }
+
+ if (isDefaultSchemaForUser()) {
+ jdbcTemplate.execute("PURGE RECYCLEBIN");
+ }
+ }
+
+
+ @Override
+ protected OceanBaseOracleModeTable[] doAllTables() throws SQLException {
+ List tableNames = ObjectType.TABLE.getObjectNames(jdbcTemplate, database, this);
+
+ OceanBaseOracleModeTable[] tables = new OceanBaseOracleModeTable[tableNames.size()];
+ for (int i = 0; i < tableNames.size(); i++) {
+ tables[i] = new OceanBaseOracleModeTable(jdbcTemplate, database, this, tableNames.get(i));
+ }
+ return tables;
+ }
+
+ @Override
+ public Table getTable(String tableName) {
+ return new OceanBaseOracleModeTable(jdbcTemplate, database, this, tableName);
+ }
+
+ /**
+ * OceanBase Oracle object types.
+ */
+ public enum ObjectType {
+ // Tables, including XML tables, except for nested tables, IOT overflow tables and other secondary objects.
+ TABLE("TABLE", "CASCADE CONSTRAINTS PURGE") {
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ boolean referencePartitionedTablesExist = database.queryReturnsRows("SELECT * FROM ALL_PART_TABLES WHERE OWNER = ? AND PARTITIONING_TYPE = 'REFERENCE'", schema.getName());
+ boolean xmlDbAvailable = database.isXmlDbAvailable();
+
+ StringBuilder tablesQuery = new StringBuilder();
+ tablesQuery.append("WITH TABLES AS (\n" + " SELECT TABLE_NAME, OWNER\n" + " FROM ALL_TABLES\n" + " WHERE OWNER = ?\n" + " AND (IOT_TYPE IS NULL OR IOT_TYPE NOT LIKE '%OVERFLOW%')\n" + " AND NESTED != 'YES'\n" + " AND SECONDARY != 'Y'\n");
+
+ tablesQuery.append(")\n" + "SELECT t.TABLE_NAME\n" + "FROM TABLES t\n");
+
+ // Reference partitioned tables should be dropped in child-to-parent order.
+ if (referencePartitionedTablesExist) {
+ tablesQuery.append(" LEFT JOIN ALL_PART_TABLES pt\n" + " ON t.OWNER = pt.OWNER\n" + " AND t.TABLE_NAME = pt.TABLE_NAME\n" + " AND pt.PARTITIONING_TYPE = 'REFERENCE'\n" + " LEFT JOIN ALL_CONSTRAINTS fk\n" + " ON pt.OWNER = fk.OWNER\n" + " AND pt.TABLE_NAME = fk.TABLE_NAME\n" + " AND pt.REF_PTN_CONSTRAINT_NAME = fk.CONSTRAINT_NAME\n" + " AND fk.CONSTRAINT_TYPE = 'R'\n" + " LEFT JOIN ALL_CONSTRAINTS puk\n" + " ON fk.R_OWNER = puk.OWNER\n" + " AND fk.R_CONSTRAINT_NAME = puk.CONSTRAINT_NAME\n" + " AND puk.CONSTRAINT_TYPE IN ('P', 'U')\n" + " LEFT JOIN TABLES p\n" + " ON puk.OWNER = p.OWNER\n" + " AND puk.TABLE_NAME = p.TABLE_NAME\n" + "START WITH p.TABLE_NAME IS NULL\n" + "CONNECT BY PRIOR t.TABLE_NAME = p.TABLE_NAME\n" + "ORDER BY LEVEL DESC");
+ }
+
+ int n = 1 + (xmlDbAvailable ? 1 : 0);
+ String[] params = new String[n];
+ Arrays.fill(params, schema.getName());
+
+ return jdbcTemplate.queryForStringList(tablesQuery.toString(), params);
+ }
+ },
+
+ // Materialized view logs.
+ MATERIALIZED_VIEW_LOG("MATERIALIZED VIEW LOG") {
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT MASTER FROM ALL_MVIEW_LOGS WHERE LOG_OWNER = ?", schema.getName());
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "DROP " + this.getName() + " ON " + database.quote(schema.getName(), objectName);
+ }
+ },
+
+ // All indexes, except for domain indexes, should be dropped after tables (if any left).
+ INDEX("INDEX") {
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT INDEX_NAME FROM ALL_INDEXES WHERE OWNER = ?" +
+ //" AND INDEX_NAME NOT LIKE 'SYS_C%'"+
+ " AND INDEX_TYPE NOT LIKE '%DOMAIN%'", schema.getName());
+ }
+ },
+
+ // Domain indexes, have related objects and should be dropped separately prior to tables.
+ DOMAIN_INDEX("INDEX", "FORCE") {
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT INDEX_NAME FROM ALL_INDEXES WHERE OWNER = ? AND INDEX_TYPE LIKE '%DOMAIN%'", schema.getName());
+ }
+ },
+
+ // Domain index types.
+ DOMAIN_INDEX_TYPE("INDEXTYPE", "FORCE"),
+
+ // Operators.
+ OPERATOR("OPERATOR", "FORCE"),
+
+ // Clusters.
+ CLUSTER("CLUSTER", "INCLUDING TABLES CASCADE CONSTRAINTS"),
+
+ // Views, including XML views.
+ VIEW("VIEW", "CASCADE CONSTRAINTS"),
+
+ // Materialized views, keep tables as they may be referenced.
+ MATERIALIZED_VIEW("MATERIALIZED VIEW", "PRESERVE TABLE"),
+
+ // Local synonyms.
+ SYNONYM("SYNONYM", "FORCE"),
+
+ // Sequences, no filtering for identity sequences, since they get dropped along with master tables.
+ SEQUENCE("SEQUENCE"),
+
+ // Procedures, functions, packages.
+ PROCEDURE("PROCEDURE"), FUNCTION("FUNCTION"), PACKAGE("PACKAGE"), PACKAGE_BODY("PACKAGE BODY"),
+
+ // Contexts, seen in DBA_CONTEXT view, may remain if DBA_CONTEXT is not accessible.
+ CONTEXT("CONTEXT") {
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT NAMESPACE FROM " + database.dbaOrAll("CONTEXT") + " WHERE SCHEMA = ?", schema.getName());
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "DROP " + this.getName() + " " + database.quote(objectName); // no owner
+ }
+ },
+
+ // Triggers of all types, should be dropped at first, because invalid DDL triggers may break the whole clean.
+ TRIGGER("TRIGGER"),
+
+ // Types.
+ TYPE("TYPE", "FORCE"),
+
+ // Java sources, classes, resources.
+ JAVA_SOURCE("JAVA SOURCE"), JAVA_CLASS("JAVA CLASS"), JAVA_RESOURCE("JAVA RESOURCE"),
+
+ // Libraries.
+ LIBRARY("LIBRARY"),
+
+ // Rewrite equivalences.
+ REWRITE_EQUIVALENCE("REWRITE EQUIVALENCE") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN SYS.DBMS_ADVANCED_REWRITE.DROP_REWRITE_EQUIVALENCE('" + database.quote(schema.getName(), objectName) + "'); END;";
+ }
+ },
+
+ // SQL translation profiles.
+ SQL_TRANSLATION_PROFILE("SQL TRANSLATION PROFILE") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SQL_TRANSLATOR.DROP_PROFILE('" + database.quote(schema.getName(), objectName) + "'); END;";
+ }
+ },
+
+ // Scheduler objects.
+ SCHEDULER_JOB("JOB") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_JOB('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, SCHEDULER_PROGRAM("PROGRAM") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_PROGRAM('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, SCHEDULE("SCHEDULE") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_SCHEDULE('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, SCHEDULER_CHAIN("CHAIN") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_CHAIN('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, FILE_WATCHER("FILE WATCHER") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_FILE_WATCHER('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ },
+
+ // Streams/rule objects.
+ RULE_SET("RULE SET") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_RULE_ADM.DROP_RULE_SET('" + database.quote(schema.getName(), objectName) + "', DELETE_RULES => FALSE); END;";
+ }
+ }, RULE("RULE") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_RULE_ADM.DROP_RULE('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, EVALUATION_CONTEXT("EVALUATION CONTEXT") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_RULE_ADM.DROP_EVALUATION_CONTEXT('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ }, FILE_GROUP("FILE GROUP") {
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_FILE_GROUP.DROP_FILE_GROUP('" + database.quote(schema.getName(), objectName) + "'); END;";
+ }
+ },
+
+ /*** Below are unsupported object types. They should be dropped explicitly in callbacks if used. ***/
+
+ // Database links and credentials, contain sensitive information (password) and hence not always can be re-created.
+ // Intentionally skip them and let the clean callbacks handle them if needed.
+ DATABASE_LINK("DATABASE LINK") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+
+ @Override
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT DB_LINK FROM " + database.dbaOrAll("DB_LINKS") + " WHERE OWNER = ?", schema.getName());
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "DROP " + this.getName() + " " + objectName; // db link name is case-insensitive and needs no owner
+ }
+ }, CREDENTIAL("CREDENTIAL") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_CREDENTIAL('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ },
+
+ // Some scheduler types, not supported yet.
+ DATABASE_DESTINATION("DESTINATION") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_DATABASE_DESTINATION('" + database.quote(schema.getName(), objectName) + "'); END;";
+ }
+ }, SCHEDULER_GROUP("SCHEDULER GROUP") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+
+ @Override
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "BEGIN DBMS_SCHEDULER.DROP_GROUP('" + database.quote(schema.getName(), objectName) + "', FORCE => TRUE); END;";
+ }
+ },
+
+ // OLAP objects, not supported yet.
+ CUBE("CUBE") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+ }, CUBE_DIMENSION("CUBE DIMENSION") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+ }, CUBE_BUILD_PROCESS("CUBE BUILD PROCESS") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()), "cube build processes");
+ }
+ }, MEASURE_FOLDER("MEASURE FOLDER") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+ },
+
+ // Undocumented objects.
+ ASSEMBLY("ASSEMBLY") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()), "assemblies");
+ }
+ }, JAVA_DATA("JAVA DATA") {
+ @Override
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) {
+ super.warnUnsupported(database.quote(schema.getName()));
+ }
+ },
+
+ // SYS-owned objects, cannot be dropped when a schema gets cleaned, simply ignore them.
+ CAPTURE("CAPTURE"), APPLY("APPLY"), DIRECTORY("DIRECTORY"), RESOURCE_PLAN("RESOURCE PLAN"), CONSUMER_GROUP("CONSUMER GROUP"), JOB_CLASS("JOB CLASS"), WINDOWS("WINDOW"), EDITION("EDITION"), AGENT_DESTINATION("DESTINATION"), UNIFIED_AUDIT_POLICY("UNIFIED AUDIT POLICY");
+
+ /**
+ * The name of the type as it mentioned in the Data Dictionary and the DROP statement.
+ */
+ private final String name;
+
+ /**
+ * The extra options used in the DROP statement to enforce the operation.
+ */
+ private final String dropOptions;
+
+ ObjectType(String name, String dropOptions) {
+ this.name = name;
+ this.dropOptions = dropOptions;
+ }
+
+ ObjectType(String name) {
+ this(name, "");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString().replace('_', ' ');
+ }
+
+ /**
+ * Returns the list of object names of this type.
+ *
+ * @throws SQLException if retrieving of objects failed.
+ */
+ public List getObjectNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ return jdbcTemplate.queryForStringList("SELECT DISTINCT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ? AND OBJECT_TYPE = ?", schema.getName(), this.getName());
+ }
+
+ /**
+ * Generates the drop statement for the specified object.
+ */
+ public String generateDropStatement(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String objectName) {
+ return "DROP " + this.getName() + " " + database.quote(schema.getName(), objectName) + (StringUtils.hasText(dropOptions) ? " " + dropOptions : "");
+ }
+
+ /**
+ * Drops all objects of this type in the specified schema.
+ *
+ * @throws SQLException if cleaning failed.
+ */
+ public void dropObjects(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ for (String objectName : getObjectNames(jdbcTemplate, database, schema)) {
+ jdbcTemplate.execute(generateDropStatement(jdbcTemplate, database, schema, objectName));
+ }
+ }
+
+ private void warnUnsupported(String schemaName, String typeDesc) {
+ LOG.warn("Unable to clean " + typeDesc + " for schema " + schemaName + ": unsupported operation");
+ }
+
+ private void warnUnsupported(String schemaName) {
+ warnUnsupported(schemaName, this.toString().toLowerCase() + "s");
+ }
+
+ /**
+ * Returns the schema's existing object types.
+ *
+ * @return a set of object type names.
+ * @throws SQLException if retrieving of object types failed.
+ */
+ public static Set getObjectTypeNames(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ boolean materializedViewEnable = schema.greaterThanCurrentVersion("4.3");
+ StringBuilder queryBuilder = new StringBuilder();
+
+ // Base query to select distinct object types
+ queryBuilder.append("SELECT DISTINCT OBJECT_TYPE FROM ")
+ .append(database.dbaOrAll("OBJECTS"))
+ .append(" WHERE OWNER = ? ");
+
+ // Add materialized view log conditionally
+ if (materializedViewEnable) {
+ queryBuilder.append("UNION SELECT '")
+ .append(MATERIALIZED_VIEW_LOG.getName())
+ .append("' FROM DUAL WHERE EXISTS (SELECT * FROM ALL_MVIEW_LOGS WHERE LOG_OWNER = ?)");
+ }
+
+ String query = queryBuilder.toString();
+
+ // Prepare parameters
+ int paramCount = materializedViewEnable ? 2 : 1;
+ String[] params = new String[paramCount];
+ Arrays.fill(params, schema.getName());
+
+ if (materializedViewEnable) {
+ params[1] = schema.getName(); // Set the same schema name for the second parameter
+ }
+
+ // Execute the query and return the result as a Set
+ return new HashSet<>(jdbcTemplate.queryForStringList(query, params));
+ }
+
+
+ /**
+ * Checks whether the specified schema contains object types that can be cleaned.
+ *
+ * @return {@code true} if it contains, {@code false} if not.
+ * @throws SQLException if retrieving of object types failed.
+ */
+ public static boolean supportedTypesExist(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema) throws SQLException {
+ Set existingTypeNames = new HashSet<>(getObjectTypeNames(jdbcTemplate, database, schema));
+
+ // Remove unsupported types.
+ existingTypeNames.removeAll(Arrays.asList(DATABASE_LINK.getName(), CREDENTIAL.getName(), DATABASE_DESTINATION.getName(), SCHEDULER_GROUP.getName(), CUBE.getName(), CUBE_DIMENSION.getName(), CUBE_BUILD_PROCESS.getName(), MEASURE_FOLDER.getName(), ASSEMBLY.getName(), JAVA_DATA.getName()));
+
+ return !existingTypeNames.isEmpty();
+ }
+ }
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeTable.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeTable.java
new file mode 100644
index 0000000..2cf2532
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/OceanBaseOracleModeTable.java
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oracle
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
+
+import java.sql.SQLException;
+
+import org.flywaydb.core.internal.database.base.Table;
+import org.flywaydb.core.internal.jdbc.JdbcTemplate;
+
+/**
+ * Oracle-specific table.
+ */
+public class OceanBaseOracleModeTable extends Table {
+ /**
+ * Creates a new Oracle 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.
+ */
+ public OceanBaseOracleModeTable(JdbcTemplate jdbcTemplate, OceanBaseOracleModeDatabase database, OceanBaseOracleModeSchema schema, String name) {
+ super(jdbcTemplate, database, schema, name);
+ }
+
+ @Override
+ protected void doDrop() throws SQLException {
+ jdbcTemplate.execute("DROP TABLE " + database.quote(schema.getName(), name) + " CASCADE CONSTRAINTS PURGE");
+ }
+
+ @Override
+ protected boolean doExists() throws SQLException {
+ return exists(null, schema, name);
+ }
+
+ @Override
+ protected void doLock() throws SQLException {
+ jdbcTemplate.execute("LOCK TABLE " + this + " IN EXCLUSIVE MODE");
+ }
+}
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/package-info.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/package-info.java
new file mode 100644
index 0000000..8b7e0b1
--- /dev/null
+++ b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/oracle/package-info.java
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * flyway-database-oceanbase
+ * ========================================================================
+ * Copyright (C) 2010 - 2024 Red Gate Software Ltd
+ * ========================================================================
+ * 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.
+ * =========================LICENSE_END==================================
+ */
+/*
+ * Copyright (C) Red Gate Software Ltd 2010-2024
+ *
+ * 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.
+ */
+/**
+ * Community-supported package. No compatibility guarantees provided.
+ */
+/**
+ * Private API. No compatibility guarantees provided.
+ */
+package org.flywaydb.community.database.oceanbase.oracle;
diff --git a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/package-info.java b/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/package-info.java
deleted file mode 100644
index 6ae21ee..0000000
--- a/flyway-database-oceanbase/src/main/java/org/flywaydb/community/database/oceanbase/package-info.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) Red Gate Software Ltd 2010-2024
- *
- * 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.
- */
-/**
- * Community-supported package. No compatibility guarantees provided.
- */
-package org.flywaydb.community.database.oceanbase;