提交 59ed4567 authored 作者: Thomas Mueller's avatar Thomas Mueller

Page store: large values in indexed columns could corrupt the index.

上级 64b9cd64
...@@ -140,7 +140,6 @@ public abstract class PageBtree extends Page { ...@@ -140,7 +140,6 @@ public abstract class PageBtree extends Page {
* @param row the row to add * @param row the row to add
* @return the split point of this page, or -1 if no split is required * @return the split point of this page, or -1 if no split is required
*/ */
abstract int addRowTry(SearchRow row) throws SQLException; abstract int addRowTry(SearchRow row) throws SQLException;
/** /**
......
...@@ -91,11 +91,15 @@ public class PageBtreeLeaf extends PageBtree { ...@@ -91,11 +91,15 @@ public class PageBtreeLeaf extends PageBtree {
} }
int addRowTry(SearchRow row) throws SQLException { int addRowTry(SearchRow row) throws SQLException {
return addRow(row, true);
}
private int addRow(SearchRow row, boolean tryOnly) throws SQLException {
int rowLength = index.getRowSize(data, row, onlyPosition); int rowLength = index.getRowSize(data, row, onlyPosition);
int pageSize = index.getPageStore().getPageSize(); int pageSize = index.getPageStore().getPageSize();
int last = entryCount == 0 ? pageSize : offsets[entryCount - 1]; int last = entryCount == 0 ? pageSize : offsets[entryCount - 1];
if (last - rowLength < start + OFFSET_LENGTH) { if (last - rowLength < start + OFFSET_LENGTH) {
if (entryCount > 1) { if (tryOnly && entryCount > 1) {
int x = find(row, false, true, true); int x = find(row, false, true, true);
if (entryCount < 5) { if (entryCount < 5) {
// required, otherwise the index doesn't work correctly // required, otherwise the index doesn't work correctly
...@@ -107,6 +111,7 @@ public class PageBtreeLeaf extends PageBtree { ...@@ -107,6 +111,7 @@ public class PageBtreeLeaf extends PageBtree {
int third = entryCount / 3; int third = entryCount / 3;
return x < third ? third : x >= 2 * third ? 2 * third : x; return x < third ? third : x >= 2 * third ? 2 * third : x;
} }
readAllRows();
onlyPosition = true; onlyPosition = true;
// change the offsets (now storing only positions) // change the offsets (now storing only positions)
int o = pageSize; int o = pageSize;
...@@ -151,7 +156,7 @@ public class PageBtreeLeaf extends PageBtree { ...@@ -151,7 +156,7 @@ public class PageBtreeLeaf extends PageBtree {
return -1; return -1;
} }
private void removeRow(int i) throws SQLException { private void removeRow(int at) throws SQLException {
readAllRows(); readAllRows();
index.getPageStore().logUndo(this, data); index.getPageStore().logUndo(this, data);
entryCount--; entryCount--;
...@@ -161,14 +166,14 @@ public class PageBtreeLeaf extends PageBtree { ...@@ -161,14 +166,14 @@ public class PageBtreeLeaf extends PageBtree {
} }
int[] newOffsets = new int[entryCount]; int[] newOffsets = new int[entryCount];
SearchRow[] newRows = new SearchRow[entryCount]; SearchRow[] newRows = new SearchRow[entryCount];
System.arraycopy(offsets, 0, newOffsets, 0, i); System.arraycopy(offsets, 0, newOffsets, 0, at);
System.arraycopy(rows, 0, newRows, 0, i); System.arraycopy(rows, 0, newRows, 0, at);
int startNext = i > 0 ? offsets[i - 1] : index.getPageStore().getPageSize(); int startNext = at > 0 ? offsets[at - 1] : index.getPageStore().getPageSize();
int rowLength = startNext - offsets[i]; int rowLength = startNext - offsets[at];
for (int j = i; j < entryCount; j++) { for (int j = at; j < entryCount; j++) {
newOffsets[j] = offsets[j + 1] + rowLength; newOffsets[j] = offsets[j + 1] + rowLength;
} }
System.arraycopy(rows, i + 1, newRows, i, entryCount - i); System.arraycopy(rows, at + 1, newRows, at, entryCount - at);
start -= OFFSET_LENGTH; start -= OFFSET_LENGTH;
offsets = newOffsets; offsets = newOffsets;
rows = newRows; rows = newRows;
...@@ -182,7 +187,7 @@ public class PageBtreeLeaf extends PageBtree { ...@@ -182,7 +187,7 @@ public class PageBtreeLeaf extends PageBtree {
int newPageId = index.getPageStore().allocatePage(); int newPageId = index.getPageStore().allocatePage();
PageBtreeLeaf p2 = PageBtreeLeaf.create(index, newPageId, parentPageId); PageBtreeLeaf p2 = PageBtreeLeaf.create(index, newPageId, parentPageId);
for (int i = splitPoint; i < entryCount;) { for (int i = splitPoint; i < entryCount;) {
p2.addRowTry(getRow(splitPoint)); p2.addRow(getRow(splitPoint), false);
removeRow(splitPoint); removeRow(splitPoint);
} }
return p2; return p2;
......
...@@ -143,6 +143,7 @@ public class PageBtreeNode extends PageBtree { ...@@ -143,6 +143,7 @@ public class PageBtreeNode extends PageBtree {
int pageSize = index.getPageStore().getPageSize(); int pageSize = index.getPageStore().getPageSize();
int last = entryCount == 0 ? pageSize : offsets[entryCount - 1]; int last = entryCount == 0 ? pageSize : offsets[entryCount - 1];
if (last - rowLength < start + CHILD_OFFSET_PAIR_LENGTH) { if (last - rowLength < start + CHILD_OFFSET_PAIR_LENGTH) {
readAllRows();
onlyPosition = true; onlyPosition = true;
// change the offsets (now storing only positions) // change the offsets (now storing only positions)
int o = pageSize; int o = pageSize;
......
...@@ -122,6 +122,9 @@ public class PageDataLeaf extends PageData { ...@@ -122,6 +122,9 @@ public class PageDataLeaf extends PageData {
keys = new long[entryCount]; keys = new long[entryCount];
rows = new Row[entryCount]; rows = new Row[entryCount];
if (type == Page.TYPE_DATA_LEAF) { if (type == Page.TYPE_DATA_LEAF) {
if (entryCount != 1) {
Message.throwInternalError("entries: " + entryCount);
}
firstOverflowPageId = data.readInt(); firstOverflowPageId = data.readInt();
} }
for (int i = 0; i < entryCount; i++) { for (int i = 0; i < entryCount; i++) {
...@@ -256,6 +259,9 @@ public class PageDataLeaf extends PageData { ...@@ -256,6 +259,9 @@ public class PageDataLeaf extends PageData {
if (entryCount < 0) { if (entryCount < 0) {
Message.throwInternalError(); Message.throwInternalError();
} }
firstOverflowPageId = 0;
overflowRowSize = 0;
rowRef = null;
int keyOffsetPairLen = 2 + data.getVarLongLen(keys[i]); int keyOffsetPairLen = 2 + data.getVarLongLen(keys[i]);
int[] newOffsets = new int[entryCount]; int[] newOffsets = new int[entryCount];
long[] newKeys = new long[entryCount]; long[] newKeys = new long[entryCount];
...@@ -333,7 +339,10 @@ public class PageDataLeaf extends PageData { ...@@ -333,7 +339,10 @@ public class PageDataLeaf extends PageData {
int newPageId = index.getPageStore().allocatePage(); int newPageId = index.getPageStore().allocatePage();
PageDataLeaf p2 = PageDataLeaf.create(index, newPageId, parentPageId); PageDataLeaf p2 = PageDataLeaf.create(index, newPageId, parentPageId);
for (int i = splitPoint; i < entryCount;) { for (int i = splitPoint; i < entryCount;) {
p2.addRowTry(getRowAt(splitPoint)); int split = p2.addRowTry(getRowAt(splitPoint));
if (split != -1) {
Message.throwInternalError("split " + split);
}
removeRow(splitPoint); removeRow(splitPoint);
} }
return p2; return p2;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
package org.h2.test.unit; package org.h2.test.unit;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
...@@ -32,6 +33,8 @@ public class TestPageStore extends TestBase { ...@@ -32,6 +33,8 @@ public class TestPageStore extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
testExistingOld();
testLargeRows();
testRecoverDropIndex(); testRecoverDropIndex();
testDropPk(); testDropPk();
testCreatePkLater(); testCreatePkLater();
...@@ -42,6 +45,110 @@ public class TestPageStore extends TestBase { ...@@ -42,6 +45,110 @@ public class TestPageStore extends TestBase {
testFuzzOperations(); testFuzzOperations();
} }
private void testExistingOld() throws SQLException {
if (config.memory) {
return;
}
Connection conn;
deleteDb("pageStore");
String url;
url = "jdbc:h2:" + baseDir + "/pageStore";
conn = DriverManager.getConnection(url + ";PAGE_STORE=FALSE");
conn.createStatement().execute("create table test(id int) as select 1");
conn.close();
conn = DriverManager.getConnection(url);
conn.createStatement().execute("select * from test");
conn.close();
conn = DriverManager.getConnection(url + ";PAGE_STORE=TRUE");
conn.createStatement().execute("create table test(id int) as select 2");
conn.close();
conn = DriverManager.getConnection(url);
this.assertResult("2", conn.createStatement(), "select * from test");
conn.close();
}
private void testLargeRows() throws Exception {
if (config.memory) {
return;
}
for (int i = 0; i < 10; i++) {
testLargeRows(i);
}
}
private void testLargeRows(int seed) throws Exception {
deleteDb("pageStore");
String url = getURL("pageStore;CACHE_SIZE=16", true);
Connection conn = null;
Statement stat = null;
int count = 0;
try {
Class.forName("org.h2.Driver");
conn = DriverManager.getConnection(url);
stat = conn.createStatement();
int tableCount = 1;
PreparedStatement[] insert = new PreparedStatement[tableCount];
PreparedStatement[] deleteMany = new PreparedStatement[tableCount];
PreparedStatement[] updateMany = new PreparedStatement[tableCount];
for (int i = 0; i < tableCount; i++) {
stat.execute("create table test" + i + "(id int primary key, name varchar)");
stat.execute("create index idx_test" + i + " on test" + i + "(name)");
insert[i] = conn.prepareStatement("insert into test" + i + " values(?, ? || space(?))");
deleteMany[i] = conn.prepareStatement("delete from test" + i + " where id between ? and ?");
updateMany[i] = conn.prepareStatement("update test" + i + " set name=? || space(?) where id between ? and ?");
}
Random random = new Random(seed);
for (int i = 0; i < 1000; i++) {
count = i;
PreparedStatement p;
if (random.nextInt(100) < 95) {
p = insert[random.nextInt(tableCount)];
p.setInt(1, i);
p.setInt(2, i);
if (random.nextInt(30) == 5) {
p.setInt(3, 3000);
} else {
p.setInt(3, random.nextInt(100));
}
p.execute();
} else if (random.nextInt(100) < 90) {
p = updateMany[random.nextInt(tableCount)];
p.setInt(1, i);
p.setInt(2, random.nextInt(50));
int start = random.nextInt(1 + i);
p.setInt(3, start);
p.setInt(4, start + random.nextInt(50));
p.executeUpdate();
} else {
p = deleteMany[random.nextInt(tableCount)];
int start = random.nextInt(1 + i);
p.setInt(1, start);
p.setInt(2, start + random.nextInt(100));
p.executeUpdate();
}
}
conn.close();
conn = DriverManager.getConnection(url);
conn.close();
conn = DriverManager.getConnection(url);
stat = conn.createStatement();
stat.execute("script to '" + baseDir + "/pageStore.sql'");
conn.close();
} catch (Exception e) {
try {
stat.execute("shutdown immediately");
} catch (SQLException e2) {
// ignore
}
try {
conn.close();
} catch (SQLException e2) {
// ignore
}
fail("count: " + count + " " + e);
}
}
private void testRecoverDropIndex() throws SQLException { private void testRecoverDropIndex() throws SQLException {
if (config.memory) { if (config.memory) {
return; return;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论