Skip to content

Commit

Permalink
feat: add custom parser and serializer to PGlite options (#397)
Browse files Browse the repository at this point in the history
* feat: add custom parser and serializer to PGlite options

* chore: changeset

* chore: docs

* chore: fix formatting

* Fix styles

---------

Co-authored-by: Sam Willis <[email protected]>
  • Loading branch information
typedarray and samwillis authored Nov 12, 2024
1 parent 6e536de commit c442c88
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-peas-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': patch
---

Added custom parser and serializer options to `PGliteOptions`. Added custom serializer option to `QueryOptions`.
55 changes: 40 additions & 15 deletions docs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`<br />
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` <br />
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` <br />
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`

Expand Down Expand Up @@ -114,25 +139,25 @@ The `query` and `exec` methods take an optional `options` objects with the follo
- `rowMode: "object" | "array"` <br />
The returned row object type, either an object of `fieldName: value` mappings or an array of positional values. Defaults to `"object"`.
- `parsers: ParserOptions` <br />
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` <br />
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` <br />
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).

Expand Down
2 changes: 1 addition & 1 deletion packages/pglite/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions packages/pglite/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down Expand Up @@ -75,6 +80,8 @@ export interface PGliteOptions {
initialMemory?: number
wasmModule?: WebAssembly.Module
fsBundle?: Blob | File
parsers?: ParserOptions
serializers?: SerializerOptions
}

export type PGliteInterface = {
Expand Down
8 changes: 8 additions & 0 deletions packages/pglite/src/pglite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions packages/pglite/tests/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
Expand Down

0 comments on commit c442c88

Please sign in to comment.