Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: make json serialization faster #4753

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions Cargo.toml
Expand Up @@ -80,11 +80,11 @@ path = "quaint"
[profile.dev.package.backtrace]
opt-level = 3

[profile.release.package.query-engine-node-api]
strip = "symbols"
# [profile.release.package.query-engine-node-api]
# strip = "symbols"

[profile.release.package.query-engine]
strip = "symbols"
# [profile.release.package.query-engine]
# strip = "symbols"

[profile.release]
lto = "fat"
Expand Down
117 changes: 106 additions & 11 deletions libs/prisma-value/src/lib.rs
Expand Up @@ -7,6 +7,7 @@ use chrono::prelude::*;
use serde::de::Unexpected;
use serde::ser::SerializeMap;
use serde::{ser::Serializer, Deserialize, Deserializer, Serialize};
use std::borrow::Cow;
use std::{convert::TryFrom, fmt, str::FromStr};
use uuid::Uuid;

Expand All @@ -23,7 +24,7 @@ pub enum PrismaValue {
Int(i64),
Uuid(Uuid),
List(PrismaListValue),
Json(String),
Json(PrismaValueJson),

/// A collections of key-value pairs constituting an object.
#[serde(serialize_with = "serialize_object")]
Expand All @@ -45,6 +46,93 @@ pub enum PrismaValue {
Bytes(Vec<u8>),
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PrismaValueJson {
Serialized(String),
Deserialized(Box<serde_json::Value>),
}

impl std::fmt::Display for PrismaValueJson {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PrismaValueJson::Serialized(s) => write!(f, "{}", s),
PrismaValueJson::Deserialized(v) => write!(f, "{}", v),
}
}
}

impl PrismaValueJson {
pub fn as_str(&self) -> Cow<'_, str> {
match self {
PrismaValueJson::Serialized(s) => Cow::Borrowed(s),
PrismaValueJson::Deserialized(v) => Cow::Owned(v.to_string()),
}
}

pub fn try_as_value(&self) -> serde_json::Result<Cow<'_, serde_json::Value>> {
match self {
PrismaValueJson::Serialized(s) => serde_json::from_str(s).map(Cow::Owned),
PrismaValueJson::Deserialized(v) => Ok(Cow::Borrowed(v)),
}
}

pub fn try_into_value(self) -> serde_json::Result<serde_json::Value> {
match self {
PrismaValueJson::Serialized(s) => serde_json::from_str(&s),
PrismaValueJson::Deserialized(v) => Ok(*v),
}
}
}

impl From<String> for PrismaValueJson {
fn from(value: String) -> Self {
PrismaValueJson::Serialized(value)
}
}

impl From<&String> for PrismaValueJson {
fn from(value: &String) -> Self {
PrismaValueJson::Serialized(value.to_owned())
}
}

impl From<&str> for PrismaValueJson {
fn from(value: &str) -> Self {
PrismaValueJson::Serialized(value.to_owned())
}
}

impl From<Cow<'_, str>> for PrismaValueJson {
fn from(value: Cow<'_, str>) -> Self {
PrismaValueJson::Serialized(value.into_owned())
}
}

impl From<serde_json::Value> for PrismaValueJson {
fn from(value: serde_json::Value) -> Self {
PrismaValueJson::Deserialized(Box::new(value))
}
}

impl PartialOrd for PrismaValueJson {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for PrismaValueJson {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(&other.as_str())
}
}

impl std::hash::Hash for PrismaValueJson {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}

/// Stringify a date to the following format
/// 1999-05-01T00:00:00.000Z
pub fn stringify_datetime(datetime: &DateTime<FixedOffset>) -> String {
Expand All @@ -61,6 +149,10 @@ pub fn parse_datetime(datetime: &str) -> chrono::ParseResult<DateTime<FixedOffse
DateTime::parse_from_rfc3339(datetime)
}

pub fn stringify_decimal(decimal: &BigDecimal) -> f64 {
decimal.to_string().parse::<f64>().unwrap()
}

pub fn encode_bytes(bytes: &[u8]) -> String {
base64::encode(bytes)
}
Expand Down Expand Up @@ -132,7 +224,7 @@ impl TryFrom<serde_json::Value> for PrismaValue {
decode_bytes(value).map(PrismaValue::Bytes)
}

_ => Ok(PrismaValue::Json(serde_json::to_string(&obj).unwrap())),
_ => Ok(PrismaValue::new_json(serde_json::Value::Object(obj))),
},
}
}
Expand Down Expand Up @@ -170,7 +262,7 @@ fn serialize_decimal<S>(decimal: &BigDecimal, serializer: S) -> Result<S::Ok, S:
where
S: Serializer,
{
decimal.to_string().parse::<f64>().unwrap().serialize(serializer)
stringify_decimal(decimal).serialize(serializer)
}

fn deserialize_decimal<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error>
Expand Down Expand Up @@ -276,6 +368,13 @@ impl PrismaValue {
}
}

pub fn into_json(self) -> Option<PrismaValueJson> {
match self {
PrismaValue::Json(j) => Some(j),
_ => None,
}
}

pub fn into_list(self) -> Option<PrismaListValue> {
match self {
PrismaValue::List(l) => Some(l),
Expand All @@ -298,20 +397,16 @@ impl PrismaValue {
PrismaValue::DateTime(parse_datetime(datetime).unwrap())
}

pub fn new_json(json: impl Into<PrismaValueJson>) -> PrismaValue {
PrismaValue::Json(json.into())
}

pub fn as_boolean(&self) -> Option<&bool> {
match self {
PrismaValue::Boolean(bool) => Some(bool),
_ => None,
}
}

pub fn as_json(&self) -> Option<&String> {
if let Self::Json(v) = self {
Some(v)
} else {
None
}
}
}

impl fmt::Display for PrismaValue {
Expand Down
Expand Up @@ -361,14 +361,14 @@ impl MongoReadQueryBuilder {
) -> crate::Result<Self> {
for aggr in virtual_selections {
let join = match aggr {
VirtualSelection::RelationCount(rf, filter) => {
let filter = filter
.as_ref()
VirtualSelection::RelationCount(x) => {
let filter = x
.filter()
.map(|f| MongoFilterVisitor::new(FilterPrefix::default(), false).visit(f.clone()))
.transpose()?;

JoinStage {
source: rf.clone(),
source: x.field().clone(),
alias: Some(aggr.db_alias()),
nested: vec![],
filter,
Expand Down
Expand Up @@ -120,8 +120,7 @@ impl QueryRawConversionExtension for &PrismaValue {
fn try_as_bson(&self, arg_name: &str) -> crate::Result<Bson> {
match self {
PrismaValue::Json(json) => {
let json: serde_json::Value = serde_json::from_str(json.as_str())?;
let bson = Bson::try_from(json)?;
let bson = Bson::try_from(json.try_as_value()?.into_owned())?;

Ok(bson)
}
Expand Down
Expand Up @@ -11,7 +11,6 @@ use psl::builtin_connectors::MongoDbType;
use query_structure::{
CompositeFieldRef, Field, PrismaValue, RelationFieldRef, ScalarFieldRef, SelectedField, TypeIdentifier,
};
use serde_json::Value;
use std::{convert::TryFrom, fmt::Display};

/// Transforms a `PrismaValue` of a specific selected field into the BSON mapping as prescribed by
Expand Down Expand Up @@ -190,7 +189,7 @@ impl IntoBson for (&MongoDbType, PrismaValue) {
}),

(MongoDbType::Json, PrismaValue::Json(json)) => {
let val: Value = serde_json::from_str(&json)?;
let val = json.try_as_value()?.into_owned();

Bson::try_from(val).map_err(|_| MongoError::ConversionError {
from: "Stringified JSON".to_owned(),
Expand Down Expand Up @@ -259,7 +258,8 @@ impl IntoBson for (&TypeIdentifier, PrismaValue) {

// Json
(TypeIdentifier::Json, PrismaValue::Json(json)) => {
let val: Value = serde_json::from_str(&json)?;
let val = json.try_as_value()?.into_owned();

Bson::try_from(val).map_err(|_| MongoError::ConversionError {
from: "Stringified JSON".to_owned(),
to: "Mongo BSON (extJSON)".to_owned(),
Expand Down Expand Up @@ -375,7 +375,7 @@ fn read_scalar_value(bson: Bson, meta: &ScalarOutputMeta) -> crate::Result<Prism
(TypeIdentifier::Bytes, Bson::ObjectId(oid)) => PrismaValue::Bytes(oid.bytes().to_vec()),

// Json
(TypeIdentifier::Json, bson) => PrismaValue::Json(serde_json::to_string(&bson.into_relaxed_extjson())?),
(TypeIdentifier::Json, bson) => PrismaValue::new_json(bson.into_relaxed_extjson()),

(ident, bson) => {
return Err(MongoError::ConversionError {
Expand Down
@@ -1,19 +1,30 @@
use query_structure::{FieldArity, TypeIdentifier};
use query_structure::{
FieldArity, FieldSelection, GroupedSelectedField, GroupedVirtualSelection, RelationSelection, TypeIdentifier,
};

#[derive(Clone, Debug)]
pub enum MetadataFieldKind<'a> {
Scalar,
Relation(&'a RelationSelection),
Virtual(GroupedVirtualSelection<'a>),
}

/// Helps dealing with column value conversion and possible error resolution.
#[derive(Clone, Debug, Copy)]
#[derive(Clone, Debug)]
pub(crate) struct ColumnMetadata<'a> {
identifier: &'a TypeIdentifier,
identifier: TypeIdentifier,
name: Option<&'a str>,
arity: FieldArity,
kind: MetadataFieldKind<'a>,
}

impl<'a> ColumnMetadata<'a> {
fn new(identifier: &'a TypeIdentifier, arity: FieldArity) -> Self {
fn new(identifier: TypeIdentifier, arity: FieldArity, kind: MetadataFieldKind<'a>) -> Self {
Self {
identifier,
name: None,
arity,
kind,
}
}

Expand All @@ -24,19 +35,23 @@ impl<'a> ColumnMetadata<'a> {
}

/// The type of the column.
pub fn identifier(self) -> &'a TypeIdentifier {
pub fn identifier(&self) -> TypeIdentifier {
self.identifier
}

/// The name of the column.
pub fn name(self) -> Option<&'a str> {
pub fn name(&self) -> Option<&'a str> {
self.name
}

/// The arity of the column.
pub fn arity(self) -> FieldArity {
pub fn arity(&self) -> FieldArity {
self.arity
}

pub(crate) fn kind(&self) -> &MetadataFieldKind<'_> {
&self.kind
}
}

/// Create a set of metadata objects, combining column names and type
Expand All @@ -50,14 +65,40 @@ where
idents
.iter()
.zip(field_names.iter())
.map(|((identifier, arity), name)| ColumnMetadata::new(identifier, *arity).set_name(name.as_ref()))
.map(|((identifier, arity), name)| {
ColumnMetadata::new(*identifier, *arity, MetadataFieldKind::Scalar).set_name(name.as_ref())
})
.collect()
}

pub(crate) fn create_from_selection_for_json<'a, T>(
selection: &'a FieldSelection,
field_names: &'a [T],
) -> Vec<ColumnMetadata<'a>>
where
T: AsRef<str>,
{
selection
.grouped_selections()
.zip(field_names.iter())
.map(|(field, name)| {
let (type_identifier, arity) = field.type_identifier_with_arity_for_json();

let kind = match field {
GroupedSelectedField::Scalar(_) => MetadataFieldKind::Scalar,
GroupedSelectedField::Relation(rs) => MetadataFieldKind::Relation(rs),
GroupedSelectedField::Virtual(vs) => MetadataFieldKind::Virtual(vs),
};

ColumnMetadata::new(type_identifier, arity, kind).set_name(name.as_ref())
})
.collect()
}

/// Create a set of metadata objects.
pub(crate) fn create_anonymous(idents: &[(TypeIdentifier, FieldArity)]) -> Vec<ColumnMetadata<'_>> {
idents
.iter()
.map(|(identifier, arity)| ColumnMetadata::new(identifier, *arity))
.map(|(identifier, arity)| ColumnMetadata::new(*identifier, *arity, MetadataFieldKind::Scalar))
.collect()
}