=== modified file 'src/main/org/h2/command/dml/Delete.java'
--- old/src/main/org/h2/command/dml/Delete.java	2025-12-12 20:57:59 +0000
+++ new/src/main/org/h2/command/dml/Delete.java	2026-08-17 12:59:53 +0000
@@ -93,7 +93,11 @@
     private boolean canFastDelete(int limitRows)
     {
        Table table = targetTableFilter.getTable();
+
+       // a table-wide fast delete would also bypass the undo log of any multiplex which overrides
+       // the no-undo option to be undoable, so it cannot be used once overrides are in play
        return table.isNoUndo() &&
+              !table.hasMultiplexNoUndo() &&
               condition == null &&
               (limitRows == -1 || limitRows >= table.getRowCount(session)) &&
               !table.hasTriggers() &&

=== modified file 'src/main/org/h2/embedded/DirectAccessDriver.java'
--- old/src/main/org/h2/embedded/DirectAccessDriver.java	2025-07-21 13:05:34 +0000
+++ new/src/main/org/h2/embedded/DirectAccessDriver.java	2026-08-18 07:53:59 +0000
@@ -89,6 +89,28 @@
    throws SQLException;
    
    /**
+    * Set the no-undo option of a single multiplex, overriding the option the table was created
+    * with.
+    * <p>
+    * A multiplexed table backs several logically independent tables, which need not agree on the
+    * no-undo option, so it cannot be a property of the SQL table alone.  Call this once, when the
+    * multiplex is opened and before it holds any rows;  rows written under the previous setting
+    * keep the undo records they were given.
+    *
+    * @param   tableName
+    *          The name of the table which holds the multiplex.
+    * @param   multiplex
+    *          The multiplex to set the option for.
+    * @param   noUndo
+    *          {@code true} if changes to this multiplex must not be undoable.
+    *
+    * @throws  SQLException
+    *          if the table does not exist.
+    */
+   public void setMultiplexNoUndo(String tableName, int multiplex, boolean noUndo)
+   throws SQLException;
+
+   /**
     * Retrieve a single row from a table by its recid. By convention, all H2 tables
     * targeted by this method have a primary key on a single integer column representing
     * the recid. Also, the recid should always be the first column of the table.

=== modified file 'src/main/org/h2/embedded/FWDDirectAccessDriver.java'
--- old/src/main/org/h2/embedded/FWDDirectAccessDriver.java	2025-12-11 22:22:21 +0000
+++ new/src/main/org/h2/embedded/FWDDirectAccessDriver.java	2026-08-18 07:54:09 +0000
@@ -211,8 +211,30 @@
       
       ((FWDMultiplexedIndex) idx).killMultiplex(session, multiplex);
       session.cleanUndoLog(t, multiplex);
+
+      // the multiplex is gone, so its no-undo override must not outlive it
+      t.clearMultiplexNoUndo(multiplex);
    }
    
+   @Override
+   public void setMultiplexNoUndo(String tableName, int multiplex, boolean noUndo)
+   throws SQLException
+   {
+      if (tableName == null)
+      {
+         throw new SQLException("Invalid input: null values");
+      }
+
+      Session session = getSession();
+      Table t = session.findLocalTempTable(tableName.toUpperCase());
+      if (t == null)
+      {
+         throw new SQLException("Table not found: " + tableName);
+      }
+
+      t.setMultiplexNoUndo(multiplex, noUndo);
+   }
+
    /**
     * Retrieve the number of rows that are currently in a specified table.
     * 

=== modified file 'src/main/org/h2/engine/Session.java'
--- old/src/main/org/h2/engine/Session.java	2025-12-11 22:22:21 +0000
+++ new/src/main/org/h2/engine/Session.java	2026-08-17 12:59:07 +0000
@@ -1016,7 +1016,8 @@
         if (table.isMVStore()) {
             return;
         }
-        if (table.isNoUndo()) {
+        // the no-undo option can be overridden per multiplex, so it is resolved from the row
+        if (table.isNoUndo(row)) {
            return;
         }
         if (undoLogEnabled) {

=== modified file 'src/main/org/h2/engine/UndoLog.java'
--- old/src/main/org/h2/engine/UndoLog.java	2025-12-16 10:59:41 +0000
+++ new/src/main/org/h2/engine/UndoLog.java	2026-08-17 12:59:31 +0000
@@ -21,7 +21,7 @@
  */
 public class UndoLog {
     /** The temp-tables always have the _MULTIPLEX on offset 1, after the PK (at offset 0). */
-    private static final int MULTIPLEX_OFFSET = 1; 
+    private static final int MULTIPLEX_OFFSET = Table.MULTIPLEX_COLUMN;
    
     private final Database database;
     private final ArrayList<Long> storedEntriesPos = Utils.newSmallArrayList();
@@ -187,7 +187,7 @@
        if (database.isPersistent()) {
           throw DbException.throwInternalError("It must be an in-memory database to execute a NOUNDO delete!");
        }
-       if (table.isNoUndo()) return;
+       if (table.isNoUndo(multiplex)) return;
 
        for (UndoLogRecord r : records) {
           Table rt = r.getTable();

=== modified file 'src/main/org/h2/pagestore/db/MultiplexedScanIndex.java'
--- old/src/main/org/h2/pagestore/db/MultiplexedScanIndex.java	2025-12-15 09:26:16 +0000
+++ new/src/main/org/h2/pagestore/db/MultiplexedScanIndex.java	2026-08-18 07:55:47 +0000
@@ -17,6 +17,7 @@
 import org.h2.result.SortOrder;
 import org.h2.table.Column;
 import org.h2.table.IndexColumn;
+import org.h2.table.Table;
 import org.h2.table.TableFilter;
 import org.h2.value.*;
 
@@ -64,7 +65,11 @@
    /** The table for which this index is made */
    private final PageStoreTable tableData;
    
-   /** {@code true} if this table is no-undo. Only such tables do the reclaiming currently */
+   /**
+    * {@code true} if this table is no-undo by default. Only no-undo tables do the reclaiming
+    * currently. This is the table-wide setting captured at creation;  an individual multiplex may
+    * override it, so read the effective value through {@link #isNoUndo(long)} rather than here.
+    */
    private final boolean noUndo;
    
    /** 
@@ -116,6 +121,23 @@
    }
 
    /**
+    * Get the effective no-undo option of a multiplex of this index' table.
+    * <p>
+    * One multiplexed table backs several logically independent tables, which need not agree on the
+    * no-undo option, so every decision which depends on it has to be made per multiplex rather
+    * than from the table-wide {@link #noUndo} flag.
+    *
+    * @param   multiplex
+    *          The multiplex to test.
+    *
+    * @return  {@code true} if changes to that multiplex are not undoable.
+    */
+   private boolean isNoUndo(long multiplex)
+   {
+      return tableData.isNoUndo((int) multiplex);
+   }
+
+   /**
     * Remove the index object.
     */
    @Override
@@ -288,6 +310,9 @@
       Long batchNr = (row.getKey() - FIRST_KEY) / BATCH_SIZE;
       Batch batch = batches.get(batchNr);
       boolean forUpdate = tableData.isUpdating();
+
+      // the option is resolved per multiplex, and the row is what identifies which one this is
+      boolean noUndo = tableData.isNoUndo(row);
       batch.remove(row, forUpdate);
       rowCount--;
 
@@ -301,7 +326,7 @@
       if (!forUpdate && batch.isEmpty())
       {
          // make sure we don't alter the batches if this is an update; there is an upcoming add
-         long multiplex = row.getValue(1).getInt();
+         long multiplex = row.getValue(Table.MULTIPLEX_COLUMN).getInt();
          Batch last = lastBatches.get(multiplex);
 
          // for no-undo tables the clean-up can be performed now
@@ -513,7 +538,7 @@
       // 2) the investigation shown that on-disk-like pages are used and some old batches *can*
       // be reclaimed by other multiplexes as long as we know it is clean. 
       // Until we have a page-like implementation, this will do.
-      if (forceReclaim && table.isNoUndo())
+      if (forceReclaim && isNoUndo(multiplex))
       {
          Batch batch = lastBatches.get((long) multiplex);
          if (batch == null)
@@ -557,7 +582,7 @@
       Row row = getRow(session, primaryKey);
       
       // we can reclaim only empty slots when rollbacking UNDO records
-      if (row != null || this.noUndo)
+      if (row != null || isNoUndo(multiplex))
       {
          return false;
       }
@@ -640,6 +665,7 @@
       ArrayList<Row> removedPKs = withPKs ? new ArrayList<>() : null;
       
       boolean clearBatches = !withPKs;
+      boolean noUndo = isNoUndo(multiplex);
       
       Batch batch = (clearBatches && noUndo) ? lastBatches.remove(multiplex) : lastBatches.get(multiplex);
       
@@ -727,7 +753,7 @@
       if (batch == null)
       {
          long multiplex = row.getValue(1).getInt();
-         batch = new Batch(FIRST_KEY + batchId * BATCH_SIZE, this.noUndo, multiplex);
+         batch = new Batch(FIRST_KEY + batchId * BATCH_SIZE, isNoUndo(multiplex), multiplex);
          notifyNewBatch(batch, multiplex);
          batches.put(batchId, batch);
          lastBatches.put(multiplex, batch);
@@ -752,7 +778,7 @@
       if (batch == null || batch.isFull())
       {
          long nextOffset = getAndIncNextOffset(session);
-         batch = new Batch(FIRST_KEY + nextOffset * BATCH_SIZE, this.noUndo, multiplex);
+         batch = new Batch(FIRST_KEY + nextOffset * BATCH_SIZE, isNoUndo(multiplex), multiplex);
          notifyNewBatch(batch, multiplex);
          batches.put(nextOffset, batch);
          lastBatches.put(multiplex, batch);

=== modified file 'src/main/org/h2/table/Table.java'
--- old/src/main/org/h2/table/Table.java	2025-12-12 20:57:59 +0000
+++ new/src/main/org/h2/table/Table.java	2026-08-18 07:59:17 +0000
@@ -58,6 +58,14 @@
     public static final int TYPE_MEMORY = 1;
 
     /**
+     * Index of the multiplex column in a row of a multiplexed table. The first column is always
+     * the recid and the second is always the multiplex.
+     *
+     * @see org.h2.pagestore.db.MultiplexedScanIndex
+     */
+    public static final int MULTIPLEX_COLUMN = 1;
+
+    /**
      * The columns of this table.
      */
     protected Column[] columns;
@@ -90,6 +98,21 @@
      */
     private boolean isNoUndo;
     
+    /**
+     * Per-multiplex overrides of {@link #isNoUndo}, or {@code null} when every multiplex of this
+     * table follows the table-wide flag.
+     * <p>
+     * A multiplexed table holds several logically independent tables, discriminated by the multiplex
+     * column, and they need not agree on the no-undo option. Entries are added by
+     * {@link #setMultiplexNoUndo} and dropped by {@link #clearMultiplexNoUndo} when a multiplex is
+     * killed. The map is left {@code null} until an override is actually needed, so a table whose
+     * multiplexes all follow the table-wide flag costs a single null check per row.
+     * <p>
+     * No synchronization is needed: multiplexing is used only for local temporary tables, whose
+     * {@code Table} instance belongs to a single session.
+     */
+    private HashMap<Integer, Boolean> multiplexNoUndo = null;
+
     private boolean isUpdating = false;
 
     private final HashMap<String, Column> columnMap;
@@ -1634,6 +1657,92 @@
        this.isNoUndo = isNoUndo;
    }
 
+   /**
+    * Set the no-undo option of a single multiplex of this table, overriding the table-wide flag.
+    * <p>
+    * The option has to be settable per multiplex because one multiplexed table backs several
+    * logically independent tables which need not agree on it.  Set it before the multiplex holds
+    * any rows;  changing it afterwards leaves the rows already written under the previous setting.
+    *
+    * @param   multiplex
+    *          The multiplex to set the option for.
+    * @param   noUndo
+    *          {@code true} if changes to this multiplex must not be undoable.
+    */
+   public void setMultiplexNoUndo(int multiplex, boolean noUndo) {
+      if (multiplexNoUndo == null) {
+         multiplexNoUndo = new HashMap<>();
+      }
+
+      multiplexNoUndo.put(multiplex, noUndo);
+   }
+
+   /**
+    * Forget the no-undo option of a single multiplex.  This is called when a multiplex is killed,
+    * so that the overrides do not accumulate for the life of the session.
+    *
+    * @param   multiplex
+    *          The multiplex to forget.
+    */
+   public void clearMultiplexNoUndo(int multiplex) {
+      if (multiplexNoUndo != null) {
+         multiplexNoUndo.remove(multiplex);
+
+         if (multiplexNoUndo.isEmpty()) {
+            multiplexNoUndo = null;
+         }
+      }
+   }
+
+   /**
+    * Check whether this table carries any per-multiplex override.  A table-wide operation which
+    * depends on the no-undo option cannot be applied when it does.
+    *
+    * @return  {@code true} if at least one multiplex overrides the table-wide flag.
+    */
+   public boolean hasMultiplexNoUndo() {
+      return multiplexNoUndo != null;
+   }
+
+   /**
+    * Get the effective no-undo option of a single multiplex.
+    *
+    * @param   multiplex
+    *          The multiplex to test.
+    *
+    * @return  The override for that multiplex if there is one, else the table-wide flag.
+    */
+   public boolean isNoUndo(int multiplex) {
+      if (multiplexNoUndo != null) {
+         Boolean override = multiplexNoUndo.get(multiplex);
+
+         if (override != null) {
+            return override;
+         }
+      }
+
+      return isNoUndo;
+   }
+
+   /**
+    * Get the effective no-undo option for the multiplex the given row belongs to.
+    *
+    * @param   row
+    *          The row being written. May be {@code null}.
+    *
+    * @return  The override for that row's multiplex if there is one, else the table-wide flag.
+    */
+   public boolean isNoUndo(Row row) {
+      // the common case is a table whose multiplexes all agree, which costs only this check
+      if (multiplexNoUndo == null || row == null) {
+         return isNoUndo;
+      }
+
+      Value value = row.getValue(MULTIPLEX_COLUMN);
+
+      return (value instanceof ValueInt) ? isNoUndo(value.getInt()) : isNoUndo;
+   }
+
    public boolean hasIndexedColumn(int columnId)
    {
       return true;

