Giter Site home page Giter Site logo

electric-sql / pglite Goto Github PK

View Code? Open in Web Editor NEW
4.5K 40.0 73.0 2.42 MB

Lightweight Postgres packaged as WASM into a TypeScript library for the browser, Node.js, Bun and Deno

Home Page: https://electric-sql.com

License: Apache License 2.0

Makefile 3.21% TypeScript 67.65% JavaScript 24.87% HTML 4.27%

pglite's Introduction

ElectricSQL logo

PGlite - the WASM build of Postgres from ElectricSQL.
Build reactive, realtime, local-first apps directly on Postgres.

License - Apache 2.0 Status - Alpha Chat - Discord

PGlite - Postgres in WASM

PGlite

PGlite is a WASM Postgres build packaged into a TypeScript client library that enables you to run Postgres in the browser, Node.js and Bun, with no need to install any other dependencies. It is only 2.6mb gzipped.

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> { rows: [ { message: "Hello world" } ] }

It can be used as an ephemeral in-memory database, or with persistence either to the file system (Node/Bun) or indexedDB (Browser).

Unlike previous "Postgres in the browser" projects, PGlite does not use a Linux virtual machine - it is simply Postgres in WASM.

It is being developed at ElectricSQL in collaboration with Neon. We will continue to build on this experiment with the aim of creating a fully capable lightweight WASM Postgres with support for extensions such as pgvector.

Whats new in V0.1

Version 0.1 (up from 0.0.2) includes significant changes to the Postgres build - it's about 1/3 smaller at 2.6mb gzipped, and up to 2-3 times faster. We have also found a way to statically compile Postgres extensions into the build - the first of these is pl/pgsql with more coming soon.

Key changes in this release are:

We have also published some benchmarks in comparison to a WASM SQLite build, and both native Postgres and SQLite. While PGlite is currently a little slower than WASM SQLite we have plans for further optimisations, including OPFS support and removing some the the Emscripten options that can add overhead.

Browser

It can be installed and imported using your usual package manager:

import { PGlite } from "@electric-sql/pglite";

or using a CDN such as JSDeliver:

import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js";

Then for an in-memory Postgres:

const db = new PGlite()
await db.query("select 'Hello world' as message;")
// -> { rows: [ { message: "Hello world" } ] }

or to persist the database to indexedDB:

const db = new PGlite("idb://my-pgdata");

Node/Bun

Install into your project:

npm install @electric-sql/pglite

To use the in-memory Postgres:

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> { rows: [ { message: "Hello world" } ] }

or to persist to the filesystem:

const db = new PGlite("./path/to/pgdata");

Deno

To use the in-memory Postgres, create a file server.ts:

import { PGlite } from "npm:@electric-sql/pglite";

Deno.serve(async (_request: Request) => {
  const db = new PGlite();
  const query = await db.query("select 'Hello world' as message;");

  return new Response(JSON.stringify(query));
});

Then run the file with deno run --allow-net --allow-read server.ts.

API Reference

Main Constructor:

new PGlite(dataDir: string, options: PGliteOptions)

A new pglite instance is created using the new PGlite() constructor.

dataDir

Path to the directory to store the Postgres database. You can provide a url scheme for various storage backends:

  • file:// or unprefixed: File system storage, available in Node and Bun.
  • idb://: IndexedDB storage, available in the browser.
  • memory://: In-memory ephemeral storage, available in all platforms.
options:
  • debug: 1-5 - the Postgres debug level. Logs are sent to the console.
  • relaxedDurability: boolean - under relaxed durability mode PGlite will not wait for flushes to storage to complete when using the indexedDB file system.

Methods:

.query<T>(query: string, params?: any[], options?: QueryOptions): Promise<Results<T>>

Execute a single statement, optionally with parameters.

Uses the extended query Postgres wire protocol.

Returns single result object.

Example:
await pg.query(
  'INSERT INTO test (name) VALUES ($1);',
  [ 'test' ]
);
// { affectedRows: 1 },
QueryOptions:

The query and exec methods take an optional options objects with the following parameters:

  • 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 id to parser function. For convenance the pglite package exports a const for most common Postgres types:

    import { types } from "@electric-sql/pglite";
    await pg.query(`
      SELECT * FROM test WHERE name = $1;
    `, ["test"], {
      rowMode: "array",
      parsers: {
        [types.TEXT]: (value) => value.toUpperCase(),
      }
    });

.exec(query: string, options?: QueryOptions): Promise<Array<Results>>

Execute one or more statements. (note that parameters are not supported)

This is useful for applying database migrations, or running multi-statement sql that doesn't use parameters.

Uses the simple query Postgres wire protocol.

Returns array of result objects, one for each statement.

Example:
await pg.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id SERIAL PRIMARY KEY,
    name TEXT
  );
  INSERT INTO test (name) VALUES ('test');
  SELECT * FROM test;
`);
// [
//   { affectedRows: 0 },
//   { affectedRows: 1 },
//   {
//     rows: [
//       { id: 1, name: 'test' }
//     ]
//     affectedRows: 0,
//     fields: [
//       { name: 'id', dataTypeID: '23' },
//       { name: 'name', dataTypeID: '25' },
//     ]
//   }
// ]

.transaction<T>(callback: (tx: Transaction) => Promise<T>)

To start an interactive transaction pass a callback to the transaction method. It is passed a Transaction object which can be used to perform operations within the transaction.

Transaction objects:
  • tx.query<T>(query: string, params?: any[], options?: QueryOptions): Promise<Results<T>> The same as the main .query method.
  • tx.exec(query: string, options?: QueryOptions): Promise<Array<Results>> The same as the main .exec method.
  • tx.rollback() Rollback and close the current transaction.
Example:
await pg.transaction(async (tx) => {
  await tx.query(
    'INSERT INTO test (name) VALUES ('$1');',
    [ 'test' ]
  );
  return await ts.query('SELECT * FROM test;');
});

.close(): Promise<void>

Close the database, ensuring it is shut down cleanly.

Properties:

  • .ready boolean (read only): Whether the database is ready to accept queries.
  • .closed boolean (read only): Whether the database is closed and no longer accepting queries.
  • .waitReady Promise: Promise that resolves when the database is ready to use. Note that queries will wait for this if called before the database has fully initialised, and so it's not necessary to wait for it explicitly.

Results Objects:

Result objects have the following properties:

  • rows: Row<T>[] - The rows retuned by the query
  • affectedRows?: number - Count of the rows affected by the query. Note this is not the count of rows returned, it is the number or rows in the database changed by the query.
  • fields: { name: string; dataTypeID: number }[] - Field name and Postgres data type ID for each field returned.

Row Objects:

Rows objects are a key / value mapping for each row returned by the query.

The .query<T>() method can take a TypeScript type describing the expected shape of the returned rows. (Note: this is not validated at run time, the result only cast to the provided type)

Web Workers:

It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. To aid in this we provide a PGliteWorker with the same API as the core PGlite but it runs Postgres in a dedicated Web Worker. To use, import from the /worker export:

import { PGliteWorker } from "@electric-sql/pglite/worker";

const pg = new PGliteWorker('idb://my-database');
await pg.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id SERIAL PRIMARY KEY,
    name TEXT
  );
`);

Work in progress: We plan to expand this API to allow sharing of the worker PGlite across browser tabs.

Extensions

PGlite supports the pl/pgsql procedural language extension, this is included and enabled by default.

In future we plan to support additional extensions, see the roadmap.

ORM support.

How it works

PostgreSQL typically operates using a process forking model; whenever a client initiates a connection, a new process is forked to manage that connection. However, programs compiled with Emscripten - a C to WebAssembly (WASM) compiler - cannot fork new processes, and operates strictly in a single-process mode. As a result, PostgreSQL cannot be directly compiled to WASM for conventional operation.

Fortunately, PostgreSQL includes a "single user mode" primarily intended for command-line usage during bootstrapping and recovery procedures. Building upon this capability, PGlite introduces a input/output pathway that facilitates interaction with PostgreSQL when it is compiled to WASM within a JavaScript environment.

Limitations

  • PGlite is single user/connection.

Roadmap

PGlite is Alpha and under active development, the current roadmap is:

  • CI builds #19
  • Support Postgres extensions, starting with:
  • OPFS support in browser #9
  • Muti-tab support in browser #32
  • Syncing via ElectricSQL with a Postgres server electric/#1058

Repository Structure

The PGlite project is split into two parts:

  • /packages/pglite The TypeScript package for PGlite
  • /postgres (git submodule) A fork of Postgres with changes to enable compiling to WASM: /electric-sql/postgres-wasm

Please use the issues in this main repository for filing issues related to either part of PGlite. Changes that affect both the TypeScript package and the Postgres source should be filed as two pull requests - one for each repository, and they should reference each other.

Building

There are a couple of prerequisites:

To build, checkout the repo, then:

git submodule update --init
cd ./pglite/packages/pglite
emsdk install 3.1.56
emsdk activate 3.1.56
pnpm install
pnpm build

Acknowledgments

PGlite builds on the work of Stas Kelvich of Neon in this Postgres fork.

License

PGlite is dual-licensed under the terms of theย Apache License 2.0ย and theย PostgreSQL License, you can choose which you prefer.

Changes to theย Postgres sourceย are licensed under the PostgreSQL License.

pglite's People

Contributors

burggraf avatar d3lm avatar epelc avatar jfraziz avatar kamilogorek avatar kiwicopple avatar lucianu avatar msfstef avatar pmooney-socraticworks avatar samwillis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pglite's Issues

extension: pl_pgsql

postgres-wasm has a full support for plpgsql language extension, however for some reason it's not correctly loaded when instantiating PGlite.

Code:

import { PGlite } from '@electric-sql/pglite'
const db = new PGlite()
await db.query('CREATE TRUSTED LANGUAGE plpgsql')

Output:

error: could not open extension control file "/usr/local/pgsql/share/extension/plpgsql.control": No such file or directory

Browser compatibility

Testing the compatibility across browsers (copying code from test.html)

Test: https://kiwicopple.github.io/postgres.run/compatibility.html
Source: https://github.com/kiwicopple/postgres.run/blob/main/compatibility.html

  • Chrome โœ…
  • Arc โœ…
  • Safari โœ…
  • Firefox โŒ: TypeError: window.indexedDB.databases is not a function
  • Mullvad โŒ: TypeError: window.indexedDB.databases is not a function
  • Edge ยฏ_(ใƒ„)_/ยฏ

Offending line:

const dbExists = (await window.indexedDB.databases())

Relevant links

Validate that dbdata dir is of the correct PG version when starting.

I have a data directory that I initialized with initdb. When I try to run the example connecting to that data directory, the Promise returned by db.query(...) never resolves. Here's the code:

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite("./pgdata");

await db.waitReady;

const result = await db.query("select 'Hello world' as message;");

console.log(result);

await db.close();

EDIT: I just realized that this is most likely because there is a difference in the version of postgres that created the dbs:

// created by installed initdb command on my machine
PostgreSQL 16.0 on aarch64-apple-darwin21.6.0, compiled by Apple clang version 14.0.0 (clang-1400.0.29.102), 64-bit

// created by pglite
PostgreSQL 15devel on aarch64-apple-darwin22.6.0, compiled by emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.25 (febd44b21ecaca86e2cb2a25ef3ed4a0a2076365), 32-bit

It would be nice to include an error for this type of thing. Are there plans for this to be resolved?

Missing has_parameter_privilege function

Currently, pglite is using a fork of v15 (as per #57), however despite has_parameter_privilege function being added in said v15, it's not possible to call it and it will throw with function has_parameter_privilege does not exist.

I checked a few other privilege functions, and they work as expected, eg.

SELECT pg_has_role('pg_read_all_settings', 'MEMBER');
SELECT has_column_privilege('test', 'name', 'SELECT');
SELECT has_schema_privilege('public', 'USAGE');

ref: https://pgpedia.info/h/has_parameter_privilege.html
ref: https://pgpedia.info/p/privilege.html

Attempting to change column type throws error

Attempting to change column type throws error

expected one dependency record for TOAST table, found 4

Reproduction:

import { PGlite } from '@electric-sql/pglite'

const db = new PGlite()

await db.exec(`-- CreateEnum

-- CreateTable
CREATE TABLE "my_table" (
    "id" SERIAL NOT NULL,
    "1" VARCHAR(9) NOT NULL,
    "2" VARCHAR(12) NOT NULL,
    "3" VARCHAR(36) NOT NULL,
    "4" BOOLEAN,
    "5" VARCHAR(10),
    "6" VARCHAR(10),
    "7" VARCHAR(7) NOT NULL,
    "8" VARCHAR(7),
    "9" VARCHAR(12),
    "10" VARCHAR(7),
    "11" INTEGER,
    "12" VARCHAR(45) NOT NULL,
    "13" DATE NOT NULL,
    "14" VARCHAR(8) NOT NULL,
    "15" VARCHAR(16) NOT NULL,
    "16" VARCHAR(8) NOT NULL,
    "17" DATE NOT NULL,
    "18" VARCHAR(14),
    "19" VARCHAR(12),
    "20" VARCHAR(8),
    "21" VARCHAR(80),
    "22" VARCHAR(10) NOT NULL,
    "23" VARCHAR(8) NOT NULL,
    "24" VARCHAR(8) NOT NULL,
    "25" VARCHAR(8) NOT NULL,
    "26" VARCHAR(8) NOT NULL,
    "27" VARCHAR(10),
    "28" VARCHAR(15),
    "29" DATE,
    "30" VARCHAR(9),
    "31" VARCHAR(8),
    "32" DECIMAL(5,2) NOT NULL,
    "33" VARCHAR(8),
    "34" VARCHAR(8),
    "35" VARCHAR(20) NOT NULL,
    "36" VARCHAR(8),
    "37" VARCHAR(10) NOT NULL,
    "38" VARCHAR(45),
    "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT "my_table_pkey" PRIMARY KEY ("id")
);
`)


await db.exec(`
-- AlterTable
ALTER TABLE "my_table" ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMPTZ(3);
`)


console.log(await db.exec(`SELECT * from "my_table";`))

The following actions individually will cause the code to execute successfully:

  1. Removing column 38
  2. Reducing the varchar size of column 38 to 10
  3. Removing the ALTER COLUMN statement
  4. Setting the created_at column as type TIMESTAMPTZ(3) in the CREATE TABLE statement

Edit: formatting

initdb failing to complete in Bun

When attempting to launch PGLite with the specified file path, I encounter an error. However, if PGLite is run in memory, the error does not occur. OS: macOS, Runtime: bun.

Error:

2024-04-02 13:02:37.490 GMT [42] NOTICE:  Init WASM shared memory
2024-04-02 13:02:37.492 GMT [42] LOG:  database system was interrupted; last known up at 2024-04-02 13:02:37 GMT
2024-04-02 13:02:37.493 GMT [42] DEBUG:  checkpoint record is at 0/1000028
2024-04-02 13:02:37.493 GMT [42] DEBUG:  redo record is at 0/1000028; shutdown true
2024-04-02 13:02:37.493 GMT [42] DEBUG:  next transaction ID: 3; next OID: 12000
2024-04-02 13:02:37.493 GMT [42] DEBUG:  next MultiXactId: 1; next MultiXactOffset: 0
2024-04-02 13:02:37.493 GMT [42] DEBUG:  oldest unfrozen transaction ID: 3, in database 1
2024-04-02 13:02:37.493 GMT [42] DEBUG:  oldest MultiXactId: 1, in database 1
2024-04-02 13:02:37.493 GMT [42] DEBUG:  commit timestamp Xid oldest/newest: 0/0
2024-04-02 13:02:37.493 GMT [42] DEBUG:  transaction ID wrap limit is 2147483650, limited by database with OID 1
2024-04-02 13:02:37.494 GMT [42] DEBUG:  MultiXactId wrap limit is 2147483648, limited by database with OID 1
2024-04-02 13:02:37.494 GMT [42] DEBUG:  starting up replication slots
2024-04-02 13:02:37.494 GMT [42] LOG:  database system was not properly shut down; automatic recovery in progress
2024-04-02 13:02:37.494 GMT [42] DEBUG:  resetting unlogged relations: cleanup 1 init 0
2024-04-02 13:02:37.496 GMT [42] LOG:  invalid record length at 0/10000A0: wanted 24, got 0
2024-04-02 13:02:37.496 GMT [42] LOG:  redo is not required
2024-04-02 13:02:37.496 GMT [42] DEBUG:  resetting unlogged relations: cleanup 0 init 1
2024-04-02 13:02:37.498 GMT [42] DEBUG:  MultiXactId wrap limit is 2147483648, limited by database with OID 1
2024-04-02 13:02:37.498 GMT [42] DEBUG:  MultiXact member stop limit is now 4294914944 based on MultiXact 1
2024-04-02 13:02:37.499 GMT [42] FATAL:  could not open file "global/pg_filenode.map": No such file or directory
2024-04-02 13:02:37.499 GMT [42] NOTICE:  shutting down
2024-04-02 13:02:37.500 GMT [42] NOTICE:  database system is shut down

Code:

import { PGlite } from "@electric-sql/pglite";

export const db = new PGlite("file://pgdata", { debug: 1 });
const res = await db.query("select 'Hello world' as message;");
console.log(res);

Investigate removing EMULATE_FUNCTION_POINTER_CASTS

Emscripten option EMULATE_FUNCTION_POINTER_CASTS is currently required to compile as Postgres makes usage of function pointer casts. However this is a potential performance penalty. We should investigate a route to removing the need for this option.

https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html

Use EMULATE_FUNCTION_POINTER_CASTS. When you build with -sEMULATE_FUNCTION_POINTER_CASTS, Emscripten emits code to emulate function pointer casts at runtime, adding extra arguments/dropping them/changing their type/adding or dropping a return type/etc. This can add significant runtime overhead, so it is not recommended, but is be worth trying.

`affectedRows` not being returned in UPDATE `.query` method

I'm on version 1.1 of pglite, and am finding that my update query is not returning the affectedRows field as mentioned here

Given a query

UPDATE "todo" SET "name"='foo' WHERE "id" = 1;

My result is:

{ rows: [], fields: [] }

I'd expect my query to return

{ rows: [], fields: [], affectedRows: 1 }

I see affectedRows is being returned in SELECT queries, FWIW.

Calling postgres.wasm from Python

Hi

I was wondering if it's possible to make use of postgres.wasm from Python using something like wastime PyPi package.

This could provide a handy way of supporting educational activities using postgres without having to ship a PostgreSQL server.

Is there any documentation on the postgres.wasm package, its exports, etc?

`import("module")).default` is undefined

Hello ๐Ÿ‘‹,

I'm experimenting with pglite for an upcoming adapter for @snaplet/seed.

I'm not exactly sure why, but default is undefined when it hits this line:

require = (await import("module")).default.createRequire(import.meta.url);

My setup is quite complicated as I define the PGlite() connection within a TypeScript configuration file loaded through https://github.com/unjs/c12 ๐Ÿ˜…

But everything seems to work if I remove default and directly consume createRequire like this:

require = (await import("module")).createRequire(import.meta.url);

Do you think it would be possible to have a fallback so it works in both cases:

const module = await import("module")
const createRequire = module.default?.createRequire ?? module.createRequire
require = createRequire(import.meta.url);

Calling `execProtocol` when PGlite is not ready

Hey, it's me again. ๐Ÿ˜„

execProtocol is not checking if the database is ready, which triggers this error when called externally to send wire protocol messages:

TypeError: undefined is not an object (evaluating 'this.emp._malloc')

I see 3 solutions:

  • Calling await this.#checkReady(); in the execProtocol function
  • Keeping execProtocol as is for internal use within PGlite but exposing a version with await this.#checkReady(); applied
  • Making PGlite extends EventEmitter and emits a ready event when ready (my favorite solution)

In the end, I think execProtocol is exposed for low-level implementations such as pglite-server so I wouldn't do extra work in execProtocol.

Attempting to `SET TIME ZONE` throws error

Attempting to SET TIME ZONE 'UTC'; will throw an error:

invalid value for parameter "TimeZone": "UTC"

This is due to missing timezone data in the Emscripten filesystem:

SELECT * FROM pg_timezone_names();
-- could not open directory "/usr/share/zoneinfo": No such file or directory

OOB when select() with over about 1k response?

Hi. Is there some kind of limit in place with how much data can be returned with a select? Or a way to increase it, if there is one? I feel like 1KiB is a really low limit in this context so I'm unsure if it's my setup or not.


  • "@electric-sql/pglite": "0.1.5"
  • node v18.20.2, and also v20.12.2
  • pnpm (in case it matters)
  • linux (headless ubuntu 22)

I'm using Drizzle to talk to pglite in a pretty vanilla way:

const client = new PGlite();
const db = drizzlePglite(client);

I've got a table like

pgTable('stuff', { id: integer('id'), data: text('data') });

I can insert data into it, I can query this data, but if my payload exceeds what appears to be close to 1KiB then it'll throw an OOB.

RuntimeError: memory access out of bounds
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:4451557
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:4561193
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:4561497
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:1953804
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:4803833
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:1954597
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:81379
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:4791378
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:1678555
 โฏ null.<anonymous> wasm:/wasm/018421ca:1:3094049

I would get this with db.select().from(stuff). Through raw queries I was able to roughly pinpoint the size to 1KiB.

This way:

await sql.raw('SELECT SUBSTRING(data, 0, 1013) AS a FROM stuff')

would be fine while this:

await sql.raw('SELECT SUBSTRING(data, 0, 1014) AS a FROM stuff')

would throw the above OOB error.

When adding id to the columns to fetch, the size drops and 1013 also throws the OOB error, leading me to think there's some kind of limit to the whole data set. So adding some overhead, it feels to me like that is awkwardly close to a precise 1KiB.

I've tried using debug output, up to level 5, but it's not showing me any relevant information when this error is thrown. Just shows the select etc.

So my question is whether I'm indeed hitting a limit somehow and how I might resolve this.

"memory access out of bounds" when creating tables

I want to preface this bug report by saying I really appreciate this project! I've been waiting for this to exist for use in unit tests for years :)

When creating tables with certain schema, a RuntimeError: memory access out of bounds error is thrown.

One way to reproduce is creating a table with at least 20 columns.

So for instance this throws the error in my testing:

create table example_table
  (
    column_1 text,
    column_2 text,
    column_3 text,
    column_4 text,
    column_5 text,
    column_6 text,
    column_7 text,
    column_8 text,
    column_9 text,
    column_10 text,
    column_11 text,
    column_12 text,
    column_13 text,
    column_14 text,
    column_15 text,
    column_16 text,
    column_17 text,
    column_18 text,
    column_19 text,
    column_20 text
  );

but this does not:

create table example_table
  (
    column_1 text,
    column_2 text,
    column_3 text,
    column_4 text,
    column_5 text,
    column_6 text,
    column_7 text,
    column_8 text,
    column_9 text,
    column_10 text,
    column_11 text,
    column_12 text,
    column_13 text,
    column_14 text,
    column_15 text,
    column_16 text,
    column_17 text,
    column_18 text,
    column_19 text
  );

But this isn't limited to column count, I've seen it happen for all kinds of different setups. An especially confusing one is this case, where the only difference is whitespace:

Working:

create table files
  (
    id uuid not null primary key,
    name text not null,
    file_type text not null,
    size integer not null,
    demographics jsonb not null,
    person_id uuid,
    facility_id uuid
  );

Error:

    create table files
      (
        id               uuid                                               not null primary key,
        name             text                                               not null,
        file_type        text                                               not null,
        size             integer                                            not null,
        demographics     jsonb                                              not null,
        person_id        uuid,
        facility_id      uuid,
      );

But the whitespace alone isn't a problem. The same whitespace, but only for the first 4 columns works:

    create table files
      (
        id               uuid                                               not null primary key,
        name             text                                               not null,
        file_type        text                                               not null,
        size             integer                                            not null
      );

and it's not because we removed the only jsonb column.
the same as the previous, but with the first column jsonb also works:

    create table files
      (
        id               jsonb                                               not null primary key,
        name             text                                               not null,
        file_type        text                                               not null,
        size             integer                                            not null
      );

I can't discern a pattern here.

Any help would be appreciated, I'd love to start using this for unit tests but this issue is preventing me from being able to use it.

RuntimeError with NodeJS on Windows

Thanks for this project.

I tried the basic kindof hello word you provide with NodeJS:

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> [ { message: "Hello world" } ]

tried in Node REPL, with no success using import(). So made a app.mjs file with same content. When I run node app.mjs, I got this runtime error :

> node .\app.mjs
file:///C:/temp/pglite-101/node_modules/@electric-sql/pglite/dist/chunk-Z6L7HPDY.js:95
          throw ex;
          ^

RuntimeError: Aborted(Error: ENOENT: no such file or directory, open 'C:\C:\temp\pglite-101\node_modules\@electric-sql\pglite\dist\postgres.wasm'). Build with -sASSERTIONS for more info.
    at abort (file:///C:/temp/pglite-101/node_modules/@electric-sql/pglite/dist/chunk-Z6L7HPDY.js:384:15)
    at getBinary (file:///C:/temp/pglite-101/node_modules/@electric-sql/pglite/dist/chunk-Z6L7HPDY.js:414:9)
    at file:///C:/temp/pglite-101/node_modules/@electric-sql/pglite/dist/chunk-Z6L7HPDY.js:439:16

Node.js v21.6.1

Note this strange C:\C:\.

Question: How to access it from a GUI?

Hi folks, I'm playing around with the project and I'm using local files to store the data. Is there any GUI that would support pqlite? I would like to use it to debug things. Thank you!

License

Hello, thank you for releasing this. Would you mind adding a license? Thank you!

Parameterized queries

In order to support parameterized queries we need to use the extended postgres wire protocol to pass queries.

Add lock to indexeddb vfs so that the database can only be opened once.

Each time I try to use the IndexedDb I get this error.

this.program: could not access the server configuration file "/pgdata/postgresql.conf": No such file or directory

image

This is the code that trigger it:

"use client";

import { PGlite } from "@electric-sql/pglite";
import { useEffect, useState } from "react";

export default function PgLiteComponent() {
  const [data, setData] = useState<any>("PENDING");

  useEffect(() => {
    (async () => {
      const client = new PGlite("idb://local", { debug: 1 });
      const result = await client.exec("SELECT NOW()");
      setData(result);
    })();
  }, []);

  return (
    <div>
      <h1>PGLite</h1>
      <pre>{JSON.stringify(data ?? "", null, 2)}</pre>
    </div>
  );
}

The app halts, the error cannot be caught, so is not possible to do anything.

PGlite bindings for other runtimes?

I'm trying to run and access PGLite through a golang program via wasmer-go. However it fails with the error regardgin env not instantiated. I assume that there is no support for other runtimes (#40)

Is there a way I could contribute to adding support for more runtimes?

$ wazero run postgres.wasm                                                                                                                                                                                         
error instantiating wasm binary: module[env] not instantiated

$ wasmtime postgres.wasm                                                                                                                                                                                           
Error: failed to run main module `postgres.wasm`

Caused by:
    0: failed to instantiate "postgres.wasm"
    1: unknown import: `env::invoke_ii` has not been defined

extension: Foreign data wrappers

I know it might be early to ask but it'd be great to have support for it and use pglite as a wrapper for running SQL over other resources securely

Attempting to refresh a materialised view throws error

Attempting to refresh a materialised view throws error:

Uncaught error: expected one dependency record for TOAST table, found 4

Reproduction:

import { PGlite } from "../dist/index.js";

console.log("Starting...");
const pg = new PGlite();

// Test materialized view
await pg.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id SERIAL PRIMARY KEY,
    name TEXT
  );
`);
await pg.exec(`
  INSERT INTO test (name) VALUES ('test');
`);
await pg.exec(`
  CREATE MATERIALIZED VIEW test_view AS
  SELECT * FROM test;
`);
const res = await pg.exec(`
  SELECT * FROM test_view;
`);
console.log(res);
await pg.exec(`
  INSERT INTO test (name) VALUES ('test2');
`);
const res2 = await pg.exec(`
  SELECT * FROM test_view;
`);
console.log(res2);
await pg.exec(`
  REFRESH MATERIALIZED VIEW test_view;
`);
const res3 = await pg.exec(`
  SELECT * FROM test_view;
`);
console.log(res3);

Generic Extension Strategy

Lets say I have a few extensions that the pglite repo doesn't know about, will the expected workflow be to fork pglite, add those extensions as part of the build process, then build it myself?

I have a bunch of extensions I'd like to see used in my pglite db, but doesn't seem like those should be part of the default offering.

In-browser PostgreSQL playground

Hey folks, thanks for this great project! I've created an embeddable in-browser playground on top of it. The project is also open source, so if you need a playground somewhere (maybe in the docs), feel free to use it. Just FYI.

Support for opfs in browsers via wasmfs

For browsers it would be better to add support for OPFS so that we are not bounded by the memory limits and also faster than indexdb. Infact there is no serialization of data to be done of we use the filesystem that is available. OPFS is supported in all major browsers. emscripten also supports wasmfs which can be used with OPFS

relation "information_schema.tables" does not exist

I tried running this SQL query:

select * from information_schema.tables; 

And got the following error:

relation "information_schema.tables" does not exist at character 15

The following query also errored:

select * from information_schema; 
relation "information_schema does not exist at character 15

Shouldn't this exist in all pg databases? (related to #8 probably)

RuntimeError: memory access out of bounds

I've found that PGlite works fine for simple queries, but fails with the "memory access out of bounds" for more complex queries (when using the browser version).

For example, this code:

import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js";

const sql = `
create table department (
    dep_id varchar(10) primary key,
    name varchar(50),
    manager_id integer
);

create table employee (
    emp_id integer primary key,
    first_name varchar(50),
    last_name varchar(50),
    birth_dt date,
    hire_dt date,
    dep_id varchar(10),
    city varchar(50),
    salary integer,

    constraint department_fk foreign key (dep_id)
    references department(dep_id)
);

insert into department
(dep_id, name, manager_id)
values
('hr', 'HR', 12),
('it', 'IT', 24),
('sales', 'Sales', 31);

insert into employee
(emp_id, first_name, last_name, birth_dt, hire_dt, dep_id, city, salary)
values
(11, 'Diane', 'Wood', '1973-07-21', '2018-03-15', 'hr', 'London', 70),
(12, 'Bob', 'Yakamoto', '1985-11-09', '2019-07-22', 'hr', 'London', 78),
(21, 'Emma', 'Verde', '1978-03-05', '2020-11-08', 'it', 'London', 84),
(22, 'Grace', 'Tanner', '1994-10-17', '2021-02-27', 'it', 'Berlin', 90),
(23, 'Henry', 'Sivers', '1982-01-29', '2022-06-13', 'it', 'London', 104),
(24, 'Irene', 'Romen', '1971-05-14', '2018-09-04', 'it', 'Berlin', 104),
(25, 'Frank', 'Utrecht', '1989-08-23', '2019-12-19', 'it', 'Berlin', 120),
(31, 'Cindy', 'Xerst', '1976-12-06', '2020-05-26', 'sales', 'Berlin', 96),
(32, 'Dave', 'Walsh', '1999-04-08', '2021-08-03', 'sales', 'London', 96),
(33, 'Alice', 'Zakas', '1987-02-19', '2023-01-29', 'sales', 'Berlin', 100);

select 'ok' as message;
`

const db = new PGlite();
const res = await db.query(sql);
console.log(res);

Leads to the following error:

Uncaught (in promise) RuntimeError: memory access out of bounds
    at postgres.wasm:0x59d76
    at postgres.wasm:0x725c6
    at postgres.wasm:0x46ab2
    at postgres.wasm:0x9c8b61
    at postgres.wasm:0x9ddb89
    at ret.<computed> (postgres.js:9:128289)
    at invoke_vii (postgres.js:9:145948)
    at postgres.wasm:0x4ff2e8
    at postgres.wasm:0x882051
    at ret.<computed> (postgres.js:9:128289)

JSFiddle to reproduce the problem: https://jsfiddle.net/zgc5jy7o/

Benchmarks

I would love to see how this compares to SQLite in WAL mode, and if running Postgres in a single thread will actually beat it.

Aborted in WebContainer

I am trying to run this in WebContainer and now that #1 was merged and released I am seeing the following error:

RuntimeError: Aborted(). Build with -sASSERTIONS for more info.
    at abort (file:///home/projects/node-oqtitn/node_modules/@electric-sql/pglite/dist/chunk-RM7SEAOI.js:412:15)
    at _abort (file:///home/projects/node-oqtitn/node_modules/@electric-sql/pglite/dist/chunk-RM7SEAOI.js:5626:7)
    at wasm://wasm/02d347d2:wasm-function[123]:0x77e6
    at wasm://wasm/02d347d2:wasm-function[6809]:0x6ac11a
    at wasm://wasm/02d347d2:wasm-function[4366]:0x437a87
    at wasm://wasm/02d347d2:wasm-function[14076]:0x9a2381
    at wasm://wasm/02d347d2:wasm-function[15480]:0x9de758

But only if there's a db folder. If you remove the folder then it works fine.

Any idea? I think it'd be nice to have a debug build as well where -sASSERTIONS is enabled. This could maybe be published under a specific debug tag?

Weird inconsistency with real postgres

I have a library that generates SQL that looks like this

with "cte1" as (
  (select arg_0 as "a", arg_1 as "b" from (values (1, 'Foo'), (2, 'Bar'), (3, 'Baz')) as vals(arg_0, arg_1))
)
select "a", "b" from "cte1";

In real postgres it returns correctly

psql (16.2 (Debian 16.2-1.pgdg120+2))
Type "help" for help.

postgres=# with "cte1" as (
  (select arg_0 as "a", arg_1 as "b" from (values (1, 'Foo'), (2, 'Bar'), (3, 'Baz')) as vals(arg_0, arg_1))
)
select "a", "b" from "cte1";
 a |  b
---+-----
 1 | Foo
 2 | Bar
 3 | Baz
(3 rows)

In pglite I get weird answers

import { PGlite } from "@electric-sql/pglite"

const db = new PGlite();

const res = await db.query(`
  with "cte1" as (
    (select arg_0 as "a", arg_1 as "b" from (values (1, 'Foo'), (2, 'Bar'), (3, 'Baz')) as vals(arg_0, arg_1))
  )
  select "a", "b" from "cte1";
`);
console.log(res);
> bun foo.mjs
[
  {
    "": "Foo",
  }, {
    "": "Bar",
  }, {
    "": "Baz",
  }
]
> deno run -A foo.mjs
[ { "": "Foo" }, { "": "Bar" }, { "": "Baz" } ]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.