This is an automated email from the ASF dual-hosted git repository. suxiaogang223 pushed a commit to branch feature/paimon-jni-write-v1 in repository https://gitbox.apache.org/repos/asf/doris.git
commit 7a303dbf9c3a0ea244cdd8c4cc4be355f3b899a5 Author: Socrates <[email protected]> AuthorDate: Wed Jul 8 15:37:50 2026 +0800 [feature](paimon) Step 6: FE Transaction commit coordinator Add Paimon transaction support: - PaimonTransaction: receives TPaimonCommitMessage from BE, deserializes DPCM payloads, executes StreamTableCommit.filterAndCommit() for idempotent commit, supports rollback via committer.abort() - PaimonTransactionManager + PaimonInsertExecutor: plug into the existing transaction framework (same pattern as Iceberg) - Wire paimon_commit_messages in Coordinator and LoadProcessor - Wire PaimonInsertExecutor in InsertIntoTableCommand - Add PAIMON to TransactionType enum Co-Authored-By: Claude <[email protected]> --- .../doris/datasource/paimon/PaimonTransaction.java | 318 +++++++++++++++++++++ .../commands/insert/InsertIntoTableCommand.java | 15 + .../insert/PaimonInsertCommandContext.java} | 12 +- .../commands/insert/PaimonInsertExecutor.java | 63 ++++ .../main/java/org/apache/doris/qe/Coordinator.java | 6 + .../org/apache/doris/qe/runtime/LoadProcessor.java | 6 + ...tionType.java => PaimonTransactionManager.java} | 22 +- .../transaction/TransactionManagerFactory.java | 5 + .../apache/doris/transaction/TransactionType.java | 3 +- 9 files changed, 436 insertions(+), 14 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonTransaction.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonTransaction.java new file mode 100644 index 00000000000..51f13f93008 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonTransaction.java @@ -0,0 +1,318 @@ +// 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.doris.datasource.paimon; + +import org.apache.doris.common.UserException; +import org.apache.doris.common.security.authentication.ExecutionAuthenticator; +import org.apache.doris.datasource.mvcc.MvccUtil; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertCommandContext; +import org.apache.doris.thrift.TPaimonCommitMessage; +import org.apache.doris.transaction.Transaction; + +import com.google.common.collect.Lists; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.paimon.io.DataInputDeserializer; +import org.apache.paimon.table.InnerTable; +import org.apache.paimon.table.sink.CommitMessage; +import org.apache.paimon.table.sink.CommitMessageSerializer; +import org.apache.paimon.table.sink.StreamTableCommit; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Paimon transaction. + * + * Lifecycle: + * 1. beginInsert() — record target table + * 2. updateCommitMessages() — called multiple times as BE reports commit data + * 3. commit() — deserialize all CommitMessages, call StreamTableCommit.filterAndCommit() + * 4. rollback() — abort uncommitted data files + * + * CommitMessage wire format: + * BE ← JNI ← Java: byte[] with DPCM header (magic + version + length) + Paimon + * CommitMessageSerializer payload + */ +public class PaimonTransaction implements Transaction { + private static final Logger LOG = LogManager.getLogger(PaimonTransaction.class); + + private static final int COMMIT_HEADER_SIZE = 12; + private static final byte[] COMMIT_MAGIC = new byte[] {'D', 'P', 'C', 'M'}; + + private final PaimonMetadataOps ops; + private PaimonExternalTable table; + private long transactionId = -1L; + private String commitUser = ""; + private boolean committed = false; + + private final List<byte[]> commitPayloads = Lists.newArrayList(); + private final Set<String> commitPayloadSet = new HashSet<>(); + + public PaimonTransaction(PaimonMetadataOps ops) { + this.ops = ops; + } + + // ──────────────────────────────────────────────────────────── + // Transaction lifecycle + // ──────────────────────────────────────────────────────────── + + public void beginInsert(PaimonExternalTable table, Optional<InsertCommandContext> insertCtx) { + this.table = table; + } + + public void finishInsert(PaimonExternalTable table, Optional<InsertCommandContext> insertCtx) { + } + + @Override + public void commit() throws UserException { + List<byte[]> rawPayloads = snapshotPayloads(); + if (rawPayloads.isEmpty()) { + LOG.info("Skip empty PaimonTransaction commit, txnId={}, table={}", + transactionId, tableName()); + return; + } + if (table == null) { + throw new UserException("Missing paimon table for transaction"); + } + if (transactionId <= 0) { + throw new UserException("Missing transaction id for paimon commit"); + } + + try { + List<CommitMessage> allMessages = deserializePayloads(rawPayloads); + LOG.info("Commit PaimonTransaction, txnId={}, table={}, payloads={}, messages={}", + transactionId, tableName(), rawPayloads.size(), allMessages.size()); + if (allMessages.isEmpty()) { + throw new RuntimeException( + "Paimon commit messages are empty, payloads=" + rawPayloads.size()); + } + doCommit(allMessages); + synchronized (this) { + committed = true; + } + } catch (Exception e) { + throw new UserException("Failed to commit paimon transaction on FE", e); + } + } + + @Override + public void rollback() { + if (isCommitted()) { + LOG.info("Skip rollback for committed PaimonTransaction, txnId={}, table={}", + transactionId, tableName()); + return; + } + List<byte[]> rawPayloads = snapshotPayloads(); + if (rawPayloads.isEmpty()) { + LOG.info("Skip empty PaimonTransaction rollback, txnId={}, table={}", + transactionId, tableName()); + return; + } + if (table == null) { + LOG.warn("Skip PaimonTransaction rollback, table missing, txnId={}", transactionId); + return; + } + try { + List<CommitMessage> allMessages = deserializePayloads(rawPayloads); + if (allMessages.isEmpty()) { + LOG.info("Skip PaimonTransaction rollback with empty decoded messages, " + + "txnId={}, table={}", transactionId, tableName()); + return; + } + LOG.info("Rollback PaimonTransaction, txnId={}, table={}, payloads={}, messages={}", + transactionId, tableName(), rawPayloads.size(), allMessages.size()); + doAbort(allMessages); + } catch (Exception e) { + LOG.warn("Failed to rollback PaimonTransaction, txnId={}, table={}", + transactionId, tableName(), e); + } + } + + // ──────────────────────────────────────────────────────────── + // CommitMessage collection (called from Coordinator) + // ──────────────────────────────────────────────────────────── + + public void updateCommitMessages(List<TPaimonCommitMessage> messages) { + if (messages == null || messages.isEmpty()) { + return; + } + synchronized (this) { + for (TPaimonCommitMessage msg : messages) { + addPayload(msg); + } + } + } + + private void addPayload(TPaimonCommitMessage message) { + if (message == null || !message.isSetPayload()) { + return; + } + byte[] payload = message.getPayload(); + if (payload == null || payload.length == 0) { + return; + } + String key = Base64.getEncoder().encodeToString(payload); + if (commitPayloadSet.add(key)) { + commitPayloads.add(Arrays.copyOf(payload, payload.length)); + } + } + + // ──────────────────────────────────────────────────────────── + // Setters + // ──────────────────────────────────────────────────────────── + + public void setTransactionId(long transactionId) { + this.transactionId = transactionId; + this.commitUser = "doris_txn_" + transactionId; + } + + public String getCommitUser() { + return commitUser; + } + + public long getTransactionId() { + return transactionId; + } + + boolean isCommitted() { + synchronized (this) { + return committed; + } + } + + int getPayloadCount() { + synchronized (this) { + return commitPayloads.size(); + } + } + + // ──────────────────────────────────────────────────────────── + // Internal commit logic + // ──────────────────────────────────────────────────────────── + + private void doCommit(List<CommitMessage> msgs) throws Exception { + ops.getCatalog(); + ExecutionAuthenticator authenticator = ops.dorisCatalog.getExecutionAuthenticator(); + authenticator.execute(() -> { + InnerTable paimonTable = getInnerTable(); + StreamTableCommit committer = paimonTable.newCommit(commitUser); + try { + Map<Long, List<CommitMessage>> commitMap = new HashMap<>(); + commitMap.put(transactionId, msgs); + committer.filterAndCommit(commitMap); + return null; + } finally { + committer.close(); + } + }); + } + + private void doAbort(List<CommitMessage> msgs) throws Exception { + ExecutionAuthenticator authenticator = ops.dorisCatalog.getExecutionAuthenticator(); + authenticator.execute(() -> { + InnerTable paimonTable = getInnerTable(); + StreamTableCommit committer = paimonTable.newCommit(commitUser); + try { + committer.abort(msgs); + return null; + } finally { + committer.close(); + } + }); + } + + private InnerTable getInnerTable() { + org.apache.paimon.table.Table paimonTable = + table.getPaimonTable(MvccUtil.getSnapshotFromContext(table)); + if (!(paimonTable instanceof InnerTable)) { + throw new RuntimeException( + "Paimon table does not support commit: " + paimonTable.getClass()); + } + return (InnerTable) paimonTable; + } + + // ──────────────────────────────────────────────────────────── + // Serialization helpers + // ──────────────────────────────────────────────────────────── + + private List<byte[]> snapshotPayloads() { + synchronized (this) { + List<byte[]> copy = new ArrayList<>(commitPayloads.size()); + for (byte[] p : commitPayloads) { + copy.add(Arrays.copyOf(p, p.length)); + } + return copy; + } + } + + static List<CommitMessage> deserializePayloads(List<byte[]> payloads) throws IOException { + List<CommitMessage> all = new ArrayList<>(); + for (byte[] payload : payloads) { + all.addAll(deserializePayload(payload)); + } + return all; + } + + static List<CommitMessage> deserializePayload(byte[] payload) throws IOException { + if (payload == null || payload.length < COMMIT_HEADER_SIZE || !hasMagic(payload)) { + throw new IOException("Invalid paimon commit message payload header"); + } + int version = readInt(payload, 4); + int len = readInt(payload, 8); + if (len < 0 || payload.length != COMMIT_HEADER_SIZE + len) { + throw new IOException("Invalid paimon commit message payload length"); + } + byte[] raw = new byte[len]; + System.arraycopy(payload, COMMIT_HEADER_SIZE, raw, 0, len); + List<CommitMessage> messages = + new CommitMessageSerializer().deserializeList(version, new DataInputDeserializer(raw)); + if (messages == null) { + throw new IOException("Paimon commit message payload deserialized to null"); + } + return messages; + } + + private static boolean hasMagic(byte[] payload) { + for (int i = 0; i < COMMIT_MAGIC.length; i++) { + if (payload[i] != COMMIT_MAGIC[i]) { + return false; + } + } + return true; + } + + private static int readInt(byte[] payload, int offset) { + return ((payload[offset] & 0xFF) << 24) + | ((payload[offset + 1] & 0xFF) << 16) + | ((payload[offset + 2] & 0xFF) << 8) + | (payload[offset + 3] & 0xFF); + } + + private String tableName() { + return table == null ? "null" : table.getName(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java index 1372cce62e2..d64dc53b59c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java @@ -37,6 +37,7 @@ import org.apache.doris.datasource.doris.RemoteOlapTable; import org.apache.doris.datasource.hive.HMSExternalTable; import org.apache.doris.datasource.iceberg.IcebergExternalTable; import org.apache.doris.datasource.maxcompute.MaxComputeExternalTable; +import org.apache.doris.datasource.paimon.PaimonExternalTable; import org.apache.doris.dictionary.Dictionary; import org.apache.doris.load.loadv2.LoadJob; import org.apache.doris.load.loadv2.LoadStatistic; @@ -74,6 +75,7 @@ import org.apache.doris.nereids.trees.plans.physical.PhysicalEmptyRelation; import org.apache.doris.nereids.trees.plans.physical.PhysicalHiveTableSink; import org.apache.doris.nereids.trees.plans.physical.PhysicalIcebergTableSink; import org.apache.doris.nereids.trees.plans.physical.PhysicalMaxComputeTableSink; +import org.apache.doris.nereids.trees.plans.physical.PhysicalPaimonTableSink; import org.apache.doris.nereids.trees.plans.physical.PhysicalOlapTableSink; import org.apache.doris.nereids.trees.plans.physical.PhysicalSink; import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; @@ -565,6 +567,19 @@ public class InsertIntoTableCommand extends Command implements NeedAuditEncrypti emptyInsert, jobId ) ); + } else if (physicalSink instanceof PhysicalPaimonTableSink) { + boolean emptyInsert = childIsEmptyRelation(physicalSink); + PaimonExternalTable paimonTable = (PaimonExternalTable) targetTableIf; + PaimonInsertCommandContext paimonCtx = insertCtx + .map(ctx1 -> (PaimonInsertCommandContext) ctx1) + .orElseGet(PaimonInsertCommandContext::new); + return ExecutorFactory.from( + planner, + dataSink, + physicalSink, + () -> new PaimonInsertExecutor(ctx, paimonTable, label, planner, + Optional.of(paimonCtx), emptyInsert, jobId) + ); } else if (physicalSink instanceof PhysicalConnectorTableSink) { boolean emptyInsert = childIsEmptyRelation(physicalSink); ExternalTable externalTable = (ExternalTable) targetTableIf; diff --git a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertCommandContext.java similarity index 79% copy from fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java copy to fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertCommandContext.java index 83e092c0ed7..f0bf011b793 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertCommandContext.java @@ -15,12 +15,10 @@ // specific language governing permissions and limitations // under the License. -package org.apache.doris.transaction; +package org.apache.doris.nereids.trees.plans.commands.insert; -public enum TransactionType { - UNKNOWN, - HMS, - ICEBERG, - JDBC, - MAXCOMPUTE +/** + * Insert command context for Paimon tables. + */ +public class PaimonInsertCommandContext extends BaseExternalTableInsertCommandContext { } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertExecutor.java new file mode 100644 index 00000000000..eb4ce2f3279 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/PaimonInsertExecutor.java @@ -0,0 +1,63 @@ +// 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.doris.nereids.trees.plans.commands.insert; + +import org.apache.doris.common.UserException; +import org.apache.doris.datasource.paimon.PaimonExternalTable; +import org.apache.doris.datasource.paimon.PaimonTransaction; +import org.apache.doris.nereids.NereidsPlanner; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.transaction.TransactionType; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +/** + * Insert executor for Paimon tables. + */ +public class PaimonInsertExecutor extends BaseExternalTableInsertExecutor { + private static final Logger LOG = LogManager.getLogger(PaimonInsertExecutor.class); + + public PaimonInsertExecutor(ConnectContext ctx, PaimonExternalTable table, + String labelName, NereidsPlanner planner, + Optional<InsertCommandContext> insertCtx, + boolean emptyInsert, long jobId) { + super(ctx, table, labelName, planner, insertCtx, emptyInsert, jobId); + } + + @Override + protected void beforeExec() throws UserException { + PaimonTransaction transaction = + (PaimonTransaction) transactionManager.getTransaction(txnId); + transaction.beginInsert((PaimonExternalTable) table, insertCtx); + } + + @Override + protected void doBeforeCommit() throws UserException { + PaimonTransaction transaction = + (PaimonTransaction) transactionManager.getTransaction(txnId); + transaction.finishInsert((PaimonExternalTable) table, insertCtx); + } + + @Override + protected TransactionType transactionType() { + return TransactionType.PAIMON; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java index 74e0128e8da..eaad250d2bd 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java @@ -40,6 +40,7 @@ import org.apache.doris.datasource.ExternalScanNode; import org.apache.doris.datasource.FileQueryScanNode; import org.apache.doris.datasource.hive.HMSTransaction; import org.apache.doris.datasource.iceberg.IcebergTransaction; +import org.apache.doris.datasource.paimon.PaimonTransaction; import org.apache.doris.datasource.maxcompute.MCTransaction; import org.apache.doris.load.loadv2.LoadJob; import org.apache.doris.metric.MetricRepo; @@ -2644,6 +2645,11 @@ public class Coordinator implements CoordInterface { ((MCTransaction) Env.getCurrentEnv().getGlobalExternalTransactionInfoMgr().getTxnById(txnId)) .updateMCCommitData(params.getMcCommitDatas()); } + if (params.isSetPaimonCommitMessages()) { + ((PaimonTransaction) Env.getCurrentEnv().getGlobalExternalTransactionInfoMgr() + .getTxnById(txnId)) + .updateCommitMessages(params.getPaimonCommitMessages()); + } if (ctx.done) { if (LOG.isDebugEnabled()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/runtime/LoadProcessor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/runtime/LoadProcessor.java index 38ba2ebbc79..d2297fa8d57 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/runtime/LoadProcessor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/runtime/LoadProcessor.java @@ -23,6 +23,7 @@ import org.apache.doris.common.Status; import org.apache.doris.common.util.DebugUtil; import org.apache.doris.datasource.hive.HMSTransaction; import org.apache.doris.datasource.iceberg.IcebergTransaction; +import org.apache.doris.datasource.paimon.PaimonTransaction; import org.apache.doris.datasource.maxcompute.MCTransaction; import org.apache.doris.nereids.util.Utils; import org.apache.doris.qe.AbstractJobProcessor; @@ -240,6 +241,11 @@ public class LoadProcessor extends AbstractJobProcessor { ((MCTransaction) Env.getCurrentEnv().getGlobalExternalTransactionInfoMgr().getTxnById(txnId)) .updateMCCommitData(params.getMcCommitDatas()); } + if (params.isSetPaimonCommitMessages()) { + ((PaimonTransaction) Env.getCurrentEnv().getGlobalExternalTransactionInfoMgr() + .getTxnById(txnId)) + .updateCommitMessages(params.getPaimonCommitMessages()); + } if (fragmentTask.isDone()) { if (LOG.isDebugEnabled()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java b/fe/fe-core/src/main/java/org/apache/doris/transaction/PaimonTransactionManager.java similarity index 62% copy from fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java copy to fe/fe-core/src/main/java/org/apache/doris/transaction/PaimonTransactionManager.java index 83e092c0ed7..e01dc674cc1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/transaction/PaimonTransactionManager.java @@ -17,10 +17,20 @@ package org.apache.doris.transaction; -public enum TransactionType { - UNKNOWN, - HMS, - ICEBERG, - JDBC, - MAXCOMPUTE +import org.apache.doris.datasource.paimon.PaimonMetadataOps; +import org.apache.doris.datasource.paimon.PaimonTransaction; + +/** + * Transaction manager for Paimon external tables. + */ +public class PaimonTransactionManager extends AbstractExternalTransactionManager<PaimonTransaction> { + + public PaimonTransactionManager(PaimonMetadataOps ops) { + super(ops); + } + + @Override + PaimonTransaction createTransaction() { + return new PaimonTransaction((PaimonMetadataOps) ops); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java index 9a5584a0601..1801b65b7f9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java +++ b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java @@ -20,6 +20,7 @@ package org.apache.doris.transaction; import org.apache.doris.datasource.hive.HiveMetadataOps; import org.apache.doris.datasource.iceberg.IcebergMetadataOps; import org.apache.doris.datasource.maxcompute.MaxComputeExternalCatalog; +import org.apache.doris.datasource.paimon.PaimonMetadataOps; import org.apache.doris.fs.SpiSwitchingFileSystem; import java.util.concurrent.Executor; @@ -38,4 +39,8 @@ public class TransactionManagerFactory { public static TransactionManager createMCTransactionManager(MaxComputeExternalCatalog catalog) { return new MCTransactionManager(catalog); } + + public static TransactionManager createPaimonTransactionManager(PaimonMetadataOps ops) { + return new PaimonTransactionManager(ops); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java index 83e092c0ed7..d6a587eaa2e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionType.java @@ -22,5 +22,6 @@ public enum TransactionType { HMS, ICEBERG, JDBC, - MAXCOMPUTE + MAXCOMPUTE, + PAIMON } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
