提交 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
"
"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.
The command will fail if dependent views exist and the RESTRICT clause is used. This is the default.
......
......@@ -35,6 +35,8 @@ Change Log
</li><li>Version 1.2.137 could still not be converted to Java 1.4
(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>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>
<h2>Version 1.2.137 (2010-06-06)</h2>
......@@ -383,7 +385,7 @@ Change Log
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>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>The Lucene fulltext search ignored transaction rollback. Fixed using a trigger on rollback.
</li><li>Trigger can now be called on rollback.
......
......@@ -1154,6 +1154,10 @@ public class Parser {
command.setViewName(viewName);
ifExists = readIfExists(ifExists);
command.setIfExists(ifExists);
Integer dropAction = parseCascadeOrRestrict();
if (dropAction != null) {
command.setDropAction(dropAction);
}
return command;
} else if (readIf("ROLE")) {
boolean ifExists = readIfExists(false);
......@@ -3533,9 +3537,15 @@ public class Parser {
}
private Prepared parseCreate() {
boolean orReplace = false;
if (readIf("OR")) {
read("REPLACE");
orReplace = true;
}
boolean force = readIf("FORCE");
if (readIf("VIEW")) {
return parseCreateView(force);
return parseCreateView(force, orReplace);
} else if (readIf("ALIAS")) {
return parseCreateFunctionAlias(force);
} else if (readIf("SEQUENCE")) {
......@@ -3951,7 +3961,7 @@ public class Parser {
return query;
}
private CreateView parseCreateView(boolean force) {
private CreateView parseCreateView(boolean force, boolean orReplace) {
boolean ifNotExists = readIfNoExists();
String viewName = readIdentifierWithSchema();
CreateView command = new CreateView(session, getSchema());
......@@ -3959,6 +3969,8 @@ public class Parser {
command.setViewName(viewName);
command.setIfNotExists(ifNotExists);
command.setComment(readCommentIf());
command.setOrReplace(orReplace);
command.setForce(force);
if (readIf("(")) {
String[] cols = parseColumnList();
command.setColumnNames(cols);
......@@ -4613,11 +4625,11 @@ public class Parser {
}
private int parseAction() {
if (readIf("CASCADE")) {
return ConstraintReferential.CASCADE;
} else if (readIf("RESTRICT")) {
return ConstraintReferential.RESTRICT;
} else if (readIf("NO")) {
Integer result = parseCascadeOrRestrict();
if (result != null) {
return result;
}
if (readIf("NO")) {
read("ACTION");
return ConstraintReferential.RESTRICT;
} else {
......@@ -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) {
String constraintName = null, comment = null;
boolean ifNotExists = false;
......
......@@ -6,15 +6,21 @@
*/
package org.h2.command.ddl;
import org.h2.command.Prepared;
import org.h2.command.dml.Query;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
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.Table;
import org.h2.table.TableView;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents the statement
* CREATE VIEW
......@@ -28,6 +34,8 @@ public class CreateView extends SchemaCommand {
private String[] columnNames;
private String comment;
private boolean recursive;
private boolean orReplace;
private boolean force;
public CreateView(Session session, Schema schema) {
super(session, schema);
......@@ -45,15 +53,50 @@ public class CreateView extends SchemaCommand {
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() {
session.commit(true);
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) {
return 0;
}
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();
String querySQL;
if (select == null) {
......@@ -77,23 +120,73 @@ public class CreateView extends SchemaCommand {
// this is not strictly required - ignore exceptions, specially when using FORCE
}
db.addSchemaObject(session, view);
if (existingView != null) {
recreateDependentViews(db, existingView, dependentViewSql, view);
}
return 0;
}
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
private void recreateDependentViews(Database db, Table existingView, List<DependentView> dependentViewSql, TableView view) {
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);
public void setSelectSQL(String selectSQL) {
this.selectSQL = selectSQL;
} 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);
}
public void setColumnNames(String[] cols) {
this.columnNames = cols;
throw DbException.get(ErrorCode.CANNOT_DROP_2, e, existingView.getName(), failedView);
}
}
public void setComment(String comment) {
this.comment = comment;
private void loadDependentViewSql(DbObject tableOrView, List<DependentView> recreate) {
for (DbObject view : tableOrView.getChildren()) {
if (view instanceof TableView) {
recreate.add(new DependentView((TableView) view));
loadDependentViewSql(view, recreate);
}
}
}
// Class that holds a snapshot of dependent view information.
// 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 @@
package org.h2.command.ddl;
import org.h2.constant.ErrorCode;
import org.h2.constraint.ConstraintReferential;
import org.h2.engine.DbObject;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.schema.Schema;
import org.h2.table.Table;
import org.h2.table.TableView;
/**
* This class represents the statement
......@@ -21,6 +24,7 @@ public class DropView extends SchemaCommand {
private String viewName;
private boolean ifExists;
private int dropAction = ConstraintReferential.RESTRICT;
public DropView(Session session, Schema schema) {
super(session, schema);
......@@ -30,6 +34,10 @@ public class DropView extends SchemaCommand {
ifExists = b;
}
public void setDropAction(int dropAction) {
this.dropAction = dropAction;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
......@@ -46,6 +54,15 @@ public class DropView extends SchemaCommand {
throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName);
}
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);
session.getDatabase().removeSchemaObject(session, view);
}
......
......@@ -253,7 +253,7 @@ DROP USER [ IF EXISTS ] userName
","
Drops a user."
"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."
"Commands (DDL)","TRUNCATE TABLE","
......
......@@ -152,11 +152,31 @@ public class TableView extends Table {
}
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() {
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());
if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
......
......@@ -60,6 +60,7 @@ import org.h2.test.db.TestTriggersConstraints;
import org.h2.test.db.TestTwoPhaseCommit;
import org.h2.test.db.TestView;
import org.h2.test.db.TestViewAlterTable;
import org.h2.test.db.TestViewDropView;
import org.h2.test.jaqu.AliasMapTest;
import org.h2.test.jaqu.SamplesTest;
import org.h2.test.jaqu.UpdateTest;
......@@ -538,6 +539,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestTwoPhaseCommit().runTest(this);
new TestView().runTest(this);
new TestViewAlterTable().runTest(this);
new TestViewDropView().runTest(this);
// jaqu
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;
> rows: 4
DROP VIEW CHILDREN_CLASS1;
> exception
DROP VIEW CHILDREN_CLASS1 CASCADE;
> ok
DROP VIEW CHILDREN_CLASS2;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论