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

Don't call last_insert_id if not needed #1403

Merged
merged 6 commits into from Jan 19, 2023
Merged

Don't call last_insert_id if not needed #1403

merged 6 commits into from Jan 19, 2023

Conversation

tyt2y3
Copy link
Member

@tyt2y3 tyt2y3 commented Jan 18, 2023

Fixes #1357

@tyt2y3 tyt2y3 changed the title Issues/1357 Don't call last_insert_id if not needed Jan 18, 2023
src/executor/insert.rs Outdated Show resolved Hide resolved
let last_insert_id = db.execute(statement).await?.last_insert_id();
ValueTypeOf::<A>::try_from_u64(last_insert_id).ok()

enum QueryOrExecResult {

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.

Copy link
Member Author

@tyt2y3 tyt2y3 Jan 18, 2023

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.

None => return Err(DbErr::UnpackInsertId),
},
};
Some(value_tuple) => Ok(FromValueTuple::from_value_tuple(value_tuple)),

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.

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?

match insert_result {
QueryOrExecResult::Query(row) => {
let cols = PrimaryKey::<A>::iter()
.map(|col| col.to_string())

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.

Copy link
Member Author

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

@tyt2y3
Copy link
Member Author

tyt2y3 commented Jan 18, 2023

I don't see issues/1357 being run, but I assure it works on my machine(TM)

@tyt2y3
Copy link
Member Author

tyt2y3 commented Jan 18, 2023

Refactored. How about now?

})?;
res.try_get_many("", cols.as_ref()).ok()

let last_insert_id = if db.support_returning() {

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?

Copy link
Member Author

@tyt2y3 tyt2y3 Jan 18, 2023

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.

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?

Copy link
Member Author

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@tyt2y3 tyt2y3 Jan 18, 2023

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?

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.

Copy link
Member Author

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

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?

Copy link
Member Author

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 {

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!

Copy link

@nitnelave nitnelave left a 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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice rewrite!!

Comment on lines +61 to +62
#[error("None of the records are being inserted")]
RecordNotInserted,
Copy link
Member

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

Copy link
Member Author

@tyt2y3 tyt2y3 Jan 19, 2023

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

tyt2y3 added a commit that referenced this pull request Jan 19, 2023
@tyt2y3 tyt2y3 merged commit 0557e7b into master Jan 19, 2023
@tyt2y3 tyt2y3 deleted the issues/1357 branch January 19, 2023 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hitting 'negative last_insert_rowid' panic with Sqlite
3 participants