From 47de92454ce2f8ecaa9e0f5d25136c773237e00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Wed, 27 Aug 2025 15:12:50 +0200 Subject: [PATCH] feat(qc): add support for SQL NOW() function --- quaint/src/ast/function.rs | 3 +++ quaint/src/ast/function/now.rs | 26 +++++++++++++++++++ quaint/src/visitor.rs | 1 + .../query-compiler/src/expression.rs | 2 ++ .../query-compiler/src/expression/format.rs | 1 + .../src/root_queries/update/into_operation.rs | 4 +++ .../sql-query-builder/src/write.rs | 4 +++ .../query-structure/src/write_args.rs | 4 +++ .../fields/data_input_mapper/update.rs | 24 ++++++++++------- query-engine/schema/src/constants.rs | 3 +++ 10 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 quaint/src/ast/function/now.rs diff --git a/quaint/src/ast/function.rs b/quaint/src/ast/function.rs index c83eba5458d8..c9b8f1434780 100644 --- a/quaint/src/ast/function.rs +++ b/quaint/src/ast/function.rs @@ -11,6 +11,7 @@ mod json_unquote; mod lower; mod maximum; mod minimum; +mod now; mod row_number; mod row_to_json; mod search; @@ -32,6 +33,7 @@ pub use json_unquote::*; pub use lower::*; pub use maximum::*; pub use minimum::*; +pub use now::*; pub use row_number::*; pub use row_to_json::*; pub use search::*; @@ -90,6 +92,7 @@ pub(crate) enum FunctionType<'a> { UuidToBin, UuidToBinSwapped, Uuid, + Now, } impl<'a> Aliasable<'a> for Function<'a> { diff --git a/quaint/src/ast/function/now.rs b/quaint/src/ast/function/now.rs new file mode 100644 index 000000000000..f9988d9572b4 --- /dev/null +++ b/quaint/src/ast/function/now.rs @@ -0,0 +1,26 @@ +use super::{Function, FunctionType}; +use crate::ast::Expression; + +/// Generates the SQL function NOW() returning the current timestamp in MySQL/PostgreSQL. +/// ```rust +/// # use quaint::{ast::*, visitor::{Visitor, Mysql, Postgres}}; +/// # fn main() -> Result<(), quaint::error::Error> { +/// +/// let query = Select::default().value(now()); +/// +/// let (sql, _) = Mysql::build(query)?; +/// assert_eq!("SELECT NOW()", sql); +/// +/// let (sql, _) = Postgres::build(query)?; +/// assert_eq!("SELECT NOW()", sql); +/// # Ok(()) +/// # } +/// ``` +pub fn native_now() -> Expression<'static> { + let func = Function { + typ_: FunctionType::Now, + alias: None, + }; + + func.into() +} diff --git a/quaint/src/visitor.rs b/quaint/src/visitor.rs index 4742fd0972e5..81731c5666c4 100644 --- a/quaint/src/visitor.rs +++ b/quaint/src/visitor.rs @@ -1205,6 +1205,7 @@ pub trait Visitor<'a> { self.write("uuid_to_bin(uuid(), 1)")?; } FunctionType::Uuid => self.write("uuid()")?, + FunctionType::Now => self.write("NOW()")?, FunctionType::Concat(concat) => { self.visit_concat(concat)?; } diff --git a/query-compiler/query-compiler/src/expression.rs b/query-compiler/query-compiler/src/expression.rs index 333c22bf8504..6c84261b3551 100644 --- a/query-compiler/query-compiler/src/expression.rs +++ b/query-compiler/query-compiler/src/expression.rs @@ -246,6 +246,7 @@ pub enum FieldOperation { Subtract(PrismaValue), Multiply(PrismaValue), Divide(PrismaValue), + Now, } impl TryFrom for FieldOperation { @@ -258,6 +259,7 @@ impl TryFrom for FieldOperation { ScalarWriteOperation::Subtract(val) => Ok(Self::Subtract(val)), ScalarWriteOperation::Multiply(val) => Ok(Self::Multiply(val)), ScalarWriteOperation::Divide(val) => Ok(Self::Divide(val)), + ScalarWriteOperation::Now => Ok(Self::Now), ScalarWriteOperation::Field(_) | ScalarWriteOperation::Unset(_) => Err(UnsupportedScalarWriteOperation(op)), } } diff --git a/query-compiler/query-compiler/src/expression/format.rs b/query-compiler/query-compiler/src/expression/format.rs index fde6fa8b6a11..ff882d66fc19 100644 --- a/query-compiler/query-compiler/src/expression/format.rs +++ b/query-compiler/query-compiler/src/expression/format.rs @@ -453,6 +453,7 @@ where self.keyword("mul").append(self.space()).append(self.value(val)) } FieldOperation::Divide(val) => self.keyword("div").append(self.space()).append(self.value(val)), + FieldOperation::Now => self.keyword("now"), }, ) }))) diff --git a/query-engine/connectors/mongodb-query-connector/src/root_queries/update/into_operation.rs b/query-engine/connectors/mongodb-query-connector/src/root_queries/update/into_operation.rs index 05f8fec07247..8b85046b8d1c 100644 --- a/query-engine/connectors/mongodb-query-connector/src/root_queries/update/into_operation.rs +++ b/query-engine/connectors/mongodb-query-connector/src/root_queries/update/into_operation.rs @@ -44,6 +44,10 @@ impl IntoUpdateOperation for ScalarWriteOperation { field_path, doc! { "$divide": [dollar_field_path, (field, rhs).into_bson()?] }, )), + ScalarWriteOperation::Now => Some(UpdateOperation::generic( + field_path, + doc! { "$currentDate": true }, + )), ScalarWriteOperation::Unset(true) => Some(UpdateOperation::unset(field_path)), ScalarWriteOperation::Unset(false) => None, ScalarWriteOperation::Field(_) => unimplemented!(), diff --git a/query-engine/query-builders/sql-query-builder/src/write.rs b/query-engine/query-builders/sql-query-builder/src/write.rs index 09cdb47b1bc3..b2229f946980 100644 --- a/query-engine/query-builders/sql-query-builder/src/write.rs +++ b/query-engine/query-builders/sql-query-builder/src/write.rs @@ -191,6 +191,10 @@ pub fn build_update_and_set_query( e / field.value(rhs, ctx).into() } + ScalarWriteOperation::Now => { + native_now() + }, + ScalarWriteOperation::Unset(_) => unreachable!("Unset is not supported on SQL connectors"), }; diff --git a/query-engine/query-structure/src/write_args.rs b/query-engine/query-structure/src/write_args.rs index 1dde6a10ee9c..ea241b65c352 100644 --- a/query-engine/query-structure/src/write_args.rs +++ b/query-engine/query-structure/src/write_args.rs @@ -150,6 +150,9 @@ pub enum ScalarWriteOperation { /// Divide field by value. Divide(PrismaValue), + + /// Set field to the current date-time + Now, } #[derive(Debug, PartialEq, Clone)] @@ -487,5 +490,6 @@ pub fn apply_expression(val: PrismaValue, scalar_write: ScalarWriteOperation) -> ScalarWriteOperation::Multiply(rhs) => val * rhs, ScalarWriteOperation::Divide(rhs) => val / rhs, ScalarWriteOperation::Unset(_) => unimplemented!(), + ScalarWriteOperation::Now => PrismaValue::generator_now(), } } diff --git a/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs b/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs index c1f5356eb76d..1bba43ade1e1 100644 --- a/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs +++ b/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs @@ -19,16 +19,16 @@ impl UpdateDataInputFieldMapper { impl DataInputFieldMapper for UpdateDataInputFieldMapper { fn map_scalar<'a>(&self, ctx: &'a QuerySchema, sf: ScalarFieldRef) -> InputField<'a> { let base_update_type = match sf.type_identifier() { - TypeIdentifier::Float => InputType::object(update_operations_object_type(ctx, "Float", sf.clone(), true)), + TypeIdentifier::Float => InputType::object(update_operations_object_type(ctx, "Float", sf.clone(), true, false)), TypeIdentifier::Decimal => { - InputType::object(update_operations_object_type(ctx, "Decimal", sf.clone(), true)) + InputType::object(update_operations_object_type(ctx, "Decimal", sf.clone(), true, false)) } - TypeIdentifier::Int => InputType::object(update_operations_object_type(ctx, "Int", sf.clone(), true)), - TypeIdentifier::BigInt => InputType::object(update_operations_object_type(ctx, "BigInt", sf.clone(), true)), + TypeIdentifier::Int => InputType::object(update_operations_object_type(ctx, "Int", sf.clone(), true, false)), + TypeIdentifier::BigInt => InputType::object(update_operations_object_type(ctx, "BigInt", sf.clone(), true, false)), TypeIdentifier::String => { - InputType::object(update_operations_object_type(ctx, "String", sf.clone(), false)) + InputType::object(update_operations_object_type(ctx, "String", sf.clone(), false, false)) } - TypeIdentifier::Boolean => InputType::object(update_operations_object_type(ctx, "Bool", sf.clone(), false)), + TypeIdentifier::Boolean => InputType::object(update_operations_object_type(ctx, "Bool", sf.clone(), false, false)), TypeIdentifier::Enum(enum_id) => { let enum_name = ctx.internal_data_model.walk(enum_id).name(); InputType::object(update_operations_object_type( @@ -36,14 +36,15 @@ impl DataInputFieldMapper for UpdateDataInputFieldMapper { &format!("Enum{enum_name}"), sf.clone(), false, + false, )) } TypeIdentifier::Json => map_scalar_input_type_for_field(ctx, &sf), TypeIdentifier::DateTime => { - InputType::object(update_operations_object_type(ctx, "DateTime", sf.clone(), false)) + InputType::object(update_operations_object_type(ctx, "DateTime", sf.clone(), false, true)) } - TypeIdentifier::UUID => InputType::object(update_operations_object_type(ctx, "Uuid", sf.clone(), false)), - TypeIdentifier::Bytes => InputType::object(update_operations_object_type(ctx, "Bytes", sf.clone(), false)), + TypeIdentifier::UUID => InputType::object(update_operations_object_type(ctx, "Uuid", sf.clone(), false, false)), + TypeIdentifier::Bytes => InputType::object(update_operations_object_type(ctx, "Bytes", sf.clone(), false, false)), TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), }; @@ -160,6 +161,7 @@ fn update_operations_object_type<'a>( prefix: &str, sf: ScalarField, with_number_operators: bool, + with_datetime_operators: bool, ) -> InputObjectType<'a> { let ident = Identifier::new_prisma(IdentifierType::FieldUpdateOperationsInput( !sf.is_required(), @@ -184,6 +186,10 @@ fn update_operations_object_type<'a>( fields.push(simple_input_field(operations::DIVIDE, typ, None).optional()); } + if with_datetime_operators { + fields.push(InputField::new(operations::NOW.into(), vec![], None, false)); + } + if ctx.has_capability(ConnectorCapability::UndefinedType) && !sf.is_required() { fields.push(simple_input_field(operations::UNSET, InputType::boolean(), None).optional()); } diff --git a/query-engine/schema/src/constants.rs b/query-engine/schema/src/constants.rs index 47adbb822351..ad3d7b6c08e5 100644 --- a/query-engine/schema/src/constants.rs +++ b/query-engine/schema/src/constants.rs @@ -51,6 +51,9 @@ pub mod operations { pub const DECREMENT: &str = "decrement"; pub const MULTIPLY: &str = "multiply"; pub const DIVIDE: &str = "divide"; + + // date-time + pub const NOW: &str = "now"; } pub mod filters {