From 883c3988a5996f1d432966398b7c126681c10cbf Mon Sep 17 00:00:00 2001 From: James Holman Date: Thu, 30 Jan 2025 20:07:38 +1100 Subject: [PATCH 01/26] feat: add geometry path --- sqlx-postgres/src/type_checking.rs | 2 + sqlx-postgres/src/types/geometry/mod.rs | 1 + sqlx-postgres/src/types/geometry/path.rs | 372 +++++++++++++++++++++++ sqlx-postgres/src/types/mod.rs | 2 + tests/postgres/types.rs | 11 + 5 files changed, 388 insertions(+) create mode 100644 sqlx-postgres/src/types/geometry/path.rs diff --git a/sqlx-postgres/src/type_checking.rs b/sqlx-postgres/src/type_checking.rs index 68a4fcfeff..5758c264a1 100644 --- a/sqlx-postgres/src/type_checking.rs +++ b/sqlx-postgres/src/type_checking.rs @@ -40,6 +40,8 @@ impl_type_checking!( sqlx::postgres::types::PgBox, + sqlx::postgres::types::PgPath, + #[cfg(feature = "uuid")] sqlx::types::Uuid, diff --git a/sqlx-postgres/src/types/geometry/mod.rs b/sqlx-postgres/src/types/geometry/mod.rs index 7fe2898fcd..f67846fef2 100644 --- a/sqlx-postgres/src/types/geometry/mod.rs +++ b/sqlx-postgres/src/types/geometry/mod.rs @@ -1,4 +1,5 @@ pub mod r#box; pub mod line; pub mod line_segment; +pub mod path; pub mod point; diff --git a/sqlx-postgres/src/types/geometry/path.rs b/sqlx-postgres/src/types/geometry/path.rs new file mode 100644 index 0000000000..87a3b3e8d3 --- /dev/null +++ b/sqlx-postgres/src/types/geometry/path.rs @@ -0,0 +1,372 @@ +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::{PgPoint, Type}; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; +use sqlx_core::bytes::Buf; +use sqlx_core::Error; +use std::mem; +use std::str::FromStr; + +const BYTE_WIDTH: usize = mem::size_of::(); + +/// ## Postgres Geometric Path type +/// +/// Description: Open path or Closed path (similar to polygon) +/// Representation: Open `[(x1,y1),...]`, Closed `((x1,y1),...)` +/// +/// Paths are represented by lists of connected points. Paths can be open, where the first and last points in the list are considered not connected, or closed, where the first and last points are considered connected. +/// Values of type path are specified using any of the following syntaxes: +/// ```text +/// [ ( x1 , y1 ) , ... , ( xn , yn ) ] +/// ( ( x1 , y1 ) , ... , ( xn , yn ) ) +/// ( x1 , y1 ) , ... , ( xn , yn ) +/// ( x1 , y1 , ... , xn , yn ) +/// x1 , y1 , ... , xn , yn +/// ``` +/// where the points are the end points of the line segments comprising the path. Square brackets `([])` indicate an open path, while parentheses `(())` indicate a closed path. +/// When the outermost parentheses are omitted, as in the third through fifth syntaxes, a closed path is assumed. +/// +/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-GEOMETRIC-PATHS +#[derive(Debug, Clone, PartialEq)] +pub struct PgPath { + pub closed: bool, + pub points: Vec, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct Header { + is_closed: bool, + length: usize, +} + +impl Type for PgPath { + fn type_info() -> PgTypeInfo { + PgTypeInfo::with_name("path") + } +} + +impl PgHasArrayType for PgPath { + fn array_type_info() -> PgTypeInfo { + PgTypeInfo::with_name("_path") + } +} + +impl<'r> Decode<'r, Postgres> for PgPath { + fn decode(value: PgValueRef<'r>) -> Result> { + match value.format() { + PgValueFormat::Text => Ok(PgPath::from_str(value.as_str()?)?), + PgValueFormat::Binary => Ok(PgPath::from_bytes(value.as_bytes()?)?), + } + } +} + +impl<'q> Encode<'q, Postgres> for PgPath { + fn produces(&self) -> Option { + Some(PgTypeInfo::with_name("path")) + } + + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { + self.serialize(buf)?; + Ok(IsNull::No) + } +} + +impl FromStr for PgPath { + type Err = Error; + + fn from_str(s: &str) -> Result { + let closed = !s.contains('['); + let sanitised = s.replace(['(', ')', '[', ']', ' '], ""); + let parts = sanitised.split(',').collect::>(); + + let mut points = vec![]; + + if parts.len() % 2 != 0 { + return Err(Error::Decode( + format!("Unmatched pair in PATH: {}", s).into(), + )); + } + + for chunk in parts.chunks_exact(2) { + if let [x_str, y_str] = chunk { + let x = parse_float_from_str(x_str, "could not get x")?; + let y = parse_float_from_str(y_str, "could not get y")?; + + let point = PgPoint { x, y }; + points.push(point); + } + } + + if !points.is_empty() { + return Ok(PgPath { points, closed }); + } + + Err(Error::Decode( + format!("could not get path from {}", s).into(), + )) + } +} + +impl PgPath { + fn header(&self) -> Header { + Header { + is_closed: self.closed, + length: self.points.len(), + } + } + + fn from_bytes(mut bytes: &[u8]) -> Result { + let header = Header::try_read(&mut bytes)?; + + if bytes.len() != header.data_size() { + return Err(format!( + "expected {} bytes after header, got {}", + header.data_size(), + bytes.len() + ) + .into()); + } + + if bytes.len() % BYTE_WIDTH * 2 != 0 { + return Err(format!( + "data length not divisible by pairs of {BYTE_WIDTH}: {}", + bytes.len() + ) + .into()); + } + + let mut out_points = Vec::with_capacity(bytes.len() / (BYTE_WIDTH * 2)); + + while bytes.has_remaining() { + let point = PgPoint { + x: bytes.get_f64(), + y: bytes.get_f64(), + }; + out_points.push(point) + } + Ok(PgPath { + closed: header.is_closed, + points: out_points, + }) + } + + fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { + let header = self.header(); + buff.reserve(header.data_size()); + header.try_write(buff)?; + + for point in &self.points { + buff.extend_from_slice(&point.x.to_be_bytes()); + buff.extend_from_slice(&point.y.to_be_bytes()); + } + Ok(()) + } + + #[cfg(test)] + fn serialize_to_vec(&self) -> Vec { + let mut buff = PgArgumentBuffer::default(); + self.serialize(&mut buff).unwrap(); + buff.to_vec() + } +} + +impl Header { + const HEADER_WIDTH: usize = mem::size_of::() + mem::size_of::(); + + fn data_size(&self) -> usize { + self.length * BYTE_WIDTH * 2 + } + + fn try_read(buf: &mut &[u8]) -> Result { + if buf.len() < Self::HEADER_WIDTH { + return Err(format!( + "expected PATH data to contain at least {} bytes, got {}", + Self::HEADER_WIDTH, + buf.len() + )); + } + + let is_closed = buf.get_i8(); + let length = buf.get_i32(); + + let length = usize::try_from(length).ok().ok_or_else(|| { + format!( + "received PATH data length: {length}. Expected length between 0 and {}", + usize::MAX + ) + })?; + + Ok(Self { + is_closed: is_closed != 0, + length, + }) + } + + fn try_write(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { + let is_closed = self.is_closed as i8; + + let length = i32::try_from(self.length).map_err(|_| { + format!( + "PATH length exceeds allowed maximum ({} > {})", + self.length, + i32::MAX + ) + })?; + + buff.extend(is_closed.to_be_bytes()); + buff.extend(length.to_be_bytes()); + + Ok(()) + } +} + +fn parse_float_from_str(s: &str, error_msg: &str) -> Result { + s.parse().map_err(|_| Error::Decode(error_msg.into())) +} + +#[cfg(test)] +mod path_tests { + + use std::str::FromStr; + + use crate::types::PgPoint; + + use super::PgPath; + + const PATH_CLOSED_BYTES: &[u8] = &[ + 1, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, + 64, 16, 0, 0, 0, 0, 0, 0, + ]; + + const PATH_OPEN_BYTES: &[u8] = &[ + 0, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, + 64, 16, 0, 0, 0, 0, 0, 0, + ]; + + const PATH_UNEVEN_POINTS: &[u8] = &[ + 0, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, + 64, 16, 0, 0, + ]; + + #[test] + fn can_deserialise_path_type_bytes_closed() { + let path = PgPath::from_bytes(PATH_CLOSED_BYTES).unwrap(); + assert_eq!( + path, + PgPath { + closed: true, + points: vec![PgPoint { x: 1.0, y: 2.0 }, PgPoint { x: 3.0, y: 4.0 }] + } + ) + } + + #[test] + fn cannot_deserialise_path_type_uneven_point_bytes() { + let path = PgPath::from_bytes(PATH_UNEVEN_POINTS); + assert!(path.is_err()); + + if let Err(err) = path { + assert_eq!( + err.to_string(), + format!("expected 32 bytes after header, got 28") + ) + } + } + + #[test] + fn can_deserialise_path_type_bytes_open() { + let path = PgPath::from_bytes(PATH_OPEN_BYTES).unwrap(); + assert_eq!( + path, + PgPath { + closed: false, + points: vec![PgPoint { x: 1.0, y: 2.0 }, PgPoint { x: 3.0, y: 4.0 }] + } + ) + } + + #[test] + fn can_deserialise_path_type_str_first_syntax() { + let path = PgPath::from_str("[( 1, 2), (3, 4 )]").unwrap(); + assert_eq!( + path, + PgPath { + closed: false, + points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] + } + ); + } + + #[test] + fn cannot_deserialise_path_type_str_uneven_points_first_syntax() { + let input_str = "[( 1, 2), (3)]"; + let path = PgPath::from_str(input_str); + + assert!(path.is_err()); + + if let Err(err) = path { + assert_eq!( + err.to_string(), + format!("error occurred while decoding: Unmatched pair in PATH: {input_str}") + ) + } + } + + #[test] + fn can_deserialise_path_type_str_second_syntax() { + let path = PgPath::from_str("(( 1, 2), (3, 4 ))").unwrap(); + assert_eq!( + path, + PgPath { + closed: true, + points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] + } + ); + } + + #[test] + fn can_deserialise_path_type_str_third_syntax() { + let path = PgPath::from_str("(1, 2), (3, 4 )").unwrap(); + assert_eq!( + path, + PgPath { + closed: true, + points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] + } + ); + } + + #[test] + fn can_deserialise_path_type_str_fourth_syntax() { + let path = PgPath::from_str("1, 2, 3, 4").unwrap(); + assert_eq!( + path, + PgPath { + closed: true, + points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] + } + ); + } + + #[test] + fn can_deserialise_path_type_str_float() { + let path = PgPath::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap(); + assert_eq!( + path, + PgPath { + closed: true, + points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }] + } + ); + } + + #[test] + fn can_serialise_path_type() { + let path = PgPath { + closed: true, + points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }], + }; + assert_eq!(path.serialize_to_vec(), PATH_CLOSED_BYTES,) + } +} diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index e53f88ffec..f1efdef0f5 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -25,6 +25,7 @@ //! | [`PgLine] | LINE | //! | [`PgLSeg] | LSEG | //! | [`PgBox] | BOX | +//! | [`PgPath] | PATH | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., @@ -262,6 +263,7 @@ pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; +pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index ccf88b1099..2a65f9c136 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -524,6 +524,17 @@ test_type!(_box>(Postgres, "array[box('1,2,3,4'),box('((1.1, 2.2), (3.3, 4.4))')]" @= vec![sqlx::postgres::types::PgBox { upper_right_x: 3., upper_right_y: 4., lower_left_x: 1., lower_left_y: 2. }, sqlx::postgres::types::PgBox { upper_right_x: 3.3, upper_right_y: 4.4, lower_left_x: 1.1, lower_left_y: 2.2 }], )); +#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] +test_type!(path(Postgres, + "path('((1.0, 2.0), (3.0,4.0))')" == sqlx::postgres::types::PgPath { closed: true, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, + "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, +)); + +#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] +test_type!(_path>(Postgres, + "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3 , y: 4.4 } ]},], +)); + #[cfg(feature = "rust_decimal")] test_type!(decimal(Postgres, "0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(), From d75a3fe0b6d84d0eda2d70b71fb27399497f713e Mon Sep 17 00:00:00 2001 From: James Holman Date: Thu, 30 Jan 2025 20:16:34 +1100 Subject: [PATCH 02/26] fix: paths to pg point --- tests/postgres/types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 2a65f9c136..fa9d7353ec 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -526,13 +526,13 @@ test_type!(_box>(Postgres, #[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] test_type!(path(Postgres, - "path('((1.0, 2.0), (3.0,4.0))')" == sqlx::postgres::types::PgPath { closed: true, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, - "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, + "path('((1.0, 2.0), (3.0,4.0))')" == sqlx::postgres::types::PgPath { closed: true, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, + "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, )); #[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] test_type!(_path>(Postgres, - "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ PgPoint { x: 1., y: 2. }, PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3 , y: 4.4 } ]},], + "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1.1, y: 2.2 }, sqlx::postgres::types::PgPoint { x: 3.3 , y: 4.4 } ]},], )); #[cfg(feature = "rust_decimal")] From c0f728a0ef0af8265ad3f273c52af2ebbcfd92dd Mon Sep 17 00:00:00 2001 From: James Holman Date: Thu, 30 Jan 2025 20:22:21 +1100 Subject: [PATCH 03/26] test: remove array tests for path --- tests/postgres/types.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index fa9d7353ec..0d15caf8de 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -530,11 +530,6 @@ test_type!(path(Postgres, "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, )); -#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] -test_type!(_path>(Postgres, - "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1.1, y: 2.2 }, sqlx::postgres::types::PgPoint { x: 3.3 , y: 4.4 } ]},], -)); - #[cfg(feature = "rust_decimal")] test_type!(decimal(Postgres, "0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(), From 12e1d74258491bfc7a2d5fe76c185bb4856cf1c6 Mon Sep 17 00:00:00 2001 From: Jon Thacker Date: Fri, 31 Jan 2025 15:22:47 -0800 Subject: [PATCH 04/26] Fix readme: uuid feature is gating for all repos (#3720) The readme previously stated that the uuid feature is only for postres but it actually also gates the functionality in mysql and sqlite. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15d68bbb42..c3b501ca48 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ be removed in the future. - `migrate`: Add support for the migration management and `migrate!` macro, which allow compile-time embedded migrations. -- `uuid`: Add support for UUID (in Postgres). +- `uuid`: Add support for UUID. - `chrono`: Add support for date and time types from `chrono`. From db649f932f2dc47c9ee5860cfd42b23a5f2b32f2 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 2 Feb 2025 01:01:56 +0100 Subject: [PATCH 05/26] Replace some futures_util APIs with std variants (#3721) --- sqlx-core/src/io/write_and_flush.rs | 5 ++--- sqlx-core/src/net/socket/mod.rs | 3 +-- sqlx-core/src/net/tls/util.rs | 3 +-- sqlx-core/src/pool/inner.rs | 16 +++++----------- sqlx-core/src/pool/mod.rs | 6 +++--- sqlx-mysql/src/any.rs | 5 ++--- sqlx-mysql/src/connection/executor.rs | 7 +++---- sqlx-postgres/src/any.rs | 5 ++--- sqlx-postgres/src/connection/executor.rs | 10 ++++------ sqlx-sqlite/src/any.rs | 13 +++++++------ sqlx-sqlite/src/connection/executor.rs | 8 +++----- tests/postgres/postgres.rs | 5 ++--- 12 files changed, 35 insertions(+), 51 deletions(-) diff --git a/sqlx-core/src/io/write_and_flush.rs b/sqlx-core/src/io/write_and_flush.rs index 9e7824af81..8a0db31293 100644 --- a/sqlx-core/src/io/write_and_flush.rs +++ b/sqlx-core/src/io/write_and_flush.rs @@ -1,10 +1,9 @@ use crate::error::Error; -use futures_core::Future; -use futures_util::ready; use sqlx_rt::AsyncWrite; +use std::future::Future; use std::io::{BufRead, Cursor}; use std::pin::Pin; -use std::task::{Context, Poll}; +use std::task::{ready, Context, Poll}; // Atomic operation that writes the full buffer to the stream, flushes the stream, and then // clears the buffer (even if either of the two previous operations failed). diff --git a/sqlx-core/src/net/socket/mod.rs b/sqlx-core/src/net/socket/mod.rs index 6b09d318f7..d11f15884e 100644 --- a/sqlx-core/src/net/socket/mod.rs +++ b/sqlx-core/src/net/socket/mod.rs @@ -2,10 +2,9 @@ use std::future::Future; use std::io; use std::path::Path; use std::pin::Pin; -use std::task::{Context, Poll}; +use std::task::{ready, Context, Poll}; use bytes::BufMut; -use futures_core::ready; pub use buffered::{BufferedSocket, WriteBuffer}; diff --git a/sqlx-core/src/net/tls/util.rs b/sqlx-core/src/net/tls/util.rs index 02a16ef5e1..ddbc7a58f2 100644 --- a/sqlx-core/src/net/tls/util.rs +++ b/sqlx-core/src/net/tls/util.rs @@ -1,9 +1,8 @@ use crate::net::Socket; use std::io::{self, Read, Write}; -use std::task::{Context, Poll}; +use std::task::{ready, Context, Poll}; -use futures_core::ready; use futures_util::future; pub struct StdSocket { diff --git a/sqlx-core/src/pool/inner.rs b/sqlx-core/src/pool/inner.rs index bbcc43134e..2066364a8e 100644 --- a/sqlx-core/src/pool/inner.rs +++ b/sqlx-core/src/pool/inner.rs @@ -10,6 +10,7 @@ use crate::sync::{AsyncSemaphore, AsyncSemaphoreReleaser}; use std::cmp; use std::future::Future; +use std::pin::pin; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::sync::{Arc, RwLock}; use std::task::Poll; @@ -130,19 +131,12 @@ impl PoolInner { // This is just going to cause unnecessary churn in `acquire()`. .filter(|_| self.size() < self.options.max_connections); - let acquire_self = self.semaphore.acquire(1).fuse(); - let mut close_event = self.close_event(); + let mut acquire_self = pin!(self.semaphore.acquire(1).fuse()); + let mut close_event = pin!(self.close_event()); if let Some(parent) = parent { - let acquire_parent = parent.0.semaphore.acquire(1); - let parent_close_event = parent.0.close_event(); - - futures_util::pin_mut!( - acquire_parent, - acquire_self, - close_event, - parent_close_event - ); + let mut acquire_parent = pin!(parent.0.semaphore.acquire(1)); + let mut parent_close_event = pin!(parent.0.close_event()); let mut poll_parent = false; diff --git a/sqlx-core/src/pool/mod.rs b/sqlx-core/src/pool/mod.rs index e998618413..042bc5c7bc 100644 --- a/sqlx-core/src/pool/mod.rs +++ b/sqlx-core/src/pool/mod.rs @@ -56,7 +56,7 @@ use std::fmt; use std::future::Future; -use std::pin::Pin; +use std::pin::{pin, Pin}; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; @@ -565,11 +565,11 @@ impl CloseEvent { .await .map_or(Ok(()), |_| Err(Error::PoolClosed))?; - futures_util::pin_mut!(fut); + let mut fut = pin!(fut); // I find that this is clearer in intent than `futures_util::future::select()` // or `futures_util::select_biased!{}` (which isn't enabled anyway). - futures_util::future::poll_fn(|cx| { + std::future::poll_fn(|cx| { // Poll `fut` first as the wakeup event is more likely for it than `self`. if let Poll::Ready(ret) = fut.as_mut().poll(cx) { return Poll::Ready(Ok(ret)); diff --git a/sqlx-mysql/src/any.rs b/sqlx-mysql/src/any.rs index 0466bfc0a4..e01e41d68e 100644 --- a/sqlx-mysql/src/any.rs +++ b/sqlx-mysql/src/any.rs @@ -16,7 +16,7 @@ use sqlx_core::database::Database; use sqlx_core::describe::Describe; use sqlx_core::executor::Executor; use sqlx_core::transaction::TransactionManager; -use std::future; +use std::{future, pin::pin}; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = MySql); @@ -113,8 +113,7 @@ impl AnyConnectionBackend for MySqlConnection { Box::pin(async move { let arguments = arguments?; - let stream = self.run(query, arguments, persistent).await?; - futures_util::pin_mut!(stream); + let mut stream = pin!(self.run(query, arguments, persistent).await?); while let Some(result) = stream.try_next().await? { if let Either::Right(row) = result { diff --git a/sqlx-mysql/src/connection/executor.rs b/sqlx-mysql/src/connection/executor.rs index d7f8fcfa14..d93aac0d68 100644 --- a/sqlx-mysql/src/connection/executor.rs +++ b/sqlx-mysql/src/connection/executor.rs @@ -21,8 +21,8 @@ use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_core::Stream; -use futures_util::{pin_mut, TryStreamExt}; -use std::{borrow::Cow, sync::Arc}; +use futures_util::TryStreamExt; +use std::{borrow::Cow, pin::pin, sync::Arc}; impl MySqlConnection { async fn prepare_statement<'c>( @@ -263,8 +263,7 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection { Box::pin(try_stream! { let arguments = arguments?; - let s = self.run(sql, arguments, persistent).await?; - pin_mut!(s); + let mut s = pin!(self.run(sql, arguments, persistent).await?); while let Some(v) = s.try_next().await? { r#yield!(v); diff --git a/sqlx-postgres/src/any.rs b/sqlx-postgres/src/any.rs index efa9a044bc..a7b30fb65b 100644 --- a/sqlx-postgres/src/any.rs +++ b/sqlx-postgres/src/any.rs @@ -5,7 +5,7 @@ use crate::{ use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::{stream, StreamExt, TryFutureExt, TryStreamExt}; -use std::future; +use std::{future, pin::pin}; use sqlx_core::any::{ Any, AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, @@ -115,8 +115,7 @@ impl AnyConnectionBackend for PgConnection { Box::pin(async move { let arguments = arguments?; - let stream = self.run(query, arguments, 1, persistent, None).await?; - futures_util::pin_mut!(stream); + let mut stream = pin!(self.run(query, arguments, 1, persistent, None).await?); if let Some(Either::Right(row)) = stream.try_next().await? { return Ok(Some(AnyRow::try_from(&row)?)); diff --git a/sqlx-postgres/src/connection/executor.rs b/sqlx-postgres/src/connection/executor.rs index 97503a5004..076c4209f6 100644 --- a/sqlx-postgres/src/connection/executor.rs +++ b/sqlx-postgres/src/connection/executor.rs @@ -15,10 +15,10 @@ use crate::{ use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_core::Stream; -use futures_util::{pin_mut, TryStreamExt}; +use futures_util::TryStreamExt; use sqlx_core::arguments::Arguments; use sqlx_core::Either; -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, pin::pin, sync::Arc}; async fn prepare( conn: &mut PgConnection, @@ -393,8 +393,7 @@ impl<'c> Executor<'c> for &'c mut PgConnection { Box::pin(try_stream! { let arguments = arguments?; - let s = self.run(sql, arguments, 0, persistent, metadata).await?; - pin_mut!(s); + let mut s = pin!(self.run(sql, arguments, 0, persistent, metadata).await?); while let Some(v) = s.try_next().await? { r#yield!(v); @@ -420,8 +419,7 @@ impl<'c> Executor<'c> for &'c mut PgConnection { Box::pin(async move { let arguments = arguments?; - let s = self.run(sql, arguments, 1, persistent, metadata).await?; - pin_mut!(s); + let mut s = pin!(self.run(sql, arguments, 1, persistent, metadata).await?); // With deferred constraints we need to check all responses as we // could get a OK response (with uncommitted data), only to get an diff --git a/sqlx-sqlite/src/any.rs b/sqlx-sqlite/src/any.rs index 01600d9931..2cc5855405 100644 --- a/sqlx-sqlite/src/any.rs +++ b/sqlx-sqlite/src/any.rs @@ -17,6 +17,7 @@ use sqlx_core::database::Database; use sqlx_core::describe::Describe; use sqlx_core::executor::Executor; use sqlx_core::transaction::TransactionManager; +use std::pin::pin; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Sqlite); @@ -105,12 +106,12 @@ impl AnyConnectionBackend for SqliteConnection { let args = arguments.map(map_arguments); Box::pin(async move { - let stream = self - .worker - .execute(query, args, self.row_channel_size, persistent, Some(1)) - .map_ok(flume::Receiver::into_stream) - .await?; - futures_util::pin_mut!(stream); + let mut stream = pin!( + self.worker + .execute(query, args, self.row_channel_size, persistent, Some(1)) + .map_ok(flume::Receiver::into_stream) + .await? + ); if let Some(Either::Right(row)) = stream.try_next().await? { return Ok(Some(AnyRow::try_from(&row)?)); diff --git a/sqlx-sqlite/src/connection/executor.rs b/sqlx-sqlite/src/connection/executor.rs index 541a4f7d4d..1f6ce7726f 100644 --- a/sqlx-sqlite/src/connection/executor.rs +++ b/sqlx-sqlite/src/connection/executor.rs @@ -8,7 +8,7 @@ use sqlx_core::describe::Describe; use sqlx_core::error::Error; use sqlx_core::executor::{Execute, Executor}; use sqlx_core::Either; -use std::future; +use std::{future, pin::pin}; impl<'c> Executor<'c> for &'c mut SqliteConnection { type Database = Sqlite; @@ -56,13 +56,11 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection { let persistent = query.persistent() && arguments.is_some(); Box::pin(async move { - let stream = self + let mut stream = pin!(self .worker .execute(sql, arguments, self.row_channel_size, persistent, Some(1)) .map_ok(flume::Receiver::into_stream) - .try_flatten_stream(); - - futures_util::pin_mut!(stream); + .try_flatten_stream()); while let Some(res) = stream.try_next().await? { if let Either::Right(row) = res { diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 86707e23e6..7de4a9cdc6 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -9,7 +9,7 @@ use sqlx::{Column, Connection, Executor, Row, Statement, TypeInfo}; use sqlx_core::{bytes::Bytes, error::BoxDynError}; use sqlx_test::{new, pool, setup_if_needed}; use std::env; -use std::pin::Pin; +use std::pin::{pin, Pin}; use std::sync::Arc; use std::time::Duration; @@ -637,8 +637,7 @@ async fn pool_smoke_test() -> anyhow::Result<()> { let pool = pool.clone(); sqlx_core::rt::spawn(async move { while !pool.is_closed() { - let acquire = pool.acquire(); - futures::pin_mut!(acquire); + let mut acquire = pin!(pool.acquire()); // poll the acquire future once to put the waiter in the queue future::poll_fn(move |cx| { From 4932c1c216e0e89c9cc4a54f6692fef6fd41f575 Mon Sep 17 00:00:00 2001 From: Ben Wilber Date: Tue, 4 Feb 2025 13:52:43 -0500 Subject: [PATCH 06/26] feat(sqlx-cli): Add flag to disable automatic loading of .env files (#3724) * Add flag to disable automatic loading of .env files * Update sqlx-cli/src/opt.rs Co-authored-by: Austin Bonander --------- Co-authored-by: Austin Bonander --- sqlx-cli/src/bin/cargo-sqlx.rs | 5 ++++- sqlx-cli/src/bin/sqlx.rs | 9 +++++++-- sqlx-cli/src/opt.rs | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sqlx-cli/src/bin/cargo-sqlx.rs b/sqlx-cli/src/bin/cargo-sqlx.rs index 58f7b345f4..c87147b6a3 100644 --- a/sqlx-cli/src/bin/cargo-sqlx.rs +++ b/sqlx-cli/src/bin/cargo-sqlx.rs @@ -13,9 +13,12 @@ enum Cli { #[tokio::main] async fn main() { - dotenvy::dotenv().ok(); let Cli::Sqlx(opt) = Cli::parse(); + if !opt.no_dotenv { + dotenvy::dotenv().ok(); + } + if let Err(error) = sqlx_cli::run(opt).await { println!("{} {}", style("error:").bold().red(), error); process::exit(1); diff --git a/sqlx-cli/src/bin/sqlx.rs b/sqlx-cli/src/bin/sqlx.rs index 59025cd7da..c19b61f393 100644 --- a/sqlx-cli/src/bin/sqlx.rs +++ b/sqlx-cli/src/bin/sqlx.rs @@ -4,9 +4,14 @@ use sqlx_cli::Opt; #[tokio::main] async fn main() { - dotenvy::dotenv().ok(); + let opt = Opt::parse(); + + if !opt.no_dotenv { + dotenvy::dotenv().ok(); + } + // no special handling here - if let Err(error) = sqlx_cli::run(Opt::parse()).await { + if let Err(error) = sqlx_cli::run(opt).await { println!("{} {}", style("error:").bold().red(), error); std::process::exit(1); } diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index d5fe315234..07058aa147 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -7,6 +7,10 @@ use clap_complete::Shell; #[derive(Parser, Debug)] #[clap(version, about, author)] pub struct Opt { + /// Do not automatically load `.env` files. + #[clap(long)] + pub no_dotenv: bool, + #[clap(subcommand)] pub command: Command, } From 81bfc8978038c9d6ef9d6cef69dffda0addbe6f0 Mon Sep 17 00:00:00 2001 From: joeydewaal <99046430+joeydewaal@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:53:05 +0100 Subject: [PATCH 07/26] chore: expose bstr feature (#3714) --- Cargo.toml | 2 ++ sqlx-core/src/types/mod.rs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f93ed3dded..5a040e546f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ _unstable-all-types = [ "mac_address", "uuid", "bit-vec", + "bstr" ] # Base runtime features without TLS @@ -122,6 +123,7 @@ rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mys time = ["sqlx-core/time", "sqlx-macros?/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"] uuid = ["sqlx-core/uuid", "sqlx-macros?/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"] regexp = ["sqlx-sqlite?/regexp"] +bstr = ["sqlx-core/bstr"] [workspace.dependencies] # Core Crates diff --git a/sqlx-core/src/types/mod.rs b/sqlx-core/src/types/mod.rs index 25837b1e77..909dd4927b 100644 --- a/sqlx-core/src/types/mod.rs +++ b/sqlx-core/src/types/mod.rs @@ -85,6 +85,9 @@ pub mod mac_address { pub use json::{Json, JsonRawValue, JsonValue}; pub use text::Text; +#[cfg(feature = "bstr")] +pub use bstr::{BStr, BString}; + /// Indicates that a SQL type is supported for a database. /// /// ## Compile-time verification From 218cb2081c396322a1ce03d7b276f2a60da8e229 Mon Sep 17 00:00:00 2001 From: tottoto Date: Thu, 6 Feb 2025 06:48:19 +0900 Subject: [PATCH 08/26] chore: replace rustls-pemfile with rustls-pki-types (#3725) --- Cargo.lock | 11 +---------- sqlx-core/Cargo.toml | 5 ++--- sqlx-core/src/net/tls/tls_rustls.rs | 22 ++++++++++------------ 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c112899415..c24d2ff8fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,15 +3143,6 @@ dependencies = [ "security-framework 3.2.0", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.10.1" @@ -3591,7 +3582,7 @@ dependencies = [ "rust_decimal", "rustls", "rustls-native-certs", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "sha2", diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index f767507bb4..dcd8083023 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -25,7 +25,7 @@ _tls-native-tls = ["native-tls"] _tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs", "webpki-roots"] _tls-rustls-ring-webpki = ["_tls-rustls", "rustls/ring", "webpki-roots"] _tls-rustls-ring-native-roots = ["_tls-rustls", "rustls/ring", "rustls-native-certs"] -_tls-rustls = ["rustls", "rustls-pemfile"] +_tls-rustls = ["rustls"] _tls-none = [] # support offline/decoupled building (enables serialization of `Describe`) @@ -39,8 +39,7 @@ tokio = { workspace = true, optional = true } # TLS native-tls = { version = "0.2.10", optional = true } -rustls = { version = "0.23.11", default-features = false, features = ["std", "tls12"], optional = true } -rustls-pemfile = { version = "2", optional = true } +rustls = { version = "0.23.15", default-features = false, features = ["std", "tls12"], optional = true } webpki-roots = { version = "0.26", optional = true } rustls-native-certs = { version = "0.8.0", optional = true } diff --git a/sqlx-core/src/net/tls/tls_rustls.rs b/sqlx-core/src/net/tls/tls_rustls.rs index d56859808c..1a85cf0ff9 100644 --- a/sqlx-core/src/net/tls/tls_rustls.rs +++ b/sqlx-core/src/net/tls/tls_rustls.rs @@ -1,5 +1,5 @@ use futures_util::future; -use std::io::{self, BufReader, Cursor, Read, Write}; +use std::io::{self, Read, Write}; use std::sync::Arc; use std::task::{Context, Poll}; @@ -9,7 +9,10 @@ use rustls::{ WebPkiServerVerifier, }, crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, - pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime}, + pki_types::{ + pem::{self, PemObject}, + CertificateDer, PrivateKeyDer, ServerName, UnixTime, + }, CertificateError, ClientConfig, ClientConnection, Error as TlsError, RootCertStore, }; @@ -141,9 +144,8 @@ where if let Some(ca) = tls_config.root_cert_path { let data = ca.data().await?; - let mut cursor = Cursor::new(data); - for result in rustls_pemfile::certs(&mut cursor) { + for result in CertificateDer::pem_slice_iter(&data) { let Ok(cert) = result else { return Err(Error::Tls(format!("Invalid certificate {ca}").into())); }; @@ -196,19 +198,15 @@ where } fn certs_from_pem(pem: Vec) -> Result>, Error> { - let cur = Cursor::new(pem); - let mut reader = BufReader::new(cur); - rustls_pemfile::certs(&mut reader) + CertificateDer::pem_slice_iter(&pem) .map(|result| result.map_err(|err| Error::Tls(err.into()))) .collect() } fn private_key_from_pem(pem: Vec) -> Result, Error> { - let cur = Cursor::new(pem); - let mut reader = BufReader::new(cur); - match rustls_pemfile::private_key(&mut reader) { - Ok(Some(key)) => Ok(key), - Ok(None) => Err(Error::Configuration("no keys found pem file".into())), + match PrivateKeyDer::from_pem_slice(&pem) { + Ok(key) => Ok(key), + Err(pem::Error::NoItemsFound) => Err(Error::Configuration("no keys found pem file".into())), Err(e) => Err(Error::Configuration(e.to_string().into())), } } From a27a10f8fb54dbd260980fd2b115afafc1605dfb Mon Sep 17 00:00:00 2001 From: Ethan Wang Date: Tue, 18 Feb 2025 09:39:40 +0800 Subject: [PATCH 09/26] QueryBuilder: add `debug_assert` when `push_values` is passed an empty set of tuples (#3734) * throw a warning in tracing so that the empty tuples would be noticed * use debug assertion to throw a panic in debug mode --- Cargo.lock | 1 - sqlx-core/src/query_builder.rs | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c24d2ff8fa..f34884628e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3582,7 +3582,6 @@ dependencies = [ "rust_decimal", "rustls", "rustls-native-certs", - "rustls-pki-types", "serde", "serde_json", "sha2", diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs index 0d02048dcf..b242bf7b2a 100644 --- a/sqlx-core/src/query_builder.rs +++ b/sqlx-core/src/query_builder.rs @@ -323,6 +323,11 @@ where separated.push_unseparated(")"); } + debug_assert!( + separated.push_separator, + "No value being pushed. QueryBuilder may not build correct sql query!" + ); + separated.query_builder } From 8145fb9d7f774f60d538e16c3cd4d4054026a719 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:16:27 +1100 Subject: [PATCH 10/26] fix: merge conflicts --- sqlx-mysql/src/testing/mod.rs | 2 +- sqlx-postgres/src/testing/mod.rs | 2 +- sqlx-postgres/src/types/cube.rs | 4 ++-- sqlx-postgres/src/types/mod.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sqlx-mysql/src/testing/mod.rs b/sqlx-mysql/src/testing/mod.rs index 9e462c1131..1981cf73c5 100644 --- a/sqlx-mysql/src/testing/mod.rs +++ b/sqlx-mysql/src/testing/mod.rs @@ -30,7 +30,7 @@ impl TestSupport for MySql { Box::pin(async move { let mut conn = MASTER_POOL .get() - .expect("cleanup_test() invoked outside `#[sqlx::test]") + .expect("cleanup_test() invoked outside `#[sqlx::test]`") .acquire() .await?; diff --git a/sqlx-postgres/src/testing/mod.rs b/sqlx-postgres/src/testing/mod.rs index a927191dc9..373bd772dd 100644 --- a/sqlx-postgres/src/testing/mod.rs +++ b/sqlx-postgres/src/testing/mod.rs @@ -30,7 +30,7 @@ impl TestSupport for Postgres { Box::pin(async move { let mut conn = MASTER_POOL .get() - .expect("cleanup_test() invoked outside `#[sqlx::test]") + .expect("cleanup_test() invoked outside `#[sqlx::test]`") .acquire() .await?; diff --git a/sqlx-postgres/src/types/cube.rs b/sqlx-postgres/src/types/cube.rs index f39d82651c..cc2a016090 100644 --- a/sqlx-postgres/src/types/cube.rs +++ b/sqlx-postgres/src/types/cube.rs @@ -20,7 +20,7 @@ const IS_POINT_FLAG: u32 = 1 << 31; #[derive(Debug, Clone, PartialEq)] pub enum PgCube { /// A one-dimensional point. - // FIXME: `Point1D(f64) + // FIXME: `Point1D(f64)` Point(f64), /// An N-dimensional point ("represented internally as a zero-volume cube"). // FIXME: `PointND(f64)` @@ -32,7 +32,7 @@ pub enum PgCube { // FIXME: add `Cube3D { lower_left: [f64; 3], upper_right: [f64; 3] }`? /// An N-dimensional cube with points representing lower-left and upper-right corners, respectively. - // FIXME: CubeND { lower_left: Vec, upper_right: Vec }` + // FIXME: `CubeND { lower_left: Vec, upper_right: Vec }` MultiDimension(Vec>), } diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index f1efdef0f5..a9d138df64 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -21,11 +21,11 @@ //! | [`PgLQuery`] | LQUERY | //! | [`PgCiText`] | CITEXT1 | //! | [`PgCube`] | CUBE | -//! | [`PgPoint] | POINT | -//! | [`PgLine] | LINE | -//! | [`PgLSeg] | LSEG | //! | [`PgBox] | BOX | +//! | [`PgLSeg] | LSEG | +//! | [`PgLine] | LINE | //! | [`PgPath] | PATH | +//! | [`PgPoint`] | POINT | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., From dfba90265ca5cd1b18e629553378755ae50b0739 Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 21 Feb 2025 04:35:44 +0900 Subject: [PATCH 11/26] chore(cli): remove unused async-trait crate from dependencies (#3754) --- Cargo.lock | 1 - sqlx-cli/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f34884628e..e54d82c0c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3529,7 +3529,6 @@ version = "0.8.3" dependencies = [ "anyhow", "assert_cmd", - "async-trait", "backoff", "cargo_metadata", "chrono", diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 0b047ab136..00ac0df886 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -37,7 +37,6 @@ clap = { version = "4.3.10", features = ["derive", "env"] } clap_complete = { version = "4.3.1", optional = true } chrono = { version = "0.4.19", default-features = false, features = ["clock"] } anyhow = "1.0.52" -async-trait = "0.1.52" console = "0.15.0" promptly = "0.3.0" serde_json = "1.0.73" From 490145b8f837b0bf449ef9e445fe4aa5c03a6f60 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Thu, 20 Feb 2025 12:51:54 -0800 Subject: [PATCH 12/26] Update pull_request_template.md --- .github/pull_request_template.md | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a86c28731e..6d193065c2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,2 +1,50 @@ + + ### Does your PR solve an issue? -### Delete this text and add "fixes #(issue number)" +Delete this text and add "fixes #(issue number)". + +Do *not* just list issue numbers here as they will not be automatically closed on merging this pull request unless prefixed with "fixes" or "closes". + +### Is this a breaking change? +Delete this text and answer yes/no and explain. + +If yes, this pull request will need to wait for the next major release (`0.{x + 1}.0`) + +Behavior changes _can_ be breaking if significant enough. +Consider [Hyrum's Law](https://www.hyrumslaw.com/): + +> With a sufficient number of users of an API, +> it does not matter what you promise in the contract: +> all observable behaviors of your system +> will be depended on by somebody. From d31ecd03c224c5acafac1ceb5bea9116decf625c Mon Sep 17 00:00:00 2001 From: Stefan Schindler Date: Thu, 20 Feb 2025 21:57:54 +0100 Subject: [PATCH 13/26] Fix example calculation (#3741) --- sqlx-core/src/pool/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/pool/options.rs b/sqlx-core/src/pool/options.rs index 96dbf8ee3d..3d048f1795 100644 --- a/sqlx-core/src/pool/options.rs +++ b/sqlx-core/src/pool/options.rs @@ -484,7 +484,7 @@ impl PoolOptions { /// .await?; /// /// // Close the connection if the backend memory usage exceeds 256 MiB. - /// Ok(total_memory_usage <= (2 << 28)) + /// Ok(total_memory_usage <= (1 << 28)) /// })) /// .connect("postgres:// …").await?; /// # Ok(()) From 4c3a474fa36e2e52b1459e1401a3c43a7dcfba94 Mon Sep 17 00:00:00 2001 From: kildrens <5198060+kildrens@users.noreply.github.com> Date: Fri, 21 Feb 2025 02:13:56 +0000 Subject: [PATCH 14/26] Avoid privilege requirements by using an advisory lock in test setup (postgres). (#3753) * feat(sqlx-postgres): use advisory lock to avoid setup race condition * fix(sqlx-postgres): numeric hex constants not supported before postgres 16 --- sqlx-postgres/src/testing/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlx-postgres/src/testing/mod.rs b/sqlx-postgres/src/testing/mod.rs index 373bd772dd..af20fe87ea 100644 --- a/sqlx-postgres/src/testing/mod.rs +++ b/sqlx-postgres/src/testing/mod.rs @@ -134,8 +134,9 @@ async fn test_context(args: &TestArgs) -> Result, Error> { // I couldn't find a bug on the mailing list for `CREATE SCHEMA` specifically, // but a clearly related bug with `CREATE TABLE` has been known since 2007: // https://www.postgresql.org/message-id/200710222037.l9MKbCJZ098744%40wwwmaster.postgresql.org + // magic constant 8318549251334697844 is just 8 ascii bytes 'sqlxtest'. r#" - lock table pg_catalog.pg_namespace in share row exclusive mode; + select pg_advisory_xact_lock(8318549251334697844); create schema if not exists _sqlx_test; From a77f12b0e1b844425affdae7cf5c3e86d06b2684 Mon Sep 17 00:00:00 2001 From: Marti Serra Date: Sat, 22 Feb 2025 22:33:14 +0100 Subject: [PATCH 15/26] Small doc correction. (#3755) When sqlx-core/src/from_row.rs was updated to implement FromRow for tuples of up to 16 values, a comment was left stating that it was implemented up to tuples of 9 values. --- 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 66e53d55b8..ecd5847f30 100644 --- a/sqlx-core/src/from_row.rs +++ b/sqlx-core/src/from_row.rs @@ -313,7 +313,7 @@ where } // implement FromRow for tuples of types that implement Decode -// up to tuples of 9 values +// up to tuples of 16 values macro_rules! impl_from_row_for_tuple { ($( ($idx:tt) -> $T:ident );+;) => { From 49fa59e591990ce740de5e78d178b658d887f617 Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Sat, 22 Feb 2025 18:07:43 -0800 Subject: [PATCH 16/26] Update FAQ.md --- FAQ.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/FAQ.md b/FAQ.md index f0bccd3c41..cf13cf73ee 100644 --- a/FAQ.md +++ b/FAQ.md @@ -36,6 +36,62 @@ as they can often be a whole year or more out-of-date. [`rust-version`]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-rust-version-field +---------------------------------------------------------------- + +### Can SQLx Add Support for New Databases? + +We are always open to discuss adding support for new databases, but as of writing, have no plans to in the short term. + +Implementing support for a new database in SQLx is a _huge_ lift. Expecting this work to be done for free is highly unrealistic. +In all likelihood, the implementation would need to be written from scratch. +Even if Rust bindings exist, they may not support `async`. +Even if they support `async`, they may only support either Tokio or `async-std`, and not both. +Even if they support Tokio and `async-std`, the API may not be flexible enough or provide sufficient information (e.g. for implementing the macros). + +If we have to write the implementation from scratch, is the protocol publicly documented, and stable? + +Even if everything is supported on the client side, how will we run tests against the database? Is it open-source, or proprietary? Will it require a paid license? + +For example, Oracle Database's protocol is proprietary and only supported through their own libraries, which do not support Rust, and only have blocking APIs (see: [Oracle Call Interface for C](https://docs.oracle.com/en/database/oracle/oracle-database/23/lnoci/index.html)). +This makes it a poor candidate for an async-native crate like SQLx--though we support SQLite, which also only has a blocking API, that's the exception and not the rule. Wrapping blocking APIs is not very scalable. + +We still have plans to bring back the MSSQL driver, but this is not feasible as of writing with the current maintenance workload. Should this change, an announcement will be made on Github as well as our [Discord server](https://discord.gg/uuruzJ7). + +### What If I'm Willing to Contribute the Implementation? + +Being willing to contribute an implementation for a new database is one thing, but there's also the ongoing maintenance burden to consider. + +Are you willing to provide support long-term? +Will there be enough users that we can rely on outside contributions? +Or is support going to fall to the current maintainer(s)? + +This is the kind of thing that will need to be supported in SQLx _long_ after the initial implementation, or else later need to be removed. +If you don't have plans for how to support a new driver long-term, then it doesn't belong as part of SQLx itself. + +However, drivers don't necessarily need to live _in_ SQLx anymore. Since 0.7.0, drivers don't need to be compiled-in to be functional. +Support for third-party drivers in `sqlx-cli` and the `query!()` macros is pending, as well as documenting the process of writing a driver, but contributions are welcome in this regard. + +For example, see [sqlx-exasol](https://crates.io/crates/sqlx-exasol). + +---------------------------------------------------------------- +### Can SQLx Add Support for New Data-Type Crates (e.g. Jiff in addition to `chrono` and `time`)? + +This has a lot of the same considerations as adding support for new databases (see above), but with one big additional problem: Semantic Versioning. + +When we add trait implementations for types from an external crate, that crate then becomes part of our public API. We become beholden to its release cycle. + +If the crate's API is still evolving, meaning they are making breaking changes frequently, and thus releasing new major versions frequently, that then becomes a burden on us to upgrade and release a new major version as well so everyone _else_ can upgrade. + +We don't have the maintainer bandwidth to support multiple major versions simultaneously (we have no Long-Term Support policy), so this means that users who want to keep up-to-date are forced to make frequent manual upgrades as well. + +Thus, it is best that we stick to only supporting crates which have a stable API, and which are not making new major releases frequently. + +Conversely, adding support for SQLx _in_ these crates may not be desirable either, since SQLx is a large dependency and a higher-level crate. In this case, the SemVer problem gets pushed onto the other crate. + +There isn't a satisfying answer to this problem, but one option is to have an intermediate wrapper crate. +For example, [`jiff-sqlx`](https://crates.io/crates/jiff-sqlx), which is maintained by the author of Jiff. +API changes to SQLx are pending to make this pattern easier to use. + ---------------------------------------------------------------- ### I'm getting `HandshakeFailure` or `CorruptMessage` when trying to connect to a server over TLS using RusTLS. What gives? From 72e6826b89d6fc62799bf1bdec8c6a804a232ea2 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 27 Feb 2025 21:17:09 +0100 Subject: [PATCH 17/26] refactor(cli): replace promptly with dialoguer (#3669) --- Cargo.lock | 183 ++++++--------------------------------- sqlx-cli/Cargo.toml | 4 +- sqlx-cli/src/database.rs | 67 +++++++++----- sqlx-cli/src/lib.rs | 21 +++++ 4 files changed, 92 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e54d82c0c9..07754e7c22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -834,17 +834,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" -[[package]] -name = "clipboard-win" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" -dependencies = [ - "error-code", - "str-buf", - "winapi", -] - [[package]] name = "cmake" version = "0.1.52" @@ -1120,6 +1109,17 @@ dependencies = [ "serde", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "thiserror 1.0.69", +] + [[package]] name = "difflib" version = "0.4.0" @@ -1138,27 +1138,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -1209,12 +1188,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "env_filter" version = "0.1.3" @@ -1261,17 +1234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "error-code" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", + "windows-sys 0.59.0", ] [[package]] @@ -1327,17 +1290,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fd-lock" -version = "3.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" -dependencies = [ - "cfg-if", - "rustix 0.38.43", - "windows-sys 0.48.0", -] - [[package]] name = "filetime" version = "0.2.25" @@ -2102,7 +2054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2186,7 +2138,7 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8836fae9d0d4be2c8b4efcdd79e828a2faa058a90d005abf42f91cac5493a08e" dependencies = [ - "nix 0.28.0", + "nix", "winapi", ] @@ -2212,15 +2164,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -2318,28 +2261,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nix" version = "0.28.0" @@ -2350,7 +2271,7 @@ dependencies = [ "cfg-if", "cfg_aliases 0.1.1", "libc", - "memoffset 0.9.1", + "memoffset", ] [[package]] @@ -2805,15 +2726,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "promptly" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acbc6c5a5b029fe58342f58445acb00ccfe24624e538894bc2f04ce112980ba" -dependencies = [ - "rustyline", -] - [[package]] name = "ptr_meta" version = "0.1.4" @@ -2849,16 +2761,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.8.5" @@ -2948,17 +2850,6 @@ dependencies = [ "bitflags 2.7.0", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "regex" version = "1.11.1" @@ -3113,7 +3004,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3167,30 +3058,6 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" -[[package]] -name = "rustyline" -version = "9.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "clipboard-win", - "dirs-next", - "fd-lock", - "libc", - "log", - "memchr", - "nix 0.23.2", - "radix_trie", - "scopeguard", - "smallvec", - "unicode-segmentation", - "unicode-width 0.1.14", - "utf8parse", - "winapi", -] - [[package]] name = "ryu" version = "1.0.18" @@ -3384,6 +3251,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -3535,12 +3408,12 @@ dependencies = [ "clap", "clap_complete", "console", + "dialoguer", "dotenvy", "filetime", "futures", "glob", "openssl", - "promptly", "serde_json", "sqlx", "tempfile", @@ -3908,12 +3781,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "str-buf" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" - [[package]] name = "stringprep" version = "0.1.5" @@ -4099,7 +3966,7 @@ dependencies = [ "getrandom", "once_cell", "rustix 0.38.43", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4775,7 +4642,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 00ac0df886..582099a2f6 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -26,7 +26,7 @@ path = "src/bin/cargo-sqlx.rs" [dependencies] dotenvy = "0.15.0" -tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread"] } +tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread", "signal"] } sqlx = { workspace = true, default-features = false, features = [ "runtime-tokio", "migrate", @@ -38,7 +38,7 @@ clap_complete = { version = "4.3.1", optional = true } chrono = { version = "0.4.19", default-features = false, features = ["clock"] } anyhow = "1.0.52" console = "0.15.0" -promptly = "0.3.0" +dialoguer = { version = "0.11", default-features = false } serde_json = "1.0.73" glob = "0.3.0" openssl = { version = "0.10.38", optional = true } diff --git a/sqlx-cli/src/database.rs b/sqlx-cli/src/database.rs index 7a2056ab35..7a9bc6bf2f 100644 --- a/sqlx-cli/src/database.rs +++ b/sqlx-cli/src/database.rs @@ -1,9 +1,11 @@ use crate::migrate; use crate::opt::ConnectOpts; -use console::style; -use promptly::{prompt, ReadlineError}; +use console::{style, Term}; +use dialoguer::Confirm; use sqlx::any::Any; use sqlx::migrate::MigrateDatabase; +use std::{io, mem}; +use tokio::task; pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> { // NOTE: only retry the idempotent action. @@ -24,7 +26,7 @@ pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> { } pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> anyhow::Result<()> { - if confirm && !ask_to_continue_drop(connect_opts.required_db_url()?) { + if confirm && !ask_to_continue_drop(connect_opts.required_db_url()?.to_owned()).await { return Ok(()); } @@ -58,27 +60,46 @@ pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow migrate::run(migration_source, connect_opts, false, false, None).await } -fn ask_to_continue_drop(db_url: &str) -> bool { - loop { - let r: Result = - prompt(format!("Drop database at {}? (y/n)", style(db_url).cyan())); - match r { - Ok(response) => { - if response == "n" || response == "N" { - return false; - } else if response == "y" || response == "Y" { - return true; - } else { - println!( - "Response not recognized: {}\nPlease type 'y' or 'n' and press enter.", - response - ); - } - } - Err(e) => { - println!("{e}"); - return false; +async fn ask_to_continue_drop(db_url: String) -> bool { + // If the setup operation is cancelled while we are waiting for the user to decide whether + // or not to drop the database, this will restore the terminal's cursor to its normal state. + struct RestoreCursorGuard { + disarmed: bool, + } + + impl Drop for RestoreCursorGuard { + fn drop(&mut self) { + if !self.disarmed { + Term::stderr().show_cursor().unwrap() } } } + + let mut guard = RestoreCursorGuard { disarmed: false }; + + let decision_result = task::spawn_blocking(move || { + Confirm::new() + .with_prompt(format!("Drop database at {}?", style(&db_url).cyan())) + .wait_for_newline(true) + .default(false) + .show_default(true) + .interact() + }) + .await + .expect("Confirm thread panicked"); + match decision_result { + Ok(decision) => { + guard.disarmed = true; + decision + } + Err(dialoguer::Error::IO(err)) if err.kind() == io::ErrorKind::Interrupted => { + // Sometimes CTRL + C causes this error to be returned + mem::drop(guard); + false + } + Err(err) => { + mem::drop(guard); + panic!("Confirm dialog failed with {err}") + } + } } diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index bfd71e4bc1..a182f019b9 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::Result; use futures::{Future, TryFutureExt}; use sqlx::{AnyConnection, Connection}; +use tokio::{select, signal}; use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand}; @@ -21,6 +22,26 @@ mod prepare; pub use crate::opt::Opt; pub async fn run(opt: Opt) -> Result<()> { + // This `select!` is here so that when the process receives a `SIGINT` (CTRL + C), + // the futures currently running on this task get dropped before the program exits. + // This is currently necessary for the consumers of the `dialoguer` crate to restore + // the user's terminal if the process is interrupted while a dialog is being displayed. + + let ctrlc_fut = signal::ctrl_c(); + let do_run_fut = do_run(opt); + + select! { + biased; + _ = ctrlc_fut => { + Ok(()) + }, + do_run_outcome = do_run_fut => { + do_run_outcome + } + } +} + +async fn do_run(opt: Opt) -> Result<()> { match opt.command { Command::Migrate(migrate) => match migrate.command { MigrateCommand::Add { From cb1dbe437f48817a271636fd3bf4af3bf76724ce Mon Sep 17 00:00:00 2001 From: Jonas Malaco Date: Fri, 28 Feb 2025 21:42:53 -0300 Subject: [PATCH 18/26] docs(pool): recommend actix-web ThinData over Data to avoid two Arcs (#3762) Both actix_web::web::Data and sqlx::PgPool internally wrap an Arc. Thus, using Data as an extractor in an actix-web route handler results in two Arcs wrapping the data of interest, which isn't ideal. Actix-web 4.9.0 introduced a new web::ThinData extractor for cases like this, where the data is already wrapped in an `Arc` (or is otherwise similarly cheap and sensible to simply clone), which doesn't wrap the inner value in a (second) Arc. Since the new extractor is better suited to the task, suggest it in place of web::Data when giving an example on how to share a pool. --- sqlx-core/src/pool/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/pool/mod.rs b/sqlx-core/src/pool/mod.rs index 042bc5c7bc..8aa9041ab7 100644 --- a/sqlx-core/src/pool/mod.rs +++ b/sqlx-core/src/pool/mod.rs @@ -109,7 +109,8 @@ mod options; /// application/daemon/web server/etc. and then shared with all tasks throughout the process' /// lifetime. How best to accomplish this depends on your program architecture. /// -/// In Actix-Web, for example, you can share a single pool with all request handlers using [web::Data]. +/// In Actix-Web, for example, you can efficiently share a single pool with all request handlers +/// using [web::ThinData]. /// /// Cloning `Pool` is cheap as it is simply a reference-counted handle to the inner pool state. /// When the last remaining handle to the pool is dropped, the connections owned by the pool are @@ -131,7 +132,7 @@ mod options; /// * [PgPool][crate::postgres::PgPool] (PostgreSQL) /// * [SqlitePool][crate::sqlite::SqlitePool] (SQLite) /// -/// [web::Data]: https://docs.rs/actix-web/3/actix_web/web/struct.Data.html +/// [web::ThinData]: https://docs.rs/actix-web/4.9.0/actix_web/web/struct.ThinData.html /// /// ### Note: Drop Behavior /// Due to a lack of async `Drop`, dropping the last `Pool` handle may not immediately clean From 8d0d678f46926870aacd1a08f60e77b3ad2b96cc Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:18:02 +1100 Subject: [PATCH 19/26] fix: merge conflicts --- tests/postgres/types.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 0d15caf8de..fa9d7353ec 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -530,6 +530,11 @@ test_type!(path(Postgres, "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, )); +#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] +test_type!(_path>(Postgres, + "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1.1, y: 2.2 }, sqlx::postgres::types::PgPoint { x: 3.3 , y: 4.4 } ]},], +)); + #[cfg(feature = "rust_decimal")] test_type!(decimal(Postgres, "0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(), From 7791740232e6d6877f3dc956b5e3e8ab964fcde3 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:15:45 +1100 Subject: [PATCH 20/26] fix: use types mod from main --- sqlx-postgres/src/types/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index a9d138df64..a5fd708366 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -21,11 +21,10 @@ //! | [`PgLQuery`] | LQUERY | //! | [`PgCiText`] | CITEXT1 | //! | [`PgCube`] | CUBE | -//! | [`PgBox] | BOX | -//! | [`PgLSeg] | LSEG | -//! | [`PgLine] | LINE | -//! | [`PgPath] | PATH | //! | [`PgPoint`] | POINT | +//! | [`PgLine`] | LINE | +//! | [`PgLSeg`] | LSEG | +//! | [`PgBox`] | BOX | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., @@ -263,7 +262,6 @@ pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; -pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; From 0021cf63f9a18ec20c7faaa35611cc24fc605ee5 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:22:06 +1100 Subject: [PATCH 21/26] fix: merge conflicts --- sqlx-postgres/src/types/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index a5fd708366..ac40924c6a 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -20,11 +20,11 @@ //! | [`PgLTree`] | LTREE | //! | [`PgLQuery`] | LQUERY | //! | [`PgCiText`] | CITEXT1 | +//! | [`PgBox`] | BOX | //! | [`PgCube`] | CUBE | -//! | [`PgPoint`] | POINT | -//! | [`PgLine`] | LINE | //! | [`PgLSeg`] | LSEG | -//! | [`PgBox`] | BOX | +//! | [`PgLine`] | LINE | +//! | [`PgPoint`] | POINT | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., @@ -262,6 +262,7 @@ pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; +pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; From 28fba22d9eca4af2da713a6c4c35f317dbb35d45 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:21:07 +1100 Subject: [PATCH 22/26] fix: merge conflicts --- sqlx-postgres/src/types/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index ac40924c6a..384a6a8216 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -24,6 +24,7 @@ //! | [`PgCube`] | CUBE | //! | [`PgLSeg`] | LSEG | //! | [`PgLine`] | LINE | +//! | [`PgPath`] | PATH | //! | [`PgPoint`] | POINT | //! | [`PgHstore`] | HSTORE | //! From 29687a9057d35603bcda7284fa5ba20d5dfaa305 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:21:34 +1100 Subject: [PATCH 23/26] fix: merge conflicts --- sqlx-postgres/src/types/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index 384a6a8216..a58a9aad72 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -263,7 +263,6 @@ pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; -pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; From 12b0d145a2c80720e3386a4a95e3136102397c1d Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:23:13 +1100 Subject: [PATCH 24/26] fix: ordering of types mod --- sqlx-postgres/src/types/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index a58a9aad72..93e5458aaf 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -20,12 +20,12 @@ //! | [`PgLTree`] | LTREE | //! | [`PgLQuery`] | LQUERY | //! | [`PgCiText`] | CITEXT1 | -//! | [`PgBox`] | BOX | //! | [`PgCube`] | CUBE | -//! | [`PgLSeg`] | LSEG | +//! | [`PgPoint`] | POINT | //! | [`PgLine`] | LINE | +//! | [`PgLSeg`] | LSEG | +//! | [`PgBox`] | BOX | //! | [`PgPath`] | PATH | -//! | [`PgPoint`] | POINT | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., From 78f3621a90700158273a28867b6b1fa5eee86ddb Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:25:06 +1100 Subject: [PATCH 25/26] fix: path import --- sqlx-postgres/src/types/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sqlx-postgres/src/types/mod.rs b/sqlx-postgres/src/types/mod.rs index 93e5458aaf..5d684c969e 100644 --- a/sqlx-postgres/src/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -263,6 +263,7 @@ pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; +pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; From 51154ca1c0e84d382226b630ab3a6c3c6075dd64 Mon Sep 17 00:00:00 2001 From: James Holman Date: Sun, 2 Mar 2025 09:42:42 +1100 Subject: [PATCH 26/26] test: no array test for path --- tests/postgres/types.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index fa9d7353ec..0d15caf8de 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -530,11 +530,6 @@ test_type!(path(Postgres, "path('[(1.0, 2.0), (3.0,4.0)]')" == sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, )); -#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))] -test_type!(_path>(Postgres, - "array[path('(1,2),(3,4)'),path('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgPath { closed: true, points: vec![ sqlx::postgres::types::PgPoint { x: 1., y: 2. }, sqlx::postgres::types::PgPoint { x: 3. , y: 4. } ]}, sqlx::postgres::types::PgPath { closed: false, points: vec![ sqlx::postgres::types::PgPoint { x: 1.1, y: 2.2 }, sqlx::postgres::types::PgPoint { x: 3.3 , y: 4.4 } ]},], -)); - #[cfg(feature = "rust_decimal")] test_type!(decimal(Postgres, "0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),