提交 117d5acb authored 作者: Thomas Mueller's avatar Thomas Mueller

ALTER TABLE ALTER ADD / REMOVE /ALTER COLUMN dropped some dependent objects…

ALTER TABLE ALTER ADD / REMOVE /ALTER COLUMN dropped some dependent objects (access rights, triggers) of views that depend on the modified table. CREATE OR REPLACE VIEW dropped some dependent objects (access rights, triggers) if the view already existed before.
上级 c3c1212c
......@@ -18,7 +18,11 @@ Change Log
<h1>Change Log</h1>
<h2>Next Version (unreleased)</h2>
<ul><li>New aggregate function HISTOGRAM to calculate the histogram.
<ul><li>ALTER TABLE ALTER ADD / REMOVE /ALTER COLUMN dropped some dependent objects
(access rights, triggers) of views that depend on the modified table.
</li><li>CREATE OR REPLACE VIEW dropped some dependent objects (access rights, triggers)
if the view already existed before.
</li><li>New aggregate function HISTOGRAM to calculate the histogram.
The plan is to use this function is only used internally when running ANALYZE
to generate the histogram that is then used by the query optimizer.
</li><li>New system function TRUNCATE_VALUE to truncate a value to the required precision.
......
......@@ -1165,6 +1165,8 @@ public class Parser {
readIf("CONSTRAINTS");
} else if (readIf("RESTRICT")) {
command.setDropAction(ConstraintReferential.RESTRICT);
} else if (readIf("IGNORE")) {
command.setDropAction(ConstraintReferential.SET_DEFAULT);
}
return command;
} else if (readIf("INDEX")) {
......
......@@ -7,7 +7,6 @@
package org.h2.command.ddl;
import java.util.ArrayList;
import java.util.List;
import org.h2.command.CommandInterface;
import org.h2.command.Parser;
import org.h2.command.Prepared;
......@@ -179,15 +178,23 @@ public class AlterTableAlterColumn extends SchemaCommand {
Column[] columns = table.getColumns();
ArrayList<Column> newColumns = New.arrayList();
Table newTable = cloneTableStructure(columns, db, tempName, newColumns);
List<String> views;
try {
views = checkViews(table, newTable);
// check if a view would become invalid
// (because the column to drop is referenced or so)
checkViews(table, newTable);
} catch (DbException e) {
execute("DROP TABLE " + newTable.getName(), true);
throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, e, getSQL(), e.getMessage());
}
String tableName = table.getName();
execute("DROP TABLE " + table.getSQL() + " CASCADE", true);
ArrayList<TableView> views = table.getViews();
if (views != null) {
views = New.arrayList(views);
for (TableView view : views) {
table.removeView(view);
}
}
execute("DROP TABLE " + table.getSQL() + " IGNORE", true);
db.renameSchemaObject(session, newTable, tableName);
for (DbObject child : newTable.getChildren()) {
if (child instanceof Sequence) {
......@@ -212,8 +219,11 @@ public class AlterTableAlterColumn extends SchemaCommand {
db.renameSchemaObject(session, so, name);
}
}
for (String view : views) {
execute(view, true);
if (views != null) {
for (TableView view : views) {
String sql = view.getCreateSQL(true, true);
execute(sql, true);
}
}
}
......@@ -341,13 +351,9 @@ public class AlterTableAlterColumn extends SchemaCommand {
}
/**
* Check that all views are still valid.
*
* @return the list of SQL statements to re-create views that depend on this
* table
* Check that all views and other dependent objects.
*/
private List<String> checkViews(SchemaObject sourceTable, SchemaObject newTable) {
List<String> viewSql = new ArrayList<String>();
private void checkViews(SchemaObject sourceTable, SchemaObject newTable) {
String sourceTableName = sourceTable.getName();
String newTableName = newTable.getName();
Database db = sourceTable.getDatabase();
......@@ -356,7 +362,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
try {
// have our new table impersonate the target table
db.renameSchemaObject(session, newTable, sourceTableName);
checkViewsAreValid(sourceTable, viewSql);
checkViewsAreValid(sourceTable);
} finally {
// always put the source tables back with their proper names
try {
......@@ -365,17 +371,14 @@ public class AlterTableAlterColumn extends SchemaCommand {
db.renameSchemaObject(session, sourceTable, sourceTableName);
}
}
return viewSql;
}
/**
* Check that a table or view is still valid.
*
* @param tableOrView the table or view to check
* @param recreate the list of SQL statements to re-create views that depend
* on this table
*/
private void checkViewsAreValid(DbObject tableOrView, List<String> recreate) {
private void checkViewsAreValid(DbObject tableOrView) {
for (DbObject view : tableOrView.getChildren()) {
if (view instanceof TableView) {
String sql = ((TableView) view).getQuery();
......@@ -383,9 +386,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
// do not execute, not even with limit 1, because that could
// have side effects or take a very long time
session.prepare(sql);
recreate.add(view.getDropSQL());
recreate.add(view.getCreateSQL());
checkViewsAreValid(view, recreate);
checkViewsAreValid(view);
}
}
}
......
......@@ -9,6 +9,7 @@ package org.h2.command.ddl;
import org.h2.command.CommandInterface;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.table.TableView;
/**
......@@ -30,7 +31,10 @@ public class AlterView extends DefineCommand {
public int update() {
session.commit(true);
session.getUser().checkRight(view, Right.ALL);
view.recompile(session);
DbException e = view.recompile(session, false);
if (e != null) {
throw e;
}
return 0;
}
......
......@@ -7,14 +7,11 @@
package org.h2.command.ddl;
import java.util.ArrayList;
import java.util.List;
import org.h2.command.CommandInterface;
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.expression.Parameter;
import org.h2.message.DbException;
......@@ -73,24 +70,19 @@ public class CreateView extends SchemaCommand {
this.force = force;
}
public int update() {
session.commit(true);
Database db = session.getDatabase();
Table existingView = getSchema().findTableOrView(session, viewName);
List<DependentView> dependentViewSql = new ArrayList<DependentView>();
if (existingView != null) {
TableView view = null;
Table old = getSchema().findTableOrView(session, viewName);
if (old != null) {
if (ifNotExists) {
return 0;
}
if (orReplace && existingView.getTableType().equals(Table.VIEW)) {
db.renameSchemaObject(session, existingView, db.getTempTableName(session));
loadDependentViewSql(existingView, dependentViewSql);
} else {
if (!orReplace || !old.getTableType().equals(Table.VIEW)) {
throw DbException.get(ErrorCode.VIEW_ALREADY_EXISTS_1, viewName);
}
view = (TableView) old;
}
int id = getObjectId();
String querySQL;
......@@ -104,93 +96,30 @@ public class CreateView extends SchemaCommand {
querySQL = select.getPlanSQL();
}
Session sysSession = db.getSystemSession();
TableView view;
try {
Schema schema = session.getDatabase().getSchema(session.getCurrentSchemaName());
sysSession.setCurrentSchema(schema);
view = new TableView(getSchema(), id, viewName, querySQL, null, columnNames, sysSession, false);
if (view == null) {
Schema schema = session.getDatabase().getSchema(session.getCurrentSchemaName());
sysSession.setCurrentSchema(schema);
view = new TableView(getSchema(), id, viewName, querySQL, null, columnNames, sysSession, false);
} else {
view.replace(querySQL, columnNames, sysSession, false, force);
}
} finally {
sysSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN));
}
view.setComment(comment);
try {
view.recompileQuery(session);
} catch (DbException e) {
// this is not strictly required - ignore exceptions, specially when using FORCE
if (comment != null) {
view.setComment(comment);
}
db.addSchemaObject(session, view);
if (existingView != null) {
recreateDependentViews(db, existingView, dependentViewSql, view);
if (old == null) {
db.addSchemaObject(session, view);
} else {
db.update(session, view);
}
return 0;
}
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);
} 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);
}
}
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 static class DependentView {
String viewName;
String createSql;
String createForceSql;
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);
}
}
public int getType() {
return CommandInterface.CREATE_VIEW;
}
}
......@@ -648,11 +648,7 @@ public class Database implements DataHandler {
if (obj instanceof TableView) {
TableView view = (TableView) obj;
if (view.isInvalid()) {
try {
view.recompile(session);
} catch (DbException e) {
// ignore
}
view.recompile(session, true);
if (!view.isInvalid()) {
recompileSuccessful = true;
}
......@@ -667,11 +663,7 @@ public class Database implements DataHandler {
if (obj instanceof TableView) {
TableView view = (TableView) obj;
if (!view.isInvalid()) {
try {
view.recompile(systemSession);
} catch (DbException e) {
// ignore
}
view.recompile(systemSession, true);
}
}
}
......
......@@ -706,7 +706,7 @@ public abstract class Table extends SchemaObjectBase {
*
* @param view the view to remove
*/
void removeView(TableView view) {
public void removeView(TableView view) {
remove(views, view);
}
......
......@@ -40,7 +40,7 @@ public class TableView extends Table {
private String querySQL;
private ArrayList<Table> tables;
private final String[] columnNames;
private String[] columnNames;
private Query viewQuery;
private ViewIndex index;
private boolean recursive;
......@@ -56,34 +56,87 @@ public class TableView extends Table {
public TableView(Schema schema, int id, String name, String querySQL, ArrayList<Parameter> params, String[] columnNames,
Session session, boolean recursive) {
super(schema, id, name, false, true);
init(querySQL, params, columnNames, session, recursive);
}
/**
* Try to replace the SQL statement of the view and re-compile this and all
* dependent views.
*
* @param querySQL the SQL statement
* @param columnNames the column names
* @param session the session
* @param recursive whether this is a recursive view
* @param force if errors should be ignored
*/
public void replace(String querySQL, String[] columnNames, Session session, boolean recursive, boolean force) {
String oldQuerySQL = this.querySQL;
String[] oldColumnNames = this.columnNames;
boolean oldRecursive = this.recursive;
init(querySQL, null, columnNames, session, recursive);
DbException e = recompile(session, force);
if (e != null) {
init(oldQuerySQL, null, oldColumnNames, session, oldRecursive);
recompile(session, true);
throw e;
}
}
private void init(String querySQL, ArrayList<Parameter> params, String[] columnNames, Session session, boolean recursive) {
this.querySQL = querySQL;
this.columnNames = columnNames;
this.recursive = recursive;
index = new ViewIndex(this, querySQL, params, recursive);
indexCache.clear();
initColumnsAndTables(session);
}
private static Query compileViewQuery(Session session, String sql) {
Prepared p = session.prepare(sql);
if (!(p instanceof Query)) {
throw DbException.getSyntaxError(sql, 0);
}
return (Query) p;
}
/**
* Re-compile the query, updating the SQL statement.
* Re-compile the view query and all views that depend on this object.
*
* @param session the session
* @return the query
* @param force if exceptions should be ignored
* @return the exception if re-compiling this or any dependent view failed (only when force is disabled)
*/
public Query recompileQuery(Session session) {
Prepared p = session.prepare(querySQL);
if (!(p instanceof Query)) {
throw DbException.getSyntaxError(querySQL, 0);
public DbException recompile(Session session, boolean force) {
try {
compileViewQuery(session, querySQL);
} catch (DbException e) {
if (!force) {
return e;
}
}
ArrayList<TableView> views = getViews();
if (views != null) {
views = New.arrayList(views);
}
Query query = (Query) p;
querySQL = query.getPlanSQL();
return query;
indexCache.clear();
initColumnsAndTables(session);
if (views != null) {
for (TableView v : views) {
DbException e = v.recompile(session, force);
if (e != null && !force) {
return e;
}
}
}
return force ? null : createException;
}
private void initColumnsAndTables(Session session) {
Column[] cols;
removeViewFromTables();
try {
Query query = recompileQuery(session);
Query query = compileViewQuery(session, querySQL);
this.querySQL = query.getPlanSQL();
tables = New.arrayList(query.getTables());
ArrayList<Expression> expressions = query.getExpressions();
ArrayList<Column> list = New.arrayList();
......@@ -109,6 +162,7 @@ public class TableView extends Table {
createException = null;
viewQuery = query;
} catch (DbException e) {
e.addSQL(getCreateSQL());
createException = e;
// if it can't be compiled, then it's a 'zero column table'
// this avoids problems when creating the view when opening the
......@@ -158,6 +212,11 @@ public class TableView extends Table {
return buff.toString();
}
public String getCreateSQLForCopy(Table table, String quotedName) {
return getCreateSQL(false, true, quotedName);
}
public String getCreateSQL() {
return getCreateSQL(false, true);
}
......@@ -170,6 +229,10 @@ public class TableView extends Table {
* @return the SQL statement
*/
public String getCreateSQL(boolean orReplace, boolean force) {
return getCreateSQL(orReplace, force, getSQL());
}
private String getCreateSQL(boolean orReplace, boolean force, String quotedName) {
StatementBuilder buff = new StatementBuilder("CREATE ");
if (orReplace) {
buff.append("OR REPLACE ");
......@@ -178,11 +241,11 @@ public class TableView extends Table {
buff.append("FORCE ");
}
buff.append("VIEW ");
buff.append(getSQL());
buff.append(quotedName);
if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
}
if (columns.length > 0) {
if (columns != null && columns.length > 0) {
buff.append('(');
for (Column c : columns) {
buff.appendExceptFirst(", ");
......@@ -291,19 +354,6 @@ public class TableView extends Table {
return null;
}
/**
* Re-compile the view query.
*
* @param session the session
*/
public void recompile(Session session) {
for (Table t : tables) {
t.removeView(this);
}
tables.clear();
initColumnsAndTables(session);
}
public long getMaxDataModificationId() {
if (createException != null) {
return Long.MAX_VALUE;
......@@ -364,10 +414,10 @@ public class TableView extends Table {
String querySQL = query.getPlanSQL();
TableView v = new TableView(mainSchema, 0, name, querySQL, query.getParameters(), null, session,
false);
v.setTopQuery(topQuery);
if (v.createException != null) {
throw v.createException;
}
v.setTopQuery(topQuery);
v.setOwner(owner);
v.setTemporary(true);
return v;
......
......@@ -165,6 +165,8 @@ public class TestViewAlterTable extends TestBase {
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");
stat.execute("create user if not exists test_user password 'x'");
stat.execute("grant select on v2 to test_user");
// sibling of v1
stat.execute("create view v3 as select a from test");
}
......@@ -180,6 +182,10 @@ public class TestViewAlterTable extends TestBase {
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
rs = stat.executeQuery("select * from information_schema.rights");
assertTrue(rs.next());
assertEquals("TEST_USER", rs.getString("GRANTEE"));
assertEquals("V2", rs.getString("TABLE_NAME"));
rs = stat.executeQuery("select b from test");
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
......@@ -187,11 +193,12 @@ public class TestViewAlterTable extends TestBase {
stat.execute("drop table test cascade");
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));
rs = conn.getMetaData().getTables(null, null, null, null);
while (rs.next()) {
if (!rs.getString(2).equals("INFORMATION_SCHEMA")) {
fail("Should have no tables left in the database, not: " + rs.getString(2) + "." + rs.getString(3));
}
}
}
}
......@@ -48,6 +48,12 @@ public class TestViewDropView extends TestBase {
}
private void testCreateForceView() throws SQLException {
try {
stat.execute("create view test_view as select * from test");
fail();
} catch (SQLException e) {
assertEquals(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e.getErrorCode());
}
stat.execute("create force view test_view as select * from test");
stat.execute("create table test(id int)");
stat.execute("alter view test_view recompile");
......@@ -142,7 +148,7 @@ public class TestViewDropView extends TestBase {
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());
assertEquals(ErrorCode.COLUMN_NOT_FOUND_1, e.getErrorCode());
}
// Make sure our old views come back ok
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论