提交 088746ee authored 作者: Niklas Mehner's avatar Niklas Mehner

Implement basic features of table synonym.

上级 a655cd87
......@@ -456,6 +456,12 @@ public interface CommandInterface {
*/
int ALTER_TABLE_RENAME_CONSTRAINT = 85;
/**
* The type of a CREATE SYNONYM statement.
*/
int CREATE_SYNONYM = 86;
/**
* Get command type.
*
......
......@@ -37,6 +37,7 @@ import org.h2.command.ddl.CreateLinkedTable;
import org.h2.command.ddl.CreateRole;
import org.h2.command.ddl.CreateSchema;
import org.h2.command.ddl.CreateSequence;
import org.h2.command.ddl.CreateSynonym;
import org.h2.command.ddl.CreateTable;
import org.h2.command.ddl.CreateTableData;
import org.h2.command.ddl.CreateTrigger;
......@@ -4225,6 +4226,8 @@ public class Parser {
cached = database.getDefaultTableType() == Table.TYPE_CACHED;
}
return parseCreateTable(false, false, cached);
} else if (readIf("SYNONYM")) {
return parseCreateSynonym();
} else {
boolean hash = false, primaryKey = false;
boolean unique = false, spatial = false;
......@@ -6055,6 +6058,22 @@ public class Parser {
return command;
}
private CreateSynonym parseCreateSynonym() {
boolean ifNotExists = readIfNoExists();
String name = readIdentifierWithSchema();
read("FOR");
String tableName = readIdentifierWithSchema();
Schema schema = getSchema();
CreateSynonym command = new CreateSynonym(session, schema);
command.setName(name);
command.setSynonymFor(tableName);
command.setComment(readCommentIf());
command.setIfNotExists(ifNotExists);
return command;
}
private static int getCompareType(int tokenType) {
switch (tokenType) {
case EQUAL:
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.ddl;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.schema.Schema;
import org.h2.table.TableSynonym;
import org.h2.table.Table;
import org.h2.util.New;
import java.util.HashSet;
/**
* This class represents the statement
* CREATE SYNONYM
*/
public class CreateSynonym extends SchemaCommand {
private final CreateSynonymData data = new CreateSynonymData();
private boolean ifNotExists;
private String comment;
public CreateSynonym(Session session, Schema schema) {
super(session, schema);
}
public void setName(String name) {
data.synonymName = name;
}
public void setSynonymFor(String tableName) {
data.synonymFor = tableName;
}
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}
@Override
public int update() {
if (!transactional) {
session.commit(true);
}
Database db = session.getDatabase();
// TODO: Check when/if meta data is unlocked...
db.lockMeta(session);
if (getSchema().findTableOrView(session, data.synonymName) != null) {
if (ifNotExists) {
return 0;
}
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.synonymName);
}
data.id = getObjectId();
data.session = session;
TableSynonym table = getSchema().createSynonym(data);
table.setComment(comment);
db.addSchemaObject(session, table);
try {
HashSet<DbObject> set = New.hashSet();
set.clear();
table.addDependencies(set);
for (DbObject obj : set) {
if (obj == table) {
continue;
}
if (obj.getType() == DbObject.TABLE_OR_VIEW) {
if (obj instanceof Table) {
Table t = (Table) obj;
if (t.getId() > table.getId()) {
throw DbException.get(
ErrorCode.FEATURE_NOT_SUPPORTED_1,
"TableSynonym depends on another table " +
"with a higher ID: " + t +
", this is currently not supported, " +
"as it would prevent the database from " +
"being re-opened");
}
}
}
}
} catch (DbException e) {
db.checkPowerOff();
db.removeSchemaObject(session, table);
if (!transactional) {
session.commit(true);
}
throw e;
}
return 0;
}
public void setComment(String comment) {
this.comment = comment;
}
@Override
public int getType() {
return CommandInterface.CREATE_SYNONYM;
}
}
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.ddl;
import org.h2.engine.Session;
import org.h2.schema.Schema;
/**
* The data required to create a synonym.
*/
public class CreateSynonymData {
/**
* The schema.
*/
public Schema schema;
/**
* The synonyms name.
*/
public String synonymName;
/**
* The name of the table the synonym is created for.
*/
public String synonymFor;
/**
* The object id.
*/
public int id;
/**
* The session.
*/
public Session session;
}
......@@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.command.ddl.CreateSynonymData;
import org.h2.command.ddl.CreateTableData;
import org.h2.constraint.Constraint;
import org.h2.engine.Database;
......@@ -24,6 +25,7 @@ import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.mvstore.db.MVTableEngine;
import org.h2.table.RegularTable;
import org.h2.table.TableSynonym;
import org.h2.table.Table;
import org.h2.table.TableLink;
import org.h2.util.New;
......@@ -214,12 +216,12 @@ public class Schema extends DbObjectBase {
*/
public void add(SchemaObject obj) {
if (SysProperties.CHECK && obj.getSchema() != this) {
DbException.throwInternalError("wrong schema");
throw DbException.throwInternalError("wrong schema");
}
String name = obj.getName();
HashMap<String, SchemaObject> map = getMap(obj.getType());
if (SysProperties.CHECK && map.get(name) != null) {
DbException.throwInternalError("object already exists: " + name);
throw DbException.throwInternalError("object already exists: " + name);
}
map.put(name, obj);
freeUniqueName(name);
......@@ -236,10 +238,10 @@ public class Schema extends DbObjectBase {
HashMap<String, SchemaObject> map = getMap(type);
if (SysProperties.CHECK) {
if (!map.containsKey(obj.getName())) {
DbException.throwInternalError("not found: " + obj.getName());
throw DbException.throwInternalError("not found: " + obj.getName());
}
if (obj.getName().equals(newName) || map.containsKey(newName)) {
DbException.throwInternalError("object already exists: " + newName);
throw DbException.throwInternalError("object already exists: " + newName);
}
}
obj.checkRename();
......@@ -592,6 +594,14 @@ public class Schema extends DbObjectBase {
}
}
public TableSynonym createSynonym(CreateSynonymData data) {
synchronized (database) {
database.lockMeta(data.session);
data.schema = this;
return new TableSynonym(data);
}
}
/**
* Add a linked table to the schema.
*
......
......@@ -75,6 +75,11 @@ public abstract class Table extends SchemaObjectBase {
*/
public static final String VIEW = "VIEW";
/**
* The table type name for synonyms.
*/
public static final String SYNONYM = "SYNONYM";
/**
* The table type name for external table engines.
*/
......@@ -621,7 +626,7 @@ public abstract class Table extends SchemaObjectBase {
}
public Row getTemplateRow() {
return database.createRow(new Value[columns.length], Row.MEMORY_CALCULATE);
return database.createRow(new Value[getColumns().length], Row.MEMORY_CALCULATE);
}
/**
......@@ -632,9 +637,9 @@ public abstract class Table extends SchemaObjectBase {
*/
public SearchRow getTemplateSimpleRow(boolean singleColumn) {
if (singleColumn) {
return new SimpleRowValue(columns.length);
return new SimpleRowValue(getColumns().length);
}
return new SimpleRow(new Value[columns.length]);
return new SimpleRow(new Value[getColumns().length]);
}
Row getNullRow() {
......@@ -642,7 +647,7 @@ public abstract class Table extends SchemaObjectBase {
if (row == null) {
// Here can be concurrently produced more than one row, but it must
// be ok.
Value[] values = new Value[columns.length];
Value[] values = new Value[getColumns().length];
Arrays.fill(values, ValueNull.INSTANCE);
nullRow = row = database.createRow(values, 1);
}
......@@ -665,7 +670,7 @@ public abstract class Table extends SchemaObjectBase {
* @return the column
*/
public Column getColumn(int index) {
return columns[index];
return getColumns()[index];
}
/**
......@@ -769,9 +774,9 @@ public abstract class Table extends SchemaObjectBase {
* @param row the row
*/
public void validateConvertUpdateSequence(Session session, Row row) {
for (int i = 0; i < columns.length; i++) {
for (int i = 0; i < getColumns().length; i++) {
Value value = row.getValue(i);
Column column = columns[i];
Column column = getColumns()[i];
Value v2;
if (column.getComputed()) {
// force updating the value
......
package org.h2.table;
import org.h2.command.ddl.CreateSynonymData;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.result.Row;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Sysonym for an existing table or view. All DML requests are forwarded to the backing table. Adding indices
* to a synonym or altering the table is not supported.
*/
public class TableSynonym extends Table {
private final Table synonymFor;
public TableSynonym(CreateSynonymData data) {
super(data.schema, data.id, data.synonymName, false, false);
this.synonymFor = data.schema.getTableOrView(data.session, data.synonymFor);
}
@Override
public void addDependencies(HashSet<DbObject> dependencies) {
dependencies.add(synonymFor);
}
@Override
public Column[] getColumns() {
return synonymFor.getColumns();
}
@Override
public boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc) {
return synonymFor.lock(session, exclusive, forceLockEvenInMvcc);
}
@Override
public void close(Session session) {
synonymFor.close(session);
}
@Override
public void unlock(Session s) {
synonymFor.unlock(s);
}
@Override
public Index addIndex(Session session, String indexName, int indexId, IndexColumn[] cols, IndexType indexType, boolean create, String indexComment) {
throw DbException.getUnsupportedException("SYNONYM");
}
@Override
public void removeRow(Session session, Row row) {
synonymFor.removeRow(session, row);
}
@Override
public void truncate(Session session) {
synonymFor.truncate(session);
}
@Override
public void addRow(Session session, Row row) {
synonymFor.addRow(session, row);
}
@Override
public void checkSupportAlter() {
throw DbException.getUnsupportedException("SYNONYM");
}
@Override
public String getTableType() {
return SYNONYM;
}
@Override
public Index getScanIndex(Session session) {
return synonymFor.getScanIndex(session);
}
@Override
public Index getUniqueIndex() {
return synonymFor.getUniqueIndex();
}
@Override
public ArrayList<Index> getIndexes() {
return synonymFor.getIndexes();
}
@Override
public boolean isLockedExclusively() {
return synonymFor.isLockedExclusively();
}
@Override
public long getMaxDataModificationId() {
return synonymFor.getMaxDataModificationId();
}
@Override
public boolean isDeterministic() {
return synonymFor.isDeterministic();
}
@Override
public boolean canGetRowCount() {
return synonymFor.canGetRowCount();
}
@Override
public boolean canDrop() {
return false;
}
@Override
public long getRowCount(Session session) {
return synonymFor.getRowCount(session);
}
@Override
public long getRowCountApproximation() {
return synonymFor.getRowCountApproximation();
}
@Override
public long getDiskSpaceUsed() {
return synonymFor.getDiskSpaceUsed();
}
@Override
public String getCreateSQL() {
return "CREATE SYNONYM " + getName() + " FOR " + synonymFor;
}
@Override
public String getDropSQL() {
return "DROP SYNONYM " + getName() + " FOR " + synonymFor;
}
@Override
public void checkRename() {
throw DbException.getUnsupportedException("SYNONYM");
}
public boolean canTruncate() {
return synonymFor.canTruncate();
}
}
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import org.h2.test.TestBase;
import java.sql.*;
/**
* Test for the read-only database feature.
*/
public class TestSynonymForTable extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testSelectFromSynonym();
testInsertIntoSynonym();
testDeleteFromSynonym();
testTruncateSynonym();
// TODO: check create existing tablename
// TODO: check create for non existing table
// TODO: Test Meta Data
// TODO: CREATE OR REPLACE TableSynonym.
// TODO: Check schema name in synonym and table
testReopenDatabase();
// TODO: test drop synonym.
}
/**
* Make sure, that the schema changes are persisted when reopening the database
*/
private void testReopenDatabase() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
insertIntoBackingTable(conn, 9);
conn.close();
Connection conn2 = getConnection("synonym");
assertSynonymContains(conn2, 9);
conn2.close();
}
private void testTruncateSynonym() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
insertIntoBackingTable(conn, 7);
assertBackingTableContains(conn, 7);
conn.createStatement().execute("TRUNCATE TABLE testsynonym");
assertBackingTableIsEmpty(conn);
conn.close();
}
private void testDeleteFromSynonym() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
insertIntoBackingTable(conn, 7);
assertBackingTableContains(conn, 7);
deleteFromSynonym(conn, 7);
assertBackingTableIsEmpty(conn);
conn.close();
}
private void deleteFromSynonym(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement(
"DELETE FROM testsynonym WHERE id = ?");
prep.setInt(1, id);
prep.execute();
}
private void assertBackingTableIsEmpty(Connection conn) throws SQLException {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT id FROM backingtable");
assertFalse(rs.next());
}
private void testInsertIntoSynonym() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
insertIntoSynonym(conn, 5);
assertBackingTableContains(conn, 5);
conn.close();
}
private void assertBackingTableContains(Connection conn, int testValue) throws SQLException {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT id FROM backingtable");
assertTrue(rs.next());
assertEquals(testValue, rs.getInt(1));
assertFalse(rs.next());
}
private void testSelectFromSynonym() throws SQLException {
deleteDb("synonym");
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
insertIntoBackingTable(conn, 1);
assertSynonymContains(conn, 1);
conn.close();
}
private void assertSynonymContains(Connection conn, int id) throws SQLException {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT id FROM testsynonym");
assertTrue(rs.next());
assertEquals(id, rs.getInt(1));
assertFalse(rs.next());
}
private void insertIntoSynonym(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement(
"insert into testsynonym values(?)");
prep.setInt(1, id);
prep.execute();
}
private void insertIntoBackingTable(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement(
"insert into backingtable values(?)");
prep.setInt(1, id);
prep.execute();
}
private void createTableWithSynonym(Connection conn) throws SQLException {
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)");
stat.execute("CREATE SYNONYM IF NOT EXISTS testsynonym FOR backingtable");
stat.execute("TRUNCATE TABLE backingtable");
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论