Skip to content

Conversation

iamjpotts
Copy link
Contributor

@iamjpotts iamjpotts commented Aug 8, 2025

  • De-duplicates implementations of AnyConnectionBackend::fetch_optional to be in terms of fetch_many
  • Standardizes all fetch_optional implementations to loop over the fetch_many result stream until the first row is found (previously, some implementations could return None if a row was present in the stream, but a statistics entry AnyQueryResult was returned first).

Builds on:
#3960

Does your PR solve an issue?

No

Is this a breaking change?

No, but it does add a default implementation for AnyConnectionBackend::fetch_optional which only the sqlite implementation overrides.

@iamjpotts iamjpotts force-pushed the jp/arguments-trait-lifetime-post-refactor-1 branch from 95f7772 to 3358247 Compare August 8, 2025 14:03
@bobozaur
Copy link
Contributor

bobozaur commented Aug 8, 2025

Wouldn't fetch be a more correct choice than fetch_many? Asking because multi statement queries (which you'd use with fetch_many) cannot be prepared and drivers tend to implement fetch_optional on top of fetch. The fact that fetch is implemented on top of fetch_many is in my view an implementation detail. sqlx-exasol, for instance, does not/can not do that: https://github.com/bobozaur/sqlx-exasol/blob/3c874343e3e75110f66a4260a74b1e0425120bf4/src/connection/executor.rs#L69

@iamjpotts
Copy link
Contributor Author

Wouldn't fetch be a more correct choice than fetch_many? Asking because multi statement queries (which you'd use with fetch_many) cannot be prepared and drivers tend to implement fetch_optional on top of fetch. The fact that fetch is implemented on top of fetch_many is in my view an implementation detail. sqlx-exasol, for instance, does not/can not do that: https://github.com/bobozaur/sqlx-exasol/blob/3c874343e3e75110f66a4260a74b1e0425120bf4/src/connection/executor.rs#L69

Thanks for bringing this up.

fetch_optional can still have a database platform specific implementation if this change were merged; any existing implementations of AnyConnectionBackend::fetch_optional should be unaffected. This change is intended to DRY / de-duplicate some of the existing implementations.

Though not a part of this PR, I am exploring an implementation of named parameters and this being de-duplicated helps with it (as well as simplifying the existing code).

@bobozaur
Copy link
Contributor

bobozaur commented Aug 8, 2025

You're absolutely right, my bad. My main fear was that this would alter existent behavior but, given that the method did not have a default impl, it won't. Also silly me because AnyConnectionBackend doesn't even expose a fetch method. Perhaps it should?

Just stumbled upon this issue as I was searching for something else and wanted to give my 2 cents. The whole reason I'm interested in this is because there seems not to be a clear separation between fetch/execute and fetch_many/execute_many in the sense that the former is meant to be used with single statement queries which can be prepared and can have parameters bound to them where as the latter is meant to be used with multi-statement queries and cannot be prepared and thus cannot have parameters bound to them.

I was even considering going to the extent of erroring out early in sqlx-exasol if a query passed to fetch_many/execute_many has arguments or if query.persistent() == true because the database would reject it anyway. But I'm second guessing my understanding now 😅 .

Anyway, just to wrap up my brainstorming, while AnyConnectionBackend does not need a separate fetch method, I think adding it would help better separate things. And then the default fetch_optional impl could be on top of fetch.

@iamjpotts
Copy link
Contributor Author

You're absolutely right, my bad.

Please feel free to add more comments even if you're not sure. I have a few more prs that you may be interested in a heads up on since you maintain an implementation of another db platform:

#3957
#3958
#3960

While I am not a maintainer, an extra set of eyes and comments may be helpful to the maintainer if/when he gets around to looking at these.

@iamjpotts iamjpotts force-pushed the jp/arguments-trait-lifetime-post-refactor-1 branch from 3358247 to b27505b Compare August 10, 2025 18:25
@iamjpotts iamjpotts force-pushed the jp/arguments-trait-lifetime-post-refactor-1 branch from b27505b to 65a8b39 Compare August 19, 2025 02:30
@abonander
Copy link
Collaborator

I don't think this is an appropriate change. I've been planning on deleting fetch_many() because its existence is confusing: #3108

TL;DR: the implementation isn't at all consistent between drivers:

  • SQLite supports multiple statements in one query string because that's the primary interface for executing queries, but it actually involves splitting the query string (which SQLite does for us), preparing and then executing each statement individually. I thought we had them all execute in a single transaction for consistency, but it looks like we don't even do that. That actually makes fetch_many() extremely hazardous, IMO.
    • Migrations at least use their own transaction:
      // Use a single transaction for the actual migration script and the essential bookeeping so we never
      // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966.
      // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for
      // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1
      // and update it once the actual transaction completed.
  • MySQL doesn't support multiple statements in one query string, but a query can return multiple result-sets if it invokes a stored procedure. This isn't really the same behavior as SQLite's so it doesn't make much sense to lump them under the same API.
  • Postgres, to my knowledge, can never return more than one result set from a prepared statement. Only batch/non-prepared queries (i.e. sqlx::raw_sql()) can return multiple result sets. So really, fetch_many() doesn't belong on anything but RawSql.

@iamjpotts
Copy link
Contributor Author

iamjpotts commented Aug 19, 2025

MySQL doesn't support multiple statements in one query string, but a query can return multiple result-sets if it invokes a stored procedure. This isn't really the same behavior as SQLite's so it doesn't make much sense to lump them under the same API.

That's also a behavior of SQL Server (though SQL Server support obviously is not a part of the core crate workspace anymore).

I'm going to leave this in draft since what (if anything) is done with it would hinge on the outcome of #3108.

@abonander
Copy link
Collaborator

We could adopt some sort of FetchMany trait that databases can implement where they're capable of returning multiple result sets from one statement.

@iamjpotts iamjpotts force-pushed the jp/arguments-trait-lifetime-post-refactor-1 branch from 65a8b39 to d552c6f Compare August 28, 2025 00:37
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