-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Make TryInsert functions within the packages module use INSERT ... ON CONFLICT #21063
base: main
Are you sure you want to change the base?
Make TryInsert functions within the packages module use INSERT ... ON CONFLICT #21063
Conversation
The TryInsert* functions within the packages models make incorrect assumptions about transactional isolation within most databases. It is perfectly possible for a SELECT to return nothing but an INSERT fail with a duplicate in most DBs as it is only INSERT that the locking occurs. This PR changes the code to simply try to insert first and if there is an error then attempt to SELECT from the table. If the SELECT works then the INSERT error is assumed to have been related to the unique constraint failure. This technique avoids us having to parse the error returned from the DBs as these are varied and different. If the SELECT fails then the INSERT error is returned to the user. Fix go-gitea#19586 Signed-off-by: Andrew Thornton <[email protected]>
ugh looks like my worries about transactions were right - we're gonna need to use an upsert style... |
I'm going to have to mark this WIP as we actually have to use the UPSERT forms otherwise the transactions will fail. |
Signed-off-by: Andrew Thornton <[email protected]>
0663049
to
b29ab42
Compare
Signed-off-by: Andrew Thornton <[email protected]>
…t-then-try-to-get
…t-then-try-to-get
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
models/packages/package.go
Outdated
return nil, err | ||
} | ||
if n != 0 { | ||
return p, nil |
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 insert methods need to return the filled bean. For example it's expected that ID
has a value in the return value which is not the case 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.
The code auto fills the ID of the bean in the same way that xorm's Insert() does.
…t-then-try-to-get
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
If I understand correctly, the test cases haven't cover the behavior of comment:
That's the only test code for asserting Update: out-dated _, _ = e.Exec("DELETE FROM one_unique")
has, err := db.InsertOnConflictDoNothing(ctx, &OneUnique{})
assert.Error(t, err)
assert.False(t, has)
toInsert := &OneUnique{Data: "test"}
has, err = db.InsertOnConflictDoNothing(ctx, toInsert)
assert.NoError(t, err)
assert.True(t, has)
assert.NotEqual(t, 0, toInsert.ID) |
If I understand correctly, there are still some problems in code:
|
Code changed so that I need to review again
Looks like things become too complex to difficult to review. |
It feels a bit like you're being deliberately obstructive and I don't quite understand why you're being this way. This might be a language issue - but there are better ways to say I think you should add a line that checks if the ID remains at 0. To which I might reply why would we care and why are you being so bloody difficult about it? That the ID remains unchanged is irrelevant. Fine I'll add it but honestly it's just making the test less clear and less easy to read for zero real world benefit. It feels like you're targeting me for some reason and I don't understand why. You left maintainers - was it because of me?
I was using
Again just make a suggestion. It's a good catch that I forgot about it but your tone is frankly insulting.
I don't understand your question? There are two cases:
In the second case we do not get an ID. Nor do we attempt to use it because 0 rows are affected.
This code is not attempting to update the |
Signed-off-by: Andrew Thornton <[email protected]>
I am not obstructive, but really want to help, I have tested a lot on my side. It depends on the ID-behavior mentioned in #21063 (comment) I have tested SQLite3 and MySQL. I do not think your code could work for some cases, and the tests were incorrect. For SQLite, For MySQL, And the test has to cover: insert(keyA), insert(keyB), insert(keyA) and assert getting A-id That's all.
No, when reviewing, I only care about the code and solution are right or not. Maybe you are also too strict for some problems, so there are more likely divergences. There are also some strong objections when my PRs get reviewed.
Sorry I didn't mean that. But just want make the problem clear. Could you suggest about how to express the problem more gently. ps: I have made some suggestions, but I didn't find that you had got the point, and I also got "Why don't you try it", maybe I also misunderstood something, but the problems are indeed there. We can have a private chat with the SQL problems later.
There is still no comment about why And, maybe I really misunderstood something -- about the design or about the logic (no comment nor test at the beginning), I do not know. Maybe the divergence starts here #21063 (comment): "The code auto fills the ID of the bean in the same way that xorm's Insert() does.": The bean should has the ID filled when there is no error. So feel free to dismiss all my comments. Thank you for your work! |
Signed-off-by: Andrew Thornton <[email protected]>
…t-then-try-to-get
We don't use or get the ID when the INSERT fails because there is a conflict because
Clearly I need to separate out the code paths here. There is no SQLite will always return the last inserted ID as part of its driver so we don't need to do that. If there is a conflict we don't use it - we do nothing.
No. As far as I can see that would cause every conflict to cause an update of the primary key (or fail.) The code as written: Again the code is not attempting to return the ID when there is a conflict.
See above, the code does not promise to get the ID when there is a conflict. It promises to do nothing.
Using INSERT IGNORE didn't work in my testing when there was a primary key with autoincrement - and if I didn't instead use the ON DUPLICATE KEY UPDATE no-op it would error out. This may have been warnings being returned rather than errors. It seems however now that
Ah. Perhaps that is the problem. I'm not promising to get the ID when there is no insert. The function has comment on it that says it will update the ID of the provided bean if it is inserted. It doesn't say it will do that when it's not inserted - and in fact it will NOT do that - instead on conflict it will do nothing as the function name suggests.
There were always tests - just not specific ones. The package repositories code has good coverage of this function. As requested though I've added some specific tests and covered and fixed some more edge cases which should make the function usable in other contexts too. |
From my knowledge, I always use
Thank you for letting me know more details. Anyway, it's clear for me now. I think this PR is good to have, maybe it could help to remove the workaround mutex for the package code after it gets merged: |
Signed-off-by: Andrew Thornton <[email protected]>
…t-then-try-to-get
Ah yes that mutex can be removed too. Apologies for the confusion earlier - it just felt we were going round and round in circles for no good reason and I couldn't understand why. |
…t-then-try-to-get
Signed-off-by: Andrew Thornton <[email protected]>
Thanks for your hard-working @zeripath . It really should be a part of XORM. :) |
How about taking this PR now, and move to XORM in the future? The "workaround mutex" really seems fragile, if there is a chance, we could use a DB insert-ignore instead of that in-process mutex for a stable release. |
Signed-off-by: Andrew Thornton <[email protected]>
func TestInsertOnConflictDoNothing(t *testing.T) { | ||
defer tests.PrepareTestEnv(t)() | ||
|
||
ctx := db.DefaultContext |
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.
Should we have a parallel test?
I think the key problem is the package property table have no unique keys. per #30335 (comment) |
The TryInsert* functions within the packages models make incorrect assumptions
about transactional isolation within most databases. It is perfectly possible for
a SELECT to return nothing but an INSERT fail with a duplicate in most DBs as it
is only INSERT that the locking occurs.
This PR introduces a common
InsertOnConflictDoNothing
function which will attemptto
INSERT
provided bean but will return0
as the number of rows affected if there is aconflict.
Fix #19586
Signed-off-by: Andrew Thornton [email protected]