Skip to content

Commit

Permalink
feat(driver-adapters): fix bigint handling on creation and filter (#4648
Browse files Browse the repository at this point in the history
)

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

* chore(driver-adapters): comment out native_other_types, like it was before

* chore(driver-adapters): excluded PlanetScale from test

* tmp/fix: avoid adding quotes to bigint numbers in PlanetScale, waiting for upstream PR to be merged

* chore: update comments, remove PlanetScale exclusion for "using_bigint_field" test
  • Loading branch information
jkomyno authored Jan 22, 2024
1 parent 240fa90 commit 53daea5
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 11 deletions.
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
90 changes: 90 additions & 0 deletions query-engine/driver-adapters/executor/src/planetscale/sanitize.ts
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

0 comments on commit 53daea5

Please sign in to comment.