外观
SQLite(本地数据库)
SQLite 插件为 Web 页面和 Go 提供本地数据库,包括原生 UI 应用。它随附 预编译的 SQLite 库,并通过 purego 加载它们,因此应用无需 cgo 或 C 编译 器即可构建。它支持 macOS、Linux 和 Windows 的 amd64 与 arm64。
使用页面插件
仅当 Web 前端使用 @mygo-plugins/sqlite 时才调用 mygo.Use(sqlite.Plugin)。它注册那个 TypeScript 客户端所调用的 Go 服务。Go 和原生 UI 应用直接使用 sqlite.Open,如下文 所示。
go
import (
"github.com/egoist/mygo"
"github.com/egoist/mygo/plugins/sqlite"
)
func main() {
mygo.Use(sqlite.Plugin)
// 照常创建窗口并调用 App.Run。
}用 bun add @mygo-plugins/sqlite 安装前端包,然后:
ts
import { open } from "@mygo-plugins/sqlite";
const db = await open("app.db");
await db.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT)");
const result = await db.execute("INSERT INTO notes(title) VALUES (?)", ["Hello SQLite"]);
const { columns, rows } = await db.query("SELECT id, title FROM notes WHERE id = ?", [result.lastInsertId]);
// columns: ["id", "title"], rows: [[1, "Hello SQLite"]]
await db.close();open 接受一个数据库文件名,例如 app.db,或 :memory: 表示一个私有 的内存数据库。文件位于 mygo.PathUserData 中。绝对路径、目录穿越、 符号链接的数据库文件和 Windows 设备名都会被拒绝。要选定目录和锁等待 时间:
go
mygo.Use(sqlite.New(sqlite.Options{
Directory: "/path/to/databases",
BusyTimeout: 2 * time.Second,
}))Directory 默认为 mygo.PathUserData;可写数据库的目录不存在时会被 创建。BusyTimeout 默认为五秒;负时长会禁用等待。 open("app.db", { readOnly: true }) 以只读方式打开一个已存在的文件, 不允许写入。
查询与值
execute(sql, params?) 只接受恰好一条语句,消费任何 RETURNING 行,并 返回 { changes, lastInsertId }。changes 统计直接受影响的行数; lastInsertId 是该连接上 SQLite 最后插入的 rowid,在 TypeScript 中始终 是 bigint。
query(sql, params?) 返回 { columns, rows }。每一行是按列顺序排列的 单元格数组,保留重复的列名。它支持 SELECT、pragma 以及带 RETURNING 的语句。结果会被缓冲;对于大表请使用 LIMIT 和分页。
参数按它们的 SQLite 索引绑定:接受 ?、?NNN、:name、$name 和 @name,值以数组提供。所有参数索引都必须有值;缺少或多余的参数都会 失败。切勿把应用值插入(interpolate)到 SQL 字符串中。多条语句会在第一 条执行之前被拒绝;允许结尾的分号和注释。
| SQLite 存储类 | TypeScript | Go |
|---|---|---|
| NULL | null | nil |
| INTEGER | 安全范围内为 number,否则为 bigint | int64 |
| REAL | number | float64 |
| TEXT | string | string |
| BLOB | Uint8Array | []byte |
布尔参数会变成整数 0 或 1。当整数超出 JavaScript 的安全范围时绑定 bigint;超出 SQLite 的有符号 64 位范围的值会失败。非有限数值参数会 失败。空 BLOB 和空文本与 NULL 保持区分。文本和 BLOB 参数由 SQLite 复制, 结果 BLOB 在语句关闭前复制。
事务与生命周期
ts
await db.transaction([
{ sql: "INSERT INTO notes(title) VALUES (?)", params: ["first"] },
{ sql: "INSERT INTO notes(title) VALUES (?)", params: ["second"] },
]);transaction 在 COMMIT 之后为每条语句返回一个执行结果。它使用 BEGIN IMMEDIATE,在整个批次期间持有连接,并在任何错误或取消时回滚。 空批次返回 []。事务控制语句(BEGIN、COMMIT、ROLLBACK、 SAVEPOINT、RELEASE)在用户 SQL 中会被拒绝;请使用批次 API。 ATTACH、DETACH 和 VACUUM INTO 会被拒绝,以免 SQL 打开配置目录之外 的文件。普通的 VACUUM 受支持。无法加载 SQLite 扩展。
每个连接属于调用它的页面。其他窗口不能使用它的句柄。页面导航和窗口 关闭会取消调用并关闭该页面的连接;应用退出会关闭所有剩余的连接。 close() 是幂等的,并会取消进行中的操作。一个连接上的并发操作会串行化; 等待有依赖关系的调用以给它们排序。
取消由 C 中的进度处理器和忙处理器以及一个原子取消令牌处理。SQLite 不 保留任何 Go 指针或 purego 回调。Go 调用方可以用每个操作的 context 取消 它。
从 Go 或原生 UI 使用
无需 mygo.Use(sqlite.Plugin) 注册。直接打开连接,用完关闭:
go
ctx := context.Background()
db, err := sqlite.Open(ctx, "app.db", sqlite.OpenOptions{})
if err != nil { return err }
defer db.Close()
_, err = db.Execute(ctx, "CREATE TABLE IF NOT EXISTS notes (title TEXT)")
if err != nil { return err }
_, err = db.Transaction(ctx, []sqlite.Statement{
{SQL: "INSERT INTO notes VALUES (?)", Args: []any{"first"}},
{SQL: "INSERT INTO notes VALUES (?)", Args: []any{"second"}},
})
if err != nil { return err }
rows, err := db.Query(ctx, "SELECT title FROM notes")
// rows.Columns 和 rows.Values 包含复制后的结果。Go 的 Open 直接接受一个路径;它的父目录必须存在。连接可以从任何 goroutine 安全使用。它们的生命周期属于 Go 调用方,调用方必须调用 Close。sqlite.Error 暴露主要和扩展的 SQLite 错误码;已关闭的连接 返回 sqlite.ErrClosed,被取消的操作返回其 context 错误。
构建与发布
预编译库发布于 sqlite-3.53.4-1。 CI、go test 和未打包的程序会下载匹配的库并校验其 SHA-256。测试和构建 应用无需安装 Zig。
更新 SQLite 或其 C shim 时,使用 Zig 0.16.0 运行 go generate ./plugins/sqlite,从固定的、经校验和验证的 SQLite 3.53.4 源码构建全部六个原生库。它会写入清单、被忽略的发布资产和本地 CLI 缓存。 在 plugins/sqlite 中,go run ./internal/libbuild -target host 只为主 机构建,以便在新的发布资产发布之前进行本地开发。
mygo build 和 mygo dev 会发现 mygo-plugin.json,并把正确的库放入 应用的资源中,在 macOS 上与应用一起签名。macOS 通用构建会合并两种架构。 打包的应用不需要运行时下载。MYGO_SQLITE_LIBRARY 为开发指定一个自定义 构建。