fwd-h2-11745.patch
| 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; |