Skip to content
Open
1 change: 1 addition & 0 deletions changes/en-us/2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#7559](https://github.com/apache/incubator-seata/pull/7559)] Introduce Cleanup API for TableMetaRefreshHolder Instance
- [[#7669](https://github.com/apache/incubator-seata/pull/7669)] add support for Jackson serialization and deserialization of PostgreSQL array types
- [[#7664](https://github.com/apache/incubator-seata/pull/7664)] support shentongdatabase XA mode
- [[#7675](https://github.com/apache/incubator-seata/pull/7675)] support Oracle Batch Insert
- [[#7663](https://github.com/apache/incubator-seata/pull/7663)] add Java 25 support in CI configuration files


Expand Down
1 change: 1 addition & 0 deletions changes/zh-cn/2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- [[#7559](https://github.com/apache/incubator-seata/pull/7559)] 为 TableMetaRefreshHolder 实例引入清理 API
- [[#7669](https://github.com/apache/incubator-seata/pull/7669)] 添加对 Jackson 序列化和反序列化 PostgreSQL 数组类型的支持
- [[#7664](https://github.com/apache/incubator-seata/pull/7565)] 支持神通数据库的XA模式
- [[#7675](https://github.com/apache/incubator-seata/pull/7675)] 支持Oracle批量插入
- [[#7663](https://github.com/apache/incubator-seata/pull/7663)] 支持java25版本的CI流水綫


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleMultiInsertStatement;
import org.apache.seata.common.exception.NotSupportYetException;
import org.apache.seata.common.util.CollectionUtils;
import org.apache.seata.sqlparser.SQLRecognizer;
import org.apache.seata.sqlparser.SQLRecognizerFactory;
import org.apache.seata.sqlparser.druid.oracle.OracleOperateRecognizerHolder;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -72,6 +74,14 @@ public List<SQLRecognizer> create(String sql, String dbType) {
recognizer = recognizerHolder.getDeleteRecognizer(sql, sqlStatement);
} else if (sqlStatement instanceof SQLSelectStatement) {
recognizer = recognizerHolder.getSelectForUpdateRecognizer(sql, sqlStatement);
} else if (sqlStatement instanceof OracleMultiInsertStatement) {
OracleMultiInsertStatement stmt = (OracleMultiInsertStatement) sqlStatement;
if (stmt.getOption() == OracleMultiInsertStatement.Option.FIRST) {
throw new NotSupportYetException("INSERT FIRST not supported yet");
}
// Use specialized methods to handle Oracle bulk inserts
recognizer =
((OracleOperateRecognizerHolder) recognizerHolder).getMultiInsertRecognizer(sql, sqlStatement);
}

// When recognizer is null, it indicates that recognizerHolder cannot allocate unsupported syntax, like
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, only the insert all syntax of Oracle Batch Insert and the same column situation is supported. The same effect can be achieved by adding multiple values ​​clauses to a normal insert. Are there plans to support more in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we only support batch insert of multiple columns (same columns) in a single table. We will continue to support this in the future.

Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*
* 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.oracle;

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.OracleMultiInsertStatement;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor;
import org.apache.seata.common.exception.NotSupportYetException;
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;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* Oracle Multi Insert Recognizer for INSERT ALL statements
*/
public class OracleMultiInsertRecognizer extends BaseOracleRecognizer implements SQLInsertRecognizer {

private final OracleMultiInsertStatement ast;
private String validatedTableName;
private List<String> validatedColumns;

public OracleMultiInsertRecognizer(String originalSQL, SQLStatement ast) {
super(originalSQL);
this.ast = (OracleMultiInsertStatement) ast;
validateSingleTableConsistency();
}

/**
* Verify that all entries are in the same table and have consistent column definitions.
*/
private void validateSingleTableConsistency() {
if (CollectionUtils.isEmpty(ast.getEntries())) {
return;
}

String firstTableName = null;
List<String> firstColumns = null;

for (OracleMultiInsertStatement.Entry entry : ast.getEntries()) {
// Check whether conditional insertion is included. It is not supported yet.
if (entry instanceof OracleMultiInsertStatement.ConditionalInsertClause) {
throw new NotSupportYetException(
"Oracle Multi Insert with conditional clauses (WHEN...THEN) is not supported yet. " + "SQL: "
+ getOriginalSQL());
}
if (entry instanceof OracleMultiInsertStatement.InsertIntoClause) {
OracleMultiInsertStatement.InsertIntoClause insertClause =
(OracleMultiInsertStatement.InsertIntoClause) entry;

// Get the current table name
String currentTableName = getTableNameFromClause(insertClause);
if (currentTableName == null) {
continue;
}

// Get current column information
List<String> currentColumns = getColumnsFromClause(insertClause);

if (firstTableName == null) {
firstTableName = currentTableName;
firstColumns = currentColumns;
} else {
// Check whether the table names are consistent
if (!firstTableName.equalsIgnoreCase(currentTableName)) {
throw new NotSupportYetException(
"Oracle Multi Insert with different tables is not supported yet. " + "Found tables: "
+ firstTableName + " and " + currentTableName + ". SQL: "
+ getOriginalSQL());
}

// Check that column definitions are consistent
if (!Objects.equals(firstColumns, currentColumns)) {
throw new NotSupportYetException(
"Oracle Multi Insert with different column definitions is not supported yet. "
+ "Table: " + firstTableName + ". SQL: " + getOriginalSQL());
}
}
}
}

this.validatedTableName = firstTableName;
this.validatedColumns = firstColumns;
}

private String getTableNameFromClause(OracleMultiInsertStatement.InsertIntoClause insertClause) {
if (insertClause.getTableSource() != null) {
StringBuilder sb = new StringBuilder();
OracleOutputVisitor visitor = new OracleOutputVisitor(sb) {
@Override
public boolean visit(SQLExprTableSource x) {
printTableSourceExpr(x.getExpr());
return false;
}
};
visitor.visit(insertClause.getTableSource());
return sb.toString();
}
return null;
}

private List<String> getColumnsFromClause(OracleMultiInsertStatement.InsertIntoClause insertClause) {
if (CollectionUtils.isEmpty(insertClause.getColumns())) {
return Collections.emptyList();
}

List<String> columns = new ArrayList<>();
for (SQLExpr expr : insertClause.getColumns()) {
if (expr instanceof SQLIdentifierExpr) {
columns.add(((SQLIdentifierExpr) expr).getName());
} else {
// Handling non-standard column names
wrapSQLParsingException(expr);
}
}
return columns;
}

@Override
public SQLType getSQLType() {
return SQLType.INSERT;
}

@Override
public String getTableName() {
return validatedTableName;
}

@Override
public String getTableAlias() {
if (!CollectionUtils.isEmpty(ast.getEntries())) {
OracleMultiInsertStatement.Entry firstEntry = ast.getEntries().get(0);
if (firstEntry instanceof OracleMultiInsertStatement.InsertIntoClause) {
OracleMultiInsertStatement.InsertIntoClause insertClause =
(OracleMultiInsertStatement.InsertIntoClause) firstEntry;
if (insertClause.getTableSource() != null) {
return insertClause.getTableSource().getAlias();
}
}
}
return null;
}

@Override
public List<String> getInsertColumns() {
return validatedColumns;
}

@Override
public boolean insertColumnsIsEmpty() {
return CollectionUtils.isEmpty(validatedColumns);
}

@Override
public List<List<Object>> getInsertRows(Collection<Integer> primaryKeyIndex) {
List<List<Object>> allRows = new ArrayList<>();

if (!CollectionUtils.isEmpty(ast.getEntries())) {
for (OracleMultiInsertStatement.Entry entry : ast.getEntries()) {
if (entry instanceof OracleMultiInsertStatement.InsertIntoClause) {
OracleMultiInsertStatement.InsertIntoClause insertClause =
(OracleMultiInsertStatement.InsertIntoClause) entry;

if (!CollectionUtils.isEmpty(insertClause.getValuesList())) {
for (SQLInsertStatement.ValuesClause valuesClause : insertClause.getValuesList()) {
List<SQLExpr> exprs = valuesClause.getValues();
List<Object> row = new ArrayList<>(exprs.size());
allRows.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 allRows;
}

@Override
public List<String> getInsertParamsValue() {
return null;
}

@Override
public List<String> getDuplicateKeyUpdate() {
return null;
}

@Override
public List<String> getInsertColumnsUnEscape() {
List<String> insertColumns = getInsertColumns();
return ColumnUtils.delEscape(insertColumns, getDbType());
}

@Override
protected SQLStatement getAst() {
return ast;
}

@Override
public boolean isSqlSyntaxSupports() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ public SQLRecognizer getSelectForUpdateRecognizer(String sql, SQLStatement ast)
}
return null;
}

public SQLRecognizer getMultiInsertRecognizer(String sql, SQLStatement ast) {
return new OracleMultiInsertRecognizer(sql, ast);
}
}
Loading
Loading