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

Expose last_insert_id from Ok packet on MySQL #4271

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

mcronce
Copy link
Contributor

@mcronce mcronce commented Sep 19, 2024

Took a whack at implementing what we discussed in #3605 (finally); I'm sure I need to add some documentation at minimum.

Notably, I copied most of the body of execute_returning_id() from execute_returning_count(), not sure if that's how you want to handle that or not

Might have overabstracted; I implemented it using traits for extensibility.

I'll have a follow-up in diesel_async once this is merged.

@mcronce
Copy link
Contributor Author

mcronce commented Sep 19, 2024

I don't think that that test failure has anything to do with my changes?

@weiznich weiznich requested a review from a team September 22, 2024 18:29
Copy link
Member

@weiznich weiznich left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. I've added a few comments around the Safety remarks of the new unsafe function. I think we do need additional restrictions/checks there to make sure the result is defined.

As for the implementation: I personally think we don't need to introduce these two new traits there, as we could just implement the relevant methods directly on InsertStatement and MysqlConnection. We don't need to be super generic there as these methods are only relevant for this combination. (That's nothing I would block the merge on, although I would be happy to see that resolved)

// we have not called result yet, so calling `execute` is
// fine
let stmt_use = unsafe { stmt.execute() }?;
Ok(unsafe { stmt_use.insert_id() })
Copy link
Member

Choose a reason for hiding this comment

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

There should be a Safety: explain how the invariants of the called function are uphold comment here.

Additionally as far as I can see the insert_id() function states that it is only safe to call this function on insert statements, while we can call it on arbitrary statements here. That sounds like the current implementation is unsound.

In the end we want to replace the T: QueryFragement<Self::Backend> bound above with essentially InsertStatement to restrict the allowed statements to only insert statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

@@ -160,6 +160,11 @@ impl<'a> StatementUse<'a> {
.map_err(|e| Error::DeserializationError(Box::new(e)))
}

/// SAFETY: This should only be called for INSERT queries.
pub(in crate::mysql::connection) unsafe fn insert_id(&self) -> u64 {
ffi::mysql_stmt_insert_id(self.inner.stmt.as_ptr())
Copy link
Member

Choose a reason for hiding this comment

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

The mysql documentation states that:

Return value is undefined if statement does not set AUTO_INCREMENT value.

https://dev.mysql.com/doc/c-api/8.0/en/mysql-stmt-insert-id.html

Given that it is possible to have insert statements that do not have a AUTO_INCREMENT value I cannot see how the safety comment above is sufficient. We do need some sort of additional checks here to ensure that the return value is defined, but I'm not sure what would be a sufficient check for that.

Copy link
Contributor Author

@mcronce mcronce Sep 23, 2024

Choose a reason for hiding this comment

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

While that's how the documentation is written, I don't think that the result is UB. The value is always present in the Ok packet and will always be a valid byte sequence for a u64. The value absolutely might not make any sense if no AUTO_INCREMENT column is present, but that's not the same as the "spooky action at a difference" type of Undefined Behavior.

Looking at what mysql_common does, for example, the only check it performs is during packet deserialization; last_insert_id is an Option<u64> in their representation, and if last_insert_id on the wire is 0, they consider that None

I also, FWIW, don't have any idea what kind of check we'd be able to perform in a query context for that beyond possibly matching that 0 check

Copy link
Member

Choose a reason for hiding this comment

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

I'm still wary to relay on something that's explicitly documented as not being defined as that could change at any point in the future. Remember that we want to provide a reasonable level of stability that should be decoupled from our dependencies. So yes, it might not be undefined behavior in terms of programming to read this value, the result still stays undefined and might change at any point. It's something that at least I do not feel comfortable with exposing in a stable API.

What we can easily do is to restrict it to insert statements with Integer primary keys at compile time by adding the relevant trait bounds on the target type for InsertStatement. What's harder to do is to check if the column is actually marked as AUTO_INCREMENT, as that would additional changes to include this information in the generated schema. That's still not impossible, although I think it might be to much trouble in this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another way to quickly get this feature is expose it under some unstable feature flag, if you not against that.

Copy link
Member

Choose a reason for hiding this comment

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

I would rather prefer restricting it at least to Integer primary keys, as that isn't much harder than marking it as unstable feature. After all that should just require one or two lines of trait bounds on the relevant method.

DB: Backend = <Conn as Connection>::Backend,
>: Sized
{
/// Execute this command
Copy link
Member

Choose a reason for hiding this comment

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

There should be a doc test/example on this function to show how this works

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

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.

3 participants