提交 3a4a3dbf authored 作者: Niklas Mehner's avatar Niklas Mehner

Implemented:

* "Create or replace" for synonyms
* Meta-Data support
* "Drop synonym" statement
* Support for synonyms in different schema
* added more tests.
上级 0e36eb37
......@@ -461,6 +461,11 @@ public interface CommandInterface {
*/
int CREATE_SYNONYM = 86;
/**
* The type of a DROP SYNONYM statement.
*/
int DROP_SYNONYM = 87;
/**
* Get command type.
......
......@@ -1469,6 +1469,14 @@ public class Parser {
return parseDropUserDataType();
} else if (readIf("AGGREGATE")) {
return parseDropAggregate();
} else if (readIf("SYNONYM")) {
boolean ifExists = readIfExists(false);
String synonymName = readIdentifierWithSchema();
DropSynonym command = new DropSynonym(session, getSchema());
command.setSynonymName(synonymName);
ifExists = readIfExists(ifExists);
command.setIfExists(ifExists);
return command;
}
throw getSyntaxError();
}
......@@ -4227,7 +4235,7 @@ public class Parser {
}
return parseCreateTable(false, false, cached);
} else if (readIf("SYNONYM")) {
return parseCreateSynonym();
return parseCreateSynonym(orReplace);
} else {
boolean hash = false, primaryKey = false;
boolean unique = false, spatial = false;
......@@ -6058,19 +6066,21 @@ public class Parser {
return command;
}
private CreateSynonym parseCreateSynonym() {
private CreateSynonym parseCreateSynonym(boolean orReplace) {
boolean ifNotExists = readIfNoExists();
String name = readIdentifierWithSchema();
Schema synonymSchema = getSchema();
read("FOR");
String tableName = readIdentifierWithSchema();
Schema schema = getSchema();
CreateSynonym command = new CreateSynonym(session, schema);
Schema targetSchema = getSchema();
CreateSynonym command = new CreateSynonym(session, synonymSchema);
command.setName(name);
command.setSynonymFor(tableName);
command.setSynonymForSchema(targetSchema);
command.setComment(readCommentIf());
command.setIfNotExists(ifNotExists);
command.setOrReplace(orReplace);
return command;
}
......
......@@ -26,6 +26,7 @@ public class CreateSynonym extends SchemaCommand {
private final CreateSynonymData data = new CreateSynonymData();
private boolean ifNotExists;
private boolean orReplace;
private String comment;
public CreateSynonym(Session session, Schema schema) {
......@@ -40,68 +41,64 @@ public class CreateSynonym extends SchemaCommand {
data.synonymFor = tableName;
}
public void setSynonymForSchema(Schema synonymForSchema) {
data.synonymForSchema = synonymForSchema;
}
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}
public void setOrReplace(boolean orReplace) { this.orReplace = orReplace; }
@Override
public int update() {
if (!transactional) {
session.commit(true);
}
Database db = session.getDatabase();
data.session = session;
// TODO: Check when/if meta data is unlocked...
db.lockMeta(session);
if (getSchema().findTableOrView(session, data.synonymName) != null) {
if (ifNotExists) {
Table old = getSchema().findTableOrView(session, data.synonymName);
if (old != null) {
if (orReplace && old instanceof TableSynonym) {
// ok, we replacing the existing synonym
} else if (ifNotExists && old instanceof TableSynonym) {
return 0;
} else {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.synonymName);
}
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;
validateBackingTableExists();
TableSynonym table;
if (old != null) {
table = (TableSynonym) old;
data.schema = table.getSchema();
table.updateData(data);
table.setModified();
} else {
data.id = getObjectId();
table = getSchema().createSynonym(data);
table.setComment(comment);
db.addSchemaObject(session, table);
}
return 0;
}
private void validateBackingTableExists() {
// This call throws an exception if the table does not exist.
if (data.synonymForSchema.findTableOrView(session, data.synonymFor) == null) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1,
data.synonymForSchema.getName() + "." + data.synonymFor);
}
}
public void setComment(String comment) {
this.comment = comment;
}
......@@ -111,4 +108,5 @@ public class CreateSynonym extends SchemaCommand {
return CommandInterface.CREATE_SYNONYM;
}
}
......@@ -28,6 +28,9 @@ public class CreateSynonymData {
*/
public String synonymFor;
/** Schema synonymFor is located in. */
public Schema synonymForSchema;
/**
* The object id.
*/
......@@ -38,5 +41,4 @@ public class CreateSynonymData {
*/
public Session session;
}
......@@ -108,7 +108,8 @@ public class MetaTable extends Table {
private static final int LOCKS = 26;
private static final int SESSION_STATE = 27;
private static final int QUERY_STATISTICS = 28;
private static final int META_TABLE_TYPE_COUNT = QUERY_STATISTICS + 1;
private static final int SYNONYMS = 29;
private static final int META_TABLE_TYPE_COUNT = SYNONYMS + 1;
private final int type;
private final int indexColumn;
......@@ -537,6 +538,20 @@ public class MetaTable extends Table {
);
break;
}
case SYNONYMS: {
setObjectName("SYNONYMS");
cols = createColumns(
"SYNONYM_CATALOG",
"SYNONYM_SCHEMA",
"SYNONYM_NAME",
"SYNONYM_FOR",
"STATUS",
"REMARKS",
"ID INT"
);
indexColumnName = "SYNONYM_NAME";
break;
}
default:
throw DbException.throwInternalError("type="+type);
}
......@@ -1858,6 +1873,32 @@ public class MetaTable extends Table {
}
break;
}
case SYNONYMS: {
for (Table table : getAllTables(session)) {
if (!table.getTableType().equals(Table.SYNONYM)) {
continue;
}
String synonymName = identifier(table.getName());
TableSynonym synonym = (TableSynonym) table;
add(rows,
// SYNONYM_CATALOG
catalog,
// SYNONYM_SCHEMA
identifier(table.getSchema().getName()),
// SYNONYM_NAME
synonymName,
// SYNONYM_FOR
synonym.getSynonymForName(),
// STATUS
synonym.isInvalid() ? "INVALID" : "VALID",
// REMARKS
replaceNullWithEmpty(synonym.getComment()),
// ID
"" + synonym.getId()
);
}
break;
}
default:
DbException.throwInternalError("type="+type);
}
......
......@@ -17,36 +17,45 @@ import java.util.HashSet;
*/
public class TableSynonym extends Table {
private final Table synonymFor;
private CreateSynonymData data;
public TableSynonym(CreateSynonymData data) {
super(data.schema, data.id, data.synonymName, false, false);
this.synonymFor = data.schema.getTableOrView(data.session, data.synonymFor);
this.data = data;
}
public void updateData(CreateSynonymData data) {
this.data = data;
}
private Table getSynonymFor() {
return data.synonymForSchema.getTableOrView(data.session, data.synonymFor);
}
@Override
public void addDependencies(HashSet<DbObject> dependencies) {
dependencies.add(synonymFor);
// no dependency. A table synonym will not prevent the backing table from being dropped, but
// will become invalid instead.
}
@Override
public Column[] getColumns() {
return synonymFor.getColumns();
return getSynonymFor().getColumns();
}
@Override
public boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc) {
return synonymFor.lock(session, exclusive, forceLockEvenInMvcc);
return getSynonymFor().lock(session, exclusive, forceLockEvenInMvcc);
}
@Override
public void close(Session session) {
synonymFor.close(session);
getSynonymFor().close(session);
}
@Override
public void unlock(Session s) {
synonymFor.unlock(s);
getSynonymFor().unlock(s);
}
@Override
......@@ -56,17 +65,17 @@ public class TableSynonym extends Table {
@Override
public void removeRow(Session session, Row row) {
synonymFor.removeRow(session, row);
getSynonymFor().removeRow(session, row);
}
@Override
public void truncate(Session session) {
synonymFor.truncate(session);
getSynonymFor().truncate(session);
}
@Override
public void addRow(Session session, Row row) {
synonymFor.addRow(session, row);
getSynonymFor().addRow(session, row);
}
@Override
......@@ -81,37 +90,37 @@ public class TableSynonym extends Table {
@Override
public Index getScanIndex(Session session) {
return synonymFor.getScanIndex(session);
return getSynonymFor().getScanIndex(session);
}
@Override
public Index getUniqueIndex() {
return synonymFor.getUniqueIndex();
return getSynonymFor().getUniqueIndex();
}
@Override
public ArrayList<Index> getIndexes() {
return synonymFor.getIndexes();
return getSynonymFor().getIndexes();
}
@Override
public boolean isLockedExclusively() {
return synonymFor.isLockedExclusively();
return getSynonymFor().isLockedExclusively();
}
@Override
public long getMaxDataModificationId() {
return synonymFor.getMaxDataModificationId();
return getSynonymFor().getMaxDataModificationId();
}
@Override
public boolean isDeterministic() {
return synonymFor.isDeterministic();
return getSynonymFor().isDeterministic();
}
@Override
public boolean canGetRowCount() {
return synonymFor.canGetRowCount();
return getSynonymFor().canGetRowCount();
}
@Override
......@@ -121,27 +130,27 @@ public class TableSynonym extends Table {
@Override
public long getRowCount(Session session) {
return synonymFor.getRowCount(session);
return getSynonymFor().getRowCount(session);
}
@Override
public long getRowCountApproximation() {
return synonymFor.getRowCountApproximation();
return getSynonymFor().getRowCountApproximation();
}
@Override
public long getDiskSpaceUsed() {
return synonymFor.getDiskSpaceUsed();
return getSynonymFor().getDiskSpaceUsed();
}
@Override
public String getCreateSQL() {
return "CREATE SYNONYM " + getName() + " FOR " + synonymFor;
return "CREATE SYNONYM " + getName() + " FOR " + data.synonymForSchema.getName() + "." + data.synonymFor;
}
@Override
public String getDropSQL() {
return "DROP SYNONYM " + getName() + " FOR " + synonymFor;
return "DROP SYNONYM " + getName();
}
@Override
......@@ -150,6 +159,20 @@ public class TableSynonym extends Table {
}
public boolean canTruncate() {
return synonymFor.canTruncate();
return getSynonymFor().canTruncate();
}
public String getSynonymForName() {
return data.synonymFor;
}
public boolean isInvalid() {
try {
getSynonymFor();
return false;
} catch (DbException e) {
return true;
}
}
}
......@@ -5,9 +5,15 @@
*/
package org.h2.test.db;
import org.h2.engine.Constants;
import org.h2.jdbc.JdbcSQLException;
import org.h2.test.TestBase;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Test for the read-only database feature.
......@@ -24,18 +30,158 @@ public class TestSynonymForTable extends TestBase {
}
@Override
public void test() throws Exception {
public void test() throws SQLException {
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
testExistingTableName();
testCreateForUnknownTable();
testMetaData();
testCreateOrReplace();
testCreateOrReplaceExistingTable();
testSynonymInDifferentSchema();
testReopenDatabase();
// TODO: test drop synonym.
testDropSynonym();
testDropTable();
testDropSchema();
}
private void testDropSchema() throws SQLException {
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
stat.execute("CREATE SCHEMA IF NOT EXISTS s1");
stat.execute("CREATE TABLE IF NOT EXISTS s1.backingtable(id INT PRIMARY KEY)");
stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR s1.backingtable");
stat.execute("DROP SCHEMA s1");
assertThrows(JdbcSQLException.class, stat).execute("SELECT id FROM testsynonym");
}
private void testDropTable() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
Statement stat = conn.createStatement();
stat.execute("DROP TABLE backingtable");
// Backing table does not exist anymore.
assertThrows(JdbcSQLException.class, stat).execute("SELECT id FROM testsynonym");
// Meta data should show INVALID
ResultSet synonyms = conn.createStatement().executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYNONYMS WHERE SYNONYM_NAME='TESTSYNONYM'");
assertTrue(synonyms.next());
assertEquals("TESTSYNONYM", synonyms.getString("SYNONYM_NAME"));
assertEquals("INVALID", synonyms.getString("STATUS"));
conn.close();
// Reopending should work with invalid synonym
Connection conn2 = getConnection("synonym");
assertThrows(JdbcSQLException.class, stat).execute("SELECT id FROM testsynonym");
conn2.close();
}
private void testDropSynonym() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
Statement stat = conn.createStatement();
stat.execute("DROP SYNONYM testsynonym");
// Synonym does not exist anymore.
assertThrows(JdbcSQLException.class, stat).execute("SELECT id FROM testsynonym");
// Dropping with "if exists" should succeed even if the synonym does not exist anymore.
stat.execute("DROP SYNONYM IF EXISTS testsynonym");
// Without "if exists" the command should fail.
assertThrows(JdbcSQLException.class, stat).execute("DROP SYNONYM testsynonym");
}
private void testSynonymInDifferentSchema() throws SQLException {
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
stat.execute("CREATE SCHEMA IF NOT EXISTS s1");
stat.execute("CREATE TABLE IF NOT EXISTS s1.backingtable(id INT PRIMARY KEY)");
stat.execute("TRUNCATE TABLE s1.backingtable");
stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR s1.backingtable");
stat.execute("INSERT INTO s1.backingtable VALUES(15)");
assertSynonymContains(conn, 15);
}
private void testCreateOrReplaceExistingTable() throws SQLException {
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)");
assertThrows(JdbcSQLException.class, stat).execute("CREATE OR REPLACE SYNONYM backingtable FOR backingtable");
conn.close();
}
private void testCreateOrReplace() throws SQLException {
// start with a fresh db so the first create or replace has to actually create the synonym.
deleteDb("synonym");
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)");
stat.execute("CREATE TABLE IF NOT EXISTS backingtable2(id INT PRIMARY KEY)");
stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable");
insertIntoBackingTable(conn, 17);
ResultSet rs = stat.executeQuery("SELECT id FROM testsynonym");
assertTrue(rs.next());
assertEquals(17, rs.getInt(1));
stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable2");
// Should not return a result, since backingtable2 is empty.
ResultSet rs2 = stat.executeQuery("SELECT id FROM testsynonym");
assertFalse(rs2.next());
conn.close();
deleteDb("synonym");
}
private void testMetaData() throws SQLException {
Connection conn = getConnection("synonym");
createTableWithSynonym(conn);
ResultSet tables = conn.getMetaData().getTables(null, Constants.SCHEMA_MAIN, null,
new String[]{"SYNONYM"});
assertTrue(tables.next());
assertEquals(tables.getString("TABLE_NAME"), "TESTSYNONYM");
assertEquals(tables.getString("TABLE_TYPE"), "SYNONYM");
assertFalse(tables.next());
ResultSet synonyms = conn.createStatement().executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYNONYMS");
assertTrue(synonyms.next());
assertEquals("SYNONYM", synonyms.getString("SYNONYM_CATALOG"));
assertEquals("PUBLIC", synonyms.getString("SYNONYM_SCHEMA"));
assertEquals("TESTSYNONYM", synonyms.getString("SYNONYM_NAME"));
assertEquals("BACKINGTABLE", synonyms.getString("SYNONYM_FOR"));
assertEquals("VALID", synonyms.getString("STATUS"));
assertEquals("", synonyms.getString("REMARKS"));
assertTrue(synonyms.getString("ID") != null);
assertFalse(synonyms.next());
conn.close();
}
private void testCreateForUnknownTable() throws SQLException {
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
assertThrows(JdbcSQLException.class, stat).execute("CREATE SYNONYM someSynonym FOR nonexistingTable");
conn.close();
}
private void testExistingTableName() throws SQLException {
Connection conn = getConnection("synonym");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)");
assertThrows(JdbcSQLException.class, stat).execute("CREATE SYNONYM backingtable FOR backingtable");
conn.close();
}
/**
......@@ -125,14 +271,14 @@ public class TestSynonymForTable extends TestBase {
private void insertIntoSynonym(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement(
"insert into testsynonym values(?)");
"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(?)");
"INSERT INTO backingtable VALUES(?)");
prep.setInt(1, id);
prep.execute();
}
......@@ -140,7 +286,7 @@ public class TestSynonymForTable extends TestBase {
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("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable");
stat.execute("TRUNCATE TABLE backingtable");
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论