From c442c882e60489e5442fca082be903156ec4d292 Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Tue, 12 Nov 2024 04:50:43 -0500 Subject: [PATCH] feat: add custom parser and serializer to PGlite options (#397) * feat: add custom parser and serializer to PGlite options * chore: changeset * chore: docs * chore: fix formatting * Fix styles --------- Co-authored-by: Sam Willis --- .changeset/violet-peas-check.md | 5 +++ docs/docs/api.md | 55 +++++++++++++++++++++-------- packages/pglite/src/base.ts | 2 +- packages/pglite/src/interface.ts | 7 ++++ packages/pglite/src/pglite.ts | 8 +++++ packages/pglite/tests/basic.test.js | 37 +++++++++++++++++++ 6 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 .changeset/violet-peas-check.md diff --git a/.changeset/violet-peas-check.md b/.changeset/violet-peas-check.md new file mode 100644 index 00000000..5f1a0234 --- /dev/null +++ b/.changeset/violet-peas-check.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/pglite': patch +--- + +Added custom parser and serializer options to `PGliteOptions`. Added custom serializer option to `QueryOptions`. diff --git a/docs/docs/api.md b/docs/docs/api.md index 8365e358..e0f49b0b 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -62,6 +62,31 @@ Path to the directory for storing the Postgres database. You can provide a URI s A precompiled WASM module to use instead of downloading the default version, or when using a bundler that either can, or requires, loading the WASM module with a ESM import. - `fsBundle?: Blob | File`
A filesystem bundle to use instead of downloading the default version. This is useful if in a restricted environment such as an edge worker. +- `parsers: ParserOptions`
+ An object of type `{ [pgType: number]: (value: string) => any; }` mapping Postgres data type IDs to parser functions. For convenience, the `pglite` package exports a constant for most common Postgres types. + + ```ts + import { PGlite, types } from '@electric-sql/pglite' + + const pg = await PGlite.create({ + parsers: { + [types.TEXT]: (value) => value.toUpperCase(), + }, + }) + ``` + +- `serializers: SerializerOptions`
+ An object of type `{ [pgType: number]: (value: any) => string; }` mapping Postgres data type IDs to serializer functions. + + ```ts + import { PGlite, types } from '@electric-sql/pglite' + + const pg = await PGlite.create({ + serializers: { + [types.NUMERIC]: (value) => value.toString(), + }, + }) + ``` #### `options.extensions` @@ -114,25 +139,25 @@ The `query` and `exec` methods take an optional `options` objects with the follo - `rowMode: "object" | "array"`
The returned row object type, either an object of `fieldName: value` mappings or an array of positional values. Defaults to `"object"`. - `parsers: ParserOptions`
- An object of type `{[[pgType: number]: (value: string) => any;]}` mapping Postgres data type IDs to parser functions. - For convenience, the `pglite` package exports a constant for most common Postgres types: - + An object mapping Postgres data type IDs to parser functions. This option overrides any parsers set at the instance level. ```ts import { types } from '@electric-sql/pglite' - await pg.query( - ` - SELECT * FROM test WHERE name = $1; - `, - ['test'], - { - rowMode: 'array', - parsers: { - [types.TEXT]: (value) => value.toUpperCase(), - }, + await pg.query(`SELECT * FROM test WHERE name = $1;`, ['test'], { + parsers: { + [types.TEXT]: (value) => value.toUpperCase(), }, - ) + }) + ``` +- `serializers: SerializerOptions`
+ An object mapping Postgres data type IDs to serializer functions. This option overrides any serializers set at the instance level. + ```ts + import { types } from '@electric-sql/pglite' + await pg.query(`INSERT INTO test (numeric) VALUES ($1);`, [100n], { + serializers: { + [types.NUMERIC]: (value: number | bigint) => value.toString(), + }, + }) ``` - - `blob: Blob | File`
Attach a `Blob` or `File` object to the query that can used with a `COPY FROM` command by using the virtual `/dev/blob` device, see [importing and exporting](#dev-blob). diff --git a/packages/pglite/src/base.ts b/packages/pglite/src/base.ts index 82724346..c7081d4f 100644 --- a/packages/pglite/src/base.ts +++ b/packages/pglite/src/base.ts @@ -226,7 +226,7 @@ export abstract class BasePGlite if (param === null || param === undefined) { return null } - const serialize = this.serializers[oid] + const serialize = options?.serializers?.[oid] ?? this.serializers[oid] if (serialize) { return serialize(param) } else { diff --git a/packages/pglite/src/interface.ts b/packages/pglite/src/interface.ts index f4c52cf4..d44232e9 100644 --- a/packages/pglite/src/interface.ts +++ b/packages/pglite/src/interface.ts @@ -15,9 +15,14 @@ export interface ParserOptions { [pgType: number]: (value: string) => any } +export interface SerializerOptions { + [pgType: number]: (value: any) => string +} + export interface QueryOptions { rowMode?: RowMode parsers?: ParserOptions + serializers?: SerializerOptions blob?: Blob | File onNotice?: (notice: NoticeMessage) => void paramTypes?: number[] @@ -75,6 +80,8 @@ export interface PGliteOptions { initialMemory?: number wasmModule?: WebAssembly.Module fsBundle?: Blob | File + parsers?: ParserOptions + serializers?: SerializerOptions } export type PGliteInterface = { diff --git a/packages/pglite/src/pglite.ts b/packages/pglite/src/pglite.ts index dd6e62f2..6413b4fa 100644 --- a/packages/pglite/src/pglite.ts +++ b/packages/pglite/src/pglite.ts @@ -93,6 +93,14 @@ export class PGlite } this.dataDir = options.dataDir + // Override default parsers and serializers if requested + if (options.parsers !== undefined) { + this.parsers = { ...this.parsers, ...options.parsers } + } + if (options.serializers !== undefined) { + this.serializers = { ...this.serializers, ...options.serializers } + } + // Enable debug logging if requested if (options?.debug !== undefined) { this.debug = options.debug diff --git a/packages/pglite/tests/basic.test.js b/packages/pglite/tests/basic.test.js index 0ff669b6..3ff58020 100644 --- a/packages/pglite/tests/basic.test.js +++ b/packages/pglite/tests/basic.test.js @@ -271,6 +271,43 @@ await testEsmAndCjs(async (importType) => { ) }) + it('custom parser and serializer', async () => { + const db = new PGlite({ + serializers: { 1700: (x) => x.toString() }, + parsers: { 1700: (x) => BigInt(x) }, + }) + await db.query(` + CREATE TABLE IF NOT EXISTS test ( + id SERIAL PRIMARY KEY, + numeric NUMERIC + ); + `) + await db.query('INSERT INTO test (numeric) VALUES ($1);', [100n]) + const res = await db.query(` + SELECT * FROM test; + `) + + expect(res).toEqual({ + rows: [ + { + id: 1, + numeric: 100n, + }, + ], + fields: [ + { + name: 'id', + dataTypeID: 23, + }, + { + name: 'numeric', + dataTypeID: 1700, + }, + ], + affectedRows: 0, + }) + }) + it('params', async () => { const db = new PGlite() await db.query(`