提交 20e2a017 authored 作者: Sergi Vladykin's avatar Sergi Vladykin

Added external table engines support

上级 2116380c
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
import org.h2.table.RecreatableTable;
import org.h2.command.ddl.CreateTableData;
/**
* Class for creating custom table implementations
*
* @author Sergi Vladykin
*/
public interface TableEngine {
/**
* Create new table
* @param data for table construction
* @return created table
*/
RecreatableTable createTable(CreateTableData data);
}
......@@ -122,7 +122,6 @@ import org.h2.table.FunctionTable;
import org.h2.table.IndexColumn;
import org.h2.table.RangeTable;
import org.h2.table.Table;
import org.h2.table.TableData;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.Utils;
......@@ -3860,7 +3859,7 @@ public class Parser {
private Query parserWith() {
String tempViewName = readIdentifierWithSchema();
Schema schema = getSchema();
TableData recursiveTable;
Table recursiveTable;
read("(");
ArrayList<Column> columns = New.arrayList();
String[] cols = parseColumnList();
......@@ -4789,6 +4788,9 @@ public class Parser {
command.setQuery(parseSelect());
}
}
if (readIf("ENGINE")) {
command.setTableEngine(readString());
}
if (temp) {
if (readIf("ON")) {
read("COMMIT");
......
......@@ -28,7 +28,6 @@ import org.h2.schema.Sequence;
import org.h2.schema.TriggerObject;
import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableData;
import org.h2.table.TableView;
import org.h2.util.New;
......@@ -207,7 +206,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
String tempName = db.getTempTableName(session);
Column[] columns = table.getColumns();
ArrayList<Column> newColumns = New.arrayList();
TableData newTable = cloneTableStructure(columns, db, tempName, newColumns);
Table newTable = cloneTableStructure(columns, db, tempName, newColumns);
List<String> views;
try {
views = checkViews(table, newTable);
......@@ -246,7 +245,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
}
}
private TableData cloneTableStructure(Column[] columns, Database db, String tempName, ArrayList<Column> newColumns) {
private Table cloneTableStructure(Column[] columns, Database db, String tempName, ArrayList<Column> newColumns) {
for (Column col : columns) {
newColumns.add(col.getClone());
}
......@@ -282,7 +281,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
data.persistIndexes = table.isPersistIndexes();
data.create = true;
data.session = session;
TableData newTable = getSchema().createTable(data);
Table newTable = getSchema().createTable(data);
newTable.setComment(table.getComment());
StringBuilder buff = new StringBuilder();
buff.append(newTable.getCreateSQL());
......@@ -312,7 +311,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
newTable.removeChildrenAndResources(session);
execute(newTableSQL, true);
newTable = (TableData) newTableSchema.getTableOrView(session, newTableName);
newTable = newTableSchema.getTableOrView(session, newTableName);
ArrayList<String> triggers = New.arrayList();
for (DbObject child : table.getChildren()) {
if (child instanceof Sequence) {
......
......@@ -19,7 +19,7 @@ import org.h2.schema.Schema;
import org.h2.schema.Sequence;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.table.Table;
import org.h2.util.New;
import org.h2.value.DataType;
......@@ -33,7 +33,6 @@ public class CreateTable extends SchemaCommand {
private ArrayList<Prepared> constraintCommands = New.arrayList();
private IndexColumn[] pkColumns;
private boolean ifNotExists;
private boolean globalTemporary;
private boolean onCommitDrop;
private boolean onCommitTruncate;
private Query asQuery;
......@@ -137,10 +136,9 @@ public class CreateTable extends SchemaCommand {
data.id = getObjectId();
data.create = create;
data.session = session;
TableData table = getSchema().createTable(data);
Table table = getSchema().createTable(data);
table.setComment(comment);
table.setGlobalTemporary(globalTemporary);
if (data.temporary && !globalTemporary) {
if (data.temporary && !data.globalTemporary) {
if (onCommitDrop) {
table.setOnCommitDrop(true);
}
......@@ -236,7 +234,7 @@ public class CreateTable extends SchemaCommand {
}
public void setGlobalTemporary(boolean globalTemporary) {
this.globalTemporary = globalTemporary;
data.globalTemporary = globalTemporary;
}
/**
......@@ -265,4 +263,11 @@ public class CreateTable extends SchemaCommand {
this.sortedInsertMode = sortedInsertMode;
}
/**
* @param tableEngine the table engine to set
*/
public void setTableEngine(String tableEngine) {
data.tableEngine = tableEngine;
}
}
......@@ -42,6 +42,11 @@ public class CreateTableData {
*/
public boolean temporary;
/**
* Whether the table global temporary
*/
public boolean globalTemporary;
/**
* Whether the indexes should be persisted.
*/
......@@ -61,5 +66,10 @@ public class CreateTableData {
* The session.
*/
public Session session;
/**
* Table engine to use
*/
public String tableEngine;
}
......@@ -48,7 +48,6 @@ import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.MetaTable;
import org.h2.table.Table;
import org.h2.table.TableData;
import org.h2.table.TableLinkConnection;
import org.h2.table.TableView;
import org.h2.tools.DeleteDbFiles;
......@@ -111,7 +110,7 @@ public class Database implements DataHandler {
private int nextTempTableId;
private User systemUser;
private Session systemSession;
private TableData meta;
private Table meta;
private Index metaIdIndex;
private FileLock lock;
private WriterThread writer;
......
......@@ -594,7 +594,8 @@ public class Session extends SessionWithState implements SessionFactory {
if (SysProperties.CHECK) {
int lockMode = database.getLockMode();
if (lockMode != Constants.LOCK_MODE_OFF && !database.isMultiVersion()) {
if (locks.indexOf(log.getTable()) < 0 && !Table.TABLE_LINK.equals(log.getTable().getTableType())) {
if (locks.indexOf(log.getTable()) < 0 && !Table.TABLE_LINK.equals(log.getTable().getTableType())
&& !Table.EXTERNAL_TABLE_ENGINE.equals(log.getTable().getTableType())) {
DbException.throwInternalError();
}
}
......
......@@ -48,7 +48,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* not yet known
* @param newIndexType the index type
*/
void initBaseIndex(Table newTable, int id, String name, IndexColumn[] newIndexColumns, IndexType newIndexType) {
protected void initBaseIndex(Table newTable, int id, String name, IndexColumn[] newIndexColumns, IndexType newIndexType) {
initSchemaObjectBase(newTable.getSchema(), id, name, Trace.INDEX);
this.indexType = newIndexType;
this.table = newTable;
......
......@@ -70,7 +70,7 @@ public class HashIndex extends BaseIndex {
} else {
result = tableData.getRow(session, pos.intValue());
}
return new HashCursor(result);
return new SingleRowCursor(result);
}
public long getRowCount(Session session) {
......
......@@ -11,14 +11,16 @@ import org.h2.result.Row;
import org.h2.result.SearchRow;
/**
* The cursor for a hash index.
* At most one row can be accessed.
* The cursor with at most one row.
*/
public class HashCursor implements Cursor {
public class SingleRowCursor implements Cursor {
private Row row;
private boolean end;
HashCursor(Row row) {
/**
* @param row - the single row (if null then cursor is empty)
*/
public SingleRowCursor(Row row) {
this.row = row;
}
......
......@@ -49,7 +49,7 @@ public class ResultTempTable implements ResultExternal {
data.persistData = true;
data.create = true;
data.session = session;
table = schema.createTable(data);
table = (TableData) schema.createTable(data);
int indexId = session.getDatabase().allocateObjectId();
IndexColumn indexColumn = new IndexColumn();
indexColumn.column = column;
......
......@@ -9,6 +9,7 @@ package org.h2.schema;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -25,6 +26,7 @@ import org.h2.table.Table;
import org.h2.table.TableData;
import org.h2.table.TableLink;
import org.h2.util.New;
import org.h2.util.Utils;
/**
* A schema as created by the SQL statement
......@@ -482,11 +484,20 @@ public class Schema extends DbObjectBase {
* Add a table to the schema.
*
* @param data the create table information
* @return the created {@link TableData} object
* @return the created {@link Table} object
*/
public TableData createTable(CreateTableData data) {
public Table createTable(CreateTableData data) {
synchronized (database) {
data.schema = this;
if (data.tableEngine != null) {
TableEngine engine;
try {
engine = (TableEngine) Utils.loadUserClass(data.tableEngine).newInstance();
} catch (Exception e) {
throw DbException.convert(e);
}
return engine.createTable(data);
}
return new TableData(data);
}
}
......
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.table;
import org.h2.command.ddl.CreateTableData;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
/**
* @author Thomas Mueller
* @author Sergi Vladykin
*/
public abstract class RecreatableTable extends Table {
protected final String tableEngine;
private final boolean globalTemporary;
public RecreatableTable(CreateTableData data) {
super(data.schema, data.id, data.tableName, data.persistIndexes, data.persistData);
this.tableEngine = data.tableEngine;
this.globalTemporary = data.globalTemporary;
setTemporary(data.temporary);
Column[] cols = new Column[data.columns.size()];
data.columns.toArray(cols);
setColumns(cols);
}
@Override
public String getDropSQL() {
return "DROP TABLE IF EXISTS " + getSQL();
}
@Override
public String getCreateSQL() {
StatementBuilder buff = new StatementBuilder("CREATE ");
if (isTemporary()) {
if (isGlobalTemporary()) {
buff.append("GLOBAL ");
} else {
buff.append("LOCAL ");
}
buff.append("TEMPORARY ");
} else if (isPersistIndexes()) {
buff.append("CACHED ");
} else {
buff.append("MEMORY ");
}
buff.append("TABLE ").append(getSQL());
if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
}
buff.append("(\n ");
for (Column column : columns) {
buff.appendExceptFirst(",\n ");
buff.append(column.getCreateSQL());
}
buff.append("\n)");
if (tableEngine != null) {
buff.append("\nENGINE '");
buff.append(tableEngine);
buff.append("'");
}
if (!isPersistIndexes() && !isPersistData()) {
buff.append("\nNOT PERSISTENT");
}
return buff.toString();
}
public boolean isGlobalTemporary() {
return globalTemporary;
}
}
......@@ -75,6 +75,11 @@ public abstract class Table extends SchemaObjectBase {
*/
public static final String VIEW = "VIEW";
/**
* The table type name for external table engines.
*/
public static final String EXTERNAL_TABLE_ENGINE = "EXTERNAL";
/**
* The columns of this table.
*/
......@@ -101,7 +106,7 @@ public abstract class Table extends SchemaObjectBase {
private boolean onCommitDrop, onCommitTruncate;
private Row nullRow;
Table(Schema schema, int id, String name, boolean persistIndexes, boolean persistData) {
public Table(Schema schema, int id, String name, boolean persistIndexes, boolean persistData) {
initSchemaObjectBase(schema, id, name, Trace.TABLE);
this.persistIndexes = persistIndexes;
this.persistData = persistData;
......@@ -365,7 +370,7 @@ public abstract class Table extends SchemaObjectBase {
* @param session the session
* @return true if it is
*/
boolean isLockedExclusivelyBy(Session session) {
public boolean isLockedExclusivelyBy(Session session) {
return false;
}
......
......@@ -38,8 +38,6 @@ import org.h2.result.SortOrder;
import org.h2.schema.SchemaObject;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.CompareMode;
import org.h2.value.DataType;
import org.h2.value.Value;
......@@ -49,13 +47,12 @@ import org.h2.value.Value;
* in the database. The actual data is not kept here, instead it is kept in the
* indexes. There is at least one index, the scan index.
*/
public class TableData extends Table {
public class TableData extends RecreatableTable {
private Index scanIndex;
private long rowCount;
private volatile Session lockExclusive;
private HashSet<Session> lockShared = New.hashSet();
private Trace traceLock;
private boolean globalTemporary;
private final ArrayList<Index> indexes = New.arrayList();
private long lastModificationId;
private boolean containsLargeObject;
......@@ -69,19 +66,15 @@ public class TableData extends Table {
private boolean waitForLock;
public TableData(CreateTableData data) {
super(data.schema, data.id, data.tableName, data.persistIndexes, data.persistData);
Column[] cols = new Column[data.columns.size()];
data.columns.toArray(cols);
setColumns(cols);
setTemporary(data.temporary);
super(data);
if (data.persistData && database.isPersistent()) {
mainIndex = new PageDataIndex(this, data.id, IndexColumn.wrap(cols), IndexType.createScan(data.persistData), data.create, data.session);
mainIndex = new PageDataIndex(this, data.id, IndexColumn.wrap(getColumns()), IndexType.createScan(data.persistData), data.create, data.session);
scanIndex = mainIndex;
} else {
scanIndex = new ScanIndex(this, data.id, IndexColumn.wrap(cols), IndexType.createScan(data.persistData));
scanIndex = new ScanIndex(this, data.id, IndexColumn.wrap(getColumns()), IndexType.createScan(data.persistData));
}
indexes.add(scanIndex);
for (Column col : cols) {
for (Column col : getColumns()) {
if (DataType.isLargeObject(col.getType())) {
containsLargeObject = true;
memoryPerRow = Row.MEMORY_CALCULATE;
......@@ -554,40 +547,6 @@ public class TableData extends Table {
}
}
public String getDropSQL() {
return "DROP TABLE IF EXISTS " + getSQL();
}
public String getCreateSQL() {
StatementBuilder buff = new StatementBuilder("CREATE ");
if (isTemporary()) {
if (globalTemporary) {
buff.append("GLOBAL ");
} else {
buff.append("LOCAL ");
}
buff.append("TEMPORARY ");
} else if (isPersistIndexes()) {
buff.append("CACHED ");
} else {
buff.append("MEMORY ");
}
buff.append("TABLE ").append(getSQL());
if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
}
buff.append("(\n ");
for (Column column : columns) {
buff.appendExceptFirst(",\n ");
buff.append(column.getCreateSQL());
}
buff.append("\n)");
if (!isTemporary() && !isPersistIndexes() && !isPersistData()) {
buff.append("\nNOT PERSISTENT");
}
return buff.toString();
}
public boolean isLockedExclusively() {
return lockExclusive != null;
}
......@@ -686,14 +645,6 @@ public class TableData extends Table {
return Table.TABLE;
}
public void setGlobalTemporary(boolean globalTemporary) {
this.globalTemporary = globalTemporary;
}
public boolean isGlobalTemporary() {
return globalTemporary;
}
public long getMaxDataModificationId() {
return lastModificationId;
}
......
......@@ -52,6 +52,7 @@ import org.h2.test.db.TestSequence;
import org.h2.test.db.TestSessionsLocks;
import org.h2.test.db.TestSpaceReuse;
import org.h2.test.db.TestSpeed;
import org.h2.test.db.TestTableEngines;
import org.h2.test.db.TestTempTables;
import org.h2.test.db.TestTransaction;
import org.h2.test.db.TestTriggersConstraints;
......@@ -502,6 +503,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestSequence().runTest(this);
new TestSpaceReuse().runTest(this);
new TestSpeed().runTest(this);
new TestTableEngines().runTest(this);
new TestTempTables().runTest(this);
new TestTransaction().runTest(this);
new TestTriggersConstraints().runTest(this);
......
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.engine.Session;
import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.SingleRowCursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.IndexColumn;
import org.h2.table.RecreatableTable;
import org.h2.table.Table;
import org.h2.test.TestBase;
/**
* The class for external table engines mechanism testing.
*
* @author Sergi Vladykin
*/
public class TestTableEngines extends TestBase {
public void test() throws Exception {
if (!config.mvcc) {
deleteDb("tableEngine");
Connection conn = getConnection("tableEngine");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE t1(id int, name varchar) ENGINE '" + OneRowTableEngine.class.getName() + "'");
testStatements(stat);
stat.close();
conn.close();
if (!config.memory) {
conn = getConnection("tableEngine");
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT name FROM t1");
assertFalse(rs.next());
rs.close();
testStatements(stat);
stat.close();
conn.close();
}
deleteDb("tableEngine");
}
}
private void testStatements(Statement stat) throws SQLException {
assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(2,'aaa')"), 1);
assertEquals(stat.executeUpdate("UPDATE t1 SET name = 'bbb' WHERE id=2"), 1);
assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(3,'ccc')"), 1);
assertEquals(stat.executeUpdate("DELETE FROM t1 WHERE id=2"), 0);
assertEquals(stat.executeUpdate("DELETE FROM t1 WHERE id=3"), 1);
ResultSet rs = stat.executeQuery("SELECT name FROM t1");
assertFalse(rs.next());
rs.close();
assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(2,'aaa')"), 1);
assertEquals(stat.executeUpdate("UPDATE t1 SET name = 'bbb' WHERE id=2"), 1);
assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(3,'ccc')"), 1);
rs = stat.executeQuery("SELECT name FROM t1");
assertTrue(rs.next());
assertEquals(rs.getString(1), "ccc");
assertFalse(rs.next());
rs.close();
}
/**
* Execute only this test
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
TestBase.createCaller().init().test();
}
/**
* Test table factory
*/
public static class OneRowTableEngine implements TableEngine {
/**
* Table implementation with one row
*/
private static class OneRowTable extends RecreatableTable {
/**
* One row scan index
*/
private class Scan extends BaseIndex {
Scan(Table table) {
initBaseIndex(table, table.getId(), table.getName() + "_SCAN",
IndexColumn.wrap(table.getColumns()), IndexType.createScan(false));
}
public long getRowCountApproximation() {
return table.getRowCountApproximation();
}
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) {
return 0;
}
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
return new SingleRowCursor(row);
}
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
return new SingleRowCursor(row);
}
@Override
public void close(Session session) {
// do nothing
}
@Override
public boolean canGetFirstOrLast() {
return true;
}
@Override
public void add(Session session, Row r) {
// do nothing
}
}
volatile Row row;
private final Index scanIndex;
OneRowTable(CreateTableData data) {
super(data);
scanIndex = new Scan(this);
}
@Override
public Index addIndex(Session session, String indexName, int indexId, IndexColumn[] cols,
IndexType indexType, boolean create, String indexComment) {
return null;
}
@Override
public void addRow(Session session, Row r) {
this.row = r;
}
@Override
public boolean canDrop() {
return true;
}
@Override
public boolean canGetRowCount() {
return true;
}
@Override
public void checkSupportAlter() {
// do nothing
}
@Override
public void close(Session session) {
// do nothing
}
@Override
public ArrayList<Index> getIndexes() {
return null;
}
@Override
public long getMaxDataModificationId() {
return 0;
}
@Override
public long getRowCount(Session session) {
return getRowCountApproximation();
}
@Override
public long getRowCountApproximation() {
return row == null ? 0 : 1;
}
@Override
public Index getScanIndex(Session session) {
return scanIndex;
}
@Override
public String getTableType() {
return EXTERNAL_TABLE_ENGINE;
}
@Override
public Index getUniqueIndex() {
return null;
}
@Override
public boolean isDeterministic() {
return false;
}
@Override
public boolean isLockedExclusively() {
return false;
}
@Override
public void lock(Session session, boolean exclusive, boolean force) {
// do nothing
}
@Override
public void removeRow(Session session, Row r) {
this.row = null;
}
@Override
public void truncate(Session session) {
row = null;
}
@Override
public void unlock(Session s) {
// do nothing
}
@Override
public void checkRename() {
// do nothing
}
}
/**
* Create new OneRowTable
*/
public OneRowTable createTable(final CreateTableData data) {
return new OneRowTable(data);
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论