Skip to content

Commit

Permalink
perf: serialize raw results as arrays instead of object (#4939)
Browse files Browse the repository at this point in the history
  • Loading branch information
Weakky authored Jul 12, 2024
1 parent 9618390 commit c4fe130
Show file tree
Hide file tree
Showing 39 changed files with 1,410 additions and 850 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions libs/prisma-value/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod arithmetic;

mod error;
mod raw_json;

use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive};
use chrono::prelude::*;
Expand All @@ -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<T> = std::result::Result<T, ConversionFailure>;
pub type PrismaListValue = Vec<PrismaValue>;

pub use base64::encode as encode_base64;

#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(untagged)]
pub enum PrismaValue {
Expand Down
38 changes: 38 additions & 0 deletions libs/prisma-value/src/raw_json.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
Ok(Self {
value: serde_json::to_string(&value)?,
})
}
}

impl Serialize for RawJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let raw_value = RawValue::from_string(self.value.to_owned()).map_err(serde::ser::Error::custom)?;
raw_value.serialize(serializer)
}
}
3 changes: 2 additions & 1 deletion quaint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions quaint/src/ast/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions quaint/src/ast/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
34 changes: 30 additions & 4 deletions quaint/src/connector/result_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
Expand All @@ -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<Vec<String>>,
pub(crate) internal_iterator: std::vec::IntoIter<Vec<Value<'static>>>,
}

impl Iterator for ResultSetIterator {
impl Iterator for ResultSetIntoIterator {
type Item = ResultRow;

fn next(&mut self) -> Option<Self::Item> {
Expand All @@ -106,6 +113,25 @@ impl Iterator for ResultSetIterator {
}
}

pub struct ResultSetIterator<'a> {
pub(crate) columns: Arc<Vec<String>>,
pub(crate) internal_iterator: std::slice::Iter<'a, Vec<Value<'static>>>,
}

impl<'a> Iterator for ResultSetIterator<'a> {
type Item = ResultRowRef<'a>;

fn next(&mut self) -> Option<Self::Item> {
match self.internal_iterator.next() {
Some(row) => Some(ResultRowRef {
columns: Arc::clone(&self.columns),
values: row,
}),
None => None,
}
}
}

impl From<ResultSet> for serde_json::Value {
fn from(result_set: ResultSet) -> Self {
let columns: Vec<String> = result_set.columns().iter().map(ToString::to_string).collect();
Expand Down
20 changes: 20 additions & 0 deletions quaint/src/connector/result_set/result_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ pub struct ResultRowRef<'a> {
pub(crate) values: &'a Vec<Value<'static>>,
}

impl<'a> ResultRowRef<'a> {
pub fn iter(&self) -> impl Iterator<Item = &'a Value<'a>> {
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
Expand Down Expand Up @@ -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<Item = &'a Value<'static>> {
self.values.iter()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand All @@ -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(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]" => "<rand_int>"
}, @r###"
{
"data": {
"queryRaw": {
"columns": [
"f0"
],
"types": [
"int"
],
"rows": [
[
"<rand_int>"
]
]
}
"#})
.await?
.assert_success();
}
}
"###);

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
Loading

0 comments on commit c4fe130

Please sign in to comment.