-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-19211][SQL] Explicitly prevent Insert into View or Create View As Insert #17125
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
Conversation
|
Test build #73696 has finished for PR 17125 at commit
|
|
cc @gatorsmile @cloud-fan Please have a look at this when you have time, thanks! |
| // Inserting into a view is not allowed, we should throw an AnalysisException. | ||
| newTable match { | ||
| case v: View => | ||
| u.failAnalysis(s"${v.desc.identifier} is a view, inserting into a view is not allowed") |
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 move this to PreprocessTableInsertion?
|
|
||
| // CREATE VIEW AS INSERT INTO ... is not allowed, we should throw an AnalysisException. | ||
| analyzedPlan match { | ||
| case i: InsertIntoHadoopFsRelationCommand => |
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.
_: InsertIntoHadoopFsRelationCommand
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.
shall we forbid all commands? e.g. CREATE VIEW xxx AS CREATE TABLE ... should also be disallowed right?
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.
The sql parser only allows CREATE VIEW AS query here, a query can only be a SELECT ... or INSERT INTO ... or a CTE, so perhaps we don't have to consider other commands here.
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.
hmmmm, why INSERT INTO ... is a query?
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.
queryNoWith
: insertInto? queryTerm queryOrganization #singleInsertQuery
| fromClause multiInsertQueryBody+ #multiInsertQuery
;
Seems we have mixed them together.
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 fix it at parser side? cc @hvanhovell
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.
Hmmm... fixing this in grammar itself would a little bit of work (we also support multi-insert and that makes this harder). I suppose we could try to add a check in the AstBuilder.
Why not check this in the CreateViewCommand? That seems safer to me.
| analyzedPlan match { | ||
| case i: InsertIntoHadoopFsRelationCommand => | ||
| throw new AnalysisException("Creating a view as insert into a table is not allowed") | ||
| case i: InsertIntoDataSourceCommand => |
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.
The same here
| // CREATE VIEW AS INSERT INTO ... is not allowed, we should throw an AnalysisException. | ||
| analyzedPlan match { | ||
| case i: InsertIntoHadoopFsRelationCommand => | ||
| throw new AnalysisException("Creating a view as insert into a table is not allowed") |
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.
It will be nice to put a view name in the error message.
| i.copy(table = EliminateSubqueryAliases(lookupTableFromCatalog(u))) | ||
| val newTable = EliminateSubqueryAliases(lookupTableFromCatalog(u)) | ||
| // Inserting into a view is not allowed, we should throw an AnalysisException. | ||
| newTable match { |
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.
NIT In this case if would just write the following:
if (newTable.isInstanceOf[View]) {
u.failAnalysis(s"${v.desc.identifier} is a view, inserting into a view is not allowed")
}| val analyzedPlan = qe.analyzed | ||
|
|
||
| // CREATE VIEW AS INSERT INTO ... is not allowed, we should throw an AnalysisException. | ||
| analyzedPlan match { |
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.
This pattern match does not work with multi-inserts (hive feature). Those are represented using a Union of inserts.
| i.copy(table = EliminateSubqueryAliases(lookupTableFromCatalog(u))) | ||
| val newTable = EliminateSubqueryAliases(lookupTableFromCatalog(u)) | ||
| // Inserting into a view is not allowed, we should throw an AnalysisException. | ||
| if (newTable.isInstanceOf[View]) { |
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.
The rule ResolveRelations executes before PreprocessTableInsertion, so we can fail early here.
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.
We prefer to doing all the error handling in the same rule. It can help us find the hole and maintain the codes. cc @cloud-fan
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 fact I'm neutral with which rule this code block should be placed, but I feel this is not a typical "Preprocess" since the query failed and the process ends. @cloud-fan @hvanhovell any suggestions?
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.
Just move it to preprocess insert.
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.
Discussed with @cloud-fan and the concern is that in case the child of the view node is invalid(e.g. exists cyclic view reference or exceed max reference depth), we should still indicate that INSERT INTO VIEW is not allowed(instead of other error messages), so we should not resolve the child of the view, instead we throw an Exception here.
|
Test build #73780 has finished for PR 17125 at commit
|
|
Test build #73857 has finished for PR 17125 at commit
|
| withView("testView") { | ||
| // Single insert query | ||
| val e1 = intercept[ParseException] { | ||
| sql(s"CREATE VIEW testView AS INSERT INTO jt VALUES(1, 1)") |
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.
Nit: remove string interpolator.
| // Multi insert query | ||
| val e2 = intercept[ParseException] { | ||
| sql(s"CREATE VIEW testView AS FROM jt INSERT INTO tbl1 SELECT * WHERE jt.id < 5 " + | ||
| s"INSERT INTO tbl2 SELECT * WHERE jt.id > 4") |
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.
Nit: remove the above two string interpolators
| } | ||
| } | ||
|
|
||
| test("create view as insert into 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.
Move it to SparkSqlParserSuite?
If we do it here, it will be tested twice.
| } else { | ||
| // CREATE VIEW ... AS INSERT INTO is not allowed. | ||
| val query = ctx.query.queryNoWith | ||
| query match { |
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.
Nit: ctx.query.queryNoWith match {
| query match { | ||
| case s: SingleInsertQueryContext if s.insertInto != null => | ||
| operationNotAllowed("CREATE VIEW ... AS INSERT INTO", ctx) | ||
| case m: MultiInsertQueryContext => |
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.
Nit: case _: MultiInsertQueryContext =>
| } else { | ||
| // CREATE VIEW ... AS INSERT INTO is not allowed. | ||
| ctx.query.queryNoWith match { | ||
| case s: SingleInsertQueryContext if s.insertInto != null => |
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.
when s.insertInto will be null?
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.
For example, CREATE VIEW v AS SELECT * FROM jt.
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.
hmm, a select query is SingleInsertQueryContext?
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.
Yeah... You can see that in:
queryNoWith
: insertInto? queryTerm queryOrganization #singleInsertQuery
| fromClause multiInsertQueryBody+ #multiInsertQuery
;
| def apply(plan: LogicalPlan): LogicalPlan = plan resolveOperators { | ||
| case i @ InsertIntoTable(u: UnresolvedRelation, parts, child, _, _) if child.resolved => | ||
| i.copy(table = EliminateSubqueryAliases(lookupTableFromCatalog(u))) | ||
| i.copy(table = resolveRelation(EliminateSubqueryAliases(lookupTableFromCatalog(u)))) |
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.
why this change?
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.
When we try to insert into a view, the logical plan that lookupTableFromCatalog() returns is not resolved, so we have to perform resolveRelation() over the node.
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.
so we will resolve something to View by doing this?
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.
We will resolve the child of the view by doing this I think.
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.
why do we need to resolve the child of view? Once we see the pattern Insert(View, ...) we will throw exception, we don't care about whether the child of view is resolved or not.
|
LGTM except 2 questions |
|
Test build #73894 has finished for PR 17125 at commit
|
|
Test build #73958 has finished for PR 17125 at commit
|
| // Inserting into a view is not allowed, we should throw an AnalysisException. | ||
| if (newTable.isInstanceOf[View]) { | ||
| u.failAnalysis(s"${newTable.asInstanceOf[View].desc.identifier} is a view, inserting " + | ||
| s"into a view is not allowed") |
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.
Nit: s"into a view is not allowed" -> "into a view is not allowed"
| u.failAnalysis(s"${newTable.asInstanceOf[View].desc.identifier} is a view, inserting " + | ||
| s"into a view is not allowed") | ||
| } | ||
| i.copy(table = newTable) |
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.
How about?
lookupTableFromCatalog(u).canonicalized match {
case v: View =>
u.failAnalysis(s"Inserting into a view is not allowed. View: ${v.desc.identifier}.")
case other => i.copy(table = other)
}|
Test build #73978 has started for PR 17125 at commit |
|
LGTM, pending tests |
|
retest this please |
|
Test build #73981 has finished for PR 17125 at commit
|
|
LGTM too. |
|
Thanks! Merging to master. |
What changes were proposed in this pull request?
Currently we don't explicitly forbid the following behaviors:
After this PR, the behavior changes to:
How was this patch tested?
Add a new test case in
SparkSqlParserSuite;Update the corresponding test case in
SQLViewSuite.