-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve error message for DROP/RENAME TABLE on MV #8869
Improve error message for DROP/RENAME TABLE on MV #8869
Conversation
@@ -54,6 +55,14 @@ public String getName() | |||
Session session = stateMachine.getSession(); | |||
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); | |||
|
|||
Optional<ConnectorMaterializedViewDefinition> view = metadata.getMaterializedView(session, tableName); | |||
if (view.isPresent()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does metadata.getTableHandle
return non-empty for materialized view? If not please move the new code to if (tableHandle.isEmpty()) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we check for if (!statement.isExists()) {
here as below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we check for if (!statement.isExists()) { here as below?
Good catch. I think we should. ie. we don't want the statement:
DROP TABLE existing_materialized view IF EXISTS
to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added guards for both view and materialized view. Hive connector returns view from getTableHandle
method so it needs to happen even if getTableHandle
returns a not empty value.
I added case where IF EXISTS
is used + tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// those two guards are here because some connectors violate SPI contract and return views or materialized views from getTableHandle.
Which connectors? Can we fix them? Also, that would argue for detecting misbehaving connectors and failing with some kind of internal error to indicate so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hive connector returns views from getTableHandle
. This in turn makes GRANT SELECT ON...
or COMMENT ON TABLE
work on views.
|
||
assertQueryFails( | ||
"DROP VIEW " + viewName, | ||
format("line 1:1: View '.*.%s' does not exist", viewName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have consistent message for view and table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some comments/questions
ffd89e0
to
d5e292d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comments addressed
@@ -54,6 +55,14 @@ public String getName() | |||
Session session = stateMachine.getSession(); | |||
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); | |||
|
|||
Optional<ConnectorMaterializedViewDefinition> view = metadata.getMaterializedView(session, tableName); | |||
if (view.isPresent()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we check for if (!statement.isExists()) { here as below?
Good catch. I think we should. ie. we don't want the statement:
DROP TABLE existing_materialized view IF EXISTS
to fail.
@@ -54,6 +55,14 @@ public String getName() | |||
Session session = stateMachine.getSession(); | |||
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); | |||
|
|||
Optional<ConnectorMaterializedViewDefinition> view = metadata.getMaterializedView(session, tableName); | |||
if (view.isPresent()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added guards for both view and materialized view. Hive connector returns view from getTableHandle
method so it needs to happen even if getTableHandle
returns a not empty value.
I added case where IF EXISTS
is used + tests
core/trino-main/src/main/java/io/trino/execution/DropTableTask.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java
Outdated
Show resolved
Hide resolved
|
||
assertUpdate(format("ALTER TABLE IF EXISTS %s RENAME TO %s_new", viewName, viewName)); | ||
|
||
assertQueryFails( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it still behave same way if connector supports materialized views but not views (not sure if we have a case like that now)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't have a case like that. The only connector that supports materialized views is the iceberg connector and it also supports views.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. nits.
61e1a86
to
a49d545
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applied suggestions and squashed
|
||
assertUpdate(format("ALTER TABLE IF EXISTS %s RENAME TO %s_new", viewName, viewName)); | ||
|
||
assertQueryFails( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't have a case like that. The only connector that supports materialized views is the iceberg connector and it also supports views.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also enhance DropMaterializedViewTask
in a similar way?
} | ||
return immediateVoidFuture(); | ||
} | ||
Optional<ConnectorMaterializedViewDefinition> materializedView = metadata.getMaterializedView(session, tableName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should check MV first. Follow object resolution order from io.trino.sql.analyzer.StatementAnalyzer.Visitor#visitTable
(MV, view, table)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. Question though, given all 3 object types should be in the same namespace. Is this only for the sake of code consistency or there is another reason?
@@ -54,6 +56,28 @@ public String getName() | |||
Session session = stateMachine.getSession(); | |||
QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); | |||
|
|||
// those two guards are here because some connectors violate SPI contract and return views or materialized views from getTableHandle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this comment. It's not only about connectors, but also about hinting user at using correct command
@@ -57,6 +58,13 @@ public String getName() | |||
Optional<ConnectorViewDefinition> view = metadata.getView(session, name); | |||
if (view.isEmpty()) { | |||
if (!statement.isExists()) { | |||
Optional<ConnectorMaterializedViewDefinition> materializedView = metadata.getMaterializedView(session, name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the existence of MV before if (view.isEmpty())
(again, follow check order of MV, view, table).
Here (after view existence check), you should check if table is present instead.
core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java
Outdated
Show resolved
Hide resolved
@@ -58,6 +59,13 @@ public String getName() | |||
QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getSource()); | |||
Optional<ConnectorViewDefinition> viewDefinition = metadata.getView(session, viewName); | |||
if (viewDefinition.isEmpty()) { | |||
Optional<ConnectorMaterializedViewDefinition> materializedView = metadata.getMaterializedView(session, viewName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, move MV check above if (viewDefinition.isEmpty())
. Here (after view existence check), you can check that table handle does not exist
@@ -537,6 +537,28 @@ public void testMaterializedView() | |||
"FROM\n" + | |||
" nation"); | |||
|
|||
assertQueryFails( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these aren't really a connector test. Could you add unit test similar to TestCreateMaterializedViewTask
for each task that you change? Alternatively, could you extract these test cases as separate tests here (however, I would still prefer TestCreateMaterializedViewTask
style tests)?
also, checkout |
a49d545
to
ec09bb5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add enhancements in DropMaterializedViewTask + order of checks changed to always MV/VIEW/TABLE.
Tests moved from BaseConnectorTest to separate unit tests for *Task classes.
} | ||
return immediateVoidFuture(); | ||
} | ||
Optional<ConnectorMaterializedViewDefinition> materializedView = metadata.getMaterializedView(session, tableName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. Question though, given all 3 object types should be in the same namespace. Is this only for the sake of code consistency or there is another reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm % minor comments
@@ -74,6 +99,7 @@ public String getName() | |||
if (!tableName.getCatalogName().equals(target.getCatalogName())) { | |||
throw semanticException(NOT_SUPPORTED, statement, "Table rename across catalogs is not supported"); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undo
throw semanticException( | ||
TABLE_NOT_FOUND, | ||
statement, | ||
"View '%s' does not exist. Attempting to use view DDL syntax on a table. Did you mean RENAME TABLE %s...?", viewName, viewName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is no RENAME TABLE
command in Trino
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaced with Did you mean ALTER TABLE %s RENAME
true); | ||
} | ||
|
||
private QueryStateMachine stateMachine(TransactionManager transactionManager, MetadataManager metadata, AccessControl accessControl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can it be static?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it static (with one additional parameter)
@Override | ||
public void renameTable(Session session, TableHandle tableHandle, QualifiedObjectName newTableName) | ||
{ | ||
tables.put(newTableName.asSchemaTableName(), tables.get(getTableName(tableHandle))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validate that tables.get
is not null
@Override | ||
public void renameView(Session session, QualifiedObjectName source, QualifiedObjectName target) | ||
{ | ||
views.put(target.asSchemaTableName(), views.get(source.asSchemaTableName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure view.get
is not null
// no exception | ||
} | ||
|
||
private ListenableFuture<Void> executeDropMaterializedView(DropMaterializedView statement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you make it accept viewName, ifExists
instead of statement
? This way we don't need to create DropMaterializedView
object in tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. I refactored execute...
the same way for all new test classes
// no exception | ||
} | ||
|
||
private ListenableFuture<Void> executeDropTable(DropTable statement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make executeDropTable
create DropTable
statement itself
|
||
private ListenableFuture<Void> executeDropView(DropView statement) | ||
{ | ||
return new DropViewTask().execute(statement, transactionManager, metadata, new AllowAllAccessControl(), queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make executeDropView
create DropView
statement itself
.hasMessage("View '%s' does not exist. Attempting to use view DDL syntax on a materialized view.", viewName); | ||
} | ||
|
||
private ListenableFuture<Void> executeRenameView(RenameView statement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make executeRenameView
create RenameView
itself
ec09bb5
to
469a7ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments addressed
throw semanticException( | ||
TABLE_NOT_FOUND, | ||
statement, | ||
"View '%s' does not exist. Attempting to use view DDL syntax on a table. Did you mean RENAME TABLE %s...?", viewName, viewName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaced with Did you mean ALTER TABLE %s RENAME
// no exception | ||
} | ||
|
||
private ListenableFuture<Void> executeDropMaterializedView(DropMaterializedView statement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. I refactored execute...
the same way for all new test classes
true); | ||
} | ||
|
||
private QueryStateMachine stateMachine(TransactionManager transactionManager, MetadataManager metadata, AccessControl accessControl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it static (with one additional parameter)
throw semanticException( | ||
TABLE_NOT_FOUND, | ||
statement, | ||
"Materialized view '%s' does not exist. Attempting to use materialized view DDL syntax on a view. Did you mean DROP VIEW %s?", name, name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"DDL" is not a very intuitive term for users. I would just rephrase as:
Materialized view '%s' does not exist, but a view with that name exists. Did you mean DROP VIEW %s?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I updated all the new error messages to this pattern.
Add information that operation fails due to action on a view, materialized view or table. Add tests that confirm DROP/RENAME TABLE does not work on VIEW and MATERIALIZED VIEW Add tests that confirm DROP/RENAME TABLE/VIEW does not work on VIEW and MATERIALIZED VIEW
469a7ac
to
0e9a2b5
Compare
@martint do you want to take a final look? |
statement, | ||
"Table '%s' does not exist, but a view with that name exists. Did you mean DROP VIEW %s?", tableName, tableName); | ||
} | ||
return immediateVoidFuture(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we be throwing an error from here? returning a void future here means that DROP TABLE IF EXISTS actually_a_view
succeeds without dropping anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In HiveQueryRunner:
trino:tpch> create view a_view as select * from nation;
CREATE VIEW
trino:tpch> drop table a_view;
Query 20210830_082947_00044_cki4q failed: line 1:1: Table 'hive.tpch.a_view' does not exist, but a view with that name exists. Did you mean DROP VIEW hive.tpch.a_view?
drop table a_view
trino:tpch> drop table if exists a_view;
DROP TABLE
trino:tpch>
This changes the semantics - I'm not sure if this was expected or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this is correct behaviour since the table does not exist and IF EXISTS
was used.
Add information that operation fails due to action on a materialized view.
Add tests that confirm DROP/RENAME TABLE/VIEW does not work on MATERIALIZED VIEW