海外开发者实践分享:用 MoonBit 开发 SQLC 插件(其一)

近日,日本开发者 4245ryomt 在 Zenn 上发布了一系列围绕 MoonBit 与 WebAssembly 插件实践 的技术文章,分享了他使用 MoonBit 编写 sqlc Wasm 插件 的完整尝试过程。

文章通过可运行代码,介绍了插件请求处理、文件生成以及 Wasm 执行等关键环节,展示了 MoonBit 在工具链与 Wasm 场景下的实际开发体验,适合作为相关方向的实践参考。

📄 原文链接:https://zenn.dev/4245ryomt/articles/9680434dc60c0c

↓以下为系列第一篇的原文翻译

本文内容

本文介绍了使用 MoonBit 来制作 sqlc 插件的过程。

本文并不会完成一个能够在 MoonBit 中"很好地执行 SQL "的库。

在这个过程中,会用到 在 MoonBit 中使用 protoc,以及 执行由 MoonBit 构建的 wasm。

写着写着发现内容比预期要长,因此拆分成多篇文章。

第 1 篇 :将 sqlc 插件的输入用 MoonBit 进行解析。

第 2 篇 :将 MoonBit 输出的 wasm **作为 sqlc 的插件来使用。

本文中使用的 moon 命令版本如下:

shell 复制代码
$ moon version                                                                                                    
> moon 0.1.20251202 (1a59800 2025-12-02)

首先,使用 moon new 命令创建一个项目。

shell 复制代码
$ moon new try_moonbit_sqlc_plugin_dev
$ cd try_moonbit_sqlc_plugin_dev

什么是 sqlc 插件

sqlc 是一个以 SQL 作为输入,并生成能够 "很好地" 执行这些 SQL 的 源代码 的工具。[1](#1)

这里所说的"很好地",指的是:

在静态类型语言中,执行 SQL 所需的参数,以及执行后得到的结果,都被指定类型。[2](#2)

sqlc 的插件本身,就是实现这种"很好地输出"的逻辑。

插件的输入和输出由 protobuf 定义,可以通过标准输入和标准输出,与任意编程语言编写的程序进行通信。

插件有两种执行方式:

一种是作为普通命令,通过标准输入/输出运行的进程类型;

另一种是以 wasm 的形式执行。

MoonBit 的一大优点就是可以输出 wasm,因此这次就尝试用 MoonBit 来实现。

用于定义插件输入输出的 protobuf 文件位于 sqlc 仓库中(Lines 1 to 132 in main)。[3](#3)

将其作为子模块添加进来会比较方便。

ProtoBuf 复制代码
$ git submodule add git@github.com:sqlc-dev/sqlc.git sqlc

MoonBit 与 protoc

这里就不展开介绍 Protocol Buffers[4](#4) 是什么了。

从 proto 文件中定义的 schema 出发,用于在各种语言中处理 protobuf 格式消息的工具已经有很多了。

令人惊喜的是,MoonBit 已经存在用于处理 protobuf 消息的工具[5](#5)

不过,这次要使用的 proto 文件[6](#6)------codegen.proto,会触发 protoc-gen-mbt 中的一个缺陷。

只要修改 proto 文件本身即可规避这个问题,所以这里直接进行修改。

Shell 复制代码
$ mkdir proto
$ cp -r sqlc/protos/plugin/codegen.proto proto/codegen.proto

params 字段名修改为与 json_name 一致的 parameters

json 复制代码
message Query {
  string text = 1 [json_name = "text"];
  string name = 2 [json_name = "name"];
  string cmd = 3 [json_name = "cmd"];
  repeated Column columns = 4 [json_name = "columns"];
  repeated Parameter params = 5 [json_name = "parameters"];
  repeated string comments = 6 [json_name = "comments"];
  string filename = 7 [json_name = "filename"];
  Identifier insert_into_table = 8 [json_name = "insert_into_table"];
}

protoc-gen-mbt 是作为 protoc 命令的插件实现的。

因此只要有 protoc 命令即可,不过我个人平时更习惯使用 buf,所以这里选择使用 buf[7](#7)


从 buf 调用 protoc-gen-mbt

首先,从源码构建 protoc-gen-mbt

shell 复制代码
$ git clone git@github.com:moonbitlang/protoc-gen-mbt.git tmp
$ cd tmp
$ moon build -C cli
$ cp cli/target/native/release/build/protoc-gen-mbt.exe ../protoc-gen-mbt.exe
$ cd ..
$ rm -rf tmp

接下来,在两个 buf 的配置文件中指定 proto 文件的位置,以及刚刚构建好的 protoc-gen-mbt 可执行文件。

buf.yaml

yaml 复制代码
version: v2
modules:
  - path: proto

buf.gen.yaml

yaml 复制代码
version: v2
inputs:
  - directory: proto
plugins:
  - local: ["./protoc-gen-mbt.exe"]
    out: .
    opt:
      # https://github.com/moonbitlang/protoc-gen-mbt?tab=readme-ov-file#arguments
      - project_name=sqlc_plugin
      - json=false
      - async=false
      - username=yourname

使用 buf generate 命令,根据 proto 文件生成 MoonBit 的源代码。

Shell 复制代码
$ buf generate

生成的代码会作为一个 MoonBit 项目输出。

shell 复制代码
$ tree sqlc_plugin
> sqlc_plugin
> ├── moon.mod.json
> └── src
>   └── plugin
>        ├── moon.pkg.json
>        └── top.mbt

看看 sqlc 插件的输入内容

在制作 sqlc 插件之前,先来看一下它的输入到底是什么样的。

因此,这里先尝试将标准输入原样输出到标准错误输出

首先,创建一个 shell 可执行文件。

sqlc 的插件在异常退出时,会把标准错误输出的内容记录到日志中。

相反,如果是正常退出,标准错误输出的内容是不会出现在日志里的,这一点需要注意。

dump_stdin_to_stderr.sh

shell 复制代码
#!/bin/sh

cat - >&2
exit 1
shell 复制代码
$ chmod +x dump_stdin_to_stderr.sh

为了使用 sqlc 进行代码生成,创建一些示例 SQL 文件。

shell 复制代码
$ mkdir sqlite
$ touch sqlite/schema.sql
$ touch sqlite/query.sql

sqlite /schema.sql

yaml 复制代码
CREATE TABLE authors (
          id   integer    PRIMARY KEY AUTOINCREMENT,
          name text   NOT NULL,
          bio  text
);

sqlite /query.sql

yaml 复制代码
/* name: get_author :one */
SELECT * FROM authors
WHERE id = ? LIMIT 1;

/* name: list_authors :many */
SELECT * FROM authors
ORDER BY name;

/* name: create_author :execresult */
INSERT INTO authors (
  name, bio
) VALUES (
  ?, ? 
);

/* name: delete_author :exec */
DELETE FROM authors
WHERE id = ?;

创建 sqlc 的配置文件,指定 SQL 文件和插件(shell 可执行文件)。

sqlc.yaml

yaml 复制代码
version: '2'
plugins:
- name: dump_stdin_to_stderr
  process:
    cmd: ./dump_stdin_to_stderr.sh
sql:
- name: sqlite
  schema: sqlite/schema.sql
  queries: sqlite/query.sql
  engine: sqlite
  database:
    uri: file:authors?mode=memory&cache=shared
  codegen:
  - out: generated
    plugin: dump_stdin_to_stderr

执行 sqlc generate 后,dump_stdin_to_stderr.sh 输出的内容会原样显示出来。

shell 复制代码
$ sqlc generate                                                                                 main ✭ ✱
> # package dump_stdin_to_stderr
> error generating code: process: error running command 
> p
> 2sqlitesqlite/schema.sql"sqlite/query.sqlb>
>         generateddump_stdin_to_stderr*
> ump_stdin_to_stderr.sh�main"�main�
>         authors'
> id0���������R   authorsb        integer&
> name0���������R authorsbtext#
> bio0���������R  authorsbtext�

虽然直接看并不能理解内容,但可以确认:

确实有 protobuf 格式的二进制数据输入了插件。


在 MoonBit 中处理 sqlc 插件的输入

终于到了这里,可以开始编写 MoonBit 代码了。

首先,将必要的模块添加到项目中。

shell 复制代码
$ moon add moonbitlang/async
$ moon add moonbitlang/protobuf

通过 buf 生成的、定义了 sqlc 插件输入输出的模块,需要直接编辑 moon.mod.json 来进行引用[8](#8)

json 复制代码
  "deps": {
      "ryota0624/sqlc_plugin": {
          "version": "0.1.0",
          "path": "./sqlc_plugin"
          },
        }

作为运行时入口点的包,其 moon.pkg.json 如下:

cmd /main/moon.pkg.json

yaml 复制代码
{
  "is-main": true,
  "import": [
    {
      "path": "yourname/try_moonbit_sqlc_plugin_dev",
      "alias": "lib"
    },
    "moonbitlang/async/stdio",
    "moonbitlang/async",
    "moonbitlang/async/io",
    "yourname/sqlc_plugin/plugin",
    "moonbitlang/protobuf"
  ]
}

由于 GenerateRequest 消息中包含了查询名称,因此将这些名称输出到标准错误。

最后,通过调用 panic 让进程异常退出。

cmd /main/main.mbt

rust 复制代码
fn main {
  @async.run_async_main(main_async)
  panic()
}

async fn main_async() -> Unit {
  let input = @stdio.stdin.read_all()
  let request = @lib.parse_generate_request(input.binary())
  for query in request.queries {
    @stdio.stderr.write(query.name + "\n")
  }
}

fn parse_generate_request(data : Bytes) -> @plugin.GenerateRequest raise {
  @protobuf.BytesReader::from_bytes(data) |> @protobuf.Read::read
}

使用 moon build 并指定 native 作为目标进行构建后,会在

target/native/release/build/cmd/main/main.exe 生成可执行文件。

shell 复制代码
$ moon build --target native

sqlc.yaml 中指定该可执行文件。

yaml 复制代码
version: '2'
plugins:
- name: moonbit
  process:
    cmd: target/native/release/build/cmd/main/main.exe
sql:
- name: sqlite
  schema: sqlite/schema.sql
  queries: sqlite/query.sql
  engine: sqlite
  database:
    uri: file:authors?mode=memory&cache=shared
  codegen:
  - out: generated
    plugin: moonbit

执行 sqlc generate 后,就会输出查询名称。

yaml 复制代码
& sqlc generate
> # package moonbit
> error generating code: process: error running command get_author
> list_authors
> create_author
> delete_author

  1. https://sqlc.dev/ ↩︎

  2. https://docs.sqlc.dev/en/latest/guides/plugins.html ↩︎

  3. https://github.com/sqlc-dev/sqlc/blob/main/protos/plugin/codegen.proto ↩︎

  4. https://protobuf.dev/ ↩︎

  5. https://github.com/moonbitlang/protoc-gen-mbt ↩︎

  6. https://github.com/moonbitlang/protoc-gen-mbt ↩︎

  7. https://buf.build/product/cli ↩︎

  8. https://github.com/ryota0624/try_moonbit_sqlc_plugin/blob/main/moon.mod.json#L4-L8 ↩︎

相关推荐
小高求学之路8 小时前
eo4j 图数据库备忘单
数据库·neo4j
2301_796512528 小时前
React Native鸿蒙跨平台开发如何使用MongoDB或Firebase作为后端数据库来存储车辆信息、保养记录和预约信息
数据库·mongodb·react native
电商API_180079052478 小时前
主流电商平台 API 横向测评:淘宝、京东、拼多多接口能力与对接成本分析
大数据·开发语言·网络·数据库·人工智能
Chasing__Dreams10 小时前
kafka--基础知识点--6.4--LSO
数据库·分布式·kafka
极限实验室16 小时前
APM(一):Skywalking 与 Easyearch 集成
数据库·云原生
饕餮争锋16 小时前
SQL条件中WHERE 1=1 的功能
数据库·sql
玄斎17 小时前
MySQL 单表操作通关指南:建库 / 建表 / 插入 / 增删改查
运维·服务器·数据库·学习·程序人生·mysql·oracle
编织幻境的妖17 小时前
SQL查询连续登录用户方法详解
java·数据库·sql
编程小Y17 小时前
MySQL 与 MCP 集成全解析(核心原理 + 实战步骤 + 应用场景)
数据库·mysql·adb