diff --git a/Cargo.lock b/Cargo.lock index 5ca5fe497c4..ba368db9378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1935,6 +1935,9 @@ dependencies = [ "console", "lazy_static", "linked-hash-map", + "pest", + "pest_derive", + "serde", "similar", "yaml-rust", ] @@ -3598,6 +3601,7 @@ dependencies = [ "connection-string", "crosstarget-utils", "either", + "expect-test", "futures", "getrandom 0.2.11", "hex", @@ -5082,6 +5086,7 @@ dependencies = [ "bigdecimal", "chrono", "cuid", + "expect-test", "futures", "itertools 0.12.0", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 1403197c3e8..e8fccb1e96c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ members = [ async-trait = { version = "0.1.77" } enumflags2 = { version = "0.7", features = ["serde"] } psl = { path = "./psl/psl" } -serde_json = { version = "1", features = ["float_roundtrip", "preserve_order"] } +serde_json = { version = "1", features = ["float_roundtrip", "preserve_order", "raw_value"] } serde = { version = "1", features = ["derive"] } tokio = { version = "1.25", features = [ "rt-multi-thread", diff --git a/libs/prisma-value/src/lib.rs b/libs/prisma-value/src/lib.rs index 43aff0156dd..0d6fe54a254 100644 --- a/libs/prisma-value/src/lib.rs +++ b/libs/prisma-value/src/lib.rs @@ -1,6 +1,7 @@ pub mod arithmetic; mod error; +mod raw_json; use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use chrono::prelude::*; @@ -11,9 +12,12 @@ use std::{convert::TryFrom, fmt, str::FromStr}; use uuid::Uuid; pub use error::ConversionFailure; +pub use raw_json::RawJson; pub type PrismaValueResult = std::result::Result; pub type PrismaListValue = Vec; +pub use base64::encode as encode_base64; + #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[serde(untagged)] pub enum PrismaValue { diff --git a/libs/prisma-value/src/raw_json.rs b/libs/prisma-value/src/raw_json.rs new file mode 100644 index 00000000000..e0e3596b05d --- /dev/null +++ b/libs/prisma-value/src/raw_json.rs @@ -0,0 +1,38 @@ +use serde::Serialize; +use serde_json::value::RawValue; + +/// We are using RawJson object to prevent stringification of +/// certain JSON values. Difference between this is and PrismaValue::Json +/// is the following: +/// +/// PrismaValue::Json(r"""{"foo": "bar"}""") when serialized will produce the string "{\"foo\":\"bar\"}". +/// RawJson(r"""{"foo": "bar"}""") will produce {"foo": "bar" } JSON object. +/// So, it essentially would treat provided string as pre-serialized JSON fragment and not a string to be serialized. +/// +/// It is a wrapper of `serde_json::value::RawValue`. We don't want to use `RawValue` inside of `ArgumentValue` +/// directly because: +/// 1. We need `Eq` implementation +/// 2. `serde_json::value::RawValue::from_string` may error and we'd like to delay handling of that error to +/// serialization time +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RawJson { + value: String, +} + +impl RawJson { + pub fn try_new(value: impl Serialize) -> serde_json::Result { + Ok(Self { + value: serde_json::to_string(&value)?, + }) + } +} + +impl Serialize for RawJson { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let raw_value = RawValue::from_string(self.value.to_owned()).map_err(serde::ser::Error::custom)?; + raw_value.serialize(serializer) + } +} diff --git a/quaint/Cargo.toml b/quaint/Cargo.toml index 66f917c90af..d30e2740878 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -89,7 +89,7 @@ native-tls = { version = "0.2", optional = true } bit-vec = { version = "0.6.1", optional = true } bytes = { version = "1.0", optional = true } mobc = { version = "0.8", optional = true } -serde = { version = "1.0", optional = true } +serde = { version = "1.0" } sqlformat = { version = "0.2.3", optional = true } uuid.workspace = true crosstarget-utils = { path = "../libs/crosstarget-utils" } @@ -103,6 +103,7 @@ serde = { version = "1.0", features = ["derive"] } quaint-test-macros = { path = "quaint-test-macros" } quaint-test-setup = { path = "quaint-test-setup" } tokio = { version = "1.0", features = ["macros", "time"] } +expect-test = "1" [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] version = "0.2" diff --git a/quaint/src/ast/enums.rs b/quaint/src/ast/enums.rs index a4e93836d24..3fa5a94ee10 100644 --- a/quaint/src/ast/enums.rs +++ b/quaint/src/ast/enums.rs @@ -13,6 +13,10 @@ impl<'a> EnumVariant<'a> { self.0.into_owned() } + pub fn inner(&self) -> &str { + self.0.as_ref() + } + pub fn into_text(self) -> Value<'a> { Value::text(self.0) } diff --git a/quaint/src/ast/values.rs b/quaint/src/ast/values.rs index a5708fd2f18..3a452f5954c 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -3,6 +3,7 @@ use crate::error::{Error, ErrorKind}; use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; + use serde_json::{Number, Value as JsonValue}; use std::fmt::Display; use std::{ diff --git a/quaint/src/connector/result_set.rs b/quaint/src/connector/result_set.rs index b98d252a057..7592a85ea15 100644 --- a/quaint/src/connector/result_set.rs +++ b/quaint/src/connector/result_set.rs @@ -71,14 +71,21 @@ impl ResultSet { None => Err(Error::builder(ErrorKind::NotFound).build()), } } + + pub fn iter(&self) -> ResultSetIterator<'_> { + ResultSetIterator { + columns: self.columns.clone(), + internal_iterator: self.rows.iter(), + } + } } impl IntoIterator for ResultSet { type Item = ResultRow; - type IntoIter = ResultSetIterator; + type IntoIter = ResultSetIntoIterator; fn into_iter(self) -> Self::IntoIter { - ResultSetIterator { + ResultSetIntoIterator { columns: self.columns, internal_iterator: self.rows.into_iter(), } @@ -87,12 +94,12 @@ impl IntoIterator for ResultSet { /// Thin iterator for ResultSet rows. /// Might become lazy one day. -pub struct ResultSetIterator { +pub struct ResultSetIntoIterator { pub(crate) columns: Arc>, pub(crate) internal_iterator: std::vec::IntoIter>>, } -impl Iterator for ResultSetIterator { +impl Iterator for ResultSetIntoIterator { type Item = ResultRow; fn next(&mut self) -> Option { @@ -106,6 +113,25 @@ impl Iterator for ResultSetIterator { } } +pub struct ResultSetIterator<'a> { + pub(crate) columns: Arc>, + pub(crate) internal_iterator: std::slice::Iter<'a, Vec>>, +} + +impl<'a> Iterator for ResultSetIterator<'a> { + type Item = ResultRowRef<'a>; + + fn next(&mut self) -> Option { + match self.internal_iterator.next() { + Some(row) => Some(ResultRowRef { + columns: Arc::clone(&self.columns), + values: row, + }), + None => None, + } + } +} + impl From for serde_json::Value { fn from(result_set: ResultSet) -> Self { let columns: Vec = result_set.columns().iter().map(ToString::to_string).collect(); diff --git a/quaint/src/connector/result_set/result_row.rs b/quaint/src/connector/result_set/result_row.rs index 2befc1b9b65..a6a5b55c3c6 100644 --- a/quaint/src/connector/result_set/result_row.rs +++ b/quaint/src/connector/result_set/result_row.rs @@ -41,6 +41,12 @@ pub struct ResultRowRef<'a> { pub(crate) values: &'a Vec>, } +impl<'a> ResultRowRef<'a> { + pub fn iter(&self) -> impl Iterator> { + self.values.iter() + } +} + impl ResultRow { /// Take a value from a certain position in the row, if having a value in /// that position. Usage documentation in @@ -108,4 +114,18 @@ impl<'a> ResultRowRef<'a> { pub fn get(&self, name: &str) -> Option<&'a Value<'static>> { self.columns.iter().position(|c| c == name).map(|idx| &self.values[idx]) } + + /// Returns the length of the row. + pub fn len(&self) -> usize { + self.values.len() + } + + /// Returns whether the rows are empty. + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + pub fn values(&self) -> impl Iterator> { + self.values.iter() + } } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml b/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml index c60b9cca459..46d1d4b845f 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml +++ b/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml @@ -26,5 +26,5 @@ futures = "0.3" paste = "1.0.14" [dev-dependencies] -insta = "1.7.1" +insta = { version = "1.7.1", features = ["json", "redactions"] } itertools.workspace = true diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/interactive_tx.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/interactive_tx.rs index bf738076d91..da0db0a0e70 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/interactive_tx.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/interactive_tx.rs @@ -145,7 +145,7 @@ mod interactive_tx { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw("SELECT * FROM \"TestModel\"", vec![])), - @r###"{"data":{"queryRaw":[{"id":{"prisma__type":"int","prisma__value":1},"field":{"prisma__type":"string","prisma__value":"Test"}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["id","field"],"types":["int","string"],"rows":[[1,"Test"]]}}}"### ); let res = runner.commit_tx(tx_id.clone()).await?; @@ -155,7 +155,7 @@ mod interactive_tx { // Data still there after commit. insta::assert_snapshot!( run_query!(&runner, fmt_query_raw("SELECT * FROM \"TestModel\"", vec![])), - @r###"{"data":{"queryRaw":[{"id":{"prisma__type":"int","prisma__value":1},"field":{"prisma__type":"string","prisma__value":"Test"}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["id","field"],"types":["int","string"],"rows":[[1,"Test"]]}}}"### ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_21369.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_21369.rs index f25a83629da..887e6eb6ec8 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_21369.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_21369.rs @@ -4,12 +4,12 @@ use query_engine_tests::*; mod prisma_21369 { #[connector_test] async fn select_null_works(runner: Runner) -> TestResult<()> { - let query = fmt_query_raw("SELECT NULL AS result", []); - let result = run_query!(runner, query); + match_connector_result!( + &runner, + fmt_query_raw("SELECT NULL AS result", []), + Sqlite(_) | MySql(_) | SqlServer(_) | Vitess(_) => vec![r#"{"data":{"queryRaw":{"columns":["result"],"types":["int"],"rows":[[null]]}}}"#], + _ => vec![r#"{"data":{"queryRaw":{"columns":["result"],"types":["string"],"rows":[[null]]}}}"#] - assert_eq!( - result, - r#"{"data":{"queryRaw":[{"result":{"prisma__type":"null","prisma__value":null}}]}}"# ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_6173.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_6173.rs index 92fbaa0e1dd..3aa1e9b0e2d 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_6173.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_6173.rs @@ -16,18 +16,40 @@ mod query_raw { // MariaDB is the only one supporting anon blocks #[connector_test(only(MySQL("mariadb")))] async fn mysql_call(runner: Runner) -> TestResult<()> { + let res = run_query_json!( + &runner, + r#" + mutation { + queryRaw( + query: "BEGIN NOT ATOMIC\n INSERT INTO Test VALUES(FLOOR(RAND()*1000));\n SELECT * FROM Test;\n END", + parameters: "[]" + ) + } + "# + ); // fmt_execute_raw cannot run this query, doing it directly instead - runner - .query(indoc! {r#" - mutation { - queryRaw( - query: "BEGIN NOT ATOMIC\n INSERT INTO Test VALUES(FLOOR(RAND()*1000));\n SELECT * FROM Test;\n END", - parameters: "[]" - ) + insta::assert_json_snapshot!(res, + { + ".data.queryRaw.rows[0][0]" => "" + }, @r###" + { + "data": { + "queryRaw": { + "columns": [ + "f0" + ], + "types": [ + "int" + ], + "rows": [ + [ + "" + ] + ] } - "#}) - .await? - .assert_success(); + } + } + "###); Ok(()) } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/batching/transactional_batch.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/batching/transactional_batch.rs index 0130b3ee710..6ebe42d0b08 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/batching/transactional_batch.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/batching/transactional_batch.rs @@ -176,7 +176,7 @@ mod transactional { let batch_results = runner.batch(queries, true, None).await?; insta::assert_snapshot!( batch_results.to_string(), - @r###"{"batchResult":[{"data":{"createOneModelB":{"id":1}}},{"data":{"executeRaw":1}},{"data":{"queryRaw":[]}}]}"### + @r###"{"batchResult":[{"data":{"createOneModelB":{"id":1}}},{"data":{"executeRaw":1}},{"data":{"queryRaw":{"columns":["id"],"types":[],"rows":[]}}}]}"### ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/casts.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/casts.rs index 91483cb5e06..a7832c8b1c6 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/casts.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/casts.rs @@ -69,70 +69,61 @@ mod casts { @r###" { "data": { - "queryRaw": [ - { - "i8_to_f4": { - "prisma__type": "float", - "prisma__value": 42.0 - }, - "i8_to_f8": { - "prisma__type": "double", - "prisma__value": 42.0 - }, - "numeric_to_i4": { - "prisma__type": "int", - "prisma__value": 43 - }, - "numeric_to_i8": { - "prisma__type": "bigint", - "prisma__value": "43" - }, - "bigint_to_i4": { - "prisma__type": "int", - "prisma__value": 42 - }, - "bigint_to_f4": { - "prisma__type": "float", - "prisma__value": 42.0 - }, - "bigint_to_f8": { - "prisma__type": "double", - "prisma__value": 42.0 - }, - "decimal_to_i4": { - "prisma__type": "int", - "prisma__value": 43 - }, - "decimal_to_i8": { - "prisma__type": "bigint", - "prisma__value": "43" - }, - "decimal_to_f4": { - "prisma__type": "float", - "prisma__value": 42.5099983215332 - }, - "decimal_to_f8": { - "prisma__type": "double", - "prisma__value": 42.51 - }, - "text_to_i4": { - "prisma__type": "int", - "prisma__value": 42 - }, - "text_to_i8": { - "prisma__type": "bigint", - "prisma__value": "42" - }, - "text_to_f4": { - "prisma__type": "float", - "prisma__value": 42.5099983215332 - }, - "text_to_f8": { - "prisma__type": "double", - "prisma__value": 42.51 - } - } - ] + "queryRaw": { + "columns": [ + "i8_to_f4", + "i8_to_f8", + "numeric_to_i4", + "numeric_to_i8", + "bigint_to_i4", + "bigint_to_f4", + "bigint_to_f8", + "decimal_to_i4", + "decimal_to_i8", + "decimal_to_f4", + "decimal_to_f8", + "text_to_i4", + "text_to_i8", + "text_to_f4", + "text_to_f8" + ], + "types": [ + "float", + "double", + "int", + "bigint", + "int", + "float", + "double", + "int", + "bigint", + "float", + "double", + "int", + "bigint", + "float", + "double" + ], + "rows": [ + [ + 42.0, + 42.0, + 43, + "43", + 42, + 42.0, + 42.0, + 43, + "43", + 42.51, + 42.51, + 42, + "42", + 42.51, + 42.51 + ] + ] + } } } "### @@ -161,22 +152,25 @@ mod casts { @r###" { "data": { - "queryRaw": [ - { - "text_to_interval": { - "prisma__type": "datetime", - "prisma__value": "2021-01-01T00:00:00+00:00" - }, - "is_year_2023": { - "prisma__type": "bool", - "prisma__value": true - }, - "text_to_time": { - "prisma__type": "time", - "prisma__value": "12:34:00" - } - } - ] + "queryRaw": { + "columns": [ + "text_to_interval", + "is_year_2023", + "text_to_time" + ], + "types": [ + "datetime", + "bool", + "time" + ], + "rows": [ + [ + "2021-01-01T00:00:00+00:00", + true, + "12:34:00" + ] + ] + } } } "### diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/mod.rs index 1656da414f6..c7e3002c513 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/mod.rs @@ -1,5 +1,5 @@ mod casts; mod errors; mod input_coercion; -mod null_list; +mod scalar_list; mod typed_output; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/null_list.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/null_list.rs deleted file mode 100644 index 42c2a3c8a56..00000000000 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/null_list.rs +++ /dev/null @@ -1,268 +0,0 @@ -use indoc::indoc; -use query_engine_tests::*; - -#[test_suite(only(Postgres))] -mod null_list { - use query_engine_tests::{fmt_query_raw, run_query, run_query_pretty}; - - #[connector_test(schema(common_list_types), only(Postgres))] - async fn null_scalar_lists(runner: Runner) -> TestResult<()> { - run_query!( - &runner, - fmt_execute_raw( - r#"INSERT INTO "TestModel" ("id", "string", "int", "bInt", "float", "bytes", "bool", "dt") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);"#, - vec![ - RawParam::from(1), - RawParam::array(vec![RawParam::from("hello"), RawParam::Null]), - RawParam::array(vec![RawParam::from(1337), RawParam::Null]), - RawParam::array(vec![RawParam::bigint(133737), RawParam::Null]), - RawParam::array(vec![RawParam::from(13.37), RawParam::Null]), - RawParam::array(vec![RawParam::bytes(&[1, 2, 3]), RawParam::Null]), - RawParam::array(vec![RawParam::from(true), RawParam::Null]), - RawParam::array(vec![ - RawParam::try_datetime("1900-10-10T01:10:10.001Z")?, - RawParam::Null - ]), - ], - ) - ); - - insta::assert_snapshot!( - run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), - @r###" - { - "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "string": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "string", - "prisma__value": "hello" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "int": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "int", - "prisma__value": 1337 - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "bInt": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "bigint", - "prisma__value": "133737" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "float": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "double", - "prisma__value": 13.37 - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "bytes": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "bytes", - "prisma__value": "AQID" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "bool": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "bool", - "prisma__value": true - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "dt": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "datetime", - "prisma__value": "1900-10-10T01:10:10.001+00:00" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - } - } - ] - } - } - "### - ); - - Ok(()) - } - - fn native_list_types() -> String { - let schema = indoc! { - r#"model TestModel { - #id(id, Int, @id) - uuid String[] @test.Uuid - bit String[] @test.Bit(1) - inet String[] @test.Inet - oid Int[] @test.Oid - }"# - }; - - schema.to_owned() - } - - #[connector_test(schema(native_list_types))] - async fn null_native_type_lists(runner: Runner) -> TestResult<()> { - run_query!( - &runner, - fmt_execute_raw( - r#"INSERT INTO "TestModel" ("id", "uuid", "bit", "inet", "oid") VALUES ($1, $2, $3, $4, $5);"#, - vec![ - RawParam::from(1), - RawParam::array(vec![ - RawParam::from("936DA01F-9ABD-4D9D-80C7-02AF85C822A8"), - RawParam::Null - ]), - RawParam::array(vec![RawParam::from("1"), RawParam::Null]), - RawParam::array(vec![RawParam::from("127.0.0.1"), RawParam::Null]), - RawParam::array(vec![RawParam::from(123), RawParam::Null]), - ], - ) - ); - - insta::assert_snapshot!( - run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), - @r###" - { - "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "uuid": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "uuid", - "prisma__value": "936da01f-9abd-4d9d-80c7-02af85c822a8" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "bit": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "string", - "prisma__value": "1" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "inet": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "string", - "prisma__value": "127.0.0.1" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - }, - "oid": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "bigint", - "prisma__value": "123" - }, - { - "prisma__type": "null", - "prisma__value": null - } - ] - } - } - ] - } - } - "### - ); - - Ok(()) - } - - // Regression test for https://github.com/prisma/prisma/issues/11339 - #[connector_test(schema(common_nullable_types))] - async fn prisma_11339(runner: Runner) -> TestResult<()> { - run_query!( - &runner, - "mutation { - createManyTestModel(data: [ - { id: 1, int: 1 }, - { id: 2 } - ]) { count } - }" - ); - - insta::assert_snapshot!( - run_query!(&runner, fmt_query_raw(r#"SELECT ARRAY_AGG(int) FROM "TestModel";"#, vec![])), - @r###"{"data":{"queryRaw":[{"array_agg":{"prisma__type":"array","prisma__value":[{"prisma__type":"int","prisma__value":1},{"prisma__type":"null","prisma__value":null}]}}]}}"### - ); - - Ok(()) - } -} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/scalar_list.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/scalar_list.rs new file mode 100644 index 00000000000..20b1e853b64 --- /dev/null +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/scalar_list.rs @@ -0,0 +1,363 @@ +use indoc::indoc; +use query_engine_tests::*; + +#[test_suite(only(Postgres, CockroachDb))] +mod scalar_list { + use query_engine_tests::{fmt_query_raw, run_query, run_query_pretty}; + + #[connector_test(schema(common_list_types))] + async fn null_scalar_lists(runner: Runner) -> TestResult<()> { + run_query!( + &runner, + fmt_execute_raw( + r#"INSERT INTO "TestModel" ("id", "string", "int", "bInt", "float", "bytes", "bool", "dt") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);"#, + vec![ + RawParam::from(1), + RawParam::array(vec![RawParam::Null, RawParam::from("hello")]), + RawParam::array(vec![RawParam::from(1337), RawParam::Null]), + RawParam::array(vec![RawParam::Null, RawParam::bigint(133737)]), + RawParam::array(vec![RawParam::from(13.37), RawParam::Null]), + RawParam::array(vec![RawParam::Null, RawParam::bytes(&[1, 2, 3])]), + RawParam::array(vec![RawParam::from(true), RawParam::Null]), + RawParam::array(vec![ + RawParam::Null, + RawParam::try_datetime("1900-10-10T01:10:10.001Z")?, + ]), + ], + ) + ); + + insta::assert_snapshot!( + run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), + @r###" + { + "data": { + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt" + ], + "types": [ + "int", + "string-array", + "int-array", + "bigint-array", + "double-array", + "bytes-array", + "bool-array", + "datetime-array" + ], + "rows": [ + [ + 1, + [ + null, + "hello" + ], + [ + 1337, + null + ], + [ + null, + "133737" + ], + [ + 13.37, + null + ], + [ + null, + "AQID" + ], + [ + true, + null + ], + [ + null, + "1900-10-10T01:10:10.001+00:00" + ] + ] + ] + } + } + } + "### + ); + + Ok(()) + } + + fn native_list_types() -> String { + let schema = indoc! { + r#"model TestModel { + #id(id, Int, @id) + uuid String[] @test.Uuid + bit String[] @test.Bit(1) + inet String[] @test.Inet + oid Int[] @test.Oid + }"# + }; + + schema.to_owned() + } + + #[connector_test(schema(native_list_types))] + async fn null_native_type_lists(runner: Runner) -> TestResult<()> { + run_query!( + &runner, + fmt_execute_raw( + r#"INSERT INTO "TestModel" ("id", "uuid", "bit", "inet", "oid") VALUES ($1, $2, $3, $4, $5);"#, + vec![ + RawParam::from(1), + RawParam::array(vec![ + RawParam::from("936DA01F-9ABD-4D9D-80C7-02AF85C822A8"), + RawParam::Null + ]), + RawParam::array(vec![RawParam::from("1"), RawParam::Null]), + RawParam::array(vec![RawParam::Null, RawParam::from("127.0.0.1")]), + RawParam::array(vec![RawParam::Null, RawParam::from(123)]), + ], + ) + ); + + insta::assert_snapshot!( + run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), + @r###" + { + "data": { + "queryRaw": { + "columns": [ + "id", + "uuid", + "bit", + "inet", + "oid" + ], + "types": [ + "int", + "uuid-array", + "string-array", + "string-array", + "bigint-array" + ], + "rows": [ + [ + 1, + [ + "936da01f-9abd-4d9d-80c7-02af85c822a8", + null + ], + [ + "1", + null + ], + [ + null, + "127.0.0.1" + ], + [ + null, + "123" + ] + ] + ] + } + } + } + "### + ); + + Ok(()) + } + + // Regression test for https://github.com/prisma/prisma/issues/11339 + #[connector_test(schema(common_nullable_types))] + async fn prisma_11339(runner: Runner) -> TestResult<()> { + run_query!( + &runner, + "mutation { + createManyTestModel(data: [ + { id: 1, int: 1 }, + { id: 2 } + ]) { count } + }" + ); + + insta::assert_snapshot!( + run_query!(&runner, fmt_query_raw(r#"SELECT ARRAY_AGG(int) FROM "TestModel";"#, vec![])), + @r###"{"data":{"queryRaw":{"columns":["array_agg"],"types":["int-array"],"rows":[[[1,null]]]}}}"### + ); + + Ok(()) + } + + #[connector_test(schema(common_list_types))] + async fn empty_scalar_lists(runner: Runner) -> TestResult<()> { + run_query!( + &runner, + fmt_execute_raw( + r#"INSERT INTO "TestModel" ("id", "string", "int", "bInt", "float", "bytes", "bool", "dt") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);"#, + vec![ + RawParam::from(1), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + ], + ) + ); + + run_query!( + &runner, + fmt_execute_raw( + r#"INSERT INTO "TestModel" ("id", "string", "int", "bInt", "float", "bytes", "bool", "dt") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);"#, + vec![ + RawParam::from(2), + RawParam::Array(vec![RawParam::Null, RawParam::from("hello")]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + RawParam::Array(vec![]), + ], + ) + ); + + insta::assert_snapshot!( + run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), + @r###" + { + "data": { + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt" + ], + "types": [ + "int", + "string-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array" + ], + "rows": [ + [ + 1, + [], + [], + [], + [], + [], + [], + [] + ], + [ + 2, + [ + null, + "hello" + ], + [], + [], + [], + [], + [], + [] + ] + ] + } + } + } + "### + ); + + Ok(()) + } + + #[connector_test(schema(common_list_types))] + async fn null_only_scalar_lists(runner: Runner) -> TestResult<()> { + run_query!( + &runner, + fmt_execute_raw( + r#"INSERT INTO "TestModel" ("id", "string", "int", "bInt", "float", "bytes", "bool", "dt") VALUES ($1, $2, $3, $4, $5, $6, $7, $8);"#, + vec![ + RawParam::from(1), + RawParam::Null, + RawParam::Null, + RawParam::Null, + RawParam::Null, + RawParam::Null, + RawParam::Null, + RawParam::Null, + ], + ) + ); + + insta::assert_snapshot!( + run_query_pretty!(&runner, fmt_query_raw(r#"SELECT * FROM "TestModel";"#, vec![])), + @r###" + { + "data": { + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt" + ], + "types": [ + "int", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array", + "unknown-array" + ], + "rows": [ + [ + 1, + null, + null, + null, + null, + null, + null, + null + ] + ] + } + } + } + "### + ); + + Ok(()) + } +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs index ac5f27f2a0f..fa7b8d64692 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs @@ -53,136 +53,76 @@ mod typed_output { @r###" { "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "string": { - "prisma__type": "string", - "prisma__value": "str" - }, - "int": { - "prisma__type": "int", - "prisma__value": 42 - }, - "bInt": { - "prisma__type": "bigint", - "prisma__value": "9223372036854775807" - }, - "float": { - "prisma__type": "double", - "prisma__value": 1.5432 - }, - "bytes": { - "prisma__type": "bytes", - "prisma__value": "AQID" - }, - "bool": { - "prisma__type": "bool", - "prisma__value": true - }, - "dt": { - "prisma__type": "datetime", - "prisma__value": "1900-10-10T01:10:10.001+00:00" - }, - "dec": { - "prisma__type": "decimal", - "prisma__value": "123.4567891" - }, - "json": { - "prisma__type": "json", - "prisma__value": { + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt", + "dec", + "json", + "string_list", + "bInt_list" + ], + "types": [ + "int", + "string", + "int", + "bigint", + "double", + "bytes", + "bool", + "datetime", + "decimal", + "json", + "string-array", + "bigint-array" + ], + "rows": [ + [ + 1, + "str", + 42, + "9223372036854775807", + 1.5432, + "AQID", + true, + "1900-10-10T01:10:10.001+00:00", + "123.4567891", + { "a": "b" - } - }, - "string_list": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "string", - "prisma__value": "1" - }, - { - "prisma__type": "string", - "prisma__value": "a" - }, - { - "prisma__type": "string", - "prisma__value": "2" - }, - { - "prisma__type": "string", - "prisma__value": "123123213" - } - ] - }, - "bInt_list": { - "prisma__type": "array", - "prisma__value": [ - { - "prisma__type": "bigint", - "prisma__value": "-9223372036854775808" - }, - { - "prisma__type": "bigint", - "prisma__value": "9223372036854775807" - } + }, + [ + "1", + "a", + "2", + "123123213" + ], + [ + "-9223372036854775808", + "9223372036854775807" ] - } - }, - { - "id": { - "prisma__type": "int", - "prisma__value": 2 - }, - "string": { - "prisma__type": "null", - "prisma__value": null - }, - "int": { - "prisma__type": "null", - "prisma__value": null - }, - "bInt": { - "prisma__type": "null", - "prisma__value": null - }, - "float": { - "prisma__type": "null", - "prisma__value": null - }, - "bytes": { - "prisma__type": "null", - "prisma__value": null - }, - "bool": { - "prisma__type": "null", - "prisma__value": null - }, - "dt": { - "prisma__type": "null", - "prisma__value": null - }, - "dec": { - "prisma__type": "null", - "prisma__value": null - }, - "json": { - "prisma__type": "null", - "prisma__value": null - }, - "string_list": { - "prisma__type": "null", - "prisma__value": null - }, - "bInt_list": { - "prisma__type": "null", - "prisma__value": null - } - } - ] + ], + [ + 2, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + ] + } } } "### @@ -190,7 +130,7 @@ mod typed_output { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT 1 + 1;"#, vec![])), - @r###"{"data":{"queryRaw":[{"?column?":{"prisma__type":"int","prisma__value":2}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["?column?"],"types":["int"],"rows":[[2]]}}}"### ); Ok(()) @@ -240,94 +180,60 @@ mod typed_output { @r###" { "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "string": { - "prisma__type": "string", - "prisma__value": "str" - }, - "int": { - "prisma__type": "int", - "prisma__value": 42 - }, - "bInt": { - "prisma__type": "bigint", - "prisma__value": "9223372036854775807" - }, - "float": { - "prisma__type": "double", - "prisma__value": 1.5432 - }, - "bytes": { - "prisma__type": "bytes", - "prisma__value": "AQID" - }, - "bool": { - "prisma__type": "int", - "prisma__value": 1 - }, - "dt": { - "prisma__type": "datetime", - "prisma__value": "1900-10-10T01:10:10.001+00:00" - }, - "dec": { - "prisma__type": "decimal", - "prisma__value": "123.4567891" - }, - "json": { - "prisma__type": "json", - "prisma__value": { + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt", + "dec", + "json" + ], + "types": [ + "int", + "string", + "int", + "bigint", + "double", + "bytes", + "int", + "datetime", + "decimal", + "json" + ], + "rows": [ + [ + 1, + "str", + 42, + "9223372036854775807", + 1.5432, + "AQID", + 1, + "1900-10-10T01:10:10.001+00:00", + "123.4567891", + { "a": "b" } - } - }, - { - "id": { - "prisma__type": "int", - "prisma__value": 2 - }, - "string": { - "prisma__type": "null", - "prisma__value": null - }, - "int": { - "prisma__type": "null", - "prisma__value": null - }, - "bInt": { - "prisma__type": "null", - "prisma__value": null - }, - "float": { - "prisma__type": "null", - "prisma__value": null - }, - "bytes": { - "prisma__type": "null", - "prisma__value": null - }, - "bool": { - "prisma__type": "null", - "prisma__value": null - }, - "dt": { - "prisma__type": "null", - "prisma__value": null - }, - "dec": { - "prisma__type": "null", - "prisma__value": null - }, - "json": { - "prisma__type": "null", - "prisma__value": null - } - } - ] + ], + [ + 2, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + ] + } } } "### @@ -335,7 +241,7 @@ mod typed_output { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT 1 + 1;"#, vec![])), - @r###"{"data":{"queryRaw":[{"1 + 1":{"prisma__type":"bigint","prisma__value":"2"}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["1 + 1"],"types":["bigint"],"rows":[["2"]]}}}"### ); Ok(()) @@ -366,92 +272,58 @@ mod typed_output { @r###" { "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "string": { - "prisma__type": "string", - "prisma__value": "str" - }, - "int": { - "prisma__type": "int", - "prisma__value": 42 - }, - "bInt": { - "prisma__type": "bigint", - "prisma__value": "9223372036854775807" - }, - "float": { - "prisma__type": "double", - "prisma__value": 1.5432 - }, - "bytes": { - "prisma__type": "bytes", - "prisma__value": "AQID" - }, - "bool": { - "prisma__type": "int", - "prisma__value": 1 - }, - "dt": { - "prisma__type": "datetime", - "prisma__value": "1900-10-10T01:10:10.001+00:00" - }, - "dec": { - "prisma__type": "decimal", - "prisma__value": "123.4567891" - }, - "json": { - "prisma__type": "string", - "prisma__value": "{\"a\":\"b\"}" - } - }, - { - "id": { - "prisma__type": "int", - "prisma__value": 2 - }, - "string": { - "prisma__type": "null", - "prisma__value": null - }, - "int": { - "prisma__type": "null", - "prisma__value": null - }, - "bInt": { - "prisma__type": "null", - "prisma__value": null - }, - "float": { - "prisma__type": "null", - "prisma__value": null - }, - "bytes": { - "prisma__type": "null", - "prisma__value": null - }, - "bool": { - "prisma__type": "null", - "prisma__value": null - }, - "dt": { - "prisma__type": "null", - "prisma__value": null - }, - "dec": { - "prisma__type": "null", - "prisma__value": null - }, - "json": { - "prisma__type": "null", - "prisma__value": null - } - } - ] + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt", + "dec", + "json" + ], + "types": [ + "int", + "string", + "int", + "bigint", + "double", + "bytes", + "int", + "datetime", + "decimal", + "string" + ], + "rows": [ + [ + 1, + "str", + 42, + "9223372036854775807", + 1.5432, + "AQID", + 1, + "1900-10-10T01:10:10.001+00:00", + "123.4567891", + "{\"a\":\"b\"}" + ], + [ + 2, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + ] + } } } "### @@ -459,7 +331,7 @@ mod typed_output { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT 1 + 1;"#, vec![])), - @r###"{"data":{"queryRaw":[{"1 + 1":{"prisma__type":"int","prisma__value":2}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["1 + 1"],"types":["int"],"rows":[[2]]}}}"### ); Ok(()) @@ -507,84 +379,54 @@ mod typed_output { @r###" { "data": { - "queryRaw": [ - { - "id": { - "prisma__type": "int", - "prisma__value": 1 - }, - "string": { - "prisma__type": "string", - "prisma__value": "str" - }, - "int": { - "prisma__type": "int", - "prisma__value": 42 - }, - "bInt": { - "prisma__type": "bigint", - "prisma__value": "9223372036854775807" - }, - "float": { - "prisma__type": "double", - "prisma__value": 1.5432 - }, - "bytes": { - "prisma__type": "bytes", - "prisma__value": "AQID" - }, - "bool": { - "prisma__type": "bool", - "prisma__value": true - }, - "dt": { - "prisma__type": "datetime", - "prisma__value": "1900-10-10T01:10:10.001+00:00" - }, - "dec": { - "prisma__type": "decimal", - "prisma__value": "123.4567891" - } - }, - { - "id": { - "prisma__type": "int", - "prisma__value": 2 - }, - "string": { - "prisma__type": "null", - "prisma__value": null - }, - "int": { - "prisma__type": "null", - "prisma__value": null - }, - "bInt": { - "prisma__type": "null", - "prisma__value": null - }, - "float": { - "prisma__type": "null", - "prisma__value": null - }, - "bytes": { - "prisma__type": "null", - "prisma__value": null - }, - "bool": { - "prisma__type": "null", - "prisma__value": null - }, - "dt": { - "prisma__type": "null", - "prisma__value": null - }, - "dec": { - "prisma__type": "null", - "prisma__value": null - } - } - ] + "queryRaw": { + "columns": [ + "id", + "string", + "int", + "bInt", + "float", + "bytes", + "bool", + "dt", + "dec" + ], + "types": [ + "int", + "string", + "int", + "bigint", + "double", + "bytes", + "bool", + "datetime", + "decimal" + ], + "rows": [ + [ + 1, + "str", + 42, + "9223372036854775807", + 1.5432, + "AQID", + true, + "1900-10-10T01:10:10.001+00:00", + "123.4567891" + ], + [ + 2, + null, + null, + null, + null, + null, + null, + null, + null + ] + ] + } } } "### @@ -592,7 +434,7 @@ mod typed_output { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT 1 + 1;"#, vec![])), - @r###"{"data":{"queryRaw":[{"1 + 1":{"prisma__type":"bigint","prisma__value":"2"}}]}}"### + @r###"{"data":{"queryRaw":{"columns":["1 + 1"],"types":["bigint"],"rows":[["2"]]}}}"### ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/json_adapter/response.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/json_adapter/response.rs index a366fb6bdc1..66efd7f780e 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/json_adapter/response.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/json_adapter/response.rs @@ -67,6 +67,7 @@ fn item_ref_to_owned_item(item_ref: ItemRef) -> Item { Item::List(list) => Item::List(list.to_owned()), Item::Value(val) => Item::Value(val.to_owned()), Item::Json(json) => Item::Json(json.to_owned()), + Item::RawJson(json) => Item::RawJson(json.clone()), Item::Ref(nested_ref) => item_ref_to_owned_item(nested_ref.clone()), } } diff --git a/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs b/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs index 2ba7b5350d5..9f923251752 100644 --- a/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs +++ b/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs @@ -221,7 +221,7 @@ impl WriteOperations for MongoDbConnection { model: Option<&Model>, inputs: HashMap, query_type: Option, - ) -> connector_interface::Result { + ) -> connector_interface::Result { catch(write::query_raw( &self.database, &mut self.session, diff --git a/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs b/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs index 2e27d485999..e0933e7e840 100644 --- a/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs +++ b/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs @@ -258,7 +258,7 @@ impl<'conn> WriteOperations for MongoDbTransaction<'conn> { model: Option<&Model>, inputs: HashMap, query_type: Option, - ) -> connector_interface::Result { + ) -> connector_interface::Result { catch(write::query_raw( &self.connection.database, &mut self.connection.session, diff --git a/query-engine/connectors/mongodb-query-connector/src/root_queries/write.rs b/query-engine/connectors/mongodb-query-connector/src/root_queries/write.rs index 2ddea4b1d58..f66057d1ff2 100644 --- a/query-engine/connectors/mongodb-query-connector/src/root_queries/write.rs +++ b/query-engine/connectors/mongodb-query-connector/src/root_queries/write.rs @@ -505,7 +505,7 @@ pub async fn query_raw<'conn>( model: Option<&Model>, inputs: HashMap, query_type: Option, -) -> crate::Result { +) -> crate::Result { let db_statement = get_raw_db_statement(&query_type, &model, database); let span = info_span!( "prisma:engine:db_query", @@ -565,7 +565,8 @@ pub async fn query_raw<'conn>( } } }; - Ok(json_result) + + Ok(RawJson::try_new(json_result)?) } .instrument(span) .await diff --git a/query-engine/connectors/query-connector/src/interface.rs b/query-engine/connectors/query-connector/src/interface.rs index 275a77c010c..cbdafcaeeee 100644 --- a/query-engine/connectors/query-connector/src/interface.rs +++ b/query-engine/connectors/query-connector/src/interface.rs @@ -359,5 +359,5 @@ pub trait WriteOperations { model: Option<&Model>, inputs: HashMap, query_type: Option, - ) -> crate::Result; + ) -> crate::Result; } diff --git a/query-engine/connectors/sql-query-connector/Cargo.toml b/query-engine/connectors/sql-query-connector/Cargo.toml index c617800a966..5fbe4137020 100644 --- a/query-engine/connectors/sql-query-connector/Cargo.toml +++ b/query-engine/connectors/sql-query-connector/Cargo.toml @@ -53,6 +53,9 @@ tracing-opentelemetry = "0.17.3" cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } quaint.workspace = true +[dev-dependencies] +expect-test = "1" + [dependencies.connector-interface] package = "query-connector" path = "../query-connector" diff --git a/query-engine/connectors/sql-query-connector/src/database/connection.rs b/query-engine/connectors/sql-query-connector/src/database/connection.rs index de866ed6837..f928fcacdfa 100644 --- a/query-engine/connectors/sql-query-connector/src/database/connection.rs +++ b/query-engine/connectors/sql-query-connector/src/database/connection.rs @@ -332,7 +332,7 @@ where _model: Option<&Model>, inputs: HashMap, _query_type: Option, - ) -> connector::Result { + ) -> connector::Result { catch(&self.connection_info, write::query_raw(&self.inner, inputs)).await } } diff --git a/query-engine/connectors/sql-query-connector/src/database/operations/write.rs b/query-engine/connectors/sql-query-connector/src/database/operations/write.rs index 487cf17aad6..b56118b1c69 100644 --- a/query-engine/connectors/sql-query-connector/src/database/operations/write.rs +++ b/query-engine/connectors/sql-query-connector/src/database/operations/write.rs @@ -504,9 +504,6 @@ pub(crate) async fn execute_raw( /// Execute a plain SQL query with the given parameters, returning the answer as /// a JSON `Value`. -pub(crate) async fn query_raw( - conn: &dyn Queryable, - inputs: HashMap, -) -> crate::Result { +pub(crate) async fn query_raw(conn: &dyn Queryable, inputs: HashMap) -> crate::Result { Ok(conn.raw_json(inputs).await?) } diff --git a/query-engine/connectors/sql-query-connector/src/database/transaction.rs b/query-engine/connectors/sql-query-connector/src/database/transaction.rs index 59590d62cd3..263c541f6b4 100644 --- a/query-engine/connectors/sql-query-connector/src/database/transaction.rs +++ b/query-engine/connectors/sql-query-connector/src/database/transaction.rs @@ -334,7 +334,7 @@ impl<'tx> WriteOperations for SqlConnectorTransaction<'tx> { _model: Option<&Model>, inputs: HashMap, _query_type: Option, - ) -> connector::Result { + ) -> connector::Result { catch( &self.connection_info, write::query_raw(self.inner.as_queryable(), inputs), diff --git a/query-engine/connectors/sql-query-connector/src/error.rs b/query-engine/connectors/sql-query-connector/src/error.rs index 1156ed13a59..29664f2b118 100644 --- a/query-engine/connectors/sql-query-connector/src/error.rs +++ b/query-engine/connectors/sql-query-connector/src/error.rs @@ -31,6 +31,7 @@ pub(crate) enum RawError { External { id: i32, }, + ConversionError(anyhow::Error), } impl From for SqlError { @@ -55,6 +56,7 @@ impl From for SqlError { message: message.unwrap_or_else(|| String::from("N/A")), }, RawError::External { id } => Self::ExternalError(id), + RawError::ConversionError(err) => Self::ConversionError(err), } } } @@ -87,6 +89,12 @@ impl From for RawError { } } +impl From for RawError { + fn from(e: serde_json::error::Error) -> Self { + Self::ConversionError(e.into()) + } +} + // Catching the panics from the database driver for better error messages. impl From> for RawError { fn from(e: Box) -> Self { diff --git a/query-engine/connectors/sql-query-connector/src/lib.rs b/query-engine/connectors/sql-query-connector/src/lib.rs index da2bbe996e8..9bd6c2d7f21 100644 --- a/query-engine/connectors/sql-query-connector/src/lib.rs +++ b/query-engine/connectors/sql-query-connector/src/lib.rs @@ -15,9 +15,9 @@ mod query_arguments_ext; mod query_builder; mod query_ext; mod row; +mod ser_raw; mod sql_trace; mod value; -mod value_ext; use self::{column_metadata::*, context::Context, query_ext::QueryExt, row::*}; use quaint::prelude::Queryable; diff --git a/query-engine/connectors/sql-query-connector/src/query_ext.rs b/query-engine/connectors/sql-query-connector/src/query_ext.rs index 1f6b6c0e644..c0d511f9e6d 100644 --- a/query-engine/connectors/sql-query-connector/src/query_ext.rs +++ b/query-engine/connectors/sql-query-connector/src/query_ext.rs @@ -1,7 +1,8 @@ use crate::filter::FilterBuilder; +use crate::ser_raw::SerializedResultSet; use crate::{ column_metadata, error::*, model_extensions::*, sql_trace::trace_parent_to_string, sql_trace::SqlTraceComment, - value_ext::IntoTypedJsonExtension, ColumnMetadata, Context, SqlRow, ToSqlRow, + ColumnMetadata, Context, SqlRow, ToSqlRow, }; use async_trait::async_trait; use connector_interface::RecordFilter; @@ -11,7 +12,6 @@ use opentelemetry::trace::TraceContextExt; use opentelemetry::trace::TraceFlags; use quaint::{ast::*, connector::Queryable}; use query_structure::*; -use serde_json::{Map, Value}; use std::{collections::HashMap, panic::AssertUnwindSafe}; use tracing::{info_span, Span}; use tracing_futures::Instrument; @@ -54,7 +54,7 @@ impl QueryExt for Q { async fn raw_json<'a>( &'a self, mut inputs: HashMap, - ) -> std::result::Result { + ) -> std::result::Result { // Unwrapping query & params is safe since it's already passed the query parsing stage let query = inputs.remove("query").unwrap().into_string().unwrap(); let params = inputs.remove("parameters").unwrap().into_list().unwrap(); @@ -62,24 +62,9 @@ impl QueryExt for Q { let result_set = AssertUnwindSafe(self.query_raw_typed(&query, ¶ms)) .catch_unwind() .await??; + let raw_json = RawJson::try_new(SerializedResultSet(result_set))?; - // `query_raw` does not return column names in `ResultSet` when a call to a stored procedure is done - let columns: Vec = result_set.columns().iter().map(ToString::to_string).collect(); - let mut result = Vec::new(); - - for row in result_set.into_iter() { - let mut object = Map::new(); - - for (idx, p_value) in row.into_iter().enumerate() { - let column_name = columns.get(idx).unwrap_or(&format!("f{idx}")).clone(); - - object.insert(column_name, p_value.as_typed_json()); - } - - result.push(Value::Object(object)); - } - - Ok(Value::Array(result)) + Ok(raw_json) } async fn raw_count<'a>( @@ -190,7 +175,7 @@ pub(crate) trait QueryExt { async fn raw_json<'a>( &'a self, mut inputs: HashMap, - ) -> std::result::Result; + ) -> std::result::Result; /// Execute a singular SQL query in the database, returning the number of /// affected rows. diff --git a/query-engine/connectors/sql-query-connector/src/ser_raw.rs b/query-engine/connectors/sql-query-connector/src/ser_raw.rs new file mode 100644 index 00000000000..3c80b91d34e --- /dev/null +++ b/query-engine/connectors/sql-query-connector/src/ser_raw.rs @@ -0,0 +1,560 @@ +use quaint::{ + connector::{ResultRowRef, ResultSet}, + Value, ValueType, +}; +use serde::{ser::*, Serialize, Serializer}; + +pub struct SerializedResultSet(pub ResultSet); + +#[derive(Debug, Serialize)] +struct InnerSerializedResultSet<'a> { + columns: SerializedColumns<'a>, + types: &'a SerializedTypes, + rows: SerializedRows<'a>, +} + +impl serde::Serialize for SerializedResultSet { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let this = &self.0; + + InnerSerializedResultSet { + columns: SerializedColumns(this), + types: &SerializedTypes::new(this), + rows: SerializedRows(this), + } + .serialize(serializer) + } +} + +#[derive(Debug)] +struct SerializedColumns<'a>(&'a ResultSet); + +impl<'a> Serialize for SerializedColumns<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let this = &self.0; + + if this.is_empty() { + return this.columns().serialize(serializer); + } + + let first_row = this.first().unwrap(); + + let mut seq = serializer.serialize_seq(Some(first_row.len()))?; + + for (idx, _) in first_row.iter().enumerate() { + if let Some(column_name) = this.columns().get(idx) { + seq.serialize_element(column_name)?; + } else { + // `query_raw` does not return column names in `ResultSet` when a call to a stored procedure is done + // See https://github.com/prisma/prisma/issues/6173 + seq.serialize_element(&format!("f{idx}"))?; + } + } + + seq.end() + } +} + +#[derive(Debug, Serialize)] +#[serde(transparent)] +struct SerializedTypes(Vec); + +impl SerializedTypes { + fn new(rows: &ResultSet) -> Self { + if rows.is_empty() { + return Self(Vec::with_capacity(0)); + } + + let row_len = rows.first().unwrap().len(); + let mut types = vec![SerializedValueType::Unknown; row_len]; + let mut types_found = 0; + + // This attempts to infer types based on `quaint::Value` present in the rows. + // We need to go through every row because because empty and null arrays don't encode their inner type. + // In the best case scenario, this loop stops at the first row. + // In the worst case scenario, it'll keep looping until it finds an array with a non-null value. + 'outer: for row in rows.iter() { + for (idx, value) in row.iter().enumerate() { + let current_type = types[idx]; + + if matches!( + current_type, + SerializedValueType::Unknown | SerializedValueType::UnknownArray + ) { + let inferred_type = SerializedValueType::infer_from(value); + + if inferred_type != SerializedValueType::Unknown && inferred_type != current_type { + types[idx] = inferred_type; + + if inferred_type != SerializedValueType::UnknownArray { + types_found += 1; + } + } + } + + if types_found == row_len { + break 'outer; + } + } + } + + // Client doesn't know how to handle unknown types. + assert!(!types.contains(&SerializedValueType::Unknown)); + + Self(types) + } +} + +#[derive(Debug)] +struct SerializedRows<'a>(&'a ResultSet); + +impl<'a> Serialize for SerializedRows<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let rows = &self.0; + let mut seq = serializer.serialize_seq(Some(rows.len()))?; + + for row in rows.iter() { + seq.serialize_element(&SerializedRow(&row))?; + } + + seq.end() + } +} + +struct SerializedRow<'a>(&'a ResultRowRef<'a>); + +impl Serialize for SerializedRow<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let row = &self.0; + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + + for value in row.iter() { + seq.serialize_element(&SerializedValue(value))?; + } + + seq.end() + } +} + +struct SerializedValue<'a>(&'a Value<'a>); + +impl<'a> Serialize for SerializedValue<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let val = &self.0; + + match &val.typed { + ValueType::Array(Some(values)) => { + let mut seq = serializer.serialize_seq(Some(values.len()))?; + + for value in values { + seq.serialize_element(&SerializedValue(value))?; + } + + seq.end() + } + ValueType::Array(None) => serializer.serialize_none(), + ValueType::Int32(value) => value.serialize(serializer), + ValueType::Int64(value) => value.map(|val| val.to_string()).serialize(serializer), + ValueType::Numeric(value) => value + .as_ref() + .map(|value| value.normalized().to_string()) + .serialize(serializer), + ValueType::Float(value) => value.serialize(serializer), + ValueType::Double(value) => value.serialize(serializer), + ValueType::Text(value) => value.serialize(serializer), + ValueType::Enum(value, _) => value.as_ref().map(|value| value.inner()).serialize(serializer), + ValueType::EnumArray(Some(variants), _) => { + let mut seq = serializer.serialize_seq(Some(variants.len()))?; + + for variant in variants { + seq.serialize_element(variant.inner())?; + } + + seq.end() + } + ValueType::EnumArray(None, _) => serializer.serialize_none(), + ValueType::Bytes(value) => value.as_ref().map(prisma_value::encode_base64).serialize(serializer), + ValueType::Boolean(value) => value.serialize(serializer), + ValueType::Char(value) => value.serialize(serializer), + ValueType::Json(value) => value.serialize(serializer), + ValueType::Xml(value) => value.serialize(serializer), + ValueType::Uuid(value) => value.serialize(serializer), + ValueType::DateTime(value) => value.map(|value| value.to_rfc3339()).serialize(serializer), + ValueType::Date(value) => value.serialize(serializer), + ValueType::Time(value) => value.serialize(serializer), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Serialize)] +enum SerializedValueType { + #[serde(rename = "int")] + Int32, + #[serde(rename = "bigint")] + Int64, + #[serde(rename = "float")] + Float, + #[serde(rename = "double")] + Double, + #[serde(rename = "string")] + Text, + #[serde(rename = "enum")] + Enum, + #[serde(rename = "bytes")] + Bytes, + #[serde(rename = "bool")] + Boolean, + #[serde(rename = "char")] + Char, + #[serde(rename = "decimal")] + Numeric, + #[serde(rename = "json")] + Json, + #[serde(rename = "xml")] + Xml, + #[serde(rename = "uuid")] + Uuid, + #[serde(rename = "datetime")] + DateTime, + #[serde(rename = "date")] + Date, + #[serde(rename = "time")] + Time, + + #[serde(rename = "int-array")] + Int32Array, + #[serde(rename = "bigint-array")] + Int64Array, + #[serde(rename = "float-array")] + FloatArray, + #[serde(rename = "double-array")] + DoubleArray, + #[serde(rename = "string-array")] + TextArray, + #[serde(rename = "bytes-array")] + BytesArray, + #[serde(rename = "bool-array")] + BooleanArray, + #[serde(rename = "char-array")] + CharArray, + #[serde(rename = "decimal-array")] + NumericArray, + #[serde(rename = "json-array")] + JsonArray, + #[serde(rename = "xml-array")] + XmlArray, + #[serde(rename = "uuid-array")] + UuidArray, + #[serde(rename = "datetime-array")] + DateTimeArray, + #[serde(rename = "date-array")] + DateArray, + #[serde(rename = "time-array")] + TimeArray, + + #[serde(rename = "unknown-array")] + UnknownArray, + + #[serde(rename = "unknown")] + Unknown, +} + +impl SerializedValueType { + fn infer_from(value: &Value) -> SerializedValueType { + match &value.typed { + ValueType::Int32(_) => SerializedValueType::Int32, + ValueType::Int64(_) => SerializedValueType::Int64, + ValueType::Float(_) => SerializedValueType::Float, + ValueType::Double(_) => SerializedValueType::Double, + ValueType::Text(_) => SerializedValueType::Text, + ValueType::Enum(_, _) => SerializedValueType::Enum, + ValueType::EnumArray(_, _) => SerializedValueType::TextArray, + ValueType::Bytes(_) => SerializedValueType::Bytes, + ValueType::Boolean(_) => SerializedValueType::Boolean, + ValueType::Char(_) => SerializedValueType::Char, + ValueType::Numeric(_) => SerializedValueType::Numeric, + ValueType::Json(_) => SerializedValueType::Json, + ValueType::Xml(_) => SerializedValueType::Xml, + ValueType::Uuid(_) => SerializedValueType::Uuid, + ValueType::DateTime(_) => SerializedValueType::DateTime, + ValueType::Date(_) => SerializedValueType::Date, + ValueType::Time(_) => SerializedValueType::Time, + + ValueType::Array(Some(values)) => { + if values.is_empty() { + return SerializedValueType::UnknownArray; + } + + match &values[0].typed { + ValueType::Int32(_) => SerializedValueType::Int32Array, + ValueType::Int64(_) => SerializedValueType::Int64Array, + ValueType::Float(_) => SerializedValueType::FloatArray, + ValueType::Double(_) => SerializedValueType::DoubleArray, + ValueType::Text(_) => SerializedValueType::TextArray, + ValueType::Bytes(_) => SerializedValueType::BytesArray, + ValueType::Boolean(_) => SerializedValueType::BooleanArray, + ValueType::Char(_) => SerializedValueType::CharArray, + ValueType::Numeric(_) => SerializedValueType::NumericArray, + ValueType::Json(_) => SerializedValueType::JsonArray, + ValueType::Xml(_) => SerializedValueType::XmlArray, + ValueType::Uuid(_) => SerializedValueType::UuidArray, + ValueType::DateTime(_) => SerializedValueType::DateTimeArray, + ValueType::Date(_) => SerializedValueType::DateArray, + ValueType::Time(_) => SerializedValueType::TimeArray, + ValueType::Enum(_, _) => SerializedValueType::TextArray, + ValueType::Array(_) | ValueType::EnumArray(_, _) => { + unreachable!("Only PG supports scalar lists and tokio-postgres does not support 2d arrays") + } + } + } + ValueType::Array(None) => SerializedValueType::UnknownArray, + } + } +} + +#[cfg(test)] +mod tests { + use super::SerializedResultSet; + use bigdecimal::BigDecimal; + use chrono::{DateTime, Utc}; + use expect_test::expect; + use quaint::{ + ast::{EnumName, EnumVariant}, + connector::ResultSet, + Value, + }; + use std::str::FromStr; + + #[test] + fn serialize_result_set() { + let names = vec![ + "int32".to_string(), + "int64".to_string(), + "float".to_string(), + "double".to_string(), + "text".to_string(), + "enum".to_string(), + "bytes".to_string(), + "boolean".to_string(), + "char".to_string(), + "numeric".to_string(), + "json".to_string(), + "xml".to_string(), + "uuid".to_string(), + "datetime".to_string(), + "date".to_string(), + "time".to_string(), + "intArray".to_string(), + ]; + let rows = vec![vec![ + Value::int32(42), + Value::int64(42), + Value::float(42.523), + Value::double(42.523), + Value::text("heLlo"), + Value::enum_variant_with_name("Red", EnumName::new("Color", Option::::None)), + Value::bytes(b"hello".to_vec()), + Value::boolean(true), + Value::character('c'), + Value::numeric(BigDecimal::from_str("123456789.123456789").unwrap()), + Value::json(serde_json::json!({"hello": "world"})), + Value::xml("world"), + Value::uuid(uuid::Uuid::from_str("550e8400-e29b-41d4-a716-446655440000").unwrap()), + Value::datetime( + chrono::DateTime::parse_from_rfc3339("2021-01-01T02:00:00Z") + .map(DateTime::::from) + .unwrap(), + ), + Value::date(chrono::NaiveDate::from_ymd_opt(2021, 1, 1).unwrap()), + Value::time(chrono::NaiveTime::from_hms_opt(2, 0, 0).unwrap()), + Value::array(vec![Value::int32(42), Value::int32(42)]), + ]]; + let result_set = ResultSet::new(names, rows); + + let serialized = serde_json::to_string_pretty(&SerializedResultSet(result_set)).unwrap(); + + let expected = expect![[r#" + { + "columns": [ + "int32", + "int64", + "float", + "double", + "text", + "enum", + "bytes", + "boolean", + "char", + "numeric", + "json", + "xml", + "uuid", + "datetime", + "date", + "time", + "intArray" + ], + "types": [ + "int", + "bigint", + "float", + "double", + "string", + "enum", + "bytes", + "bool", + "char", + "decimal", + "json", + "xml", + "uuid", + "datetime", + "date", + "time", + "int-array" + ], + "rows": [ + [ + 42, + "42", + 42.523, + 42.523, + "heLlo", + "Red", + "aGVsbG8=", + true, + "c", + "123456789.123456789", + { + "hello": "world" + }, + "world", + "550e8400-e29b-41d4-a716-446655440000", + "2021-01-01T02:00:00+00:00", + "2021-01-01", + "02:00:00", + [ + 42, + 42 + ] + ] + ] + }"#]]; + + expected.assert_eq(&serialized); + } + + #[test] + fn serialize_empty_result_set() { + let names = vec!["hello".to_string()]; + let result_set = ResultSet::new(names, vec![]); + + let serialized = serde_json::to_string_pretty(&SerializedResultSet(result_set)).unwrap(); + + let expected = expect![[r#" + { + "columns": [ + "hello" + ], + "types": [], + "rows": [] + }"#]]; + + expected.assert_eq(&serialized) + } + + #[test] + fn serialize_arrays() { + let names = vec!["array".to_string()]; + let rows = vec![ + vec![Value::null_array()], + vec![Value::array(vec![Value::int32(42), Value::int64(42)])], + vec![Value::array(vec![Value::text("heLlo"), Value::null_text()])], + ]; + let result_set = ResultSet::new(names, rows); + + let serialized = serde_json::to_string_pretty(&SerializedResultSet(result_set)).unwrap(); + + let expected = expect![[r#" + { + "columns": [ + "array" + ], + "types": [ + "int-array" + ], + "rows": [ + [ + null + ], + [ + [ + 42, + "42" + ] + ], + [ + [ + "heLlo", + null + ] + ] + ] + }"#]]; + + expected.assert_eq(&serialized); + } + + #[test] + fn serialize_enum_array() { + let names = vec!["array".to_string()]; + let rows = vec![ + vec![Value::enum_array_with_name( + vec![EnumVariant::new("A"), EnumVariant::new("B")], + EnumName::new("Alphabet", Some("foo")), + )], + vec![Value::null_enum_array()], + ]; + let result_set = ResultSet::new(names, rows); + + let serialized = serde_json::to_string_pretty(&SerializedResultSet(result_set)).unwrap(); + + let expected = expect![[r#" + { + "columns": [ + "array" + ], + "types": [ + "string-array" + ], + "rows": [ + [ + [ + "A", + "B" + ] + ], + [ + null + ] + ] + }"#]]; + + expected.assert_eq(&serialized); + } +} diff --git a/query-engine/connectors/sql-query-connector/src/value_ext.rs b/query-engine/connectors/sql-query-connector/src/value_ext.rs deleted file mode 100644 index a84c9da0380..00000000000 --- a/query-engine/connectors/sql-query-connector/src/value_ext.rs +++ /dev/null @@ -1,51 +0,0 @@ -pub trait IntoTypedJsonExtension { - // Returns the type-name - fn type_name(&self) -> String; - /// Decorate all values with type-hints - fn as_typed_json(self) -> serde_json::Value; -} - -impl<'a> IntoTypedJsonExtension for quaint::Value<'a> { - fn type_name(&self) -> String { - if self.is_null() { - return "null".to_owned(); - } - - let type_name = match self.typed { - quaint::ValueType::Int32(_) => "int", - quaint::ValueType::Int64(_) => "bigint", - quaint::ValueType::Float(_) => "float", - quaint::ValueType::Double(_) => "double", - quaint::ValueType::Text(_) => "string", - quaint::ValueType::Enum(_, _) => "enum", - quaint::ValueType::Bytes(_) => "bytes", - quaint::ValueType::Boolean(_) => "bool", - quaint::ValueType::Char(_) => "char", - quaint::ValueType::Numeric(_) => "decimal", - quaint::ValueType::Json(_) => "json", - quaint::ValueType::Xml(_) => "xml", - quaint::ValueType::Uuid(_) => "uuid", - quaint::ValueType::DateTime(_) => "datetime", - quaint::ValueType::Date(_) => "date", - quaint::ValueType::Time(_) => "time", - quaint::ValueType::Array(_) | quaint::ValueType::EnumArray(_, _) => "array", - }; - - type_name.to_owned() - } - - fn as_typed_json(self) -> serde_json::Value { - let type_name = self.type_name(); - - let json_value = match self.typed { - quaint::ValueType::Array(Some(values)) => { - serde_json::Value::Array(values.into_iter().map(|value| value.as_typed_json()).collect()) - } - quaint::ValueType::Int64(Some(value)) => serde_json::Value::String(value.to_string()), - quaint::ValueType::Numeric(Some(decimal)) => serde_json::Value::String(decimal.normalized().to_string()), - x => serde_json::Value::from(x), - }; - - serde_json::json!({ "prisma__type": type_name, "prisma__value": json_value }) - } -} diff --git a/query-engine/core/src/interpreter/query_interpreters/write.rs b/query-engine/core/src/interpreter/query_interpreters/write.rs index f896bfa5cfc..d3146c38363 100644 --- a/query-engine/core/src/interpreter/query_interpreters/write.rs +++ b/query-engine/core/src/interpreter/query_interpreters/write.rs @@ -6,7 +6,7 @@ use crate::{ QueryResult, RecordSelection, }; use connector::{ConnectionLike, DatasourceFieldName, NativeUpsert, WriteArgs}; -use query_structure::{ManyRecords, Model}; +use query_structure::{ManyRecords, Model, RawJson}; pub(crate) async fn execute( tx: &mut dyn ConnectionLike, @@ -31,14 +31,16 @@ pub(crate) async fn execute( async fn query_raw(tx: &mut dyn ConnectionLike, q: RawQuery) -> InterpretationResult { let res = tx.query_raw(q.model.as_ref(), q.inputs, q.query_type).await?; - Ok(QueryResult::Json(res)) + Ok(QueryResult::RawJson(res)) } async fn execute_raw(tx: &mut dyn ConnectionLike, q: RawQuery) -> InterpretationResult { let res = tx.execute_raw(q.inputs).await?; let num = serde_json::Value::Number(serde_json::Number::from(res)); - Ok(QueryResult::Json(num)) + Ok(QueryResult::RawJson( + RawJson::try_new(num).map_err(|err| InterpreterError::Generic(err.to_string()))?, + )) } async fn create_one( diff --git a/query-engine/core/src/response_ir/internal.rs b/query-engine/core/src/response_ir/internal.rs index 13be5bbd9d8..1ca949651e8 100644 --- a/query-engine/core/src/response_ir/internal.rs +++ b/query-engine/core/src/response_ir/internal.rs @@ -62,7 +62,7 @@ pub(crate) fn serialize_internal( Ok(result) } - QueryResult::Json(_) => unimplemented!(), + QueryResult::RawJson(_) => unimplemented!(), QueryResult::Id(_) | QueryResult::RecordSelection(None) => unreachable!(), QueryResult::Unit => unimplemented!(), } diff --git a/query-engine/core/src/response_ir/ir_serializer.rs b/query-engine/core/src/response_ir/ir_serializer.rs index d8efa710bff..d66e3354a92 100644 --- a/query-engine/core/src/response_ir/ir_serializer.rs +++ b/query-engine/core/src/response_ir/ir_serializer.rs @@ -21,8 +21,8 @@ impl<'a> IrSerializer<'a> { ) -> crate::Result { let _span = info_span!("prisma:engine:serialize", user_facing = true); match result { - ExpressionResult::Query(QueryResult::Json(json)) => { - Ok(ResponseData::new(self.key.clone(), Item::Json(json))) + ExpressionResult::Query(QueryResult::RawJson(json)) => { + Ok(ResponseData::new(self.key.clone(), Item::RawJson(json))) } ExpressionResult::Query(r) => { diff --git a/query-engine/core/src/response_ir/mod.rs b/query-engine/core/src/response_ir/mod.rs index 535a409474d..027914258f8 100644 --- a/query-engine/core/src/response_ir/mod.rs +++ b/query-engine/core/src/response_ir/mod.rs @@ -18,7 +18,7 @@ pub(crate) use ir_serializer::*; use crate::ArgumentValue; use indexmap::IndexMap; -use query_structure::PrismaValue; +use query_structure::{PrismaValue, RawJson}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::{collections::HashMap, fmt, sync::Arc}; @@ -102,6 +102,7 @@ pub enum Item { List(List), Value(PrismaValue), Json(serde_json::Value), + RawJson(RawJson), /// Wrapper type to allow multiple parent records /// to claim the same item without copying data @@ -200,6 +201,7 @@ impl Serialize for Item { } Self::Value(pv) => pv.serialize(serializer), Self::Json(value) => value.serialize(serializer), + Self::RawJson(value) => value.serialize(serializer), Self::Ref(item_ref) => item_ref.serialize(serializer), } } diff --git a/query-engine/core/src/result_ast/mod.rs b/query-engine/core/src/result_ast/mod.rs index e450b721377..c3ff426b254 100644 --- a/query-engine/core/src/result_ast/mod.rs +++ b/query-engine/core/src/result_ast/mod.rs @@ -1,5 +1,5 @@ use connector::AggregationRow; -use query_structure::{ManyRecords, Model, SelectionResult, VirtualSelection}; +use query_structure::{ManyRecords, Model, RawJson, SelectionResult, VirtualSelection}; #[derive(Debug, Clone)] pub(crate) enum QueryResult { @@ -7,7 +7,7 @@ pub(crate) enum QueryResult { Count(usize), RecordSelection(Option>), RecordSelectionWithRelations(Box), - Json(serde_json::Value), + RawJson(RawJson), RecordAggregations(RecordAggregations), Unit, } diff --git a/query-engine/driver-adapters/src/conversion/js_to_quaint.rs b/query-engine/driver-adapters/src/conversion/js_to_quaint.rs index dd18d5e72fc..1232351a689 100644 --- a/query-engine/driver-adapters/src/conversion/js_to_quaint.rs +++ b/query-engine/driver-adapters/src/conversion/js_to_quaint.rs @@ -249,7 +249,7 @@ pub fn js_value_to_quaint( serde_json::Value::String(s) => uuid::Uuid::parse_str(&s) .map(QuaintValue::uuid) .map_err(|_| conversion_error!("Expected a UUID string in column '{column_name}'")), - serde_json::Value::Null => Ok(QuaintValue::null_bytes()), + serde_json::Value::Null => Ok(QuaintValue::null_uuid()), mismatch => Err(conversion_error!( "Expected a UUID string in column '{column_name}', found {mismatch}" )),