使用Deno尝试PGlite:本地环境实现

初稿撰写于2024年11月29日。

みなさん、久しぶりっ!

今年这边家庭发生了不太好的事情,我自己没心思研究技术,也就放置了博客很久。现在大概从阴霾走出来了,就写一些最近的尝试成果吧。

前段时间获知electric-sql/pglite这个项目用WASM运行PostgreSQL数据库,并且(初步?)支持了Deno,那我就想试试看在本地环境下体验究竟如何。

至于为什么是本地环境而非web app,原因是希望尽快尝试,而且持久化到本地的数据库可以通过Deno CLI方便地操作。

环境配置

没什么特别的,主要就是现在使用的是Deno 2:

> deno --version
deno 2.1.1 (stable, release, x86_64-pc-windows-msvc)
v8 13.0.245.12-rusty
typescript 5.6.2

项目结构

在PowerShell 7.4.6用deno新建工程,并添加所需package:

> deno init C:\prj\pg_test
> Set-Location C:\prj\pg_test
> deno add npm:@electric-sql/pglite
Add npm:@electric-sql/[email protected]
> tree . /F
フォルダー パスの一覧:  ボリューム Windows
ボリューム シリアル番号は D8C4-41B5 です
C:\PRJ\PG_TEST
│  deno.json
│  deno.lock
│  main.ts
│  main_test.ts
│
└─.vscode
        settings.json

deno.json内容如下:

{
  "tasks": {
    "dev": "deno run --watch main.ts"
  },
  "imports": {
    "@electric-sql/pglite": "npm:@electric-sql/pglite@^0.2.14",
    "@std/assert": "jsr:@std/assert@1"
  }
}

本体实现

main.ts内容如下:

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

const PG_DATA_PATH = './pgdata.tar';

export async function init() {
  const pg = new PGlite();

  await pg.exec(`
    CREATE TABLE IF NOT EXISTS cashbook (
      id SERIAL PRIMARY KEY
      , act_on TIMESTAMP WITH TIME ZONE
      , item TEXT
      , cash BIGINT
      , note TEXT
    );
  `);

  const file = await pg.dumpDataDir('none'); // should not compress
  await Deno.writeFile(PG_DATA_PATH, file.stream());

  await pg.close();
}

// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
  console.log('Checking DB...');
  try {
    await Deno.lstat(PG_DATA_PATH);
    console.log('Exists!');
  } catch (err) {
    if (!(err instanceof Deno.errors.NotFound)) {
      throw err;
    }
    console.log('Not exists!');
    console.log('Initializing...');
    await init();
    console.log('Done!');
  }

  const arr = await Deno.readFile(PG_DATA_PATH);
  const pg = new PGlite({
    loadDataDir: new Blob([arr])
  });
  const tables = await pg.query(`
    SELECT *
    FROM information_schema.tables
    WHERE table_schema = 'public';
  `);
  console.log(tables.rows);

  await pg.close();
}

大体流程如下:

  1. 优先寻找特定的已持久化数据库,若没有则新建
  2. 载入数据库
  3. 简单检查数据库

由于现在PGlite对Deno的支持不甚完善,现在使用最低限度的实现方式,即:

  1. 先在内存中建立数据库,最后持久化到本地。
  2. 持久化时,为避免默认的gzip压缩与解压带来额外的复杂度,故直接持久化为tar文件。

建表时的字段名参考自

初次执行(以✅起始的输出即权限要求通过时的提示。下同):

> deno run .\main.ts
Checking DB...
✅ Granted all read access.
Not exists!
Initializing...
✅ Granted all write access.
Done!
[
  {
    table_catalog: "template1",
    table_schema: "public",
    table_name: "cashbook",
    table_type: "BASE TABLE",
    self_referencing_column_name: null,
    reference_generation: null,
    user_defined_type_catalog: null,
    user_defined_type_schema: null,
    user_defined_type_name: null,
    is_insertable_into: "YES",
    is_typed: "NO",
    commit_action: null
  }
]

现在数据库就被持久化好了:

> Get-ChildItem .\pgdata.tar

    Directory: C:\prj\pg_test

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2024/11/30     0:30       28258304 pgdata.tar

再次执行:

> deno run .\main.ts
Checking DB...
✅ Granted all read access.
Exists!
[
  {
    table_catalog: "template1",
    table_schema: "public",
    table_name: "cashbook",
    table_type: "BASE TABLE",
    self_referencing_column_name: null,
    reference_generation: null,
    user_defined_type_catalog: null,
    user_defined_type_schema: null,
    user_defined_type_name: null,
    is_insertable_into: "YES",
    is_typed: "NO",
    commit_action: null
  }
]

对我来说现在这样就可以了。之后的事情就可以自己用Deno CLI折腾了。

后记

时过境迁,用WASM能完成的事颇有些超乎想象。感叹技术进步的同时,自己却不得不持续学习。不要停下来!

那么就到这里,以后看看还能玩什么再跟大家分享。

2025-03-02

之前上网冲浪找到了这么一个实现:Deno + Pglite + Drizzle で依存の少ないDBアプリを作る

至于Drizzle,它是个ORM。我理应也要找个ORM用来着,以免维护困难。这位仁兄提供了更完整的思路,值得学习。

InSb

InSb

只是跟工作和生活相关的记录