From e3ee16e7f2385f56334afc7016373ea699ddb638 Mon Sep 17 00:00:00 2001 From: "ranyu.zyh" Date: Fri, 11 Apr 2025 16:48:02 +0800 Subject: [PATCH 1/4] feature:add oceanbase oracle supprot --- .../oceanbase/OceanBaseInsertExecutor.java | 139 +++++ .../oceanbase/OceanBaseEscapeHandler.java | 554 ++++++++++++++++++ .../struct/cache/OceanBaseTableMetaCache.java | 188 ++++++ .../OceanBaseUndoExecutorHolder.java | 49 ++ .../oceanbase/OceanBaseUndoLogManager.java | 102 ++++ ...he.seata.rm.datasource.exec.InsertExecutor | 1 + ...eata.rm.datasource.undo.UndoExecutorHolder | 1 + ...he.seata.rm.datasource.undo.UndoLogManager | 1 + .../org.apache.seata.sqlparser.EscapeHandler | 1 + ...ache.seata.sqlparser.struct.TableMetaCache | 1 + .../exec/OceanBaseInsertExecutorTest.java | 451 ++++++++++++++ .../sql/struct/TableMetaCacheFactoryTest.java | 2 + script/client/at/db/oceanbase.sql | 43 ++ script/client/saga/db/oceanbase.sql | 81 +++ script/client/tcc/db/oceanbase.sql | 29 + script/server/db/oceanbase.sql | 96 +++ .../oceanbase/BaseOceanBaseRecognizer.java | 97 +++ .../oceanbase/OceanBaseDeleteRecognizer.java | 140 +++++ .../oceanbase/OceanBaseInsertRecognizer.java | 166 ++++++ .../OceanBaseOperateRecognizerHolder.java | 39 ++ .../OceanBaseSelectForUpdateRecognizer.java | 140 +++++ .../oceanbase/OceanBaseUpdateRecognizer.java | 187 ++++++ ...sqlparser.druid.SQLOperateRecognizerHolder | 1 + 23 files changed, 2509 insertions(+) create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oceanbase/OceanBaseInsertExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oceanbase/OceanBaseEscapeHandler.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OceanBaseTableMetaCache.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoExecutorHolder.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoLogManager.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OceanBaseInsertExecutorTest.java create mode 100644 script/client/at/db/oceanbase.sql create mode 100644 script/client/saga/db/oceanbase.sql create mode 100644 script/client/tcc/db/oceanbase.sql create mode 100644 script/server/db/oceanbase.sql create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oceanbase/OceanBaseInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oceanbase/OceanBaseInsertExecutor.java new file mode 100644 index 00000000000..97dcb9c3c88 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oceanbase/OceanBaseInsertExecutor.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.exec.oceanbase; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.loader.Scope; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.BaseInsertExecutor; +import org.apache.seata.rm.datasource.exec.StatementCallback; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.Sequenceable; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Oracle insert executor. + * + */ +@LoadLevel(name = JdbcConstants.ORACLE, scope = Scope.PROTOTYPE) +public class OceanBaseInsertExecutor extends BaseInsertExecutor implements Sequenceable { + + private static final Logger LOGGER = LoggerFactory.getLogger(OceanBaseInsertExecutor.class); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public OceanBaseInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + /** + * 1. If the insert columns are not empty and do not contain any pk columns, + * it means that there is no pk value in the insert rows, then all the pk values should come from auto-increment. + *

+ * 2. The pk value exists in insert rows. The possible situations are: + *

+ * + * @return {@link Map}<{@link String}, {@link List}<{@link Object}>> + * @throws SQLException the sql exception + */ + @Override + public Map> getPkValues() throws SQLException { + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + Map> pkValuesMap = new HashMap<>(pkColumnNameList.size()); + + // first obtain the existing pk value from the insert rows (if exists) + if (!containsColumns() || containsAnyPk()) { + pkValuesMap.putAll(getPkValuesByColumn()); + } + // other from auto-increment + for (String columnName : pkColumnNameList) { + if (!pkValuesMap.containsKey(columnName)) { + pkValuesMap.put(columnName, getGeneratedKeys(columnName)); + } + } + return pkValuesMap; + } + + /** + * Whether the insert columns contain any pk columns + * + * @return true: contain at least one pk column. false: do not contain any pk columns + */ + public boolean containsAnyPk() { + SQLInsertRecognizer recognizer = (SQLInsertRecognizer)sqlRecognizer; + List insertColumns = recognizer.getInsertColumns(); + if (CollectionUtils.isEmpty(insertColumns)) { + return false; + } + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + if (CollectionUtils.isEmpty(pkColumnNameList)) { + return false; + } + List newColumns = ColumnUtils.delEscape(insertColumns, getDbType()); + return pkColumnNameList.stream().anyMatch(pkColumn -> newColumns.contains(pkColumn) + || CollectionUtils.toUpperList(newColumns).contains(pkColumn.toUpperCase())); + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + Set keySet = pkValuesMap.keySet(); + for (String pkKey : keySet) { + List pkValues = pkValuesMap.get(pkKey); + for (int i = 0; i < pkValues.size(); i++) { + if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlSequenceExpr) { + pkValues.set(i, getPkValuesBySequence((SqlSequenceExpr) pkValues.get(i), pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlMethodExpr) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof Null) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } + } + pkValuesMap.put(pkKey, pkValues); + } + return pkValuesMap; + } + + @Override + public String getSequenceSql(SqlSequenceExpr expr) { + return "SELECT " + expr.getSequence() + ".currval FROM DUAL"; + } + +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oceanbase/OceanBaseEscapeHandler.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oceanbase/OceanBaseEscapeHandler.java new file mode 100644 index 00000000000..a9aa9359b06 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oceanbase/OceanBaseEscapeHandler.java @@ -0,0 +1,554 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.sql.handler.oceanbase; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.EscapeHandler; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The type oracle sql keyword checker. + * + */ +@LoadLevel(name = JdbcConstants.OCEANBASE) +public class OceanBaseEscapeHandler implements EscapeHandler { + + private Set keywordSet = Arrays.stream(OracleKeyword.values()).map(OracleKeyword::name).collect(Collectors.toSet()); + + /** + * oracle keyword + */ + private enum OracleKeyword { + /** + * ACCESS is oracle keyword + */ + ACCESS("ACCESS"), + /** + * ADD is oracle keyword + */ + ADD("ADD"), + /** + * ALL is oracle keyword + */ + ALL("ALL"), + /** + * ALTER is oracle keyword + */ + ALTER("ALTER"), + /** + * AND is oracle keyword + */ + AND("AND"), + /** + * ANY is oracle keyword + */ + ANY("ANY"), + /** + * AS is oracle keyword + */ + AS("AS"), + /** + * ASC is oracle keyword + */ + ASC("ASC"), + /** + * AUDIT is oracle keyword + */ + AUDIT("AUDIT"), + /** + * BETWEEN is oracle keyword + */ + BETWEEN("BETWEEN"), + /** + * BY is oracle keyword + */ + BY("BY"), + /** + * CHAR is oracle keyword + */ + CHAR("CHAR"), + /** + * CHECK is oracle keyword + */ + CHECK("CHECK"), + /** + * CLUSTER is oracle keyword + */ + CLUSTER("CLUSTER"), + /** + * COLUMN is oracle keyword + */ + COLUMN("COLUMN"), + /** + * COLUMN_VALUE is oracle keyword + */ + COLUMN_VALUE("COLUMN_VALUE"), + /** + * COMMENT is oracle keyword + */ + COMMENT("COMMENT"), + /** + * COMPRESS is oracle keyword + */ + COMPRESS("COMPRESS"), + /** + * CONNECT is oracle keyword + */ + CONNECT("CONNECT"), + /** + * CREATE is oracle keyword + */ + CREATE("CREATE"), + /** + * CURRENT is oracle keyword + */ + CURRENT("CURRENT"), + /** + * DATE is oracle keyword + */ + DATE("DATE"), + /** + * DECIMAL is oracle keyword + */ + DECIMAL("DECIMAL"), + /** + * DEFAULT is oracle keyword + */ + DEFAULT("DEFAULT"), + /** + * DELETE is oracle keyword + */ + DELETE("DELETE"), + /** + * DESC is oracle keyword + */ + DESC("DESC"), + /** + * DISTINCT is oracle keyword + */ + DISTINCT("DISTINCT"), + /** + * DROP is oracle keyword + */ + DROP("DROP"), + /** + * ELSE is oracle keyword + */ + ELSE("ELSE"), + /** + * EXCLUSIVE is oracle keyword + */ + EXCLUSIVE("EXCLUSIVE"), + /** + * EXISTS is oracle keyword + */ + EXISTS("EXISTS"), + /** + * FILE is oracle keyword + */ + FILE("FILE"), + /** + * FLOAT is oracle keyword + */ + FLOAT("FLOAT"), + /** + * FOR is oracle keyword + */ + FOR("FOR"), + /** + * FROM is oracle keyword + */ + FROM("FROM"), + /** + * GRANT is oracle keyword + */ + GRANT("GRANT"), + /** + * GROUP is oracle keyword + */ + GROUP("GROUP"), + /** + * HAVING is oracle keyword + */ + HAVING("HAVING"), + /** + * IDENTIFIED is oracle keyword + */ + IDENTIFIED("IDENTIFIED"), + /** + * IMMEDIATE is oracle keyword + */ + IMMEDIATE("IMMEDIATE"), + /** + * IN is oracle keyword + */ + IN("IN"), + /** + * INCREMENT is oracle keyword + */ + INCREMENT("INCREMENT"), + /** + * INDEX is oracle keyword + */ + INDEX("INDEX"), + /** + * INITIAL is oracle keyword + */ + INITIAL("INITIAL"), + /** + * INSERT is oracle keyword + */ + INSERT("INSERT"), + /** + * INTEGER is oracle keyword + */ + INTEGER("INTEGER"), + /** + * INTERSECT is oracle keyword + */ + INTERSECT("INTERSECT"), + /** + * INTO is oracle keyword + */ + INTO("INTO"), + /** + * IS is oracle keyword + */ + IS("IS"), + /** + * LEVEL is oracle keyword + */ + LEVEL("LEVEL"), + /** + * LIKE is oracle keyword + */ + LIKE("LIKE"), + /** + * LOCK is oracle keyword + */ + LOCK("LOCK"), + /** + * LONG is oracle keyword + */ + LONG("LONG"), + /** + * MAXEXTENTS is oracle keyword + */ + MAXEXTENTS("MAXEXTENTS"), + /** + * MINUS is oracle keyword + */ + MINUS("MINUS"), + /** + * MLSLABEL is oracle keyword + */ + MLSLABEL("MLSLABEL"), + /** + * MODE is oracle keyword + */ + MODE("MODE"), + /** + * MODIFY is oracle keyword + */ + MODIFY("MODIFY"), + /** + * NESTED_TABLE_ID is oracle keyword + */ + NESTED_TABLE_ID("NESTED_TABLE_ID"), + /** + * NOAUDIT is oracle keyword + */ + NOAUDIT("NOAUDIT"), + /** + * NOCOMPRESS is oracle keyword + */ + NOCOMPRESS("NOCOMPRESS"), + /** + * NOT is oracle keyword + */ + NOT("NOT"), + /** + * NOWAIT is oracle keyword + */ + NOWAIT("NOWAIT"), + /** + * NULL is oracle keyword + */ + NULL("NULL"), + /** + * NUMBER is oracle keyword + */ + NUMBER("NUMBER"), + /** + * OF is oracle keyword + */ + OF("OF"), + /** + * OFFLINE is oracle keyword + */ + OFFLINE("OFFLINE"), + /** + * ON is oracle keyword + */ + ON("ON"), + /** + * ONLINE is oracle keyword + */ + ONLINE("ONLINE"), + /** + * OPTION is oracle keyword + */ + OPTION("OPTION"), + /** + * OR is oracle keyword + */ + OR("OR"), + /** + * ORDER is oracle keyword + */ + ORDER("ORDER"), + /** + * PCTFREE is oracle keyword + */ + PCTFREE("PCTFREE"), + /** + * PRIOR is oracle keyword + */ + PRIOR("PRIOR"), + /** + * PUBLIC is oracle keyword + */ + PUBLIC("PUBLIC"), + /** + * RAW is oracle keyword + */ + RAW("RAW"), + /** + * RENAME is oracle keyword + */ + RENAME("RENAME"), + /** + * RESOURCE is oracle keyword + */ + RESOURCE("RESOURCE"), + /** + * REVOKE is oracle keyword + */ + REVOKE("REVOKE"), + /** + * ROW is oracle keyword + */ + ROW("ROW"), + /** + * ROWID is oracle keyword + */ + ROWID("ROWID"), + /** + * ROWNUM is oracle keyword + */ + ROWNUM("ROWNUM"), + /** + * ROWS is oracle keyword + */ + ROWS("ROWS"), + /** + * SELECT is oracle keyword + */ + SELECT("SELECT"), + /** + * SESSION is oracle keyword + */ + SESSION("SESSION"), + /** + * SET is oracle keyword + */ + SET("SET"), + /** + * SHARE is oracle keyword + */ + SHARE("SHARE"), + /** + * SIZE is oracle keyword + */ + SIZE("SIZE"), + /** + * SMALLINT is oracle keyword + */ + SMALLINT("SMALLINT"), + /** + * START is oracle keyword + */ + START("START"), + /** + * SUCCESSFUL is oracle keyword + */ + SUCCESSFUL("SUCCESSFUL"), + /** + * SYNONYM is oracle keyword + */ + SYNONYM("SYNONYM"), + /** + * SYSDATE is oracle keyword + */ + SYSDATE("SYSDATE"), + /** + * TABLE is oracle keyword + */ + TABLE("TABLE"), + /** + * THEN is oracle keyword + */ + THEN("THEN"), + /** + * TO is oracle keyword + */ + TO("TO"), + /** + * TRIGGER is oracle keyword + */ + TRIGGER("TRIGGER"), + /** + * UID is oracle keyword + */ + UID("UID"), + /** + * UNION is oracle keyword + */ + UNION("UNION"), + /** + * UNIQUE is oracle keyword + */ + UNIQUE("UNIQUE"), + /** + * UPDATE is oracle keyword + */ + UPDATE("UPDATE"), + /** + * USER is oracle keyword + */ + USER("USER"), + /** + * VALIDATE is oracle keyword + */ + VALIDATE("VALIDATE"), + /** + * VALUES is oracle keyword + */ + VALUES("VALUES"), + /** + * VARCHAR is oracle keyword + */ + VARCHAR("VARCHAR"), + /** + * VARCHAR2 is oracle keyword + */ + VARCHAR2("VARCHAR2"), + /** + * VIEW is oracle keyword + */ + VIEW("VIEW"), + /** + * WHENEVER is oracle keyword + */ + WHENEVER("WHENEVER"), + /** + * WHERE is oracle keyword + */ + WHERE("WHERE"), + /** + * WITH is oracle keyword + */ + WITH("WITH"); + /** + * The Name. + */ + public final String name; + + OracleKeyword(String name) { + this.name = name; + } + } + + @Override + public boolean checkIfKeyWords(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + + @Override + public boolean checkIfNeedEscape(String columnName, TableMeta tableMeta) { + if (StringUtils.isBlank(columnName)) { + return false; + } + columnName = columnName.trim(); + if (containsEscape(columnName)) { + return false; + } + boolean isKeyWord = checkIfKeyWords(columnName); + if (isKeyWord) { + return true; + } + // oracle + // we are recommend table name and column name must uppercase. + // if exists full uppercase, the table name or column name doesn't bundle escape symbol. + //create\read table TABLE "table" "TABLE" + // + //table √ √ × √ + // + //TABLE √ √ × √ + // + //"table" × × √ × + // + //"TABLE" √ √ × √ + if (null != tableMeta) { + ColumnMeta columnMeta = tableMeta.getColumnMeta(columnName); + if (null != columnMeta) { + return columnMeta.isCaseSensitive(); + } + } else if (isUppercase(columnName)) { + return false; + } + return true; + } + + private static boolean isUppercase(String fieldOrTableName) { + if (fieldOrTableName == null) { + return false; + } + char[] chars = fieldOrTableName.toCharArray(); + for (char ch : chars) { + if (ch >= 'a' && ch <= 'z') { + return false; + } + } + return true; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OceanBaseTableMetaCache.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OceanBaseTableMetaCache.java new file mode 100644 index 00000000000..410efd0d96f --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OceanBaseTableMetaCache.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.sql.struct.cache; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.IndexMeta; +import org.apache.seata.sqlparser.struct.IndexType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The type Table meta cache. + */ +@LoadLevel(name = JdbcConstants.OCEANBASE) +public class OceanBaseTableMetaCache extends AbstractTableMetaCache { + + @Override + protected String getCacheKey(Connection connection, String tableName, String resourceId) { + StringBuilder cacheKey = new StringBuilder(resourceId); + cacheKey.append("."); + + // Use the original table name to avoid cache errors of tables with the same name across databases + if (tableName.contains("\"")) { + cacheKey.append(tableName.replace("\"", "")); + } else { + cacheKey.append(tableName.toUpperCase()); + } + return cacheKey.toString(); + } + + @Override + protected TableMeta fetchSchema(Connection connection, String tableName) throws SQLException { + try { + return resultSetMetaToSchema(connection.getMetaData(), tableName); + } catch (SQLException sqlEx) { + throw sqlEx; + } catch (Exception e) { + throw new SQLException(String.format("Failed to fetch schema of %s", tableName), e); + } + } + + protected TableMeta resultSetMetaToSchema(DatabaseMetaData dbmd, String tableName) throws SQLException { + TableMeta tm = new TableMeta(); + // Save the original table name information for active cache refresh + // to avoid refresh failure caused by missing catalog information + tm.setOriginalTableName(tableName); + String[] schemaTable = tableName.split("\\."); + //oceanbase username is schemaName + String schemaName = schemaTable.length > 1 ? schemaTable[0] : dbmd.getUserName().split("@")[0]; + tableName = schemaTable.length > 1 ? schemaTable[1] : tableName; + if (schemaName.contains("\"")) { + schemaName = schemaName.replace("\"", ""); + } else { + schemaName = schemaName.toUpperCase(); + } + + if (tableName.contains("\"")) { + tableName = tableName.replace("\"", ""); + + } else { + tableName = tableName.toUpperCase(); + } + // Ensure consistent metadata information for the same table with mixed upper and lower case letters. + tm.setTableName(tableName); + tm.setCaseSensitive(StringUtils.hasLowerCase(tableName)); + + try (ResultSet rsColumns = dbmd.getColumns("", schemaName, tableName, "%"); + ResultSet rsIndex = dbmd.getIndexInfo(null, schemaName, tableName, false, true); + ResultSet rsPrimary = dbmd.getPrimaryKeys(null, schemaName, tableName)) { + while (rsColumns.next()) { + ColumnMeta col = new ColumnMeta(); + col.setTableCat(rsColumns.getString("TABLE_CAT")); + col.setTableSchemaName(rsColumns.getString("TABLE_SCHEM")); + col.setTableName(rsColumns.getString("TABLE_NAME")); + col.setColumnName(rsColumns.getString("COLUMN_NAME")); + col.setDataType(rsColumns.getInt("DATA_TYPE")); + col.setDataTypeName(rsColumns.getString("TYPE_NAME")); + col.setColumnSize(rsColumns.getInt("COLUMN_SIZE")); + col.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS")); + col.setNumPrecRadix(rsColumns.getInt("NUM_PREC_RADIX")); + col.setNullAble(rsColumns.getInt("NULLABLE")); + col.setRemarks(rsColumns.getString("REMARKS")); + col.setColumnDef(rsColumns.getString("COLUMN_DEF")); + col.setSqlDataType(rsColumns.getInt("SQL_DATA_TYPE")); + col.setSqlDatetimeSub(rsColumns.getInt("SQL_DATETIME_SUB")); + col.setCharOctetLength(rsColumns.getInt("CHAR_OCTET_LENGTH")); + col.setOrdinalPosition(rsColumns.getInt("ORDINAL_POSITION")); + col.setIsNullAble(rsColumns.getString("IS_NULLABLE")); + col.setCaseSensitive(StringUtils.hasLowerCase(col.getColumnName())); + + if (tm.getAllColumns().containsKey(col.getColumnName())) { + throw new NotSupportYetException("Not support the table has the same column name with different case yet"); + } + tm.getAllColumns().put(col.getColumnName(), col); + } + + while (rsIndex.next()) { + String indexName = rsIndex.getString("INDEX_NAME"); + if (StringUtils.isNullOrEmpty(indexName)) { + continue; + } + String colName = rsIndex.getString("COLUMN_NAME"); + ColumnMeta col = tm.getAllColumns().get(colName); + if (tm.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tm.getAllIndexes().get(indexName); + index.getValues().add(col); + } else { + IndexMeta index = new IndexMeta(); + index.setIndexName(indexName); + index.setNonUnique(rsIndex.getBoolean("NON_UNIQUE")); + index.setIndexQualifier(rsIndex.getString("INDEX_QUALIFIER")); + index.setIndexName(rsIndex.getString("INDEX_NAME")); + index.setType(rsIndex.getShort("TYPE")); + index.setOrdinalPosition(rsIndex.getShort("ORDINAL_POSITION")); + index.setAscOrDesc(rsIndex.getString("ASC_OR_DESC")); + index.setCardinality(rsIndex.getLong("CARDINALITY")); + index.getValues().add(col); + if (!index.isNonUnique()) { + index.setIndextype(IndexType.UNIQUE); + } else { + index.setIndextype(IndexType.NORMAL); + } + tm.getAllIndexes().put(indexName, index); + } + } + if (tm.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException(String.format("Could not found any index in the table: %s", tableName)); + } + + // Handle primary key constraint naming issues in Oracle. + List pkcol = new ArrayList<>(); + while (rsPrimary.next()) { + String pkConstraintName = rsPrimary.getString("PK_NAME"); + if (tm.getAllIndexes().containsKey(pkConstraintName)) { + IndexMeta index = tm.getAllIndexes().get(pkConstraintName); + index.setIndextype(IndexType.PRIMARY); + } else { + pkcol.add(rsPrimary.getString("COLUMN_NAME")); + } + } + if (!pkcol.isEmpty()) { + int matchCols = 0; + for (Map.Entry entry : tm.getAllIndexes().entrySet()) { + IndexMeta index = entry.getValue(); + if (index.getIndextype().value() == IndexType.UNIQUE.value()) { + for (ColumnMeta col : index.getValues()) { + if (pkcol.contains(col.getColumnName())) { + matchCols++; + } + } + if (matchCols == pkcol.size()) { + index.setIndextype(IndexType.PRIMARY); + break; + } else { + matchCols = 0; + } + } + } + } + } + return tm; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoExecutorHolder.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoExecutorHolder.java new file mode 100644 index 00000000000..d11d817ca54 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoExecutorHolder.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.undo.oceanbase; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.rm.datasource.undo.UndoExecutorHolder; +import org.apache.seata.rm.datasource.undo.oracle.OracleUndoDeleteExecutor; +import org.apache.seata.rm.datasource.undo.oracle.OracleUndoInsertExecutor; +import org.apache.seata.rm.datasource.undo.oracle.OracleUndoUpdateExecutor; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The Type OracleUndoExecutorHolder + * + */ +@LoadLevel(name = JdbcConstants.OCEANBASE) +public class OceanBaseUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoLogManager.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoLogManager.java new file mode 100644 index 00000000000..af021a87d91 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oceanbase/OceanBaseUndoLogManager.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.undo.oceanbase; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.DateUtil; +import org.apache.seata.core.compressor.CompressorType; +import org.apache.seata.core.constants.ClientTableColumnsName; +import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager; +import org.apache.seata.rm.datasource.undo.UndoLogParser; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@LoadLevel(name = JdbcConstants.OCEANBASE) +public class OceanBaseUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(OceanBaseUndoLogManager.class); + + private static final String CHECK_UNDO_LOG_TABLE_EXIST_SQL = "SELECT 1 FROM " + UNDO_LOG_TABLE_NAME + " WHERE ROWNUM = 1"; + + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + + ClientTableColumnsName.UNDO_LOG_XID + ", " + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + "VALUES ( ?, ?, ?, ?, ?, sysdate, sysdate)"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= to_date(?,'yyyy-mm-dd hh24:mi:ss') and ROWNUM <= ?"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { + String dateStr = DateUtil.formatDate(logCreated, "yyyy-MM-dd HH:mm:ss"); + deletePST.setString(1, dateStr); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId, rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), + State.GlobalFinished, conn); + } + + private void insertUndoLog(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { + pst.setLong(1, branchID); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected String getCheckUndoLogTableExistSql() { + return CHECK_UNDO_LOG_TABLE_EXIST_SQL; + } +} diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor index bdd61e97e24..8cab1134020 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor @@ -16,6 +16,7 @@ # org.apache.seata.rm.datasource.exec.mysql.MySQLInsertExecutor org.apache.seata.rm.datasource.exec.oracle.OracleInsertExecutor +org.apache.seata.rm.datasource.exec.oceanbase.OceanBaseInsertExecutor org.apache.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor org.apache.seata.rm.datasource.exec.sqlserver.SqlServerInsertExecutor org.apache.seata.rm.datasource.exec.mariadb.MariadbInsertExecutor diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder index e2ae497e2ab..ca85fa8c3cc 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder @@ -16,6 +16,7 @@ # org.apache.seata.rm.datasource.undo.mysql.MySQLUndoExecutorHolder org.apache.seata.rm.datasource.undo.oracle.OracleUndoExecutorHolder +org.apache.seata.rm.datasource.undo.oceanbase.OceanBaseUndoExecutorHolder org.apache.seata.rm.datasource.undo.postgresql.PostgresqlUndoExecutorHolder org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoExecutorHolder org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoExecutorHolder diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager index fbc80b929ff..e7a79f04751 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager @@ -16,6 +16,7 @@ # org.apache.seata.rm.datasource.undo.mysql.MySQLUndoLogManager org.apache.seata.rm.datasource.undo.oracle.OracleUndoLogManager +org.apache.seata.rm.datasource.undo.oceanbase.OceanBaseUndoLogManager org.apache.seata.rm.datasource.undo.postgresql.PostgresqlUndoLogManager org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoLogManager org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoLogManager diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler index 0b4af20529c..0e16df727e1 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler @@ -16,6 +16,7 @@ # org.apache.seata.rm.datasource.sql.handler.oracle.OracleEscapeHandler org.apache.seata.rm.datasource.sql.handler.mysql.MySQLEscapeHandler +org.apache.seata.rm.datasource.sql.handler.oceanbase.OceanBaseEscapeHandler org.apache.seata.rm.datasource.sql.handler.postgresql.PostgresqlEscapeHandler org.apache.seata.rm.datasource.sql.handler.mariadb.MariadbEscapeHandler org.apache.seata.rm.datasource.sql.handler.sqlserver.SqlServerEscapeHandler diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache index 4ed7384dac1..16f453326e0 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache @@ -16,6 +16,7 @@ # org.apache.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.OracleTableMetaCache +org.apache.seata.rm.datasource.sql.struct.cache.OceanBaseTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.PostgresqlTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.SqlServerTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.MariadbTableMetaCache diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OceanBaseInsertExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OceanBaseInsertExecutorTest.java new file mode 100644 index 00000000000..229d0712707 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OceanBaseInsertExecutorTest.java @@ -0,0 +1,451 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.rm.datasource.exec; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.rm.datasource.ConnectionProxy; +import org.apache.seata.rm.datasource.PreparedStatementProxy; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.oracle.OracleInsertExecutor; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class OceanBaseInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE_ID = 100; + private static final Integer PK_VALUE_USER_ID = 200; + + private ConnectionProxy connectionProxy; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private StatementCallback statementCallback; + + private TableMeta tableMeta; + + private OracleInsertExecutor insertExecutor; + + private final int pkIndexId = 0; + + private final int pkIndexUserId = 1; + + private HashMap pkIndexMap; + + private HashMap multiPkIndexMap; + + @BeforeEach + public void init() { + connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.OCEANBASE); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new OracleInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + }}; + + multiPkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + put(USER_ID_COLUMN, pkIndexUserId); + }}; + } + + @Test + public void testPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValuesSeq = new ArrayList<>(); + pkValuesSeq.add(PK_VALUE_ID); + + doReturn(pkValuesSeq).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeq); + } + + @Test + public void testMultiPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersMultiPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + List pkValuesSeqId = new ArrayList<>(); + pkValuesSeqId.add(PK_VALUE_ID); + List pkValuesSeqUserId = new ArrayList<>(); + pkValuesSeqUserId.add(PK_VALUE_USER_ID); + + doReturn(pkValuesSeqId).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkValuesSeqUserId).when(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + verify(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeqId); + Assertions.assertEquals(pkValuesByColumn.get(USER_ID_COLUMN), pkValuesSeqUserId); + } + + @Test + public void testPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + doReturn(Arrays.asList(new Object[]{PK_VALUE_ID})).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + Map> pkValuesByAuto = insertExecutor.getPkValues(); + + verify(insertExecutor).getGeneratedKeys(ID_COLUMN); + Assertions.assertEquals(pkValuesByAuto.get(ID_COLUMN), Arrays.asList(new Object[]{PK_VALUE_ID})); + } + + @Test + public void testMultiPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersMultiPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValues(); + }); + + + } + + @Test + public void testStatement_pkValueByAuto_NotSupportYetException() throws Exception { + mockInsertColumns(); + mockStatementInsertRows(); + + statementProxy = mock(StatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.OCEANBASE); + + insertExecutor = Mockito.spy(new OracleInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Map map = new HashMap<>(); + map.put(ID_COLUMN, mock(ColumnMeta.class)); + doReturn(map).when(tableMeta).getPrimaryKeyMap(); + + ResultSet rs = mock(ResultSet.class); + doReturn(rs).when(statementProxy).getGeneratedKeys(); + doReturn(false).when(rs).next(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getGeneratedKeys(ID_COLUMN); + }); + + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValuesByColumn(); + }); + + } + + @Test + public void testGetPkValues_SinglePk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock pk values from insert rows + Map> mockPkValuesFromColumn = new HashMap<>(); + mockPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + + // situation1: insert columns are empty + List columns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain the pk column + columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns are not empty and do not contain the pk column + columns = new ArrayList<>(); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals( + Collections.singletonMap(ID_COLUMN, mockPkValuesAutoGenerated).entrySet(), + insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testGetPkValues_MultiPk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock all pk values from insert rows + Map> mockAllPkValuesFromColumn = new HashMap<>(); + mockAllPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + mockAllPkValuesFromColumn.put(USER_ID_COLUMN, Collections.singletonList(PK_VALUE_USER_ID + 1)); + doReturn(mockAllPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated_ID = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated_ID).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + List mockPkValuesAutoGenerated_USER_ID = Collections.singletonList(PK_VALUE_USER_ID); + doReturn(mockPkValuesAutoGenerated_USER_ID).when(insertExecutor).getGeneratedKeys(USER_ID_COLUMN); + + // situation1: insert columns are empty + List insertColumns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain all pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns contain partial pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + Map> mockPkValuesFromColumn_ID = new HashMap<>(); + mockPkValuesFromColumn_ID.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn_ID).when(insertExecutor).getPkValuesByColumn(); + + Map> expectPkValues = new HashMap<>(mockPkValuesFromColumn_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation4: insert columns are not empty and do not contain the pk column + insertColumns = new ArrayList<>(); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + doReturn(new HashMap<>()).when(insertExecutor).getPkValuesByColumn(); + + expectPkValues = new HashMap<>(); + expectPkValues.put(ID_COLUMN, mockPkValuesAutoGenerated_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testContainsAnyPK() { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + mockInsertColumns(); + doReturn(null).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + List pkColumns = new ArrayList<>(); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private SqlSequenceExpr mockParametersPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private SqlSequenceExpr mockParametersMultiPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(expr); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private void mockParametersPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + private void mockParametersMultiPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(Null.get()); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + } + + private void mockStatementInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList(Null.get(), "xx", "xx", "xx")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java index ef746ad05eb..844c0aab34f 100644 --- a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java @@ -18,6 +18,7 @@ import org.apache.seata.rm.datasource.sql.struct.TableMetaCacheFactory; import org.apache.seata.rm.datasource.sql.struct.cache.MariadbTableMetaCache; +import org.apache.seata.rm.datasource.sql.struct.cache.OceanBaseTableMetaCache; import org.apache.seata.rm.datasource.sql.struct.cache.PolarDBXTableMetaCache; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -38,6 +39,7 @@ public void getTableMetaCache() { Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MARIADB) instanceof MariadbTableMetaCache); Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POLARDBX) instanceof PolarDBXTableMetaCache); Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE) instanceof OracleTableMetaCache); + Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.OCEANBASE) instanceof OceanBaseTableMetaCache); Assertions.assertEquals(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE), TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE)); Assertions.assertEquals(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL), TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)); Assertions.assertThrows(EnhancedServiceNotFoundException.class, () -> { diff --git a/script/client/at/db/oceanbase.sql b/script/client/at/db/oceanbase.sql new file mode 100644 index 00000000000..d9c94f37caa --- /dev/null +++ b/script/client/at/db/oceanbase.sql @@ -0,0 +1,43 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +-- for AT mode you must to init this sql for you business database. the seata server not need it. +CREATE TABLE undo_log +( + id NUMBER(19) NOT NULL, + branch_id NUMBER(19) NOT NULL, + xid VARCHAR2(128) NOT NULL, + context VARCHAR2(128) NOT NULL, + rollback_info BLOB NOT NULL, + log_status NUMBER(10) NOT NULL, + log_created TIMESTAMP(0) NOT NULL, + log_modified TIMESTAMP(0) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT ux_undo_log UNIQUE (xid, branch_id) +); +CREATE INDEX ix_log_created ON undo_log(log_created); +COMMENT ON TABLE undo_log IS 'AT transaction mode undo table'; +COMMENT ON COLUMN undo_log.branch_id is 'branch transaction id'; +COMMENT ON COLUMN undo_log.xid is 'global transaction id'; +COMMENT ON COLUMN undo_log.context is 'undo_log context,such as serialization'; +COMMENT ON COLUMN undo_log.rollback_info is 'rollback info'; +COMMENT ON COLUMN undo_log.log_status is '0:normal status,1:defense status'; +COMMENT ON COLUMN undo_log.log_created is 'create datetime'; +COMMENT ON COLUMN undo_log.log_modified is 'modify datetime'; + +-- Generate ID using sequence and trigger +CREATE SEQUENCE UNDO_LOG_SEQ START WITH 1 INCREMENT BY 1; \ No newline at end of file diff --git a/script/client/saga/db/oceanbase.sql b/script/client/saga/db/oceanbase.sql new file mode 100644 index 00000000000..db29e40c5f7 --- /dev/null +++ b/script/client/saga/db/oceanbase.sql @@ -0,0 +1,81 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +CREATE TABLE seata_state_machine_def +( + id VARCHAR(32) NOT NULL, + name VARCHAR(128) NOT NULL, + tenant_id VARCHAR(32) NOT NULL, + app_name VARCHAR(32) NOT NULL, + type VARCHAR(20), + comment_ VARCHAR(255), + ver VARCHAR(16) NOT NULL, + gmt_create TIMESTAMP(3) NOT NULL, + status VARCHAR(2) NOT NULL, + content CLOB, + recover_strategy VARCHAR(16), + PRIMARY KEY (id) +); + +CREATE TABLE seata_state_machine_inst +( + id VARCHAR(128) NOT NULL, + machine_id VARCHAR(32) NOT NULL, + tenant_id VARCHAR(32) NOT NULL, + parent_id VARCHAR(128), + gmt_started TIMESTAMP(3) NOT NULL, + business_key VARCHAR(48), + uni_business_key VARCHAR(128) GENERATED ALWAYS AS ( + CASE + WHEN "BUSINESS_KEY" IS NULL + THEN "ID" + ELSE "BUSINESS_KEY" + END), + start_params CLOB, + gmt_end TIMESTAMP(3), + excep BLOB, + end_params CLOB, + status VARCHAR(2), + compensation_status VARCHAR(2), + is_running SMALLINT, + gmt_updated TIMESTAMP(3) NOT NULL, + PRIMARY KEY (id) +); +CREATE UNIQUE INDEX state_machine_inst_unibuzkey ON seata_state_machine_inst (uni_business_key, tenant_id); + +CREATE TABLE seata_state_inst +( + id VARCHAR(48) NOT NULL, + machine_inst_id VARCHAR(46) NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(20), + service_name VARCHAR(128), + service_method VARCHAR(128), + service_type VARCHAR(16), + business_key VARCHAR(48), + state_id_compensated_for VARCHAR(50), + state_id_retried_for VARCHAR(50), + gmt_started TIMESTAMP(3) NOT NULL, + is_for_update SMALLINT, + input_params CLOB, + output_params CLOB, + status VARCHAR(2) NOT NULL, + excep BLOB, + gmt_updated TIMESTAMP(3), + gmt_end TIMESTAMP(3), + PRIMARY KEY (id, machine_inst_id) +); diff --git a/script/client/tcc/db/oceanbase.sql b/script/client/tcc/db/oceanbase.sql new file mode 100644 index 00000000000..f4b5b566348 --- /dev/null +++ b/script/client/tcc/db/oceanbase.sql @@ -0,0 +1,29 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +CREATE TABLE tcc_fence_log +( + xid VARCHAR2(128) NOT NULL, + branch_id NUMBER(19) NOT NULL, + action_name VARCHAR2(64) NOT NULL, + status NUMBER(3) NOT NULL, + gmt_create TIMESTAMP(3) NOT NULL, + gmt_modified TIMESTAMP(3) NOT NULL, + PRIMARY KEY (xid, branch_id) +); +CREATE INDEX idx_gmt_modified ON tcc_fence_log (gmt_modified); +CREATE INDEX idx_status ON tcc_fence_log (status); \ No newline at end of file diff --git a/script/server/db/oceanbase.sql b/script/server/db/oceanbase.sql new file mode 100644 index 00000000000..797603b7976 --- /dev/null +++ b/script/server/db/oceanbase.sql @@ -0,0 +1,96 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +-- -------------------------------- The script used when storeMode is 'db' -------------------------------- +-- the table to store GlobalSession data +CREATE TABLE global_table +( + xid VARCHAR2(128) NOT NULL, + transaction_id NUMBER(19), + status NUMBER(3) NOT NULL, + application_id VARCHAR2(32), + transaction_service_group VARCHAR2(32), + transaction_name VARCHAR2(128), + timeout NUMBER(10), + begin_time NUMBER(19), + application_data VARCHAR2(2000), + gmt_create TIMESTAMP(0), + gmt_modified TIMESTAMP(0), + PRIMARY KEY (xid) +); + +CREATE INDEX idx_status_gmt_modified ON global_table (status, gmt_modified); +CREATE INDEX idx_transaction_id ON global_table (transaction_id); + +-- the table to store BranchSession data +CREATE TABLE branch_table +( + branch_id NUMBER(19) NOT NULL, + xid VARCHAR2(128) NOT NULL, + transaction_id NUMBER(19), + resource_group_id VARCHAR2(32), + resource_id VARCHAR2(256), + branch_type VARCHAR2(8), + status NUMBER(3), + client_id VARCHAR2(64), + application_data VARCHAR2(2000), + gmt_create TIMESTAMP(6), + gmt_modified TIMESTAMP(6), + PRIMARY KEY (branch_id) +); + +CREATE INDEX idx_xid ON branch_table (xid); + +-- the table to store lock data +CREATE TABLE lock_table +( + row_key VARCHAR2(128) NOT NULL, + xid VARCHAR2(128), + transaction_id NUMBER(19), + branch_id NUMBER(19) NOT NULL, + resource_id VARCHAR2(256), + table_name VARCHAR2(32), + pk VARCHAR2(36), + status NUMBER(3) DEFAULT 0 NOT NULL, + gmt_create TIMESTAMP(0), + gmt_modified TIMESTAMP(0), + PRIMARY KEY (row_key) +); + +comment on column lock_table.status is '0:locked ,1:rollbacking'; +CREATE INDEX idx_branch_id ON lock_table (branch_id); +CREATE INDEX idx_lock_table_xid ON lock_table (xid); +CREATE INDEX idx_status ON lock_table (status); + +CREATE TABLE distributed_lock ( + lock_key VARCHAR2(20) NOT NULL, + lock_value VARCHAR2(20) NOT NULL, + expire DECIMAL(18) NOT NULL, + PRIMARY KEY (lock_key) +); + +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); + +CREATE TABLE vgroup_table +( + vGroup VARCHAR2(255) PRIMARY KEY, + namespace VARCHAR2(255), + cluster VARCHAR2(255) +); \ No newline at end of file diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java new file mode 100644 index 00000000000..193e192043f --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java @@ -0,0 +1,97 @@ +package org.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.druid.BaseRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.util.JdbcConstants; + +public abstract class BaseOceanBaseRecognizer extends BaseRecognizer { + + /** + * Instantiates a new OceanBase base recognizer + * + * @param originalSql the original sql + */ + public BaseOceanBaseRecognizer(String originalSql) { + super(originalSql); + } + + public SQLASTOutputVisitor createOracleOutputVisitor(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList, + final StringBuilder sb) { + + return new OracleOutputVisitor(sb) { + @Override + public boolean visit(SQLVariantRefExpr x) { + if ("?".equals(x.getName())) { + ArrayList oneParamValues = parametersHolder.getParameters().get(x.getIndex() + 1); + if (paramAppenderList.isEmpty()) { + oneParamValues.forEach(t -> paramAppenderList.add(new ArrayList<>())); + } + for (int i = 0; i < oneParamValues.size(); i++) { + Object o = oneParamValues.get(i); + paramAppenderList.get(i).add(o instanceof Null ? null : o); + } + } + return super.visit(x); + } + }; + } + + public String getWhereCondition(SQLExpr where, final ParametersHolder parametersHolder, final ArrayList> paramAppenderList) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, createOracleOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + public String getWhereCondition(SQLExpr where) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, new OracleOutputVisitor(sb)); + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, new OracleOutputVisitor(sb)); + + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy, final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, createOracleOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + public String getDbType() { + return JdbcConstants.OCEANBASE; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java new file mode 100644 index 00000000000..7bf494219d8 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleDeleteStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLDeleteRecognizer; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.oracle.BaseOracleRecognizer; + +/** + * The type oceanbase delete recognizer. + * + */ +public class OceanBaseDeleteRecognizer extends BaseOceanBaseRecognizer implements SQLDeleteRecognizer { + + private final SQLDeleteStatement ast; + + /** + * Instantiates a new My sql delete recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OceanBaseDeleteRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLDeleteStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.DELETE; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of delete with join table"); + } + }; + SQLTableSource tableSource; + if (ast.getFrom() == null) { + tableSource = ast.getTableSource(); + } else { + tableSource = ast.getFrom(); + } + + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of delete with unknow"); + } + return sb.toString(); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oceanbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oceanbase does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //oceanbase does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oceanbase does not support order by yet + return null; + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java new file mode 100644 index 00000000000..dc85abfa1d7 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; +import com.alibaba.druid.sql.ast.expr.SQLSequenceExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; + +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; + +/** + * The type oracle insert recognizer. + * + */ +public class OceanBaseInsertRecognizer extends BaseOceanBaseRecognizer implements SQLInsertRecognizer { + + private final SQLInsertStatement ast; + + /** + * Instantiates a new My sql insert recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OceanBaseInsertRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLInsertStatement) ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.INSERT; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit(ast.getTableSource()); + return sb.toString(); + } + + @Override + public boolean insertColumnsIsEmpty() { + return CollectionUtils.isEmpty(ast.getColumns()); + } + + @Override + public List getInsertColumns() { + List columnSQLExprs = ast.getColumns(); + if (columnSQLExprs.isEmpty()) { + // INSERT INTO ta VALUES (...), without fields clarified + return null; + } + List list = new ArrayList<>(columnSQLExprs.size()); + for (SQLExpr expr : columnSQLExprs) { + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List> getInsertRows(Collection primaryKeyIndex) { + List valuesClauses = ast.getValuesList(); + List> rows = new ArrayList<>(valuesClauses.size()); + for (SQLInsertStatement.ValuesClause valuesClause : valuesClauses) { + List exprs = valuesClause.getValues(); + List row = new ArrayList<>(exprs.size()); + rows.add(row); + for (int i = 0, len = exprs.size(); i < len; i++) { + SQLExpr expr = exprs.get(i); + if (expr instanceof SQLNullExpr) { + row.add(Null.get()); + } else if (expr instanceof SQLValuableExpr) { + row.add(((SQLValuableExpr) expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + row.add(((SQLVariantRefExpr) expr).getName()); + } else if (expr instanceof SQLMethodInvokeExpr) { + row.add(SqlMethodExpr.get()); + } else if (expr instanceof SQLSequenceExpr) { + SQLSequenceExpr sequenceExpr = (SQLSequenceExpr) expr; + String sequence = sequenceExpr.getSequence().getSimpleName(); + String function = sequenceExpr.getFunction().name; + row.add(new SqlSequenceExpr(sequence, function)); + } else { + if (primaryKeyIndex.contains(i)) { + wrapSQLParsingException(expr); + } + row.add(NotPlaceholderExpr.get()); + } + } + } + return rows; + } + + @Override + public List getInsertParamsValue() { + return null; + } + + @Override + public List getDuplicateKeyUpdate() { + return null; + } + + @Override + public List getInsertColumnsUnEscape() { + List insertColumns = getInsertColumns(); + return ColumnUtils.delEscape(insertColumns, getDbType()); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java new file mode 100644 index 00000000000..19df00ae405 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java @@ -0,0 +1,39 @@ +package org.apache.seata.sqlparser.druid.oceanbase; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The class OceanBaseOperateRecognizerHolder + * + */ +@LoadLevel(name = JdbcConstants.OCEANBASE) +public class OceanBaseOperateRecognizerHolder implements SQLOperateRecognizerHolder { + + @Override + public SQLRecognizer getDeleteRecognizer(String sql, SQLStatement ast) { + return new OceanBaseDeleteRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getInsertRecognizer(String sql, SQLStatement ast) { + return new OceanBaseInsertRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getUpdateRecognizer(String sql, SQLStatement ast) { + return new OceanBaseUpdateRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getSelectForUpdateRecognizer(String sql, SQLStatement ast) { + if (((SQLSelectStatement) ast).getSelect().getFirstQueryBlock().isForUpdate()) { + return new OceanBaseSelectForUpdateRecognizer(sql, ast); + } + return null; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizer.java new file mode 100644 index 00000000000..934d930b83f --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizer.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; + +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLSelectRecognizer; +import org.apache.seata.sqlparser.SQLType; + +/** + * The type oceanbase select for update recognizer. + * + */ +public class OceanBaseSelectForUpdateRecognizer extends BaseOceanBaseRecognizer implements SQLSelectRecognizer { + + private final SQLSelectStatement ast; + + /** + * Instantiates a new My sql select for update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OceanBaseSelectForUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLSelectStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.SELECT_FOR_UPDATE; + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oceanbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oracle does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy); + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy, parametersHolder, paramAppenderList); + } + + private SQLSelectQueryBlock getSelect() { + SQLSelect select = ast.getSelect(); + if (select == null) { + throw new SQLParsingException("should never happen!"); + } + SQLSelectQueryBlock selectQueryBlock = select.getFirstQueryBlock(); + if (selectQueryBlock == null) { + throw new SQLParsingException("should never happen!"); + } + return selectQueryBlock; + } + + @Override + public String getTableAlias() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + return tableSource.getAlias(); + } + + @Override + public String getTableName() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit((SQLExprTableSource)tableSource); + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java new file mode 100644 index 00000000000..81419ac5aec --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.SQLUpdateRecognizer; +import org.apache.seata.sqlparser.druid.oracle.BaseOracleRecognizer; +import org.apache.seata.sqlparser.util.ColumnUtils; + +/** + * The type oceanbase update recognizer. + * + */ +public class OceanBaseUpdateRecognizer extends BaseOceanBaseRecognizer implements SQLUpdateRecognizer { + + private SQLUpdateStatement ast; + + /** + * Instantiates a new My sql update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OceanBaseUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLUpdateStatement) ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.UPDATE; + } + + @Override + public List getUpdateColumns() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getColumn(); + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else if (expr instanceof SQLPropertyExpr) { + // This is alias case, like UPDATE xxx_tbl a SET a.name = ? WHERE a.id = ? + SQLExpr owner = ((SQLPropertyExpr)expr).getOwner(); + if (owner instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)owner).getName() + "." + ((SQLPropertyExpr)expr).getName()); + //This is table Field Full path, like update xxx_database.xxx_tbl set xxx_database.xxx_tbl.xxx_field... + } else if (((SQLPropertyExpr) expr).getOwnerName().split("\\.").length > 1) { + list.add(((SQLPropertyExpr)expr).getOwnerName() + "." + ((SQLPropertyExpr)expr).getName()); + } + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateValues() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getValue(); + if (expr instanceof SQLValuableExpr) { + list.add(((SQLValuableExpr)expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + list.add(new VMarker()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateColumnsUnEscape() { + List updateColumns = getUpdateColumns(); + return ColumnUtils.delEscape(updateColumns, getDbType()); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oceanbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oceanbase does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //oceanbase does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oceanbase does not support order by yet + return null; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of update with join table"); + } + }; + SQLTableSource tableSource = ast.getTableSource(); + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of update with unknow"); + } + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder index 4d33c59879f..a434bd3f1e4 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder +++ b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder @@ -15,6 +15,7 @@ # limitations under the License. # org.apache.seata.sqlparser.druid.mysql.MySQLOperateRecognizerHolder +org.apache.seata.sqlparser.druid.oceanbase.OceanBaseOperateRecognizerHolder org.apache.seata.sqlparser.druid.mariadb.MariadbOperateRecognizerHolder org.apache.seata.sqlparser.druid.oracle.OracleOperateRecognizerHolder org.apache.seata.sqlparser.druid.postgresql.PostgresqlOperateRecognizerHolder From ae0ee3fb7d1ce95b21a04925a7fa204c9c3a4c9a Mon Sep 17 00:00:00 2001 From: "ranyu.zyh" Date: Fri, 11 Apr 2025 17:52:54 +0800 Subject: [PATCH 2/4] Empty-Commit From d671483dcbe819aa9067a79dd1aa644d0c94382c Mon Sep 17 00:00:00 2001 From: "ranyu.zyh" Date: Mon, 14 Apr 2025 09:42:32 +0800 Subject: [PATCH 3/4] feature:add oceanbase oracle supprot --- .../oceanbase/BaseOceanBaseRecognizer.java | 16 ++ .../OceanBaseOperateRecognizerHolder.java | 16 ++ .../OceanBaseDeleteRecognizerTest.java | 196 ++++++++++++++++++ .../OceanBaseInsertRecognizerTest.java | 128 ++++++++++++ ...ceanBaseSelectForUpdateRecognizerTest.java | 109 ++++++++++ .../OceanBaseUpdateRecognizerTest.java | 155 ++++++++++++++ 6 files changed, 620 insertions(+) create mode 100644 sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizerTest.java create mode 100644 sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizerTest.java create mode 100644 sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizerTest.java create mode 100644 sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizerTest.java diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java index 193e192043f..09268bccdb4 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/BaseOceanBaseRecognizer.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; import java.util.ArrayList; diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java index 19df00ae405..195d5aee38e 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseOperateRecognizerHolder.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; import com.alibaba.druid.sql.ast.SQLStatement; diff --git a/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizerTest.java b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizerTest.java new file mode 100644 index 00000000000..21a5ead8db3 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizerTest.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleArgumentExpr; +import org.apache.seata.sqlparser.druid.oceanbase.OceanBaseDeleteRecognizer; + +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OceanBaseDeleteRecognizerTest { + + private static final String DB_TYPE = "oceanbase"; + + @Test + public void testGetSqlType() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseDeleteRecognizer recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.DELETE); + } + + @Test + public void testGetTableAlias() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseDeleteRecognizer recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseDeleteRecognizer recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseDeleteRecognizer recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for normal sql + Assertions.assertEquals("id = ?", whereCondition); + + sql = "delete from t where id in (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for sql with in + Assertions.assertEquals("id IN (?)", whereCondition); + + sql = "delete from t where id between ? and ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + ArrayList idParam2 = new ArrayList<>(); + idParam.add(2); + Map result = new HashMap(); + result.put(1, idParam); + result.put(2, idParam2); + return result; + } + }, new ArrayList<>()); + //test for sql with in + Assertions.assertEquals("id BETWEEN ? AND ?", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OceanBaseDeleteRecognizer(s, deleteAst).getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return new HashMap<>(); + } + }, new ArrayList<>()); + }); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseDeleteRecognizer recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = 1"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for normal sql + Assertions.assertEquals("id = 1", whereCondition); + + sql = "delete from t where id in (1)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for sql with in + Assertions.assertEquals("id IN (1)", whereCondition); + + sql = "delete from t where id between 1 and 2"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + //test for sql with in + Assertions.assertEquals("id BETWEEN 1 AND 2", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (1)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OceanBaseDeleteRecognizer(s, deleteAst).getWhereCondition(); + }); + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizerTest.java b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizerTest.java new file mode 100644 index 00000000000..562210365ad --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizerTest.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.Collections; +import java.util.List; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryDoubleExpr; +import org.apache.seata.sqlparser.druid.oceanbase.OceanBaseInsertRecognizer; + +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OceanBaseInsertRecognizerTest { + + private static final String DB_TYPE = "oceanbase"; + + @Test + public void testGetSqlType() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.INSERT); + } + + @Test + public void testGetTableAlias() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetInsertColumns() { + + //test for no column + String sql = "insert into t values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + List insertColumns = recognizer.getInsertColumns(); + Assertions.assertNull(insertColumns); + + //test for normal + sql = "insert into t(a) values (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + insertColumns = recognizer.getInsertColumns(); + Assertions.assertEquals(1, insertColumns.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getColumns().add(new OracleBinaryDoubleExpr()); + + OceanBaseInsertRecognizer OceanBaseInsertRecognizer = new OceanBaseInsertRecognizer(s, sqlInsertStatement); + OceanBaseInsertRecognizer.getInsertColumns(); + }); + } + + @Test + public void testGetInsertRows() { + final int pkIndex = 0; + //test for null value + String sql = "insert into t(id, no, name, age, time) values (id_seq.nextval, null, 'a', ?, now())"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer recognizer = new OceanBaseInsertRecognizer(sql, asts.get(0)); + List> insertRows = recognizer.getInsertRows(Collections.singletonList(pkIndex)); + Assertions.assertEquals(1, insertRows.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getValuesList().get(0).getValues().set(pkIndex, new OracleBinaryDoubleExpr()); + + OceanBaseInsertRecognizer OceanBaseInsertRecognizer = new OceanBaseInsertRecognizer(s, sqlInsertStatement); + OceanBaseInsertRecognizer.getInsertRows(Collections.singletonList(pkIndex)); + }); + } + + @Test + public void testNotPlaceholder_giveValidPkIndex() { + String sql = "insert into test(create_time) values(sysdate)"; + List sqlStatements = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseInsertRecognizer oracle = new OceanBaseInsertRecognizer(sql, sqlStatements.get(0)); + List> insertRows = oracle.getInsertRows(Collections.singletonList(-1)); + Assertions.assertTrue(insertRows.get(0).get(0) instanceof NotPlaceholderExpr); + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizerTest.java b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizerTest.java new file mode 100644 index 00000000000..58da56f4645 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseSelectForUpdateRecognizerTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.sqlparser.druid.oceanbase.OceanBaseSelectForUpdateRecognizer; + +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OceanBaseSelectForUpdateRecognizerTest { + + private static final String DB_TYPE = "oceanbase"; + + @Test + public void testGetSqlType() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseSelectForUpdateRecognizer recognizer = new OceanBaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.SELECT_FOR_UPDATE); + } + + + @Test + public void testGetWhereCondition_0() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseSelectForUpdateRecognizer recognizer = new OceanBaseSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseSelectForUpdateRecognizer recognizer = new OceanBaseSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + + //test for select was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t for update"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.setSelect(null); + new OceanBaseSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + + //test for query was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.getSelect().setQuery(null); + new OceanBaseSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + } + + @Test + public void testGetTableAlias() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseSelectForUpdateRecognizer recognizer = new OceanBaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseSelectForUpdateRecognizer recognizer = new OceanBaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizerTest.java b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizerTest.java new file mode 100644 index 00000000000..d024c1ff241 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/test/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizerTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.seata.sqlparser.druid.oceanbase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr; +import org.apache.seata.sqlparser.druid.oceanbase.OceanBaseUpdateRecognizer; + +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OceanBaseUpdateRecognizerTest { + + private static final String DB_TYPE = "oceanbase"; + + @Test + public void testGetSqlType() { + String sql = "update t set n = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.UPDATE); + } + + @Test + public void testGetUpdateColumns() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + List updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + // test with alias + sql = "update t set a.a = ?, a.b = ?, a.c = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + //test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = a"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setColumn(new OracleCursorExpr()); + } + OceanBaseUpdateRecognizer OceanBaseUpdateRecognizer = new OceanBaseUpdateRecognizer(s, sqlUpdateStatement); + OceanBaseUpdateRecognizer.getUpdateColumns(); + }); + } + + @Test + public void testGetUpdateValues() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + List updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with values + sql = "update t set a = 1, b = 2, c = 3"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = ?"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement)sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setValue(new OracleCursorExpr()); + } + OceanBaseUpdateRecognizer OceanBaseUpdateRecognizer = new OceanBaseUpdateRecognizer(s, sqlUpdateStatement); + OceanBaseUpdateRecognizer.getUpdateValues(); + }); + } + + @Test + public void testGetWhereCondition_0() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OceanBaseUpdateRecognizer recognizer = new OceanBaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} From dd31ae6305ad28410855a07e2088b3645758e7c5 Mon Sep 17 00:00:00 2001 From: "ranyu.zyh" Date: Mon, 14 Apr 2025 10:05:30 +0800 Subject: [PATCH 4/4] feature:add oceanbase oracle supprot --- .../sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java | 2 -- .../sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java | 1 - .../sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java | 2 -- 3 files changed, 5 deletions(-) diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java index 7bf494219d8..e7b9330942f 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseDeleteRecognizer.java @@ -25,14 +25,12 @@ import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLTableSource; -import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleDeleteStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; import org.apache.seata.common.exception.NotSupportYetException; import org.apache.seata.sqlparser.ParametersHolder; import org.apache.seata.sqlparser.SQLDeleteRecognizer; import org.apache.seata.sqlparser.SQLType; -import org.apache.seata.sqlparser.druid.oracle.BaseOracleRecognizer; /** * The type oceanbase delete recognizer. diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java index dc85abfa1d7..5c0212182a0 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseInsertRecognizer.java @@ -30,7 +30,6 @@ import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; -import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleInsertStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; import org.apache.seata.common.util.CollectionUtils; diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java index 81419ac5aec..f590dbbbcd7 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oceanbase/OceanBaseUpdateRecognizer.java @@ -30,14 +30,12 @@ import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; -import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; import org.apache.seata.common.exception.NotSupportYetException; import org.apache.seata.sqlparser.ParametersHolder; import org.apache.seata.sqlparser.SQLType; import org.apache.seata.sqlparser.SQLUpdateRecognizer; -import org.apache.seata.sqlparser.druid.oracle.BaseOracleRecognizer; import org.apache.seata.sqlparser.util.ColumnUtils; /**