Project

General

Profile

fwd-h2-11745.patch

Teodor Gorghe, 08/18/2026 03:59 AM

Download (13.6 KB)

View differences:

new/src/main/org/h2/command/dml/Delete.java 2026-08-17 12:59:53 +0000
93 93
    private boolean canFastDelete(int limitRows)
94 94
    {
95 95
       Table table = targetTableFilter.getTable();
96

  
97
       // a table-wide fast delete would also bypass the undo log of any multiplex which overrides
98
       // the no-undo option to be undoable, so it cannot be used once overrides are in play
96 99
       return table.isNoUndo() &&
100
              !table.hasMultiplexNoUndo() &&
97 101
              condition == null &&
98 102
              (limitRows == -1 || limitRows >= table.getRowCount(session)) &&
99 103
              !table.hasTriggers() &&
new/src/main/org/h2/embedded/DirectAccessDriver.java 2026-08-18 07:53:59 +0000
89 89
   throws SQLException;
90 90
   
91 91
   /**
92
    * Set the no-undo option of a single multiplex, overriding the option the table was created
93
    * with.
94
    * <p>
95
    * A multiplexed table backs several logically independent tables, which need not agree on the
96
    * no-undo option, so it cannot be a property of the SQL table alone.  Call this once, when the
97
    * multiplex is opened and before it holds any rows;  rows written under the previous setting
98
    * keep the undo records they were given.
99
    *
100
    * @param   tableName
101
    *          The name of the table which holds the multiplex.
102
    * @param   multiplex
103
    *          The multiplex to set the option for.
104
    * @param   noUndo
105
    *          {@code true} if changes to this multiplex must not be undoable.
106
    *
107
    * @throws  SQLException
108
    *          if the table does not exist.
109
    */
110
   public void setMultiplexNoUndo(String tableName, int multiplex, boolean noUndo)
111
   throws SQLException;
112

  
113
   /**
92 114
    * Retrieve a single row from a table by its recid. By convention, all H2 tables
93 115
    * targeted by this method have a primary key on a single integer column representing
94 116
    * the recid. Also, the recid should always be the first column of the table.
new/src/main/org/h2/embedded/FWDDirectAccessDriver.java 2026-08-18 07:54:09 +0000
211 211
      
212 212
      ((FWDMultiplexedIndex) idx).killMultiplex(session, multiplex);
213 213
      session.cleanUndoLog(t, multiplex);
214

  
215
      // the multiplex is gone, so its no-undo override must not outlive it
216
      t.clearMultiplexNoUndo(multiplex);
214 217
   }
215 218
   
219
   @Override
220
   public void setMultiplexNoUndo(String tableName, int multiplex, boolean noUndo)
221
   throws SQLException
222
   {
223
      if (tableName == null)
224
      {
225
         throw new SQLException("Invalid input: null values");
226
      }
227

  
228
      Session session = getSession();
229
      Table t = session.findLocalTempTable(tableName.toUpperCase());
230
      if (t == null)
231
      {
232
         throw new SQLException("Table not found: " + tableName);
233
      }
234

  
235
      t.setMultiplexNoUndo(multiplex, noUndo);
236
   }
237

  
216 238
   /**
217 239
    * Retrieve the number of rows that are currently in a specified table.
218 240
    * 
new/src/main/org/h2/engine/Session.java 2026-08-17 12:59:07 +0000
1016 1016
        if (table.isMVStore()) {
1017 1017
            return;
1018 1018
        }
1019
        if (table.isNoUndo()) {
1019
        // the no-undo option can be overridden per multiplex, so it is resolved from the row
1020
        if (table.isNoUndo(row)) {
1020 1021
           return;
1021 1022
        }
1022 1023
        if (undoLogEnabled) {
new/src/main/org/h2/engine/UndoLog.java 2026-08-17 12:59:31 +0000
21 21
 */
22 22
public class UndoLog {
23 23
    /** The temp-tables always have the _MULTIPLEX on offset 1, after the PK (at offset 0). */
24
    private static final int MULTIPLEX_OFFSET = 1; 
24
    private static final int MULTIPLEX_OFFSET = Table.MULTIPLEX_COLUMN;
25 25
   
26 26
    private final Database database;
27 27
    private final ArrayList<Long> storedEntriesPos = Utils.newSmallArrayList();
......
187 187
       if (database.isPersistent()) {
188 188
          throw DbException.throwInternalError("It must be an in-memory database to execute a NOUNDO delete!");
189 189
       }
190
       if (table.isNoUndo()) return;
190
       if (table.isNoUndo(multiplex)) return;
191 191

  
192 192
       for (UndoLogRecord r : records) {
193 193
          Table rt = r.getTable();
new/src/main/org/h2/pagestore/db/MultiplexedScanIndex.java 2026-08-18 07:55:47 +0000
17 17
import org.h2.result.SortOrder;
18 18
import org.h2.table.Column;
19 19
import org.h2.table.IndexColumn;
20
import org.h2.table.Table;
20 21
import org.h2.table.TableFilter;
21 22
import org.h2.value.*;
22 23

  
......
64 65
   /** The table for which this index is made */
65 66
   private final PageStoreTable tableData;
66 67
   
67
   /** {@code true} if this table is no-undo. Only such tables do the reclaiming currently */
68
   /**
69
    * {@code true} if this table is no-undo by default. Only no-undo tables do the reclaiming
70
    * currently. This is the table-wide setting captured at creation;  an individual multiplex may
71
    * override it, so read the effective value through {@link #isNoUndo(long)} rather than here.
72
    */
68 73
   private final boolean noUndo;
69 74
   
70 75
   /** 
......
116 121
   }
117 122

  
118 123
   /**
124
    * Get the effective no-undo option of a multiplex of this index' table.
125
    * <p>
126
    * One multiplexed table backs several logically independent tables, which need not agree on the
127
    * no-undo option, so every decision which depends on it has to be made per multiplex rather
128
    * than from the table-wide {@link #noUndo} flag.
129
    *
130
    * @param   multiplex
131
    *          The multiplex to test.
132
    *
133
    * @return  {@code true} if changes to that multiplex are not undoable.
134
    */
135
   private boolean isNoUndo(long multiplex)
136
   {
137
      return tableData.isNoUndo((int) multiplex);
138
   }
139

  
140
   /**
119 141
    * Remove the index object.
120 142
    */
121 143
   @Override
......
288 310
      Long batchNr = (row.getKey() - FIRST_KEY) / BATCH_SIZE;
289 311
      Batch batch = batches.get(batchNr);
290 312
      boolean forUpdate = tableData.isUpdating();
313

  
314
      // the option is resolved per multiplex, and the row is what identifies which one this is
315
      boolean noUndo = tableData.isNoUndo(row);
291 316
      batch.remove(row, forUpdate);
292 317
      rowCount--;
293 318

  
......
301 326
      if (!forUpdate && batch.isEmpty())
302 327
      {
303 328
         // make sure we don't alter the batches if this is an update; there is an upcoming add
304
         long multiplex = row.getValue(1).getInt();
329
         long multiplex = row.getValue(Table.MULTIPLEX_COLUMN).getInt();
305 330
         Batch last = lastBatches.get(multiplex);
306 331

  
307 332
         // for no-undo tables the clean-up can be performed now
......
513 538
      // 2) the investigation shown that on-disk-like pages are used and some old batches *can*
514 539
      // be reclaimed by other multiplexes as long as we know it is clean. 
515 540
      // Until we have a page-like implementation, this will do.
516
      if (forceReclaim && table.isNoUndo())
541
      if (forceReclaim && isNoUndo(multiplex))
517 542
      {
518 543
         Batch batch = lastBatches.get((long) multiplex);
519 544
         if (batch == null)
......
557 582
      Row row = getRow(session, primaryKey);
558 583
      
559 584
      // we can reclaim only empty slots when rollbacking UNDO records
560
      if (row != null || this.noUndo)
585
      if (row != null || isNoUndo(multiplex))
561 586
      {
562 587
         return false;
563 588
      }
......
640 665
      ArrayList<Row> removedPKs = withPKs ? new ArrayList<>() : null;
641 666
      
642 667
      boolean clearBatches = !withPKs;
668
      boolean noUndo = isNoUndo(multiplex);
643 669
      
644 670
      Batch batch = (clearBatches && noUndo) ? lastBatches.remove(multiplex) : lastBatches.get(multiplex);
645 671
      
......
727 753
      if (batch == null)
728 754
      {
729 755
         long multiplex = row.getValue(1).getInt();
730
         batch = new Batch(FIRST_KEY + batchId * BATCH_SIZE, this.noUndo, multiplex);
756
         batch = new Batch(FIRST_KEY + batchId * BATCH_SIZE, isNoUndo(multiplex), multiplex);
731 757
         notifyNewBatch(batch, multiplex);
732 758
         batches.put(batchId, batch);
733 759
         lastBatches.put(multiplex, batch);
......
752 778
      if (batch == null || batch.isFull())
753 779
      {
754 780
         long nextOffset = getAndIncNextOffset(session);
755
         batch = new Batch(FIRST_KEY + nextOffset * BATCH_SIZE, this.noUndo, multiplex);
781
         batch = new Batch(FIRST_KEY + nextOffset * BATCH_SIZE, isNoUndo(multiplex), multiplex);
756 782
         notifyNewBatch(batch, multiplex);
757 783
         batches.put(nextOffset, batch);
758 784
         lastBatches.put(multiplex, batch);
new/src/main/org/h2/table/Table.java 2026-08-18 07:59:17 +0000
58 58
    public static final int TYPE_MEMORY = 1;
59 59

  
60 60
    /**
61
     * Index of the multiplex column in a row of a multiplexed table. The first column is always
62
     * the recid and the second is always the multiplex.
63
     *
64
     * @see org.h2.pagestore.db.MultiplexedScanIndex
65
     */
66
    public static final int MULTIPLEX_COLUMN = 1;
67

  
68
    /**
61 69
     * The columns of this table.
62 70
     */
63 71
    protected Column[] columns;
......
90 98
     */
91 99
    private boolean isNoUndo;
92 100
    
101
    /**
102
     * Per-multiplex overrides of {@link #isNoUndo}, or {@code null} when every multiplex of this
103
     * table follows the table-wide flag.
104
     * <p>
105
     * A multiplexed table holds several logically independent tables, discriminated by the multiplex
106
     * column, and they need not agree on the no-undo option. Entries are added by
107
     * {@link #setMultiplexNoUndo} and dropped by {@link #clearMultiplexNoUndo} when a multiplex is
108
     * killed. The map is left {@code null} until an override is actually needed, so a table whose
109
     * multiplexes all follow the table-wide flag costs a single null check per row.
110
     * <p>
111
     * No synchronization is needed: multiplexing is used only for local temporary tables, whose
112
     * {@code Table} instance belongs to a single session.
113
     */
114
    private HashMap<Integer, Boolean> multiplexNoUndo = null;
115

  
93 116
    private boolean isUpdating = false;
94 117

  
95 118
    private final HashMap<String, Column> columnMap;
......
1634 1657
       this.isNoUndo = isNoUndo;
1635 1658
   }
1636 1659

  
1660
   /**
1661
    * Set the no-undo option of a single multiplex of this table, overriding the table-wide flag.
1662
    * <p>
1663
    * The option has to be settable per multiplex because one multiplexed table backs several
1664
    * logically independent tables which need not agree on it.  Set it before the multiplex holds
1665
    * any rows;  changing it afterwards leaves the rows already written under the previous setting.
1666
    *
1667
    * @param   multiplex
1668
    *          The multiplex to set the option for.
1669
    * @param   noUndo
1670
    *          {@code true} if changes to this multiplex must not be undoable.
1671
    */
1672
   public void setMultiplexNoUndo(int multiplex, boolean noUndo) {
1673
      if (multiplexNoUndo == null) {
1674
         multiplexNoUndo = new HashMap<>();
1675
      }
1676

  
1677
      multiplexNoUndo.put(multiplex, noUndo);
1678
   }
1679

  
1680
   /**
1681
    * Forget the no-undo option of a single multiplex.  This is called when a multiplex is killed,
1682
    * so that the overrides do not accumulate for the life of the session.
1683
    *
1684
    * @param   multiplex
1685
    *          The multiplex to forget.
1686
    */
1687
   public void clearMultiplexNoUndo(int multiplex) {
1688
      if (multiplexNoUndo != null) {
1689
         multiplexNoUndo.remove(multiplex);
1690

  
1691
         if (multiplexNoUndo.isEmpty()) {
1692
            multiplexNoUndo = null;
1693
         }
1694
      }
1695
   }
1696

  
1697
   /**
1698
    * Check whether this table carries any per-multiplex override.  A table-wide operation which
1699
    * depends on the no-undo option cannot be applied when it does.
1700
    *
1701
    * @return  {@code true} if at least one multiplex overrides the table-wide flag.
1702
    */
1703
   public boolean hasMultiplexNoUndo() {
1704
      return multiplexNoUndo != null;
1705
   }
1706

  
1707
   /**
1708
    * Get the effective no-undo option of a single multiplex.
1709
    *
1710
    * @param   multiplex
1711
    *          The multiplex to test.
1712
    *
1713
    * @return  The override for that multiplex if there is one, else the table-wide flag.
1714
    */
1715
   public boolean isNoUndo(int multiplex) {
1716
      if (multiplexNoUndo != null) {
1717
         Boolean override = multiplexNoUndo.get(multiplex);
1718

  
1719
         if (override != null) {
1720
            return override;
1721
         }
1722
      }
1723

  
1724
      return isNoUndo;
1725
   }
1726

  
1727
   /**
1728
    * Get the effective no-undo option for the multiplex the given row belongs to.
1729
    *
1730
    * @param   row
1731
    *          The row being written. May be {@code null}.
1732
    *
1733
    * @return  The override for that row's multiplex if there is one, else the table-wide flag.
1734
    */
1735
   public boolean isNoUndo(Row row) {
1736
      // the common case is a table whose multiplexes all agree, which costs only this check
1737
      if (multiplexNoUndo == null || row == null) {
1738
         return isNoUndo;
1739
      }
1740

  
1741
      Value value = row.getValue(MULTIPLEX_COLUMN);
1742

  
1743
      return (value instanceof ValueInt) ? isNoUndo(value.getInt()) : isNoUndo;
1744
   }
1745

  
1637 1746
   public boolean hasIndexedColumn(int columnId)
1638 1747
   {
1639 1748
      return true;