From 7becff70ba21ab314b2e613671165fcdb25b9aec Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 7 Dec 2023 14:35:01 +0800 Subject: [PATCH 01/59] fix duplicate "`" in FromRow "default" attribute doc comment --- sqlx-core/src/from_row.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/from_row.rs b/sqlx-core/src/from_row.rs index 234dd94176..a6129b9611 100644 --- a/sqlx-core/src/from_row.rs +++ b/sqlx-core/src/from_row.rs @@ -91,7 +91,7 @@ use crate::{error::Error, row::Row}; /// will set the value of the field `location` to the default value of `Option`, /// which is `None`. /// -/// Moreover, if the struct has an implementation for [`Default`], you can use the `default`` +/// Moreover, if the struct has an implementation for [`Default`], you can use the `default` /// attribute at the struct level rather than for each single field. If a field does not appear in the result, /// its value is taken from the `Default` implementation for the struct. /// For example: From 91c6e6668b824604cd8ea3c5e915d3882c5261be Mon Sep 17 00:00:00 2001 From: Jesse Wang Date: Tue, 12 Dec 2023 22:53:00 +1300 Subject: [PATCH 02/59] fix(postgres): avoid unnecessary flush in PgCopyIn::read_from --- sqlx-postgres/src/copy.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sqlx-postgres/src/copy.rs b/sqlx-postgres/src/copy.rs index f5a6ea8573..c6daea7774 100644 --- a/sqlx-postgres/src/copy.rs +++ b/sqlx-postgres/src/copy.rs @@ -221,10 +221,6 @@ impl> PgCopyIn { } let conn: &mut PgConnection = self.conn.as_deref_mut().expect("copy_from: conn taken"); - - // flush any existing messages in the buffer and clear it - conn.stream.flush().await?; - loop { let buf = conn.stream.write_buffer_mut(); From 9c45eaa062217ed44f8d4129e9ae4022b9a5e713 Mon Sep 17 00:00:00 2001 From: Tadgh Henry <47073445+tadghh@users.noreply.github.com> Date: Tue, 2 Jan 2024 12:49:53 -0600 Subject: [PATCH 03/59] Fixed badge styling whitespace is being interpreted by the a tag causing blue hyperlinks to show up between the badges. --- README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 14d965837d..6afef1b138 100644 --- a/README.md +++ b/README.md @@ -10,27 +10,20 @@
- actions status - + actions status Crates.io version - + alt="Crates.io version" /> - chat - + chat - docs.rs docs - + docs.rs docs - Download + Download
From 9b0a09387c399aacccd4a00c0cccaf861b825a07 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Fri, 5 Jan 2024 18:51:27 -0800 Subject: [PATCH 04/59] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6afef1b138..e713c96690 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ SQLx is an async, pure Rust SQL crate featuring compile-time check † The SQLite driver uses the libsqlite3 C library as SQLite is an embedded database (the only way we could be pure Rust for SQLite is by porting _all_ of SQLite to Rust). -†† SQLx uses `#![forbid(unsafe_code)]` unless the `sqlite` feature is enabled. As the SQLite driver interacts -with C, those interactions are `unsafe`. +†† SQLx uses `#![forbid(unsafe_code)]` unless the `sqlite` feature is enabled. +The SQLite driver directly invokes the SQLite3 API via `libsqlite3-sys`, which requires `unsafe`. From 4c229946055765040414f0c0f3395fb882244a13 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Fri, 5 Jan 2024 18:55:34 -0800 Subject: [PATCH 05/59] fix(core): export `net::socket::WriteBuffer` --- sqlx-core/src/net/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/net/mod.rs b/sqlx-core/src/net/mod.rs index 3c75f32c92..f9c43668ab 100644 --- a/sqlx-core/src/net/mod.rs +++ b/sqlx-core/src/net/mod.rs @@ -1,4 +1,6 @@ mod socket; pub mod tls; -pub use socket::{connect_tcp, connect_uds, BufferedSocket, Socket, SocketIntoBox, WithSocket}; +pub use socket::{ + connect_tcp, connect_uds, BufferedSocket, Socket, SocketIntoBox, WithSocket, WriteBuffer, +}; From 7044a928586a37bd9989c0e895b2d9a2a9a8a8a0 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Fri, 5 Jan 2024 19:05:16 -0800 Subject: [PATCH 06/59] fix: fix new warnings that cropped up --- sqlx-core/src/net/socket/mod.rs | 4 +++- sqlx-postgres/src/message/mod.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/net/socket/mod.rs b/sqlx-core/src/net/socket/mod.rs index f11c10dedd..076aa9a3e3 100644 --- a/sqlx-core/src/net/socket/mod.rs +++ b/sqlx-core/src/net/socket/mod.rs @@ -244,6 +244,8 @@ pub async fn connect_uds, Ws: WithSocket>( ) -> crate::Result { #[cfg(not(unix))] { + drop((path, with_socket)); + return Err(io::Error::new( io::ErrorKind::Unsupported, "Unix domain sockets are not supported on this platform", @@ -270,7 +272,7 @@ pub async fn connect_uds, Ws: WithSocket>( return Ok(with_socket.with_socket(stream)); } - #[cfg(not(feature = "_rt-async-std"))] + #[cfg(all(unix, not(feature = "_rt-async-std")))] { crate::rt::missing_rt((path, with_socket)) } diff --git a/sqlx-postgres/src/message/mod.rs b/sqlx-postgres/src/message/mod.rs index 6e7daad239..ef1dbfabf0 100644 --- a/sqlx-postgres/src/message/mod.rs +++ b/sqlx-postgres/src/message/mod.rs @@ -37,6 +37,7 @@ pub use copy::{CopyData, CopyDone, CopyFail, CopyResponse}; pub use data_row::DataRow; pub use describe::Describe; pub use execute::Execute; +#[allow(unused_imports)] pub use flush::Flush; pub use notification::Notification; pub use parameter_description::ParameterDescription; From a5c1cd021c5aafe5d24882169b8d22b7908f20bb Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 14 Jan 2024 21:56:54 -0800 Subject: [PATCH 07/59] sqlx-core: Remove dotenvy dependency sqlx-core doesn't use dotenvy. Removing it means that users who only depend on sqlx with sqlite and default-feature = false don't need it. --- Cargo.lock | 1 - sqlx-core/Cargo.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84f95476bc..45ea420047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3277,7 +3277,6 @@ dependencies = [ "crc", "crossbeam-queue", "digest", - "dotenvy", "either", "encoding_rs", "event-listener 2.5.3", diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 98688969c3..384fbab169 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -89,8 +89,6 @@ hashlink = "0.8.0" indexmap = "2.0" event-listener = "2.5.2" -dotenvy = "0.15" - [dev-dependencies] sqlx = { workspace = true, features = ["postgres", "sqlite", "mysql", "migrate", "macros", "time", "uuid"] } tokio = { version = "1", features = ["rt"] } From 2cc3e0f90bff932386beb68ae4d86c575cea5157 Mon Sep 17 00:00:00 2001 From: Dawson <101001810+Dawsoncodes@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:49:25 +0300 Subject: [PATCH 08/59] Minor fixes (#2955) * doc link fix variable declaration moved for better readability * migration version abstraction --- sqlx-cli/src/migrate.rs | 18 +++++++++--------- sqlx-core/src/migrate/migrator.rs | 5 +++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index ef18051369..1ab8929fc1 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -109,11 +109,6 @@ pub async fn add( ) -> anyhow::Result<()> { fs::create_dir_all(migration_source).context("Unable to create migrations directory")?; - // if the migrations directory is empty - let has_existing_migrations = fs::read_dir(migration_source) - .map(|mut dir| dir.next().is_some()) - .unwrap_or(false); - let migrator = Migrator::new(Path::new(migration_source)).await?; // Type of newly created migration will be the same as the first one // or reversible flag if this is the first migration @@ -144,6 +139,11 @@ pub async fn add( )?; } + // if the migrations directory is empty + let has_existing_migrations = fs::read_dir(migration_source) + .map(|mut dir| dir.next().is_some()) + .unwrap_or(false); + if !has_existing_migrations { let quoted_source = if migration_source != "migrations" { format!("{migration_source:?}") @@ -163,7 +163,7 @@ sqlx::migrate!({}).run(<&your_pool OR &mut your_connection>).await?; Note that the compiler won't pick up new migrations if no Rust source files have changed. You can create a Cargo build script to work around this with `sqlx migrate build-script`. -See: https://docs.rs/sqlx/0.5/sqlx/macro.migrate.html +See: https://docs.rs/sqlx/latest/sqlx/macro.migrate.html "#, quoted_source ); @@ -228,7 +228,7 @@ pub async fn info(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow: ), ); println!( - "local migration has checksum {}", + "local migration has checksum {}", short_checksum(&migration.checksum) ) } @@ -268,7 +268,7 @@ pub async fn run( ) -> anyhow::Result<()> { let migrator = Migrator::new(Path::new(migration_source)).await?; if let Some(target_version) = target_version { - if !migrator.iter().any(|m| target_version == m.version) { + if !migrator.version_exists(target_version) { bail!(MigrateError::VersionNotPresent(target_version)); } } @@ -363,7 +363,7 @@ pub async fn revert( ) -> anyhow::Result<()> { let migrator = Migrator::new(Path::new(migration_source)).await?; if let Some(target_version) = target_version { - if target_version != 0 && !migrator.iter().any(|m| target_version == m.version) { + if target_version != 0 && !migrator.version_exists(target_version) { bail!(MigrateError::VersionNotPresent(target_version)); } } diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 585d6d9d86..9a4d7a7a9f 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -86,6 +86,11 @@ impl Migrator { self.migrations.iter() } + /// Check if a migration version exists. + pub fn version_exists(&self, version: i64) -> bool { + self.iter().any(|m| m.version == version) + } + /// Run any pending migrations against the database; and, validate previously applied migrations /// against the current migration source to detect accidental changes in previously-applied migrations. /// From b4269b7c865f05701fd0d2d1da1b2d8be7a159b8 Mon Sep 17 00:00:00 2001 From: kshramt Date: Thu, 18 Jan 2024 12:15:22 +0900 Subject: [PATCH 09/59] Support `query!` for cargo-free systems (#2927) Co-authored-by: kshramt --- sqlx-macros-core/src/query/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlx-macros-core/src/query/mod.rs b/sqlx-macros-core/src/query/mod.rs index 0a12ad1537..cb60ea35b9 100644 --- a/sqlx-macros-core/src/query/mod.rs +++ b/sqlx-macros-core/src/query/mod.rs @@ -166,13 +166,13 @@ pub fn expand_input<'a>( // Check SQLX_OFFLINE_DIR, then local .sqlx, then workspace .sqlx. let dirs = [ - env("SQLX_OFFLINE_DIR").ok().map(PathBuf::from), - Some(METADATA.manifest_dir.join(".sqlx")), - Some(METADATA.workspace_root().join(".sqlx")), + || env("SQLX_OFFLINE_DIR").ok().map(PathBuf::from), + || Some(METADATA.manifest_dir.join(".sqlx")), + || Some(METADATA.workspace_root().join(".sqlx")), ]; let Some(data_file_path) = dirs .iter() - .filter_map(|path| path.as_ref()) + .filter_map(|path| path()) .map(|path| path.join(&filename)) .find(|path| path.exists()) else { From 609fcc33e3cc098e208fbb50ee70f8e10463dd80 Mon Sep 17 00:00:00 2001 From: takenoko-gohan <59072363+takenoko-gohan@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:15:51 +0900 Subject: [PATCH 10/59] Update ahash to 0.8.7 (#2996) --- Cargo.lock | 16 ++++++++-------- sqlx-core/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45ea420047..9200b1a9e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom", @@ -1547,7 +1547,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.7", "allocator-api2", ] @@ -3264,7 +3264,7 @@ dependencies = [ name = "sqlx-core" version = "0.7.3" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.7", "async-io", "async-std", "atoi", @@ -4482,18 +4482,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 384fbab169..5d08010ee1 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -51,7 +51,7 @@ uuid = { workspace = true, optional = true } async-io = { version = "1.9.0", optional = true } paste = "1.0.6" -ahash = "0.8.6" +ahash = "0.8.7" atoi = "2.0" bytes = "1.1.0" From fd53e95ff9bddfe5e45901d8cfc3a23e7efe2c5a Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Wed, 17 Jan 2024 19:16:02 -0800 Subject: [PATCH 11/59] doc(FAQ): add entry explaining prepared statements (#2997) --- FAQ.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/FAQ.md b/FAQ.md index 40ebb39c11..514d4ed3b8 100644 --- a/FAQ.md +++ b/FAQ.md @@ -69,6 +69,162 @@ However, if you do encounter this error, please try to capture a Wireshark or `t in covering cases that trigger this (as it might indicate a protocol handling bug or the server is doing something non-standard): https://github.com/rustls/rustls/issues/893 +---------------------------------------------------------------- +### How does SQLx help prevent SQL Injection? +### How do Query Parameters work? +### Why does SQLx use Prepared Statements for most queries? +### Can I Use Query Parameters to add conditional SQL to my query? +### Why can't I use DDL (e.g. `CREATE TABLE`, `ALTER TABLE`, etc.) with the `sqlx::query*()` functions or `sqlx::query*!()` macros? + +These questions can all be answered by a thorough explanation of prepared statements. Feel free to skip the parts you already know. + +Back in the day, if a web application wanted to include user input in a SQL query, +a search parameter for example, it had no choice but to simply format that data into the query. +PHP applications used to be full of snippets like this: + +```php +/* Imagine this is user input */ +$city = "Munich"; + +/* $query = "SELECT country FROM city WHERE name='Munich'" */ +$query = sprintf("SELECT country FROM city WHERE name='%s'", $city); +$result = $mysqli->query($query); +``` + +However, this leaves the application vulnerable to [SQL injection attacks](https://en.wikipedia.org/wiki/SQL_injection), +because it's trivial to craft an input string that will terminate the existing query and begin a new one, +and the database won't know the difference and will execute both. As illustrated in the famous XKCD #327: + +Exploits of a Mom + +The fictional school's student database application might have contained a query that looked like this: +```php +$student_name = "Robert');DROP TABLE Students;--" + +$query = sprintf("INSERT INTO Students (name) VALUES ('%s')", $student_name); +$result = $mysqli->query($query); +``` + +When formatted into the middle of this query, the maliciously crafted input string closes the quotes and finishes the statement (`Robert');`), +then starts another one with the nefarious payload (`DROP TABLE Students;`), and causes the rest of the original query to be ignored by starting a SQL comment (`--`). +Thus, the database server sees, and executes, three separate statements like so: + +```SQL +INSERT INTO Students(firstname) VALUES ('Robert'); +DROP TABLE Students; +--'); +``` + +And thus the school has lost this year's student records (at least they had last years' backed up?). + +The original mitigation for this attack was to make sure that any untrustworthy user input was properly escaped (or "sanitized"), +and many frameworks provided utility functions for this, such as PHP's [`mysqli::real_escape_string()`](https://www.php.net/manual/en/mysqli.real-escape-string.php) (not to be confused with the obsolete [`mysql_real_escape_string()`](https://www.php.net/manual/en/function.mysql-real-escape-string) or [`mysql_escape_string()`](https://www.php.net/manual/en/function.mysql-escape-string.php)). + +These would prefix any syntactically significant characters (in this case, quotation marks) with a backslash, +so it's less likely to affect the database server's interpretation of the query: + +```php +$student_name = $mysqli->real_escape_string("Robert');DROP TABLE Students;--"); + +/* + Everything is okay now as the dastardly single-quote has been inactivated by the backslash: + "INSERT INTO Students (name) VALUES ('Robert\');DROP TABLE Students;--');" +*/ +$query = sprintf("INSERT INTO Students (name) VALUES ('%s')", $student_name); +``` + +The database server sees the backslash and knows that the single-quote is part of the string content, not its terminating character. + +However, this was something that you still had to _remember_ to do, making it only half a solution. Additionally, properly escaping the string requires knowledge of the current character set of the connection which is why the `mysqli` object is a required parameter +(or the receiver in object-oriented style). And you could always just forget to wrap the string parameter in quotes (`'%s'`) in the first place, which these wouldn't help with. + +Even when everything is working correctly, formatting dynamic data into a query still requires the database server to +re-parse and generate a new query plan with every new variant--caching helps, but is not a silver bullet. + +#### Prepared Statements to the rescue! + +These solve both problems (injection and re-parsing) by **completely separating** the query from any dynamic input data. + +Instead of formatting data into the query, you use a (database-specific) token to signify a value that will be passed separately: + +```SQL +-- MySQL +INSERT INTO Students (name) VALUES(?); +-- Postgres and SQLite +INSERT INTO Students (name) VALUES($1); +``` + +The database will substitute a given value when _executing_ the query, long after it's finished parsing it. +The database will effectively treat the parameter as a variable. +There is, by design, **no way** for a query parameter to modify the SQL of a query, +unless you're using some `exec()`-like SQL function that lets you execute a string as a query, +but then hopefully you know what you're doing. + +In fact, parsing and executing prepared statements are explicitly separate steps in pretty much every database's protocol, +where the query string, without any values attached, is parsed first and given an identifier, then a separate execution step +simply passes that identifier along with the values to substitute. + +The response from the initial parsing often contains useful metadata about the query, which SQLx's query macros use to great effect +(see "How do the query macros work under the hood?" below). + +Unfortunately, query parameters do not appear to be standardized, as every database has a different syntax. +Look through the project for specific examples for your database, and consult your database manual about prepared statements +for more information. + +The syntax SQLite supports is effectively a superset of many databases' syntaxes, including MySQL and Postgres. +To simplify our examples, we use the same syntax for Postgres and SQLite; though SQLite's syntax technically allows +alphanumeric identifiers, that's not currently exposed in SQLx, and it's expected to be a numeric 1-based index like Postgres. + +Some databases, like MySQL and PostgreSQL, may have special statements that let the user explicitly create and execute prepared statements (often `PREPARE` and `EXECUTE`, respectively), +but most of the time an application, or library like SQLx, will interact with prepared statements using specialized messages in the database's client/server protocol. +Prepared statements created through this protocol may or may not be accessible using explicit SQL statements, depending on the database flavor. + +Since the dynamic data is handled separately, an application only needs to prepare a statement once, +and then it can execute it as many times as it wants with all kinds of different data (at least of the same type and number). +Prepared statements are generally tracked per-connection, so an application may need to re-prepare a statement several times over its lifetime as it opens new connections. +If it uses a connection pool, ideally all connections will eventually have all statements already prepared (assuming a closed set of statements), +so the overhead of parsing and generating a query plan is amortized. + +Query parameters are also usually transmitted in a compact binary format, which saves bandwidth over having to send them as human-readable strings. + +Because of the obvious security and performance benefits of prepared statements, the design of SQLx tries to make them as easy to use and transparent as possible. +The `sqlx::query*()` family of functions, as well as the `sqlx::query*!()` macros, will always prefer prepared statements. This was an explicit goal from day one. + +SQLx will **never** substitute query parameters for values on the client-side, it will always let the database server handle that. We have concepts for making certain usage patterns easier, +like expanding a dynamic list of parameters (e.g. `?, ?, ?, ?, ...`) since MySQL and SQLite don't really support arrays, but will never simply format data into a query implicitly. + +Our pervasive use of prepared statements can cause some problems with third-party database implementations, e.g. projects like CockroachDB or PGBouncer that support the Postgres protocol but have their own semantics. +In this case, you might try setting [`.persistent(false)`](https://docs.rs/sqlx/latest/sqlx/query/struct.Query.html#method.persistent) before executing a query, which will cause the connection not to retain +the prepared statement after executing it. + +Not all SQL statements are allowed in prepared statements, either. +As a general rule, DML (Data Manipulation Language, i.e. `SELECT`, `INSERT`, `UPDATE`, `DELETE`) is allowed while DDL (Data Definition Language, e.g. `CREATE TABLE`, `ALTER TABLE`, etc.) is not. +Consult your database manual for details. + +To execute DDL requires using a different API than `query*()` or `query*!()` in SQLx. +Ideally, we'd like to encourage you to use SQLx's built-in support for migrations (though that could be better documented, we'll get to it). +However, in the event that isn't feasible, or you have different needs, you can execute pretty much any statement, +including multiple statements separated by semicolons (`;`), by directly invoking methods of the [`Executor` trait](https://docs.rs/sqlx/latest/sqlx/trait.Executor.html#method.execute) +on any type that implements it, and passing your query string, e.g.: + +```rust +use sqlx::postgres::PgConnection; +use sqlx::Executor; + +let mut conn: PgConnection = connect().await?; + +conn + .execute( + "CREATE TABLE IF NOT EXISTS StudentContactInfo (student_id INTEGER, person_name TEXT, relation TEXT, phone TEXT);\ + INSERT INTO StudentContactInfo (student_id, person_name, relation, phone) \ + SELECT student_id, guardian_name, guardian_relation, guardian_phone FROM Students;\ + ALTER TABLE Students DROP guardian_name, guardian_relation, guardian_phone;" + ) + .await?; +``` + +This is also pending a redesign to make it easier to discover and utilize. + ---------------------------------------------------------------- ### How can I do a `SELECT ... WHERE foo IN (...)` query? From 9d2c52178d4c7da84ee17d49d538cab3e91a69fa Mon Sep 17 00:00:00 2001 From: Ian Gilfillan Date: Sat, 20 Jan 2024 05:46:10 +0200 Subject: [PATCH 12/59] Update README to clarify MariaDB support (#3001) --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e713c96690..ecbf0137cc 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ SQLx is an async, pure Rust SQL crate featuring compile-time check - **Compile-time checked queries** (if you want). See [SQLx is not an ORM](#sqlx-is-not-an-orm). -- **Database Agnostic**. Support for [PostgreSQL], [MySQL], [SQLite]. +- **Database Agnostic**. Support for [PostgreSQL], [MySQL], [MariaDB], [SQLite]. - [MSSQL] was supported prior to version 0.7, but has been removed pending a full rewrite of the driver as part of our [SQLx Pro initiative]. - **Pure Rust**. The Postgres and MySQL/MariaDB drivers are written in pure Rust using **zero** unsafe†† code. @@ -83,6 +83,7 @@ The SQLite driver directly invokes the SQLite3 API via `libsqlite3-sys`, which r [postgresql]: http://postgresql.org/ [sqlite]: https://sqlite.org/ [mysql]: https://www.mysql.com/ +[mariadb]: https://www.mariadb.org/ [mssql]: https://www.microsoft.com/en-us/sql-server [SQLx Pro initiative]: https://github.com/launchbadge/sqlx/discussions/1616 @@ -100,7 +101,7 @@ The SQLite driver directly invokes the SQLite3 API via `libsqlite3-sys`, which r - Simple (unprepared) query execution including fetching results into the same `Row` types used by the high-level API. Supports batch execution and returns results from all statements. -- Transport Layer Security (TLS) where supported ([MySQL] and [PostgreSQL]). +- Transport Layer Security (TLS) where supported ([MySQL], [MariaDB] and [PostgreSQL]). - Asynchronous notifications using `LISTEN` and `NOTIFY` for [PostgreSQL]. @@ -232,14 +233,14 @@ use sqlx::postgres::PgPoolOptions; // or #[actix_web::main] async fn main() -> Result<(), sqlx::Error> { // Create a connection pool - // for MySQL, use MySqlPoolOptions::new() + // for MySQL/MariaDB, use MySqlPoolOptions::new() // for SQLite, use SqlitePoolOptions::new() // etc. let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:password@localhost/test").await?; - // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL) + // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL/MariaDB) let row: (i64,) = sqlx::query_as("SELECT $1") .bind(150_i64) .fetch_one(&pool).await?; From 31e541ac7a9c7d18ee2b3b91c58349e77eac28f7 Mon Sep 17 00:00:00 2001 From: Thomas de Zeeuw Date: Sat, 20 Jan 2024 11:36:06 +0100 Subject: [PATCH 13/59] Fix handling of deferred constraints for PostgreSQL (#2913) --- sqlx-postgres/src/connection/executor.rs | 15 ++++++++++----- tests/postgres/postgres.rs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/sqlx-postgres/src/connection/executor.rs b/sqlx-postgres/src/connection/executor.rs index 67ffa16ac9..b1ce29c516 100644 --- a/sqlx-postgres/src/connection/executor.rs +++ b/sqlx-postgres/src/connection/executor.rs @@ -402,13 +402,18 @@ impl<'c> Executor<'c> for &'c mut PgConnection { let s = self.run(sql, arguments, 1, persistent, metadata).await?; pin_mut!(s); - while let Some(s) = s.try_next().await? { - if let Either::Right(r) = s { - return Ok(Some(r)); + // With deferred constraints we need to check all responses as we + // could get a OK response (with uncommitted data), only to get an + // error response after (when the deferred constraint is actually + // checked). + let mut ret = None; + while let Some(result) = s.try_next().await? { + match result { + Either::Right(r) if ret.is_none() => ret = Some(r), + _ => {} } } - - Ok(None) + Ok(ret) }) } diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 477a455d74..352d3278ea 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -1813,3 +1813,27 @@ async fn test_shrink_buffers() -> anyhow::Result<()> { Ok(()) } + +#[sqlx_macros::test] +async fn test_error_handling_with_deferred_constraints() -> anyhow::Result<()> { + let mut conn = new::().await?; + + sqlx::query("CREATE TABLE IF NOT EXISTS deferred_constraint ( id INTEGER PRIMARY KEY )") + .execute(&mut conn) + .await?; + + sqlx::query("CREATE TABLE IF NOT EXISTS deferred_constraint_fk ( fk INTEGER CONSTRAINT deferred_fk REFERENCES deferred_constraint(id) DEFERRABLE INITIALLY DEFERRED )") + .execute(&mut conn) + .await?; + + let result: sqlx::Result = + sqlx::query_scalar("INSERT INTO deferred_constraint_fk VALUES (1) RETURNING fk") + .fetch_one(&mut conn) + .await; + + let err = result.unwrap_err(); + let db_err = err.as_database_error().unwrap(); + assert_eq!(db_err.constraint(), Some("deferred_fk")); + + Ok(()) +} From 29dcd44a6a0a1825c2680e69e86844d0849561c1 Mon Sep 17 00:00:00 2001 From: Lars Schumacher Date: Sun, 21 Jan 2024 03:20:04 +0100 Subject: [PATCH 14/59] fix(mysql): Close prepared statement if persistence is disabled (#2905) * close prepared statement if persistence or statement cache are disabled * add tests --- sqlx-mysql/src/connection/executor.rs | 90 ++++++++++++++++++--------- tests/mysql/mysql.rs | 66 ++++++++++++++++++++ 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/sqlx-mysql/src/connection/executor.rs b/sqlx-mysql/src/connection/executor.rs index b668c67396..21fec1ec6b 100644 --- a/sqlx-mysql/src/connection/executor.rs +++ b/sqlx-mysql/src/connection/executor.rs @@ -25,16 +25,10 @@ use futures_util::{pin_mut, TryStreamExt}; use std::{borrow::Cow, sync::Arc}; impl MySqlConnection { - async fn get_or_prepare<'c>( + async fn prepare_statement<'c>( &mut self, sql: &str, - persistent: bool, ) -> Result<(u32, MySqlStatementMetadata), Error> { - if let Some(statement) = self.cache_statement.get_mut(sql) { - // is internally reference-counted - return Ok((*statement).clone()); - } - // https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html // https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK @@ -72,11 +66,23 @@ impl MySqlConnection { column_names: Arc::new(column_names), }; - if persistent && self.cache_statement.is_enabled() { - // in case of the cache being full, close the least recently used statement - if let Some((id, _)) = self.cache_statement.insert(sql, (id, metadata.clone())) { - self.stream.send_packet(StmtClose { statement: id }).await?; - } + Ok((id, metadata)) + } + + async fn get_or_prepare_statement<'c>( + &mut self, + sql: &str, + ) -> Result<(u32, MySqlStatementMetadata), Error> { + if let Some(statement) = self.cache_statement.get_mut(sql) { + // is internally reference-counted + return Ok((*statement).clone()); + } + + let (id, metadata) = self.prepare_statement(sql).await?; + + // in case of the cache being full, close the least recently used statement + if let Some((id, _)) = self.cache_statement.insert(sql, (id, metadata.clone())) { + self.stream.send_packet(StmtClose { statement: id }).await?; } Ok((id, metadata)) @@ -102,21 +108,37 @@ impl MySqlConnection { let mut columns = Arc::new(Vec::new()); let (mut column_names, format, mut needs_metadata) = if let Some(arguments) = arguments { - let (id, metadata) = self.get_or_prepare( - sql, - persistent, - ) - .await?; - - // https://dev.mysql.com/doc/internals/en/com-stmt-execute.html - self.stream - .send_packet(StatementExecute { - statement: id, - arguments: &arguments, - }) - .await?; - - (metadata.column_names, MySqlValueFormat::Binary, false) + if persistent && self.cache_statement.is_enabled() { + let (id, metadata) = self + .get_or_prepare_statement(sql) + .await?; + + // https://dev.mysql.com/doc/internals/en/com-stmt-execute.html + self.stream + .send_packet(StatementExecute { + statement: id, + arguments: &arguments, + }) + .await?; + + (metadata.column_names, MySqlValueFormat::Binary, false) + } else { + let (id, metadata) = self + .prepare_statement(sql) + .await?; + + // https://dev.mysql.com/doc/internals/en/com-stmt-execute.html + self.stream + .send_packet(StatementExecute { + statement: id, + arguments: &arguments, + }) + .await?; + + self.stream.send_packet(StmtClose { statement: id }).await?; + + (metadata.column_names, MySqlValueFormat::Binary, false) + } } else { // https://dev.mysql.com/doc/internals/en/com-query.html self.stream.send_packet(Query(sql)).await?; @@ -269,7 +291,15 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection { Box::pin(async move { self.stream.wait_until_ready().await?; - let (_, metadata) = self.get_or_prepare(sql, true).await?; + let metadata = if self.cache_statement.is_enabled() { + self.get_or_prepare_statement(sql).await?.1 + } else { + let (id, metadata) = self.prepare_statement(sql).await?; + + self.stream.send_packet(StmtClose { statement: id }).await?; + + metadata + }; Ok(MySqlStatement { sql: Cow::Borrowed(sql), @@ -287,7 +317,9 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection { Box::pin(async move { self.stream.wait_until_ready().await?; - let (_, metadata) = self.get_or_prepare(sql, false).await?; + let (id, metadata) = self.prepare_statement(sql).await?; + + self.stream.send_packet(StmtClose { statement: id }).await?; let columns = (&*metadata.columns).clone(); diff --git a/tests/mysql/mysql.rs b/tests/mysql/mysql.rs index 586cef2ed0..ba10824f89 100644 --- a/tests/mysql/mysql.rs +++ b/tests/mysql/mysql.rs @@ -237,6 +237,57 @@ async fn it_caches_statements() -> anyhow::Result<()> { Ok(()) } +#[sqlx_macros::test] +async fn it_closes_statements_with_persistent_disabled() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let old_statement_count = select_statement_count(&mut conn).await.unwrap_or_default(); + + for i in 0..2 { + let row = sqlx::query("SELECT ? AS val") + .bind(i) + .persistent(false) + .fetch_one(&mut conn) + .await?; + + let val: i32 = row.get("val"); + + assert_eq!(i, val); + } + + let new_statement_count = select_statement_count(&mut conn).await.unwrap_or_default(); + + assert_eq!(old_statement_count, new_statement_count); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_closes_statements_with_cache_disabled() -> anyhow::Result<()> { + setup_if_needed(); + + let mut url = url::Url::parse(&env::var("DATABASE_URL")?)?; + url.query_pairs_mut() + .append_pair("statement-cache-capacity", "0"); + + let mut conn = MySqlConnection::connect(url.as_ref()).await?; + + let old_statement_count = select_statement_count(&mut conn).await.unwrap_or_default(); + + for index in 1..=10_i32 { + let _ = sqlx::query("SELECT ?") + .bind(index) + .execute(&mut conn) + .await?; + } + + let new_statement_count = select_statement_count(&mut conn).await.unwrap_or_default(); + + assert_eq!(old_statement_count, new_statement_count); + + Ok(()) +} + #[sqlx_macros::test] async fn it_can_bind_null_and_non_null_issue_540() -> anyhow::Result<()> { let mut conn = new::().await?; @@ -510,3 +561,18 @@ async fn test_shrink_buffers() -> anyhow::Result<()> { Ok(()) } + +async fn select_statement_count(conn: &mut MySqlConnection) -> Result { + // Fails if performance schema does not exist + sqlx::query_scalar( + r#" + SELECT COUNT(*) + FROM performance_schema.threads AS t + INNER JOIN performance_schema.prepared_statements_instances AS psi + ON psi.OWNER_THREAD_ID = t.THREAD_ID + WHERE t.processlist_id = CONNECTION_ID() + "#, + ) + .fetch_one(conn) + .await +} From a7862ae416450acd5caea881dd9db482b414ab67 Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Sat, 20 Jan 2024 23:21:34 -0300 Subject: [PATCH 15/59] feat: expose connect options fields (#2891) Exposes some of the main fields for PgConnectOptions and MySqlConnectOptions. Exposed fields include: host, port, socket, ssl mode, application name, and charset. --- sqlx-mysql/src/options/mod.rs | 140 +++++++++++++++++++++---- sqlx-postgres/src/options/mod.rs | 173 +++++++++++++++++++++++-------- 2 files changed, 248 insertions(+), 65 deletions(-) diff --git a/sqlx-mysql/src/options/mod.rs b/sqlx-mysql/src/options/mod.rs index b4fe540751..a797576937 100644 --- a/sqlx-mysql/src/options/mod.rs +++ b/sqlx-mysql/src/options/mod.rs @@ -152,20 +152,6 @@ impl MySqlConnectOptions { self } - /// Get the current database name. - /// - /// # Example - /// - /// ```rust - /// # use sqlx_core::mysql::MySqlConnectOptions; - /// let options = MySqlConnectOptions::new() - /// .database("mysqldb"); - /// assert!(options.get_database().is_some()); - /// ``` - pub fn get_database(&self) -> Option<&str> { - self.database.as_deref() - } - /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated /// with the server. /// @@ -175,7 +161,7 @@ impl MySqlConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::Required); /// ``` @@ -189,7 +175,7 @@ impl MySqlConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_ca("path/to/ca.crt"); @@ -204,7 +190,7 @@ impl MySqlConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_ca_from_pem(vec![]); @@ -219,7 +205,7 @@ impl MySqlConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_cert("path/to/client.crt"); @@ -238,7 +224,7 @@ impl MySqlConnectOptions { /// This is for illustration purposes only. /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// /// const CERT: &[u8] = b"\ /// -----BEGIN CERTIFICATE----- @@ -259,7 +245,7 @@ impl MySqlConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_key("path/to/client.key"); @@ -278,7 +264,7 @@ impl MySqlConnectOptions { /// This is for illustration purposes only. /// /// ```rust - /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// /// const KEY: &[u8] = b"\ /// -----BEGIN PRIVATE KEY----- @@ -348,3 +334,115 @@ impl MySqlConnectOptions { self } } + +impl MySqlConnectOptions { + /// Get the current host. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .host("127.0.0.1"); + /// assert_eq!(options.get_host(), "127.0.0.1"); + /// ``` + pub fn get_host(&self) -> &str { + &self.host + } + + /// Get the server's port. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .port(6543); + /// assert_eq!(options.get_port(), 6543); + /// ``` + pub fn get_port(&self) -> u16 { + self.port + } + + /// Get the socket path. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .socket("/tmp"); + /// assert!(options.get_socket().is_some()); + /// ``` + pub fn get_socket(&self) -> Option<&PathBuf> { + self.socket.as_ref() + } + + /// Get the server's port. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .username("foo"); + /// assert_eq!(options.get_username(), "foo"); + /// ``` + pub fn get_username(&self) -> &str { + &self.username + } + + /// Get the current database name. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .database("postgres"); + /// assert!(options.get_database().is_some()); + /// ``` + pub fn get_database(&self) -> Option<&str> { + self.database.as_deref() + } + + /// Get the SSL mode. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::{MySqlConnectOptions, MySqlSslMode}; + /// let options = MySqlConnectOptions::new(); + /// assert!(matches!(options.get_ssl_mode(), MySqlSslMode::Preferred)); + /// ``` + pub fn get_ssl_mode(&self) -> MySqlSslMode { + self.ssl_mode + } + + /// Get the server charset. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new(); + /// assert_eq!(options.get_charset(), "utf8mb4"); + /// ``` + pub fn get_charset(&self) -> &str { + &self.charset + } + + /// Get the server collation. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_mysql::MySqlConnectOptions; + /// let options = MySqlConnectOptions::new() + /// .collation("collation"); + /// assert!(options.get_collation().is_some()); + /// ``` + pub fn get_collation(&self) -> Option<&str> { + self.collation.as_deref() + } +} diff --git a/sqlx-postgres/src/options/mod.rs b/sqlx-postgres/src/options/mod.rs index 2da782841c..2a2c43006a 100644 --- a/sqlx-postgres/src/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -130,7 +130,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new(); /// ``` pub fn new() -> Self { @@ -196,7 +196,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .host("localhost"); /// ``` @@ -205,20 +205,6 @@ impl PgConnectOptions { self } - /// Get the current host. - /// - /// # Example - /// - /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; - /// let options = PgConnectOptions::new() - /// .host("127.0.0.1"); - /// assert_eq!(options.get_host(), "127.0.0.1"); - /// ``` - pub fn get_host(&self) -> &str { - self.host.as_str() - } - /// Sets the port to connect to at the server host. /// /// The default port for PostgreSQL is `5432`. @@ -226,7 +212,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .port(5432); /// ``` @@ -252,7 +238,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .username("postgres"); /// ``` @@ -266,7 +252,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .username("root") /// .password("safe-and-secure"); @@ -281,7 +267,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .database("postgres"); /// ``` @@ -290,20 +276,6 @@ impl PgConnectOptions { self } - /// Get the current database name. - /// - /// # Example - /// - /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; - /// let options = PgConnectOptions::new() - /// .database("postgres"); - /// assert!(options.get_database().is_some()); - /// ``` - pub fn get_database(&self) -> Option<&str> { - self.database.as_deref() - } - /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated /// with the server. /// @@ -315,7 +287,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// .ssl_mode(PgSslMode::Require); /// ``` @@ -331,7 +303,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) @@ -347,7 +319,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) @@ -367,7 +339,7 @@ impl PgConnectOptions { /// This is for illustration purposes only. /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// /// const CERT: &[u8] = b"\ /// -----BEGIN CERTIFICATE----- @@ -389,7 +361,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) @@ -409,7 +381,7 @@ impl PgConnectOptions { /// This is for illustration purposes only. /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// /// const KEY: &[u8] = b"\ /// -----BEGIN PRIVATE KEY----- @@ -431,7 +403,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) @@ -458,7 +430,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .application_name("my-app"); /// ``` @@ -505,7 +477,7 @@ impl PgConnectOptions { /// /// ### Examples /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// /// let mut options = PgConnectOptions::new() /// // for Redshift and Postgres 10 @@ -525,7 +497,7 @@ impl PgConnectOptions { /// # Example /// /// ```rust - /// # use sqlx_core::postgres::PgConnectOptions; + /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .options([("geqo", "off"), ("statement_timeout", "5min")]); /// ``` @@ -564,6 +536,119 @@ impl PgConnectOptions { } } +impl PgConnectOptions { + /// Get the current host. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .host("127.0.0.1"); + /// assert_eq!(options.get_host(), "127.0.0.1"); + /// ``` + pub fn get_host(&self) -> &str { + &self.host + } + + /// Get the server's port. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .port(6543); + /// assert_eq!(options.get_port(), 6543); + /// ``` + pub fn get_port(&self) -> u16 { + self.port + } + + /// Get the socket path. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .socket("/tmp"); + /// assert!(options.get_socket().is_some()); + /// ``` + pub fn get_socket(&self) -> Option<&PathBuf> { + self.socket.as_ref() + } + + /// Get the server's port. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .username("foo"); + /// assert_eq!(options.get_username(), "foo"); + /// ``` + pub fn get_username(&self) -> &str { + &self.username + } + + /// Get the current database name. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .database("postgres"); + /// assert!(options.get_database().is_some()); + /// ``` + pub fn get_database(&self) -> Option<&str> { + self.database.as_deref() + } + + /// Get the SSL mode. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::{PgConnectOptions, PgSslMode}; + /// let options = PgConnectOptions::new(); + /// assert!(matches!(options.get_ssl_mode(), PgSslMode::Prefer)); + /// ``` + pub fn get_ssl_mode(&self) -> PgSslMode { + self.ssl_mode + } + + /// Get the application name. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .application_name("service"); + /// assert!(options.get_application_name().is_some()); + /// ``` + pub fn get_application_name(&self) -> Option<&str> { + self.application_name.as_deref() + } + + /// Get the options. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_postgres::PgConnectOptions; + /// let options = PgConnectOptions::new() + /// .options([("foo", "bar")]); + /// assert!(options.get_options().is_some()); + /// ``` + pub fn get_options(&self) -> Option<&str> { + self.options.as_deref() + } +} + fn default_host(port: u16) -> String { // try to check for the existence of a unix socket and uses that let socket = format!(".s.PGSQL.{port}"); From 5890afe95bcd0c5c0be197193c2a1ea1ec6bc2ea Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:16:06 -0500 Subject: [PATCH 16/59] chore(deps): Replace unmaintained tempdir crate with tempfile (#3006) Signed-off-by: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> --- Cargo.lock | 190 +++++++++++++++++++------------------- Cargo.toml | 2 +- tests/sqlite/sqlcipher.rs | 4 +- 3 files changed, 100 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9200b1a9e6..c1c301df0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,7 +341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e" dependencies = [ "nix 0.26.4", - "rand 0.8.5", + "rand", ] [[package]] @@ -431,7 +431,7 @@ dependencies = [ "getrandom", "instant", "pin-project-lite", - "rand 0.8.5", + "rand", "tokio", ] @@ -1184,12 +1184,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1262,7 +1262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.38.25", + "rustix 0.38.30", "windows-sys 0.48.0", ] @@ -1340,12 +1340,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "2.0.0" @@ -1853,9 +1847,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libm" @@ -1893,9 +1887,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -2127,7 +2121,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand", "smallvec", "zeroize", ] @@ -2290,7 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2579,19 +2573,6 @@ dependencies = [ "nibble_vec", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -2600,7 +2581,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2610,24 +2591,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -2643,7 +2609,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2666,15 +2632,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2733,15 +2690,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "rend" version = "0.4.1" @@ -2806,7 +2754,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.4", + "rand_core", "signature", "spki", "subtle", @@ -2823,7 +2771,7 @@ dependencies = [ "borsh", "bytes", "num-traits", - "rand 0.8.5", + "rand", "rkyv", "serde", "serde_json", @@ -2851,15 +2799,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", ] [[package]] @@ -3124,7 +3072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -3217,7 +3165,7 @@ dependencies = [ "hex", "libsqlite3-sys", "paste", - "rand 0.8.5", + "rand", "rand_xoshiro", "serde", "serde_json", @@ -3227,7 +3175,7 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "sqlx-test", - "tempdir", + "tempfile", "time", "tokio", "trybuild", @@ -3338,7 +3286,7 @@ dependencies = [ "axum", "dotenvy", "once_cell", - "rand 0.8.5", + "rand", "regex", "serde", "serde_json", @@ -3510,7 +3458,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand", "rsa", "rust_decimal", "serde", @@ -3556,7 +3504,7 @@ dependencies = [ "memchr", "num-bigint", "once_cell", - "rand 0.8.5", + "rand", "rust_decimal", "serde", "serde_json", @@ -3715,27 +3663,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.25", - "windows-sys 0.48.0", + "rustix 0.38.30", + "windows-sys 0.52.0", ] [[package]] @@ -4348,6 +4286,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4378,6 +4325,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4390,6 +4352,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4402,6 +4370,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4414,6 +4388,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4426,6 +4406,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4438,6 +4424,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4450,6 +4442,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4462,6 +4460,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.19" diff --git a/Cargo.toml b/Cargo.toml index f13975a94f..e49c2de930 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,7 +174,7 @@ url = "2.2.2" rand = "0.8.4" rand_xoshiro = "0.6.0" hex = "0.4.3" -tempdir = "0.3.7" +tempfile = "3.9.0" criterion = {version = "0.4", features = ["async_tokio"]} # Needed to test SQLCipher diff --git a/tests/sqlite/sqlcipher.rs b/tests/sqlite/sqlcipher.rs index a14a1e518d..5d228619b5 100644 --- a/tests/sqlite/sqlcipher.rs +++ b/tests/sqlite/sqlcipher.rs @@ -3,10 +3,10 @@ use std::str::FromStr; use sqlx::sqlite::SqliteQueryResult; use sqlx::{query, Connection, SqliteConnection}; use sqlx::{sqlite::SqliteConnectOptions, ConnectOptions}; -use tempdir::TempDir; +use tempfile::TempDir; async fn new_db_url() -> anyhow::Result<(String, TempDir)> { - let dir = TempDir::new("sqlcipher_test")?; + let dir = TempDir::new()?; let filepath = dir.path().join("database.sqlite3"); Ok((format!("sqlite://{}", filepath.display()), dir)) From af31d5059d993738477d4aff297373b689b63f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Urbanek?= Date: Fri, 26 Jan 2024 08:29:51 +0100 Subject: [PATCH 17/59] Add default implementation for PgInterval (#3013) --- sqlx-postgres/src/types/interval.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sqlx-postgres/src/types/interval.rs b/sqlx-postgres/src/types/interval.rs index 1c0567e965..0712a26a76 100644 --- a/sqlx-postgres/src/types/interval.rs +++ b/sqlx-postgres/src/types/interval.rs @@ -10,7 +10,7 @@ use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValue // `PgInterval` is available for direct access to the INTERVAL type -#[derive(Debug, Eq, PartialEq, Clone, Hash)] +#[derive(Debug, Eq, PartialEq, Clone, Hash, Default)] pub struct PgInterval { pub months: i32, pub days: i32, @@ -301,6 +301,15 @@ fn test_encode_interval() { )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); buf.clear(); + + assert_eq!( + PgInterval::default(), + PgInterval { + months: 0, + days: 0, + microseconds: 0, + } + ); } #[test] From 0e998ceccc60f1f40a99320578d97c838187bacc Mon Sep 17 00:00:00 2001 From: Dorje Gilfillan Date: Fri, 26 Jan 2024 09:30:02 +0200 Subject: [PATCH 18/59] Added support to IpAddr with MySQL/MariaDB. (#3011) * Added support for IpAddr with MySQL/MariaDB * Added IpAddr to mysql/types documentation --- sqlx-mysql/src/types/inet.rs | 92 ++++++++++++++++++++++++++++++++++++ sqlx-mysql/src/types/mod.rs | 2 + 2 files changed, 94 insertions(+) create mode 100644 sqlx-mysql/src/types/inet.rs diff --git a/sqlx-mysql/src/types/inet.rs b/sqlx-mysql/src/types/inet.rs new file mode 100644 index 0000000000..385ca4a61a --- /dev/null +++ b/sqlx-mysql/src/types/inet.rs @@ -0,0 +1,92 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::io::MySqlBufMutExt; +use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; + +impl Type for Ipv4Addr { + fn type_info() -> MySqlTypeInfo { + <&str as Type>::type_info() + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + <&str as Type>::compatible(ty) + } +} + +impl Encode<'_, MySql> for Ipv4Addr { + fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { + buf.put_str_lenenc(&self.to_string()); + + IsNull::No + } +} + +impl Decode<'_, MySql> for Ipv4Addr { + fn decode(value: MySqlValueRef<'_>) -> Result { + // delegate to the &str type to decode from MySQL + let text = <&str as Decode>::decode(value)?; + + // parse a Ipv4Addr from the text + text.parse().map_err(Into::into) + } +} + +impl Type for Ipv6Addr { + fn type_info() -> MySqlTypeInfo { + <&str as Type>::type_info() + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + <&str as Type>::compatible(ty) + } +} + +impl Encode<'_, MySql> for Ipv6Addr { + fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { + buf.put_str_lenenc(&self.to_string()); + + IsNull::No + } +} + +impl Decode<'_, MySql> for Ipv6Addr { + fn decode(value: MySqlValueRef<'_>) -> Result { + // delegate to the &str type to decode from MySQL + let text = <&str as Decode>::decode(value)?; + + // parse a Ipv6Addr from the text + text.parse().map_err(Into::into) + } +} + +impl Type for IpAddr { + fn type_info() -> MySqlTypeInfo { + <&str as Type>::type_info() + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + <&str as Type>::compatible(ty) + } +} + +impl Encode<'_, MySql> for IpAddr { + fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { + buf.put_str_lenenc(&self.to_string()); + + IsNull::No + } +} + +impl Decode<'_, MySql> for IpAddr { + fn decode(value: MySqlValueRef<'_>) -> Result { + // delegate to the &str type to decode from MySQL + let text = <&str as Decode>::decode(value)?; + + // parse a IpAddr from the text + text.parse().map_err(Into::into) + } +} diff --git a/sqlx-mysql/src/types/mod.rs b/sqlx-mysql/src/types/mod.rs index 6cbbde7166..be35111401 100644 --- a/sqlx-mysql/src/types/mod.rs +++ b/sqlx-mysql/src/types/mod.rs @@ -17,6 +17,7 @@ //! | `f64` | DOUBLE | //! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | //! | `&[u8]`, `Vec` | VARBINARY, BINARY, BLOB | +//! | `IpAddr`, `Ipv4Addr`, `Ipv6Addr` | VARCHAR, TEXT | //! //! ##### Note: `BOOLEAN`/`BOOL` Type //! MySQL and MariaDB treat `BOOLEAN` as an alias of the `TINYINT` type: @@ -102,6 +103,7 @@ pub(crate) use sqlx_core::types::*; mod bool; mod bytes; mod float; +mod inet; mod int; mod str; mod text; From 3946cc82dbe8346a6fdf8dcbdebc7b8d7b8e34c9 Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Fri, 26 Jan 2024 02:30:14 -0500 Subject: [PATCH 19/59] chore(deps): Upgrade criterion to 0.5.1 (#3010) Signed-off-by: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> --- Cargo.lock | 58 ++++++++++++++----------------------------- Cargo.toml | 2 +- sqlx-bench/Cargo.toml | 2 +- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1c301df0f..d76ab61e7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,23 +744,11 @@ dependencies = [ "atty", "bitflags 1.3.2", "strsim 0.8.0", - "textwrap 0.11.0", + "textwrap", "unicode-width", "vec_map", ] -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "textwrap 0.16.0", -] - [[package]] name = "clap" version = "4.4.8" @@ -779,7 +767,7 @@ checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", - "clap_lex 0.6.0", + "clap_lex", "strsim 0.10.0", ] @@ -804,15 +792,6 @@ dependencies = [ "syn 2.0.39", ] -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.6.0" @@ -906,20 +885,20 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", - "clap 3.2.25", + "clap 4.4.8", "criterion-plot", "futures", + "is-terminal", "itertools 0.10.5", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -1794,6 +1773,17 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi 0.3.3", + "rustix 0.38.30", + "windows-sys 0.52.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2242,12 +2232,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "parking" version = "2.2.0" @@ -3700,12 +3684,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.50" diff --git a/Cargo.toml b/Cargo.toml index e49c2de930..156eece0cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,7 +175,7 @@ rand = "0.8.4" rand_xoshiro = "0.6.0" hex = "0.4.3" tempfile = "3.9.0" -criterion = {version = "0.4", features = ["async_tokio"]} +criterion = { version = "0.5.1", features = ["async_tokio"] } # Needed to test SQLCipher libsqlite3-sys = { version = "0.27", features = ["bundled-sqlcipher"] } diff --git a/sqlx-bench/Cargo.toml b/sqlx-bench/Cargo.toml index ad78395ddb..a2028e40f4 100644 --- a/sqlx-bench/Cargo.toml +++ b/sqlx-bench/Cargo.toml @@ -26,7 +26,7 @@ postgres = ["sqlx/postgres"] sqlite = ["sqlx/sqlite"] [dependencies] -criterion = "0.3.3" +criterion = "0.5.1" dotenvy = "0.15.0" once_cell = "1.4" sqlx = { workspace = true, default-features = false, features = ["macros"] } From 235604fbced7a1385cfff3dc04b5b4fae9d8a5f4 Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Fri, 26 Jan 2024 02:30:25 -0500 Subject: [PATCH 20/59] chore(dev-deps): Upgrade env_logger from 0.9 to 0.11 (#3009) Signed-off-by: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> --- Cargo.lock | 24 +++++++++++++++++------- Cargo.toml | 2 +- sqlx-test/Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d76ab61e7c..56cde771a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -1142,17 +1142,27 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.9.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" dependencies = [ - "atty", + "anstream", + "anstyle", + "env_filter", "humantime", "log", - "regex", - "termcolor", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 156eece0cb..9a4dfdf5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,7 +161,7 @@ sqlx-sqlite = { workspace = true, optional = true } anyhow = "1.0.52" time_ = { version = "0.3.2", package = "time" } futures = "0.3.19" -env_logger = "0.9.0" +env_logger = "0.11" async-std = { version = "1.12.0", features = ["attributes"] } tokio = { version = "1.15.0", features = ["full"] } dotenvy = "0.15.0" diff --git a/sqlx-test/Cargo.toml b/sqlx-test/Cargo.toml index 23f417b7f9..ddc94d216e 100644 --- a/sqlx-test/Cargo.toml +++ b/sqlx-test/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] sqlx = { default-features = false, path = ".." } -env_logger = "0.9.0" +env_logger = "0.11" dotenvy = "0.15.0" anyhow = "1.0.26" async-std = { version = "1.8.0", features = [ "attributes" ] } From 0299959ca6623415f68fb0e231251b098c23f0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Urbanek?= Date: Tue, 30 Jan 2024 22:23:45 +0100 Subject: [PATCH 21/59] Add default implementation for PgMoney (#3018) --- sqlx-postgres/src/types/money.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sqlx-postgres/src/types/money.rs b/sqlx-postgres/src/types/money.rs index 963186bec2..1ae3e0a97d 100644 --- a/sqlx-postgres/src/types/money.rs +++ b/sqlx-postgres/src/types/money.rs @@ -48,7 +48,7 @@ use std::{ /// https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/cash.c#L114-L123 /// /// [`MONEY`]: https://www.postgresql.org/docs/current/datatype-money.html -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct PgMoney( /// The raw integer value sent over the wire; for locales with `frac_digits=2` (i.e. most /// of them), this will be the value in whole cents. @@ -278,6 +278,13 @@ mod tests { assert_eq!(PgMoney(-1), money); } + #[test] + fn default_value() { + let money = PgMoney::default(); + + assert_eq!(money, PgMoney(0)); + } + #[test] #[should_panic] fn add_overflow_panics() { From 978bd50a577fc2702effdb14624248201453d1d5 Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:23:58 -0500 Subject: [PATCH 22/59] chore: Ignore .sqlx folder created by running ci steps locally (#3008) Signed-off-by: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9e71baaa01..df28142027 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ target/ # Integration testing extension library for SQLite. ipaddr.dylib ipaddr.so + +# Temporary files from running the tests locally like they would be run from CI +.sqlx From 84d576004c93a32133688426eacb50434bb5c5f0 Mon Sep 17 00:00:00 2001 From: Ian Gilfillan Date: Tue, 30 Jan 2024 23:24:19 +0200 Subject: [PATCH 23/59] Update docs to reflect support for MariaDB data types (#3026) --- sqlx-mysql/src/types/mod.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sqlx-mysql/src/types/mod.rs b/sqlx-mysql/src/types/mod.rs index be35111401..9b7ef29fc7 100644 --- a/sqlx-mysql/src/types/mod.rs +++ b/sqlx-mysql/src/types/mod.rs @@ -1,8 +1,8 @@ -//! Conversions between Rust and **MySQL** types. +//! Conversions between Rust and **MySQL/MariaDB** types. //! //! # Types //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bool` | TINYINT(1), BOOLEAN, BOOL (see below) | //! | `i8` | TINYINT | @@ -17,7 +17,9 @@ //! | `f64` | DOUBLE | //! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | //! | `&[u8]`, `Vec` | VARBINARY, BINARY, BLOB | -//! | `IpAddr`, `Ipv4Addr`, `Ipv6Addr` | VARCHAR, TEXT | +//! | `IpAddr` | VARCHAR, TEXT | +//! | `Ipv4Addr` | INET4 (MariaDB-only), VARCHAR, TEXT | +//! | `Ipv6Addr` | INET6 (MariaDB-only), VARCHAR, TEXT | //! //! ##### Note: `BOOLEAN`/`BOOL` Type //! MySQL and MariaDB treat `BOOLEAN` as an alias of the `TINYINT` type: @@ -40,7 +42,7 @@ //! //! Requires the `chrono` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `chrono::DateTime` | TIMESTAMP | //! | `chrono::DateTime` | TIMESTAMP | @@ -52,7 +54,7 @@ //! //! Requires the `time` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `time::PrimitiveDateTime` | DATETIME | //! | `time::OffsetDateTime` | TIMESTAMP | @@ -62,14 +64,14 @@ //! ### [`bigdecimal`](https://crates.io/crates/bigdecimal) //! Requires the `bigdecimal` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | DECIMAL | //! //! ### [`decimal`](https://crates.io/crates/rust_decimal) //! Requires the `decimal` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `rust_decimal::Decimal` | DECIMAL | //! @@ -77,17 +79,17 @@ //! //! Requires the `uuid` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `uuid::Uuid` | BINARY(16), VARCHAR, CHAR, TEXT | -//! | `uuid::fmt::Hyphenated` | CHAR(36) | +//! | `uuid::fmt::Hyphenated` | CHAR(36), UUID (MariaDB-only) | //! | `uuid::fmt::Simple` | CHAR(32) | //! //! ### [`json`](https://crates.io/crates/serde_json) //! //! Requires the `json` Cargo feature flag. //! -//! | Rust type | MySQL type(s) | +//! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | [`Json`] | JSON | //! | `serde_json::JsonValue` | JSON | @@ -96,7 +98,7 @@ //! # Nullable //! //! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents -//! a potentially `NULL` value from MySQL. +//! a potentially `NULL` value from MySQL/MariaDB. pub(crate) use sqlx_core::types::*; From d7cbf940c3cecef0cde4ad89c0652bb247651529 Mon Sep 17 00:00:00 2001 From: iamjpotts <8704475+iamjpotts@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:51:55 -0600 Subject: [PATCH 24/59] feat(logging): Add numeric elapsed time field elapsed_secs as f64 (#3004) Signed-off-by: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> --- sqlx-core/src/logger.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sqlx-core/src/logger.rs b/sqlx-core/src/logger.rs index 8c0298b4ac..7780fd4cff 100644 --- a/sqlx-core/src/logger.rs +++ b/sqlx-core/src/logger.rs @@ -124,7 +124,10 @@ impl<'q> QueryLogger<'q> { db.statement = sql, rows_affected = self.rows_affected, rows_returned = self.rows_returned, + // Human-friendly - includes units (usually ms). Also kept for backward compatibility ?elapsed, + // Search friendly - numeric + elapsed_secs = elapsed.as_secs_f64(), // When logging to JSON, one can trigger alerts from the presence of this field. slow_threshold=?self.settings.slow_statements_duration, // Make sure to use "slow" in the message as that's likely @@ -139,7 +142,10 @@ impl<'q> QueryLogger<'q> { db.statement = sql, rows_affected = self.rows_affected, rows_returned = self.rows_returned, + // Human-friendly - includes units (usually ms). Also kept for backward compatibility ?elapsed, + // Search friendly - numeric + elapsed_secs = elapsed.as_secs_f64(), ); } } From 4c057a0628bc8f4936d069d4506768f8fdfc6005 Mon Sep 17 00:00:00 2001 From: Mirek Klimos Date: Thu, 15 Feb 2024 22:26:52 -0800 Subject: [PATCH 25/59] Set TCP_NODELAY option on TCP sockets (#3055) --- sqlx-core/src/net/socket/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/net/socket/mod.rs b/sqlx-core/src/net/socket/mod.rs index 076aa9a3e3..f7bc985ecb 100644 --- a/sqlx-core/src/net/socket/mod.rs +++ b/sqlx-core/src/net/socket/mod.rs @@ -195,6 +195,7 @@ pub async fn connect_tcp( use tokio::net::TcpStream; let stream = TcpStream::connect((host, port)).await?; + stream.set_nodelay(true)?; return Ok(with_socket.with_socket(stream)); } @@ -209,7 +210,13 @@ pub async fn connect_tcp( // Loop through all the Socket Addresses that the hostname resolves to for socket_addr in (host, port).to_socket_addrs().await? { - match Async::::connect(socket_addr).await { + let stream = Async::::connect(socket_addr) + .await + .and_then(|s| { + s.get_ref().set_nodelay(true)?; + Ok(s) + }); + match stream { Ok(stream) => return Ok(with_socket.with_socket(stream)), Err(e) => last_err = Some(e), } From a1e4984c6c86b31d75dc25d08fa2a6d2c33c8d2f Mon Sep 17 00:00:00 2001 From: zoomiti <34012653+zoomiti@users.noreply.github.com> Date: Thu, 15 Feb 2024 22:27:22 -0800 Subject: [PATCH 26/59] fix: spans in sqlite tracing (#2876) (#3056) --- sqlx-sqlite/src/connection/worker.rs | 32 +++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/sqlx-sqlite/src/connection/worker.rs b/sqlx-sqlite/src/connection/worker.rs index 38587c688a..10cb45798d 100644 --- a/sqlx-sqlite/src/connection/worker.rs +++ b/sqlx-sqlite/src/connection/worker.rs @@ -13,6 +13,7 @@ use sqlx_core::transaction::{ begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql, }; use sqlx_core::Either; +use tracing::span::Span; use crate::connection::describe::describe; use crate::connection::establish::EstablishParams; @@ -27,7 +28,7 @@ use crate::{Sqlite, SqliteArguments, SqliteQueryResult, SqliteRow, SqliteStateme // unlikely. pub(crate) struct ConnectionWorker { - command_tx: flume::Sender, + command_tx: flume::Sender<(Command, tracing::Span)>, /// The `sqlite3` pointer. NOTE: access is unsynchronized! pub(crate) _handle_raw: ConnectionHandleRaw, /// Mutex for locking access to the database. @@ -117,7 +118,8 @@ impl ConnectionWorker { // would rollback an already completed transaction. let mut ignore_next_start_rollback = false; - for cmd in command_rx { + for (cmd, span) in command_rx { + let _guard = span.enter(); match cmd { Command::Prepare { query, tx } => { tx.send(prepare(&mut conn, &query).map(|prepared| { @@ -289,12 +291,15 @@ impl ConnectionWorker { let (tx, rx) = flume::bounded(chan_size); self.command_tx - .send_async(Command::Execute { - query: query.into(), - arguments: args.map(SqliteArguments::into_static), - persistent, - tx, - }) + .send_async(( + Command::Execute { + query: query.into(), + arguments: args.map(SqliteArguments::into_static), + persistent, + tx, + }, + Span::current(), + )) .await .map_err(|_| Error::WorkerCrashed)?; @@ -318,7 +323,7 @@ impl ConnectionWorker { pub(crate) fn start_rollback(&mut self) -> Result<(), Error> { self.command_tx - .send(Command::Rollback { tx: None }) + .send((Command::Rollback { tx: None }, Span::current())) .map_err(|_| Error::WorkerCrashed) } @@ -333,7 +338,7 @@ impl ConnectionWorker { let (tx, rx) = oneshot::channel(); self.command_tx - .send_async(command(tx)) + .send_async((command(tx), Span::current())) .await .map_err(|_| Error::WorkerCrashed)?; @@ -347,7 +352,7 @@ impl ConnectionWorker { let (tx, rx) = rendezvous_oneshot::channel(); self.command_tx - .send_async(command(tx)) + .send_async((command(tx), Span::current())) .await .map_err(|_| Error::WorkerCrashed)?; @@ -362,7 +367,8 @@ impl ConnectionWorker { let (guard, res) = futures_util::future::join( // we need to join the wait queue for the lock before we send the message self.shared.conn.lock(), - self.command_tx.send_async(Command::UnlockDb), + self.command_tx + .send_async((Command::UnlockDb, Span::current())), ) .await; @@ -379,7 +385,7 @@ impl ConnectionWorker { let send_res = self .command_tx - .send(Command::Shutdown { tx }) + .send((Command::Shutdown { tx }, Span::current())) .map_err(|_| Error::WorkerCrashed); async move { From dd900e50b653e2802610b06dee261de9cd16c8d9 Mon Sep 17 00:00:00 2001 From: Mirek Klimos Date: Fri, 16 Feb 2024 16:32:31 -0800 Subject: [PATCH 27/59] Optimize SASL auth in sqlx-postgres (#3050) * Optimize SASL auth in sqlx-postgres * fix formatting --- sqlx-postgres/Cargo.toml | 2 +- sqlx-postgres/src/connection/sasl.rs | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index 34ad38e13a..987d8e8cbc 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -29,7 +29,7 @@ futures-util = { version = "0.3.19", default-features = false, features = ["allo # Cryptographic Primitives crc = "3.0.0" hkdf = "0.12.0" -hmac = { version = "0.12.0", default-features = false } +hmac = { version = "0.12.0", default-features = false, features = ["reset"]} md-5 = { version = "0.10.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } sha1 = { version = "0.10.1", default-features = false } diff --git a/sqlx-postgres/src/connection/sasl.rs b/sqlx-postgres/src/connection/sasl.rs index e82df42582..16ccd20484 100644 --- a/sqlx-postgres/src/connection/sasl.rs +++ b/sqlx-postgres/src/connection/sasl.rs @@ -195,15 +195,33 @@ fn hi<'a>(s: &'a str, salt: &'a [u8], iter_count: u32) -> Result<[u8; 32], Error mac.update(&salt); mac.update(&1u32.to_be_bytes()); - let mut u = mac.finalize().into_bytes(); + let mut u = mac.finalize_reset().into_bytes(); let mut hi = u; for _ in 1..iter_count { - let mut mac = Hmac::::new_from_slice(s.as_bytes()).map_err(Error::protocol)?; mac.update(u.as_slice()); - u = mac.finalize().into_bytes(); + u = mac.finalize_reset().into_bytes(); hi = hi.iter().zip(u.iter()).map(|(&a, &b)| a ^ b).collect(); } Ok(hi.into()) } + +#[cfg(all(test, not(debug_assertions)))] +#[bench] +fn bench_sasl_hi(b: &mut test::Bencher) { + use test::black_box; + + let mut rng = rand::thread_rng(); + let nonce: Vec = std::iter::repeat(()) + .map(|()| rng.sample(rand::distributions::Alphanumeric)) + .take(64) + .collect(); + b.iter(|| { + let _ = hi( + test::black_box("secret_password"), + test::black_box(&nonce), + test::black_box(4096), + ); + }); +} From ca518b7185138c0d4c2fa53be53c974d8df9c13b Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Sun, 18 Feb 2024 15:38:23 -0800 Subject: [PATCH 28/59] feat: add `raw_sql` API (#3007) This is meant to be much easier to discover than the current approach of directly invoking `Executor` methods. In addition, I'm improving documentation for the `query*()` functions across the board. --- sqlx-core/src/lib.rs | 2 + sqlx-core/src/query.rs | 221 ++++++++++++++++++++++++++-- sqlx-core/src/query_as.rs | 193 ++++++++++++++++++++++-- sqlx-core/src/query_scalar.rs | 181 +++++++++++++++++++++-- sqlx-core/src/raw_sql.rs | 267 ++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + tests/mysql/mysql.rs | 6 +- tests/postgres/postgres.rs | 3 +- tests/sqlite/sqlite.rs | 2 +- 9 files changed, 839 insertions(+), 37 deletions(-) create mode 100644 sqlx-core/src/raw_sql.rs diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index 468242e9e6..34c871729e 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -74,6 +74,8 @@ pub mod net; pub mod query_as; pub mod query_builder; pub mod query_scalar; + +pub mod raw_sql; pub mod row; pub mod rt; pub mod sync; diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 2d7a1c9b15..4403fae006 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -12,7 +12,7 @@ use crate::executor::{Execute, Executor}; use crate::statement::Statement; use crate::types::Type; -/// Raw SQL query with bind parameters. Returned by [`query`][crate::query::query]. +/// A single SQL query as a prepared statement. Returned by [`query()`]. #[must_use = "query must be executed to affect database"] pub struct Query<'q, DB: Database, A> { pub(crate) statement: Either<&'q str, &'q >::Statement>, @@ -21,7 +21,9 @@ pub struct Query<'q, DB: Database, A> { pub(crate) persistent: bool, } -/// SQL query that will map its results to owned Rust types. +/// A single SQL query that will map its results to an owned Rust type. +/// +/// Executes as a prepared statement. /// /// Returned by [`Query::try_map`], `query!()`, etc. Has most of the same methods as [`Query`] but /// the return types are changed to reflect the mapping. However, there is no equivalent of @@ -96,6 +98,8 @@ where /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// + /// If `false`, the prepared statement will be closed after execution. + /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.persistent = value; @@ -155,6 +159,7 @@ where /// Execute multiple queries and return the rows affected from each query, in a stream. #[inline] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] pub async fn execute_many<'e, 'c: 'e, E>( self, executor: E, @@ -178,9 +183,13 @@ where executor.fetch(self) } - /// Execute multiple queries and return the generated results as a stream - /// from each query, in a stream. + /// Execute multiple queries and return the generated results as a stream. + /// + /// For each query in the stream, any generated rows are returned first, + /// then the `QueryResult` with the number of rows affected. #[inline] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] + // TODO: we'll probably still want a way to get the `DB::QueryResult` at the end of a `fetch()` stream. pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, @@ -193,7 +202,13 @@ where executor.fetch_many(self) } - /// Execute the query and return all the generated results, collected into a [`Vec`]. + /// Execute the query and return all the resulting rows collected into a [`Vec`]. + /// + /// ### Note: beware result set size. + /// This will attempt to collect the full result set of the query into memory. + /// + /// To avoid exhausting available memory, ensure the result set has a known upper bound, + /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where @@ -204,7 +219,18 @@ where executor.fetch_all(self).await } - /// Execute the query and returns exactly one row. + /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where @@ -215,7 +241,18 @@ where executor.fetch_one(self).await } - /// Execute the query and returns at most one row. + /// Execute the query, returning the first row or `None` otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where @@ -307,6 +344,9 @@ where F: 'e, O: 'e, { + // FIXME: this should have used `executor.fetch()` but that's a breaking change + // because this technically allows multiple statements in one query string. + #[allow(deprecated)] self.fetch_many(executor) .try_filter_map(|step| async move { Ok(match step { @@ -319,6 +359,7 @@ where /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] pub fn fetch_many<'e, 'c: 'e, E>( mut self, executor: E, @@ -346,7 +387,13 @@ where }) } - /// Execute the query and return all the generated results, collected into a [`Vec`]. + /// Execute the query and return all the resulting rows collected into a [`Vec`]. + /// + /// ### Note: beware result set size. + /// This will attempt to collect the full result set of the query into memory. + /// + /// To avoid exhausting available memory, ensure the result set has a known upper bound, + /// e.g. using `LIMIT`. pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, @@ -358,7 +405,18 @@ where self.fetch(executor).try_collect().await } - /// Execute the query and returns exactly one row. + /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, @@ -375,7 +433,18 @@ where .await } - /// Execute the query and returns at most one row. + /// Execute the query, returning the first row or `None` otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_optional<'e, 'c: 'e, E>(mut self, executor: E) -> Result, Error> where 'q: 'e, @@ -394,7 +463,7 @@ where } } -// Make a SQL query from a statement. +/// Execute a single SQL query as a prepared statement (explicitly created). pub fn query_statement<'q, DB>( statement: &'q >::Statement, ) -> Query<'q, DB, >::Arguments> @@ -409,7 +478,7 @@ where } } -// Make a SQL query from a statement, with the given arguments. +/// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments. pub fn query_statement_with<'q, DB, A>( statement: &'q >::Statement, arguments: A, @@ -426,7 +495,129 @@ where } } -/// Make a SQL query. +/// Execute a single SQL query as a prepared statement (transparently cached). +/// +/// The query string may only contain a single DML statement: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants. +/// The SQLite driver does not currently follow this restriction, but that behavior is deprecated. +/// +/// The connection will transparently prepare and cache the statement, which means it only needs to be parsed once +/// in the connection's lifetime, and any generated query plans can be retained. +/// Thus, the overhead of executing the statement is amortized. +/// +/// Some third-party databases that speak a supported protocol, e.g. CockroachDB or PGBouncer that speak Postgres, +/// may have issues with the transparent caching of prepared statements. If you are having trouble, +/// try setting [`.persistent(false)`][Query::persistent]. +/// +/// See the [`Query`] type for the methods you may call. +/// +/// ### Dynamic Input: Use Query Parameters (Prevents SQL Injection) +/// At some point, you'll likely want to include some form of dynamic input in your query, possibly from the user. +/// +/// Your first instinct might be to do something like this: +/// ```rust,no_run +/// # async fn example() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// // Imagine this is input from the user, e.g. a search form on a website. +/// let user_input = "possibly untrustworthy input!"; +/// +/// // DO NOT DO THIS unless you're ABSOLUTELY CERTAIN it's what you need! +/// let query = format!("SELECT * FROM articles WHERE content LIKE '%{user_input}%'"); +/// // where `conn` is `PgConnection` or `MySqlConnection` +/// // or some other type that implements `Executor`. +/// let results = sqlx::query(&query).fetch_all(&mut conn).await?; +/// # } +/// ``` +/// +/// The example above showcases a **SQL injection vulnerability**, because it's trivial for a malicious user to craft +/// an input that can "break out" of the string literal. +/// +/// For example, if they send the input `foo'; DELETE FROM articles; --` +/// then your application would send the following to the database server (line breaks added for clarity): +/// +/// ```sql +/// SELECT * FROM articles WHERE content LIKE '%foo'; +/// DELETE FROM articles; +/// --%' +/// ``` +/// +/// In this case, because this interface *always* uses prepared statements, you would likely be fine because prepared +/// statements _generally_ (see above) are only allowed to contain a single query. This would simply return an error. +/// +/// However, it would also break on legitimate user input. +/// What if someone wanted to search for the string `Alice's Apples`? It would also return an error because +/// the database would receive a query with a broken string literal (line breaks added for clarity): +/// +/// ```sql +/// SELECT * FROM articles WHERE content LIKE '%Alice' +/// s Apples%' +/// ``` +/// +/// Of course, it's possible to make this syntactically valid by escaping the apostrophe, but there's a better way. +/// +/// ##### You should always prefer query parameters for dynamic input. +/// +/// When using query parameters, you add placeholders to your query where a value +/// should be substituted at execution time, then call [`.bind()`][Query::bind] with that value. +/// +/// The syntax for placeholders is unfortunately not standardized and depends on the database: +/// +/// * Postgres and SQLite: use `$1`, `$2`, `$3`, etc. +/// * The number is the Nth bound value, starting from one. +/// * The same placeholder can be used arbitrarily many times to refer to the same bound value. +/// * SQLite technically supports MySQL's syntax as well as others, but we recommend using this syntax +/// as SQLx's SQLite driver is written with it in mind. +/// * MySQL and MariaDB: use `?`. +/// * Placeholders are purely positional, similar to `println!("{}, {}", foo, bar)`. +/// * The order of bindings must match the order of placeholders in the query. +/// * To use a value in multiple places, you must bind it multiple times. +/// +/// In both cases, the placeholder syntax acts as a variable expression representing the bound value: +/// +/// ```rust,no_run +/// # async fn example2() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// let user_input = "Alice's Apples"; +/// +/// // Postgres and SQLite +/// let results = sqlx::query( +/// // Notice how we only have to bind the argument once and we can use it multiple times: +/// "SELECT * FROM articles +/// WHERE title LIKE '%' || $1 || '%' +/// OR content LIKE '%' || $1 || '%'" +/// ) +/// .bind(user_input) +/// .fetch_all(&mut conn) +/// .await?; +/// +/// // MySQL and MariaDB +/// let results = sqlx::query( +/// "SELECT * FROM articles +/// WHERE title LIKE CONCAT('%', ?, '%') +/// OR content LIKE CONCAT('%', ?, '%')" +/// ) +/// // If we want to reference the same value multiple times, we have to bind it multiple times: +/// .bind(user_input) +/// .bind(user_input) +/// .fetch_all(&mut conn) +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// ##### The value bound to a query parameter is entirely separate from the query and does not affect its syntax. +/// Thus, SQL injection is impossible (barring shenanigans like calling a SQL function that lets you execute a string +/// as a statement) and *all* strings are valid. +/// +/// This also means you cannot use query parameters to add conditional SQL fragments. +/// +/// **SQLx does not substitute placeholders on the client side**. It is done by the database server itself. +/// +/// ##### SQLx supports many different types for parameter binding, not just strings. +/// Any type that implements [`Encode`][Encode] and [`Type`] can be bound as a parameter. +/// +/// See [the `types` module][crate::types] (links to `sqlx_core::types` but you should use `sqlx::types`) for details. +/// +/// As an additional benefit, query parameters are usually sent in a compact binary encoding instead of a human-readable +/// text encoding, which saves bandwidth. pub fn query(sql: &str) -> Query<'_, DB, >::Arguments> where DB: Database, @@ -439,7 +630,9 @@ where } } -/// Make a SQL query, with the given arguments. +/// Execute a SQL query as a prepared statement (transparently cached), with the given arguments. +/// +/// See [`query()`][query] for details, such as supported syntax. pub fn query_with<'q, DB, A>(sql: &'q str, arguments: A) -> Query<'q, DB, A> where DB: Database, diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 43a611cf65..3877c4e401 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -13,8 +13,8 @@ use crate::from_row::FromRow; use crate::query::{query, query_statement, query_statement_with, query_with, Query}; use crate::types::Type; -/// Raw SQL query with bind parameters, mapped to a concrete type using [`FromRow`]. -/// Returned from [`query_as`][crate::query_as::query_as]. +/// A single SQL query as a prepared statement, mapping results using [`FromRow`]. +/// Returned by [`query_as()`]. #[must_use = "query must be executed to affect database"] pub struct QueryAs<'q, DB: Database, O, A> { pub(crate) inner: Query<'q, DB, A>, @@ -68,6 +68,8 @@ where /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// + /// If `false`, the prepared statement will be closed after execution. + /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.inner = self.inner.persistent(value); @@ -92,6 +94,9 @@ where O: 'e, A: 'e, { + // FIXME: this should have used `executor.fetch()` but that's a breaking change + // because this technically allows multiple statements in one query string. + #[allow(deprecated)] self.fetch_many(executor) .try_filter_map(|step| async move { Ok(step.right()) }) .boxed() @@ -99,6 +104,7 @@ where /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, @@ -120,7 +126,13 @@ where .boxed() } - /// Execute the query and return all the generated results, collected into a [`Vec`]. + /// Execute the query and return all the resulting rows collected into a [`Vec`]. + /// + /// ### Note: beware result set size. + /// This will attempt to collect the full result set of the query into memory. + /// + /// To avoid exhausting available memory, ensure the result set has a known upper bound, + /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where @@ -133,7 +145,18 @@ where self.fetch(executor).try_collect().await } - /// Execute the query and returns exactly one row. + /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, @@ -147,7 +170,18 @@ where .and_then(|row| row.ok_or(Error::RowNotFound)) } - /// Execute the query and returns at most one row. + /// Execute the query, returning the first row or `None` otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, @@ -165,8 +199,145 @@ where } } -/// Make a SQL query that is mapped to a concrete type -/// using [`FromRow`]. +/// Execute a single SQL query as a prepared statement (transparently cached). +/// Maps rows to Rust types using [`FromRow`]. +/// +/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. +/// +/// ### Example: Map Rows using Tuples +/// [`FromRow`] is implemented for tuples of up to 16 elements1. +/// Using a tuple of N elements will extract the first N columns from each row using [`Decode`][crate::decode::Decode]. +/// Any extra columns are ignored. +/// +/// See [`sqlx::types`][crate::types] for the types that can be used. +/// +/// The `FromRow` implementation will check [`Type::compatible()`] for each column to ensure a compatible type mapping +/// is used. If an incompatible mapping is detected, an error is returned. +/// To statically assert compatible types at compile time, see the `query!()` family of macros. +/// +/// **NOTE**: `SELECT *` is not recommended with this approach because the ordering of returned columns may be different +/// than expected, especially when using joins. +/// +/// ```rust,no_run +/// # async fn example1() -> sqlx::Result<()> { +/// use sqlx::Connection; +/// use sqlx::PgConnection; +/// +/// // This example can be applied to any database as it only uses standard types and syntax. +/// let mut conn: PgConnection = PgConnection::connect("").await?; +/// +/// sqlx::raw_sql( +/// "CREATE TABLE users(id INTEGER PRIMARY KEY, username TEXT UNIQUE, created_at TIMESTAMP DEFAULT (now())" +/// ) +/// .execute(&mut conn) +/// .await?; +/// +/// sqlx::query("INSERT INTO users(id, username) VALUES (1, 'alice'), (2, 'bob');") +/// .execute(&mut conn) +/// .await?; +/// +/// // Get the first row of the result (note the `LIMIT 1` for efficiency) +/// // This assumes the `time` feature of SQLx is enabled. +/// let oldest_user: (i64, String, time::OffsetDateTime) = sqlx::query_as( +/// "SELECT id, username, created_at FROM users ORDER BY created_at LIMIT 1" +/// ) +/// .fetch_one(&mut conn) +/// .await?; +/// +/// assert_eq!(oldest_user.0, 1); +/// assert_eq!(oldest_user.1, "alice"); +/// +/// // Get at most one row +/// let maybe_charlie: Option<(i64, String, time::OffsetDateTime)> = sqlx::query_as( +/// "SELECT id, username, created_at FROM users WHERE username = 'charlie'" +/// ) +/// .fetch_optional(&mut conn) +/// .await?; +/// +/// assert_eq!(maybe_charlie, None); +/// +/// // Get all rows in result (Beware of the size of the result set! Consider using `LIMIT`) +/// let users: Vec<(i64, String, time::OffsetDateTime)> = sqlx::query_as( +/// "SELECT id, username, created_at FROM users ORDER BY id" +/// ) +/// .fetch_all(&mut conn) +/// .await?; +/// +/// println!("{users:?}"); +/// # Ok(()) +/// # } +/// ``` +/// +/// 1: It's impossible in Rust to implement a trait for tuples of arbitrary size. +/// For larger result sets, either use an explicit struct (see below) or use [`query()`][crate::query::query] +/// instead and extract columns dynamically. +/// +/// ### Example: Map Rows using `#[derive(FromRow)]` +/// Using `#[derive(FromRow)]`, we can create a Rust struct to represent our row type +/// so we can look up fields by name instead of tuple index. +/// +/// When querying this way, columns will be matched up to the corresponding fields by name, so `SELECT *` is safe to use. +/// However, you will still want to be aware of duplicate column names in your query when using joins. +/// +/// The derived `FromRow` implementation will check [`Type::compatible()`] for each column to ensure a compatible type +/// mapping is used. If an incompatible mapping is detected, an error is returned. +/// To statically assert compatible types at compile time, see the `query!()` family of macros. +/// +/// An error will also be returned if an expected column is missing from the result set. +/// +/// `#[derive(FromRow)]` supports several control attributes which can be used to change how column names and types +/// are mapped. See [`FromRow`] for details. +/// +/// Using our previous table definition, we can convert our queries like so: +/// ```rust,no_run +/// # async fn example2() -> sqlx::Result<()> { +/// use sqlx::Connection; +/// use sqlx::PgConnection; +/// +/// use time::OffsetDateTime; +/// +/// #[derive(sqlx::FromRow, Debug, PartialEq, Eq)] +/// struct User { +/// id: i64, +/// username: String, +/// // Note: the derive won't compile if the `time` feature of SQLx is not enabled. +/// created_at: OffsetDateTime, +/// } +/// +/// let mut conn: PgConnection = PgConnection::connect("").await?; +/// +/// // Get the first row of the result (note the `LIMIT 1` for efficiency) +/// let oldest_user: User = sqlx::query_as( +/// "SELECT id, username, created_at FROM users ORDER BY created_at LIMIT 1" +/// ) +/// .fetch_one(&mut conn) +/// .await?; +/// +/// assert_eq!(oldest_user.id, 1); +/// assert_eq!(oldest_user.username, "alice"); +/// +/// // Get at most one row +/// let maybe_charlie: Option = sqlx::query_as( +/// "SELECT id, username, created_at FROM users WHERE username = 'charlie'" +/// ) +/// .fetch_optional(&mut conn) +/// .await?; +/// +/// assert_eq!(maybe_charlie, None); +/// +/// // Get all rows in result (Beware of the size of the result set! Consider using `LIMIT`) +/// let users: Vec = sqlx::query_as( +/// "SELECT id, username, created_at FROM users ORDER BY id" +/// ) +/// .fetch_all(&mut conn) +/// .await?; +/// +/// assert_eq!(users[1].id, 2); +/// assert_eq!(users[1].username, "bob"); +/// # Ok(()) +/// # } +/// +/// ``` #[inline] pub fn query_as<'q, DB, O>(sql: &'q str) -> QueryAs<'q, DB, O, >::Arguments> where @@ -179,8 +350,12 @@ where } } -/// Make a SQL query, with the given arguments, that is mapped to a concrete type -/// using [`FromRow`]. +/// Execute a single SQL query, with the given arguments as a prepared statement (transparently cached). +/// Maps rows to Rust types using [`FromRow`]. +/// +/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. +/// +/// For details about type mapping from [`FromRow`], see [`query_as()`]. #[inline] pub fn query_as_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryAs<'q, DB, O, A> where diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs index 0ef8cbde30..395278a8d9 100644 --- a/sqlx-core/src/query_scalar.rs +++ b/sqlx-core/src/query_scalar.rs @@ -13,8 +13,8 @@ use crate::query_as::{ }; use crate::types::Type; -/// Raw SQL query with bind parameters, mapped to a concrete type using [`FromRow`] on `(O,)`. -/// Returned from [`query_scalar`]. +/// A single SQL query as a prepared statement which extracts only the first column of each row. +/// Returned by [`query_scalar()`]. #[must_use = "query must be executed to affect database"] pub struct QueryScalar<'q, DB: Database, O, A> { pub(crate) inner: QueryAs<'q, DB, (O,), A>, @@ -40,7 +40,7 @@ where #[inline] fn persistent(&self) -> bool { - self.inner.persistent() + Execute::persistent(&self.inner) } } @@ -65,6 +65,8 @@ where /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// + /// If `false`, the prepared statement will be closed after execution. + /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.inner = self.inner.persistent(value); @@ -97,6 +99,7 @@ where /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. #[inline] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, @@ -108,13 +111,20 @@ where A: 'e, O: 'e, { + #[allow(deprecated)] self.inner .fetch_many(executor) .map_ok(|v| v.map_right(|it| it.0)) .boxed() } - /// Execute the query and return all the generated results, collected into a [`Vec`]. + /// Execute the query and return all the resulting rows collected into a [`Vec`]. + /// + /// ### Note: beware result set size. + /// This will attempt to collect the full result set of the query into memory. + /// + /// To avoid exhausting available memory, ensure the result set has a known upper bound, + /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where @@ -131,7 +141,18 @@ where .await } - /// Execute the query and returns exactly one row. + /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where @@ -144,7 +165,18 @@ where self.inner.fetch_one(executor).map_ok(|it| it.0).await } - /// Execute the query and returns at most one row. + /// Execute the query, returning the first row or `None` otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns at most one row + /// can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where @@ -158,8 +190,133 @@ where } } -/// Make a SQL query that is mapped to a single concrete type -/// using [`FromRow`]. +/// Execute a single SQL query as a prepared statement (transparently cached) and extract the first +/// column of each row. +/// +/// Extracts the first column of each row. Additional columns are ignored. +/// Any type that implements `Type + Decode` may be used. +/// +/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. +/// +/// ### Example: Simple Lookup +/// If you just want to look up a single value with little fanfare, this API is perfect for you: +/// +/// ```rust,no_run +/// # async fn example_lookup() -> Result<(), Box> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// use uuid::Uuid; +/// +/// // MySQL and MariaDB: use `?` +/// let user_id: Option = sqlx::query_scalar("SELECT user_id FROM users WHERE username = $1") +/// .bind("alice") +/// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. +/// .fetch_optional(&mut conn) +/// .await?; +/// +/// let user_id = user_id.ok_or("unknown user")?; +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// Note how we're using `.fetch_optional()` because the lookup may return no results, +/// in which case we need to be able to handle an empty result set. +/// Any rows after the first are ignored. +/// +/// ### Example: `COUNT` +/// This API is the easiest way to invoke an aggregate query like `SELECT COUNT(*)`, because you +/// can conveniently extract the result: +/// +/// ```rust,no_run +/// # async fn example_count() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// // Note that `usize` is not used here because unsigned integers are generally not supported, +/// // and `usize` doesn't even make sense as a mapping because the database server may have +/// // a completely different architecture. +/// // +/// // `i64` is generally a safe choice for `COUNT`. +/// let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE accepted_tos IS TRUE") +/// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. +/// .fetch_one(&mut conn) +/// .await?; +/// +/// // The above is functionally equivalent to the following: +/// // Note the trailing comma, required for the compiler to recognize a 1-element tuple. +/// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users WHERE accepted_tos IS TRUE") +/// .fetch_one(&mut conn) +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// +/// ### Example: `EXISTS` +/// To test if a row exists or not, use `SELECT EXISTS()`: +/// +/// ```rust,no_run +/// # async fn example_exists() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// // MySQL and MariaDB: use `?` +/// let username_taken: bool = sqlx::query_scalar( +/// "SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)" +/// ) +/// .bind("alice") +/// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. +/// .fetch_one(&mut conn) +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// +/// ### Example: Other Aggregates +/// Be aware that most other aggregate functions return `NULL` if the query yields an empty set: +/// +/// ```rust,no_run +/// # async fn example_aggregate() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// let max_upvotes: Option = sqlx::query_scalar("SELECT MAX(upvotes) FROM posts") +/// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. +/// .fetch_one(&mut conn) +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// +/// Note how we're using `Option` with `.fetch_one()`, because we're always expecting one row +/// but the column value may be `NULL`. If no rows are returned, this will error. +/// +/// This is in contrast to using `.fetch_optional()` with `Option`, which implies that +/// we're expecting _either_ a row with a `i64` (`BIGINT`), _or_ no rows at all. +/// +/// Either way, any rows after the first are ignored. +/// +/// ### Example: `Vec` of Scalars +/// If you want to collect a single column from a query into a vector, +/// try `.fetch_all()`: +/// +/// ```rust,no_run +/// # async fn example_vec() -> sqlx::Result<()> { +/// # let mut conn: sqlx::PgConnection = unimplemented!(); +/// let top_users: Vec = sqlx::query_scalar( +/// // Note the `LIMIT` to ensure that this doesn't return *all* users: +/// "SELECT username +/// FROM ( +/// SELECT SUM(upvotes) total, user_id +/// FROM posts +/// GROUP BY user_id +/// ) top_users +/// INNER JOIN users USING (user_id) +/// ORDER BY total DESC +/// LIMIT 10" +/// ) +/// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. +/// .fetch_all(&mut conn) +/// .await?; +/// +/// // `top_users` could be empty, too. +/// assert!(top_users.len() <= 10); +/// # Ok(()) +/// # } +/// ``` #[inline] pub fn query_scalar<'q, DB, O>( sql: &'q str, @@ -173,8 +330,12 @@ where } } -/// Make a SQL query, with the given arguments, that is mapped to a single concrete type -/// using [`FromRow`]. +/// Execute a SQL query as a prepared statement (transparently cached), with the given arguments, +/// and extract the first column of each row. +/// +/// See [`query_scalar()`] for details. +/// +/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. #[inline] pub fn query_scalar_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryScalar<'q, DB, O, A> where diff --git a/sqlx-core/src/raw_sql.rs b/sqlx-core/src/raw_sql.rs new file mode 100644 index 0000000000..48162f2c3b --- /dev/null +++ b/sqlx-core/src/raw_sql.rs @@ -0,0 +1,267 @@ +use crate::database::{Database, HasArguments, HasStatement}; +use crate::executor::{Execute, Executor}; +use crate::Error; +use either::Either; +use futures_core::stream::BoxStream; + +// AUTHOR'S NOTE: I was just going to call this API `sql()` and `Sql`, respectively, +// but realized that would be extremely annoying to deal with as a SQLite user +// because IDE smart completion would always recommend the `Sql` type first. +// +// It doesn't really need a super convenient name anyway as it's not meant to be used very often. + +/// One or more raw SQL statements, separated by semicolons (`;`). +/// +/// See [`raw_sql()`] for details. +pub struct RawSql<'q>(&'q str); + +/// Execute one or more statements as raw SQL, separated by semicolons (`;`). +/// +/// This interface can be used to execute both DML +/// (Data Manipulation Language: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants) +/// as well as DDL (Data Definition Language: `CREATE TABLE`, `ALTER TABLE`, etc). +/// +/// This will not create or cache any prepared statements. +/// +/// ### Note: singular DML queries, prefer `query()` +/// This API does not use prepared statements, so usage of it is missing out on their benefits. +/// +/// Prefer [`query()`][crate::query::query] instead if executing a single query. +/// +/// It's also possible to combine multiple DML queries into one for use with `query()`: +/// +/// ##### Common Table Expressions (CTEs: i.e The `WITH` Clause) +/// Common Table Expressions effectively allow you to define aliases for queries +/// that can be referenced like temporary tables: +/// +/// ```sql +/// WITH inserted_foos AS ( +/// -- Note that only Postgres allows data-modifying statements in CTEs +/// INSERT INTO foo (bar_id) VALUES ($1) +/// RETURNING foo_id, bar_id +/// ) +/// SELECT foo_id, bar_id, bar +/// FROM inserted_foos +/// INNER JOIN bar USING (bar_id) +/// ``` +/// +/// It's important to note that data modifying statements (`INSERT`, `UPDATE`, `DELETE`) may +/// behave differently than expected. In Postgres, all data-modifying subqueries in a `WITH` +/// clause execute with the same view of the data; they *cannot* see each other's modifications. +/// +/// MySQL, MariaDB and SQLite appear to *only* allow `SELECT` statements in CTEs. +/// +/// See the appropriate entry in your database's manual for details: +/// * [MySQL](https://dev.mysql.com/doc/refman/8.0/en/with.html) +/// * [MariaDB](https://mariadb.com/kb/en/with/) +/// * [Postgres](https://www.postgresql.org/docs/current/queries-with.html) +/// * [SQLite](https://www.sqlite.org/lang_with.html) +/// +/// ##### `UNION`/`INTERSECT`/`EXCEPT` +/// You can also use various set-theory operations on queries, +/// including `UNION ALL` which simply concatenates their results. +/// +/// See the appropriate entry in your database's manual for details: +/// * [MySQL](https://dev.mysql.com/doc/refman/8.0/en/set-operations.html) +/// * [MariaDB](https://mariadb.com/kb/en/joins-subqueries/) +/// * [Postgres](https://www.postgresql.org/docs/current/queries-union.html) +/// * [SQLite](https://www.sqlite.org/lang_select.html#compound_select_statements) +/// +/// ### Note: query parameters are not supported. +/// Query parameters require the use of prepared statements which this API does support. +/// +/// If you require dynamic input data in your SQL, you can use `format!()` but **be very careful +/// doing this with user input**. SQLx does **not** provide escaping or sanitization for inserting +/// dynamic input into queries this way. +/// +/// See [`query()`][crate::query::query] for details. +/// +/// ### Note: multiple statements and autocommit. +/// By default, when you use this API to execute a SQL string containing multiple statements +/// separated by semicolons (`;`), the database server will treat those statements as all executing +/// within the same transaction block, i.e. wrapped in `BEGIN` and `COMMIT`: +/// +/// ```rust,no_run +/// # async fn example() -> sqlx::Result<()> { +/// let mut conn: sqlx::PgConnection = todo!("e.g. PgConnection::connect()"); +/// +/// sqlx::raw_sql( +/// // Imagine we're moving data from one table to another: +/// // Implicit `BEGIN;` +/// "UPDATE foo SET bar = foobar.bar FROM foobar WHERE foobar.foo_id = foo.id;\ +/// DELETE FROM foobar;" +/// // Implicit `COMMIT;` +/// ) +/// .execute(&mut conn) +/// .await?; +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// If one statement triggers an error, the whole script aborts and rolls back. +/// You can include explicit `BEGIN` and `COMMIT` statements in the SQL string +/// to designate units that can be committed or rolled back piecemeal. +/// +/// This also allows for a rudimentary form of pipelining as the whole SQL string is sent in one go. +/// +/// ##### MySQL and MariaDB: DDL implicitly commits! +/// MySQL and MariaDB do not support DDL in transactions. Instead, any active transaction is +/// immediately and implicitly committed by the database server when executing a DDL statement. +/// Beware of this behavior. +/// +/// See [MySQL manual, section 13.3.3: Statements That Cause an Implicit Commit](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) for details. +/// See also: [MariaDB manual: SQL statements That Cause an Implicit Commit](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/). +pub fn raw_sql(sql: &str) -> RawSql<'_> { + RawSql(sql) +} + +impl<'q, DB: Database> Execute<'q, DB> for RawSql<'q> { + fn sql(&self) -> &'q str { + self.0 + } + + fn statement(&self) -> Option<&>::Statement> { + None + } + + fn take_arguments(&mut self) -> Option<>::Arguments> { + None + } + + fn persistent(&self) -> bool { + false + } +} + +impl<'q> RawSql<'q> { + /// Execute the SQL string and return the total number of rows affected. + #[inline] + pub async fn execute<'e, E>( + self, + executor: E, + ) -> crate::Result<::QueryResult> + where + 'q: 'e, + E: Executor<'e>, + { + executor.execute(self).await + } + + /// Execute the SQL string. Returns a stream which gives the number of rows affected for each statement in the string. + #[inline] + pub fn execute_many<'e, E>( + self, + executor: E, + ) -> BoxStream<'e, crate::Result<::QueryResult>> + where + 'q: 'e, + E: Executor<'e>, + { + executor.execute_many(self) + } + + /// Execute the SQL string and return the generated results as a stream. + /// + /// If the string contains multiple statements, their results will be concatenated together. + #[inline] + pub fn fetch<'e, E>( + self, + executor: E, + ) -> BoxStream<'e, Result<::Row, Error>> + where + 'q: 'e, + E: Executor<'e>, + { + executor.fetch(self) + } + + /// Execute the SQL string and return the generated results as a stream. + /// + /// For each query in the stream, any generated rows are returned first, + /// then the `QueryResult` with the number of rows affected. + #[inline] + pub fn fetch_many<'e, E>( + self, + executor: E, + ) -> BoxStream< + 'e, + Result< + Either<::QueryResult, ::Row>, + Error, + >, + > + where + 'q: 'e, + E: Executor<'e>, + { + executor.fetch_many(self) + } + + /// Execute the SQL string and return all the resulting rows collected into a [`Vec`]. + /// + /// ### Note: beware result set size. + /// This will attempt to collect the full result set of the query into memory. + /// + /// To avoid exhausting available memory, ensure the result set has a known upper bound, + /// e.g. using `LIMIT`. + #[inline] + pub async fn fetch_all<'e, E>( + self, + executor: E, + ) -> crate::Result::Row>> + where + 'q: 'e, + E: Executor<'e>, + { + executor.fetch_all(self).await + } + + /// Execute the SQL string, returning the first row or [`Error::RowNotFound`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns + /// at most one row can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. + #[inline] + pub async fn fetch_one<'e, E>( + self, + executor: E, + ) -> crate::Result<::Row> + where + 'q: 'e, + E: Executor<'e>, + { + executor.fetch_one(self).await + } + + /// Execute the SQL string, returning the first row or [`None`] otherwise. + /// + /// ### Note: for best performance, ensure the query returns at most one row. + /// Depending on the driver implementation, if your query can return more than one row, + /// it may lead to wasted CPU time and bandwidth on the database server. + /// + /// Even when the driver implementation takes this into account, ensuring the query returns + /// at most one row can result in a more optimal query plan. + /// + /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. + /// + /// Otherwise, you might want to add `LIMIT 1` to your query. + #[inline] + pub async fn fetch_optional<'e, E>( + self, + executor: E, + ) -> crate::Result<::Row> + where + 'q: 'e, + E: Executor<'e>, + { + executor.fetch_one(self).await + } +} diff --git a/src/lib.rs b/src/lib.rs index c70dfcc66d..5ba95c241f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub use sqlx_core::query::{query, query_with}; pub use sqlx_core::query_as::{query_as, query_as_with}; pub use sqlx_core::query_builder::{self, QueryBuilder}; pub use sqlx_core::query_scalar::{query_scalar, query_scalar_with}; +pub use sqlx_core::raw_sql::{raw_sql, RawSql}; pub use sqlx_core::row::Row; pub use sqlx_core::statement::Statement; pub use sqlx_core::transaction::{Transaction, TransactionManager}; diff --git a/tests/mysql/mysql.rs b/tests/mysql/mysql.rs index ba10824f89..19a3887c83 100644 --- a/tests/mysql/mysql.rs +++ b/tests/mysql/mysql.rs @@ -327,7 +327,7 @@ async fn it_can_bind_only_null_issue_540() -> anyhow::Result<()> { async fn it_can_bind_and_return_years() -> anyhow::Result<()> { let mut conn = new::().await?; - conn.execute( + sqlx::raw_sql( r#" CREATE TEMPORARY TABLE too_many_years ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -335,6 +335,7 @@ CREATE TEMPORARY TABLE too_many_years ( ); "#, ) + .execute(&mut conn) .await?; sqlx::query( @@ -442,7 +443,8 @@ async fn test_issue_622() -> anyhow::Result<()> { #[sqlx_macros::test] async fn it_can_work_with_transactions() -> anyhow::Result<()> { let mut conn = new::().await?; - conn.execute("CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY);") + sqlx::raw_sql("CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY);") + .execute(&mut conn) .await?; // begin .. rollback diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 352d3278ea..4f204e8005 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -49,7 +49,8 @@ async fn it_pings() -> anyhow::Result<()> { async fn it_pings_after_suspended_query() -> anyhow::Result<()> { let mut conn = new::().await?; - conn.execute("create temporary table processed_row(val int4 primary key)") + sqlx::raw_sql("create temporary table processed_row(val int4 primary key)") + .execute(&mut conn) .await?; // This query wants to return 50 rows but we only read the first one. diff --git a/tests/sqlite/sqlite.rs b/tests/sqlite/sqlite.rs index d4cb4b881f..2d0b3267ba 100644 --- a/tests/sqlite/sqlite.rs +++ b/tests/sqlite/sqlite.rs @@ -243,7 +243,7 @@ async fn it_opens_temp_on_disk() -> anyhow::Result<()> { #[sqlx_macros::test] async fn it_fails_to_parse() -> anyhow::Result<()> { let mut conn = new::().await?; - let res = conn.execute("SEELCT 1").await; + let res = sqlx::raw_sql("SEELCT 1").execute(&mut conn).await; assert!(res.is_err()); From 02f196b4ac6c702b4848bb0310439ffa49e116eb Mon Sep 17 00:00:00 2001 From: Vraj Shah <89588024+Vrajs16@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:20:29 -0500 Subject: [PATCH 29/59] fix: link to correct version in first-migration message (#2900) Co-authored-by: Austin Bonander --- sqlx-cli/src/migrate.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index 1ab8929fc1..e939f5bec8 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -151,6 +151,19 @@ pub async fn add( "".to_string() }; + // Provide a link to the current version in case the details change. + // Patch version is deliberately omitted. + let version = if let (Some(major), Some(minor)) = ( + // Don't fail if we're not being built by Cargo + option_env!("CARGO_PKG_VERSION_MAJOR"), + option_env!("CARGO_PKG_VERSION_MINOR"), + ) { + format!("{major}.{minor}") + } else { + // If a version isn't available, "latest" is fine. + "latest".to_string() + }; + print!( r#" Congratulations on creating your first migration! @@ -158,14 +171,13 @@ Congratulations on creating your first migration! Did you know you can embed your migrations in your application binary? On startup, after creating your database connection or pool, add: -sqlx::migrate!({}).run(<&your_pool OR &mut your_connection>).await?; +sqlx::migrate!({quoted_source}).run(<&your_pool OR &mut your_connection>).await?; Note that the compiler won't pick up new migrations if no Rust source files have changed. You can create a Cargo build script to work around this with `sqlx migrate build-script`. -See: https://docs.rs/sqlx/latest/sqlx/macro.migrate.html +See: https://docs.rs/sqlx/{version}/sqlx/macro.migrate.html "#, - quoted_source ); } From b615d2a826d2062f3a5084357ade3529ea3c3d57 Mon Sep 17 00:00:00 2001 From: Matt Fellenz Date: Sat, 24 Feb 2024 01:49:51 -0800 Subject: [PATCH 30/59] Use `create_new` instead of `atomic-file-write` (#2914) * Use `create_new` instead of `atomic-file-write` This provides the same functionality but without temporary files, platform-specific code, fragility of `O_TMPFILE` support, and an extra dependency. * Properly handle acceptable failure cases * Consider `PermissionDenied` as acceptable Apparently this can occur on Windows. --------- Co-authored-by: Austin Bonander --- Cargo.lock | 37 ++---------------------------- sqlx-macros-core/Cargo.toml | 1 - sqlx-macros-core/src/query/data.rs | 37 ++++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56cde771a9..931d5eff5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,16 +334,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomic-write-file" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e" -dependencies = [ - "nix 0.26.4", - "rand", -] - [[package]] name = "atty" version = "0.2.14" @@ -1916,7 +1906,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" dependencies = [ - "nix 0.23.2", + "nix", "winapi", ] @@ -1951,15 +1941,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.0" @@ -2069,19 +2050,6 @@ dependencies = [ "memoffset 0.6.5", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", -] - [[package]] name = "nom" version = "7.1.3" @@ -2848,7 +2816,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.23.2", + "nix", "radix_trie", "scopeguard", "smallvec", @@ -3402,7 +3370,6 @@ name = "sqlx-macros-core" version = "0.7.3" dependencies = [ "async-std", - "atomic-write-file", "dotenvy", "either", "heck 0.4.1", diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index 53fadd21c0..48e8d26491 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -48,7 +48,6 @@ tokio = { workspace = true, optional = true } dotenvy = { workspace = true } -atomic-write-file = { version = "0.1" } hex = { version = "0.4.3" } heck = { version = "0.4", features = ["unicode"] } either = "1.6.1" diff --git a/sqlx-macros-core/src/query/data.rs b/sqlx-macros-core/src/query/data.rs index 70eb9c04e0..a0d23911d3 100644 --- a/sqlx-macros-core/src/query/data.rs +++ b/sqlx-macros-core/src/query/data.rs @@ -151,22 +151,41 @@ where } pub(super) fn save_in(&self, dir: impl AsRef) -> crate::Result<()> { + use std::io::ErrorKind; + let path = dir.as_ref().join(format!("query-{}.json", self.hash)); - let mut file = atomic_write_file::AtomicWriteFile::open(&path) - .map_err(|err| format!("failed to open the temporary file: {err:?}"))?; + match std::fs::remove_file(&path) { + Ok(()) => {} + Err(err) + if matches!( + err.kind(), + ErrorKind::NotFound | ErrorKind::PermissionDenied, + ) => {} + Err(err) => return Err(format!("failed to delete {path:?}: {err:?}").into()), + } + let mut file = match std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&path) + { + Ok(file) => file, + // We overlapped with a concurrent invocation and the other one succeeded. + Err(err) if matches!(err.kind(), ErrorKind::AlreadyExists) => return Ok(()), + Err(err) => { + return Err(format!("failed to exclusively create {path:?}: {err:?}").into()) + } + }; - serde_json::to_writer_pretty(file.as_file_mut(), self) - .map_err(|err| format!("failed to serialize query data to file: {err:?}"))?; + let data = serde_json::to_string_pretty(self) + .map_err(|err| format!("failed to serialize query data: {err:?}"))?; + file.write_all(data.as_bytes()) + .map_err(|err| format!("failed to write query data to file: {err:?}"))?; // Ensure there is a newline at the end of the JSON file to avoid // accidental modification by IDE and make github diff tool happier. - file.as_file_mut() - .write_all(b"\n") + file.write_all(b"\n") .map_err(|err| format!("failed to append a newline to file: {err:?}"))?; - file.commit() - .map_err(|err| format!("failed to commit the query data to {path:?}: {err:?}"))?; - Ok(()) } } From 8c43b8c7bcf33baf0b6c1b3bcc5c32ec13ee683e Mon Sep 17 00:00:00 2001 From: ciffelia Date: Sun, 25 Feb 2024 10:02:25 +0900 Subject: [PATCH 31/59] Change the name of "inner" function generated by `#[sqlx::test]` (#3072) --- sqlx-macros-core/src/test_attr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlx-macros-core/src/test_attr.rs b/sqlx-macros-core/src/test_attr.rs index f3bd53c56d..a86547cc6b 100644 --- a/sqlx-macros-core/src/test_attr.rs +++ b/sqlx-macros-core/src/test_attr.rs @@ -161,7 +161,7 @@ fn expand_advanced(args: syn::AttributeArgs, input: syn::ItemFn) -> crate::Resul #[::core::prelude::v1::test] #(#attrs)* fn #name() #ret { - async fn inner(#inputs) #ret { + async fn #name(#inputs) #ret { #body } @@ -172,7 +172,7 @@ fn expand_advanced(args: syn::AttributeArgs, input: syn::ItemFn) -> crate::Resul args.fixtures(&[#(#fixtures),*]); // We need to give a coercion site or else we get "unimplemented trait" errors. - let f: fn(#(#fn_arg_types),*) -> _ = inner; + let f: fn(#(#fn_arg_types),*) -> _ = #name; ::sqlx::testing::TestFn::run_test(f, args) } From 27a49914ad5e0f21b8fb1f2a62db8d838724548f Mon Sep 17 00:00:00 2001 From: darkecho731 <92435436+darkecho731@users.noreply.github.com> Date: Sun, 25 Feb 2024 04:30:50 +0100 Subject: [PATCH 32/59] feat(mysql): allow to connect with mysql driver without default behavor (#3037) * feat(mysql): provide options to disable default connection settings after connecting * style(mysql): remove unecessary newlines and run rustfmt * feat(mysql): allow to pass a custom timezone to the database after connecting docs(mysql): improve docs for options set_names and no_engine_substitution --- sqlx-mysql/src/options/connect.rs | 42 +++++++++++++------- sqlx-mysql/src/options/mod.rs | 65 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/sqlx-mysql/src/options/connect.rs b/sqlx-mysql/src/options/connect.rs index e6a8600272..0b52a761bb 100644 --- a/sqlx-mysql/src/options/connect.rs +++ b/sqlx-mysql/src/options/connect.rs @@ -46,22 +46,36 @@ impl ConnectOptions for MySqlConnectOptions { // https://mathiasbynens.be/notes/mysql-utf8mb4 - let mut options = String::new(); + let mut sql_mode = Vec::new(); if self.pipes_as_concat { - options.push_str(r#"SET sql_mode=(SELECT CONCAT(@@sql_mode, ',PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION')),"#); - } else { - options.push_str( - r#"SET sql_mode=(SELECT CONCAT(@@sql_mode, ',NO_ENGINE_SUBSTITUTION')),"#, - ); + sql_mode.push(r#"PIPES_AS_CONCAT"#); + } + if self.no_engine_subsitution { + sql_mode.push(r#"NO_ENGINE_SUBSTITUTION"#); + } + + let mut options = Vec::new(); + if !sql_mode.is_empty() { + options.push(format!( + r#"sql_mode=(SELECT CONCAT(@@sql_mode, ',{}'))"#, + sql_mode.join(",") + )); + } + if let Some(timezone) = &self.timezone { + options.push(format!(r#"time_zone='{}'"#, timezone)); + } + if self.set_names { + options.push(format!( + r#"NAMES {} COLLATE {}"#, + conn.stream.charset.as_str(), + conn.stream.collation.as_str() + )) + } + + if !options.is_empty() { + conn.execute(&*format!(r#"SET {};"#, options.join(","))) + .await?; } - options.push_str(r#"time_zone='+00:00',"#); - options.push_str(&format!( - r#"NAMES {} COLLATE {};"#, - conn.stream.charset.as_str(), - conn.stream.collation.as_str() - )); - - conn.execute(&*options).await?; Ok(conn) }) diff --git a/sqlx-mysql/src/options/mod.rs b/sqlx-mysql/src/options/mod.rs index a797576937..1aaab0ead7 100644 --- a/sqlx-mysql/src/options/mod.rs +++ b/sqlx-mysql/src/options/mod.rs @@ -77,6 +77,9 @@ pub struct MySqlConnectOptions { pub(crate) log_settings: LogSettings, pub(crate) pipes_as_concat: bool, pub(crate) enable_cleartext_plugin: bool, + pub(crate) no_engine_subsitution: bool, + pub(crate) timezone: Option, + pub(crate) set_names: bool, } impl Default for MySqlConnectOptions { @@ -105,6 +108,9 @@ impl MySqlConnectOptions { log_settings: Default::default(), pipes_as_concat: true, enable_cleartext_plugin: false, + no_engine_subsitution: true, + timezone: Some(String::from("+00:00")), + set_names: true, } } @@ -333,6 +339,65 @@ impl MySqlConnectOptions { self.enable_cleartext_plugin = flag_val; self } + + /// Flag that enables or disables the `NO_ENGINE_SUBSTITUTION` sql_mode setting after + /// connection. + /// + /// If not set, if the available storage engine specified by a `CREATE TABLE` is not available, + /// a warning is given and the default storage engine is used instead. + /// + /// By default, this is `true` (`NO_ENGINE_SUBSTITUTION` is passed, forbidding engine + /// substitution). + /// + /// https://mariadb.com/kb/en/sql-mode/ + pub fn no_engine_subsitution(mut self, flag_val: bool) -> Self { + self.no_engine_subsitution = flag_val; + self + } + + /// If `Some`, sets the `time_zone` option to the given string after connecting to the database. + /// + /// If `None`, no `time_zone` parameter is sent; the server timezone will be used instead. + /// + /// Defaults to `Some(String::from("+00:00"))` to ensure all timestamps are in UTC. + /// + /// ### Warning + /// Changing this setting from its default will apply an unexpected skew to any + /// `time::OffsetDateTime` or `chrono::DateTime` value, whether passed as a parameter or + /// decoded as a result. `TIMESTAMP` values are not encoded with their UTC offset in the MySQL + /// protocol, so encoding and decoding of these types assumes the server timezone is *always* + /// UTC. + /// + /// If you are changing this option, ensure your application only uses + /// `time::PrimitiveDateTime` or `chrono::NaiveDateTime` and that it does not assume these + /// timestamps can be placed on a real timeline without applying the proper offset. + pub fn timezone(mut self, value: impl Into>) -> Self { + self.timezone = value.into(); + self + } + + /// If enabled, `SET NAMES '{charset}' COLLATE '{collation}'` is passed with the values of + /// [`.charset()`] and [`.collation()`] after connecting to the database. + /// + /// This ensures the connection uses the specified character set and collation. + /// + /// Enabled by default. + /// + /// ### Warning + /// If this is disabled and the default charset is not binary-compatible with UTF-8, query + /// strings, column names and string values will likely not decode (or encode) correctly, which + /// may result in unexpected errors or garbage outputs at runtime. + /// + /// For proper functioning, you *must* ensure the server is using a binary-compatible charset, + /// such as ASCII or Latin-1 (ISO 8859-1), and that you do not pass any strings containing + /// codepoints not supported by said charset. + /// + /// Instead of disabling this, you may also consider setting [`.charset()`] to a charset that + /// is supported by your MySQL or MariaDB server version and compatible with UTF-8. + pub fn set_names(mut self, flag_val: bool) -> Self { + self.set_names = flag_val; + self + } } impl MySqlConnectOptions { From b4f6596b0610fc761b21716323e16e165dcc7101 Mon Sep 17 00:00:00 2001 From: Mirek Klimos Date: Mon, 4 Mar 2024 16:39:59 -0800 Subject: [PATCH 33/59] Improve max_lifetime handling (#3065) * Check max lifetime in return_to_pool, not on acquire * Improve checks in backgrand maintenance task * add tests * adjust test to fix --- sqlx-core/src/pool/connection.rs | 9 +++- sqlx-core/src/pool/inner.rs | 64 ++++++++++----------------- tests/any/pool.rs | 74 +++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 49 deletions(-) diff --git a/sqlx-core/src/pool/connection.rs b/sqlx-core/src/pool/connection.rs index e73f4f2735..c1e163c704 100644 --- a/sqlx-core/src/pool/connection.rs +++ b/sqlx-core/src/pool/connection.rs @@ -9,7 +9,7 @@ use crate::connection::Connection; use crate::database::Database; use crate::error::Error; -use super::inner::{DecrementSizeGuard, PoolInner}; +use super::inner::{is_beyond_max_lifetime, DecrementSizeGuard, PoolInner}; use crate::pool::options::PoolConnectionMetadata; use std::future::Future; @@ -239,6 +239,13 @@ impl Floating> { return false; } + // If the connection is beyond max lifetime, close the connection and + // immediately create a new connection + if is_beyond_max_lifetime(&self.inner, &self.guard.pool.options) { + self.close().await; + return false; + } + if let Some(test) = &self.guard.pool.options.after_release { let meta = self.metadata(); match (test)(&mut self.inner.raw, meta).await { diff --git a/sqlx-core/src/pool/inner.rs b/sqlx-core/src/pool/inner.rs index c14e6a434b..3c265476ff 100644 --- a/sqlx-core/src/pool/inner.rs +++ b/sqlx-core/src/pool/inner.rs @@ -197,7 +197,7 @@ impl PoolInner { } pub(super) fn release(&self, floating: Floating>) { - // `options.after_release` is invoked by `PoolConnection::release_to_pool()`. + // `options.after_release` and other checks are in `PoolConnection::return_to_pool()`. let Floating { inner: idle, guard } = floating.into_idle(); @@ -273,7 +273,7 @@ impl PoolInner { // `try_increment_size()`. tracing::debug!("woke but was unable to acquire idle connection or open new one; retrying"); // If so, we're likely in the current-thread runtime if it's Tokio - // and so we should yield to let any spawned release_to_pool() tasks + // and so we should yield to let any spawned return_to_pool() tasks // execute. crate::rt::yield_now().await; continue; @@ -417,7 +417,10 @@ impl Drop for PoolInner { } /// Returns `true` if the connection has exceeded `options.max_lifetime` if set, `false` otherwise. -fn is_beyond_max_lifetime(live: &Live, options: &PoolOptions) -> bool { +pub(super) fn is_beyond_max_lifetime( + live: &Live, + options: &PoolOptions, +) -> bool { options .max_lifetime .map_or(false, |max| live.created_at.elapsed() > max) @@ -434,12 +437,6 @@ async fn check_idle_conn( mut conn: Floating>, options: &PoolOptions, ) -> Result>, DecrementSizeGuard> { - // If the connection we pulled has expired, close the connection and - // immediately create a new connection - if is_beyond_max_lifetime(&conn, options) { - return Err(conn.close().await); - } - if options.test_before_acquire { // Check that the connection is still live if let Err(error) = conn.ping().await { @@ -503,22 +500,30 @@ fn spawn_maintenance_tasks(pool: &Arc>) { crate::rt::spawn(async move { let _ = close_event .do_until(async { - let mut slept = true; - // If the last handle to the pool was dropped while we were sleeping while let Some(pool) = pool_weak.upgrade() { if pool.is_closed() { return; } - // Don't run the reaper right away. - if slept && !pool.idle_conns.is_empty() { - do_reap(&pool).await; - } - let next_run = Instant::now() + period; - pool.min_connections_maintenance(Some(next_run)).await; + // Go over all idle connections, check for idleness and lifetime, + // and if we have fewer than min_connections after reaping a connection, + // open a new one immediately. Note that other connections may be popped from + // the queue in the meantime - that's fine, there is no harm in checking more + for _ in 0..pool.num_idle() { + if let Some(conn) = pool.try_acquire() { + if is_beyond_idle_timeout(&conn, &pool.options) + || is_beyond_max_lifetime(&conn, &pool.options) + { + let _ = conn.close().await; + pool.min_connections_maintenance(Some(next_run)).await; + } else { + pool.release(conn.into_live()); + } + } + } // Don't hold a reference to the pool while sleeping. drop(pool); @@ -530,37 +535,12 @@ fn spawn_maintenance_tasks(pool: &Arc>) { // `next_run` is in the past, just yield. crate::rt::yield_now().await; } - - slept = true; } }) .await; }); } -async fn do_reap(pool: &Arc>) { - // reap at most the current size minus the minimum idle - let max_reaped = pool.size().saturating_sub(pool.options.min_connections); - - // collect connections to reap - let (reap, keep) = (0..max_reaped) - // only connections waiting in the queue - .filter_map(|_| pool.try_acquire()) - .partition::, _>(|conn| { - is_beyond_idle_timeout(conn, &pool.options) - || is_beyond_max_lifetime(conn, &pool.options) - }); - - for conn in keep { - // return valid connections to the pool first - pool.release(conn.into_live()); - } - - for conn in reap { - let _ = conn.close().await; - } -} - /// RAII guard returned by `Pool::try_increment_size()` and others. /// /// Will decrement the pool size if dropped, to avoid semantically "leaking" connections diff --git a/tests/any/pool.rs b/tests/any/pool.rs index ed36dd74b6..901258a5c9 100644 --- a/tests/any/pool.rs +++ b/tests/any/pool.rs @@ -1,9 +1,8 @@ use sqlx::any::{AnyConnectOptions, AnyPoolOptions}; use sqlx::Executor; -use std::sync::atomic::AtomicI32; use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, + atomic::{AtomicI32, AtomicUsize, Ordering}, + Arc, Mutex, }; use std::time::Duration; @@ -116,7 +115,7 @@ async fn test_pool_callbacks() -> anyhow::Result<()> { CREATE TEMPORARY TABLE conn_stats( id int primary key, before_acquire_calls int default 0, - after_release_calls int default 0 + after_release_calls int default 0 ); INSERT INTO conn_stats(id) VALUES ({}); "#, @@ -137,7 +136,7 @@ async fn test_pool_callbacks() -> anyhow::Result<()> { // MySQL and MariaDB don't support UPDATE ... RETURNING sqlx::query( r#" - UPDATE conn_stats + UPDATE conn_stats SET before_acquire_calls = before_acquire_calls + 1 "#, ) @@ -161,7 +160,7 @@ async fn test_pool_callbacks() -> anyhow::Result<()> { Box::pin(async move { sqlx::query( r#" - UPDATE conn_stats + UPDATE conn_stats SET after_release_calls = after_release_calls + 1 "#, ) @@ -216,3 +215,66 @@ async fn test_pool_callbacks() -> anyhow::Result<()> { Ok(()) } + +#[sqlx_macros::test] +async fn test_connection_maintenance() -> anyhow::Result<()> { + sqlx::any::install_default_drivers(); + sqlx_test::setup_if_needed(); + let conn_options: AnyConnectOptions = std::env::var("DATABASE_URL")?.parse()?; + + let last_meta = Arc::new(Mutex::new(None)); + let last_meta_ = last_meta.clone(); + let pool = AnyPoolOptions::new() + .max_lifetime(Duration::from_millis(400)) + .min_connections(3) + .before_acquire(move |_conn, _meta| { + *last_meta_.lock().unwrap() = Some(_meta); + Box::pin(async { Ok(true) }) + }) + .connect_lazy_with(conn_options); + + // Open and release 5 connections + let conns = vec![ + pool.acquire().await?, + pool.acquire().await?, + pool.acquire().await?, + pool.acquire().await?, + pool.acquire().await?, + ]; + assert_eq!(pool.size(), 5); + assert_eq!(pool.num_idle(), 0); + for mut conn in conns { + conn.return_to_pool().await; + } + + assert_eq!(pool.size(), 5); + assert_eq!(pool.num_idle(), 5); + + // Wait for at least two iterations of maintenance task + sqlx_core::rt::sleep(Duration::from_secs(1)).await; + + // Existing connections should have been closed due to max lifetime + // and the pool should have reopened min_connections new ones. + // One connection might be in the process of being replaced so we assert 2-3. + assert!( + pool.size() >= 2 && pool.size() <= 3, + "pool.size() = {}", + pool.size() + ); + for _ in 0..2 { + // Check that the connections was both acquired from the pool AND it's new + let _ = pool.acquire().await.expect("failed to acquire connection"); + let meta = last_meta + .lock() + .unwrap() + .take() + .expect("expected a connection from the pool"); + assert!( + meta.age < Duration::from_secs(1), + "expected a fresh connection (age {:?})", + meta.age + ); + } + + Ok(()) +} From c32809af91f4829e837ff5fffee34a86f7a7d9ba Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 4 Mar 2024 17:09:43 -0800 Subject: [PATCH 34/59] fix(migrate): improve error message when parsing version from filename supercedes #2906 --- sqlx-core/src/migrate/source.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index dd6d36afcc..7966dd1273 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -47,7 +47,10 @@ impl<'s> MigrationSource<'s> for &'s Path { continue; } - let version: i64 = parts[0].parse()?; + let version: i64 = parts[0].parse() + .map_err(|_e| { + format!("error parsing migration filename {file_name:?}; expected integer version prefix (e.g. `01_foo.sql`)") + })?; let migration_type = MigrationType::from_filename(parts[1]); // remove the `.sql` and replace `_` with ` ` From c78425b3ac20b1d706a610abde361cec3af3847c Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 4 Mar 2024 19:30:45 -0800 Subject: [PATCH 35/59] fix(ci): disable `mariadb_verylatest` pass for RusTLS see: #3091 --- .github/workflows/sqlx.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 153ea24479..a7cf763cbe 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -470,6 +470,10 @@ jobs: mariadb: [verylatest, 10_11, 10_4] runtime: [async-std, tokio] tls: [native-tls, rustls, none] + exclude: + # FIXME: `rustls` cannot accept MariaDB's new self-signed certificates: https://github.com/launchbadge/sqlx/issues/3091 + - mariadb: verylatest + tls: rustls needs: check steps: - uses: actions/checkout@v2 From 664dbdf54b899d63fb04f309342363048cf6cff4 Mon Sep 17 00:00:00 2001 From: Carter Date: Sat, 9 Dec 2023 21:19:15 -0700 Subject: [PATCH 36/59] docs: update example for `PgConnectOptions` --- sqlx-postgres/src/options/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-postgres/src/options/mod.rs b/sqlx-postgres/src/options/mod.rs index 2a2c43006a..73e08bc7c2 100644 --- a/sqlx-postgres/src/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -80,7 +80,7 @@ mod ssl_mode; /// /// // Change the log verbosity level for queries. /// // Information about SQL queries is logged at `DEBUG` level by default. -/// opts.log_statements(log::LevelFilter::Trace); +/// opts = opts.log_statements(log::LevelFilter::Trace); /// /// let pool = PgPool::connect_with(&opts).await?; /// # } From e8fc35a5a4ea8e54d4ad49b4ff836fb90fef1245 Mon Sep 17 00:00:00 2001 From: Rafael Guerreiro Date: Wed, 28 Feb 2024 19:52:46 -0800 Subject: [PATCH 37/59] Remove sha1 because it's not being used in postgres --- Cargo.lock | 1 - sqlx-postgres/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 931d5eff5c..9166c1b9d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3469,7 +3469,6 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index 987d8e8cbc..77cc0e2403 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -32,7 +32,6 @@ hkdf = "0.12.0" hmac = { version = "0.12.0", default-features = false, features = ["reset"]} md-5 = { version = "0.10.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } -sha1 = { version = "0.10.1", default-features = false } sha2 = { version = "0.10.0", default-features = false } # Type Integrations (versions inherited from `[workspace.dependencies]`) From dfb60141bd371e0cecadc70414094c77f286c3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 11 Feb 2024 18:11:48 +0100 Subject: [PATCH 38/59] fix: do not panic when binding a large BigDecimal --- sqlx-postgres/src/types/bigdecimal.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sqlx-postgres/src/types/bigdecimal.rs b/sqlx-postgres/src/types/bigdecimal.rs index 8240bb0d27..9d66d359d9 100644 --- a/sqlx-postgres/src/types/bigdecimal.rs +++ b/sqlx-postgres/src/types/bigdecimal.rs @@ -138,12 +138,18 @@ impl TryFrom<&'_ BigDecimal> for PgNumeric { } } -/// ### Panics -/// If this `BigDecimal` cannot be represented by `PgNumeric`. impl Encode<'_, Postgres> for BigDecimal { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + use std::str::FromStr; + // If the argument is too big, then we replace it with a less big argument. + // This less big argument is already outside the range of allowed PostgreSQL DECIMAL, which + // means that PostgreSQL will return the 22P03 error kind upon receiving it. This is the + // expected error, and the user should be ready to handle it anyway. PgNumeric::try_from(self) - .expect("BigDecimal magnitude too great for Postgres NUMERIC type") + .unwrap_or_else(|_| { + PgNumeric::try_from(&BigDecimal::from_str(&format!("{:030000}", 0)).unwrap()) + .unwrap() + }) .encode(buf); IsNull::No From e5c18b354e50249a426cb83071fd713be90c83ed Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Tue, 5 Mar 2024 18:04:45 -0800 Subject: [PATCH 39/59] fix: gate `sqlcipher` testing behind `cfg` to make development less annoying --- .github/workflows/sqlx.yml | 3 +++ Cargo.toml | 5 ++++- tests/sqlite/sqlcipher.rs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index a7cf763cbe..037dd732fd 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -167,6 +167,9 @@ jobs: matrix: runtime: [async-std, tokio] needs: check + env: + # Enable tests with SQLCipher + RUSTFLAGS: --cfg sqlite_test_sqlcipher steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 9a4dfdf5fe..e765d06371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,10 @@ hex = "0.4.3" tempfile = "3.9.0" criterion = { version = "0.5.1", features = ["async_tokio"] } -# Needed to test SQLCipher +# If this is an unconditional dev-dependency then Cargo will *always* try to build `libsqlite3-sys`, +# even when SQLite isn't the intended test target, and fail if the build environment is not set up for compiling C code. +[target.'cfg(sqlite_test_sqlcipher)'.dev-dependencies] +# Enable testing with SQLCipher if specifically requested. libsqlite3-sys = { version = "0.27", features = ["bundled-sqlcipher"] } # diff --git a/tests/sqlite/sqlcipher.rs b/tests/sqlite/sqlcipher.rs index 5d228619b5..ddcc2397a0 100644 --- a/tests/sqlite/sqlcipher.rs +++ b/tests/sqlite/sqlcipher.rs @@ -125,6 +125,7 @@ async fn it_fails_if_password_is_incorrect() -> anyhow::Result<()> { Ok(()) } +#[cfg(sqlite_test_sqlcipher)] #[sqlx_macros::test] async fn it_honors_order_of_encryption_pragmas() -> anyhow::Result<()> { let (url, _dir) = new_db_url().await?; From 791a7f5417ca46859ababd97ee0d52c0356f4024 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Tue, 5 Mar 2024 18:06:41 -0800 Subject: [PATCH 40/59] doc(pg): document behavior of `bigdecimal` and `rust_decimal` with out-of-range values also add a regression test --- sqlx-postgres/src/types/bigdecimal-range.md | 20 ++++++++ sqlx-postgres/src/types/bigdecimal.rs | 26 +++++++--- sqlx-postgres/src/types/mod.rs | 4 ++ sqlx-postgres/src/types/rust_decimal-range.md | 10 ++++ sqlx-postgres/src/types/rust_decimal.rs | 6 +-- tests/postgres/postgres.rs | 48 +++++++++++++++++-- 6 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 sqlx-postgres/src/types/bigdecimal-range.md create mode 100644 sqlx-postgres/src/types/rust_decimal-range.md diff --git a/sqlx-postgres/src/types/bigdecimal-range.md b/sqlx-postgres/src/types/bigdecimal-range.md new file mode 100644 index 0000000000..5d4ee502bb --- /dev/null +++ b/sqlx-postgres/src/types/bigdecimal-range.md @@ -0,0 +1,20 @@ +#### Note: `BigDecimal` Has a Larger Range than `NUMERIC` +`BigDecimal` can represent values with a far, far greater range than the `NUMERIC` type in Postgres can. + +`NUMERIC` is limited to 131,072 digits before the decimal point, and 16,384 digits after it. +See [Section 8.1, Numeric Types] of the Postgres manual for details. + +Meanwhile, `BigDecimal` can theoretically represent a value with an arbitrary number of decimal digits, albeit +with a maximum of 263 significant figures. + +Because encoding in the current API design _must_ be infallible, +when attempting to encode a `BigDecimal` that cannot fit in the wire representation of `NUMERIC`, +SQLx may instead encode a sentinel value that falls outside the allowed range but is still representable. + +This will cause the query to return a `DatabaseError` with code `22P03` (`invalid_binary_representation`) +and the error message `invalid scale in external "numeric" value` (though this may be subject to change). + +However, `BigDecimal` should be able to decode any `NUMERIC` value except `NaN`, +for which it has no representation. + +[Section 8.1, Numeric Types]: https://www.postgresql.org/docs/current/datatype-numeric.html diff --git a/sqlx-postgres/src/types/bigdecimal.rs b/sqlx-postgres/src/types/bigdecimal.rs index 9d66d359d9..f42a1794b7 100644 --- a/sqlx-postgres/src/types/bigdecimal.rs +++ b/sqlx-postgres/src/types/bigdecimal.rs @@ -127,10 +127,7 @@ impl TryFrom<&'_ BigDecimal> for PgNumeric { } Ok(PgNumeric::Number { - sign: match sign { - Sign::Plus | Sign::NoSign => PgNumericSign::Positive, - Sign::Minus => PgNumericSign::Negative, - }, + sign: sign_to_pg(sign), scale, weight, digits, @@ -138,17 +135,22 @@ impl TryFrom<&'_ BigDecimal> for PgNumeric { } } +#[doc=include_str!("bigdecimal-range.md")] impl Encode<'_, Postgres> for BigDecimal { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { - use std::str::FromStr; // If the argument is too big, then we replace it with a less big argument. // This less big argument is already outside the range of allowed PostgreSQL DECIMAL, which // means that PostgreSQL will return the 22P03 error kind upon receiving it. This is the // expected error, and the user should be ready to handle it anyway. PgNumeric::try_from(self) .unwrap_or_else(|_| { - PgNumeric::try_from(&BigDecimal::from_str(&format!("{:030000}", 0)).unwrap()) - .unwrap() + PgNumeric::Number { + digits: vec![1], + // This is larger than the maximum allowed value, so Postgres should return an error. + scale: 0x4000, + weight: 0, + sign: sign_to_pg(self.sign()), + } }) .encode(buf); @@ -162,6 +164,9 @@ impl Encode<'_, Postgres> for BigDecimal { } } +/// ### Note: `NaN` +/// `BigDecimal` has a greater range than `NUMERIC` (see the corresponding `Encode` impl for details) +/// but cannot represent `NaN`, so decoding may return an error. impl Decode<'_, Postgres> for BigDecimal { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { @@ -171,6 +176,13 @@ impl Decode<'_, Postgres> for BigDecimal { } } +fn sign_to_pg(sign: Sign) -> PgNumericSign { + match sign { + Sign::Plus | Sign::NoSign => PgNumericSign::Positive, + Sign::Minus => PgNumericSign::Negative, + } +} + #[cfg(test)] mod bigdecimal_to_pgnumeric { use super::{BigDecimal, PgNumeric, PgNumericSign}; diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index cf2c9ea933..d68d9b9178 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -32,6 +32,8 @@ //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | NUMERIC | //! +#![doc=include_str!("bigdecimal-range.md")] +//! //! ### [`rust_decimal`](https://crates.io/crates/rust_decimal) //! Requires the `rust_decimal` Cargo feature flag. //! @@ -39,6 +41,8 @@ //! |---------------------------------------|------------------------------------------------------| //! | `rust_decimal::Decimal` | NUMERIC | //! +#![doc=include_str!("rust_decimal-range.md")] +//! //! ### [`chrono`](https://crates.io/crates/chrono) //! //! Requires the `chrono` Cargo feature flag. diff --git a/sqlx-postgres/src/types/rust_decimal-range.md b/sqlx-postgres/src/types/rust_decimal-range.md new file mode 100644 index 0000000000..f986d616f0 --- /dev/null +++ b/sqlx-postgres/src/types/rust_decimal-range.md @@ -0,0 +1,10 @@ +#### Note: `rust_decimal::Decimal` Has a Smaller Range than `NUMERIC` +`NUMERIC` is can have up to 131,072 digits before the decimal point, and 16,384 digits after it. +See [Section 8.1, Numeric Types] of the Postgres manual for details. + +However, `rust_decimal::Decimal` is limited to a maximum absolute magnitude of 296 - 1, +a number with 67 decimal digits, and a minimum absolute magnitude of 10-28, a number with, unsurprisingly, +28 decimal digits. + +Thus, in contrast with `BigDecimal`, `NUMERIC` can actually represent every possible value of `rust_decimal::Decimal`, +but not the other way around. This means that encoding should never fail, but decoding can. diff --git a/sqlx-postgres/src/types/rust_decimal.rs b/sqlx-postgres/src/types/rust_decimal.rs index 4a0c720fad..9f00e3ca32 100644 --- a/sqlx-postgres/src/types/rust_decimal.rs +++ b/sqlx-postgres/src/types/rust_decimal.rs @@ -71,6 +71,7 @@ impl TryFrom for Decimal { } } +// This impl is effectively infallible because `NUMERIC` has a greater range than `Decimal`. impl TryFrom<&'_ Decimal> for PgNumeric { type Error = BoxDynError; @@ -142,18 +143,17 @@ impl TryFrom<&'_ Decimal> for PgNumeric { } } -/// ### Panics -/// If this `Decimal` cannot be represented by `PgNumeric`. impl Encode<'_, Postgres> for Decimal { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { PgNumeric::try_from(self) - .expect("Decimal magnitude too great for Postgres NUMERIC type") + .expect("BUG: `Decimal` to `PgNumeric` conversion should be infallible") .encode(buf); IsNull::No } } +#[doc=include_str!("rust_decimal-range.md")] impl Decode<'_, Postgres> for Decimal { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 4f204e8005..91728bde8d 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -933,11 +933,7 @@ from (values (null)) vals(val) #[sqlx_macros::test] async fn test_listener_cleanup() -> anyhow::Result<()> { - #[cfg(feature = "_rt-tokio")] - use tokio::time::timeout; - - #[cfg(feature = "_rt-async-std")] - use async_std::future::timeout; + use sqlx_core::rt::timeout; use sqlx::pool::PoolOptions; use sqlx::postgres::PgListener; @@ -1838,3 +1834,45 @@ async fn test_error_handling_with_deferred_constraints() -> anyhow::Result<()> { Ok(()) } + +#[sqlx_macros::test] +#[cfg(feature = "bigdecimal")] +async fn test_issue_3052() { + use sqlx::types::BigDecimal; + + // https://github.com/launchbadge/sqlx/issues/3052 + // Previously, attempting to bind a `BigDecimal` would panic if the value was out of range. + // Now, we rewrite it to a sentinel value so that Postgres will return a range error. + let too_small: BigDecimal = "1E-65536".parse().unwrap(); + let too_large: BigDecimal = "1E262144".parse().unwrap(); + + let mut conn = new::().await.unwrap(); + + let too_small_res = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric") + .bind(&too_small) + .fetch_one(&mut conn) + .await; + + match too_small_res { + Err(sqlx::Error::Database(dbe)) => { + let dbe = dbe.downcast::(); + + assert_eq!(dbe.code(), "22P03"); + } + other => panic!("expected Err(DatabaseError), got {other:?}"), + } + + let too_large_res = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric") + .bind(&too_large) + .fetch_one(&mut conn) + .await; + + match too_large_res { + Err(sqlx::Error::Database(dbe)) => { + let dbe = dbe.downcast::(); + + assert_eq!(dbe.code(), "22P03"); + } + other => panic!("expected Err(DatabaseError), got {other:?}"), + } +} From 34860b7f99d72f22cfa4c4e68364ff39108a7f2a Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Tue, 5 Mar 2024 18:12:59 -0800 Subject: [PATCH 41/59] fix(ci): just cfg-out the whole `tests/sqlite/sqlcipher.rs` --- .github/workflows/sqlx.yml | 5 +---- tests/sqlite/sqlcipher.rs | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 037dd732fd..844e2efe20 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -167,9 +167,6 @@ jobs: matrix: runtime: [async-std, tokio] needs: check - env: - # Enable tests with SQLCipher - RUSTFLAGS: --cfg sqlite_test_sqlcipher steps: - uses: actions/checkout@v2 @@ -201,7 +198,7 @@ jobs: env: DATABASE_URL: sqlite:tests/sqlite/sqlite.db SQLX_OFFLINE_DIR: .sqlx - RUSTFLAGS: --cfg sqlite_ipaddr + RUSTFLAGS: --cfg sqlite_ipaddr --cfg sqlite_test_sqlcipher LD_LIBRARY_PATH: /tmp/sqlite3-lib # Remove test artifacts diff --git a/tests/sqlite/sqlcipher.rs b/tests/sqlite/sqlcipher.rs index ddcc2397a0..9be0a179bb 100644 --- a/tests/sqlite/sqlcipher.rs +++ b/tests/sqlite/sqlcipher.rs @@ -1,3 +1,5 @@ +#![cfg(sqlite_test_sqlcipher)] + use std::str::FromStr; use sqlx::sqlite::SqliteQueryResult; @@ -125,7 +127,6 @@ async fn it_fails_if_password_is_incorrect() -> anyhow::Result<()> { Ok(()) } -#[cfg(sqlite_test_sqlcipher)] #[sqlx_macros::test] async fn it_honors_order_of_encryption_pragmas() -> anyhow::Result<()> { let (url, _dir) = new_db_url().await?; From b29eab0439b9914fdae20aa6e2ca6af0e5dc4969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADlian?= <69756012+lily-mosquitoes@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:46:49 +0200 Subject: [PATCH 42/59] feat: add `to_url_lossy` to connect options (#2902) * feat: add get_url to connect options Add a get_url to connect options and implement it for all needed types; include get_filename for sqlite. These changes make it easier to test sqlx. * refactor: use expect with message * refactor: change method name to `to_url_lossy` * fix: remove unused imports --- sqlx-core/src/any/options.rs | 4 ++ sqlx-core/src/connection.rs | 29 +++++++++ sqlx-mysql/src/options/connect.rs | 4 ++ sqlx-mysql/src/options/parse.rs | 76 +++++++++++++++++++++++- sqlx-postgres/src/options/connect.rs | 4 ++ sqlx-postgres/src/options/parse.rs | 88 +++++++++++++++++++++++++++- sqlx-sqlite/src/options/connect.rs | 4 ++ sqlx-sqlite/src/options/mod.rs | 5 ++ sqlx-sqlite/src/options/parse.rs | 44 +++++++++++++- 9 files changed, 253 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/any/options.rs b/sqlx-core/src/any/options.rs index dfa677e672..bb29d817c9 100644 --- a/sqlx-core/src/any/options.rs +++ b/sqlx-core/src/any/options.rs @@ -43,6 +43,10 @@ impl ConnectOptions for AnyConnectOptions { }) } + fn to_url_lossy(&self) -> Url { + self.database_url.clone() + } + #[inline] fn connect(&self) -> BoxFuture<'_, Result> { AnyConnection::connect(self) diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index f254344a27..584e5c4756 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -189,6 +189,35 @@ pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug + /// Parse the `ConnectOptions` from a URL. fn from_url(url: &Url) -> Result; + /// Get a connection URL that may be used to connect to the same database as this `ConnectOptions`. + /// + /// ### Note: Lossy + /// Any flags or settings which do not have a representation in the URL format will be lost. + /// They will fall back to their default settings when the URL is parsed. + /// + /// The only settings guaranteed to be preserved are: + /// * Username + /// * Password + /// * Hostname + /// * Port + /// * Database name + /// * Unix socket or SQLite database file path + /// * SSL mode (if applicable) + /// * SSL CA certificate path + /// * SSL client certificate path + /// * SSL client key path + /// + /// Additional settings are driver-specific. Refer to the source of a given implementation + /// to see which options are preserved in the URL. + /// + /// ### Panics + /// This defaults to `unimplemented!()`. + /// + /// Individual drivers should override this to implement the intended behavior. + fn to_url_lossy(&self) -> Url { + unimplemented!() + } + /// Establish a new database connection with the options specified by `self`. fn connect(&self) -> BoxFuture<'_, Result> where diff --git a/sqlx-mysql/src/options/connect.rs b/sqlx-mysql/src/options/connect.rs index 0b52a761bb..4c89b43921 100644 --- a/sqlx-mysql/src/options/connect.rs +++ b/sqlx-mysql/src/options/connect.rs @@ -14,6 +14,10 @@ impl ConnectOptions for MySqlConnectOptions { Self::parse_from_url(url) } + fn to_url_lossy(&self) -> Url { + self.build_url() + } + fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized, diff --git a/sqlx-mysql/src/options/parse.rs b/sqlx-mysql/src/options/parse.rs index 5ba5c3207a..971510cae5 100644 --- a/sqlx-mysql/src/options/parse.rs +++ b/sqlx-mysql/src/options/parse.rs @@ -1,9 +1,9 @@ use std::str::FromStr; -use percent_encoding::percent_decode_str; +use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use sqlx_core::Url; -use crate::error::Error; +use crate::{error::Error, MySqlSslMode}; use super::MySqlConnectOptions; @@ -78,6 +78,65 @@ impl MySqlConnectOptions { Ok(options) } + + pub(crate) fn build_url(&self) -> Url { + let mut url = Url::parse(&format!( + "mysql://{}@{}:{}", + self.username, self.host, self.port + )) + .expect("BUG: generated un-parseable URL"); + + if let Some(password) = &self.password { + let password = utf8_percent_encode(&password, NON_ALPHANUMERIC).to_string(); + let _ = url.set_password(Some(&password)); + } + + if let Some(database) = &self.database { + url.set_path(&database); + } + + let ssl_mode = match self.ssl_mode { + MySqlSslMode::Disabled => "DISABLED", + MySqlSslMode::Preferred => "PREFERRED", + MySqlSslMode::Required => "REQUIRED", + MySqlSslMode::VerifyCa => "VERIFY_CA", + MySqlSslMode::VerifyIdentity => "VERIFY_IDENTITY", + }; + url.query_pairs_mut().append_pair("ssl-mode", ssl_mode); + + if let Some(ssl_ca) = &self.ssl_ca { + url.query_pairs_mut() + .append_pair("ssl-ca", &ssl_ca.to_string()); + } + + url.query_pairs_mut().append_pair("charset", &self.charset); + + if let Some(collation) = &self.collation { + url.query_pairs_mut().append_pair("charset", &collation); + } + + if let Some(ssl_client_cert) = &self.ssl_client_cert { + url.query_pairs_mut() + .append_pair("ssl-cert", &ssl_client_cert.to_string()); + } + + if let Some(ssl_client_key) = &self.ssl_client_key { + url.query_pairs_mut() + .append_pair("ssl-key", &ssl_client_key.to_string()); + } + + url.query_pairs_mut().append_pair( + "statement-cache-capacity", + &self.statement_cache_capacity.to_string(), + ); + + if let Some(socket) = &self.socket { + url.query_pairs_mut() + .append_pair("socket", &socket.to_string_lossy()); + } + + url + } } impl FromStr for MySqlConnectOptions { @@ -104,3 +163,16 @@ fn it_parses_password_with_non_ascii_chars_correctly() { assert_eq!(Some("p@ssw0rd".into()), opts.password); } + +#[test] +fn it_returns_the_parsed_url() { + let url = "mysql://username:p@ssw0rd@hostname:3306/database"; + let opts = MySqlConnectOptions::from_str(url).unwrap(); + + let mut expected_url = Url::parse(url).unwrap(); + // MySqlConnectOptions defaults + let query_string = "ssl-mode=PREFERRED&charset=utf8mb4&statement-cache-capacity=100"; + expected_url.set_query(Some(query_string)); + + assert_eq!(expected_url, opts.build_url()); +} diff --git a/sqlx-postgres/src/options/connect.rs b/sqlx-postgres/src/options/connect.rs index f61909a9e8..bc6e4adce9 100644 --- a/sqlx-postgres/src/options/connect.rs +++ b/sqlx-postgres/src/options/connect.rs @@ -13,6 +13,10 @@ impl ConnectOptions for PgConnectOptions { Self::parse_from_url(url) } + fn to_url_lossy(&self) -> Url { + self.build_url() + } + fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized, diff --git a/sqlx-postgres/src/options/parse.rs b/sqlx-postgres/src/options/parse.rs index 4c5cf41c3e..559516c062 100644 --- a/sqlx-postgres/src/options/parse.rs +++ b/sqlx-postgres/src/options/parse.rs @@ -1,6 +1,6 @@ use crate::error::Error; -use crate::PgConnectOptions; -use sqlx_core::percent_encoding::percent_decode_str; +use crate::{PgConnectOptions, PgSslMode}; +use sqlx_core::percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use sqlx_core::Url; use std::net::IpAddr; use std::str::FromStr; @@ -108,6 +108,62 @@ impl PgConnectOptions { Ok(options) } + + pub(crate) fn build_url(&self) -> Url { + let host = match &self.socket { + Some(socket) => { + utf8_percent_encode(&*socket.to_string_lossy(), NON_ALPHANUMERIC).to_string() + } + None => self.host.to_owned(), + }; + + let mut url = Url::parse(&format!( + "postgres://{}@{}:{}", + self.username, host, self.port + )) + .expect("BUG: generated un-parseable URL"); + + if let Some(password) = &self.password { + let password = utf8_percent_encode(&password, NON_ALPHANUMERIC).to_string(); + let _ = url.set_password(Some(&password)); + } + + if let Some(database) = &self.database { + url.set_path(&database); + } + + let ssl_mode = match self.ssl_mode { + PgSslMode::Allow => "ALLOW", + PgSslMode::Disable => "DISABLED", + PgSslMode::Prefer => "PREFERRED", + PgSslMode::Require => "REQUIRED", + PgSslMode::VerifyCa => "VERIFY_CA", + PgSslMode::VerifyFull => "VERIFY_FULL", + }; + url.query_pairs_mut().append_pair("ssl-mode", ssl_mode); + + if let Some(ssl_root_cert) = &self.ssl_root_cert { + url.query_pairs_mut() + .append_pair("ssl-root-cert", &ssl_root_cert.to_string()); + } + + if let Some(ssl_client_cert) = &self.ssl_client_cert { + url.query_pairs_mut() + .append_pair("ssl-cert", &ssl_client_cert.to_string()); + } + + if let Some(ssl_client_key) = &self.ssl_client_key { + url.query_pairs_mut() + .append_pair("ssl-key", &ssl_client_key.to_string()); + } + + url.query_pairs_mut().append_pair( + "statement-cache-capacity", + &self.statement_cache_capacity.to_string(), + ); + + url + } } impl FromStr for PgConnectOptions { @@ -242,3 +298,31 @@ fn it_parses_sqlx_options_correctly() { opts.options ); } + +#[test] +fn it_returns_the_parsed_url_when_socket() { + let url = "postgres://username@%2Fvar%2Flib%2Fpostgres/database"; + let opts = PgConnectOptions::from_str(url).unwrap(); + + let mut expected_url = Url::parse(url).unwrap(); + // PgConnectOptions defaults + let query_string = "ssl-mode=PREFERRED&statement-cache-capacity=100"; + let port = 5432; + expected_url.set_query(Some(query_string)); + let _ = expected_url.set_port(Some(port)); + + assert_eq!(expected_url, opts.build_url()); +} + +#[test] +fn it_returns_the_parsed_url_when_host() { + let url = "postgres://username:p@ssw0rd@hostname:5432/database"; + let opts = PgConnectOptions::from_str(url).unwrap(); + + let mut expected_url = Url::parse(url).unwrap(); + // PgConnectOptions defaults + let query_string = "ssl-mode=PREFERRED&statement-cache-capacity=100"; + expected_url.set_query(Some(query_string)); + + assert_eq!(expected_url, opts.build_url()); +} diff --git a/sqlx-sqlite/src/options/connect.rs b/sqlx-sqlite/src/options/connect.rs index 5545cfa46c..309f2430e0 100644 --- a/sqlx-sqlite/src/options/connect.rs +++ b/sqlx-sqlite/src/options/connect.rs @@ -24,6 +24,10 @@ impl ConnectOptions for SqliteConnectOptions { Self::from_str(url.as_str()) } + fn to_url_lossy(&self) -> Url { + self.build_url() + } + fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized, diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index 33875720d4..ac45b84e35 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -211,6 +211,11 @@ impl SqliteConnectOptions { self } + /// Gets the current name of the database file. + pub fn get_filename(self) -> Cow<'static, Path> { + self.filename + } + /// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys). /// /// SQLx chooses to enable this by default so that foreign keys function as expected, diff --git a/sqlx-sqlite/src/options/parse.rs b/sqlx-sqlite/src/options/parse.rs index a2cab10b52..aab61b9b9c 100644 --- a/sqlx-sqlite/src/options/parse.rs +++ b/sqlx-sqlite/src/options/parse.rs @@ -1,10 +1,11 @@ use crate::error::Error; use crate::SqliteConnectOptions; -use percent_encoding::percent_decode_str; +use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use std::borrow::Cow; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; +use url::Url; // https://www.sqlite.org/uri.html @@ -111,6 +112,36 @@ impl SqliteConnectOptions { Ok(options) } + + pub(crate) fn build_url(&self) -> Url { + let filename = + utf8_percent_encode(&self.filename.to_string_lossy(), NON_ALPHANUMERIC).to_string(); + let mut url = + Url::parse(&format!("sqlite://{}", filename)).expect("BUG: generated un-parseable URL"); + + let mode = match (self.in_memory, self.create_if_missing, self.read_only) { + (true, _, _) => "memory", + (false, true, _) => "rwc", + (false, false, true) => "ro", + (false, false, false) => "rw", + }; + url.query_pairs_mut().append_pair("mode", mode); + + let cache = match self.shared_cache { + true => "shared", + false => "private", + }; + url.query_pairs_mut().append_pair("cache", cache); + + url.query_pairs_mut() + .append_pair("immutable", &self.immutable.to_string()); + + if let Some(vfs) = &self.vfs { + url.query_pairs_mut().append_pair("vfs", &vfs); + } + + url + } } impl FromStr for SqliteConnectOptions { @@ -169,3 +200,14 @@ fn test_parse_shared_in_memory() -> Result<(), Error> { Ok(()) } + +#[test] +fn it_returns_the_parsed_url() -> Result<(), Error> { + let url = "sqlite://test.db?mode=rw&cache=shared"; + let options: SqliteConnectOptions = url.parse()?; + + let expected_url = Url::parse(url).unwrap(); + assert_eq!(options.build_url(), expected_url); + + Ok(()) +} From bbfd0d711aa3bfa4123b08b03d60ff08cadbc7dc Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 6 Mar 2024 11:47:15 +0800 Subject: [PATCH 43/59] fix: AnyRow not support PgType::Varchar (#2976) --- sqlx-postgres/src/any.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-postgres/src/any.rs b/sqlx-postgres/src/any.rs index 137813f847..542e7f007b 100644 --- a/sqlx-postgres/src/any.rs +++ b/sqlx-postgres/src/any.rs @@ -178,7 +178,7 @@ impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo { PgType::Float4 => AnyTypeInfoKind::Real, PgType::Float8 => AnyTypeInfoKind::Double, PgType::Bytea => AnyTypeInfoKind::Blob, - PgType::Text => AnyTypeInfoKind::Text, + PgType::Text | PgType::Varchar => AnyTypeInfoKind::Text, PgType::DeclareWithName(UStr::Static("citext")) => AnyTypeInfoKind::Text, _ => { return Err(sqlx_core::Error::AnyDriverError( From 24be26216509fa0c9fa9febbbbc04597fbe669f8 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Tue, 5 Mar 2024 20:05:37 -0800 Subject: [PATCH 44/59] fix: restore `Migrator` to the public API --- sqlx-core/src/migrate/migrator.rs | 24 ++++++++++++++++++++---- sqlx-macros-core/src/migrate.rs | 5 ++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 9a4d7a7a9f..ba066c5055 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -5,11 +5,21 @@ use std::collections::{HashMap, HashSet}; use std::ops::Deref; use std::slice; +/// A resolved set of migrations, ready to be run. +/// +/// Can be constructed statically using `migrate!()` or at runtime using [`Migrator::new()`]. #[derive(Debug)] -#[doc(hidden)] +// Forbids `migrate!()` from constructing this: +// #[non_exhaustive] pub struct Migrator { + // NOTE: these fields are semver-exempt and may be changed or removed in any future version. + // These have to be public for `migrate!()` to be able to initialize them in an implicitly + // const-promotable context. A `const fn` constructor isn't implicitly const-promotable. + #[doc(hidden)] pub migrations: Cow<'static, [Migration]>, + #[doc(hidden)] pub ignore_missing: bool, + #[doc(hidden)] pub locking: bool, } @@ -33,6 +43,13 @@ fn validate_applied_migrations( } impl Migrator { + #[doc(hidden)] + pub const DEFAULT: Migrator = Migrator { + migrations: Cow::Borrowed(&[]), + ignore_missing: false, + locking: true, + }; + /// Creates a new instance with the given source. /// /// # Examples @@ -57,8 +74,7 @@ impl Migrator { { Ok(Self { migrations: Cow::Owned(source.resolve().await.map_err(MigrateError::Source)?), - ignore_missing: false, - locking: true, + ..Self::DEFAULT }) } @@ -68,7 +84,7 @@ impl Migrator { self } - /// Specify whether or not to lock database during migration. Defaults to `true`. + /// Specify whether or not to lock the database during migration. Defaults to `true`. /// /// ### Warning /// Disabling locking can lead to errors or data loss if multiple clients attempt to apply migrations simultaneously diff --git a/sqlx-macros-core/src/migrate.rs b/sqlx-macros-core/src/migrate.rs index 73766a2c39..7defc095bd 100644 --- a/sqlx-macros-core/src/migrate.rs +++ b/sqlx-macros-core/src/migrate.rs @@ -146,10 +146,9 @@ pub(crate) fn expand_migrator(path: &Path) -> crate::Result { Ok(quote! { ::sqlx::migrate::Migrator { migrations: ::std::borrow::Cow::Borrowed(&[ - #(#migrations),* + #(#migrations),* ]), - ignore_missing: false, - locking: true, + ..::sqlx::migrate::Migrator::DEFAULT } }) } From d005111494a5202d64d5c0442a542f94a2c1983c Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Wed, 6 Mar 2024 17:25:31 -0800 Subject: [PATCH 45/59] fix: better I/O errors when `migrate!()` cannot read a file --- sqlx-core/src/migrate/mod.rs | 3 + sqlx-core/src/migrate/source.rs | 148 ++++++++++++++++++++++---------- sqlx-macros-core/src/migrate.rs | 124 +++++++++++--------------- 3 files changed, 154 insertions(+), 121 deletions(-) diff --git a/sqlx-core/src/migrate/mod.rs b/sqlx-core/src/migrate/mod.rs index a1095fb8be..f035b8d3c1 100644 --- a/sqlx-core/src/migrate/mod.rs +++ b/sqlx-core/src/migrate/mod.rs @@ -12,3 +12,6 @@ pub use migration::{AppliedMigration, Migration}; pub use migration_type::MigrationType; pub use migrator::Migrator; pub use source::MigrationSource; + +#[doc(hidden)] +pub use source::resolve_blocking; diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index 7966dd1273..b5e4b816c7 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -1,10 +1,11 @@ use crate::error::BoxDynError; -use crate::fs; use crate::migrate::{Migration, MigrationType}; use futures_core::future::BoxFuture; use std::borrow::Cow; use std::fmt::Debug; +use std::fs; +use std::io; use std::path::{Path, PathBuf}; /// In the default implementation, a MigrationSource is a directory which @@ -28,51 +29,11 @@ pub trait MigrationSource<'s>: Debug { impl<'s> MigrationSource<'s> for &'s Path { fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { Box::pin(async move { - let mut s = fs::read_dir(self.canonicalize()?).await?; - let mut migrations = Vec::new(); - - while let Some(entry) = s.next().await? { - // std::fs::metadata traverses symlinks - if !std::fs::metadata(&entry.path)?.is_file() { - // not a file; ignore - continue; - } - - let file_name = entry.file_name.to_string_lossy(); - - let parts = file_name.splitn(2, '_').collect::>(); - - if parts.len() != 2 || !parts[1].ends_with(".sql") { - // not of the format: _.sql; ignore - continue; - } - - let version: i64 = parts[0].parse() - .map_err(|_e| { - format!("error parsing migration filename {file_name:?}; expected integer version prefix (e.g. `01_foo.sql`)") - })?; - - let migration_type = MigrationType::from_filename(parts[1]); - // remove the `.sql` and replace `_` with ` ` - let description = parts[1] - .trim_end_matches(migration_type.suffix()) - .replace('_', " ") - .to_owned(); - - let sql = fs::read_to_string(&entry.path).await?; - - migrations.push(Migration::new( - version, - Cow::Owned(description), - migration_type, - Cow::Owned(sql), - )); - } - - // ensure that we are sorted by `VERSION ASC` - migrations.sort_by_key(|m| m.version); - - Ok(migrations) + let canonical = self.canonicalize()?; + let migrations_with_paths = + crate::rt::spawn_blocking(move || resolve_blocking(canonical)).await?; + + Ok(migrations_with_paths.into_iter().map(|(m, _p)| m).collect()) }) } } @@ -82,3 +43,98 @@ impl MigrationSource<'static> for PathBuf { Box::pin(async move { self.as_path().resolve().await }) } } + +#[derive(thiserror::Error, Debug)] +#[error("{message}")] +pub struct ResolveError { + message: String, + #[source] + source: Option, +} + +// FIXME: paths should just be part of `Migration` but we can't add a field backwards compatibly +// since it's `#[non_exhaustive]`. +pub fn resolve_blocking(path: PathBuf) -> Result, ResolveError> { + let mut s = fs::read_dir(&path).map_err(|e| ResolveError { + message: format!("error reading migration directory {}: {e}", path.display()), + source: Some(e), + })?; + + let mut migrations = Vec::new(); + + while let Some(res) = s.next() { + let entry = res.map_err(|e| ResolveError { + message: format!( + "error reading contents of migration directory {}: {e}", + path.display() + ), + source: Some(e), + })?; + + let entry_path = entry.path(); + + let metadata = fs::metadata(&entry_path).map_err(|e| ResolveError { + message: format!( + "error getting metadata of migration path {}", + entry_path.display() + ), + source: Some(e), + })?; + + if !metadata.is_file() { + // not a file; ignore + continue; + } + + let file_name = entry.file_name(); + // This is arguably the wrong choice, + // but it really only matters for parsing the version and description. + // + // Using `.to_str()` and returning an error if the filename is not UTF-8 + // would be a breaking change. + let file_name = file_name.to_string_lossy(); + + let parts = file_name.splitn(2, '_').collect::>(); + + if parts.len() != 2 || !parts[1].ends_with(".sql") { + // not of the format: _.sql; ignore + continue; + } + + let version: i64 = parts[0].parse() + .map_err(|_e| ResolveError { + message: format!("error parsing migration filename {file_name:?}; expected integer version prefix (e.g. `01_foo.sql`)"), + source: None, + })?; + + let migration_type = MigrationType::from_filename(parts[1]); + // remove the `.sql` and replace `_` with ` ` + let description = parts[1] + .trim_end_matches(migration_type.suffix()) + .replace('_', " ") + .to_owned(); + + let sql = fs::read_to_string(&entry_path).map_err(|e| ResolveError { + message: format!( + "error reading contents of migration {}: {e}", + entry_path.display() + ), + source: Some(e), + })?; + + migrations.push(( + Migration::new( + version, + Cow::Owned(description), + migration_type, + Cow::Owned(sql), + ), + entry_path, + )); + } + + // Ensure that we are sorted by version in ascending order. + migrations.sort_by_key(|(m, _)| m.version); + + Ok(migrations) +} diff --git a/sqlx-macros-core/src/migrate.rs b/sqlx-macros-core/src/migrate.rs index 7defc095bd..ff0eb9f937 100644 --- a/sqlx-macros-core/src/migrate.rs +++ b/sqlx-macros-core/src/migrate.rs @@ -1,17 +1,17 @@ #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] extern crate proc_macro; +use std::path::{Path, PathBuf}; + use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; -use sha2::{Digest, Sha384}; -use sqlx_core::migrate::MigrationType; -use std::fs; -use std::path::Path; use syn::LitStr; -pub struct QuotedMigrationType(MigrationType); +use sqlx_core::migrate::{Migration, MigrationType}; + +pub struct QuoteMigrationType(MigrationType); -impl ToTokens for QuotedMigrationType { +impl ToTokens for QuoteMigrationType { fn to_tokens(&self, tokens: &mut TokenStream) { let ts = match self.0 { MigrationType::Simple => quote! { ::sqlx::migrate::MigrationType::Simple }, @@ -24,31 +24,51 @@ impl ToTokens for QuotedMigrationType { } } -struct QuotedMigration { - version: i64, - description: String, - migration_type: QuotedMigrationType, - path: String, - checksum: Vec, +struct QuoteMigration { + migration: Migration, + path: PathBuf, } -impl ToTokens for QuotedMigration { +impl ToTokens for QuoteMigration { fn to_tokens(&self, tokens: &mut TokenStream) { - let QuotedMigration { + let Migration { version, description, migration_type, - path, checksum, - } = &self; + .. + } = &self.migration; + + let migration_type = QuoteMigrationType(*migration_type); + + let sql = self + .path + .canonicalize() + .map_err(|e| { + format!( + "error canonicalizing migration path {}: {e}", + self.path.display() + ) + }) + .and_then(|path| { + let path_str = path.to_str().ok_or_else(|| { + format!( + "migration path cannot be represented as a string: {}", + self.path.display() + ) + })?; + + // this tells the compiler to watch this path for changes + Ok(quote! { include_str!(#path_str) }) + }) + .unwrap_or_else(|e| quote! { compile_error!(#e) }); let ts = quote! { ::sqlx::migrate::Migration { version: #version, description: ::std::borrow::Cow::Borrowed(#description), migration_type: #migration_type, - // this tells the compiler to watch this path for changes - sql: ::std::borrow::Cow::Borrowed(include_str!(#path)), + sql: ::std::borrow::Cow::Borrowed(#sql), checksum: ::std::borrow::Cow::Borrowed(&[ #(#checksum),* ]), @@ -59,7 +79,6 @@ impl ToTokens for QuotedMigration { } } -// mostly copied from sqlx-core/src/migrate/source.rs pub fn expand_migrator_from_lit_dir(dir: LitStr) -> crate::Result { expand_migrator_from_dir(&dir.value(), dir.span()) } @@ -74,65 +93,20 @@ pub(crate) fn expand_migrator_from_dir( } pub(crate) fn expand_migrator(path: &Path) -> crate::Result { - let mut migrations = Vec::new(); - - for entry in fs::read_dir(&path)? { - let entry = entry?; - if !fs::metadata(entry.path())?.is_file() { - // not a file; ignore - continue; - } - - let file_name = entry.file_name(); - let file_name = file_name.to_string_lossy(); - - let parts = file_name.splitn(2, '_').collect::>(); - - if parts.len() != 2 || !parts[1].ends_with(".sql") { - // not of the format: _.sql; ignore - continue; - } - - let version: i64 = parts[0].parse()?; - - let migration_type = MigrationType::from_filename(parts[1]); - // remove the `.sql` and replace `_` with ` ` - let description = parts[1] - .trim_end_matches(migration_type.suffix()) - .replace('_', " ") - .to_owned(); - - let sql = fs::read_to_string(&entry.path())?; - - let checksum = Vec::from(Sha384::digest(sql.as_bytes()).as_slice()); - - // canonicalize the path so we can pass it to `include_str!()` - let path = entry.path().canonicalize()?; - let path = path - .to_str() - .ok_or_else(|| { - format!( - "migration path cannot be represented as a string: {:?}", - path - ) - })? - .to_owned(); - - migrations.push(QuotedMigration { - version, - description, - migration_type: QuotedMigrationType(migration_type), - path, - checksum, - }) - } - - // ensure that we are sorted by `VERSION ASC` - migrations.sort_by_key(|m| m.version); + let path = path.canonicalize().map_err(|e| { + format!( + "error canonicalizing migration directory {}: {e}", + path.display() + ) + })?; + + // Use the same code path to resolve migrations at compile time and runtime. + let migrations = sqlx_core::migrate::resolve_blocking(path)? + .into_iter() + .map(|(migration, path)| QuoteMigration { migration, path }); #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] { - let path = path.canonicalize()?; let path = path.to_str().ok_or_else(|| { format!( "migration directory path cannot be represented as a string: {:?}", From 248d6170a72a8a1c38cc5578888a669f79fdb673 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 11 Mar 2024 18:46:53 -0700 Subject: [PATCH 46/59] chore: prepare 0.7.4 release --- CHANGELOG.md | 126 +++++ Cargo.lock | 1008 ++++++++++++++++----------------- Cargo.toml | 18 +- sqlx-core/src/query.rs | 4 +- sqlx-core/src/query_as.rs | 2 +- sqlx-core/src/query_scalar.rs | 2 +- 6 files changed, 638 insertions(+), 522 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a843b73bd..739d4d2aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,113 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.7.4 - 2024-03-11 + +38 pull requests were merged this release cycle. + +This is officially the **last** release of the 0.7.x release cycle. + +As of this release, development of 0.8.0 has begun on `main` and only high-priority bugfixes may be backported. + +### Added + +* [[#2891]]: feat: expose getters for connect options fields [[@saiintbrisson]] +* [[#2902]]: feat: add `to_url_lossy` to connect options [[@lily-mosquitoes]] +* [[#2927]]: Support `query!` for cargo-free systems [[@kshramt]] +* [[#2997]]: doc(FAQ): add entry explaining prepared statements [[@abonander]] +* [[#3001]]: Update README to clarify MariaDB support [[@iangilfillan]] +* [[#3004]]: feat(logging): Add numeric elapsed time field elapsed_secs [[@iamjpotts]] +* [[#3007]]: feat: add `raw_sql` API [[@abonander]] + * This hopefully makes it easier to find how to execute statements which are not supported by the default + prepared statement interfaces `query*()` and `query!()`. + * Improved documentation across the board for the `query*()` functions. + * Deprecated: `execute_many()` and `fetch_many()` on interfaces that use prepared statements. + * Multiple SQL statements in one query string were only supported by SQLite because its prepared statement + interface is the *only* way to execute SQL. All other database flavors forbid multiple statements in + one prepared statement string as an extra defense against SQL injection. + * The new `raw_sql` API retains this functionality because it explicitly does *not* use prepared statements. + Raw or text-mode query interfaces generally allow multiple statements in one query string, and this is + supported by all current databases. Due to their nature, however, one cannot use bind parameters with them. + * If this change affects you, an issue is open for discussion: https://github.com/launchbadge/sqlx/issues/3108 +* [[#3011]]: Added support to IpAddr with MySQL/MariaDB. [[@Icerath]] +* [[#3013]]: Add default implementation for PgInterval [[@pawurb]] +* [[#3018]]: Add default implementation for PgMoney [[@pawurb]] +* [[#3026]]: Update docs to reflect support for MariaDB data types [[@iangilfillan]] +* [[#3037]]: feat(mysql): allow to connect with mysql driver without default behavor [[@darkecho731]] + +### Changed + +* [[#2900]]: Show latest url to docs for macro.migrate [[@Vrajs16]] +* [[#2914]]: Use `create_new` instead of `atomic-file-write` [[@mattfbacon]] +* [[#2926]]: docs: update example for `PgConnectOptions` [[@Fyko]] +* [[#2989]]: sqlx-core: Remove dotenvy dependency [[@joshtriplett]] +* [[#2996]]: chore: Update ahash to 0.8.7 [[@takenoko-gohan]] +* [[#3006]]: chore(deps): Replace unmaintained tempdir crate with tempfile [[@iamjpotts]] +* [[#3008]]: chore: Ignore .sqlx folder created by running ci steps locally [[@iamjpotts]] +* [[#3009]]: chore(dev-deps): Upgrade env_logger from 0.9 to 0.11 [[@iamjpotts]] +* [[#3010]]: chore(deps): Upgrade criterion to 0.5.1 [[@iamjpotts]] +* [[#3050]]: Optimize SASL auth in sqlx-postgres [[@mirek26]] +* [[#3055]]: Set TCP_NODELAY option on TCP sockets [[@mirek26]] +* [[#3065]]: Improve max_lifetime handling [[@mirek26]] +* [[#3072]]: Change the name of "inner" function generated by `#[sqlx::test]` [[@ciffelia]] +* [[#3083]]: Remove sha1 because it's not being used in postgres [[@rafaelGuerreiro]] + +### Fixed + +* [[#2898]]: Fixed docs [[@Vrajs16]] +* [[#2905]]: fix(mysql): Close prepared statement if persistence is disabled [[@larsschumacher]] +* [[#2913]]: Fix handling of deferred constraints [[@Thomasdezeeuw]] +* [[#2919]]: fix duplicate "`" in FromRow "default" attribute doc comment [[@shengsheng]] +* [[#2932]]: fix(postgres): avoid unnecessary flush in PgCopyIn::read_from [[@tsing]] +* [[#2955]]: Minor fixes [[@Dawsoncodes]] +* [[#2963]]: Fixed ReadMe badge styling [[@tadghh]] +* [[#2976]]: fix: AnyRow not support PgType::Varchar [[@holicc]] +* [[#3053]]: fix: do not panic when binding a large BigDecimal [[@Ekleog]] +* [[#3056]]: fix: spans in sqlite tracing (#2876) [[@zoomiti]] +* [[#3089]]: fix(migrate): improve error message when parsing version from filename [[@abonander]] +* [[#3098]]: Migrations fixes [[@abonander]] + * Unhides `sqlx::migrate::Migrator`. + * Improves I/O error message when failing to read a file in `migrate!()`. + +[#2891]: https://github.com/launchbadge/sqlx/pull/2891 +[#2898]: https://github.com/launchbadge/sqlx/pull/2898 +[#2900]: https://github.com/launchbadge/sqlx/pull/2900 +[#2902]: https://github.com/launchbadge/sqlx/pull/2902 +[#2905]: https://github.com/launchbadge/sqlx/pull/2905 +[#2913]: https://github.com/launchbadge/sqlx/pull/2913 +[#2914]: https://github.com/launchbadge/sqlx/pull/2914 +[#2919]: https://github.com/launchbadge/sqlx/pull/2919 +[#2926]: https://github.com/launchbadge/sqlx/pull/2926 +[#2927]: https://github.com/launchbadge/sqlx/pull/2927 +[#2932]: https://github.com/launchbadge/sqlx/pull/2932 +[#2955]: https://github.com/launchbadge/sqlx/pull/2955 +[#2963]: https://github.com/launchbadge/sqlx/pull/2963 +[#2976]: https://github.com/launchbadge/sqlx/pull/2976 +[#2989]: https://github.com/launchbadge/sqlx/pull/2989 +[#2996]: https://github.com/launchbadge/sqlx/pull/2996 +[#2997]: https://github.com/launchbadge/sqlx/pull/2997 +[#3001]: https://github.com/launchbadge/sqlx/pull/3001 +[#3004]: https://github.com/launchbadge/sqlx/pull/3004 +[#3006]: https://github.com/launchbadge/sqlx/pull/3006 +[#3007]: https://github.com/launchbadge/sqlx/pull/3007 +[#3008]: https://github.com/launchbadge/sqlx/pull/3008 +[#3009]: https://github.com/launchbadge/sqlx/pull/3009 +[#3010]: https://github.com/launchbadge/sqlx/pull/3010 +[#3011]: https://github.com/launchbadge/sqlx/pull/3011 +[#3013]: https://github.com/launchbadge/sqlx/pull/3013 +[#3018]: https://github.com/launchbadge/sqlx/pull/3018 +[#3026]: https://github.com/launchbadge/sqlx/pull/3026 +[#3037]: https://github.com/launchbadge/sqlx/pull/3037 +[#3050]: https://github.com/launchbadge/sqlx/pull/3050 +[#3053]: https://github.com/launchbadge/sqlx/pull/3053 +[#3055]: https://github.com/launchbadge/sqlx/pull/3055 +[#3056]: https://github.com/launchbadge/sqlx/pull/3056 +[#3065]: https://github.com/launchbadge/sqlx/pull/3065 +[#3072]: https://github.com/launchbadge/sqlx/pull/3072 +[#3083]: https://github.com/launchbadge/sqlx/pull/3083 +[#3089]: https://github.com/launchbadge/sqlx/pull/3089 +[#3098]: https://github.com/launchbadge/sqlx/pull/3098 + ## 0.7.3 - 2023-11-22 38 pull requests were merged this release cycle. @@ -2135,3 +2242,22 @@ Fix docs.rs build by enabling a runtime feature in the docs.rs metadata in `Carg [@Vrajs16]: https://github.com/Vrajs16 [@shiftrightonce]: https://github.com/shiftrightonce [@tamasfe]: https://github.com/tamasfe +[@lily-mosquitoes]: https://github.com/lily-mosquitoes +[@larsschumacher]: https://github.com/larsschumacher +[@shengsheng]: https://github.com/shengsheng +[@Fyko]: https://github.com/Fyko +[@kshramt]: https://github.com/kshramt +[@Dawsoncodes]: https://github.com/Dawsoncodes +[@tadghh]: https://github.com/tadghh +[@holicc]: https://github.com/holicc +[@takenoko-gohan]: https://github.com/takenoko-gohan +[@iangilfillan]: https://github.com/iangilfillan +[@iamjpotts]: https://github.com/iamjpotts +[@Icerath]: https://github.com/Icerath +[@pawurb]: https://github.com/pawurb +[@darkecho731]: https://github.com/darkecho731 +[@mirek26]: https://github.com/mirek26 +[@Ekleog]: https://github.com/Ekleog +[@zoomiti]: https://github.com/zoomiti +[@ciffelia]: https://github.com/ciffelia +[@rafaelGuerreiro]: https://github.com/rafaelGuerreiro diff --git a/Cargo.lock b/Cargo.lock index 9166c1b9d5..82ef2a2489 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -102,43 +102,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "argon2" @@ -159,14 +159,14 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr", "doc-comment", - "predicates 3.0.4", + "predicates 3.1.0", "predicates-core", "predicates-tree", "wait-timeout", @@ -195,43 +195,43 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 3.1.0", - "event-listener-strategy", + "event-listener 5.2.0", + "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock 3.1.1", + "async-lock 3.3.0", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite 2.0.1", + "futures-lite 2.2.0", "slab", ] [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 1.9.0", + "async-channel 2.2.0", "async-executor", - "async-io", - "async-lock 2.8.0", + "async-io 2.3.2", + "async-lock 3.3.0", "blocking", - "futures-lite 1.13.0", + "futures-lite 2.2.0", "once_cell", ] @@ -248,13 +248,32 @@ dependencies = [ "futures-lite 1.13.0", "log", "parking", - "polling", + "polling 2.8.0", "rustix 0.37.27", "slab", "socket2 0.4.10", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.2.0", + "parking", + "polling 3.5.0", + "rustix 0.38.31", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -266,12 +285,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.1.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ - "event-listener 3.1.0", - "event-listener-strategy", + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -284,7 +303,7 @@ dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io", + "async-io 1.13.0", "async-lock 2.8.0", "crossbeam-utils", "futures-channel", @@ -304,19 +323,19 @@ dependencies = [ [[package]] name = "async-task" -version = "4.5.0" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -448,9 +467,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -460,9 +479,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" dependencies = [ "serde", ] @@ -492,9 +511,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" dependencies = [ "serde", ] @@ -535,21 +554,21 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel 2.1.0", - "async-lock 3.1.1", + "async-channel 2.2.0", + "async-lock 3.3.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite 2.0.1", + "futures-lite 2.2.0", "piper", "tracing", ] [[package]] name = "borsh" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" dependencies = [ "borsh-derive", "cfg_aliases", @@ -557,23 +576,23 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f404657a7ea7b5249e36808dff544bc88a28f26e0ac40009f674b7a009d14be3" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "syn_derive", ] [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "regex-automata", @@ -582,15 +601,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -599,9 +618,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", @@ -631,9 +650,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" dependencies = [ "serde", ] @@ -665,12 +684,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -686,22 +702,22 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -710,15 +726,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -741,9 +757,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -751,42 +767,42 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.10.0", + "strsim 0.11.0", ] [[package]] name = "clap_complete" -version = "4.4.4" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ - "clap 4.4.8", + "clap 4.5.2", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" @@ -807,37 +823,37 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -845,15 +861,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -882,7 +898,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.8", + "clap 4.5.2", "criterion-plot", "futures", "is-terminal", @@ -913,46 +929,37 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" @@ -979,6 +986,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -991,9 +1004,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -1001,27 +1014,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1037,9 +1050,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -1104,9 +1117,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" dependencies = [ "serde", ] @@ -1144,9 +1157,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ "anstream", "anstyle", @@ -1200,9 +1213,20 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" dependencies = [ "concurrent-queue", "parking", @@ -1211,11 +1235,21 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.3.0" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" dependencies = [ - "event-listener 3.1.0", + "event-listener 5.2.0", "pin-project-lite", ] @@ -1241,20 +1275,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.38.30", + "rustix 0.38.31", "windows-sys 0.48.0", ] [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall", + "windows-sys 0.52.0", ] [[package]] @@ -1327,9 +1361,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1342,9 +1376,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1352,15 +1386,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1380,9 +1414,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -1401,46 +1435,45 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ "fastrand 2.0.1", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1466,9 +1499,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1477,9 +1510,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1501,9 +1534,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1511,16 +1548,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "allocator-api2", ] @@ -1530,7 +1567,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1562,9 +1599,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1574,9 +1611,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1592,18 +1629,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1612,9 +1649,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1647,9 +1684,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1661,7 +1698,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -1670,9 +1707,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1736,12 +1773,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1759,7 +1796,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -1775,12 +1812,12 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.3", - "rustix 0.38.30", + "hermit-abi 0.3.9", + "libc", "windows-sys 0.52.0", ] @@ -1795,24 +1832,24 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1837,9 +1874,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -1853,9 +1890,9 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", - "redox_syscall 0.4.1", + "redox_syscall", ] [[package]] @@ -1893,9 +1930,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -1928,9 +1965,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -1941,15 +1978,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -1964,18 +1992,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -2047,7 +2075,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -2094,21 +2122,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -2117,9 +2150,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2131,24 +2164,24 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -2158,11 +2191,11 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -2179,7 +2212,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2190,18 +2223,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.6+3.1.4" +version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2234,7 +2267,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -2273,22 +2306,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2337,9 +2370,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" @@ -2385,6 +2418,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.31", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2413,13 +2460,12 @@ dependencies = [ [[package]] name = "predicates" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", - "itertools 0.11.0", "predicates-core", ] @@ -2441,9 +2487,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] @@ -2474,9 +2520,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2512,9 +2558,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2576,9 +2622,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -2586,23 +2632,14 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -2625,9 +2662,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -2637,9 +2674,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2654,35 +2691,37 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rend" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "ring" -version = "0.17.5" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rkyv" -version = "0.7.42" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" dependencies = [ "bitvec", "bytecheck", + "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -2694,9 +2733,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.42" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ "proc-macro2", "quote", @@ -2705,9 +2744,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -2725,9 +2764,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.33.1" +version = "1.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" +checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" dependencies = [ "arrayvec", "borsh", @@ -2761,11 +2800,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys 0.4.13", @@ -2774,9 +2813,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "ring", "rustls-webpki", @@ -2789,7 +2828,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -2828,9 +2867,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2843,11 +2882,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2897,38 +2936,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -2972,7 +3011,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3054,9 +3093,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -3070,12 +3109,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3095,9 +3134,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -3105,18 +3144,18 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.11.0", + "itertools 0.12.1", "nom", "unicode_categories", ] [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "async-std", @@ -3146,7 +3185,7 @@ dependencies = [ [[package]] name = "sqlx-cli" -version = "0.7.3" +version = "0.7.4" dependencies = [ "anyhow", "assert_cmd", @@ -3154,7 +3193,7 @@ dependencies = [ "backoff", "cargo_metadata", "chrono", - "clap 4.4.8", + "clap 4.5.2", "clap_complete", "console", "dotenvy", @@ -3172,10 +3211,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" dependencies = [ - "ahash 0.8.7", - "async-io", + "ahash 0.8.11", + "async-io 1.13.0", "async-std", "atoi", "bigdecimal", @@ -3197,7 +3236,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.1.0", + "indexmap 2.2.5", "ipnetwork", "log", "mac_address", @@ -3356,7 +3395,7 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" dependencies = [ "proc-macro2", "quote", @@ -3367,7 +3406,7 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" dependencies = [ "async-std", "dotenvy", @@ -3392,12 +3431,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.21.7", "bigdecimal", - "bitflags 2.4.1", + "bitflags 2.4.2", "byteorder", "bytes", "chrono", @@ -3437,13 +3476,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.21.7", "bigdecimal", "bit-vec", - "bitflags 2.4.1", + "bitflags 2.4.2", "byteorder", "chrono", "crc", @@ -3482,7 +3521,7 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" dependencies = [ "atoi", "chrono", @@ -3547,6 +3586,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "structopt" version = "0.3.26" @@ -3590,9 +3635,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3608,7 +3653,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3625,22 +3670,21 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.4.1", - "rustix 0.38.30", + "rustix 0.38.31", "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -3662,32 +3706,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -3702,10 +3747,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -3736,9 +3782,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -3748,7 +3794,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros", "windows-sys 0.48.0", ] @@ -3761,7 +3807,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3783,11 +3829,11 @@ checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.5", "toml_datetime", "winnow", ] @@ -3859,7 +3905,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3873,15 +3919,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" dependencies = [ "basic-toml", "glob", @@ -3913,9 +3959,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3925,18 +3971,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -3981,9 +4027,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "serde", ] @@ -4032,9 +4078,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.4.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" +checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" [[package]] name = "vcpkg" @@ -4071,9 +4117,9 @@ checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -4094,11 +4140,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4106,24 +4158,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -4133,9 +4185,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4143,28 +4195,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -4172,15 +4224,19 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall", + "wasite", +] [[package]] name = "winapi" @@ -4215,20 +4271,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -4246,22 +4293,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -4281,25 +4313,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4308,15 +4334,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -4326,15 +4346,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -4344,15 +4358,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -4362,15 +4370,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -4380,15 +4382,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -4398,15 +4394,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -4416,15 +4406,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -4455,7 +4445,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e765d06371..3602792d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "sqlx-macros-core", "sqlx-test", "sqlx-cli", -# "sqlx-bench", + # "sqlx-bench", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.7.3" +version = "0.7.4" license = "MIT OR Apache-2.0" edition = "2021" repository = "https://github.com/launchbadge/sqlx" @@ -114,17 +114,17 @@ regexp = ["sqlx-sqlite?/regexp"] [workspace.dependencies] # Core Crates -sqlx-core = { version = "=0.7.3", path = "sqlx-core" } -sqlx-macros-core = { version = "=0.7.3", path = "sqlx-macros-core" } -sqlx-macros = { version = "=0.7.3", path = "sqlx-macros" } +sqlx-core = { version = "=0.7.4", path = "sqlx-core" } +sqlx-macros-core = { version = "=0.7.4", path = "sqlx-macros-core" } +sqlx-macros = { version = "=0.7.4", path = "sqlx-macros" } # Driver crates -sqlx-mysql = { version = "=0.7.3", path = "sqlx-mysql" } -sqlx-postgres = { version = "=0.7.3", path = "sqlx-postgres" } -sqlx-sqlite = { version = "=0.7.3", path = "sqlx-sqlite" } +sqlx-mysql = { version = "=0.7.4", path = "sqlx-mysql" } +sqlx-postgres = { version = "=0.7.4", path = "sqlx-postgres" } +sqlx-sqlite = { version = "=0.7.4", path = "sqlx-sqlite" } # Facade crate (for reference from sqlx-cli) -sqlx = { version = "=0.7.3", path = ".", default-features = false } +sqlx = { version = "=0.7.4", path = ".", default-features = false } # Common type integrations shared by multiple driver crates. # These are optional unless enabled in a workspace crate. diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 4403fae006..005c0eb488 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -159,7 +159,7 @@ where /// Execute multiple queries and return the rows affected from each query, in a stream. #[inline] - #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub async fn execute_many<'e, 'c: 'e, E>( self, executor: E, @@ -188,7 +188,7 @@ where /// For each query in the stream, any generated rows are returned first, /// then the `QueryResult` with the number of rows affected. #[inline] - #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] // TODO: we'll probably still want a way to get the `DB::QueryResult` at the end of a `fetch()` stream. pub fn fetch_many<'e, 'c: 'e, E>( self, diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 3877c4e401..0ec714c7bf 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -104,7 +104,7 @@ where /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. - #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs index 395278a8d9..9280056e37 100644 --- a/sqlx-core/src/query_scalar.rs +++ b/sqlx-core/src/query_scalar.rs @@ -99,7 +99,7 @@ where /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. #[inline] - #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, From a2b89d70a7d0b6401f8d830c9e7ab1dff63e103f Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 11 Mar 2024 21:11:25 -0700 Subject: [PATCH 47/59] fix: deprecation warnings in `sqlite::types::chrono`, document `DATETIME` behavior --- sqlx-sqlite/src/types/chrono.rs | 4 ++-- sqlx-sqlite/src/types/mod.rs | 41 +++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/sqlx-sqlite/src/types/chrono.rs b/sqlx-sqlite/src/types/chrono.rs index 7b338aa214..0782f8a5d2 100644 --- a/sqlx-sqlite/src/types/chrono.rs +++ b/sqlx-sqlite/src/types/chrono.rs @@ -156,7 +156,7 @@ fn decode_datetime_from_text(value: &str) -> Option> { } fn decode_datetime_from_int(value: i64) -> Option> { - NaiveDateTime::from_timestamp_opt(value, 0).map(|dt| Utc.fix().from_utc_datetime(&dt)) + Utc.fix().timestamp_opt(value, 0).single() } fn decode_datetime_from_float(value: f64) -> Option> { @@ -166,7 +166,7 @@ fn decode_datetime_from_float(value: f64) -> Option> { let seconds = timestamp as i64; let nanos = (timestamp.fract() * 1E9) as u32; - NaiveDateTime::from_timestamp_opt(seconds, nanos).map(|dt| Utc.fix().from_utc_datetime(&dt)) + Utc.fix().timestamp_opt(seconds, nanos).single() } impl<'r> Decode<'r, Sqlite> for NaiveDateTime { diff --git a/sqlx-sqlite/src/types/mod.rs b/sqlx-sqlite/src/types/mod.rs index 54284c8ab1..0bc62737d3 100644 --- a/sqlx-sqlite/src/types/mod.rs +++ b/sqlx-sqlite/src/types/mod.rs @@ -37,11 +37,30 @@ //! //! | Rust type | Sqlite type(s) | //! |---------------------------------------|------------------------------------------------------| -//! | `chrono::NaiveDateTime` | DATETIME | -//! | `chrono::DateTime` | DATETIME | -//! | `chrono::DateTime` | DATETIME | -//! | `chrono::NaiveDate` | DATE | -//! | `chrono::NaiveTime` | TIME | +//! | `chrono::NaiveDateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::DateTime` | DATETIME (TEXT, INTEGER, REAL) | +//! | `chrono::NaiveDate` | DATE (TEXT only) | +//! | `chrono::NaiveTime` | TIME (TEXT only) | +//! +//! ##### NOTE: `DATETIME` conversions +//! SQLite may represent `DATETIME` values as one of three types: `TEXT`, `REAL`, or `INTEGER`. +//! Which one is used is entirely up to you and how you store timestamps in your database. +//! +//! The deserialization for `NaiveDateTime`, `DateTime` and `DateTime` infer the date +//! format from the type of the value they're being decoded from: +//! +//! * If `TEXT`, the format is assumed to be an ISO-8601 compatible datetime string. +//! A number of possible formats are tried; see `sqlx-sqlite/src/types/chrono.rs` for the current +//! set of formats. +//! * If `INTEGER`, it is expected to be the number of seconds since January 1, 1970 00:00 UTC, +//! as if returned from the `unixtime()` function (without the `subsec` modifier). +//! * If `REAL`, it is expected to be the (possibly fractional) number of days since the Julian epoch, +//! November 24, 4714 BCE 12:00 UTC, as if returned from the `julianday()` function. +//! +//! These types will always encode to a datetime string, either +//! with (`DateTime` for any `Tz: TimeZone`) or without (`NaiveDateTime`) a timezone offset. //! //! ### [`time`](https://crates.io/crates/time) //! @@ -49,10 +68,14 @@ //! //! | Rust type | Sqlite type(s) | //! |---------------------------------------|------------------------------------------------------| -//! | `time::PrimitiveDateTime` | DATETIME | -//! | `time::OffsetDateTime` | DATETIME | -//! | `time::Date` | DATE | -//! | `time::Time` | TIME | +//! | `time::PrimitiveDateTime` | DATETIME (TEXT, INTEGER) | +//! | `time::OffsetDateTime` | DATETIME (TEXT, INTEGER) | +//! | `time::Date` | DATE (TEXT only) | +//! | `time::Time` | TIME (TEXT only) | +//! +//! ##### NOTE: `DATETIME` conversions +//! The behavior here is identical to the corresponding `chrono` types, minus the support for `REAL` +//! values as Julian days (it's just not implemented). //! //! ### [`uuid`](https://crates.io/crates/uuid) //! From 635dba5b2682033101a1271e9fb4bf2516c0b840 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 11 Mar 2024 21:14:18 -0700 Subject: [PATCH 48/59] fix: deprecation in `postgres::types::chrono` --- sqlx-postgres/src/types/chrono/date.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sqlx-postgres/src/types/chrono/date.rs b/sqlx-postgres/src/types/chrono/date.rs index 4e968e1064..da10bd1ac7 100644 --- a/sqlx-postgres/src/types/chrono/date.rs +++ b/sqlx-postgres/src/types/chrono/date.rs @@ -1,10 +1,12 @@ +use std::mem; + +use chrono::{NaiveDate, TimeDelta}; + use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; -use chrono::{Duration, NaiveDate}; -use std::mem; impl Type for NaiveDate { fn type_info() -> PgTypeInfo { @@ -36,7 +38,13 @@ impl<'r> Decode<'r, Postgres> for NaiveDate { PgValueFormat::Binary => { // DATE is encoded as the days since epoch let days: i32 = Decode::::decode(value)?; - postgres_epoch_date() + Duration::days(days.into()) + + let days = TimeDelta::try_days(days.into()) + .unwrap_or_else(|| { + unreachable!("BUG: days ({days}) as `i32` multiplied into seconds should not overflow `i64`") + }); + + postgres_epoch_date() + days } PgValueFormat::Text => NaiveDate::parse_from_str(value.as_str()?, "%Y-%m-%d")?, From 30db1f8ffca9c0c4d48b736a4dd98548950c0e51 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Mon, 11 Mar 2024 22:38:24 -0700 Subject: [PATCH 49/59] chore: bump version to `0.8.0-alpha.0` --- Cargo.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3602792d12..e424748be0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.7.4" +version = "0.8.0-alpha.0" license = "MIT OR Apache-2.0" edition = "2021" repository = "https://github.com/launchbadge/sqlx" @@ -114,17 +114,17 @@ regexp = ["sqlx-sqlite?/regexp"] [workspace.dependencies] # Core Crates -sqlx-core = { version = "=0.7.4", path = "sqlx-core" } -sqlx-macros-core = { version = "=0.7.4", path = "sqlx-macros-core" } -sqlx-macros = { version = "=0.7.4", path = "sqlx-macros" } +sqlx-core = { version = "=0.8.0-alpha.0", path = "sqlx-core" } +sqlx-macros-core = { version = "=0.8.0-alpha.0", path = "sqlx-macros-core" } +sqlx-macros = { version = "=0.8.0-alpha.0", path = "sqlx-macros" } # Driver crates -sqlx-mysql = { version = "=0.7.4", path = "sqlx-mysql" } -sqlx-postgres = { version = "=0.7.4", path = "sqlx-postgres" } -sqlx-sqlite = { version = "=0.7.4", path = "sqlx-sqlite" } +sqlx-mysql = { version = "=0.8.0-alpha.0", path = "sqlx-mysql" } +sqlx-postgres = { version = "=0.8.0-alpha.0", path = "sqlx-postgres" } +sqlx-sqlite = { version = "=0.8.0-alpha.0", path = "sqlx-sqlite" } # Facade crate (for reference from sqlx-cli) -sqlx = { version = "=0.7.4", path = ".", default-features = false } +sqlx = { version = "=0.8.0-alpha.0", path = ".", default-features = false } # Common type integrations shared by multiple driver crates. # These are optional unless enabled in a workspace crate. From f316b0d435b60941eec515be5f7600ebf0b8ec9c Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Thu, 17 Aug 2023 18:48:48 -0300 Subject: [PATCH 50/59] fix(macros): only enable chrono when time is disabled --- sqlx-macros-core/src/database/postgres.rs | 32 +++++++++++------------ sqlx-macros-core/src/database/sqlite.rs | 6 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/sqlx-macros-core/src/database/postgres.rs b/sqlx-macros-core/src/database/postgres.rs index 0bd6b4c856..621473d44b 100644 --- a/sqlx-macros-core/src/database/postgres.rs +++ b/sqlx-macros-core/src/database/postgres.rs @@ -26,19 +26,19 @@ impl_database_ext! { #[cfg(feature = "uuid")] sqlx::types::Uuid, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::NaiveTime, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::NaiveDate, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::NaiveDateTime, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::DateTime | sqlx::types::chrono::DateTime<_>, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::postgres::types::PgTimeTz, #[cfg(feature = "time")] @@ -91,16 +91,16 @@ impl_database_ext! { #[cfg(feature = "uuid")] Vec | &[sqlx::types::Uuid], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec | &[sqlx::types::chrono::NaiveTime], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec | &[sqlx::types::chrono::NaiveDate], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec | &[sqlx::types::chrono::NaiveDateTime], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec> | &[sqlx::types::chrono::DateTime<_>], #[cfg(feature = "time")] @@ -141,13 +141,13 @@ impl_database_ext! { #[cfg(feature = "rust_decimal")] sqlx::postgres::types::PgRange, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::postgres::types::PgRange, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::postgres::types::PgRange, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::postgres::types::PgRange> | sqlx::postgres::types::PgRange>, @@ -173,19 +173,19 @@ impl_database_ext! { Vec> | &[sqlx::postgres::types::PgRange], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec> | &[sqlx::postgres::types::PgRange], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec> | &[sqlx::postgres::types::PgRange], - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] Vec>> | Vec>>, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] &[sqlx::postgres::types::PgRange>] | &[sqlx::postgres::types::PgRange>], diff --git a/sqlx-macros-core/src/database/sqlite.rs b/sqlx-macros-core/src/database/sqlite.rs index 3c4c440219..00bdd4201b 100644 --- a/sqlx-macros-core/src/database/sqlite.rs +++ b/sqlx-macros-core/src/database/sqlite.rs @@ -12,13 +12,13 @@ impl_database_ext! { String, Vec, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::NaiveDate, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::NaiveDateTime, - #[cfg(feature = "chrono")] + #[cfg(all(feature = "chrono", not(feature = "time")))] sqlx::types::chrono::DateTime | sqlx::types::chrono::DateTime<_>, #[cfg(feature = "time")] From c5357f18e5cffeea9fe1375c21b05112e4c75dbb Mon Sep 17 00:00:00 2001 From: Guilherme Favero Ferreira Date: Tue, 29 Aug 2023 21:18:45 -0300 Subject: [PATCH 51/59] bump bigdecimal version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e424748be0..281ed9b8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,7 @@ sqlx = { version = "=0.8.0-alpha.0", path = ".", default-features = false } # Common type integrations shared by multiple driver crates. # These are optional unless enabled in a workspace crate. -bigdecimal = "0.3.0" +bigdecimal = "0.4.0" bit-vec = "0.6.3" chrono = { version = "0.4.22", default-features = false } ipnetwork = "0.20.0" From 8f926e590cce1fb3a0d4120f215c771ddd88a084 Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Tue, 12 Mar 2024 19:58:21 -0300 Subject: [PATCH 52/59] refac: rebase syn 2 changes --- Cargo.lock | 30 ++-- sqlx-macros-core/Cargo.toml | 7 +- sqlx-macros-core/src/database/mod.rs | 1 + sqlx-macros-core/src/derives/attributes.rs | 172 ++++++++------------- sqlx-macros-core/src/derives/encode.rs | 4 +- sqlx-macros-core/src/derives/mod.rs | 1 - sqlx-macros-core/src/query/args.rs | 85 +++------- sqlx-macros-core/src/test_attr.rs | 171 ++++++++++---------- sqlx-macros/Cargo.toml | 4 +- sqlx-macros/src/lib.rs | 3 +- 10 files changed, 203 insertions(+), 275 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82ef2a2489..130a38c602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,10 +488,12 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.3.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +checksum = "9324c8014cd04590682b34f1e9448d38f0674d0f7b2dc553331016ef0e4e9ebc" dependencies = [ + "autocfg", + "libm", "num-bigint", "num-integer", "num-traits", @@ -2520,9 +2522,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -3155,7 +3157,7 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "anyhow", "async-std", @@ -3185,7 +3187,7 @@ dependencies = [ [[package]] name = "sqlx-cli" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "anyhow", "assert_cmd", @@ -3211,7 +3213,7 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "ahash 0.8.11", "async-io 1.13.0", @@ -3395,18 +3397,18 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "async-std", "dotenvy", @@ -3423,7 +3425,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.52", "tempfile", "tokio", "url", @@ -3431,7 +3433,7 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "atoi", "base64 0.21.7", @@ -3476,7 +3478,7 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "atoi", "base64 0.21.7", @@ -3521,7 +3523,7 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.0-alpha.0" dependencies = [ "atoi", "chrono", diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index 48e8d26491..effbd687c6 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -52,12 +52,11 @@ hex = { version = "0.4.3" } heck = { version = "0.4", features = ["unicode"] } either = "1.6.1" once_cell = "1.9.0" -proc-macro2 = { version = "1.0.36", default-features = false } +proc-macro2 = { version = "1.0.79", default-features = false } serde = { version = "1.0.132", features = ["derive"] } serde_json = { version = "1.0.73" } sha2 = { version = "0.10.0" } -syn = { version = "1.0.84", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } +syn = { version = "2.0.52", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } tempfile = { version = "3.3.0" } -quote = { version = "1.0.14", default-features = false } +quote = { version = "1.0.26", default-features = false } url = { version = "2.2.2", default-features = false } - diff --git a/sqlx-macros-core/src/database/mod.rs b/sqlx-macros-core/src/database/mod.rs index c4fe696700..ddb8762acf 100644 --- a/sqlx-macros-core/src/database/mod.rs +++ b/sqlx-macros-core/src/database/mod.rs @@ -172,6 +172,7 @@ mod mysql; mod sqlite; mod fake_sqlx { + #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))] pub use sqlx_core::*; #[cfg(feature = "mysql")] diff --git a/sqlx-macros-core/src/derives/attributes.rs b/sqlx-macros-core/src/derives/attributes.rs index 554d7a4eb0..9d7be0e85f 100644 --- a/sqlx-macros-core/src/derives/attributes.rs +++ b/sqlx-macros-core/src/derives/attributes.rs @@ -1,8 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DeriveInput, Field, Lit, - Meta, MetaNameValue, NestedMeta, Type, Variant, + punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Field, LitStr, Meta, Token, Type, + Variant, }; macro_rules! assert_attribute { @@ -77,82 +77,53 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result { - for value in list.nested.iter() { - match value { - NestedMeta::Meta(meta) => match meta { - Meta::Path(p) if p.is_ident("transparent") => { - try_set!(transparent, true, value) - } - - Meta::Path(p) if p.is_ident("no_pg_array") => { - try_set!(no_pg_array, true, value); - } - - Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(val), - .. - }) if path.is_ident("rename_all") => { - let val = match &*val.value() { - "lowercase" => RenameAll::LowerCase, - "snake_case" => RenameAll::SnakeCase, - "UPPERCASE" => RenameAll::UpperCase, - "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase, - "kebab-case" => RenameAll::KebabCase, - "camelCase" => RenameAll::CamelCase, - "PascalCase" => RenameAll::PascalCase, - _ => fail!(meta, "unexpected value for rename_all"), - }; - - try_set!(rename_all, val, value) - } - - Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(val), - .. - }) if path.is_ident("type_name") => { - try_set!( - type_name, - TypeName { - val: val.value(), - span: value.span(), - }, - value - ) - } - - Meta::Path(p) if p.is_ident("default") => { - try_set!(default, true, value) - } - - u => fail!(u, "unexpected attribute"), - }, - u => fail!(u, "unexpected attribute"), - } - } - } - Meta::List(list) if list.path.is_ident("repr") => { - if list.nested.len() != 1 { - fail!(&list.nested, "expected one value") - } - match list.nested.first().unwrap() { - NestedMeta::Meta(Meta::Path(p)) if p.get_ident().is_some() => { - try_set!(repr, p.get_ident().unwrap().clone(), list); - } - u => fail!(u, "unexpected value"), + for attr in input { + if attr.path().is_ident("sqlx") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("transparent") { + try_set!(transparent, true, attr); + } else if meta.path.is_ident("no_pg_array") { + try_set!(no_pg_array, true, attr); + } else if meta.path.is_ident("default") { + try_set!(default, true, attr); + } else if meta.path.is_ident("rename_all") { + meta.input.parse::()?; + let lit: LitStr = meta.input.parse()?; + + let val = match lit.value().as_str() { + "lowercase" => RenameAll::LowerCase, + "snake_case" => RenameAll::SnakeCase, + "UPPERCASE" => RenameAll::UpperCase, + "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase, + "kebab-case" => RenameAll::KebabCase, + "camelCase" => RenameAll::CamelCase, + "PascalCase" => RenameAll::PascalCase, + _ => fail!(lit, "unexpected value for rename_all"), + }; + + try_set!(rename_all, val, lit) + } else if meta.path.is_ident("type_name") { + meta.input.parse::()?; + let lit: LitStr = meta.input.parse()?; + let name = TypeName { + val: lit.value(), + span: lit.span(), + }; + + try_set!(type_name, name, lit) + } else { + fail!(meta.path, "unexpected attribute") } + + Ok(()) + })?; + } else if attr.path().is_ident("repr") { + let list: Punctuated = + attr.parse_args_with(>::parse_terminated)?; + + if let Some(path) = list.iter().find_map(|f| f.require_path_only().ok()) { + try_set!(repr, path.get_ident().unwrap().clone(), list); } - _ => {} } } @@ -174,35 +145,28 @@ pub fn parse_child_attributes(input: &[Attribute]) -> syn::Result match meta { - Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(val), - .. - }) if path.is_ident("rename") => try_set!(rename, val.value(), value), - Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(val), - .. - }) if path.is_ident("try_from") => try_set!(try_from, val.parse()?, value), - Meta::Path(path) if path.is_ident("default") => default = true, - Meta::Path(path) if path.is_ident("flatten") => flatten = true, - Meta::Path(path) if path.is_ident("skip") => skip = true, - Meta::Path(path) if path.is_ident("json") => json = true, - u => fail!(u, "unexpected attribute"), - }, - u => fail!(u, "unexpected attribute"), - } + for attr in input.iter().filter(|a| a.path().is_ident("sqlx")) { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("rename") { + meta.input.parse::()?; + let val: LitStr = meta.input.parse()?; + try_set!(rename, val.value(), val); + } else if meta.path.is_ident("try_from") { + meta.input.parse::()?; + let val: LitStr = meta.input.parse()?; + try_set!(try_from, val.parse()?, val); + } else if meta.path.is_ident("default") { + default = true; + } else if meta.path.is_ident("flatten") { + flatten = true; + } else if meta.path.is_ident("skip") { + skip = true; + } else if meta.path.is_ident("json") { + json = true; } - } + + return Ok(()); + })?; if json && flatten { fail!( diff --git a/sqlx-macros-core/src/derives/encode.rs b/sqlx-macros-core/src/derives/encode.rs index 7bb568210f..823af65ad3 100644 --- a/sqlx-macros-core/src/derives/encode.rs +++ b/sqlx-macros-core/src/derives/encode.rs @@ -9,7 +9,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, - FieldsUnnamed, Lifetime, LifetimeDef, Stmt, Variant, + FieldsUnnamed, Lifetime, LifetimeParam, Stmt, Variant, }; pub fn expand_derive_encode(input: &DeriveInput) -> syn::Result { @@ -66,7 +66,7 @@ fn expand_derive_encode_transparent( let mut generics = generics.clone(); generics .params - .insert(0, LifetimeDef::new(lifetime.clone()).into()); + .insert(0, LifetimeParam::new(lifetime.clone()).into()); generics .params diff --git a/sqlx-macros-core/src/derives/mod.rs b/sqlx-macros-core/src/derives/mod.rs index 45e8d521c4..5f55ab5aea 100644 --- a/sqlx-macros-core/src/derives/mod.rs +++ b/sqlx-macros-core/src/derives/mod.rs @@ -12,7 +12,6 @@ pub use row::expand_derive_from_row; use self::attributes::RenameAll; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; -use std::iter::FromIterator; use syn::DeriveInput; pub fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { diff --git a/sqlx-macros-core/src/query/args.rs b/sqlx-macros-core/src/query/args.rs index 3a07b1b303..194a444a57 100644 --- a/sqlx-macros-core/src/query/args.rs +++ b/sqlx-macros-core/src/query/args.rs @@ -1,11 +1,11 @@ use crate::database::DatabaseExt; use crate::query::QueryMacroInput; use either::Either; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use sqlx_core::describe::Describe; use syn::spanned::Spanned; -use syn::{Expr, ExprCast, ExprGroup, ExprType, Type}; +use syn::{Expr, ExprCast, ExprGroup, Type}; /// Returns a tokenstream which typechecks the arguments passed to the macro /// and binds them to `DB::Arguments` with the ident `query_args`. @@ -49,31 +49,28 @@ pub fn quote_args( .zip(arg_names.iter().zip(&input.arg_exprs)) .enumerate() .map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> { - let param_ty = match get_type_override(expr) { + if get_type_override(expr).is_some() { // cast will fail to compile if the type does not match // and we strip casts to wildcard - Some((_, false)) => return Ok(quote!()), - // type ascription is deprecated - Some((ty, true)) => return Ok(create_warning(name.clone(), &ty, &expr)), - None => { - DB::param_type_for_id(¶m_ty) - .ok_or_else(|| { - if let Some(feature_gate) = ::get_feature_gate(¶m_ty) { - format!( - "optional sqlx feature `{}` required for type {} of param #{}", - feature_gate, - param_ty, - i + 1, - ) - } else { - format!("unsupported type {} for param #{}", param_ty, i + 1) - } - })? - .parse::() - .map_err(|_| format!("Rust type mapping for {param_ty} not parsable"))? - - } - }; + return Ok(quote!()); + } + + let param_ty = + DB::param_type_for_id(¶m_ty) + .ok_or_else(|| { + if let Some(feature_gate) = ::get_feature_gate(¶m_ty) { + format!( + "optional sqlx feature `{}` required for type {} of param #{}", + feature_gate, + param_ty, + i + 1, + ) + } else { + format!("unsupported type {} for param #{}", param_ty, i + 1) + } + })? + .parse::() + .map_err(|_| format!("Rust type mapping for {param_ty} not parsable"))?; Ok(quote_spanned!(expr.span() => // this shouldn't actually run @@ -116,42 +113,10 @@ pub fn quote_args( }) } -fn create_warning(name: Ident, ty: &Type, expr: &Expr) -> TokenStream { - let Expr::Type(ExprType { expr: stripped, .. }) = expr else { - return quote!(); - }; - let current = quote!(#stripped: #ty).to_string(); - let fix = quote!(#stripped as #ty).to_string(); - let name = Ident::new(&format!("warning_{name}"), expr.span()); - - let message = format!( - " -\t\tType ascription pattern is deprecated, prefer casting -\t\tTry changing from -\t\t\t`{current}` -\t\tto -\t\t\t`{fix}` - -\t\tSee for more information -" - ); - - quote_spanned!(expr.span() => - // this shouldn't actually run - if false { - #[deprecated(note = #message)] - #[allow(non_upper_case_globals)] - const #name: () = (); - let _ = #name; - } - ) -} - -fn get_type_override(expr: &Expr) -> Option<(&Type, bool)> { +fn get_type_override(expr: &Expr) -> Option<&Type> { match expr { Expr::Group(group) => get_type_override(&group.expr), - Expr::Cast(cast) => Some((&cast.ty, false)), - Expr::Type(ascription) => Some((&ascription.ty, true)), + Expr::Cast(cast) => Some(&cast.ty), _ => None, } } @@ -167,8 +132,6 @@ fn strip_wildcard(expr: Expr) -> Expr { group_token, expr: Box::new(strip_wildcard(*expr)), }), - // type ascription syntax is experimental so we always strip it - Expr::Type(ExprType { expr, .. }) => *expr, // we want to retain casts if they semantically matter Expr::Cast(ExprCast { attrs, diff --git a/sqlx-macros-core/src/test_attr.rs b/sqlx-macros-core/src/test_attr.rs index a86547cc6b..ff9ce511b9 100644 --- a/sqlx-macros-core/src/test_attr.rs +++ b/sqlx-macros-core/src/test_attr.rs @@ -1,5 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; +use syn::parse::Parser; #[cfg(feature = "migrate")] struct Args { @@ -23,7 +24,12 @@ enum MigrationsOpt { Disabled, } -pub fn expand(args: syn::AttributeArgs, input: syn::ItemFn) -> crate::Result { +type AttributeArgs = syn::punctuated::Punctuated; + +pub fn expand(args: TokenStream, input: syn::ItemFn) -> crate::Result { + let parser = AttributeArgs::parse_terminated; + let args = parser.parse2(args)?; + if input.sig.inputs.is_empty() { if !args.is_empty() { if cfg!(feature = "migrate") { @@ -70,7 +76,7 @@ fn expand_simple(input: syn::ItemFn) -> TokenStream { } #[cfg(feature = "migrate")] -fn expand_advanced(args: syn::AttributeArgs, input: syn::ItemFn) -> crate::Result { +fn expand_advanced(args: AttributeArgs, input: syn::ItemFn) -> crate::Result { let ret = &input.sig.output; let name = &input.sig.ident; let inputs = &input.sig.inputs; @@ -180,97 +186,101 @@ fn expand_advanced(args: syn::AttributeArgs, input: syn::ItemFn) -> crate::Resul } #[cfg(feature = "migrate")] -fn parse_args(attr_args: syn::AttributeArgs) -> syn::Result { +fn parse_args(attr_args: AttributeArgs) -> syn::Result { + use syn::{punctuated::Punctuated, Expr, Lit, LitStr, Meta, MetaNameValue, Token}; + let mut fixtures = Vec::new(); let mut migrations = MigrationsOpt::InferredPath; for arg in attr_args { + let path = arg.path().clone(); + match arg { - syn::NestedMeta::Meta(syn::Meta::List(list)) if list.path.is_ident("fixtures") => { + syn::Meta::List(list) if list.path.is_ident("fixtures") => { let mut fixtures_local = vec![]; let mut fixtures_type = FixturesType::None; - for nested in list.nested { - match nested { - syn::NestedMeta::Lit(syn::Lit::Str(litstr)) => { - // fixtures("","") or fixtures("","") - parse_fixtures_args(&mut fixtures_type, litstr, &mut fixtures_local)?; - }, - syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) - if namevalue.path.is_ident("path") => - { - // fixtures(path = "", scripts("","")) checking `path` argument - parse_fixtures_path_args(&mut fixtures_type, namevalue)?; - }, - syn::NestedMeta::Meta(syn::Meta::List(list)) if list.path.is_ident("scripts") => { - // fixtures(path = "", scripts("","")) checking `scripts` argument - parse_fixtures_scripts_args(&mut fixtures_type, list, &mut fixtures_local)?; - } - other => { - return Err(syn::Error::new_spanned(other, "expected string literal")) - } - }; + let parse_nested = list.parse_nested_meta(|meta| { + if meta.path.is_ident("path") { + // fixtures(path = "", scripts("","")) checking `path` argument + meta.input.parse::()?; + let val: LitStr = meta.input.parse()?; + parse_fixtures_path_args(&mut fixtures_type, val)?; + } else if meta.path.is_ident("scripts") { + // fixtures(path = "", scripts("","")) checking `scripts` argument + let parser = >::parse_terminated; + let list = parser.parse2(list.tokens.clone())?; + parse_fixtures_scripts_args(&mut fixtures_type, list, &mut fixtures_local)?; + } else { + return Err(syn::Error::new_spanned( + meta.path, + "unexpected fixture meta", + )); + } + + Ok(()) + }); + + if parse_nested.is_err() { + // fixtures("","") or fixtures("","") + let args = + list.parse_args_with(>::parse_terminated)?; + for arg in args { + parse_fixtures_args(&mut fixtures_type, arg, &mut fixtures_local)?; + } } + fixtures.push((fixtures_type, fixtures_local)); } - syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) - if namevalue.path.is_ident("migrations") => - { + syn::Meta::NameValue(value) if value.path.is_ident("migrations") => { if !matches!(migrations, MigrationsOpt::InferredPath) { return Err(syn::Error::new_spanned( - namevalue, + value, "cannot have more than one `migrations` or `migrator` arg", )); } - migrations = match namevalue.lit { - syn::Lit::Bool(litbool) => { - if !litbool.value { - // migrations = false - MigrationsOpt::Disabled - } else { - // migrations = true - return Err(syn::Error::new_spanned( - litbool, - "`migrations = true` is redundant", - )); - } - } - // migrations = "" - syn::Lit::Str(litstr) => MigrationsOpt::ExplicitPath(litstr), - _ => { - return Err(syn::Error::new_spanned( - namevalue, - "expected string or `false`", - )) - } + let Expr::Lit(syn::ExprLit { lit, .. }) = value.value else { + return Err(syn::Error::new_spanned(path, "expected string for `false`")); }; - } - syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) - if namevalue.path.is_ident("migrator") => - { - if !matches!(migrations, MigrationsOpt::InferredPath) { + + migrations = match lit { + // migrations = false + Lit::Bool(b) if !b.value => MigrationsOpt::Disabled, + // migrations = true + Lit::Bool(b) => { return Err(syn::Error::new_spanned( - namevalue, - "cannot have more than one `migrations` or `migrator` arg", + b, + "`migrations = true` is redundant", )); } - - migrations = match namevalue.lit { - // migrator = "" - syn::Lit::Str(litstr) => MigrationsOpt::ExplicitMigrator(litstr.parse()?), - _ => { - return Err(syn::Error::new_spanned( - namevalue, - "expected string", - )) - } - }; + // migrations = "path" + Lit::Str(s) => MigrationsOpt::ExplicitPath(s), + lit => return Err(syn::Error::new_spanned(lit, "expected string or `false`")), + }; + } + // migrator = "" + Meta::NameValue(MetaNameValue { value, .. }) if path.is_ident("migrator") => { + if !matches!(migrations, MigrationsOpt::InferredPath) { + return Err(syn::Error::new_spanned( + path, + "cannot have more than one `migrations` or `migrator` arg", + )); } - other => { + + let Expr::Lit(syn::ExprLit { + lit: Lit::Str(lit), .. + }) = value + else { + return Err(syn::Error::new_spanned(path, "expected string")); + }; + + migrations = MigrationsOpt::ExplicitMigrator(lit.parse()?); + } + arg => { return Err(syn::Error::new_spanned( - other, - "expected `fixtures(\"\", ...)` or `migrations = \"\" | false` or `migrator = \"\"`", + arg, + r#"expected `fixtures("", ...)` or `migrations = "" | false` or `migrator = ""`"#, )) } } @@ -338,43 +348,34 @@ fn parse_fixtures_args( #[cfg(feature = "migrate")] fn parse_fixtures_path_args( fixtures_type: &mut FixturesType, - namevalue: syn::MetaNameValue, + namevalue: syn::LitStr, ) -> syn::Result<()> { - // fixtures(path = "", scripts("","")) checking `path` argument if !matches!(fixtures_type, FixturesType::None) { return Err(syn::Error::new_spanned( namevalue, "`path` must be the first argument of `fixtures`", )); } - *fixtures_type = match namevalue.lit { - // path = "" - syn::Lit::Str(litstr) => FixturesType::CustomRelativePath(litstr), - _ => return Err(syn::Error::new_spanned(namevalue, "expected string")), - }; + *fixtures_type = FixturesType::CustomRelativePath(namevalue); Ok(()) } #[cfg(feature = "migrate")] fn parse_fixtures_scripts_args( fixtures_type: &mut FixturesType, - list: syn::MetaList, + list: syn::punctuated::Punctuated, fixtures_local: &mut Vec, ) -> syn::Result<()> { // fixtures(path = "", scripts("","")) checking `scripts` argument + if !matches!(fixtures_type, FixturesType::CustomRelativePath(_)) { return Err(syn::Error::new_spanned( list, "`scripts` must be the second argument of `fixtures` and used together with `path`", )); } - for nested in list.nested { - let litstr = match nested { - syn::NestedMeta::Lit(syn::Lit::Str(litstr)) => litstr, - other => return Err(syn::Error::new_spanned(other, "expected string literal")), - }; - fixtures_local.push(litstr); - } + + fixtures_local.extend(list); Ok(()) } diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 82fecab730..82bf4a7d41 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -44,5 +44,5 @@ sqlx-core = { workspace = true, features = ["any"] } sqlx-macros-core = { workspace = true } proc-macro2 = { version = "1.0.36", default-features = false } -syn = { version = "1.0.84", default-features = false, features = ["parsing", "proc-macro"] } -quote = { version = "1.0.14", default-features = false } +syn = { version = "2.0.52", default-features = false, features = ["parsing", "proc-macro"] } +quote = { version = "1.0.26", default-features = false } diff --git a/sqlx-macros/src/lib.rs b/sqlx-macros/src/lib.rs index 229c1030b1..96647cf1b9 100644 --- a/sqlx-macros/src/lib.rs +++ b/sqlx-macros/src/lib.rs @@ -79,10 +79,9 @@ pub fn migrate(input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as syn::AttributeArgs); let input = syn::parse_macro_input!(input as syn::ItemFn); - match test_attr::expand(args, input) { + match test_attr::expand(args.into(), input) { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { From 4c6830210b49b217f77cf53ee875a39b683512a8 Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Tue, 12 Mar 2024 20:07:56 -0300 Subject: [PATCH 53/59] chore: remove deprecation notice for ascription --- src/macros/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/macros/mod.rs b/src/macros/mod.rs index e797668602..0723b6c0ae 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -164,10 +164,6 @@ /// Using `expr as _` simply signals to the macro to not type-check that bind expression, /// and then that syntax is stripped from the expression so as to not trigger type errors. /// -/// **NOTE:** type ascription syntax (`expr: _`) is deprecated and will be removed in a -/// future release. This is due to Rust's [RFC 3307](https://github.com/rust-lang/rfcs/pull/3307) -/// officially dropping support for the syntax. -/// /// ## Type Overrides: Output Columns /// Type overrides are also available for output columns, utilizing the SQL standard's support /// for arbitrary text in column names: From 936744dfd6054d986ea784bd1e4acc4472684992 Mon Sep 17 00:00:00 2001 From: Jesse Wang Date: Fri, 15 Mar 2024 08:05:44 +1300 Subject: [PATCH 54/59] Fix describe on PostgreSQL views with rules (#2736) * fix postgres describe on multiple explains * inline the first explain using smallvec * fix: regenerate `Cargo.lock` --------- Co-authored-by: Austin Bonander --- Cargo.lock | 19 +++++++++++-------- sqlx-postgres/Cargo.toml | 2 +- sqlx-postgres/src/connection/describe.rs | 10 ++++++---- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 130a38c602..6b9f185918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "argon2" @@ -479,9 +479,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" dependencies = [ "serde", ] @@ -3098,6 +3098,9 @@ name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3708,18 +3711,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index 77cc0e2403..ffa40471fc 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -57,7 +57,7 @@ log = "0.4.17" memchr = { version = "2.4.1", default-features = false } num-bigint = { version = "0.4.3", optional = true } once_cell = "1.9.0" -smallvec = "1.7.0" +smallvec = { version = "1.7.0", features = ["serde"] } stringprep = "0.1.2" thiserror = "1.0.35" tracing = { version = "0.1.37", features = ["log"] } diff --git a/sqlx-postgres/src/connection/describe.rs b/sqlx-postgres/src/connection/describe.rs index 1cd28a8b54..189ae3389b 100644 --- a/sqlx-postgres/src/connection/describe.rs +++ b/sqlx-postgres/src/connection/describe.rs @@ -10,6 +10,7 @@ use crate::types::Oid; use crate::HashMap; use crate::{PgArguments, PgColumn, PgConnection, PgTypeInfo}; use futures_core::future::BoxFuture; +use smallvec::SmallVec; use std::fmt::Write; use std::sync::Arc; @@ -447,20 +448,21 @@ WHERE rngtypid = $1 explain += ")"; } - let (Json([explain]),): (Json<[Explain; 1]>,) = query_as(&explain).fetch_one(self).await?; + let (Json(explains),): (Json>,) = + query_as(&explain).fetch_one(self).await?; let mut nullables = Vec::new(); - if let Explain::Plan { + if let Some(Explain::Plan { plan: plan @ Plan { output: Some(ref outputs), .. }, - } = &explain + }) = explains.first() { nullables.resize(outputs.len(), None); - visit_plan(plan, outputs, &mut nullables); + visit_plan(&plan, outputs, &mut nullables); } Ok(nullables) From 9ba488c831c1b556cd5e5208c44dfdaebdca5b75 Mon Sep 17 00:00:00 2001 From: nitn3lav <77448526+nitn3lav@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:35:52 +0100 Subject: [PATCH 55/59] Generic Associated Types in Database, replacing HasValueRef, HasArguments, HasStatement (#2973) * HasValueRef, HasArguments, HasStatement -> Database GATs replace the associated types from the generic traits `HasValueRef<'r>`, `HasArguments<'q>` and `HasStatement<'q>` with generic associated types in `Database` * fixup after rebase --------- Co-authored-by: Austin Bonander --- sqlx-core/src/any/database.rs | 27 +++------- sqlx-core/src/any/row.rs | 9 ++-- sqlx-core/src/any/types/blob.rs | 10 ++-- sqlx-core/src/any/types/bool.rs | 6 +-- sqlx-core/src/any/types/float.rs | 6 +-- sqlx-core/src/any/types/int.rs | 14 +++--- sqlx-core/src/any/types/str.rs | 12 ++--- sqlx-core/src/any/value.rs | 4 +- sqlx-core/src/arguments.rs | 12 ++--- sqlx-core/src/database.rs | 68 +++++--------------------- sqlx-core/src/decode.rs | 12 ++--- sqlx-core/src/encode.rs | 14 +++--- sqlx-core/src/executor.rs | 20 ++++---- sqlx-core/src/pool/executor.rs | 6 +-- sqlx-core/src/query.rs | 23 ++++----- sqlx-core/src/query_as.rs | 16 +++--- sqlx-core/src/query_builder.rs | 12 ++--- sqlx-core/src/query_scalar.rs | 16 +++--- sqlx-core/src/raw_sql.rs | 11 +++-- sqlx-core/src/row.rs | 7 +-- sqlx-core/src/statement.rs | 14 +++--- sqlx-core/src/testing/fixtures.rs | 4 +- sqlx-core/src/transaction.rs | 2 +- sqlx-core/src/types/bstr.rs | 8 +-- sqlx-core/src/types/json.rs | 8 +-- sqlx-core/src/types/text.rs | 4 +- sqlx-core/src/value.rs | 4 +- sqlx-macros-core/src/derives/decode.rs | 4 +- sqlx-macros-core/src/derives/encode.rs | 6 +-- sqlx-macros-core/src/query/args.rs | 4 +- sqlx-mysql/src/database.rs | 30 +++--------- sqlx-postgres/src/database.rs | 30 +++--------- sqlx-sqlite/src/database.rs | 30 +++--------- 33 files changed, 171 insertions(+), 282 deletions(-) diff --git a/sqlx-core/src/any/database.rs b/sqlx-core/src/any/database.rs index 1a57a77e2e..9c3f15bb1f 100644 --- a/sqlx-core/src/any/database.rs +++ b/sqlx-core/src/any/database.rs @@ -2,7 +2,7 @@ use crate::any::{ AnyArgumentBuffer, AnyArguments, AnyColumn, AnyConnection, AnyQueryResult, AnyRow, AnyStatement, AnyTransactionManager, AnyTypeInfo, AnyValue, AnyValueRef, }; -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; +use crate::database::{Database, HasStatementCache}; /// Opaque database driver. Capable of being used in place of any SQLx database driver. The actual /// driver used will be selected at runtime, from the connection url. @@ -23,29 +23,16 @@ impl Database for Any { type TypeInfo = AnyTypeInfo; type Value = AnyValue; - const NAME: &'static str = "Any"; - - const URL_SCHEMES: &'static [&'static str] = &[]; -} - -impl<'r> HasValueRef<'r> for Any { - type Database = Any; + type ValueRef<'r> = AnyValueRef<'r>; - type ValueRef = AnyValueRef<'r>; -} + type Arguments<'q> = AnyArguments<'q>; + type ArgumentBuffer<'q> = AnyArgumentBuffer<'q>; -impl<'q> HasStatement<'q> for Any { - type Database = Any; + type Statement<'q> = AnyStatement<'q>; - type Statement = AnyStatement<'q>; -} - -impl<'q> HasArguments<'q> for Any { - type Database = Any; - - type Arguments = AnyArguments<'q>; + const NAME: &'static str = "Any"; - type ArgumentBuffer = AnyArgumentBuffer<'q>; + const URL_SCHEMES: &'static [&'static str] = &[]; } // This _may_ be true, depending on the selected database diff --git a/sqlx-core/src/any/row.rs b/sqlx-core/src/any/row.rs index 2381902311..18c9d57daa 100644 --- a/sqlx-core/src/any/row.rs +++ b/sqlx-core/src/any/row.rs @@ -1,7 +1,7 @@ use crate::any::error::mismatched_types; use crate::any::{Any, AnyColumn, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind}; use crate::column::{Column, ColumnIndex}; -use crate::database::{Database, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::error::Error; use crate::ext::ustr::UStr; @@ -28,10 +28,7 @@ impl Row for AnyRow { &self.columns } - fn try_get_raw( - &self, - index: I, - ) -> Result<>::ValueRef, Error> + fn try_get_raw(&self, index: I) -> Result<::ValueRef<'_>, Error> where I: ColumnIndex, { @@ -141,7 +138,7 @@ impl AnyRow { } fn decode<'r, DB: Database, T: Decode<'r, DB>>( - valueref: >::ValueRef, + valueref: ::ValueRef<'r>, ) -> crate::Result { Decode::decode(valueref).map_err(Error::decode) } diff --git a/sqlx-core/src/any/types/blob.rs b/sqlx-core/src/any/types/blob.rs index 80d9602694..3f33b746f7 100644 --- a/sqlx-core/src/any/types/blob.rs +++ b/sqlx-core/src/any/types/blob.rs @@ -1,5 +1,5 @@ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; -use crate::database::{HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -15,14 +15,14 @@ impl Type for [u8] { } impl<'q> Encode<'q, Any> for &'q [u8] { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Blob((*self).into())); IsNull::No } } impl<'r> Decode<'r, Any> for &'r [u8] { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Blob(Cow::Borrowed(blob)) => Ok(blob), // This shouldn't happen in practice, it means the user got an `AnyValueRef` @@ -42,14 +42,14 @@ impl Type for Vec { } impl<'q> Encode<'q, Any> for Vec { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Blob(Cow::Owned(self.clone()))); IsNull::No } } impl<'r> Decode<'r, Any> for Vec { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Blob(blob) => Ok(blob.into_owned()), other => other.unexpected(), diff --git a/sqlx-core/src/any/types/bool.rs b/sqlx-core/src/any/types/bool.rs index 1f2a05c91a..09f7e1f792 100644 --- a/sqlx-core/src/any/types/bool.rs +++ b/sqlx-core/src/any/types/bool.rs @@ -1,5 +1,5 @@ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; -use crate::database::{HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -14,14 +14,14 @@ impl Type for bool { } impl<'q> Encode<'q, Any> for bool { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Bool(*self)); IsNull::No } } impl<'r> Decode<'r, Any> for bool { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Bool(b) => Ok(b), other => other.unexpected(), diff --git a/sqlx-core/src/any/types/float.rs b/sqlx-core/src/any/types/float.rs index f0ba923b7a..47b4b24d36 100644 --- a/sqlx-core/src/any/types/float.rs +++ b/sqlx-core/src/any/types/float.rs @@ -1,5 +1,5 @@ use crate::any::{Any, AnyArgumentBuffer, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, AnyValueRef}; -use crate::database::{HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -38,14 +38,14 @@ impl Type for f64 { } impl<'q> Encode<'q, Any> for f64 { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Double(*self)); IsNull::No } } impl<'r> Decode<'r, Any> for f64 { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { // Widening is safe AnyValueKind::Real(r) => Ok(r as f64), diff --git a/sqlx-core/src/any/types/int.rs b/sqlx-core/src/any/types/int.rs index d3dfdc136e..ae8d0e71f0 100644 --- a/sqlx-core/src/any/types/int.rs +++ b/sqlx-core/src/any/types/int.rs @@ -1,5 +1,5 @@ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; -use crate::database::{HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -18,14 +18,14 @@ impl Type for i16 { } impl<'q> Encode<'q, Any> for i16 { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::SmallInt(*self)); IsNull::No } } impl<'r> Decode<'r, Any> for i16 { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } @@ -43,14 +43,14 @@ impl Type for i32 { } impl<'q> Encode<'q, Any> for i32 { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Integer(*self)); IsNull::No } } impl<'r> Decode<'r, Any> for i32 { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } @@ -68,14 +68,14 @@ impl Type for i64 { } impl<'q> Encode<'q, Any> for i64 { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::BigInt(*self)); IsNull::No } } impl<'r> Decode<'r, Any> for i64 { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } diff --git a/sqlx-core/src/any/types/str.rs b/sqlx-core/src/any/types/str.rs index 4f519ddbc3..3ce6d28e57 100644 --- a/sqlx-core/src/any/types/str.rs +++ b/sqlx-core/src/any/types/str.rs @@ -1,6 +1,6 @@ use crate::any::types::str; use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; -use crate::database::{HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -16,7 +16,7 @@ impl Type for str { } impl<'a> Encode<'a, Any> for &'a str { - fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull + fn encode(self, buf: &mut ::ArgumentBuffer<'a>) -> IsNull where Self: Sized, { @@ -24,13 +24,13 @@ impl<'a> Encode<'a, Any> for &'a str { IsNull::No } - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'a>) -> IsNull { (*self).encode(buf) } } impl<'a> Decode<'a, Any> for &'a str { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'a>) -> Result { match value.kind { AnyValueKind::Text(Cow::Borrowed(text)) => Ok(text), // This shouldn't happen in practice, it means the user got an `AnyValueRef` @@ -50,14 +50,14 @@ impl Type for String { } impl<'q> Encode<'q, Any> for String { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { buf.0.push(AnyValueKind::Text(Cow::Owned(self.clone()))); IsNull::No } } impl<'r> Decode<'r, Any> for String { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Text(text) => Ok(text.into_owned()), other => other.unexpected(), diff --git a/sqlx-core/src/any/value.rs b/sqlx-core/src/any/value.rs index 075c935929..2ff63bc7d5 100644 --- a/sqlx-core/src/any/value.rs +++ b/sqlx-core/src/any/value.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind}; -use crate::database::{Database, HasValueRef}; +use crate::database::Database; use crate::error::BoxDynError; use crate::types::Type; use crate::value::{Value, ValueRef}; @@ -71,7 +71,7 @@ pub struct AnyValueRef<'a> { impl Value for AnyValue { type Database = Any; - fn as_ref(&self) -> >::ValueRef { + fn as_ref(&self) -> ::ValueRef<'_> { AnyValueRef { kind: match &self.kind { AnyValueKind::Null => AnyValueKind::Null, diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index c952d7df9f..d8b24d8b9e 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -1,6 +1,6 @@ //! Types and traits for passing arguments to SQL queries. -use crate::database::{Database, HasArguments}; +use crate::database::Database; use crate::encode::Encode; use crate::types::Type; use std::fmt::{self, Write}; @@ -23,8 +23,8 @@ pub trait Arguments<'q>: Send + Sized + Default { } } -pub trait IntoArguments<'q, DB: HasArguments<'q>>: Sized + Send { - fn into_arguments(self) -> >::Arguments; +pub trait IntoArguments<'q, DB: Database>: Sized + Send { + fn into_arguments(self) -> ::Arguments<'q>; } // NOTE: required due to lack of lazy normalization @@ -45,10 +45,10 @@ macro_rules! impl_into_arguments_for_arguments { } /// used by the query macros to prevent supernumerary `.bind()` calls -pub struct ImmutableArguments<'q, DB: HasArguments<'q>>(pub >::Arguments); +pub struct ImmutableArguments<'q, DB: Database>(pub ::Arguments<'q>); -impl<'q, DB: HasArguments<'q>> IntoArguments<'q, DB> for ImmutableArguments<'q, DB> { - fn into_arguments(self) -> >::Arguments { +impl<'q, DB: Database> IntoArguments<'q, DB> for ImmutableArguments<'q, DB> { + fn into_arguments(self) -> ::Arguments<'q> { self.0 } } diff --git a/sqlx-core/src/database.rs b/sqlx-core/src/database.rs index be62924077..d17621c719 100644 --- a/sqlx-core/src/database.rs +++ b/sqlx-core/src/database.rs @@ -69,15 +69,7 @@ use crate::value::{Value, ValueRef}; /// /// This trait encapsulates a complete set of traits that implement a driver for a /// specific database (e.g., MySQL, PostgreSQL). -pub trait Database: - 'static - + Sized - + Send - + Debug - + for<'r> HasValueRef<'r, Database = Self> - + for<'q> HasArguments<'q, Database = Self> - + for<'q> HasStatement<'q, Database = Self> -{ +pub trait Database: 'static + Sized + Send + Debug { /// The concrete `Connection` implementation for this database. type Connection: Connection; @@ -99,61 +91,23 @@ pub trait Database: /// The concrete type used to hold an owned copy of the not-yet-decoded value that was /// received from the database. type Value: Value + 'static; - - /// The display name for this database driver. - const NAME: &'static str; - - /// The schemes for database URLs that should match this driver. - const URL_SCHEMES: &'static [&'static str]; -} - -/// Associate [`Database`] with a [`ValueRef`](crate::value::ValueRef) of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265 -pub trait HasValueRef<'r> { - type Database: Database; - /// The concrete type used to hold a reference to the not-yet-decoded value that has just been /// received from the database. - type ValueRef: ValueRef<'r, Database = Self::Database>; -} - -/// Associate [`Database`] with an [`Arguments`](crate::arguments::Arguments) of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265 -pub trait HasArguments<'q> { - type Database: Database; + type ValueRef<'r>: ValueRef<'r, Database = Self>; /// The concrete `Arguments` implementation for this database. - type Arguments: Arguments<'q, Database = Self::Database>; - + type Arguments<'q>: Arguments<'q, Database = Self>; /// The concrete type used as a buffer for arguments while encoding. - type ArgumentBuffer; -} - -/// Associate [`Database`] with a [`Statement`](crate::statement::Statement) of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265 -pub trait HasStatement<'q> { - type Database: Database; + type ArgumentBuffer<'q>; /// The concrete `Statement` implementation for this database. - type Statement: Statement<'q, Database = Self::Database>; + type Statement<'q>: Statement<'q, Database = Self>; + + /// The display name for this database driver. + const NAME: &'static str; + + /// The schemes for database URLs that should match this driver. + const URL_SCHEMES: &'static [&'static str]; } /// A [`Database`] that maintains a client-side cache of prepared statements. diff --git a/sqlx-core/src/decode.rs b/sqlx-core/src/decode.rs index e913668e07..3249c349cc 100644 --- a/sqlx-core/src/decode.rs +++ b/sqlx-core/src/decode.rs @@ -1,6 +1,6 @@ //! Provides [`Decode`] for decoding values from the database. -use crate::database::{Database, HasValueRef}; +use crate::database::Database; use crate::error::BoxDynError; use crate::value::ValueRef; @@ -14,10 +14,10 @@ use crate::value::ValueRef; /// /// The following showcases how to implement `Decode` to be generic over [`Database`]. The /// implementation can be marginally simpler if you remove the `DB` type parameter and explicitly -/// use the concrete [`ValueRef`](HasValueRef::ValueRef) and [`TypeInfo`](Database::TypeInfo) types. +/// use the concrete [`ValueRef`](Database::ValueRef) and [`TypeInfo`](Database::TypeInfo) types. /// /// ```rust -/// # use sqlx_core::database::{Database, HasValueRef}; +/// # use sqlx_core::database::{Database}; /// # use sqlx_core::decode::Decode; /// # use sqlx_core::types::Type; /// # use std::error::Error; @@ -42,7 +42,7 @@ use crate::value::ValueRef; /// &'r str: Decode<'r, DB> /// { /// fn decode( -/// value: >::ValueRef, +/// value: ::ValueRef<'r>, /// ) -> Result> { /// // the interface of ValueRef is largely unstable at the moment /// // so this is not directly implementable @@ -60,7 +60,7 @@ use crate::value::ValueRef; /// ``` pub trait Decode<'r, DB: Database>: Sized { /// Decode a new value of this type using a raw value from the database. - fn decode(value: >::ValueRef) -> Result; + fn decode(value: ::ValueRef<'r>) -> Result; } // implement `Decode` for Option for all SQL types @@ -69,7 +69,7 @@ where DB: Database, T: Decode<'r, DB>, { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { if value.is_null() { Ok(None) } else { diff --git a/sqlx-core/src/encode.rs b/sqlx-core/src/encode.rs index b1848bebc4..0ba1865947 100644 --- a/sqlx-core/src/encode.rs +++ b/sqlx-core/src/encode.rs @@ -2,7 +2,7 @@ use std::mem; -use crate::database::{Database, HasArguments}; +use crate::database::Database; /// The return type of [Encode::encode]. pub enum IsNull { @@ -19,7 +19,7 @@ pub enum IsNull { pub trait Encode<'q, DB: Database> { /// Writes the value of `self` into `buf` in the expected format for the database. #[must_use] - fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull + fn encode(self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull where Self: Sized, { @@ -31,7 +31,7 @@ pub trait Encode<'q, DB: Database> { /// Where possible, make use of `encode` instead as it can take advantage of re-using /// memory. #[must_use] - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull; + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull; fn produces(&self) -> Option { // `produces` is inherently a hook to allow database drivers to produce value-dependent @@ -50,12 +50,12 @@ where T: Encode<'q, DB>, { #[inline] - fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode(self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { >::encode_by_ref(self, buf) } #[inline] - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { <&T as Encode>::encode(self, buf) } @@ -89,7 +89,7 @@ macro_rules! impl_encode_for_option { #[inline] fn encode( self, - buf: &mut <$DB as $crate::database::HasArguments<'q>>::ArgumentBuffer, + buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer<'q>, ) -> $crate::encode::IsNull { if let Some(v) = self { v.encode(buf) @@ -101,7 +101,7 @@ macro_rules! impl_encode_for_option { #[inline] fn encode_by_ref( &self, - buf: &mut <$DB as $crate::database::HasArguments<'q>>::ArgumentBuffer, + buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer<'q>, ) -> $crate::encode::IsNull { if let Some(v) = self { v.encode_by_ref(buf) diff --git a/sqlx-core/src/executor.rs b/sqlx-core/src/executor.rs index f8222342e4..e3f245d923 100644 --- a/sqlx-core/src/executor.rs +++ b/sqlx-core/src/executor.rs @@ -1,4 +1,4 @@ -use crate::database::{Database, HasArguments, HasStatement}; +use crate::database::Database; use crate::describe::Describe; use crate::error::Error; @@ -149,7 +149,7 @@ pub trait Executor<'c>: Send + Debug + Sized { fn prepare<'e, 'q: 'e>( self, query: &'q str, - ) -> BoxFuture<'e, Result<>::Statement, Error>> + ) -> BoxFuture<'e, Result<::Statement<'q>, Error>> where 'c: 'e, { @@ -165,7 +165,7 @@ pub trait Executor<'c>: Send + Debug + Sized { self, sql: &'q str, parameters: &'e [::TypeInfo], - ) -> BoxFuture<'e, Result<>::Statement, Error>> + ) -> BoxFuture<'e, Result<::Statement<'q>, Error>> where 'c: 'e; @@ -195,14 +195,14 @@ pub trait Execute<'q, DB: Database>: Send + Sized { fn sql(&self) -> &'q str; /// Gets the previously cached statement, if available. - fn statement(&self) -> Option<&>::Statement>; + fn statement(&self) -> Option<&DB::Statement<'q>>; /// Returns the arguments to be bound against the query string. /// /// Returning `None` for `Arguments` indicates to use a "simple" query protocol and to not /// prepare the query. Returning `Some(Default::default())` is an empty arguments object that /// will be prepared (and cached) before execution. - fn take_arguments(&mut self) -> Option<>::Arguments>; + fn take_arguments(&mut self) -> Option<::Arguments<'q>>; /// Returns `true` if the statement should be cached. fn persistent(&self) -> bool; @@ -217,12 +217,12 @@ impl<'q, DB: Database> Execute<'q, DB> for &'q str { } #[inline] - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { None } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { None } @@ -232,19 +232,19 @@ impl<'q, DB: Database> Execute<'q, DB> for &'q str { } } -impl<'q, DB: Database> Execute<'q, DB> for (&'q str, Option<>::Arguments>) { +impl<'q, DB: Database> Execute<'q, DB> for (&'q str, Option<::Arguments<'q>>) { #[inline] fn sql(&self) -> &'q str { self.0 } #[inline] - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { None } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { self.1.take() } diff --git a/sqlx-core/src/pool/executor.rs b/sqlx-core/src/pool/executor.rs index c5c7acdd39..edfaa84b73 100644 --- a/sqlx-core/src/pool/executor.rs +++ b/sqlx-core/src/pool/executor.rs @@ -3,7 +3,7 @@ use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::TryStreamExt; -use crate::database::{Database, HasStatement}; +use crate::database::Database; use crate::describe::Describe; use crate::error::Error; use crate::executor::{Execute, Executor}; @@ -52,7 +52,7 @@ where self, sql: &'q str, parameters: &'e [::TypeInfo], - ) -> BoxFuture<'e, Result<>::Statement, Error>> { + ) -> BoxFuture<'e, Result<::Statement<'q>, Error>> { let pool = self.clone(); Box::pin(async move { pool.acquire().await?.prepare_with(sql, parameters).await }) @@ -117,7 +117,7 @@ where // parameters: &'e [::TypeInfo], // ) -> futures_core::future::BoxFuture< // 'e, -// Result<>::Statement, crate::error::Error>, +// Result<::Statement<'q>, crate::error::Error>, // > // where // 'c: 'e, diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 005c0eb488..47d884a7cf 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -5,7 +5,7 @@ use futures_core::stream::BoxStream; use futures_util::{future, StreamExt, TryFutureExt, TryStreamExt}; use crate::arguments::{Arguments, IntoArguments}; -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache}; +use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::Error; use crate::executor::{Execute, Executor}; @@ -15,7 +15,7 @@ use crate::types::Type; /// A single SQL query as a prepared statement. Returned by [`query()`]. #[must_use = "query must be executed to affect database"] pub struct Query<'q, DB: Database, A> { - pub(crate) statement: Either<&'q str, &'q >::Statement>, + pub(crate) statement: Either<&'q str, &'q DB::Statement<'q>>, pub(crate) arguments: Option, pub(crate) database: PhantomData, pub(crate) persistent: bool, @@ -51,7 +51,7 @@ where } } - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { match self.statement { Either::Right(ref statement) => Some(&statement), Either::Left(_) => None, @@ -59,7 +59,7 @@ where } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { self.arguments.take().map(IntoArguments::into_arguments) } @@ -69,7 +69,7 @@ where } } -impl<'q, DB: Database> Query<'q, DB, >::Arguments> { +impl<'q, DB: Database> Query<'q, DB, ::Arguments<'q>> { /// Bind a value for use with this SQL query. /// /// If the number of times this is called does not match the number of bind parameters that @@ -275,12 +275,12 @@ where } #[inline] - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { self.inner.statement() } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { self.inner.take_arguments() } @@ -465,8 +465,8 @@ where /// Execute a single SQL query as a prepared statement (explicitly created). pub fn query_statement<'q, DB>( - statement: &'q >::Statement, -) -> Query<'q, DB, >::Arguments> + statement: &'q DB::Statement<'q>, +) -> Query<'q, DB, ::Arguments<'_>> where DB: Database, { @@ -480,7 +480,7 @@ where /// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments. pub fn query_statement_with<'q, DB, A>( - statement: &'q >::Statement, + statement: &'q DB::Statement<'q>, arguments: A, ) -> Query<'q, DB, A> where @@ -525,6 +525,7 @@ where /// // where `conn` is `PgConnection` or `MySqlConnection` /// // or some other type that implements `Executor`. /// let results = sqlx::query(&query).fetch_all(&mut conn).await?; +/// # Ok(()) /// # } /// ``` /// @@ -618,7 +619,7 @@ where /// /// As an additional benefit, query parameters are usually sent in a compact binary encoding instead of a human-readable /// text encoding, which saves bandwidth. -pub fn query(sql: &str) -> Query<'_, DB, >::Arguments> +pub fn query(sql: &str) -> Query<'_, DB, ::Arguments<'_>> where DB: Database, { diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 0ec714c7bf..88714ea9a0 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -5,7 +5,7 @@ use futures_core::stream::BoxStream; use futures_util::{StreamExt, TryStreamExt}; use crate::arguments::IntoArguments; -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache}; +use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::Error; use crate::executor::{Execute, Executor}; @@ -32,12 +32,12 @@ where } #[inline] - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { self.inner.statement() } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { self.inner.take_arguments() } @@ -47,7 +47,7 @@ where } } -impl<'q, DB: Database, O> QueryAs<'q, DB, O, >::Arguments> { +impl<'q, DB: Database, O> QueryAs<'q, DB, O, ::Arguments<'q>> { /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](Query::bind). @@ -339,7 +339,7 @@ where /// /// ``` #[inline] -pub fn query_as<'q, DB, O>(sql: &'q str) -> QueryAs<'q, DB, O, >::Arguments> +pub fn query_as<'q, DB, O>(sql: &'q str) -> QueryAs<'q, DB, O, ::Arguments<'q>> where DB: Database, O: for<'r> FromRow<'r, DB::Row>, @@ -371,8 +371,8 @@ where // Make a SQL query from a statement, that is mapped to a concrete type. pub fn query_statement_as<'q, DB, O>( - statement: &'q >::Statement, -) -> QueryAs<'q, DB, O, >::Arguments> + statement: &'q DB::Statement<'q>, +) -> QueryAs<'q, DB, O, ::Arguments<'_>> where DB: Database, O: for<'r> FromRow<'r, DB::Row>, @@ -385,7 +385,7 @@ where // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete type. pub fn query_statement_as_with<'q, DB, O, A>( - statement: &'q >::Statement, + statement: &'q DB::Statement<'q>, arguments: A, ) -> QueryAs<'q, DB, O, A> where diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs index 75c2bd995c..2c9dba591f 100644 --- a/sqlx-core/src/query_builder.rs +++ b/sqlx-core/src/query_builder.rs @@ -5,7 +5,7 @@ use std::fmt::Write; use std::marker::PhantomData; use crate::arguments::{Arguments, IntoArguments}; -use crate::database::{Database, HasArguments}; +use crate::database::Database; use crate::encode::Encode; use crate::from_row::FromRow; use crate::query::Query; @@ -27,7 +27,7 @@ where { query: String, init_len: usize, - arguments: Option<>::Arguments>, + arguments: Option<::Arguments<'args>>, } impl<'args, DB: Database> Default for QueryBuilder<'args, DB> { @@ -49,7 +49,7 @@ where /// Start building a query with an initial SQL fragment, which may be an empty string. pub fn new(init: impl Into) -> Self where - >::Arguments: Default, + ::Arguments<'args>: Default, { let init = init.into(); @@ -445,7 +445,7 @@ where /// to the state it was in immediately after [`new()`][Self::new]. /// /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. - pub fn build(&mut self) -> Query<'_, DB, >::Arguments> { + pub fn build(&mut self) -> Query<'_, DB, ::Arguments<'args>> { self.sanity_check(); Query { @@ -470,7 +470,7 @@ where /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. pub fn build_query_as<'q, T: FromRow<'q, DB::Row>>( &'q mut self, - ) -> QueryAs<'q, DB, T, >::Arguments> { + ) -> QueryAs<'q, DB, T, ::Arguments<'args>> { QueryAs { inner: self.build(), output: PhantomData, @@ -491,7 +491,7 @@ where /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. pub fn build_query_scalar<'q, T>( &'q mut self, - ) -> QueryScalar<'q, DB, T, >::Arguments> + ) -> QueryScalar<'q, DB, T, ::Arguments<'args>> where DB: Database, (T,): for<'r> FromRow<'r, DB::Row>, diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs index 9280056e37..509d7e6c83 100644 --- a/sqlx-core/src/query_scalar.rs +++ b/sqlx-core/src/query_scalar.rs @@ -3,7 +3,7 @@ use futures_core::stream::BoxStream; use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; use crate::arguments::IntoArguments; -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache}; +use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::Error; use crate::executor::{Execute, Executor}; @@ -29,12 +29,12 @@ where self.inner.sql() } - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&DB::Statement<'q>> { self.inner.statement() } #[inline] - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { self.inner.take_arguments() } @@ -44,7 +44,7 @@ where } } -impl<'q, DB: Database, O> QueryScalar<'q, DB, O, >::Arguments> { +impl<'q, DB: Database, O> QueryScalar<'q, DB, O, ::Arguments<'q>> { /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](crate::query::Query::bind). @@ -320,7 +320,7 @@ where #[inline] pub fn query_scalar<'q, DB, O>( sql: &'q str, -) -> QueryScalar<'q, DB, O, >::Arguments> +) -> QueryScalar<'q, DB, O, ::Arguments<'q>> where DB: Database, (O,): for<'r> FromRow<'r, DB::Row>, @@ -350,8 +350,8 @@ where // Make a SQL query from a statement, that is mapped to a concrete value. pub fn query_statement_scalar<'q, DB, O>( - statement: &'q >::Statement, -) -> QueryScalar<'q, DB, O, >::Arguments> + statement: &'q DB::Statement<'q>, +) -> QueryScalar<'q, DB, O, ::Arguments<'_>> where DB: Database, (O,): for<'r> FromRow<'r, DB::Row>, @@ -363,7 +363,7 @@ where // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete value. pub fn query_statement_scalar_with<'q, DB, O, A>( - statement: &'q >::Statement, + statement: &'q DB::Statement<'q>, arguments: A, ) -> QueryScalar<'q, DB, O, A> where diff --git a/sqlx-core/src/raw_sql.rs b/sqlx-core/src/raw_sql.rs index 48162f2c3b..5617bfee08 100644 --- a/sqlx-core/src/raw_sql.rs +++ b/sqlx-core/src/raw_sql.rs @@ -1,9 +1,10 @@ -use crate::database::{Database, HasArguments, HasStatement}; -use crate::executor::{Execute, Executor}; -use crate::Error; use either::Either; use futures_core::stream::BoxStream; +use crate::database::Database; +use crate::executor::{Execute, Executor}; +use crate::Error; + // AUTHOR'S NOTE: I was just going to call this API `sql()` and `Sql`, respectively, // but realized that would be extremely annoying to deal with as a SQLite user // because IDE smart completion would always recommend the `Sql` type first. @@ -121,11 +122,11 @@ impl<'q, DB: Database> Execute<'q, DB> for RawSql<'q> { self.0 } - fn statement(&self) -> Option<&>::Statement> { + fn statement(&self) -> Option<&::Statement<'q>> { None } - fn take_arguments(&mut self) -> Option<>::Arguments> { + fn take_arguments(&mut self) -> Option<::Arguments<'q>> { None } diff --git a/sqlx-core/src/row.rs b/sqlx-core/src/row.rs index ec7ca7ec16..5923e25c03 100644 --- a/sqlx-core/src/row.rs +++ b/sqlx-core/src/row.rs @@ -1,5 +1,5 @@ use crate::column::ColumnIndex; -use crate::database::{Database, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::error::{mismatched_types, Error}; @@ -171,10 +171,7 @@ pub trait Row: Unpin + Send + Sync + 'static { /// [`ColumnNotFound`]: Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: Error::ColumnIndexOutOfBounds /// - fn try_get_raw( - &self, - index: I, - ) -> Result<>::ValueRef, Error> + fn try_get_raw(&self, index: I) -> Result<::ValueRef<'_>, Error> where I: ColumnIndex; } diff --git a/sqlx-core/src/statement.rs b/sqlx-core/src/statement.rs index 83079a46a4..17dfd6428d 100644 --- a/sqlx-core/src/statement.rs +++ b/sqlx-core/src/statement.rs @@ -1,6 +1,6 @@ use crate::arguments::IntoArguments; use crate::column::ColumnIndex; -use crate::database::{Database, HasArguments, HasStatement}; +use crate::database::Database; use crate::error::Error; use crate::from_row::FromRow; use crate::query::Query; @@ -21,7 +21,7 @@ pub trait Statement<'q>: Send + Sync { /// Creates an owned statement from this statement reference. This copies /// the original SQL text. - fn to_owned(&self) -> >::Statement; + fn to_owned(&self) -> ::Statement<'static>; /// Get the original SQL text used to create this statement. fn sql(&self) -> &str; @@ -59,7 +59,7 @@ pub trait Statement<'q>: Send + Sync { Ok(&self.columns()[index.index(self)?]) } - fn query(&self) -> Query<'_, Self::Database, >::Arguments>; + fn query(&self) -> Query<'_, Self::Database, ::Arguments<'_>>; fn query_with<'s, A>(&'s self, arguments: A) -> Query<'s, Self::Database, A> where @@ -67,7 +67,7 @@ pub trait Statement<'q>: Send + Sync { fn query_as( &self, - ) -> QueryAs<'_, Self::Database, O, >::Arguments> + ) -> QueryAs<'_, Self::Database, O, ::Arguments<'_>> where O: for<'r> FromRow<'r, ::Row>; @@ -78,7 +78,7 @@ pub trait Statement<'q>: Send + Sync { fn query_scalar( &self, - ) -> QueryScalar<'_, Self::Database, O, >::Arguments> + ) -> QueryScalar<'_, Self::Database, O, ::Arguments<'_>> where (O,): for<'r> FromRow<'r, ::Row>; @@ -111,7 +111,7 @@ macro_rules! impl_statement_query { '_, Self::Database, O, - >::Arguments, + ::Arguments<'_>, > where O: for<'r> $crate::from_row::FromRow< @@ -144,7 +144,7 @@ macro_rules! impl_statement_query { '_, Self::Database, O, - >::Arguments, + ::Arguments<'_>, > where (O,): for<'r> $crate::from_row::FromRow< diff --git a/sqlx-core/src/testing/fixtures.rs b/sqlx-core/src/testing/fixtures.rs index fc44e5bf75..58945d859f 100644 --- a/sqlx-core/src/testing/fixtures.rs +++ b/sqlx-core/src/testing/fixtures.rs @@ -1,6 +1,6 @@ //! TODO: automatic test fixture capture -use crate::database::{Database, HasArguments}; +use crate::database::Database; use crate::query_builder::QueryBuilder; @@ -111,7 +111,7 @@ impl FixtureSnapshot { /// which appends to an internal string. impl ToString for Fixture where - for<'a> >::Arguments: Default, + for<'a> ::Arguments<'a>: Default, { fn to_string(&self) -> String { let mut query = QueryBuilder::::new(""); diff --git a/sqlx-core/src/transaction.rs b/sqlx-core/src/transaction.rs index 0516b6adc3..e8e83df0f7 100644 --- a/sqlx-core/src/transaction.rs +++ b/sqlx-core/src/transaction.rs @@ -140,7 +140,7 @@ where // ) -> futures_core::future::BoxFuture< // 'e, // Result< -// >::Statement, +// ::Statement<'q>, // crate::error::Error, // >, // > diff --git a/sqlx-core/src/types/bstr.rs b/sqlx-core/src/types/bstr.rs index bb3a3b883d..ef571a9bf8 100644 --- a/sqlx-core/src/types/bstr.rs +++ b/sqlx-core/src/types/bstr.rs @@ -1,5 +1,5 @@ /// Conversions between `bstr` types and SQL types. -use crate::database::{Database, HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -27,7 +27,7 @@ where DB: Database, Vec: Decode<'r, DB>, { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(BString::from) } } @@ -37,7 +37,7 @@ where DB: Database, &'q [u8]: Encode<'q, DB>, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { <&[u8] as Encode>::encode(self.as_bytes(), buf) } } @@ -47,7 +47,7 @@ where DB: Database, Vec: Encode<'q, DB>, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { as Encode>::encode(self.as_bytes().to_vec(), buf) } } diff --git a/sqlx-core/src/types/json.rs b/sqlx-core/src/types/json.rs index d1c8c0ec0d..f2b58e70af 100644 --- a/sqlx-core/src/types/json.rs +++ b/sqlx-core/src/types/json.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; pub use serde_json::value::RawValue as JsonRawValue; pub use serde_json::Value as JsonValue; -use crate::database::{Database, HasArguments, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -141,7 +141,7 @@ where for<'a> Json<&'a Self>: Encode<'q, DB>, DB: Database, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { as Encode<'q, DB>>::encode(Json(self), buf) } } @@ -151,7 +151,7 @@ where Json: Decode<'r, DB>, DB: Database, { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(|item| item.0) } } @@ -177,7 +177,7 @@ where Json: Decode<'r, DB>, DB: Database, { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(|item| item.0) } } diff --git a/sqlx-core/src/types/text.rs b/sqlx-core/src/types/text.rs index 90480e6db7..9ef865ad06 100644 --- a/sqlx-core/src/types/text.rs +++ b/sqlx-core/src/types/text.rs @@ -115,7 +115,7 @@ where String: Encode<'q, DB>, DB: Database, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer<'q>) -> IsNull { self.0.to_string().encode(buf) } } @@ -127,7 +127,7 @@ where &'r str: Decode<'r, DB>, DB: Database, { - fn decode(value: >::ValueRef) -> Result { + fn decode(value: ::ValueRef<'r>) -> Result { Ok(Text(<&'r str as Decode<'r, DB>>::decode(value)?.parse()?)) } } diff --git a/sqlx-core/src/value.rs b/sqlx-core/src/value.rs index 0176e8d12a..d7996561b7 100644 --- a/sqlx-core/src/value.rs +++ b/sqlx-core/src/value.rs @@ -1,4 +1,4 @@ -use crate::database::{Database, HasValueRef}; +use crate::database::Database; use crate::decode::Decode; use crate::error::{mismatched_types, Error}; use crate::type_info::TypeInfo; @@ -10,7 +10,7 @@ pub trait Value { type Database: Database; /// Get this value as a reference. - fn as_ref(&self) -> >::ValueRef; + fn as_ref(&self) -> ::ValueRef<'_>; /// Get the type information for this value. fn type_info(&self) -> Cow<'_, ::TypeInfo>; diff --git a/sqlx-macros-core/src/derives/decode.rs b/sqlx-macros-core/src/derives/decode.rs index f926718f6c..78ce45a79d 100644 --- a/sqlx-macros-core/src/derives/decode.rs +++ b/sqlx-macros-core/src/derives/decode.rs @@ -76,7 +76,7 @@ fn expand_derive_decode_transparent( #[automatically_derived] impl #impl_generics ::sqlx::decode::Decode<'r, DB> for #ident #ty_generics #where_clause { fn decode( - value: >::ValueRef, + value: ::ValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< @@ -118,7 +118,7 @@ fn expand_derive_decode_weak_enum( #repr: ::sqlx::decode::Decode<'r, DB>, { fn decode( - value: >::ValueRef, + value: ::ValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< diff --git a/sqlx-macros-core/src/derives/encode.rs b/sqlx-macros-core/src/derives/encode.rs index 823af65ad3..df370717b5 100644 --- a/sqlx-macros-core/src/derives/encode.rs +++ b/sqlx-macros-core/src/derives/encode.rs @@ -84,7 +84,7 @@ fn expand_derive_encode_transparent( { fn encode_by_ref( &self, - buf: &mut >::ArgumentBuffer, + buf: &mut ::ArgumentBuffer<#lifetime>, ) -> ::sqlx::encode::IsNull { <#ty as ::sqlx::encode::Encode<#lifetime, DB>>::encode_by_ref(&self.0, buf) } @@ -123,7 +123,7 @@ fn expand_derive_encode_weak_enum( { fn encode_by_ref( &self, - buf: &mut >::ArgumentBuffer, + buf: &mut ::ArgumentBuffer<'q>, ) -> ::sqlx::encode::IsNull { let value = match self { #(#values)* @@ -173,7 +173,7 @@ fn expand_derive_encode_strong_enum( { fn encode_by_ref( &self, - buf: &mut >::ArgumentBuffer, + buf: &mut ::ArgumentBuffer<'q>, ) -> ::sqlx::encode::IsNull { let val = match self { #(#value_arms)* diff --git a/sqlx-macros-core/src/query/args.rs b/sqlx-macros-core/src/query/args.rs index 194a444a57..579309c44e 100644 --- a/sqlx-macros-core/src/query/args.rs +++ b/sqlx-macros-core/src/query/args.rs @@ -17,7 +17,7 @@ pub fn quote_args( if input.arg_exprs.is_empty() { return Ok(quote! { - let query_args = <#db_path as ::sqlx::database::HasArguments>::Arguments::default(); + let query_args = <#db_path as ::sqlx::database::Database>::Arguments::<'_>::default(); }); } @@ -104,7 +104,7 @@ pub fn quote_args( #args_check - let mut query_args = <#db_path as ::sqlx::database::HasArguments>::Arguments::default(); + let mut query_args = <#db_path as ::sqlx::database::Database>::Arguments::<'_>::default(); query_args.reserve( #args_count, 0 #(+ ::sqlx::encode::Encode::<#db_path>::size_hint(#arg_name))* diff --git a/sqlx-mysql/src/database.rs b/sqlx-mysql/src/database.rs index 08040d2783..d03a567284 100644 --- a/sqlx-mysql/src/database.rs +++ b/sqlx-mysql/src/database.rs @@ -3,9 +3,7 @@ use crate::{ MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlStatement, MySqlTransactionManager, MySqlTypeInfo, }; -pub(crate) use sqlx_core::database::{ - Database, HasArguments, HasStatement, HasStatementCache, HasValueRef, -}; +pub(crate) use sqlx_core::database::{Database, HasStatementCache}; /// MySQL database driver. #[derive(Debug)] @@ -25,30 +23,16 @@ impl Database for MySql { type TypeInfo = MySqlTypeInfo; type Value = MySqlValue; + type ValueRef<'r> = MySqlValueRef<'r>; - const NAME: &'static str = "MySQL"; + type Arguments<'q> = MySqlArguments; + type ArgumentBuffer<'q> = Vec; - const URL_SCHEMES: &'static [&'static str] = &["mysql", "mariadb"]; -} - -impl<'r> HasValueRef<'r> for MySql { - type Database = MySql; - - type ValueRef = MySqlValueRef<'r>; -} + type Statement<'q> = MySqlStatement<'q>; -impl HasArguments<'_> for MySql { - type Database = MySql; - - type Arguments = MySqlArguments; - - type ArgumentBuffer = Vec; -} - -impl<'q> HasStatement<'q> for MySql { - type Database = MySql; + const NAME: &'static str = "MySQL"; - type Statement = MySqlStatement<'q>; + const URL_SCHEMES: &'static [&'static str] = &["mysql", "mariadb"]; } impl HasStatementCache for MySql {} diff --git a/sqlx-postgres/src/database.rs b/sqlx-postgres/src/database.rs index c082aafe5f..876e295899 100644 --- a/sqlx-postgres/src/database.rs +++ b/sqlx-postgres/src/database.rs @@ -5,9 +5,7 @@ use crate::{ PgTypeInfo, }; -pub(crate) use sqlx_core::database::{ - Database, HasArguments, HasStatement, HasStatementCache, HasValueRef, -}; +pub(crate) use sqlx_core::database::{Database, HasStatementCache}; /// PostgreSQL database driver. #[derive(Debug)] @@ -27,30 +25,16 @@ impl Database for Postgres { type TypeInfo = PgTypeInfo; type Value = PgValue; + type ValueRef<'r> = PgValueRef<'r>; - const NAME: &'static str = "PostgreSQL"; + type Arguments<'q> = PgArguments; + type ArgumentBuffer<'q> = PgArgumentBuffer; - const URL_SCHEMES: &'static [&'static str] = &["postgres", "postgresql"]; -} - -impl<'r> HasValueRef<'r> for Postgres { - type Database = Postgres; - - type ValueRef = PgValueRef<'r>; -} + type Statement<'q> = PgStatement<'q>; -impl HasArguments<'_> for Postgres { - type Database = Postgres; - - type Arguments = PgArguments; - - type ArgumentBuffer = PgArgumentBuffer; -} - -impl<'q> HasStatement<'q> for Postgres { - type Database = Postgres; + const NAME: &'static str = "PostgreSQL"; - type Statement = PgStatement<'q>; + const URL_SCHEMES: &'static [&'static str] = &["postgres", "postgresql"]; } impl HasStatementCache for Postgres {} diff --git a/sqlx-sqlite/src/database.rs b/sqlx-sqlite/src/database.rs index 739200fa9f..c89c7b8322 100644 --- a/sqlx-sqlite/src/database.rs +++ b/sqlx-sqlite/src/database.rs @@ -1,6 +1,4 @@ -pub(crate) use sqlx_core::database::{ - Database, HasArguments, HasStatement, HasStatementCache, HasValueRef, -}; +pub(crate) use sqlx_core::database::{Database, HasStatementCache}; use crate::{ SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, @@ -26,30 +24,16 @@ impl Database for Sqlite { type TypeInfo = SqliteTypeInfo; type Value = SqliteValue; + type ValueRef<'r> = SqliteValueRef<'r>; - const NAME: &'static str = "SQLite"; + type Arguments<'q> = SqliteArguments<'q>; + type ArgumentBuffer<'q> = Vec>; - const URL_SCHEMES: &'static [&'static str] = &["sqlite"]; -} - -impl<'r> HasValueRef<'r> for Sqlite { - type Database = Sqlite; - - type ValueRef = SqliteValueRef<'r>; -} + type Statement<'q> = SqliteStatement<'q>; -impl<'q> HasArguments<'q> for Sqlite { - type Database = Sqlite; - - type Arguments = SqliteArguments<'q>; - - type ArgumentBuffer = Vec>; -} - -impl<'q> HasStatement<'q> for Sqlite { - type Database = Sqlite; + const NAME: &'static str = "SQLite"; - type Statement = SqliteStatement<'q>; + const URL_SCHEMES: &'static [&'static str] = &["sqlite"]; } impl HasStatementCache for Sqlite {} From 0d0dddf1d000e836ce124eb59500713f64c6e5d6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 14 Mar 2024 12:37:28 -0700 Subject: [PATCH 56/59] Constrain cyclic associated types (#2702) Add bounds such that cyclic associated types are equal to themselves. ``` ::Database as Database>::Connection == T ::Connection as Connection>::Options == T ::Database as Database>::Row == T ::Database as Database>::Column == T ::Database as Database>::Value == T ``` --- sqlx-core/src/column.rs | 2 +- sqlx-core/src/connection.rs | 4 ++-- sqlx-core/src/row.rs | 2 +- sqlx-core/src/value.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/column.rs b/sqlx-core/src/column.rs index b583e480d1..9f45819ed6 100644 --- a/sqlx-core/src/column.rs +++ b/sqlx-core/src/column.rs @@ -4,7 +4,7 @@ use crate::error::Error; use std::fmt::Debug; pub trait Column: 'static + Send + Sync + Debug { - type Database: Database; + type Database: Database; /// Gets the column ordinal. /// diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index 584e5c4756..802e05fb27 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -11,7 +11,7 @@ use url::Url; /// Represents a single database connection. pub trait Connection: Send { - type Database: Database; + type Database: Database; type Options: ConnectOptions; @@ -184,7 +184,7 @@ impl LogSettings { } pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug + Clone { - type Connection: Connection + ?Sized; + type Connection: Connection + ?Sized; /// Parse the `ConnectOptions` from a URL. fn from_url(url: &Url) -> Result; diff --git a/sqlx-core/src/row.rs b/sqlx-core/src/row.rs index 5923e25c03..4c96ced048 100644 --- a/sqlx-core/src/row.rs +++ b/sqlx-core/src/row.rs @@ -12,7 +12,7 @@ use crate::value::ValueRef; /// [`FromRow`]: crate::row::FromRow /// [`Query::fetch`]: crate::query::Query::fetch pub trait Row: Unpin + Send + Sync + 'static { - type Database: Database; + type Database: Database; /// Returns `true` if this row has no columns. #[inline] diff --git a/sqlx-core/src/value.rs b/sqlx-core/src/value.rs index d7996561b7..94e8b7b16a 100644 --- a/sqlx-core/src/value.rs +++ b/sqlx-core/src/value.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; /// An owned value from the database. pub trait Value { - type Database: Database; + type Database: Database; /// Get this value as a reference. fn as_ref(&self) -> ::ValueRef<'_>; From b607251fc4b1b661dd09c2e2245037ff238fe28e Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Thu, 14 Mar 2024 13:38:19 -0600 Subject: [PATCH 57/59] Implement PgHasArrayType for all references (#2869) --- sqlx-postgres/src/types/array.rs | 13 +++++++++++++ sqlx-postgres/src/types/bytes.rs | 6 ------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/sqlx-postgres/src/types/array.rs b/sqlx-postgres/src/types/array.rs index dac9b6841c..8ffcf5a353 100644 --- a/sqlx-postgres/src/types/array.rs +++ b/sqlx-postgres/src/types/array.rs @@ -55,6 +55,19 @@ pub trait PgHasArrayType { } } +impl PgHasArrayType for &T +where + T: PgHasArrayType, +{ + fn array_type_info() -> PgTypeInfo { + T::array_type_info() + } + + fn array_compatible(ty: &PgTypeInfo) -> bool { + T::array_compatible(ty) + } +} + impl PgHasArrayType for Option where T: PgHasArrayType, diff --git a/sqlx-postgres/src/types/bytes.rs b/sqlx-postgres/src/types/bytes.rs index f0e646c86e..b0c05ca840 100644 --- a/sqlx-postgres/src/types/bytes.rs +++ b/sqlx-postgres/src/types/bytes.rs @@ -16,12 +16,6 @@ impl PgHasArrayType for &'_ [u8] { } } -impl PgHasArrayType for &'_ [u8; N] { - fn array_type_info() -> PgTypeInfo { - PgTypeInfo::BYTEA_ARRAY - } -} - impl PgHasArrayType for Box<[u8]> { fn array_type_info() -> PgTypeInfo { <[&[u8]] as Type>::type_info() From e0a1f1633c115bb5653fc7bd54b233c677546b95 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea <98585737+bobozaur@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:39:02 +0200 Subject: [PATCH 58/59] Removed Send bound from arg binding (#2960) --- sqlx-core/src/any/arguments.rs | 2 +- sqlx-core/src/arguments.rs | 2 +- sqlx-core/src/query.rs | 2 +- sqlx-core/src/query_as.rs | 2 +- sqlx-core/src/query_builder.rs | 6 +++--- sqlx-core/src/query_scalar.rs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sqlx-core/src/any/arguments.rs b/sqlx-core/src/any/arguments.rs index 0c1a973905..ca438b0286 100644 --- a/sqlx-core/src/any/arguments.rs +++ b/sqlx-core/src/any/arguments.rs @@ -18,7 +18,7 @@ impl<'q> Arguments<'q> for AnyArguments<'q> { fn add(&mut self, value: T) where - T: 'q + Send + Encode<'q, Self::Database> + Type, + T: 'q + Encode<'q, Self::Database> + Type, { let _ = value.encode(&mut self.values); } diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index d8b24d8b9e..61988eb21e 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -16,7 +16,7 @@ pub trait Arguments<'q>: Send + Sized + Default { /// Add the value to the end of the arguments. fn add(&mut self, value: T) where - T: 'q + Send + Encode<'q, Self::Database> + Type; + T: 'q + Encode<'q, Self::Database> + Type; fn format_placeholder(&self, writer: &mut W) -> fmt::Result { writer.write_str("?") diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 47d884a7cf..0b90e05747 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -78,7 +78,7 @@ impl<'q, DB: Database> Query<'q, DB, ::Arguments<'q>> { /// /// There is no validation that the value is of the type expected by the query. Most SQL /// flavors will perform type coercion (Postgres will return a database error). - pub fn bind + Type>(mut self, value: T) -> Self { + pub fn bind + Type>(mut self, value: T) -> Self { if let Some(arguments) = &mut self.arguments { arguments.add(value); } diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 88714ea9a0..f84eed8e58 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -51,7 +51,7 @@ impl<'q, DB: Database, O> QueryAs<'q, DB, O, ::Arguments<'q>> { /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](Query::bind). - pub fn bind + Type>(mut self, value: T) -> Self { + pub fn bind + Type>(mut self, value: T) -> Self { self.inner = self.inner.bind(value); self } diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs index 2c9dba591f..a764ad3209 100644 --- a/sqlx-core/src/query_builder.rs +++ b/sqlx-core/src/query_builder.rs @@ -147,7 +147,7 @@ where /// [postgres-limit-issue]: https://github.com/launchbadge/sqlx/issues/671#issuecomment-687043510 pub fn push_bind(&mut self, value: T) -> &mut Self where - T: 'args + Encode<'args, DB> + Send + Type, + T: 'args + Encode<'args, DB> + Type, { self.sanity_check(); @@ -569,7 +569,7 @@ where /// See [`QueryBuilder::push_bind()`] for details. pub fn push_bind(&mut self, value: T) -> &mut Self where - T: 'args + Encode<'args, DB> + Send + Type, + T: 'args + Encode<'args, DB> + Type, { if self.push_separator { self.query_builder.push(&self.separator); @@ -587,7 +587,7 @@ where /// Simply calls [`QueryBuilder::push_bind()`] directly. pub fn push_bind_unseparated(&mut self, value: T) -> &mut Self where - T: 'args + Encode<'args, DB> + Send + Type, + T: 'args + Encode<'args, DB> + Type, { self.query_builder.push_bind(value); self diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs index 509d7e6c83..1fcc577d33 100644 --- a/sqlx-core/src/query_scalar.rs +++ b/sqlx-core/src/query_scalar.rs @@ -48,7 +48,7 @@ impl<'q, DB: Database, O> QueryScalar<'q, DB, O, ::Arguments<'q> /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](crate::query::Query::bind). - pub fn bind + Type>(mut self, value: T) -> Self { + pub fn bind + Type>(mut self, value: T) -> Self { self.inner = self.inner.bind(value); self } From 59e3c0010b30e4b13b624e4c3289abea0c4fa7b3 Mon Sep 17 00:00:00 2001 From: benluelo Date: Wed, 1 Feb 2023 00:01:39 -0500 Subject: [PATCH 59/59] fix: Decode and Encode derives (#1031) --- sqlx-macros-core/src/derives/decode.rs | 27 ++++++++++++-------------- sqlx-macros-core/src/derives/encode.rs | 27 ++++++++++++-------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/sqlx-macros-core/src/derives/decode.rs b/sqlx-macros-core/src/derives/decode.rs index 78ce45a79d..ba4535a83e 100644 --- a/sqlx-macros-core/src/derives/decode.rs +++ b/sqlx-macros-core/src/derives/decode.rs @@ -9,7 +9,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Arm, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, - FieldsUnnamed, Stmt, Variant, + FieldsUnnamed, Stmt, TypeParamBound, Variant, }; pub fn expand_derive_decode(input: &DeriveInput) -> syn::Result { @@ -265,24 +265,21 @@ fn expand_derive_decode_struct( if cfg!(feature = "postgres") { let ident = &input.ident; - // extract type generics - let generics = &input.generics; - let (_, ty_generics, _) = generics.split_for_impl(); + let (_, ty_generics, where_clause) = input.generics.split_for_impl(); - // add db type for impl generics & where clause - let mut generics = generics.clone(); - generics.params.insert(0, parse_quote!('r)); - - let predicates = &mut generics.make_where_clause().predicates; - - for field in fields { - let ty = &field.ty; + let mut generics = input.generics.clone(); - predicates.push(parse_quote!(#ty: ::sqlx::decode::Decode<'r, ::sqlx::Postgres>)); - predicates.push(parse_quote!(#ty: ::sqlx::types::Type<::sqlx::Postgres>)); + // add db type for impl generics & where clause + for type_param in &mut generics.type_params_mut() { + type_param.bounds.extend::<[TypeParamBound; 2]>([ + parse_quote!(for<'decode> ::sqlx::decode::Decode<'decode, ::sqlx::Postgres>), + parse_quote!(::sqlx::types::Type<::sqlx::Postgres>), + ]); } - let (impl_generics, _, where_clause) = generics.split_for_impl(); + generics.params.push(parse_quote!('r)); + + let (impl_generics, _, _) = generics.split_for_impl(); let reads = fields.iter().map(|field| -> Stmt { let id = &field.ident; diff --git a/sqlx-macros-core/src/derives/encode.rs b/sqlx-macros-core/src/derives/encode.rs index df370717b5..b271c90411 100644 --- a/sqlx-macros-core/src/derives/encode.rs +++ b/sqlx-macros-core/src/derives/encode.rs @@ -9,7 +9,7 @@ use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, - FieldsUnnamed, Lifetime, LifetimeParam, Stmt, Variant, + FieldsUnnamed, Lifetime, LifetimeParam, Stmt, TypeParamBound, Variant, }; pub fn expand_derive_encode(input: &DeriveInput) -> syn::Result { @@ -205,24 +205,21 @@ fn expand_derive_encode_struct( let ident = &input.ident; let column_count = fields.len(); - // extract type generics - let generics = &input.generics; - let (_, ty_generics, _) = generics.split_for_impl(); + let (_, ty_generics, where_clause) = input.generics.split_for_impl(); - // add db type for impl generics & where clause - let mut generics = generics.clone(); - - let predicates = &mut generics.make_where_clause().predicates; - - for field in fields { - let ty = &field.ty; + let mut generics = input.generics.clone(); - predicates - .push(parse_quote!(#ty: for<'q> ::sqlx::encode::Encode<'q, ::sqlx::Postgres>)); - predicates.push(parse_quote!(#ty: ::sqlx::types::Type<::sqlx::Postgres>)); + // add db type for impl generics & where clause + for type_param in &mut generics.type_params_mut() { + type_param.bounds.extend::<[TypeParamBound; 2]>([ + parse_quote!(for<'encode> ::sqlx::encode::Encode<'encode, ::sqlx::Postgres>), + parse_quote!(::sqlx::types::Type<::sqlx::Postgres>), + ]); } - let (impl_generics, _, where_clause) = generics.split_for_impl(); + generics.params.push(parse_quote!('q)); + + let (impl_generics, _, _) = generics.split_for_impl(); let writes = fields.iter().map(|field| -> Stmt { let id = &field.ident;