提交 a4ce10d0 authored 作者: andrei's avatar andrei

Merge remote-tracking branch 'h2database/master' into issue#499.2

......@@ -21,10 +21,14 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Fix startup issue when using "CHECK" as a column name.
</li>
<li>Issue #423: ANALYZE performed multiple times on one table during execution of the same statement.
</li>
<li>Issue #426: Support ANALYZE TABLE statement
</li>
<li>Issue #438: Fix slow logging via SLF4J (TRACE_LEVEL_FILE=4).
</li>
<li>Issue #472: Support CREATE SEQUENCE ... ORDER as a NOOP for Oracle compatibility
</li>
</ul>
......
......@@ -58,4 +58,5 @@ Export-Package: org.h2;version="${version}",
org.h2.mvstore.db;version="${version}",
org.h2.mvstore.type;version="${version}",
org.h2.mvstore.rtree;version="${version}"
Provide-Capability: osgi.service;objectClass:List<String>=org.osgi.service.jdbc.DataSourceFactory
Premain-Class: org.h2.util.Profiler
......@@ -3884,7 +3884,9 @@ public class Parser {
private static int getSaveTokenType(String s, boolean supportOffsetFetch) {
switch (s.charAt(0)) {
case 'C':
if (s.equals("CURRENT_TIMESTAMP")) {
if (s.equals("CHECK")) {
return KEYWORD;
} else if (s.equals("CURRENT_TIMESTAMP")) {
return CURRENT_TIMESTAMP;
} else if (s.equals("CURRENT_TIME")) {
return CURRENT_TIME;
......@@ -4681,6 +4683,10 @@ public class Parser {
return false;
}
private boolean readIfAffinity() {
return readIf("AFFINITY") || readIf("SHARD");
}
private CreateConstant parseCreateConstant() {
boolean ifNotExists = readIfNotExists();
String constantName = readIdentifierWithSchema();
......@@ -5905,6 +5911,7 @@ public class Parser {
String constraintName = null, comment = null;
boolean ifNotExists = false;
boolean allowIndexDefinition = database.getMode().indexDefinitionInCreateTable;
boolean allowAffinityKey = database.getMode().allowAffinityKey;
if (readIf("CONSTRAINT")) {
ifNotExists = readIfNotExists();
constraintName = readIdentifierWithSchema(schema.getName());
......@@ -5956,6 +5963,12 @@ public class Parser {
read("BTREE");
}
return command;
} else if (allowAffinityKey && readIfAffinity()) {
read("KEY");
read("(");
CreateIndex command = createAffinityIndex(schema, tableName, parseIndexColumnList());
command.setIfTableExists(ifTableExists);
return command;
}
AlterTableAddConstraint command;
if (readIf("CHECK")) {
......@@ -6123,6 +6136,9 @@ public class Parser {
if (readIf("CONSTRAINT")) {
constraintName = readColumnIdentifier();
}
// For compatibility with Apache Ignite.
boolean allowAffinityKey = database.getMode().allowAffinityKey;
boolean affinity = allowAffinityKey && readIfAffinity();
if (readIf("PRIMARY")) {
read("KEY");
boolean hash = readIf("HASH");
......@@ -6138,6 +6154,16 @@ public class Parser {
if (readIf("AUTO_INCREMENT")) {
parseAutoIncrement(column);
}
if (affinity) {
CreateIndex idx = createAffinityIndex(schema, tableName, cols);
command.addConstraintCommand(idx);
}
} else if (affinity) {
read("KEY");
IndexColumn[] cols = { new IndexColumn() };
cols[0].columnName = column.getName();
CreateIndex idx = createAffinityIndex(schema, tableName, cols);
command.addConstraintCommand(idx);
} else if (readIf("UNIQUE")) {
AlterTableAddConstraint unique = new AlterTableAddConstraint(
session, schema, false);
......@@ -6256,6 +6282,14 @@ public class Parser {
return command;
}
private CreateIndex createAffinityIndex(Schema schema, String tableName, IndexColumn[] indexColumns) {
CreateIndex idx = new CreateIndex(session, schema);
idx.setTableName(tableName);
idx.setIndexColumns(indexColumns);
idx.setAffinity(true);
return idx;
}
private static int getCompareType(int tokenType) {
switch (tokenType) {
case EQUAL:
......
......@@ -26,7 +26,7 @@ public class CreateIndex extends SchemaCommand {
private String tableName;
private String indexName;
private IndexColumn[] indexColumns;
private boolean primaryKey, unique, hash, spatial;
private boolean primaryKey, unique, hash, spatial, affinity;
private boolean ifTableExists;
private boolean ifNotExists;
private String comment;
......@@ -98,6 +98,8 @@ public class CreateIndex extends SchemaCommand {
indexType = IndexType.createPrimaryKey(persistent, hash);
} else if (unique) {
indexType = IndexType.createUnique(persistent, hash);
} else if (affinity) {
indexType = IndexType.createAffinity();
} else {
indexType = IndexType.createNonUnique(persistent, hash, spatial);
}
......@@ -123,6 +125,10 @@ public class CreateIndex extends SchemaCommand {
this.spatial = b;
}
public void setAffinity(boolean b) {
this.affinity = b;
}
public void setComment(String comment) {
this.comment = comment;
}
......
......@@ -80,5 +80,4 @@ public class CreateTableData {
* The table is hidden.
*/
public boolean isHidden;
}
......@@ -155,6 +155,11 @@ public class Mode {
*/
public boolean prohibitEmptyInPredicate;
/**
* Whether AFFINITY KEY keywords are supported.
*/
public boolean allowAffinityKey;
private final String name;
static {
......@@ -252,6 +257,12 @@ public class Mode {
Pattern.compile("ApplicationName");
mode.prohibitEmptyInPredicate = true;
add(mode);
mode = new Mode("Ignite");
mode.nullConcatIsNull = true;
mode.allowAffinityKey = true;
mode.indexDefinitionInCreateTable = true;
add(mode);
}
private Mode(String name) {
......
......@@ -10,7 +10,7 @@ package org.h2.index;
*/
public class IndexType {
private boolean primaryKey, persistent, unique, hash, scan, spatial;
private boolean primaryKey, persistent, unique, hash, scan, spatial, affinity;
private boolean belongsToConstraint;
/**
......@@ -71,6 +71,16 @@ public class IndexType {
return type;
}
/**
* Create an affinity index.
*
*/
public static IndexType createAffinity() {
IndexType type = new IndexType();
type.affinity = true;
return type;
}
/**
* Create a scan pseudo-index.
*
......@@ -148,6 +158,15 @@ public class IndexType {
return unique;
}
/**
* Does this index represent an affinity key?
*
* @return true if it does
*/
public boolean isAffinity() {
return affinity;
}
/**
* Get the SQL snippet to create such an index.
*
......
......@@ -146,6 +146,9 @@ public class TraceSystem implements TraceWriter {
@Override
public boolean isEnabled(int level) {
if (levelMax == ADAPTER) {
return writer.isEnabled(level);
}
return level <= this.levelMax;
}
......
......@@ -185,7 +185,7 @@ public class WriteBuffer {
* @return this
*/
public WriteBuffer put(ByteBuffer src) {
ensureCapacity(buff.remaining()).put(src);
ensureCapacity(src.remaining()).put(src);
return this;
}
......
......@@ -149,9 +149,9 @@ ALTER VIEW [ IF EXISTS ] viewName RECOMPILE
","
Recompiles a view after the underlying tables have been changed or created."
"Commands (DDL)","ANALYZE","
ANALYZE [ SAMPLE_SIZE rowCountInt ]
ANALYZE [ TABLE tableName ] [ SAMPLE_SIZE rowCountInt ]
","
Updates the selectivity statistics of all tables."
Updates the selectivity statistics of tables."
"Commands (DDL)","COMMENT","
COMMENT ON
{ { COLUMN [ schemaName. ] tableName.columnName }
......
......@@ -167,6 +167,9 @@ public final class JoinBatch {
* @return column value for current row
*/
public Value getValue(int filterId, Column column) {
if (current == null) {
return null;
}
Object x = current.row(filterId);
assert x != null;
Row row = current.isRow(filterId) ? (Row) x : ((Cursor) x).get();
......
......@@ -52,7 +52,12 @@ public class LinkSchema {
stat = conn.createStatement();
stat.execute("CREATE SCHEMA IF NOT EXISTS " +
StringUtils.quoteIdentifier(targetSchema));
rs = c2.getMetaData().getTables(null, sourceSchema, null, null);
//Workaround for PostgreSQL to avoid index names
if (url.startsWith("jdbc:postgresql:")) {
rs = c2.getMetaData().getTables(null, sourceSchema, null, new String[]{"TABLE", "LINKED TABLE", "VIEW", "EXTERNAL"});
} else {
rs = c2.getMetaData().getTables(null, sourceSchema, null, null);
}
while (rs.next()) {
String table = rs.getString("TABLE_NAME");
StringBuilder buff = new StringBuilder();
......
......@@ -200,7 +200,8 @@ public class MetaTable extends Table {
"ID INT",
"SORT_TYPE INT",
"CONSTRAINT_NAME",
"INDEX_CLASS"
"INDEX_CLASS",
"AFFINITY BIT"
);
indexColumnName = "TABLE_NAME";
break;
......@@ -912,7 +913,10 @@ public class MetaTable extends Table {
// CONSTRAINT_NAME
constraintName,
// INDEX_CLASS
indexClass
indexClass,
// AFFINITY
index.getIndexType().isAffinity() ?
"TRUE" : "FALSE"
);
}
}
......
......@@ -445,7 +445,8 @@ public class TableLink extends Table {
@Override
public synchronized long getRowCount(Session session) {
String sql = "SELECT COUNT(*) FROM " + qualifiedTableName;
//The foo alias is used to support the PostgreSQL syntax
String sql = "SELECT COUNT(*) FROM " + qualifiedTableName + " as foo";
try {
PreparedStatement prep = execute(sql, null, false);
ResultSet rs = prep.getResultSet();
......
......@@ -50,6 +50,7 @@ public class TestCompatibility extends TestBase {
testDB2();
testDerby();
testSybaseAndMSSQLServer();
testIgnite();
conn.close();
deleteDb("compatibility");
......@@ -487,4 +488,26 @@ public class TestCompatibility extends TestBase {
conn.close();
conn = getConnection("compatibility");
}
private void testIgnite() throws SQLException {
Statement stat = conn.createStatement();
stat.execute("SET MODE Ignite");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int affinity key)");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int affinity primary key)");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int, v1 varchar, v2 long affinity key, primary key(v1, id))");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int, v1 varchar, v2 long, primary key(v1, id), affinity key (id))");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int shard key)");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int shard primary key)");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int, v1 varchar, v2 long shard key, primary key(v1, id))");
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("create table test(id int, v1 varchar, v2 long, primary key(v1, id), shard key (id))");
}
}
......@@ -38,30 +38,45 @@ public class TestQueryCache extends TestBase {
private void test1() throws Exception {
Connection conn = getConnection("queryCache;QUERY_CACHE_SIZE=10");
Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar) " +
"as select x, space(100) from system_range(1, 1000)");
stat.execute("create table test(id int, name varchar)");
PreparedStatement prep;
conn.prepareStatement("select count(*) from test t1, test t2");
// query execution may be fast here but the parsing must be slow
StringBuilder queryBuilder = new StringBuilder("select count(*) from test t1 where \n");
for (int i = 0; i < 1000; i++) {
if (i != 0) {
queryBuilder.append(" and ");
}
queryBuilder.append(" TIMESTAMP '2005-12-31 23:59:59' = TIMESTAMP '2005-12-31 23:59:59' ");
}
String query = queryBuilder.toString();
conn.prepareStatement(query);
long time;
ResultSet rs;
long first = 0;
for (int i = 0; i < 4; i++) {
// 1000 iterations to warm up and avoid JIT effects
for (int i = 0; i < 1005; i++) {
// this should both ensure results are not re-used
// stat.execute("set mode regular");
// stat.execute("create table x()");
// stat.execute("drop table x");
time = System.nanoTime();
prep = conn.prepareStatement("select count(*) from test t1, test t2");
prep = conn.prepareStatement(query);
execute(prep);
rs = stat.executeQuery("select count(*) from test t1, test t2");
prep.close();
rs = stat.executeQuery(query);
rs.next();
int c = rs.getInt(1);
rs.close();
assertEquals(1000000, c);
assertEquals(0, c);
time = System.nanoTime() - time;
if (first == 0) {
if (i == 1000) {
// take from cache and do not close, so that next iteration will have a cache miss
prep = conn.prepareStatement(query);
} else if (i == 1001) {
first = time;
} else {
// try to avoid pauses in subsequent iterations
System.gc();
} else if (i > 1001) {
assertSmaller(time, first);
}
}
......
......@@ -41,13 +41,7 @@ import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.SubQueryInfo;
import org.h2.table.Table;
import org.h2.table.TableBase;
import org.h2.table.TableFilter;
import org.h2.table.TableType;
import org.h2.table.*;
import org.h2.test.TestBase;
import org.h2.util.DoneFuture;
import org.h2.util.New;
......@@ -82,6 +76,7 @@ public class TestTableEngines extends TestBase {
testSimpleQuery();
testMultiColumnTreeSetIndex();
testBatchedJoin();
testAffinityKey();
}
private void testEarlyFilter() throws SQLException {
......@@ -504,6 +499,25 @@ public class TestTableEngines extends TestBase {
deleteDb("testBatchedJoin");
}
private void testAffinityKey() throws SQLException {
deleteDb("tableEngine");
Connection conn = getConnection("tableEngine;mode=Ignite;MV_STORE=FALSE");
Statement stat = conn.createStatement();
stat.executeUpdate("CREATE TABLE T(ID INT AFFINITY PRIMARY KEY, NAME VARCHAR, AGE INT)" +
" ENGINE \"" + AffinityTableEngine.class.getName() + "\"");
Table tbl = AffinityTableEngine.createdTbl;
assertTrue(tbl != null);
assertEquals(3, tbl.getIndexes().size());
Index aff = tbl.getIndexes().get(2);
assertTrue(aff.getIndexType().isAffinity());
assertEquals("T_AFF", aff.getName());
assertEquals(1, aff.getIndexColumns().length);
assertEquals("ID", aff.getIndexColumns()[0].columnName);
conn.close();
deleteDb("tableEngine");
}
private static void forceJoinOrder(Statement s, boolean force) throws SQLException {
s.executeUpdate("SET FORCE_JOIN_ORDER " + force);
}
......@@ -1098,6 +1112,144 @@ public class TestTableEngines extends TestBase {
}
/**
* A test table factory producing affinity aware tables.
*/
public static class AffinityTableEngine implements TableEngine {
public static Table createdTbl;
/**
* A table able to handle affinity indexes.
*/
private static class AffinityTable extends RegularTable {
/**
* A (no-op) affinity index.
*/
public class AffinityIndex extends BaseIndex {
AffinityIndex(Table table, int id, String name, IndexColumn[] newIndexColumns) {
initBaseIndex(table, id, name, newIndexColumns, IndexType.createAffinity());
}
@Override
public long getRowCountApproximation() {
return table.getRowCountApproximation();
}
@Override
public long getDiskSpaceUsed() {
return table.getDiskSpaceUsed();
}
@Override
public long getRowCount(Session session) {
return table.getRowCount(session);
}
@Override
public void checkRename() {
// do nothing
}
@Override
public void truncate(Session session) {
// do nothing
}
@Override
public void remove(Session session) {
// do nothing
}
@Override
public void remove(Session session, Row r) {
// do nothing
}
@Override
public boolean needRebuild() {
return false;
}
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder,
HashSet<Column> allColumnsSet) {
return 0;
}
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
throw DbException.getUnsupportedException("TEST");
}
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
throw DbException.getUnsupportedException("TEST");
}
@Override
public void close(Session session) {
// do nothing
}
@Override
public boolean canGetFirstOrLast() {
return false;
}
@Override
public void add(Session session, Row r) {
// do nothing
}
}
AffinityTable(CreateTableData data) {
super(data);
}
@Override
public Index addIndex(Session session, String indexName,
int indexId, IndexColumn[] cols, IndexType indexType,
boolean create, String indexComment) {
if (!indexType.isAffinity()) {
return super.addIndex(session, indexName, indexId, cols, indexType, create, indexComment);
}
boolean isSessionTemporary = isTemporary() && !isGlobalTemporary();
if (!isSessionTemporary) {
database.lockMeta(session);
}
AffinityIndex index = new AffinityIndex(this, indexId, getName() + "_AFF", cols);
index.setTemporary(isTemporary());
if (index.getCreateSQL() != null) {
index.setComment(indexComment);
if (isSessionTemporary) {
session.addLocalTempTableIndex(index);
} else {
database.addSchemaObject(session, index);
}
}
getIndexes().add(index);
setModified();
return index;
}
}
/**
* Create a new OneRowTable.
*
* @param data the meta data of the table to create
* @return the new table
*/
@Override
public Table createTable(CreateTableData data) {
return (createdTbl = new AffinityTable(data));
}
}
/**
* A test table factory.
*/
......
......@@ -32,13 +32,19 @@ public class TestTraceSystem extends TestBase {
testAdapter();
}
private static void testAdapter() {
private void testAdapter() {
TraceSystem ts = new TraceSystem(null);
ts.setName("test");
ts.setLevelFile(TraceSystem.ADAPTER);
ts.getTrace("test").debug("test");
ts.getTrace("test").info("test");
ts.getTrace("test").error(new Exception(), "test");
// The used SLF4J-nop logger has all log levels disabled,
// so this should be reflected in the trace system.
assertFalse(ts.isEnabled(TraceSystem.INFO));
assertFalse(ts.getTrace("test").isInfoEnabled());
ts.close();
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论