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

sql: INSERT..ON CONFLICT updates columns not in UPDATE clause #13962

Closed
cuongdo opened this issue Mar 7, 2017 · 7 comments
Closed

sql: INSERT..ON CONFLICT updates columns not in UPDATE clause #13962

cuongdo opened this issue Mar 7, 2017 · 7 comments
Assignees
Milestone

Comments

@cuongdo
Copy link
Contributor

cuongdo commented Mar 7, 2017

Repro:

  1. Create a table and insert a row into it:
CREATE DATABASE IF NOT EXISTS test;

CREATE TABLE IF NOT EXISTS test."users" ("id"  SERIAL , "name" VARCHAR(255), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));

INSERT INTO test."users" ("id","name","createdAt","updatedAt") VALUES (1,'original','2017-03-07 16:05:50.924 +00:00','2017-03-07 16:05:50.924 +00:00');

SELECT * FROM test.users;
+----+----------+-------------------------------+-------------------------------+
| id |   name   |           createdAt           |           updatedAt           |
+----+----------+-------------------------------+-------------------------------+
|  1 | original | 2017-03-07 16:05:50.924+00:00 | 2017-03-07 16:05:50.924+00:00 |
+----+----------+-------------------------------+-------------------------------+
  1. Then do an upsert that updates all columns except createdAt:
INSERT INTO test."users" ("id","name","createdAt","updatedAt") VALUES (1,'UPDATED','2017-03-07 16:05:51.946 +00:00','2017-03-07 16:05:51.946 +00:00')
ON CONFLICT (id) DO UPDATE SET "id" = excluded."id", "name" = excluded."name", "updatedAt" = excluded."updatedAt";

SELECT * FROM test.users;
+----+---------+-------------------------------+-------------------------------+
| id |  name   |           createdAt           |           updatedAt           |
+----+---------+-------------------------------+-------------------------------+
|  1 | UPDATED | 2017-03-07 16:05:51.946+00:00 | 2017-03-07 16:05:51.946+00:00 |
+----+---------+-------------------------------+-------------------------------+

SHA: e66b654
Actual: both createdAt and updatedAt are updated
Expected: only updatedAt is updated

I've traced the issue to this line:
https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/tablewriter.go#L235

(*tableUpserter).init() is terminating very early. When I remove that whole if block, the upsert works correctly. There are several things I don't understand here, so assigning this to @danhhz.

Also, this scenario needs a logic test.

@cuongdo
Copy link
Contributor Author

cuongdo commented Mar 7, 2017

This seems to fix the issue, but I don't know what else this might break:

diff --git a/pkg/sql/tablewriter.go b/pkg/sql/tablewriter.go
index 5b34be0..bf1e828 100644
--- a/pkg/sql/tablewriter.go
+++ b/pkg/sql/tablewriter.go
@@ -227,6 +227,7 @@ func (tu *tableUpserter) init(txn *client.Txn) error {
        tu.indexKeyPrefix = sqlbase.MakeIndexKeyPrefix(tu.tableDesc, tu.tableDesc.PrimaryIndex.ID)

        allColsIdentityExpr := len(tu.ri.insertCols) == len(tu.tableDesc.Columns) &&
+               len(tu.updateCols) == len(tu.tableDesc.Columns) &&
                tu.evaler != nil && tu.evaler.isIdentityEvaler()
        // When adding or removing a column in a schema change (mutation), the user
        // can't specify it, which means we need to do a lookup and so we can't use

@danhhz
Copy link
Contributor

danhhz commented Mar 9, 2017

Just took a look at this; this query definitely shouldn't be fast pathed. Your fix seems to me like the correct one, but I want to briefly remind myself how it worked when I wrote it (before the various intervening refactors) before I commit to that.

@danhhz
Copy link
Contributor

danhhz commented Mar 9, 2017

This was always broken, thanks for tracking it down, @cuongdo! I'm sending a fix shortly

danhhz added a commit to danhhz/cockroach that referenced this issue Mar 9, 2017
The fast path was incorrectly taken when all columns in the DO UPDATE
clause were of the form `x = excluded.x` but not every column was in the
clause. This led to a correctness issue.

Closes cockroachdb#13962.
@cuongdo
Copy link
Contributor Author

cuongdo commented Mar 22, 2017

This is happening again as of 1758050.

Same repro as above.

@cuongdo cuongdo reopened this Mar 22, 2017
@bdarnell
Copy link
Contributor

#14034 was included in the draft release notes in cockroachdb/docs#1214. Does it need to be removed from the release notes for this week?

@danhhz
Copy link
Contributor

danhhz commented Mar 23, 2017

Ah, I see. The "id" = excluded."id" is throwing it off. It's a little wonky to include a column in the conflict index in the SET clause, but I can see how it would happen. Fix coming probably sometime today

@cuongdo
Copy link
Contributor Author

cuongdo commented Mar 23, 2017

Yes, it is a little strange, but unfortunately, generated queries such as those from ORMs do have some oddities.

cuongdo pushed a commit to cockroachdb/sequelize-cockroachdb that referenced this issue Mar 23, 2017
The Postgres dialect for Sequelize uses a stored procedure to implement
upserts, for compatibility with PostgreSQL before 9.5. CockroachDB has
upsert but no stored procedures, so this monkey patch for the built-in
Sequelize dialect replaces Sequelize's Model.upsert() with a version
that uses `INSERT ... ON CONFLICT DO UPDATE SET`, which CockroachDB does
support.

require()ing the package multiple times should be fine, as `index.js`
is idempotent.

Note that tests will not pass until cockroachdb/cockroach#13962 is
fixed.

Resolves cockroachdb/cockroach#12120
danhhz added a commit to danhhz/cockroach that referenced this issue Mar 27, 2017
The previous implementation was getting confused because it only looked
for the correct number of columns in the ON CONFLICT clause. cockroachdb#13962 was
broken because it included a conflict index column in the ON CONFLICT
clause (which doesn't make sense for a manually written query, but some
ORMs will do this).

Closes cockroachdb#13962.
danhhz added a commit to danhhz/cockroach that referenced this issue Mar 30, 2017
`INSERT .. ON CONFLICT ..` is temporarily no longer eligible for fast path.

The fast path is currently only enabled when the UPSERT alias is
explicitly selected by the user. It's possible to fast path some queries
of the form INSERT ... ON CONFLICT, but the utility is low and there are
lots of edge cases (that caused real correctness bugs cockroachdb#13437 cockroachdb#13962). As
a result, we've decided to remove this until after 1.0 and re-enable it
then. See cockroachdb#14482.

Closes cockroachdb#13962.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants