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

UPDATE statement on a VIEW built with a UNION clause does not compile #1018

Closed
drymarau opened this issue Oct 21, 2018 · 3 comments
Closed
Assignees
Labels
component: sql-psi Should be implemented in https://github.com/AlecStrong/sql-psi feature
Milestone

Comments

@drymarau
Copy link
Contributor

Versions: 1.0.0-alpha5, 1.0.0-rc2

Have a couple of tables, a VIEW, a TRIGGER on a VIEW and an UPDATE statement:

CREATE TABLE foo (
  id INTEGER NOT NULL PRIMARY KEY,
  name TEXT NOT NULL,
  selected INTEGER AS Boolean NOT NULL
);

CREATE TABLE bar (
  id INTEGER NOT NULL PRIMARY KEY,
  short_name TEXT NOT NULL,
  full_name TEXT NOT NULL,
  selected INTEGER AS Boolean NOT NULL
);

CREATE VIEW foobar AS
SELECT id, name, selected FROM foo
UNION
SELECT id, short_name AS name, selected FROM bar;

CREATE TRIGGER foobar_update_added
INSTEAD OF UPDATE OF selected ON foobar
BEGIN
  UPDATE foo SET selected = new.selected WHERE id = new.id;
  UPDATE bar SET selected = new.selected WHERE id = new.id;
END;

updateFoobarSelected:
UPDATE OR IGNORE foobar SET selected = ? WHERE id = ?;

Compilation fails with the following error:

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':foobar:generateDebugSqlDelightInterface'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:110)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:77)
	at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51)
	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59)
	at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101)
	at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44)
	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91)
	at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.run(EventFiringTaskExecuter.java:51)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:46)
	at org.gradle.execution.taskgraph.LocalTaskInfoExecutor.execute(LocalTaskInfoExecutor.java:42)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:277)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:262)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:135)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:130)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.execute(DefaultTaskPlanExecutor.java:200)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.executeWithWork(DefaultTaskPlanExecutor.java:191)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.run(DefaultTaskPlanExecutor.java:130)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Failed to compile SqliteUpdateStmtLimitedImpl(UPDATE_STMT_LIMITED): [] :
UPDATE OR IGNORE foobar SET selected = ? WHERE id = ?
	at com.squareup.sqldelight.core.compiler.SqlDelightCompilerKt.tryWithElement(SqlDelightCompiler.kt:119)
	at com.squareup.sqldelight.core.compiler.QueriesTypeGenerator.generateType(QueriesTypeGenerator.kt:76)
	at com.squareup.sqldelight.core.compiler.SqlDelightCompiler.writeQueriesType$sqldelight_compiler(SqlDelightCompiler.kt:80)
	at com.squareup.sqldelight.core.compiler.SqlDelightCompiler.compile(SqlDelightCompiler.kt:36)
	at com.squareup.sqldelight.core.SqlDelightEnvironment$generateSqlDelightFiles$2.invoke(SqlDelightEnvironment.kt:108)
	at com.squareup.sqldelight.core.SqlDelightEnvironment$generateSqlDelightFiles$2.invoke(SqlDelightEnvironment.kt:53)
	at com.alecstrong.sqlite.psi.core.SqliteCoreEnvironment$forSourceFiles$1.processFile(SqliteCoreEnvironment.kt:69)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:100)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:97)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:97)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:97)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:97)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContentUnderDirectory(SqliteCoreEnvironment.kt:97)
	at com.alecstrong.sqlite.psi.core.CoreFileIndex.iterateContent(SqliteCoreEnvironment.kt:91)
	at com.alecstrong.sqlite.psi.core.SqliteCoreEnvironment.forSourceFiles(SqliteCoreEnvironment.kt:67)
	at com.squareup.sqldelight.core.SqlDelightEnvironment.generateSqlDelightFiles(SqlDelightEnvironment.kt:105)
	at com.squareup.sqldelight.gradle.SqlDelightTask.generateSqlDelightFiles(SqlDelightTask.kt:47)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26)
	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:801)
	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:768)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99)
	... 34 more
Caused by: java.lang.IllegalArgumentException: List has more than one element.
	at kotlin.collections.CollectionsKt___CollectionsKt.single(_Collections.kt:480)
	at com.squareup.sqldelight.core.compiler.model.NamedMutator$tableEffected$2.invoke(NamedMutator.kt:36)
	at com.squareup.sqldelight.core.compiler.model.NamedMutator$tableEffected$2.invoke(NamedMutator.kt:28)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.squareup.sqldelight.core.compiler.model.NamedMutator.getTableEffected$sqldelight_compiler(NamedMutator.kt)
	at com.squareup.sqldelight.core.compiler.MutatorQueryGenerator$notifyQueries$1.invoke(MutatorQueryGenerator.kt:73)
	at com.squareup.sqldelight.core.compiler.MutatorQueryGenerator$notifyQueries$1.invoke(MutatorQueryGenerator.kt:28)
	at com.squareup.sqldelight.core.lang.SqlDelightFile.iterateSqliteFiles(SqlDelightFile.kt:92)
	at com.squareup.sqldelight.core.compiler.MutatorQueryGenerator.notifyQueries(MutatorQueryGenerator.kt:71)
	at com.squareup.sqldelight.core.compiler.MutatorQueryGenerator.type(MutatorQueryGenerator.kt:167)
	at com.squareup.sqldelight.core.compiler.QueriesTypeGenerator$generateType$$inlined$forEach$lambda$2.invoke(QueriesTypeGenerator.kt:82)
	at com.squareup.sqldelight.core.compiler.QueriesTypeGenerator$generateType$$inlined$forEach$lambda$2.invoke(QueriesTypeGenerator.kt:18)
	at com.squareup.sqldelight.core.compiler.SqlDelightCompilerKt.tryWithElement(SqlDelightCompiler.kt:117)
	... 68 more

Removing UNION and the second SELECT from the VIEW lets the compiler to run just fine.

@AlecKazakova
Copy link
Collaborator

ah right. thats gonna be an interesting one. Do you know what happens to an update on a view that doesn't have a trigger? Does it crash at runtime when you execute the view or does it just do nothing? Would be nice if we can also fail at compile time if you have an update statement on a view with on corresponding trigger (if SQLite also disallows it)

@drymarau
Copy link
Contributor Author

SQLite docs seem to imply that it's an error to perform INSERT, UPDATE or DELETE on a view without an INSTEAD OF trigger (emphasis mine):

If one or more ON INSERT, ON DELETE or ON UPDATE triggers are defined on a view, then it is not an error to execute an INSERT, DELETE or UPDATE statement on the view, respectively.

A little test confirms it:

$ sqlite3
SQLite version 3.24.0 2018-06-04 14:10:15
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> CREATE TABLE foo (
   ...>   id INTEGER NOT NULL PRIMARY KEY,
   ...>   name TEXT NOT NULL
   ...> );
sqlite> CREATE VIEW bar AS SELECT * FROM foo;
sqlite> UPDATE bar SET name = 'Baz' WHERE id = 1;
Error: cannot modify bar because it is a view
sqlite> CREATE TRIGGER bar_update
   ...> INSTEAD OF UPDATE OF name ON bar
   ...> BEGIN
   ...>   UPDATE foo SET name = new.name WHERE id = new.id;
   ...> END;
sqlite> UPDATE bar SET name = 'Baz' WHERE id = 1;
sqlite> 

Would be nice if we can also fail at compile time if you have an update statement on a view with on corresponding trigger (if SQLite also disallows it)

That'd be awesome

@AlecKazakova
Copy link
Collaborator

awesome. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: sql-psi Should be implemented in https://github.com/AlecStrong/sql-psi feature
Projects
None yet
Development

No branches or pull requests

2 participants