提交 8382475f authored 作者: sainsbury.kerry's avatar sainsbury.kerry

DROP VIEW now supports the CASCADE and RESTRICT clauses

CREATE VIEW now supports the OR REPLACE clause
上级 63264225
...@@ -748,7 +748,7 @@ DROP USER TOM ...@@ -748,7 +748,7 @@ DROP USER TOM
" "
"Commands (DDL)","DROP VIEW"," "Commands (DDL)","DROP VIEW","
DROP VIEW [ IF EXISTS ] viewName [RESTRICT | CASCADE ] DROP VIEW [ IF EXISTS ] viewName [ RESTRICT | CASCADE ]
"," ","
Drops a view, and all dependent views if the CASCADE clause is used. Drops a view, and all dependent views if the CASCADE clause is used.
The command will fail if dependent views exist and the RESTRICT clause is used. This is the default. The command will fail if dependent views exist and the RESTRICT clause is used. This is the default.
......
...@@ -35,6 +35,8 @@ Change Log ...@@ -35,6 +35,8 @@ Change Log
</li><li>Version 1.2.137 could still not be converted to Java 1.4 </li><li>Version 1.2.137 could still not be converted to Java 1.4
(because the Retrotranslator doesn't support BigDecimal.precision). (because the Retrotranslator doesn't support BigDecimal.precision).
</li><li>Cluster: non-admin users could not connect when one of the cluster node was stopped. Issue 206. </li><li>Cluster: non-admin users could not connect when one of the cluster node was stopped. Issue 206.
</li><li>DROP VIEW now supports the CASCADE and RESTRICT clauses (patch from Kerry Sainsbury)
</li><li>CREATE VIEW now supports the OR REPLACE clause (patch from Kerry Sainsbury)
</li></ul> </li></ul>
<h2>Version 1.2.137 (2010-06-06)</h2> <h2>Version 1.2.137 (2010-06-06)</h2>
...@@ -383,7 +385,7 @@ Change Log ...@@ -383,7 +385,7 @@ Change Log
This could cause the server-less multi-connection mode to fail. This could cause the server-less multi-connection mode to fail.
</li><li>Result sets larger than 2 GB threw an exception "Negative seek offset". Fixed. </li><li>Result sets larger than 2 GB threw an exception "Negative seek offset". Fixed.
</li><li>If the system property h2.check was set to false, an ArrayIndexOutOfBoundsException could occur. </li><li>If the system property h2.check was set to false, an ArrayIndexOutOfBoundsException could occur.
</li><li>Alter table is now supported even if a table has views defined. </li><li>Alter table is now supported even if a table has views defined. (Patch from Kerry Sainsbury).
</li><li>Fulltext search: exceptions within the fulltext search package had the wrong SQL state. </li><li>Fulltext search: exceptions within the fulltext search package had the wrong SQL state.
</li><li>The Lucene fulltext search ignored transaction rollback. Fixed using a trigger on rollback. </li><li>The Lucene fulltext search ignored transaction rollback. Fixed using a trigger on rollback.
</li><li>Trigger can now be called on rollback. </li><li>Trigger can now be called on rollback.
......
...@@ -1154,6 +1154,10 @@ public class Parser { ...@@ -1154,6 +1154,10 @@ public class Parser {
command.setViewName(viewName); command.setViewName(viewName);
ifExists = readIfExists(ifExists); ifExists = readIfExists(ifExists);
command.setIfExists(ifExists); command.setIfExists(ifExists);
Integer dropAction = parseCascadeOrRestrict();
if (dropAction != null) {
command.setDropAction(dropAction);
}
return command; return command;
} else if (readIf("ROLE")) { } else if (readIf("ROLE")) {
boolean ifExists = readIfExists(false); boolean ifExists = readIfExists(false);
...@@ -3533,9 +3537,15 @@ public class Parser { ...@@ -3533,9 +3537,15 @@ public class Parser {
} }
private Prepared parseCreate() { private Prepared parseCreate() {
boolean orReplace = false;
if (readIf("OR")) {
read("REPLACE");
orReplace = true;
}
boolean force = readIf("FORCE"); boolean force = readIf("FORCE");
if (readIf("VIEW")) { if (readIf("VIEW")) {
return parseCreateView(force); return parseCreateView(force, orReplace);
} else if (readIf("ALIAS")) { } else if (readIf("ALIAS")) {
return parseCreateFunctionAlias(force); return parseCreateFunctionAlias(force);
} else if (readIf("SEQUENCE")) { } else if (readIf("SEQUENCE")) {
...@@ -3951,7 +3961,7 @@ public class Parser { ...@@ -3951,7 +3961,7 @@ public class Parser {
return query; return query;
} }
private CreateView parseCreateView(boolean force) { private CreateView parseCreateView(boolean force, boolean orReplace) {
boolean ifNotExists = readIfNoExists(); boolean ifNotExists = readIfNoExists();
String viewName = readIdentifierWithSchema(); String viewName = readIdentifierWithSchema();
CreateView command = new CreateView(session, getSchema()); CreateView command = new CreateView(session, getSchema());
...@@ -3959,6 +3969,8 @@ public class Parser { ...@@ -3959,6 +3969,8 @@ public class Parser {
command.setViewName(viewName); command.setViewName(viewName);
command.setIfNotExists(ifNotExists); command.setIfNotExists(ifNotExists);
command.setComment(readCommentIf()); command.setComment(readCommentIf());
command.setOrReplace(orReplace);
command.setForce(force);
if (readIf("(")) { if (readIf("(")) {
String[] cols = parseColumnList(); String[] cols = parseColumnList();
command.setColumnNames(cols); command.setColumnNames(cols);
...@@ -4613,11 +4625,11 @@ public class Parser { ...@@ -4613,11 +4625,11 @@ public class Parser {
} }
private int parseAction() { private int parseAction() {
if (readIf("CASCADE")) { Integer result = parseCascadeOrRestrict();
return ConstraintReferential.CASCADE; if (result != null) {
} else if (readIf("RESTRICT")) { return result;
return ConstraintReferential.RESTRICT; }
} else if (readIf("NO")) { if (readIf("NO")) {
read("ACTION"); read("ACTION");
return ConstraintReferential.RESTRICT; return ConstraintReferential.RESTRICT;
} else { } else {
...@@ -4630,6 +4642,16 @@ public class Parser { ...@@ -4630,6 +4642,16 @@ public class Parser {
} }
} }
private Integer parseCascadeOrRestrict() {
if (readIf("CASCADE")) {
return ConstraintReferential.CASCADE;
} else if (readIf("RESTRICT")) {
return ConstraintReferential.RESTRICT;
} else {
return null;
}
}
private Prepared parseAlterTableAddConstraintIf(String tableName, Schema schema) { private Prepared parseAlterTableAddConstraintIf(String tableName, Schema schema) {
String constraintName = null, comment = null; String constraintName = null, comment = null;
boolean ifNotExists = false; boolean ifNotExists = false;
......
...@@ -6,15 +6,21 @@ ...@@ -6,15 +6,21 @@
*/ */
package org.h2.command.ddl; package org.h2.command.ddl;
import org.h2.command.Prepared;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.table.Table;
import org.h2.table.TableView; import org.h2.table.TableView;
import java.util.ArrayList;
import java.util.List;
/** /**
* This class represents the statement * This class represents the statement
* CREATE VIEW * CREATE VIEW
...@@ -28,6 +34,8 @@ public class CreateView extends SchemaCommand { ...@@ -28,6 +34,8 @@ public class CreateView extends SchemaCommand {
private String[] columnNames; private String[] columnNames;
private String comment; private String comment;
private boolean recursive; private boolean recursive;
private boolean orReplace;
private boolean force;
public CreateView(Session session, Schema schema) { public CreateView(Session session, Schema schema) {
super(session, schema); super(session, schema);
...@@ -45,14 +53,49 @@ public class CreateView extends SchemaCommand { ...@@ -45,14 +53,49 @@ public class CreateView extends SchemaCommand {
this.select = select; this.select = select;
} }
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}
public void setSelectSQL(String selectSQL) {
this.selectSQL = selectSQL;
}
public void setColumnNames(String[] cols) {
this.columnNames = cols;
}
public void setComment(String comment) {
this.comment = comment;
}
public void setOrReplace(boolean orReplace) {
this.orReplace = orReplace;
}
public void setForce(boolean force) {
this.force = force;
}
public int update() { public int update() {
session.commit(true); session.commit(true);
Database db = session.getDatabase(); Database db = session.getDatabase();
if (getSchema().findTableOrView(session, viewName) != null) { Table existingView = getSchema().findTableOrView(session, viewName);
List<DependentView> dependentViewSql = new ArrayList<DependentView>();
if (existingView != null) {
if (ifNotExists) { if (ifNotExists) {
return 0; return 0;
} }
throw DbException.get(ErrorCode.VIEW_ALREADY_EXISTS_1, viewName);
if (orReplace && existingView.getTableType().equals(Table.VIEW)) {
db.renameSchemaObject(session, existingView, db.getTempTableName(session));
loadDependentViewSql(existingView, dependentViewSql);
} else {
throw DbException.get(ErrorCode.VIEW_ALREADY_EXISTS_1, viewName);
}
} }
int id = getObjectId(); int id = getObjectId();
String querySQL; String querySQL;
...@@ -77,23 +120,73 @@ public class CreateView extends SchemaCommand { ...@@ -77,23 +120,73 @@ public class CreateView extends SchemaCommand {
// this is not strictly required - ignore exceptions, specially when using FORCE // this is not strictly required - ignore exceptions, specially when using FORCE
} }
db.addSchemaObject(session, view); db.addSchemaObject(session, view);
if (existingView != null) {
recreateDependentViews(db, existingView, dependentViewSql, view);
}
return 0; return 0;
} }
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}
public void setSelectSQL(String selectSQL) { private void recreateDependentViews(Database db, Table existingView, List<DependentView> dependentViewSql, TableView view) {
this.selectSQL = selectSQL; String failedView = null;
try {
// recreate the dependent views
for (DependentView dependentView : dependentViewSql) {
failedView = dependentView.viewName;
if (force) {
execute(dependentView.createForceSql, true);
} else {
execute(dependentView.createSql, true);
}
}
// Delete the original view
db.removeSchemaObject(session, existingView);
} catch (DbException e) {
db.removeSchemaObject(session, view);
// Put back the old view
db.renameSchemaObject(session, existingView, viewName);
// Try to put back the dependent views
for (DependentView dependentView : dependentViewSql) {
execute(dependentView.createForceSql, true);
}
throw DbException.get(ErrorCode.CANNOT_DROP_2, e, existingView.getName(), failedView);
}
} }
public void setColumnNames(String[] cols) { private void loadDependentViewSql(DbObject tableOrView, List<DependentView> recreate) {
this.columnNames = cols; for (DbObject view : tableOrView.getChildren()) {
if (view instanceof TableView) {
recreate.add(new DependentView((TableView) view));
loadDependentViewSql(view, recreate);
}
}
} }
public void setComment(String comment) { // Class that holds a snapshot of dependent view information.
this.comment = comment; // We can't just work with TableViews directly because they become invalid
// when we drop the parent view.
private class DependentView {
String viewName;
String createSql;
String createForceSql;
private DependentView(TableView view) {
this.viewName = view.getName();
this.createSql = view.getCreateSQL(true, false);
this.createForceSql = view.getCreateSQL(true, true);
}
} }
private void execute(String sql, boolean ddl) {
Prepared command = session.prepare(sql);
command.update();
if (ddl) {
session.commit(true);
}
}
} }
...@@ -7,11 +7,14 @@ ...@@ -7,11 +7,14 @@
package org.h2.command.ddl; package org.h2.command.ddl;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constraint.ConstraintReferential;
import org.h2.engine.DbObject;
import org.h2.engine.Right; import org.h2.engine.Right;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableView;
/** /**
* This class represents the statement * This class represents the statement
...@@ -21,6 +24,7 @@ public class DropView extends SchemaCommand { ...@@ -21,6 +24,7 @@ public class DropView extends SchemaCommand {
private String viewName; private String viewName;
private boolean ifExists; private boolean ifExists;
private int dropAction = ConstraintReferential.RESTRICT;
public DropView(Session session, Schema schema) { public DropView(Session session, Schema schema) {
super(session, schema); super(session, schema);
...@@ -30,6 +34,10 @@ public class DropView extends SchemaCommand { ...@@ -30,6 +34,10 @@ public class DropView extends SchemaCommand {
ifExists = b; ifExists = b;
} }
public void setDropAction(int dropAction) {
this.dropAction = dropAction;
}
public void setViewName(String viewName) { public void setViewName(String viewName) {
this.viewName = viewName; this.viewName = viewName;
} }
...@@ -46,6 +54,15 @@ public class DropView extends SchemaCommand { ...@@ -46,6 +54,15 @@ public class DropView extends SchemaCommand {
throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName); throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName);
} }
session.getUser().checkRight(view, Right.ALL); session.getUser().checkRight(view, Right.ALL);
if (dropAction == ConstraintReferential.RESTRICT) {
for (DbObject child : view.getChildren()) {
if (child instanceof TableView) {
throw DbException.get(ErrorCode.CANNOT_DROP_2, viewName, child.getName());
}
}
}
view.lock(session, true, true); view.lock(session, true, true);
session.getDatabase().removeSchemaObject(session, view); session.getDatabase().removeSchemaObject(session, view);
} }
......
...@@ -253,7 +253,7 @@ DROP USER [ IF EXISTS ] userName ...@@ -253,7 +253,7 @@ DROP USER [ IF EXISTS ] userName
"," ","
Drops a user." Drops a user."
"Commands (DDL)","DROP VIEW"," "Commands (DDL)","DROP VIEW","
DROP VIEW [ IF EXISTS ] viewName [RESTRICT | CASCADE ] DROP VIEW [ IF EXISTS ] viewName [ RESTRICT | CASCADE ]
"," ","
Drops a view, and all dependent views if the CASCADE clause is used." Drops a view, and all dependent views if the CASCADE clause is used."
"Commands (DDL)","TRUNCATE TABLE"," "Commands (DDL)","TRUNCATE TABLE","
......
...@@ -152,11 +152,31 @@ public class TableView extends Table { ...@@ -152,11 +152,31 @@ public class TableView extends Table {
} }
public String getDropSQL() { public String getDropSQL() {
return "DROP VIEW IF EXISTS " + getSQL(); return getDropSQL(false);
}
public String getDropSQL(boolean cascade) {
StatementBuilder buff = new StatementBuilder("DROP VIEW IF EXISTS ");
buff.append(getSQL());
if (cascade) {
buff.append(" CASCADE");
}
return buff.toString();
} }
public String getCreateSQL() { public String getCreateSQL() {
StatementBuilder buff = new StatementBuilder("CREATE FORCE VIEW "); return getCreateSQL(false, true);
}
public String getCreateSQL(boolean orReplace, boolean force) {
StatementBuilder buff = new StatementBuilder("CREATE ");
if (orReplace) {
buff.append("OR REPLACE ");
}
if (force) {
buff.append("FORCE ");
}
buff.append("VIEW ");
buff.append(getSQL()); buff.append(getSQL());
if (comment != null) { if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment)); buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
......
...@@ -60,6 +60,7 @@ import org.h2.test.db.TestTriggersConstraints; ...@@ -60,6 +60,7 @@ import org.h2.test.db.TestTriggersConstraints;
import org.h2.test.db.TestTwoPhaseCommit; import org.h2.test.db.TestTwoPhaseCommit;
import org.h2.test.db.TestView; import org.h2.test.db.TestView;
import org.h2.test.db.TestViewAlterTable; import org.h2.test.db.TestViewAlterTable;
import org.h2.test.db.TestViewDropView;
import org.h2.test.jaqu.AliasMapTest; import org.h2.test.jaqu.AliasMapTest;
import org.h2.test.jaqu.SamplesTest; import org.h2.test.jaqu.SamplesTest;
import org.h2.test.jaqu.UpdateTest; import org.h2.test.jaqu.UpdateTest;
...@@ -538,6 +539,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -538,6 +539,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestTwoPhaseCommit().runTest(this); new TestTwoPhaseCommit().runTest(this);
new TestView().runTest(this); new TestView().runTest(this);
new TestViewAlterTable().runTest(this); new TestViewAlterTable().runTest(this);
new TestViewDropView().runTest(this);
// jaqu // jaqu
new AliasMapTest().runTest(this); new AliasMapTest().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 org.h2.constant.ErrorCode;
import org.h2.test.TestBase;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Test the impact of DROP VIEW statements on dependent views.
*/
public class TestViewDropView extends TestBase {
private Connection conn;
private Statement stat;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws Exception {
deleteDb("alter");
conn = getConnection("alter");
stat = conn.createStatement();
testDropViewDefaultBehaviour();
testDropViewRestrict();
testDropViewCascade();
testCreateOrReplaceView();
testCreateOrReplaceViewWithNowInvalidDependentViews();
testCreateOrReplaceForceViewWithNowInvalidDependentViews();
conn.close();
deleteDb("alter");
}
private void testDropViewDefaultBehaviour() throws SQLException {
createTestData();
try {
stat.execute("drop view v1"); // Should fail because have dependencies
} catch (SQLException e) {
assertEquals(ErrorCode.CANNOT_DROP_2, e.getErrorCode());
}
checkViewRemainsValid();
}
private void testDropViewRestrict() throws SQLException {
createTestData();
try {
stat.execute("drop view v1 restrict"); // Should fail because have dependencies
} catch (SQLException e) {
assertEquals(ErrorCode.CANNOT_DROP_2, e.getErrorCode());
}
checkViewRemainsValid();
}
private void testDropViewCascade() throws SQLException {
createTestData();
stat.execute("drop view v1 cascade");
try {
stat.execute("select * from v1");
fail("Exception should be thrown - v1 should be deleted");
} catch (SQLException e) {
assertEquals(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e.getErrorCode());
}
try {
stat.execute("select * from v2");
fail("Exception should be thrown - v2 should be deleted");
} catch (SQLException e) {
assertEquals(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e.getErrorCode());
}
try {
stat.execute("select * from v3");
fail("Exception should be thrown - v3 should be deleted");
} catch (SQLException e) {
assertEquals(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e.getErrorCode());
}
stat.execute("drop table test");
}
private void testCreateOrReplaceView() throws SQLException {
createTestData();
stat.execute("create or replace view v1 as select a as b, b as a, c from test");
checkViewRemainsValid();
}
private void testCreateOrReplaceViewWithNowInvalidDependentViews() throws SQLException {
createTestData();
try {
// v2 and v3 need more than just "c", so we should get an error
stat.execute("create or replace view v1 as select c from test");
fail("Exception should be thrown - dependent views need more columns than just 'c'");
} catch (SQLException e) {
assertEquals(ErrorCode.CANNOT_DROP_2, e.getErrorCode());
}
// Make sure our old views come back ok
checkViewRemainsValid();
}
private void testCreateOrReplaceForceViewWithNowInvalidDependentViews() throws SQLException {
createTestData();
// v2 and v3 need more than just "c", but we want to force the creation of v1 anyway
stat.execute("create or replace force view v1 as select c from test");
try {
// now v2 and v3 are broken, but they still exist -- if there is any value to that...?
ResultSet rs = stat.executeQuery("select b from v2");
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
} catch (SQLException e) {
assertEquals(ErrorCode.COLUMN_NOT_FOUND_1, e.getErrorCode());
}
stat.execute("drop table test");
}
private void createTestData() throws SQLException {
stat.execute("create table test(a int, b int, c int)");
stat.execute("insert into test(a, b, c) values (1, 2, 3)");
stat.execute("create view v1 as select a as b, b as a from test");
// child of v1
stat.execute("create view v2 as select * from v1");
// child of v2
stat.execute("create view v3 as select * from v2");
}
private void checkViewRemainsValid() throws SQLException {
ResultSet rs = stat.executeQuery("select b from v1");
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
rs = stat.executeQuery("select b from v2");
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
rs = stat.executeQuery("select b from test");
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
stat.execute("drop table test");
ResultSet d = conn.getMetaData().getTables(null, null, null, null);
while (d.next()) {
if (!d.getString(2).equals("INFORMATION_SCHEMA")) {
fail("Should have no tables left in the database, not: " + d.getString(2) + "." + d.getString(3));
}
}
}
}
...@@ -5983,6 +5983,9 @@ SELECT * FROM CHILDREN_CLASS12; ...@@ -5983,6 +5983,9 @@ SELECT * FROM CHILDREN_CLASS12;
> rows: 4 > rows: 4
DROP VIEW CHILDREN_CLASS1; DROP VIEW CHILDREN_CLASS1;
> exception
DROP VIEW CHILDREN_CLASS1 CASCADE;
> ok > ok
DROP VIEW CHILDREN_CLASS2; DROP VIEW CHILDREN_CLASS2;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论