-
-
Notifications
You must be signed in to change notification settings - Fork 527
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
Don't call last_insert_id if not needed #1403
Conversation
src/executor/insert.rs
Outdated
let last_insert_id = db.execute(statement).await?.last_insert_id(); | ||
ValueTypeOf::<A>::try_from_u64(last_insert_id).ok() | ||
|
||
enum QueryOrExecResult { |
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's quite awkward to have an enum where you already know the type it has in each branch. Can we refactor otherwise? To get rid of the unreachable
.
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.
I think the alternative is that we split the implementation into two versions.
src/executor/insert.rs
Outdated
None => return Err(DbErr::UnpackInsertId), | ||
}, | ||
}; | ||
Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)), |
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 seems to be we just have to add a db.execute(statement).await?
here rather than introduce an entirely new type.
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, if we add the execute statement in the Some
branch, we can inline the top of the function in the None
branch, further reducing the amount of work done in case we provide the primary key. Each branch would have its own singular await point.
What's the rationale for reducing the number of await points?
src/executor/insert.rs
Outdated
match insert_result { | ||
QueryOrExecResult::Query(row) => { | ||
let cols = PrimaryKey::<A>::iter() | ||
.map(|col| col.to_string()) |
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.
That might be a bigger change, but I feel that try_get_many
should accept a slice of AsRef<str>
and IdenStatic
should implement AsRef<str>
so we don't have to do this whole conversion at all.
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.
I agree, but that might be another PR
I don't see issues/1357 being run, but I assure it works on my machine(TM) |
Refactored. How about now? |
src/executor/insert.rs
Outdated
})?; | ||
res.try_get_many("", cols.as_ref()).ok() | ||
|
||
let last_insert_id = if db.support_returning() { |
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.
That's much better! Do we absolutely care about RecordNotInserted
if the primary key is provided? How can we detect it if the db doesn't support returning?
If we don't especially care about it, we could rewrite it as :
match (primary_key, db.support_returning()) {
(Some(value_tuple), _) => {db.execute(); Ok(FromValueTuple)},
(None, true) =>{db.query_all() ... },
(None, false) => {res = db.execute(); res.last_insert_id()},
}
WDYT?
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.
Yes, I think we care. Because that's the case when we inserted a duplicate record?
How can we detect it if the db doesn't support returning
Now it's a compile-time hardcoded constant, with SQLite being false. Ideally, SQLite support should be determined by its version detected runtime.
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.
No, I mean how can we detect that the record was not inserted if the db doesn't support returning?
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.
Umm, I see what you mean
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.
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.
If we can't detect that if the db doesn't support returning, then the question is should we align the behaviour or not?
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 sqlite, by calling sqlite3_changes()
(or by selecting changes()
in the query), we can get the number of rows affected. With that, we can see if it's 0 (didn't insert anything) or >0 (inserted at least one thing). That wouldn't catch the case where we insert >1 value but only some failed, but for single value inserts, it would already be helpful.
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.
I think the code as of "Refactor" can be released in 0.10 and further changes should go into 0.11
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.
Sounds good. Just FYI, I tested select changes();
on SQlite3, it seems to do what we want: after a single insert, it'll return 1 if the insert was successful, and 0 otherwise. And it's scoped to the DB connection as well, so it should be usable even in async contexts.
But anyway, inserting a single duplicate row returns an error, so probably the await?
should trigger a return, no?
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.
Yes. It should be a UNIQUE CONSTRAINT ERROR unless if we specified ON CONFLICT DO NOTHING.
let last_insert_id = match (primary_key, db.support_returning()) { | ||
(Some(value_tuple), _) => { | ||
let res = db.execute(statement).await?; | ||
if res.rows_affected() == 0 { |
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.
oh, if we have that, that's great!
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.
Looks great!
let last_insert_id_opt = match db.support_returning() { | ||
true => { | ||
|
||
let last_insert_id = match (primary_key, db.support_returning()) { |
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.
Nice rewrite!!
#[error("None of the records are being inserted")] | ||
RecordNotInserted, |
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.
I think this is a breaking change that should goes into 0.11.x
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's actually never released in 0.10
Fixes #1357