Skip to content
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

Merged

Conversation

lukasz-stec
Copy link
Member

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

@@ -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()) {
Copy link
Member

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()) {

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member Author

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));
Copy link
Member

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?

Copy link
Member

@losipiuk losipiuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments/questions

@lukasz-stec lukasz-stec force-pushed the ls/improve-msg-drop-table-on-mv branch from ffd89e0 to d5e292d Compare August 13, 2021 11:13
Copy link
Member Author

@lukasz-stec lukasz-stec left a 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()) {
Copy link
Member Author

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()) {
Copy link
Member Author

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


assertUpdate(format("ALTER TABLE IF EXISTS %s RENAME TO %s_new", viewName, viewName));

assertQueryFails(
Copy link
Member

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)

Copy link
Member Author

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.

Copy link
Member

@losipiuk losipiuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. nits.

@lukasz-stec lukasz-stec force-pushed the ls/improve-msg-drop-table-on-mv branch from 61e1a86 to a49d545 Compare August 16, 2021 11:33
Copy link
Member Author

@lukasz-stec lukasz-stec left a 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(
Copy link
Member Author

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.

Copy link
Member

@sopel39 sopel39 left a 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);
Copy link
Member

@sopel39 sopel39 Aug 17, 2021

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)

Copy link
Member Author

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.
Copy link
Member

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);
Copy link
Member

@sopel39 sopel39 Aug 17, 2021

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.

@@ -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);
Copy link
Member

@sopel39 sopel39 Aug 17, 2021

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(
Copy link
Member

@sopel39 sopel39 Aug 17, 2021

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)?

@sopel39
Copy link
Member

sopel39 commented Aug 17, 2021

also, checkout ShowQueriesRewrite.Visitor#visitShowCreate how we deal with similar issue (object resolution) as here.

@lukasz-stec lukasz-stec force-pushed the ls/improve-msg-drop-table-on-mv branch from a49d545 to ec09bb5 Compare August 18, 2021 15:53
Copy link
Member Author

@lukasz-stec lukasz-stec left a 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);
Copy link
Member Author

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?

Copy link
Member

@sopel39 sopel39 left a 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");
}

Copy link
Member

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);
Copy link
Member

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

Copy link
Member Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can it be static?

Copy link
Member Author

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)));
Copy link
Member

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()));
Copy link
Member

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)
Copy link
Member

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

Copy link
Member Author

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)
Copy link
Member

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);
Copy link
Member

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)
Copy link
Member

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

@lukasz-stec lukasz-stec force-pushed the ls/improve-msg-drop-table-on-mv branch from ec09bb5 to 469a7ac Compare August 19, 2021 11:09
Copy link
Member Author

@lukasz-stec lukasz-stec left a 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);
Copy link
Member Author

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)
Copy link
Member Author

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)
Copy link
Member Author

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);
Copy link
Member

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?

Copy link
Member Author

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
@lukasz-stec lukasz-stec force-pushed the ls/improve-msg-drop-table-on-mv branch from 469a7ac to 0e9a2b5 Compare August 19, 2021 19:18
@sopel39
Copy link
Member

sopel39 commented Aug 20, 2021

@martint do you want to take a final look?

@sopel39 sopel39 merged commit 7dff335 into trinodb:master Aug 23, 2021
@sopel39 sopel39 mentioned this pull request Aug 23, 2021
10 tasks
statement,
"Table '%s' does not exist, but a view with that name exists. Did you mean DROP VIEW %s?", tableName, tableName);
}
return immediateVoidFuture();
Copy link
Member

@hashhar hashhar Aug 30, 2021

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.

Copy link
Member

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.

Copy link
Member Author

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.

@findepi findepi deleted the ls/improve-msg-drop-table-on-mv branch August 30, 2021 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

6 participants