diff --git a/flake.nix b/flake.nix index 30273ee329ac..6a479935561b 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,7 @@ "mobc-0.7.3" = "sha256-88jSFqOyMy2E7TP1HtMcE4CQXoKhBpO8XuSFKGtfgqA="; "mysql_async-0.30.0" = "sha256-I1Q9G3H3BW/Paq9aOYGcxQf4JVwN/ZNhGuHwTqbuxWc="; "postgres-native-tls-0.5.0" = "sha256-kwqHalfwrvNQYUdAqObTAab3oWzBLl6hab2JGXVyJ3k="; - "quaint-0.2.0-alpha.13" = "sha256-8vaYhXSFTibNLt+yTj+RQepzJ8CzWthhgQXVtSl+DhI="; + "quaint-0.2.0-alpha.13" = "sha256-kCbxrImKb5VqSjC+W/qZpS7sHSbepayZMId+TpReU5k="; "tokio-native-tls-0.3.0" = "sha256-ayH3TJ1iUQeZicR2nrsuxLykMoPL1fYBqRb21ValR5Q="; }; }; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/mod.rs index 2939c97c4738..07439ed68567 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/mod.rs @@ -1,6 +1,7 @@ mod max_integer; mod prisma_10098; mod prisma_10935; +mod prisma_12572; mod prisma_12929; mod prisma_13097; mod prisma_14001; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_12572.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_12572.rs new file mode 100644 index 000000000000..1021c35593d9 --- /dev/null +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/prisma_12572.rs @@ -0,0 +1,59 @@ +use query_engine_tests::*; + +#[test_suite(schema(schema))] +mod prisma_12572 { + fn schema() -> String { + r#" + model Test1 { + #id(id, String, @id) + up1 DateTime @updatedAt + cr1 DateTime @default(now()) + cr2 DateTime @default(now()) + up2 DateTime @updatedAt + test2s Test2[] + } + + model Test2 { + #id(id, String, @id) + test1Id String @unique + test1 Test1 @relation(fields: [test1Id], references: [id]) + cr DateTime @default(now()) + up DateTime @updatedAt + } + "# + .to_owned() + } + + #[connector_test] + async fn all_generated_timestamps_are_the_same(runner: Runner) -> TestResult<()> { + runner + .query(r#"mutation { createOneTest1(data: {id:"one", test2s: { create: {id: "two"}}}) { id }}"#) + .await? + .assert_success(); + let testones = runner.query(r#"{ findManyTest1 { id up1 cr1 cr2 up2 } }"#).await?; + let testtwos = runner.query(r#"{ findManyTest2 { id up cr } }"#).await?; + testones.assert_success(); + testtwos.assert_success(); + + let testones_json = testones.to_json_value(); + let testtwos_json = testtwos.to_json_value(); + let testone_obj = &testones_json["data"]["findManyTest1"][0]; + let testtwo_obj = &testtwos_json["data"]["findManyTest2"][0]; + + let values = &[ + &testone_obj["up1"].as_str().unwrap(), + &testone_obj["up2"].as_str().unwrap(), + &testone_obj["cr1"].as_str().unwrap(), + &testone_obj["cr2"].as_str().unwrap(), + &testtwo_obj["up"].as_str().unwrap(), + &testtwo_obj["cr"].as_str().unwrap(), + ]; + + // assert that all the datetimes are the same + for datetimes in values.windows(2) { + assert_eq!(datetimes[0], datetimes[1]); + } + + Ok(()) + } +} diff --git a/query-engine/connectors/query-connector/src/write_args.rs b/query-engine/connectors/query-connector/src/write_args.rs index c2b45024c5e3..70d9d22820a2 100644 --- a/query-engine/connectors/query-connector/src/write_args.rs +++ b/query-engine/connectors/query-connector/src/write_args.rs @@ -2,7 +2,6 @@ use crate::{ error::{ConnectorError, ErrorKind}, Filter, }; -use chrono::Utc; use indexmap::{map::Keys, IndexMap}; use prisma_models::{ CompositeFieldRef, Field, ModelProjection, ModelRef, PrismaValue, ScalarFieldRef, SelectedField, SelectionResult, @@ -10,8 +9,9 @@ use prisma_models::{ use std::{borrow::Borrow, convert::TryInto, ops::Deref}; /// WriteArgs represent data to be written to an underlying data source. -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Clone)] pub struct WriteArgs { + pub request_now: PrismaValue, pub args: IndexMap, } @@ -352,24 +352,17 @@ impl TryInto for WriteOperation { } } -impl From> for WriteArgs { - fn from(args: IndexMap) -> Self { - Self { args } +impl WriteArgs { + pub fn new(args: IndexMap, request_now: PrismaValue) -> Self { + Self { args, request_now } } -} -impl From> for WriteArgs { - fn from(pairs: Vec<(DatasourceFieldName, WriteOperation)>) -> Self { + pub fn new_empty(request_now: PrismaValue) -> Self { Self { - args: pairs.into_iter().collect(), + args: Default::default(), + request_now, } } -} - -impl WriteArgs { - pub fn new() -> Self { - Self { args: IndexMap::new() } - } pub fn insert(&mut self, key: T, arg: V) where @@ -404,19 +397,19 @@ impl WriteArgs { } pub fn add_datetimes(&mut self, model: &ModelRef) { - let now = PrismaValue::DateTime(Utc::now().into()); let updated_at_fields = model.fields().updated_at(); + let value = &self.request_now; for f in updated_at_fields { if self.args.get(f.db_name()).is_none() { - self.args.insert(f.into(), WriteOperation::scalar_set(now.clone())); + self.args.insert(f.into(), WriteOperation::scalar_set(value.clone())); } } } - pub fn update_datetimes(&mut self, model: ModelRef) { + pub fn update_datetimes(&mut self, model: &ModelRef) { if !self.args.is_empty() { - self.add_datetimes(&model) + self.add_datetimes(model) } } @@ -450,7 +443,7 @@ impl WriteArgs { /// Picks all arguments out of `args` that are updating a value for a field /// contained in `projection`, as those need to be merged into the records later on. pub fn pick_args(projection: &ModelProjection, args: &WriteArgs) -> WriteArgs { - let pairs: Vec<_> = projection + let pairs = projection .scalar_fields() .into_iter() .filter_map(|field| { @@ -459,7 +452,7 @@ pub fn pick_args(projection: &ModelProjection, args: &WriteArgs) -> WriteArgs { }) .collect(); - WriteArgs::from(pairs) + WriteArgs::new(pairs, args.request_now.clone()) } /// Merges the incoming write argument values into the given, already loaded, ids. Overwrites existing values. diff --git a/query-engine/connectors/sql-query-connector/src/model_extensions/column.rs b/query-engine/connectors/sql-query-connector/src/model_extensions/column.rs index 7b0d9e009443..dcad54413d7a 100644 --- a/query-engine/connectors/sql-query-connector/src/model_extensions/column.rs +++ b/query-engine/connectors/sql-query-connector/src/model_extensions/column.rs @@ -103,10 +103,6 @@ where let col = sf.db_name().to_string(); let column = Column::from((full_table_name, col)).type_family(sf.type_family()); - - match sf.default_value.as_ref().and_then(|d| d.get()) { - Some(default) => column.default(sf.value(default)), - None => column.default(quaint::ast::DefaultValue::Generated), - } + column.default(quaint::ast::DefaultValue::Generated) } } diff --git a/query-engine/connectors/sql-query-connector/src/query_builder/write.rs b/query-engine/connectors/sql-query-connector/src/query_builder/write.rs index 8282e958729f..6e6326c18a25 100644 --- a/query-engine/connectors/sql-query-connector/src/query_builder/write.rs +++ b/query-engine/connectors/sql-query-connector/src/query_builder/write.rs @@ -9,7 +9,7 @@ use tracing::Span; /// `INSERT` a new record to the database. Resulting an `INSERT` ast and an /// optional `RecordProjection` if available from the arguments or model. -pub fn create_record(model: &ModelRef, mut args: WriteArgs, trace_id: Option) -> Insert<'static> { +pub(crate) fn create_record(model: &ModelRef, mut args: WriteArgs, trace_id: Option) -> Insert<'static> { let fields: Vec<_> = model .fields() .scalar() diff --git a/query-engine/core/src/executor/interpreting_executor.rs b/query-engine/core/src/executor/interpreting_executor.rs index 4b56ea49bd3f..3e43eb66a287 100644 --- a/query-engine/core/src/executor/interpreting_executor.rs +++ b/query-engine/core/src/executor/interpreting_executor.rs @@ -52,13 +52,16 @@ where if let Some(tx_id) = tx_id { self.itx_manager.execute(&tx_id, operation, trace_id).await } else { - execute_single_self_contained( - &self.connector, - query_schema, - operation, - trace_id, - self.force_transactions, - ) + super::with_request_now(async move { + execute_single_self_contained( + &self.connector, + query_schema, + operation, + trace_id, + self.force_transactions, + ) + .await + }) .await } } @@ -101,7 +104,13 @@ where let mut conn = self.connector.get_connection().instrument(conn_span).await?; let mut tx = conn.start_transaction(transaction.isolation_level()).await?; - let results = execute_many_operations(query_schema, tx.as_connection_like(), &operations, trace_id).await; + let results = super::with_request_now(execute_many_operations( + query_schema, + tx.as_connection_like(), + &operations, + trace_id, + )) + .await; if results.is_err() { tx.rollback().await?; @@ -111,13 +120,16 @@ where results } else { - execute_many_self_contained( - &self.connector, - query_schema, - &operations, - trace_id, - self.force_transactions, - ) + super::with_request_now(async move { + execute_many_self_contained( + &self.connector, + query_schema, + &operations, + trace_id, + self.force_transactions, + ) + .await + }) .await } } @@ -139,35 +151,38 @@ where valid_for_millis: u64, isolation_level: Option, ) -> crate::Result { - let id = TxId::default(); - trace!("[{}] Starting...", id); - let connection_name = self.connector.name(); - let conn_span = info_span!( - "prisma:engine:connection", - user_facing = true, - "db.type" = connection_name.as_str() - ); - let conn = time::timeout( - Duration::from_millis(max_acquisition_millis), - self.connector.get_connection(), - ) - .instrument(conn_span) - .await; - - let conn = conn.map_err(|_| TransactionError::AcquisitionTimeout)??; - let c_tx = OpenTx::start(conn, isolation_level).await?; - - self.itx_manager - .create_tx( - query_schema.clone(), - id.clone(), - c_tx, - Duration::from_millis(valid_for_millis), + super::with_request_now(async move { + let id = TxId::default(); + trace!("[{}] Starting...", id); + let connection_name = self.connector.name(); + let conn_span = info_span!( + "prisma:engine:connection", + user_facing = true, + "db.type" = connection_name.as_str() + ); + let conn = time::timeout( + Duration::from_millis(max_acquisition_millis), + self.connector.get_connection(), ) + .instrument(conn_span) .await; - debug!("[{}] Started.", id); - Ok(id) + let conn = conn.map_err(|_| TransactionError::AcquisitionTimeout)??; + let c_tx = OpenTx::start(conn, isolation_level).await?; + + self.itx_manager + .create_tx( + query_schema.clone(), + id.clone(), + c_tx, + Duration::from_millis(valid_for_millis), + ) + .await; + + debug!("[{}] Started.", id); + Ok(id) + }) + .await } async fn commit_tx(&self, tx_id: TxId) -> crate::Result<()> { diff --git a/query-engine/core/src/executor/mod.rs b/query-engine/core/src/executor/mod.rs index 349432bdba55..ae31262ae0ea 100644 --- a/query-engine/core/src/executor/mod.rs +++ b/query-engine/core/src/executor/mod.rs @@ -18,7 +18,6 @@ use crate::{ }; use async_trait::async_trait; use connector::Connector; - use tracing::Dispatch; #[async_trait] @@ -90,3 +89,33 @@ pub trait TransactionManager { pub fn get_current_dispatcher() -> Dispatch { tracing::dispatcher::get_default(|current| current.clone()) } + +tokio::task_local! { + static REQUEST_NOW: prisma_value::PrismaValue; +} + +/// A timestamp that should be the `NOW()` value for the whole duration of a request. So all +/// `@default(now())` and `@updatedAt` should use it. +/// +/// That panics if REQUEST_NOW has not been set with with_request_now(). +/// +/// If we had a query context we carry for all the lifetime of the query, it would belong there. +pub(crate) fn get_request_now() -> prisma_value::PrismaValue { + REQUEST_NOW.with(|rn| rn.clone()) +} + +/// Execute a future with the current "now" timestamp that can be retrieved through +/// `get_request_now()`, initializing it if necessary. +pub(crate) async fn with_request_now(fut: F) -> R +where + F: std::future::Future, +{ + let is_set = REQUEST_NOW.try_with(|_| async {}).is_ok(); + + if is_set { + fut.await + } else { + let now = prisma_value::PrismaValue::DateTime(chrono::Utc::now().into()); + REQUEST_NOW.scope(now, fut).await + } +} diff --git a/query-engine/core/src/interactive_transactions/actors.rs b/query-engine/core/src/interactive_transactions/actors.rs index 104e0d90c0df..bbdc79cc3fef 100644 --- a/query-engine/core/src/interactive_transactions/actors.rs +++ b/query-engine/core/src/interactive_transactions/actors.rs @@ -272,7 +272,7 @@ pub fn spawn_itx_actor( span.record("itx_id", &tx_id_str.as_str()); tokio::task::spawn( - async move { + crate::executor::with_request_now(async move { let sleep = time::sleep(timeout); tokio::pin!(sleep); @@ -300,7 +300,7 @@ pub fn spawn_itx_actor( let _ = send_done.send(server.id.clone()).await; trace!("[{}] has stopped with {}", server.id.to_string(), server.cached_tx); - } + }) .instrument(span) .with_subscriber(dispatcher), ); diff --git a/query-engine/core/src/query_ast/write.rs b/query-engine/core/src/query_ast/write.rs index 248f9d139498..305d23bf89f9 100644 --- a/query-engine/core/src/query_ast/write.rs +++ b/query-engine/core/src/query_ast/write.rs @@ -36,7 +36,7 @@ impl WriteQuery { ) } - args.update_datetimes(model); + args.update_datetimes(&model); } pub fn returns(&self, field_selection: &FieldSelection) -> bool { diff --git a/query-engine/core/src/query_document/parser.rs b/query-engine/core/src/query_document/parser.rs index 1afdf2272783..f1b208f4a24d 100644 --- a/query-engine/core/src/query_document/parser.rs +++ b/query-engine/core/src/query_document/parser.rs @@ -3,9 +3,8 @@ use crate::schema::*; use bigdecimal::{BigDecimal, ToPrimitive}; use chrono::prelude::*; use indexmap::IndexMap; -use once_cell::sync::Lazy; use prisma_value::PrismaValue; -use psl::dml::{ValueGenerator, ValueGeneratorFn}; +use psl::dml::ValueGeneratorFn; use std::{borrow::Borrow, collections::HashSet, convert::TryFrom, str::FromStr, sync::Arc}; use uuid::Uuid; @@ -13,18 +12,14 @@ use uuid::Uuid; pub struct QueryDocumentParser { /// NOW() default value that's reused for all NOW() defaults on a single query - default_now: Lazy, + default_now: PrismaValue, } -impl Default for QueryDocumentParser { - fn default() -> Self { - Self { - default_now: Lazy::new(|| ValueGenerator::new_now().generate().unwrap()), - } +impl QueryDocumentParser { + pub fn new(default_now: PrismaValue) -> Self { + QueryDocumentParser { default_now } } -} -impl QueryDocumentParser { /// Parses and validates a set of selections against a schema (output) object. /// On an output object, optional types designate whether or not an output field can be nulled. /// In contrast, nullable and optional types on an input object are separate concepts. @@ -233,7 +228,7 @@ impl QueryDocumentParser { } /// Attempts to parse given query value into a concrete PrismaValue based on given scalar type. - pub fn parse_scalar( + fn parse_scalar( &self, parent_path: &QueryPath, value: QueryValue, @@ -255,6 +250,7 @@ impl QueryDocumentParser { (QueryValue::String(s), ScalarType::DateTime) => { self.parse_datetime(parent_path, s.as_str()).map(PrismaValue::DateTime) } + (QueryValue::DateTime(s), ScalarType::DateTime) => Ok(PrismaValue::DateTime(s)), (QueryValue::Int(i), ScalarType::Int) => Ok(PrismaValue::Int(i)), (QueryValue::Int(i), ScalarType::Float) => Ok(PrismaValue::Float(BigDecimal::from(i))), diff --git a/query-engine/core/src/query_document/query_value.rs b/query-engine/core/src/query_document/query_value.rs index 8dec05da0ae2..c6842e874c4f 100644 --- a/query-engine/core/src/query_document/query_value.rs +++ b/query-engine/core/src/query_document/query_value.rs @@ -13,6 +13,7 @@ pub enum QueryValue { Enum(String), List(Vec), Object(IndexMap), + DateTime(chrono::DateTime), } impl PartialEq for QueryValue { @@ -26,6 +27,9 @@ impl PartialEq for QueryValue { (QueryValue::Enum(kind1), QueryValue::Enum(kind2)) => kind1 == kind2, (QueryValue::List(list1), QueryValue::List(list2)) => list1 == list2, (QueryValue::Object(t1), QueryValue::Object(t2)) => t1 == t2, + (QueryValue::DateTime(t1), QueryValue::DateTime(t2)) => t1 == t2, + (QueryValue::String(t1), QueryValue::DateTime(t2)) => t1 == stringify_date(t2).as_str(), + (QueryValue::DateTime(t1), QueryValue::String(t2)) => stringify_date(t1).as_str() == t2, _ => false, } } @@ -38,6 +42,7 @@ impl Hash for QueryValue { Self::Float(f) => f.hash(state), Self::String(s) => s.hash(state), Self::Boolean(b) => b.hash(state), + Self::DateTime(dt) => stringify_date(dt).hash(state), Self::Null => (), Self::Enum(s) => s.hash(state), Self::List(l) => l.hash(state), @@ -64,7 +69,7 @@ impl From for QueryValue { PrismaValue::String(s) => Self::String(s), PrismaValue::Float(f) => Self::Float(f), PrismaValue::Boolean(b) => Self::Boolean(b), - PrismaValue::DateTime(dt) => Self::String(stringify_date(&dt)), + PrismaValue::DateTime(dt) => Self::DateTime(dt), PrismaValue::Enum(s) => Self::Enum(s), PrismaValue::List(l) => Self::List(l.into_iter().map(QueryValue::from).collect()), PrismaValue::Int(i) => Self::Int(i), diff --git a/query-engine/core/src/query_graph_builder/builder.rs b/query-engine/core/src/query_graph_builder/builder.rs index 913fba590ec7..5a109e1f2e01 100644 --- a/query-engine/core/src/query_graph_builder/builder.rs +++ b/query-engine/core/src/query_graph_builder/builder.rs @@ -33,8 +33,11 @@ impl QueryGraphBuilder { root_object: &ObjectTypeStrongRef, // Either the query or mutation object. ) -> QueryGraphBuilderResult<(QueryGraph, IrSerializer)> { let mut selections = vec![selection]; - let mut parsed_object = - QueryDocumentParser::default().parse_object(QueryPath::default(), &selections, root_object)?; + let mut parsed_object = QueryDocumentParser::new(crate::executor::get_request_now()).parse_object( + QueryPath::default(), + &selections, + root_object, + )?; // Because we're processing root objects, there can only be one query / mutation. let field_pair = parsed_object.fields.pop().unwrap(); diff --git a/query-engine/core/src/query_graph_builder/write/update.rs b/query-engine/core/src/query_graph_builder/write/update.rs index 5ebdd83ba03a..84a694cb2404 100644 --- a/query-engine/core/src/query_graph_builder/write/update.rs +++ b/query-engine/core/src/query_graph_builder/write/update.rs @@ -148,7 +148,7 @@ where let update_args = WriteArgsParser::from(&model, data_map)?; let mut args = update_args.args; - args.update_datetimes(Arc::clone(&model)); + args.update_datetimes(&model); let filter: Filter = filter.into(); let update_parent = Query::Write(WriteQuery::UpdateRecord(UpdateRecord { @@ -183,7 +183,7 @@ where let update_args = WriteArgsParser::from(&model, data_map)?; let mut args = update_args.args; - args.update_datetimes(Arc::clone(&model)); + args.update_datetimes(&model); let update_many = UpdateManyRecords { model, diff --git a/query-engine/core/src/query_graph_builder/write/utils.rs b/query-engine/core/src/query_graph_builder/write/utils.rs index 866971f6723b..efc15a6083b0 100644 --- a/query-engine/core/src/query_graph_builder/write/utils.rs +++ b/query-engine/core/src/query_graph_builder/write/utils.rs @@ -144,7 +144,7 @@ pub fn update_records_node_placeholder(graph: &mut QueryGraph, filter: T, mod where T: Into, { - let args = WriteArgs::new(); + let args = WriteArgs::new_empty(crate::executor::get_request_now()); let filter = filter.into(); let record_filter = filter.into(); @@ -568,7 +568,7 @@ pub fn emulate_on_delete_set_null( let set_null_query = WriteQuery::UpdateManyRecords(UpdateManyRecords { model: dependent_model.clone(), record_filter: RecordFilter::empty(), - args: child_update_args.into(), + args: WriteArgs::new(child_update_args, crate::executor::get_request_now()), }); let set_null_dependents_node = graph.create_node(Query::Write(set_null_query)); @@ -693,7 +693,7 @@ pub fn emulate_on_update_set_null( let set_null_query = WriteQuery::UpdateManyRecords(UpdateManyRecords { model: dependent_model.clone(), record_filter: RecordFilter::empty(), - args: child_update_args.into(), + args: WriteArgs::new(child_update_args, crate::executor::get_request_now()), }); let set_null_dependents_node = graph.create_node(Query::Write(set_null_query)); @@ -992,7 +992,10 @@ pub fn emulate_on_update_cascade( let update_query = WriteQuery::UpdateManyRecords(UpdateManyRecords { model: dependent_model.clone(), record_filter: RecordFilter::empty(), - args: child_update_args.into(), + args: WriteArgs::new( + child_update_args.into_iter().collect(), + crate::executor::get_request_now(), + ), }); let update_dependents_node = graph.create_node(Query::Write(update_query)); diff --git a/query-engine/core/src/query_graph_builder/write/write_args_parser.rs b/query-engine/core/src/query_graph_builder/write/write_args_parser.rs index 32b3de6dfd06..dd10a0efa0f7 100644 --- a/query-engine/core/src/query_graph_builder/write/write_args_parser.rs +++ b/query-engine/core/src/query_graph_builder/write/write_args_parser.rs @@ -7,7 +7,7 @@ use prisma_models::{ use schema_builder::constants::{args, json_null, operations}; use std::{convert::TryInto, sync::Arc}; -#[derive(Default, Debug)] +#[derive(Debug)] pub struct WriteArgsParser { pub args: WriteArgs, pub nested: Vec<(RelationFieldRef, ParsedInputMap)>, @@ -18,7 +18,10 @@ impl WriteArgsParser { /// E.g.: { data: { THIS MAP } } from the `data` argument of a write query. pub fn from(model: &ModelRef, data_map: ParsedInputMap) -> QueryGraphBuilderResult { data_map.into_iter().try_fold( - WriteArgsParser::default(), + WriteArgsParser { + args: WriteArgs::new_empty(crate::executor::get_request_now()), + nested: Default::default(), + }, |mut args, (k, v): (String, ParsedInputValue)| { let field = model.fields().find_from_all(&k).unwrap(); diff --git a/query-engine/prisma-models/src/prisma_value_ext.rs b/query-engine/prisma-models/src/prisma_value_ext.rs index 5badc04d5bc5..71a330cc3a1f 100644 --- a/query-engine/prisma-models/src/prisma_value_ext.rs +++ b/query-engine/prisma-models/src/prisma_value_ext.rs @@ -2,8 +2,6 @@ use super::{PrismaValue, TypeIdentifier}; use crate::DomainError; use bigdecimal::ToPrimitive; -// use std::convert::TryFrom; - pub trait PrismaValueExtensions { fn coerce(self, to_type: &TypeIdentifier) -> crate::Result; }