diff --git a/.github/workflows/test-driver-adapters-template.yml b/.github/workflows/test-driver-adapters-template.yml index c922e4bb7f1b..eb45af278bc4 100644 --- a/.github/workflows/test-driver-adapters-template.yml +++ b/.github/workflows/test-driver-adapters-template.yml @@ -31,6 +31,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # using head ref rather than merge branch to get original commit message + ref: ${{ github.event.pull_request.head.sha }} - name: "Setup Node.js" uses: actions/setup-node@v4 with: @@ -55,7 +58,9 @@ jobs: - name: Extract Branch Name id: extract-branch run: | + echo "Extracting branch name from: $(git show -s --format=%s)" branch="$(git show -s --format=%s | grep -o "DRIVER_ADAPTERS_BRANCH=[^ ]*" | cut -f2 -d=)" + echo "branch=$branch" if [ -n "$branch" ]; then echo "Using $branch branch of driver adapters" echo "DRIVER_ADAPTERS_BRANCH=$branch" >> "$GITHUB_ENV" diff --git a/.github/workflows/wasm-benchmarks.yml b/.github/workflows/wasm-benchmarks.yml index 160abfaf5b50..cfbe4ca7b531 100644 --- a/.github/workflows/wasm-benchmarks.yml +++ b/.github/workflows/wasm-benchmarks.yml @@ -23,6 +23,7 @@ jobs: - name: Checkout PR branch uses: actions/checkout@v4 with: + # using head ref rather than merge branch to get original commit message ref: ${{ github.event.pull_request.head.sha }} - uses: ./.github/workflows/include/rust-wasm-setup @@ -48,7 +49,9 @@ jobs: - name: Extract Branch Name run: | + echo "Extracting branch name from: $(git show -s --format=%s)" branch="$(git show -s --format=%s | grep -o "DRIVER_ADAPTERS_BRANCH=[^ ]*" | cut -f2 -d=)" + echo "branch=$branch" if [ -n "$branch" ]; then echo "Using $branch branch of driver adapters" echo "DRIVER_ADAPTERS_BRANCH=$branch" >> "$GITHUB_ENV" diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/cascade.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/cascade.rs index 99cd190e161a..1da34e776286 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/cascade.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/cascade.rs @@ -33,13 +33,7 @@ mod one2one_req { schema.to_owned() } - #[connector_test(schema(required), exclude(Sqlite("cfd1")))] - /// On D1, this fails with: - /// - /// ```diff - /// - {"data":{"updateManyParent":{"count":1}}} - /// + {"data":{"updateManyParent":{"count":2}}} - /// ``` + #[connector_test(schema(required))] async fn update_parent_cascade(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, r#"mutation { @@ -176,14 +170,7 @@ mod one2one_opt { schema.to_owned() } - #[connector_test(schema(optional), exclude(Sqlite("cfd1")))] - // Updating the parent updates the child FK as well. - // On D1, this fails with: - // - // ```diff - // - {"data":{"updateManyParent":{"count":1}}} - // + {"data":{"updateManyParent":{"count":2}}} - // ``` + #[connector_test(schema(optional))] async fn update_parent_cascade(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, r#"mutation { diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/restrict.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/restrict.rs index 99c3c0b094dc..f8bff4d30f06 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/restrict.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/restrict.rs @@ -255,13 +255,7 @@ mod one2many_req { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - /// Updating the parent succeeds if no child is connected or if the linking fields aren't part of the update payload. - /// - /// ```diff - /// - {"data":{"updateManyParent":{"count":1}}} - /// + {"data":{"updateManyParent":{"count":2}}} - /// ``` + #[connector_test] async fn update_parent(runner: Runner) -> TestResult<()> { create_test_data(&runner).await?; run_query!( @@ -383,13 +377,7 @@ mod one2many_opt { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - /// Updating the parent succeeds if no child is connected or if the linking fields aren't part of the update payload. - /// - /// ```diff - /// - {"data":{"updateManyParent":{"count":1}}} - /// + {"data":{"updateManyParent":{"count":2}}} - /// ``` + #[connector_test] async fn update_parent(runner: Runner) -> TestResult<()> { create_test_data(&runner).await?; run_query!( diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/set_null.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/set_null.rs index 8ef0ab0d1e8c..dcb4083dbba9 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/set_null.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/ref_actions/on_update/set_null.rs @@ -65,13 +65,7 @@ mod one2one_opt { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"updateManyParent":{"count":1}}} - // + {"data":{"updateManyParent":{"count":2}}} - // ``` + #[connector_test] async fn update_many_parent(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, r#"mutation { createOneParent(data: { id: 1, uniq: "1", child: { create: { id: 1 }}}) { id }}"#), @@ -457,13 +451,7 @@ mod one2many_opt { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"updateManyParent":{"count":1}}} - // + {"data":{"updateManyParent":{"count":2}}} - // ``` + #[connector_test] async fn update_many_parent(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, r#"mutation { createOneParent(data: { id: 1, uniq: "1", children: { create: { id: 1 }}}) { id }}"#), diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/aggregation/group_by_having.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/aggregation/group_by_having.rs index 545c44cfe41c..15d11967178e 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/aggregation/group_by_having.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/aggregation/group_by_having.rs @@ -52,13 +52,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"groupByTestModel":[{"string":"group1","_count":{"int":2}}]}} - // + {"data":{"groupByTestModel":[]}} - // ``` + #[connector_test] async fn having_count_scalar_filter(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, int: 1, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, int: 2, string: "group1" }"#).await?; @@ -133,13 +127,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"groupByTestModel":[{"string":"group1","_sum":{"float":16.0,"int":16}}]}} - // + {"data":{"groupByTestModel":[]}} - // ``` + #[connector_test] async fn having_sum_scalar_filter(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, float: 10, int: 10, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, float: 6, int: 6, string: "group1" }"#).await?; @@ -208,13 +196,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"groupByTestModel":[{"string":"group1","_min":{"float":0.0,"int":0}},{"string":"group2","_min":{"float":0.0,"int":0}}]}} - // + {"data":{"groupByTestModel":[]}} - // ``` + #[connector_test] async fn having_min_scalar_filter(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, float: 10, int: 10, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, float: 0, int: 0, string: "group1" }"#).await?; @@ -282,13 +264,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"groupByTestModel":[{"string":"group1","_max":{"float":10.0,"int":10}},{"string":"group2","_max":{"float":10.0,"int":10}}]}} - // + {"data":{"groupByTestModel":[]}} - // ``` + #[connector_test] async fn having_max_scalar_filter(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, float: 10, int: 10, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, float: 0, int: 0, string: "group1" }"#).await?; @@ -356,13 +332,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this fails with: - // - // ```diff - // - {"data":{"groupByTestModel":[{"string":"group1","_count":{"string":2}}]}} - // + {"data":{"groupByTestModel":[]}} - // ``` + #[connector_test] async fn having_count_non_numerical_field(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, float: 10, int: 10, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, float: 0, int: 0, string: "group1" }"#).await?; @@ -380,16 +350,7 @@ mod aggr_group_by_having { Ok(()) } - #[connector_test(exclude(Sqlite("cfd1")))] - // On D1, this panics with: - // - // ``` - // assertion `left == right` failed: Query result: {"data":{"groupByTestModel":[]}} is not part of the expected results: ["{\"data\":{\"groupByTestModel\":[{\"string\":\"group1\"},{\"string\":\"group2\"}]}}", "{\"data\":{\"groupByTestModel\":[{\"string\":\"group2\"},{\"string\":\"group1\"}]}}"] for connector SQLite (cfd1) - // left: false - // right: true - // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - // FAILED - // ``` + #[connector_test] async fn having_without_aggr_sel(runner: Runner) -> TestResult<()> { create_row(&runner, r#"{ id: 1, float: 10, int: 10, string: "group1" }"#).await?; create_row(&runner, r#"{ id: 2, float: 0, int: 0, string: "group1" }"#).await?; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/nested_mutations/not_using_schema_base/nested_create_many.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/nested_mutations/not_using_schema_base/nested_create_many.rs index 821b99f9fce8..30a671f61def 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/nested_mutations/not_using_schema_base/nested_create_many.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/nested_mutations/not_using_schema_base/nested_create_many.rs @@ -140,7 +140,7 @@ mod nested_create_many { // Each DB allows a certain amount of params per single query, and a certain number of rows. // We create 1000 nested records. // "Nested createMany" should "allow creating a large number of records (horizontal partitioning check)" - #[connector_test(exclude(Sqlite("cfd1")))] + #[connector_test] async fn allow_create_large_number_records(runner: Runner) -> TestResult<()> { let records: Vec<_> = (1..=1000).map(|i| format!(r#"{{ id: {i}, str1: "{i}" }}"#)).collect(); diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/delete_many_relations.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/delete_many_relations.rs index ec9508347a6e..6bd3eab692ea 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/delete_many_relations.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/delete_many_relations.rs @@ -16,7 +16,7 @@ mod delete_many_rels { // On D1, this fails with: // // ```diff - // - {"data":{"deleteManyParent":{"count":1}}} + // - {"data":{"deleteManyParent":{"count":2}}} // + {"data":{"deleteManyParent":{"count":3}}} // ``` async fn p1_c1(runner: &Runner, _t: &DatamodelWithParams) -> TestResult<()> { diff --git a/query-engine/driver-adapters/src/conversion/js_arg_type.rs b/query-engine/driver-adapters/src/conversion/js_arg_type.rs new file mode 100644 index 000000000000..e1ea7c1c5754 --- /dev/null +++ b/query-engine/driver-adapters/src/conversion/js_arg_type.rs @@ -0,0 +1,93 @@ +/// `JSArgType` is a 1:1 mapping of [`quaint::ValueType`] that: +/// - only includes the type tag (e.g. `Int32`, `Text`, `Enum`, etc.) +/// - doesn't care for the optionality of the actual value (e.g., `quaint::Value::Int32(None)` -> `JSArgType::Int32`) +/// - is used to guide the JS side on how to serialize the query argument value before sending it to the JS driver. +#[derive(Debug, PartialEq)] +pub enum JSArgType { + /// 32-bit signed integer. + Int32, + /// 64-bit signed integer. + Int64, + /// 32-bit floating point. + Float, + /// 64-bit floating point. + Double, + /// String value. + Text, + /// Database enum value. + Enum, + /// Database enum array (PostgreSQL specific). + EnumArray, + /// Bytes value. + Bytes, + /// Boolean value. + Boolean, + /// A single character. + Char, + /// An array value (PostgreSQL). + Array, + /// A numeric value. + Numeric, + /// A JSON value. + Json, + /// A XML value. + Xml, + /// An UUID value. + Uuid, + /// A datetime value. + DateTime, + /// A date value. + Date, + /// A time value. + Time, +} + +impl core::fmt::Display for JSArgType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + JSArgType::Int32 => "Int32", + JSArgType::Int64 => "Int64", + JSArgType::Float => "Float", + JSArgType::Double => "Double", + JSArgType::Text => "Text", + JSArgType::Enum => "Enum", + JSArgType::EnumArray => "EnumArray", + JSArgType::Bytes => "Bytes", + JSArgType::Boolean => "Boolean", + JSArgType::Char => "Char", + JSArgType::Array => "Array", + JSArgType::Numeric => "Numeric", + JSArgType::Json => "Json", + JSArgType::Xml => "Xml", + JSArgType::Uuid => "Uuid", + JSArgType::DateTime => "DateTime", + JSArgType::Date => "Date", + JSArgType::Time => "Time", + }; + + write!(f, "{}", s) + } +} + +pub fn value_to_js_arg_type(value: &quaint::Value) -> JSArgType { + match &value.typed { + quaint::ValueType::Int32(_) => JSArgType::Int32, + quaint::ValueType::Int64(_) => JSArgType::Int64, + quaint::ValueType::Float(_) => JSArgType::Float, + quaint::ValueType::Double(_) => JSArgType::Double, + quaint::ValueType::Text(_) => JSArgType::Text, + quaint::ValueType::Enum(_, _) => JSArgType::Enum, + quaint::ValueType::EnumArray(_, _) => JSArgType::EnumArray, + quaint::ValueType::Bytes(_) => JSArgType::Bytes, + quaint::ValueType::Boolean(_) => JSArgType::Boolean, + quaint::ValueType::Char(_) => JSArgType::Char, + quaint::ValueType::Array(_) => JSArgType::Array, + quaint::ValueType::Numeric(_) => JSArgType::Numeric, + quaint::ValueType::Json(_) => JSArgType::Json, + quaint::ValueType::Xml(_) => JSArgType::Xml, + quaint::ValueType::Uuid(_) => JSArgType::Uuid, + quaint::ValueType::DateTime(_) => JSArgType::DateTime, + quaint::ValueType::Date(_) => JSArgType::Date, + quaint::ValueType::Time(_) => JSArgType::Time, + } +} diff --git a/query-engine/driver-adapters/src/conversion/mod.rs b/query-engine/driver-adapters/src/conversion/mod.rs index 8b7808517ba4..5d07fd33dc9b 100644 --- a/query-engine/driver-adapters/src/conversion/mod.rs +++ b/query-engine/driver-adapters/src/conversion/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod js_arg; +pub(crate) mod js_arg_type; pub(crate) mod js_to_quaint; #[cfg(feature = "mysql")] @@ -9,3 +10,4 @@ pub(crate) mod postgres; pub(crate) mod sqlite; pub use js_arg::JSArg; +pub use js_arg_type::{value_to_js_arg_type, JSArgType}; diff --git a/query-engine/driver-adapters/src/conversion/sqlite.rs b/query-engine/driver-adapters/src/conversion/sqlite.rs index af070ec0b2cd..0648f713da89 100644 --- a/query-engine/driver-adapters/src/conversion/sqlite.rs +++ b/query-engine/driver-adapters/src/conversion/sqlite.rs @@ -36,7 +36,7 @@ mod test { #[rustfmt::skip] fn test_value_to_js_arg() { let test_cases = vec![ - ( + ( // This is different than how mysql or postgres processes integral BigInt values. ValueType::Numeric(Some(1.into())), JSArg::Value(Value::Number("1.0".parse().unwrap())) diff --git a/query-engine/driver-adapters/src/napi/conversion.rs b/query-engine/driver-adapters/src/napi/conversion.rs index 2fab5a28bb73..e1ff0ec4b99c 100644 --- a/query-engine/driver-adapters/src/napi/conversion.rs +++ b/query-engine/driver-adapters/src/napi/conversion.rs @@ -1,4 +1,4 @@ -pub(crate) use crate::conversion::JSArg; +pub(crate) use crate::conversion::{JSArg, JSArgType}; use napi::bindgen_prelude::{FromNapiValue, ToNapiValue}; use napi::NapiValue; @@ -12,6 +12,12 @@ impl FromNapiValue for JSArg { } } +impl FromNapiValue for JSArgType { + unsafe fn from_napi_value(_env: napi::sys::napi_env, _napi_value: napi::sys::napi_value) -> napi::Result { + unreachable!() + } +} + // ToNapiValue is the napi equivalent to serde::Serialize. impl ToNapiValue for JSArg { unsafe fn to_napi_value(env: napi::sys::napi_env, value: Self) -> napi::Result { @@ -46,3 +52,9 @@ impl ToNapiValue for JSArg { } } } + +impl ToNapiValue for JSArgType { + unsafe fn to_napi_value(env: napi::sys::napi_env, value: Self) -> napi::Result { + ToNapiValue::to_napi_value(env, value.to_string()) + } +} diff --git a/query-engine/driver-adapters/src/queryable.rs b/query-engine/driver-adapters/src/queryable.rs index 2d1f2aef2123..fd7affc8d425 100644 --- a/query-engine/driver-adapters/src/queryable.rs +++ b/query-engine/driver-adapters/src/queryable.rs @@ -53,7 +53,7 @@ impl JsBaseQueryable { async fn build_query(&self, sql: &str, values: &[quaint::Value<'_>]) -> quaint::Result { let sql: String = sql.to_string(); - let converter = match self.provider { + let args_converter = match self.provider { #[cfg(feature = "postgresql")] AdapterFlavour::Postgres => conversion::postgres::value_to_js_arg, #[cfg(feature = "sqlite")] @@ -64,10 +64,15 @@ impl JsBaseQueryable { let args = values .iter() - .map(converter) + .map(args_converter) .collect::>>()?; - Ok(Query { sql, args }) + let arg_types = values + .iter() + .map(conversion::value_to_js_arg_type) + .collect::>(); + + Ok(Query { sql, args, arg_types }) } } diff --git a/query-engine/driver-adapters/src/types.rs b/query-engine/driver-adapters/src/types.rs index 83c69fbf146d..9e5f1eae149d 100644 --- a/query-engine/driver-adapters/src/types.rs +++ b/query-engine/driver-adapters/src/types.rs @@ -10,7 +10,7 @@ use quaint::connector::{ColumnType as QuaintColumnType, ExternalConnectionInfo, #[cfg(target_arch = "wasm32")] use tsify::Tsify; -use crate::conversion::JSArg; +use crate::conversion::{JSArg, JSArgType}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -288,6 +288,7 @@ js_column_type! { pub struct Query { pub sql: String, pub args: Vec, + pub arg_types: Vec, } #[cfg_attr(not(target_arch = "wasm32"), napi_derive::napi(object))] diff --git a/query-engine/driver-adapters/src/wasm/conversion.rs b/query-engine/driver-adapters/src/wasm/conversion.rs index d2039210a626..b697a49577c5 100644 --- a/query-engine/driver-adapters/src/wasm/conversion.rs +++ b/query-engine/driver-adapters/src/wasm/conversion.rs @@ -1,4 +1,4 @@ -use crate::conversion::JSArg; +use crate::conversion::{JSArg, JSArgType}; use super::to_js::{serde_serialize, ToJsValue}; use crate::types::Query; @@ -8,8 +8,10 @@ use wasm_bindgen::JsValue; impl ToJsValue for Query { fn to_js_value(&self) -> Result { let object = Object::new(); + let sql = self.sql.to_js_value()?; Reflect::set(&object, &JsValue::from(JsString::from("sql")), &sql)?; + let args = Array::new(); for arg in &self.args { let value = arg.to_js_value()?; @@ -17,6 +19,12 @@ impl ToJsValue for Query { } Reflect::set(&object, &JsValue::from(JsString::from("args")), &args)?; + let arg_types = Array::new(); + for arg_type in &self.arg_types { + arg_types.push(&arg_type.to_js_value()?); + } + Reflect::set(&object, &JsValue::from(JsString::from("argTypes")), &arg_types)?; + Ok(JsValue::from(object)) } } @@ -42,3 +50,9 @@ impl ToJsValue for JSArg { } } } + +impl ToJsValue for JSArgType { + fn to_js_value(&self) -> Result { + Ok(JsValue::from(self.to_string())) + } +}