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

feat(driver-adapters): fix bigint handling on creation and filter #4648

Merged
merged 7 commits into from
Jan 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ mod max_integer {
schema.to_owned()
}

// Info: `driver-adapters` are currently excluded because they yield a different error message,
// coming straight from the database.
#[connector_test(
schema(overflow_pg),
only(Postgres),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ mod bigint {
}

// "Using a BigInt field" should "work"
#[connector_test(exclude(
Postgres("pg.js.wasm", "neon.js.wasm"),
Sqlite("libsql.js.wasm"),
Vitess("planetscale.js.wasm")
))]
#[connector_test()]
async fn using_bigint_field(runner: Runner) -> TestResult<()> {
insta::assert_snapshot!(
run_query!(&runner, r#"mutation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod postgres {
}

//"Postgres native int types" should "work"
#[connector_test(schema(schema_int), only(Postgres), exclude(Postgres("pg.js.wasm", "neon.js.wasm")))]
#[connector_test(schema(schema_int), only(Postgres))]
async fn native_int_types(runner: Runner) -> TestResult<()> {
insta::assert_snapshot!(
run_query!(&runner, r#"mutation {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// This is a temporary workaround for `bigint` serialization issues in the Planetscale driver,
// which will be fixed upstream once https://github.com/planetscale/database-js/pull/159 is published.
// This only impacts Rust tests concerning `driver-adapters`.

type Stringable = { toString: () => string }
type Value = null | undefined | number | boolean | string | Array<Value> | Date | Stringable

export function format(query: string, values: Value[] | Record<string, Value>): string {
return Array.isArray(values) ? replacePosition(query, values) : replaceNamed(query, values)
}

function replacePosition(query: string, values: Value[]): string {
let index = 0
return query.replace(/\?/g, (match) => {
return index < values.length ? sanitize(values[index++]) : match
})
}

function replaceNamed(query: string, values: Record<string, Value>): string {
return query.replace(/:(\w+)/g, (match, name) => {
return hasOwn(values, name) ? sanitize(values[name]) : match
})
}

function hasOwn(obj: unknown, name: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, name)
}

function sanitize(value: Value): string {
if (value == null) {
return 'null'
}

if (['number', 'bigint'].includes(typeof value)) {
return String(value)
}

if (typeof value === 'boolean') {
return value ? 'true' : 'false'
}

if (typeof value === 'string') {
return quote(value)
}

if (Array.isArray(value)) {
return value.map(sanitize).join(', ')
}

if (value instanceof Date) {
return quote(value.toISOString().slice(0, -1))
}

return quote(value.toString())
}

function quote(text: string): string {
return `'${escape(text)}'`
}

const re = /[\0\b\n\r\t\x1a\\"']/g

function escape(text: string): string {
return text.replace(re, replacement)
}

function replacement(text: string): string {
switch (text) {
case '"':
return '\\"'
case "'":
return "\\'"
case '\n':
return '\\n'
case '\r':
return '\\r'
case '\t':
return '\\t'
case '\\':
return '\\\\'
case '\0':
return '\\0'
case '\b':
return '\\b'
case '\x1a':
return '\\Z'
default:
return ''
}
}
5 changes: 4 additions & 1 deletion query-engine/driver-adapters/executor/src/testd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { PrismaLibSQL } from '@prisma/adapter-libsql'
// planetscale dependencies
import { Client as PlanetscaleClient } from '@planetscale/database'
import { PrismaPlanetScale } from '@prisma/adapter-planetscale'
import { format as formatPlanetScaleQuery } from './planetscale/sanitize'



Expand Down Expand Up @@ -292,11 +293,13 @@ async function planetscaleAdapter(url: string): Promise<DriverAdapter> {
throw new Error("DRIVER_ADAPTER_CONFIG is not defined or empty, but its required for planetscale adapter.");
}


const client = new PlanetscaleClient({
// preserving path name so proxy url would look like real DB url
url: copyPathName(url, proxyUrl),
fetch,

// TODO: remove once https://github.com/planetscale/database-js/pull/159 is published upstream.
format: formatPlanetScaleQuery,
})

return new PrismaPlanetScale(client)
Expand Down
11 changes: 7 additions & 4 deletions query-engine/driver-adapters/src/wasm/to_js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ use serde::Serialize;
use serde_wasm_bindgen::Serializer;
use wasm_bindgen::{JsError, JsValue};

// `serialize_missing_as_null` is required to make sure that "empty" values (e.g., `None` and `()`)
// are serialized as `null` and not `undefined`.
// This is due to certain drivers (e.g., LibSQL) not supporting `undefined` values.
static DEFAULT_SERIALIZER: Serializer = Serializer::new().serialize_missing_as_null(true);
// - `serialize_missing_as_null` is required to make sure that "empty" values (e.g., `None` and `()`)
// are serialized as `null` and not `undefined`.
// This is due to certain drivers (e.g., LibSQL) not supporting `undefined` values.
// - `serialize_large_number_types_as_bigints` is required to allow reading bigints from Prisma Client.
static DEFAULT_SERIALIZER: Serializer = Serializer::new()
.serialize_large_number_types_as_bigints(true)
.serialize_missing_as_null(true);

pub(crate) trait ToJsValue: Sized {
fn to_js_value(&self) -> Result<JsValue, JsValue>;
Expand Down