From 36c9728d88ddc19500152d6fbe86efc994500ab6 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 11:02:26 -0700 Subject: [PATCH 01/15] implement decode and encode for json --- sqlx-core/src/any/types/json.rs | 42 +++++++++++++++++++++++++++++++++ sqlx-core/src/any/types/mod.rs | 4 ++++ 2 files changed, 46 insertions(+) create mode 100644 sqlx-core/src/any/types/json.rs diff --git a/sqlx-core/src/any/types/json.rs b/sqlx-core/src/any/types/json.rs new file mode 100644 index 0000000000..83da47196a --- /dev/null +++ b/sqlx-core/src/any/types/json.rs @@ -0,0 +1,42 @@ +use crate::any::{Any, AnyArgumentBuffer, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, AnyValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::{Json, Type}; +use serde::{Deserialize, Serialize}; + +impl Type for Json { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Text, + } + } + + fn compatible(ty: &AnyTypeInfo) -> bool { + matches!(ty.kind, AnyTypeInfoKind::Text | AnyTypeInfoKind::Blob) + } +} + +impl Encode<'_, Any> for Json +where + T: Serialize, +{ + fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'_>) -> Result { + let json_string = self.encode_to_string()?; + buf.0.push(AnyValueKind::Text(json_string.into())); + Ok(IsNull::No) + } +} + +impl<'r, T> Decode<'_, Any> for Json +where + T: for<'de> Deserialize<'de>, +{ + fn decode(value: AnyValueRef<'_>) -> Result { + match value.kind { + AnyValueKind::Text(text) => Json::decode_from_string(&text.into_owned()), + AnyValueKind::Blob(blob) => Json::decode_from_bytes(&blob.into_owned()), + other => other.unexpected(), + } + } +} diff --git a/sqlx-core/src/any/types/mod.rs b/sqlx-core/src/any/types/mod.rs index a0ae55156d..b87a879837 100644 --- a/sqlx-core/src/any/types/mod.rs +++ b/sqlx-core/src/any/types/mod.rs @@ -21,6 +21,7 @@ mod blob; mod bool; mod float; mod int; +mod json; mod str; #[test] @@ -50,4 +51,7 @@ fn test_type_impls() { // These imply that there are also impls for the equivalent slice types. has_type::>(); has_type::(); + + // JSON types + has_type::>(); } From cfc7f8b0fbe9ae4ee3f4083aa7b8aad9a2bb7dc7 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 11:11:18 -0700 Subject: [PATCH 02/15] add any json test --- tests/any/any.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/any/any.rs b/tests/any/any.rs index 62dc20403e..558851975c 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -3,6 +3,11 @@ use sqlx::{Any, Connection, Executor, Row}; use sqlx_core::sql_str::AssertSqlSafe; use sqlx_test::new; +#[cfg(feature = "json")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "json")] +use sqlx::types::Json; + #[sqlx_macros::test] async fn it_connects() -> anyhow::Result<()> { sqlx::any::install_default_drivers(); @@ -142,3 +147,47 @@ async fn it_can_fail_and_recover_with_pool() -> anyhow::Result<()> { Ok(()) } + +#[cfg(feature = "json")] +#[sqlx_macros::test] +async fn it_encodes_decodes_json() -> anyhow::Result<()> { + sqlx::any::install_default_drivers(); + + let mut conn = new::().await?; + + // Test with serde_json::Value + let json_value = serde_json::json!({ + "name": "test", + "value": 42, + "items": [1, 2, 3] + }); + + // This will work by encoding JSON as text and decoding it back + let result: serde_json::Value = sqlx::query_scalar("SELECT ?") + .bind(Json(&json_value)) + .fetch_one(&mut conn) + .await?; + + assert_eq!(result, json_value); + + // Test with custom struct + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct TestData { + name: String, + count: i32, + } + + let test_data = TestData { + name: "example".to_string(), + count: 100, + }; + + let result: Json = sqlx::query_scalar("SELECT ?") + .bind(Json(&test_data)) + .fetch_one(&mut conn) + .await?; + + assert_eq!(result.0, test_data); + + Ok(()) +} From fc1de8fc522bc1f17f66baca8ca328c4603b2d03 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 11:14:58 -0700 Subject: [PATCH 03/15] address lints --- sqlx-core/src/any/types/json.rs | 6 +++--- sqlx-core/src/any/types/mod.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlx-core/src/any/types/json.rs b/sqlx-core/src/any/types/json.rs index 83da47196a..7114dd7dd5 100644 --- a/sqlx-core/src/any/types/json.rs +++ b/sqlx-core/src/any/types/json.rs @@ -28,14 +28,14 @@ where } } -impl<'r, T> Decode<'_, Any> for Json +impl Decode<'_, Any> for Json where T: for<'de> Deserialize<'de>, { fn decode(value: AnyValueRef<'_>) -> Result { match value.kind { - AnyValueKind::Text(text) => Json::decode_from_string(&text.into_owned()), - AnyValueKind::Blob(blob) => Json::decode_from_bytes(&blob.into_owned()), + AnyValueKind::Text(text) => Json::decode_from_string(&text), + AnyValueKind::Blob(blob) => Json::decode_from_bytes(&blob), other => other.unexpected(), } } diff --git a/sqlx-core/src/any/types/mod.rs b/sqlx-core/src/any/types/mod.rs index b87a879837..eb62f30412 100644 --- a/sqlx-core/src/any/types/mod.rs +++ b/sqlx-core/src/any/types/mod.rs @@ -51,7 +51,7 @@ fn test_type_impls() { // These imply that there are also impls for the equivalent slice types. has_type::>(); has_type::(); - + // JSON types has_type::>(); } From 4ebb40c99511e1d7e39146bd3541688b9a8f365f Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 11:52:19 -0700 Subject: [PATCH 04/15] change any tests to suppor postgres better --- tests/any/any.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/any/any.rs b/tests/any/any.rs index 558851975c..f5cde861de 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -162,9 +162,19 @@ async fn it_encodes_decodes_json() -> anyhow::Result<()> { "items": [1, 2, 3] }); - // This will work by encoding JSON as text and decoding it back - let result: serde_json::Value = sqlx::query_scalar("SELECT ?") + // Create temp table: + sqlx::query("create temporary table json_test (data TEXT)") + .execute(&mut conn) + .await?; + + // Insert into the temporary table: + sqlx::query("insert into json_test (data) values ($1)") .bind(Json(&json_value)) + .execute(&mut conn) + .await?; + + // This will work by encoding JSON as text and decoding it back + let result: serde_json::Value = sqlx::query_scalar("select data from json_test") .fetch_one(&mut conn) .await?; @@ -174,16 +184,17 @@ async fn it_encodes_decodes_json() -> anyhow::Result<()> { #[derive(Serialize, Deserialize, Debug, PartialEq)] struct TestData { name: String, - count: i32, + value: i32, + items: [i32; 3], } let test_data = TestData { - name: "example".to_string(), - count: 100, + name: "test".to_string(), + value: 42, + items: [1, 2, 3], }; - let result: Json = sqlx::query_scalar("SELECT ?") - .bind(Json(&test_data)) + let result: Json = sqlx::query_scalar("select data from json_test") .fetch_one(&mut conn) .await?; From 1b0e32d1c5a622b3f93278dcb40646f5250d744e Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 12:00:37 -0700 Subject: [PATCH 05/15] use ? instead of --- tests/any/any.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/any/any.rs b/tests/any/any.rs index f5cde861de..5cd923cd2e 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -168,7 +168,7 @@ async fn it_encodes_decodes_json() -> anyhow::Result<()> { .await?; // Insert into the temporary table: - sqlx::query("insert into json_test (data) values ($1)") + sqlx::query("insert into json_test (data) values (?)") .bind(Json(&json_value)) .execute(&mut conn) .await?; From 1b7e2232b9362e7d92f58d26ec73623ac2988615 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 12:15:27 -0700 Subject: [PATCH 06/15] new commit to retry ci since it failed via rewwest --- Cargo.toml | 141 +++++++++++++++++++++++++++++++++++++++-------- tests/any/any.rs | 1 + 2 files changed, 118 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c37eb4a5d..b2fd4005a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,10 +64,20 @@ default = ["any", "macros", "migrate", "json"] derive = ["sqlx-macros/derive"] macros = ["derive", "sqlx-macros/macros"] -migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] +migrate = [ + "sqlx-core/migrate", + "sqlx-macros?/migrate", + "sqlx-mysql?/migrate", + "sqlx-postgres?/migrate", + "sqlx-sqlite?/migrate", +] # Enable parsing of `sqlx.toml` for configuring macros and migrations. -sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-macros?/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] +sqlx-toml = [ + "sqlx-core/sqlx-toml", + "sqlx-macros?/sqlx-toml", + "sqlx-sqlite?/sqlx-toml", +] # intended mainly for CI and docs all-databases = ["mysql", "sqlite", "postgres", "any"] @@ -82,27 +92,40 @@ _unstable-all-types = [ "mac_address", "uuid", "bit-vec", - "bstr" + "bstr", ] # Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`). _unstable-docs = [ "all-databases", "_unstable-all-types", - "sqlx-sqlite/_unstable-docs" + "sqlx-sqlite/_unstable-docs", ] # Base runtime features without TLS -runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"] +runtime-async-std = [ + "_rt-async-std", + "sqlx-core/_rt-async-std", + "sqlx-macros?/_rt-async-std", +] runtime-tokio = ["_rt-tokio", "sqlx-core/_rt-tokio", "sqlx-macros?/_rt-tokio"] # TLS features tls-native-tls = ["sqlx-core/_tls-native-tls", "sqlx-macros?/_tls-native-tls"] tls-rustls = ["tls-rustls-ring"] # For backwards compatibility -tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs", "sqlx-macros?/_tls-rustls-aws-lc-rs"] +tls-rustls-aws-lc-rs = [ + "sqlx-core/_tls-rustls-aws-lc-rs", + "sqlx-macros?/_tls-rustls-aws-lc-rs", +] tls-rustls-ring = ["tls-rustls-ring-webpki"] # For backwards compatibility -tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki", "sqlx-macros?/_tls-rustls-ring-webpki"] -tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots", "sqlx-macros?/_tls-rustls-ring-native-roots"] +tls-rustls-ring-webpki = [ + "sqlx-core/_tls-rustls-ring-webpki", + "sqlx-macros?/_tls-rustls-ring-webpki", +] +tls-rustls-ring-native-roots = [ + "sqlx-core/_tls-rustls-ring-native-roots", + "sqlx-macros?/_tls-rustls-ring-native-roots", +] # No-op feature used by the workflows to compile without TLS enabled. Not meant for general use. tls-none = [] @@ -113,14 +136,28 @@ _rt-tokio = [] _sqlite = [] # database -any = ["sqlx-core/any", "sqlx-mysql?/any", "sqlx-postgres?/any", "sqlx-sqlite?/any"] +any = [ + "sqlx-core/any", + "sqlx-mysql?/any", + "sqlx-postgres?/any", + "sqlx-sqlite?/any", +] postgres = ["sqlx-postgres", "sqlx-macros?/postgres"] mysql = ["sqlx-mysql", "sqlx-macros?/mysql"] -sqlite = ["sqlite-bundled", "sqlite-deserialize", "sqlite-load-extension", "sqlite-unlock-notify"] +sqlite = [ + "sqlite-bundled", + "sqlite-deserialize", + "sqlite-load-extension", + "sqlite-unlock-notify", +] # SQLite base features sqlite-bundled = ["_sqlite", "sqlx-sqlite/bundled", "sqlx-macros?/sqlite"] -sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled", "sqlx-macros?/sqlite-unbundled"] +sqlite-unbundled = [ + "_sqlite", + "sqlx-sqlite/unbundled", + "sqlx-macros?/sqlite-unbundled", +] # SQLite features using conditionally compiled APIs # Note: these assume `sqlite-bundled` or `sqlite-unbundled` is also enabled @@ -132,7 +169,10 @@ sqlite-deserialize = ["sqlx-sqlite/deserialize"] # Enable `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()`. # Also required to use `drivers.sqlite.unsafe-load-extensions` from `sqlx.toml`. # Cannot be used with `-DSQLITE_OMIT_LOAD_EXTENSION` -sqlite-load-extension = ["sqlx-sqlite/load-extension", "sqlx-macros?/sqlite-load-extension"] +sqlite-load-extension = [ + "sqlx-sqlite/load-extension", + "sqlx-macros?/sqlite-load-extension", +] # Enables `sqlite3_preupdate_hook` # Requires `-DSQLITE_ENABLE_PREUPDATE_HOOK` (set automatically with `sqlite-bundled`) @@ -143,17 +183,63 @@ sqlite-preupdate-hook = ["sqlx-sqlite/preupdate-hook"] sqlite-unlock-notify = ["sqlx-sqlite/unlock-notify"] # types -json = ["sqlx-core/json", "sqlx-macros?/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"] +json = [ + "sqlx-core/json", + "sqlx-macros?/json", + "sqlx-mysql?/json", + "sqlx-postgres?/json", + "sqlx-sqlite?/json", +] -bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros?/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] -bit-vec = ["sqlx-core/bit-vec", "sqlx-macros?/bit-vec", "sqlx-postgres?/bit-vec"] -chrono = ["sqlx-core/chrono", "sqlx-macros?/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] +bigdecimal = [ + "sqlx-core/bigdecimal", + "sqlx-macros?/bigdecimal", + "sqlx-mysql?/bigdecimal", + "sqlx-postgres?/bigdecimal", +] +bit-vec = [ + "sqlx-core/bit-vec", + "sqlx-macros?/bit-vec", + "sqlx-postgres?/bit-vec", +] +chrono = [ + "sqlx-core/chrono", + "sqlx-macros?/chrono", + "sqlx-mysql?/chrono", + "sqlx-postgres?/chrono", + "sqlx-sqlite?/chrono", +] ipnet = ["sqlx-core/ipnet", "sqlx-macros?/ipnet", "sqlx-postgres?/ipnet"] -ipnetwork = ["sqlx-core/ipnetwork", "sqlx-macros?/ipnetwork", "sqlx-postgres?/ipnetwork"] -mac_address = ["sqlx-core/mac_address", "sqlx-macros?/mac_address", "sqlx-postgres?/mac_address"] -rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] -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"] +ipnetwork = [ + "sqlx-core/ipnetwork", + "sqlx-macros?/ipnetwork", + "sqlx-postgres?/ipnetwork", +] +mac_address = [ + "sqlx-core/mac_address", + "sqlx-macros?/mac_address", + "sqlx-postgres?/mac_address", +] +rust_decimal = [ + "sqlx-core/rust_decimal", + "sqlx-macros?/rust_decimal", + "sqlx-mysql?/rust_decimal", + "sqlx-postgres?/rust_decimal", +] +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"] @@ -175,11 +261,16 @@ sqlx = { version = "=0.9.0-alpha.1", path = "." } # These are optional unless enabled in a workspace crate. bigdecimal = "0.4.0" bit-vec = "0.6.3" -chrono = { version = "0.4.34", default-features = false, features = ["std", "clock"] } +chrono = { version = "0.4.34", default-features = false, features = [ + "std", + "clock", +] } ipnet = "2.3.0" ipnetwork = "0.21.1" mac_address = "1.1.5" -rust_decimal = { version = "1.26.1", default-features = false, features = ["std"] } +rust_decimal = { version = "1.26.1", default-features = false, features = [ + "std", +] } time = { version = "0.3.36", features = ["formatting", "parsing", "macros"] } uuid = "1.1.2" @@ -206,7 +297,9 @@ sqlx-sqlite = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.52" time_ = { version = "0.3.2", package = "time" } -futures-util = { version = "0.3.19", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.19", default-features = false, features = [ + "alloc", +] } env_logger = "0.11" async-std = { workspace = true, features = ["attributes"] } tokio = { version = "1.15.0", features = ["full"] } diff --git a/tests/any/any.rs b/tests/any/any.rs index 5cd923cd2e..c137e104ce 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -153,6 +153,7 @@ async fn it_can_fail_and_recover_with_pool() -> anyhow::Result<()> { async fn it_encodes_decodes_json() -> anyhow::Result<()> { sqlx::any::install_default_drivers(); + // Create new connection let mut conn = new::().await?; // Test with serde_json::Value From 7d2161a5c7cdcaf38f9505f5caaccb1e63d02d2d Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 16:25:09 -0700 Subject: [PATCH 07/15] add argument mapping for sqlx and postgres --- sqlx-core/src/any/arguments.rs | 10 ++++++- sqlx-core/src/any/mod.rs | 50 +++++++++++++++++++++++++++++++++ sqlx-core/src/any/row.rs | 13 ++++++++- sqlx-core/src/any/type_info.rs | 5 ++++ sqlx-core/src/any/types/json.rs | 12 ++++++-- sqlx-core/src/any/types/mod.rs | 3 ++ sqlx-core/src/any/value.rs | 9 ++++++ sqlx-postgres/src/any.rs | 2 ++ sqlx-sqlite/src/any.rs | 2 ++ 9 files changed, 101 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/any/arguments.rs b/sqlx-core/src/any/arguments.rs index 5a617c1419..938943a0ea 100644 --- a/sqlx-core/src/any/arguments.rs +++ b/sqlx-core/src/any/arguments.rs @@ -1,5 +1,5 @@ use crate::any::value::AnyValueKind; -use crate::any::{Any, AnyTypeInfoKind}; +use crate::any::{Any, AnyJson, AnyTypeInfoKind}; use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -62,6 +62,7 @@ impl<'q> AnyArguments<'q> { f64: Type + Encode<'a, A::Database>, String: Type + Encode<'a, A::Database>, Vec: Type + Encode<'a, A::Database>, + A::Database: AnyJson, { let mut out = A::default(); @@ -76,6 +77,11 @@ impl<'q> AnyArguments<'q> { AnyValueKind::Null(AnyTypeInfoKind::Double) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Text) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Blob) => out.add(Option::>::None), + #[cfg(feature = "json")] + AnyValueKind::Null(AnyTypeInfoKind::Json) => { + let null_json = serde_json::value::RawValue::from_string("null".to_string())?; + A::Database::add_json(&mut out, null_json) + } AnyValueKind::Bool(b) => out.add(b), AnyValueKind::SmallInt(i) => out.add(i), AnyValueKind::Integer(i) => out.add(i), @@ -84,6 +90,8 @@ impl<'q> AnyArguments<'q> { AnyValueKind::Double(d) => out.add(d), AnyValueKind::Text(t) => out.add(String::from(t)), AnyValueKind::Blob(b) => out.add(Vec::from(b)), + #[cfg(feature = "json")] + AnyValueKind::Json(j) => A::Database::add_json(&mut out, j), }? } Ok(out) diff --git a/sqlx-core/src/any/mod.rs b/sqlx-core/src/any/mod.rs index 032f4dda03..369eacab43 100644 --- a/sqlx-core/src/any/mod.rs +++ b/sqlx-core/src/any/mod.rs @@ -47,6 +47,56 @@ use crate::types::Type; #[doc(hidden)] pub use value::AnyValueKind; +/// Encode and decode support for JSON with the `Any` driver. +/// +/// Exists because `where` bounds on `AnyArguments::convert_into` and `AnyValue::map_from` cannot be +/// conditional on the `json` feature. Notice how this trait is always enabled, but its method aren't. +#[doc(hidden)] +pub trait AnyJson: crate::database::Database { + #[cfg(feature = "json")] + fn add_json<'a, A>( + args: &mut A, + value: Box, + ) -> Result<(), crate::error::BoxDynError> + where + A: crate::arguments::Arguments<'a, Database = Self>; + + #[cfg(feature = "json")] + fn decode_json<'a>( + value: ::ValueRef<'a>, + ) -> Result, crate::error::BoxDynError>; +} + +/// No-op impl when `json` feature is disabled. +#[cfg(not(feature = "json"))] +impl AnyJson for DB {} + +/// Full-featured impl +#[cfg(feature = "json")] +impl AnyJson for DB +where + DB: crate::database::Database, + crate::types::Json>: + Type + for<'a> crate::decode::Decode<'a, DB> + for<'a> crate::encode::Encode<'a, DB>, +{ + fn add_json<'a, A>( + args: &mut A, + value: Box, + ) -> Result<(), crate::error::BoxDynError> + where + A: crate::arguments::Arguments<'a, Database = Self>, + { + args.add(crate::types::Json(value)) + } + + fn decode_json<'a>( + value: ::ValueRef<'a>, + ) -> Result, crate::error::BoxDynError> { + use crate::decode::Decode; + >>::decode(value).map(|j| j.0) + } +} + pub type AnyPool = crate::pool::Pool; pub type AnyPoolOptions = crate::pool::PoolOptions; diff --git a/sqlx-core/src/any/row.rs b/sqlx-core/src/any/row.rs index 57b8590b5f..dc5319f1b3 100644 --- a/sqlx-core/src/any/row.rs +++ b/sqlx-core/src/any/row.rs @@ -1,5 +1,5 @@ use crate::any::error::mismatched_types; -use crate::any::{Any, AnyColumn, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind}; +use crate::any::{Any, AnyColumn, AnyJson, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind}; use crate::column::{Column, ColumnIndex}; use crate::database::Database; use crate::decode::Decode; @@ -85,6 +85,7 @@ impl AnyRow { ) -> Result where usize: ColumnIndex, + R::Database: AnyJson, AnyTypeInfo: for<'b> TryFrom<&'b ::TypeInfo, Error = Error>, AnyColumn: for<'b> TryFrom<&'b ::Column, Error = Error>, bool: Type + Decode<'a, R::Database>, @@ -95,6 +96,7 @@ impl AnyRow { f64: Type + Decode<'a, R::Database>, String: Type + Decode<'a, R::Database>, Vec: Type + Decode<'a, R::Database>, + R::Database: AnyJson, { let mut row_out = AnyRow { column_names, @@ -127,6 +129,15 @@ impl AnyRow { AnyTypeInfoKind::Double => AnyValueKind::Double(decode(value)?), AnyTypeInfoKind::Blob => AnyValueKind::Blob(decode::<_, Vec>(value)?.into()), AnyTypeInfoKind::Text => AnyValueKind::Text(decode::<_, String>(value)?.into()), + #[cfg(feature = "json")] + AnyTypeInfoKind::Json => { + AnyValueKind::Json(R::Database::decode_json(value).map_err(|e| { + Error::ColumnDecode { + index: col.ordinal().to_string(), + source: e, + } + })?) + } }; row_out.columns.push(any_col); diff --git a/sqlx-core/src/any/type_info.rs b/sqlx-core/src/any/type_info.rs index 0879b333ca..3f4f729c5b 100644 --- a/sqlx-core/src/any/type_info.rs +++ b/sqlx-core/src/any/type_info.rs @@ -27,6 +27,9 @@ pub enum AnyTypeInfoKind { Double, Text, Blob, + #[cfg(feature = "json")] + #[cfg_attr(docsrs, doc(cfg(feature = "json")))] + Json, } impl TypeInfo for AnyTypeInfo { @@ -47,6 +50,8 @@ impl TypeInfo for AnyTypeInfo { Text => "TEXT", Blob => "BLOB", Null => "NULL", + #[cfg(feature = "json")] + Json => "JSON", } } } diff --git a/sqlx-core/src/any/types/json.rs b/sqlx-core/src/any/types/json.rs index 7114dd7dd5..13a22647ab 100644 --- a/sqlx-core/src/any/types/json.rs +++ b/sqlx-core/src/any/types/json.rs @@ -8,12 +8,15 @@ use serde::{Deserialize, Serialize}; impl Type for Json { fn type_info() -> AnyTypeInfo { AnyTypeInfo { - kind: AnyTypeInfoKind::Text, + kind: AnyTypeInfoKind::Json, } } fn compatible(ty: &AnyTypeInfo) -> bool { - matches!(ty.kind, AnyTypeInfoKind::Text | AnyTypeInfoKind::Blob) + matches!( + ty.kind, + AnyTypeInfoKind::Json | AnyTypeInfoKind::Text | AnyTypeInfoKind::Blob + ) } } @@ -23,7 +26,8 @@ where { fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'_>) -> Result { let json_string = self.encode_to_string()?; - buf.0.push(AnyValueKind::Text(json_string.into())); + let raw_value = serde_json::value::RawValue::from_string(json_string)?; + buf.0.push(AnyValueKind::Json(raw_value)); Ok(IsNull::No) } } @@ -34,6 +38,8 @@ where { fn decode(value: AnyValueRef<'_>) -> Result { match value.kind { + #[cfg(feature = "json")] + AnyValueKind::Json(raw) => Json::decode_from_string(raw.get()), AnyValueKind::Text(text) => Json::decode_from_string(&text), AnyValueKind::Blob(blob) => Json::decode_from_bytes(&blob), other => other.unexpected(), diff --git a/sqlx-core/src/any/types/mod.rs b/sqlx-core/src/any/types/mod.rs index eb62f30412..8a745b765c 100644 --- a/sqlx-core/src/any/types/mod.rs +++ b/sqlx-core/src/any/types/mod.rs @@ -21,6 +21,8 @@ mod blob; mod bool; mod float; mod int; +#[cfg(feature = "json")] +#[cfg_attr(docsrs, doc(cfg(feature = "json")))] mod json; mod str; @@ -53,5 +55,6 @@ fn test_type_impls() { has_type::(); // JSON types + #[cfg(feature = "json")] has_type::>(); } diff --git a/sqlx-core/src/any/value.rs b/sqlx-core/src/any/value.rs index 9917b39f4f..766be7299a 100644 --- a/sqlx-core/src/any/value.rs +++ b/sqlx-core/src/any/value.rs @@ -18,6 +18,9 @@ pub enum AnyValueKind<'a> { Double(f64), Text(Cow<'a, str>), Blob(Cow<'a, [u8]>), + #[cfg(feature = "json")] + #[cfg_attr(docsrs, doc(cfg(feature = "json")))] + Json(Box), } impl AnyValueKind<'_> { @@ -33,6 +36,8 @@ impl AnyValueKind<'_> { AnyValueKind::Double(_) => AnyTypeInfoKind::Double, AnyValueKind::Text(_) => AnyTypeInfoKind::Text, AnyValueKind::Blob(_) => AnyTypeInfoKind::Blob, + #[cfg(feature = "json")] + AnyValueKind::Json(_) => AnyTypeInfoKind::Json, }, } } @@ -83,6 +88,8 @@ impl Value for AnyValue { AnyValueKind::Double(d) => AnyValueKind::Double(*d), AnyValueKind::Text(t) => AnyValueKind::Text(Cow::Borrowed(t)), AnyValueKind::Blob(b) => AnyValueKind::Blob(Cow::Borrowed(b)), + #[cfg(feature = "json")] + AnyValueKind::Json(j) => AnyValueKind::Json(j.clone()), }, } } @@ -111,6 +118,8 @@ impl<'a> ValueRef<'a> for AnyValueRef<'a> { AnyValueKind::Double(d) => AnyValueKind::Double(*d), AnyValueKind::Text(t) => AnyValueKind::Text(Cow::Owned(t.to_string())), AnyValueKind::Blob(b) => AnyValueKind::Blob(Cow::Owned(b.to_vec())), + #[cfg(feature = "json")] + AnyValueKind::Json(j) => AnyValueKind::Json(j.clone()), }, } } diff --git a/sqlx-postgres/src/any.rs b/sqlx-postgres/src/any.rs index d24145637c..dcfaf4ffbd 100644 --- a/sqlx-postgres/src/any.rs +++ b/sqlx-postgres/src/any.rs @@ -197,6 +197,8 @@ impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo { PgType::Bytea => AnyTypeInfoKind::Blob, PgType::Text | PgType::Varchar => AnyTypeInfoKind::Text, PgType::DeclareWithName(UStr::Static("citext")) => AnyTypeInfoKind::Text, + #[cfg(feature = "json")] + PgType::Json | PgType::Jsonb => AnyTypeInfoKind::Json, _ => { return Err(sqlx_core::Error::AnyDriverError( format!("Any driver does not support the Postgres type {pg_type:?}").into(), diff --git a/sqlx-sqlite/src/any.rs b/sqlx-sqlite/src/any.rs index 636f986bf5..9a7f2cd738 100644 --- a/sqlx-sqlite/src/any.rs +++ b/sqlx-sqlite/src/any.rs @@ -221,6 +221,8 @@ fn map_arguments(args: AnyArguments<'_>) -> SqliteArguments { AnyValueKind::Double(d) => SqliteArgumentValue::Double(d), AnyValueKind::Text(t) => SqliteArgumentValue::Text(Arc::new(t.to_string())), AnyValueKind::Blob(b) => SqliteArgumentValue::Blob(Arc::new(b.to_vec())), + #[cfg(feature = "json")] + AnyValueKind::Json(j) => SqliteArgumentValue::Text(Arc::new(j.get().to_string())), // AnyValueKind is `#[non_exhaustive]` but we should have covered everything _ => unreachable!("BUG: missing mapping for {val:?}"), }) From 18bbab81b4d4786fbb850f80fcaad17691e52992 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 16:26:32 -0700 Subject: [PATCH 08/15] add mysql mapping --- sqlx-mysql/src/any.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlx-mysql/src/any.rs b/sqlx-mysql/src/any.rs index 70b7ad4511..edeaab13d2 100644 --- a/sqlx-mysql/src/any.rs +++ b/sqlx-mysql/src/any.rs @@ -168,6 +168,8 @@ impl<'a> TryFrom<&'a MySqlTypeInfo> for AnyTypeInfo { ColumnType::String | ColumnType::VarString | ColumnType::VarChar => { AnyTypeInfoKind::Text } + #[cfg(feature = "json")] + ColumnType::Json => AnyTypeInfoKind::Json, _ => { return Err(sqlx_core::Error::AnyDriverError( format!("Any driver does not support MySql type {type_info:?}").into(), From 35364e43d98e7598b1df6a7144143e9fcd353d14 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 25 Aug 2025 16:35:30 -0700 Subject: [PATCH 09/15] clippy --- sqlx-core/src/any/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlx-core/src/any/mod.rs b/sqlx-core/src/any/mod.rs index 369eacab43..1de3b55505 100644 --- a/sqlx-core/src/any/mod.rs +++ b/sqlx-core/src/any/mod.rs @@ -62,8 +62,8 @@ pub trait AnyJson: crate::database::Database { A: crate::arguments::Arguments<'a, Database = Self>; #[cfg(feature = "json")] - fn decode_json<'a>( - value: ::ValueRef<'a>, + fn decode_json( + value: ::ValueRef<'_>, ) -> Result, crate::error::BoxDynError>; } @@ -89,8 +89,8 @@ where args.add(crate::types::Json(value)) } - fn decode_json<'a>( - value: ::ValueRef<'a>, + fn decode_json( + value: ::ValueRef<'_>, ) -> Result, crate::error::BoxDynError> { use crate::decode::Decode; >>::decode(value).map(|j| j.0) From 977b3813ae97aba7d2e32d84f778b15e998211d4 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 07:04:37 -0700 Subject: [PATCH 10/15] us instead of ? --- tests/any/any.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/any/any.rs b/tests/any/any.rs index c137e104ce..5ede63cfa2 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -169,7 +169,7 @@ async fn it_encodes_decodes_json() -> anyhow::Result<()> { .await?; // Insert into the temporary table: - sqlx::query("insert into json_test (data) values (?)") + sqlx::query("insert into json_test (data) values ($1)") .bind(Json(&json_value)) .execute(&mut conn) .await?; From 63e43407e203e627695e94d658a2efbaba3b6ef8 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 08:00:05 -0700 Subject: [PATCH 11/15] revert Type impl for Box --- sqlx-core/src/types/json.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sqlx-core/src/types/json.rs b/sqlx-core/src/types/json.rs index f9d95c9b01..2cff5e149b 100644 --- a/sqlx-core/src/types/json.rs +++ b/sqlx-core/src/types/json.rs @@ -196,20 +196,6 @@ where } } -impl Type for Box -where - for<'a> Json<&'a Self>: Type, - DB: Database, -{ - fn type_info() -> DB::TypeInfo { - as Type>::type_info() - } - - fn compatible(ty: &DB::TypeInfo) -> bool { - as Type>::compatible(ty) - } -} - impl<'q, DB> Encode<'q, DB> for JsonRawValue where for<'a> Json<&'a Self>: Encode<'q, DB>, From 4fd03ec32eb5769ae3592e602bce0fcd80f71d86 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 08:33:12 -0700 Subject: [PATCH 12/15] use feature flags for query format --- tests/any/any.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/any/any.rs b/tests/any/any.rs index 5ede63cfa2..531054d82d 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -168,8 +168,14 @@ async fn it_encodes_decodes_json() -> anyhow::Result<()> { .execute(&mut conn) .await?; + #[cfg(feature = "postgres")] + let query = "insert into json_test (data) values ($1)"; + + #[cfg(not(feature = "postgres"))] + let query = "insert into json_test (data) values (?)"; + // Insert into the temporary table: - sqlx::query("insert into json_test (data) values ($1)") + sqlx::query(query) .bind(Json(&json_value)) .execute(&mut conn) .await?; From 6c904932a419f0604323d11d5d22f1a48667cd80 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 08:50:41 -0700 Subject: [PATCH 13/15] revert Cargo.toml --- Cargo.toml | 148 +++++++++++------------------------------------------ 1 file changed, 30 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e393e485a6..9c37eb4a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,20 +64,10 @@ default = ["any", "macros", "migrate", "json"] derive = ["sqlx-macros/derive"] macros = ["derive", "sqlx-macros/macros"] -migrate = [ - "sqlx-core/migrate", - "sqlx-macros?/migrate", - "sqlx-mysql?/migrate", - "sqlx-postgres?/migrate", - "sqlx-sqlite?/migrate", -] +migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] # Enable parsing of `sqlx.toml` for configuring macros and migrations. -sqlx-toml = [ - "sqlx-core/sqlx-toml", - "sqlx-macros?/sqlx-toml", - "sqlx-sqlite?/sqlx-toml", -] +sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-macros?/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] # intended mainly for CI and docs all-databases = ["mysql", "sqlite", "postgres", "any"] @@ -92,40 +82,27 @@ _unstable-all-types = [ "mac_address", "uuid", "bit-vec", - "bstr", + "bstr" ] # Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`). _unstable-docs = [ "all-databases", "_unstable-all-types", - "sqlx-sqlite/_unstable-docs", + "sqlx-sqlite/_unstable-docs" ] # Base runtime features without TLS -runtime-async-std = [ - "_rt-async-std", - "sqlx-core/_rt-async-std", - "sqlx-macros?/_rt-async-std", -] +runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"] runtime-tokio = ["_rt-tokio", "sqlx-core/_rt-tokio", "sqlx-macros?/_rt-tokio"] # TLS features tls-native-tls = ["sqlx-core/_tls-native-tls", "sqlx-macros?/_tls-native-tls"] tls-rustls = ["tls-rustls-ring"] # For backwards compatibility -tls-rustls-aws-lc-rs = [ - "sqlx-core/_tls-rustls-aws-lc-rs", - "sqlx-macros?/_tls-rustls-aws-lc-rs", -] +tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs", "sqlx-macros?/_tls-rustls-aws-lc-rs"] tls-rustls-ring = ["tls-rustls-ring-webpki"] # For backwards compatibility -tls-rustls-ring-webpki = [ - "sqlx-core/_tls-rustls-ring-webpki", - "sqlx-macros?/_tls-rustls-ring-webpki", -] -tls-rustls-ring-native-roots = [ - "sqlx-core/_tls-rustls-ring-native-roots", - "sqlx-macros?/_tls-rustls-ring-native-roots", -] +tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki", "sqlx-macros?/_tls-rustls-ring-webpki"] +tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots", "sqlx-macros?/_tls-rustls-ring-native-roots"] # No-op feature used by the workflows to compile without TLS enabled. Not meant for general use. tls-none = [] @@ -136,28 +113,14 @@ _rt-tokio = [] _sqlite = [] # database -any = [ - "sqlx-core/any", - "sqlx-mysql?/any", - "sqlx-postgres?/any", - "sqlx-sqlite?/any", -] +any = ["sqlx-core/any", "sqlx-mysql?/any", "sqlx-postgres?/any", "sqlx-sqlite?/any"] postgres = ["sqlx-postgres", "sqlx-macros?/postgres"] mysql = ["sqlx-mysql", "sqlx-macros?/mysql"] -sqlite = [ - "sqlite-bundled", - "sqlite-deserialize", - "sqlite-load-extension", - "sqlite-unlock-notify", -] +sqlite = ["sqlite-bundled", "sqlite-deserialize", "sqlite-load-extension", "sqlite-unlock-notify"] # SQLite base features sqlite-bundled = ["_sqlite", "sqlx-sqlite/bundled", "sqlx-macros?/sqlite"] -sqlite-unbundled = [ - "_sqlite", - "sqlx-sqlite/unbundled", - "sqlx-macros?/sqlite-unbundled", -] +sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled", "sqlx-macros?/sqlite-unbundled"] # SQLite features using conditionally compiled APIs # Note: these assume `sqlite-bundled` or `sqlite-unbundled` is also enabled @@ -169,10 +132,7 @@ sqlite-deserialize = ["sqlx-sqlite/deserialize"] # Enable `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()`. # Also required to use `drivers.sqlite.unsafe-load-extensions` from `sqlx.toml`. # Cannot be used with `-DSQLITE_OMIT_LOAD_EXTENSION` -sqlite-load-extension = [ - "sqlx-sqlite/load-extension", - "sqlx-macros?/sqlite-load-extension", -] +sqlite-load-extension = ["sqlx-sqlite/load-extension", "sqlx-macros?/sqlite-load-extension"] # Enables `sqlite3_preupdate_hook` # Requires `-DSQLITE_ENABLE_PREUPDATE_HOOK` (set automatically with `sqlite-bundled`) @@ -183,63 +143,17 @@ sqlite-preupdate-hook = ["sqlx-sqlite/preupdate-hook"] sqlite-unlock-notify = ["sqlx-sqlite/unlock-notify"] # types -json = [ - "sqlx-core/json", - "sqlx-macros?/json", - "sqlx-mysql?/json", - "sqlx-postgres?/json", - "sqlx-sqlite?/json", -] +json = ["sqlx-core/json", "sqlx-macros?/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"] -bigdecimal = [ - "sqlx-core/bigdecimal", - "sqlx-macros?/bigdecimal", - "sqlx-mysql?/bigdecimal", - "sqlx-postgres?/bigdecimal", -] -bit-vec = [ - "sqlx-core/bit-vec", - "sqlx-macros?/bit-vec", - "sqlx-postgres?/bit-vec", -] -chrono = [ - "sqlx-core/chrono", - "sqlx-macros?/chrono", - "sqlx-mysql?/chrono", - "sqlx-postgres?/chrono", - "sqlx-sqlite?/chrono", -] +bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros?/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] +bit-vec = ["sqlx-core/bit-vec", "sqlx-macros?/bit-vec", "sqlx-postgres?/bit-vec"] +chrono = ["sqlx-core/chrono", "sqlx-macros?/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] ipnet = ["sqlx-core/ipnet", "sqlx-macros?/ipnet", "sqlx-postgres?/ipnet"] -ipnetwork = [ - "sqlx-core/ipnetwork", - "sqlx-macros?/ipnetwork", - "sqlx-postgres?/ipnetwork", -] -mac_address = [ - "sqlx-core/mac_address", - "sqlx-macros?/mac_address", - "sqlx-postgres?/mac_address", -] -rust_decimal = [ - "sqlx-core/rust_decimal", - "sqlx-macros?/rust_decimal", - "sqlx-mysql?/rust_decimal", - "sqlx-postgres?/rust_decimal", -] -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", -] +ipnetwork = ["sqlx-core/ipnetwork", "sqlx-macros?/ipnetwork", "sqlx-postgres?/ipnetwork"] +mac_address = ["sqlx-core/mac_address", "sqlx-macros?/mac_address", "sqlx-postgres?/mac_address"] +rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] +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"] @@ -261,16 +175,11 @@ sqlx = { version = "=0.9.0-alpha.1", path = "." } # These are optional unless enabled in a workspace crate. bigdecimal = "0.4.0" bit-vec = "0.6.3" -chrono = { version = "0.4.34", default-features = false, features = [ - "std", - "clock", -] } +chrono = { version = "0.4.34", default-features = false, features = ["std", "clock"] } ipnet = "2.3.0" ipnetwork = "0.21.1" mac_address = "1.1.5" -rust_decimal = { version = "1.26.1", default-features = false, features = [ - "std", -] } +rust_decimal = { version = "1.26.1", default-features = false, features = ["std"] } time = { version = "0.3.36", features = ["formatting", "parsing", "macros"] } uuid = "1.1.2" @@ -297,9 +206,7 @@ sqlx-sqlite = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.52" time_ = { version = "0.3.2", package = "time" } -futures-util = { version = "0.3.19", default-features = false, features = [ - "alloc", -] } +futures-util = { version = "0.3.19", default-features = false, features = ["alloc"] } env_logger = "0.11" async-std = { workspace = true, features = ["attributes"] } tokio = { version = "1.15.0", features = ["full"] } @@ -392,7 +299,12 @@ required-features = ["sqlite"] [[test]] name = "sqlite-macros" path = "tests/sqlite/macros.rs" -required-features = ["_sqlite", "macros"] +required-features = ["sqlite", "macros"] + +[[test]] +name = "sqlite-unbundled-macros" +path = "tests/sqlite/macros.rs" +required-features = ["sqlite-unbundled", "macros"] [[test]] name = "sqlite-derives" From 402ce89a707052dd544713126ad31214d6c1cc19 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 08:53:33 -0700 Subject: [PATCH 14/15] copy directly from main --- Cargo.toml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c37eb4a5d..ae7852d6b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -299,12 +299,7 @@ required-features = ["sqlite"] [[test]] name = "sqlite-macros" path = "tests/sqlite/macros.rs" -required-features = ["sqlite", "macros"] - -[[test]] -name = "sqlite-unbundled-macros" -path = "tests/sqlite/macros.rs" -required-features = ["sqlite-unbundled", "macros"] +required-features = ["_sqlite", "macros"] [[test]] name = "sqlite-derives" @@ -443,4 +438,4 @@ required-features = ["postgres"] [[test]] name = "postgres-rustsec" path = "tests/postgres/rustsec.rs" -required-features = ["postgres", "macros", "migrate"] +required-features = ["postgres", "macros", "migrate"] \ No newline at end of file From 34bf530e28efc58730c45a7a6e60f834f9d9d5ba Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 26 Aug 2025 08:54:32 -0700 Subject: [PATCH 15/15] add newline to cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ae7852d6b3..e8279e3148 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -438,4 +438,4 @@ required-features = ["postgres"] [[test]] name = "postgres-rustsec" path = "tests/postgres/rustsec.rs" -required-features = ["postgres", "macros", "migrate"] \ No newline at end of file +required-features = ["postgres", "macros", "migrate"]