TinySQL
TinySQL 是一个用 Go 编写的可嵌入 SQL 数据库引擎。它专为 学习数据库内部机制、本地工具、测试、浏览器/WASM 应用程序以及 需要强大 SQL 层但无需运行数据库服务器的单进程服务而设计。
TinySQL 并非 PostgreSQL、MySQL 或集群 生产数据库的直接替代品。在将其用于 关键工作负载之前,请查阅局限性。
目录
快速入门
需要 Go 1.26.5+。
go get github.com/SimonWaldherr/tinySQL@latest
创建一个内存数据库,执行 SQL,并读取行:
package main
import (
"context"
"fmt"
tinysql "github.com/SimonWaldherr/tinySQL"
)
func main() {
ctx := context.Background()
db := tinysql.NewDB()
for _, query := range []string{
`CREATE TABLE users (id INT PRIMARY KEY, name TEXT)`,
`INSERT INTO users VALUES (1, 'Ada'), (2, 'Grace')`,
} {
stmt, err := tinysql.ParseSQL(query)
if err != nil {
panic(err)
}
if _, err := tinysql.Execute(ctx, db, "default", stmt); err != nil {
panic(err)
}
}
stmt, err := tinysql.ParseSQL(`SELECT id, name FROM users ORDER BY id`)
if err != nil {
panic(err)
}
result, err := tinysql.Execute(ctx, db, "default", stmt)
if err != nil {
panic(err)
}
for _, row := range result.Rows {
id, _ := tinysql.GetVal(row, "id")
name, _ := tinysql.GetVal(row, "name")
fmt.Println(id, name)
}
}
对于已经使用 database/sql 的应用,请使用
github.com/SimonWaldherr/tinySQL/driver。
它能做什么
SQL 引擎
SELECT、INSERT、UPDATE、DELETE、RETURNING、CTE、子查询、 连接、分组、窗口函数、PIVOT、EXPLAIN,以及常见的 兼容 SQLite 的PRAGMA。- 视图、物化视图、触发器、表值函数、存储 过程、作业、多租户以及系统目录视图。
- 约束,包括单列主键、唯一键、外键、
NOT NULL以及字面量默认值。 - 二级索引、通过
ANALYZE实现的精确持久化统计信息,以及 规划器选择性估计。 - JSON、YAML、文本、正则表达式、数学、日期、URL、哈希、位图、全文、向量、 混合搜索以及 RAG 辅助函数。
嵌入与交付
- 纯 Go、进程内 API 以及
database/sql驱动程序。 - 内存、WAL、磁盘、JSON、索引、混合以及分页索引存储模式。
- 浏览器/WASM 构建以及本地优先的 SQL 游乐场。
- CLI、HTTP 服务器、文件查询工具以及健康/生命周期钩子。
- 可选的审计日志、RBAC 以及针对支持的表文件 后端的静态加密。
数据与地图
- CSV、TSV、JSON/NDJSON、XML、YAML、Excel、GeoJSON、TopoJSON、KML、OSM XML、 路由图、Shapefiles 和 MBTiles 导入路径。
- GeoJSON 测量、包含、关系、编辑、清理、区域 (溶解/裁剪)和检查功能,以及空间搜索索引和 用于基于位置的 BI 仪表板的分级统计图分类(等间隔、自然断点、分位数)。
- 从查询结果导出 GeoJSON 和 TopoJSON(
-mode geojson|topojson), 这是 Power BI 形状地图和大多数映射工具首选的格式。 - Web Mercator 瓦片寻址、MBTiles 导入/导出、就地瓦片访问, 以及可选的 XYZ 瓦片端点。
参见 FUNCTIONS.sql 和 example_showcase.sql 以获取更广泛的 SQL 参考。
从 Go 中使用 SQL
ParseSQL + Execute 是基本 API。在编程方式组合
查询时,请使用流畅构建器:
query := tinysql.Select(tinysql.Col("name")).
From("users").
Where(tinysql.Eq(tinysql.Col("active"), tinysql.Val(true))).
OrderBy("name").
Build()
result, err := tinysql.Execute(ctx, db, "default", query)
The builder 支持投影、连接、CTE、排序、限制、表达式,
以及 Exists/NotExists 谓词。参见
ExampleExists 获取可运行的示例。
事务与触发器
Go 驱动程序支持跨语句事务,使用 BEGIN、COMMIT、
和 ROLLBACK(或 BeginTx)。事务可以看到其自身的写入;并发
写入冲突将作为可重试的 ErrTransactionConflict 返回。
行触发器在 BEFORE/AFTER INSERT、UPDATE 和 DELETE 时运行,
包括没有 WHERE 子句的 DELETE。直接的多行变更及其
触发器效果是语句原子的。
import (
"context"
tsqldriver "github.com/SimonWaldherr/tinySQL/driver"
)
db, err := tsqldriver.OpenInMemory("default")
if err != nil { panic(err) }
defer db.Close()
tx, err := db.BeginTx(context.Background(), nil)
if err != nil { panic(err) }
if _, err := tx.Exec(`INSERT INTO users VALUES (3, 'Lin')`); err != nil {
_ = tx.Rollback()
panic(err)
}
if err := tx.Commit(); err != nil { panic(err) }
SAVEPOINT 和嵌套事务尚未实现。
GIS 和 GeoJSON
TinySQL 将几何数据作为普通的 GeoJSON 存储,既可以存储在 TEXT/JSON 列中,
也可以存储在专用的 GEOMETRY 列类型中,该类型在写入时进行验证(裸
数字或 Feature/FeatureCollection 将被拒绝 —— GEOMETRY 列
存储的是 Geometry),并将其规范化为稳定的、字节级一致文本。大多数
几何函数也返回 GeoJSON,因此结果可以直接传递给
后续的 SQL 调用或在地图客户端中绘制。
CREATE TABLE places (name TEXT, geometry GEOMETRY);
INSERT INTO places VALUES
('Berlin', GEO_POINT(13.4050, 52.5200)),
('Munich', GEO_POINT(11.5755, 48.1372));
SELECT GEO_DISTANCE(a.geometry, b.geometry) AS meters,
GEO_BEARING(a.geometry, b.geometry) AS bearing,
GEO_MIDPOINT(a.geometry, b.geometry) AS midpoint
FROM places a JOIN places b
ON a.name = 'Berlin' AND b.name = 'Munich';
GEOMETRY/GEOM 是附加性的——现有的 TEXT/JSON 几何列以及
上述查询保持不变,继续有效。不支持 GEOMETRY(SRID) 风格的参数;
请使用裸关键字。CAST(x AS GEOMETRY) 的验证和规范化方式与列写入相同。
测量与谓词
| 任务 | 函数 | PostGIS 风格别名 |
|---|---|---|
| 创建/读取点 | GEO_POINT, GEO_LON, GEO_LAT | ST_POINT, ST_X, ST_Y |
| 大圆距离 | GEO_DISTANCE | ST_DISTANCE, HAVERSINE |
| 半径检查 | GEO_DWITHIN | ST_DWITHIN |
| 边界框检查 | GEO_WITHIN_BBOX | ST_WITHIN_BBOX |
| 方位角 / 中点 / 目的地 | GEO_BEARING, GEO_MIDPOINT, GEO_DESTINATION | ST_AZIMUTH, ST_MIDPOINT, ST_PROJECT |
| 点在多边形/多重多边形内 | GEO_WITHIN_POLYGON | ST_WITHIN, ST_CONTAINS |
| 多边形面积 / 线长度 | GEO_POLYGON_AREA, GEO_LENGTH | ST_AREA, ST_LENGTH |
| 任意共享点(点/线/多边形,任意组合) | GEO_INTERSECTS | ST_INTERSECTS |
| 无共享点 | GEO_DISJOINT | ST_DISJOINT |
| 相同坐标(与顺序/旋转/绕向无关) | GEO_EQUALS | ST_EQUALS |
| 点周围的圆形缓冲区 | GEO_BUFFER(point, meters[, segments]) | ST_BUFFER |
| 几何体顶点的凸包 | GEO_CONVEX_HULL | ST_CONVEXHULL |
| 作为多边形的边界框 | GEO_ENVELOPE | ST_ENVELOPE |
| 线上某比例处的点 | GEO_LINE_INTERPOLATE(line, fraction) | ST_LINE_INTERPOLATE_POINT |
| 将几何体裁剪到凸边界 | GEO_CLIP(geometry, boundary[, allow_nonconvex]) | ST_CLIP |
距离、方位角、目的地、长度、面积和缓冲区计算在球面上进行;
距离以米为单位,多边形面积以平方米为单位。
原始四元数形式的坐标使用 (lat, lon, lat, lon);GeoJSON
保持为 [lon, lat]。GEO_WITHIN_POLYGON/ST_CONTAINS/GEO_POLYGON_AREA
接受 GeoJSON MultiPolygon 以及 Polygon —— 属于任何
部分即视为属于整体,且面积累加所有部分。
GEO_LINE_INTERPOLATE 按沿线的实际距离进行分割,而非按
顶点数量。GEO_CONVEX_HULL 在普通的经度/纬度空间中计算凸包
(一种标准的平面近似,而非严格的球面凸包)。
GEO_INTERSECTS/GEO_DISJOINT 涵盖点/线/多边形的任意组合,
并尊重多边形孔洞(嵌套在另一个多边形孔洞内的形状
与其不相交)。没有 ST_TOUCHES/ST_CROSSES/ST_OVERLAPS:
严格区分仅边界接触与内部重叠需要完整的
DE-9IM 计算,这超出了范围——一种天真的尝试会在
实际 GIS 数据中频繁遇到的共享边/孔洞情况下
静默地出错。GEO_EQUALS 的范围限于坐标/形状相等性(在任意旋转、反转或 Polygon-与-单部分-MultiPolygon
包裹之后匹配),而非完整的 OGC 点集相等性——两个覆盖相同
区域但顶点化不同的多边形不会被检测为相等。
GEO_CLIP 使用 Sutherland-Hodgman 多边形裁剪,该算法仅保证在凸边界下正确;默认情况下它会验证凸性,否则报错,allow_nonconvex=true 作为显式的尽力而为(best-effort)退出选项。GEO_CLIP 支持 Point/MultiPoint 和 Polygon/MultiPolygon 主体;LineString 裁剪需要不同的算法,目前不支持。
几何编辑与质量
| 任务 | 函数 | PostGIS 风格别名 |
|---|---|---|
| 简化几何体 | GEO_SIMPLIFY(geometry, tolerance[, method]) | ST_SIMPLIFY |
| 检查 bbox / 质心 | GEO_BBOX, GEO_CENTROID | ST_BBOX, ST_CENTROID |
| 平移、缩放、旋转 | GEO_AFFINE | ST_AFFINE |
| Chaikin 平滑 | GEO_SMOOTH | ST_SMOOTH |
| 移除多边形孔洞 | GEO_DROP_HOLES | ST_REMOVE_HOLES |
| 清理重复顶点并闭合环 | GEO_CLEAN | ST_CLEAN |
| 将 x/y 吸附到网格 | GEO_SNAP(geometry, gridSize) | ST_SNAPTOGRID |
| 结构性 GeoJSON 检查 | GEO_IS_VALID | ST_ISVALID |
GEO_SIMPLIFY 接受 Douglas-Peucker(dp,默认值)、
visvalingam-effective 和 visvalingam-weighted。简化、仿射变换、
平滑、清理和吸附操作均在源坐标单位中进行。
GEO_SNAP 和 GEO_CLEAN 会拒绝将线或多边形环
折叠至低于 GeoJSON 最小顶点数的结果。
GEO_IS_VALID 检查受支持的 GeoJSON 结构和顶点要求。它
目前尚不能检测自相交等拓扑问题。没有
R-tree,普通的 WHERE GEO_DWITHIN(...)/GEO_WITHIN_BBOX(...)
谓词不会获得规划器加速——它在普通索引
缩小范围之后过滤行。对于 BI 规模的点表,GEO_SEARCH(下文)提供了
显式的、基于索引的替代方案。
交互式地图演示 可以本地编辑内置或上传的 GeoJSON,显示源数据和结果,调整 参数,检查有效性,并下载结果。
区域操作、空间搜索和分级统计图分类
受 Mapshaper 启发的区域编辑动词和面向 BI 的辅助函数,用于将 原始几何数据转换为基于位置的 KPI 和仪表板:
| 任务 | 函数 |
|---|---|
| 将一组多边形合并为一个(共享边溶解) | GEO_DISSOLVE(geometry[, snap_grid_degrees]) |
| 相同操作,聚合式命名 | GEO_UNION_AGG, ST_UNION |
| 跨越一组的边界框 | GEO_BBOX_AGG(geometry) |
| (可选加权的)跨越一组的质心 | GEO_CENTROID_AGG(geometry[, weight]) |
| 对表格进行索引化的 bbox/半径搜索 | GEO_SEARCH(table, geom_col, 'bbox'|'radius', ...) |
| 等间隔分级统计图分类 | EQUAL_INTERVAL(n) OVER (ORDER BY kpi) |
| 自然断点(Jenks)分级统计图分类 | NATURAL_BREAKS(n) OVER (ORDER BY kpi) |
| 分位数分级统计图分类 | NTILE(n) OVER (ORDER BY kpi)(已存在) |
-- Dissolve adjacent building footprints into one district boundary per region,
-- then bucket a KPI (e.g. building count) into 5 choropleth classes.
SELECT region, GEO_DISSOLVE(footprint) AS boundary, COUNT(*) AS buildings
FROM parcels
GROUP BY region;
SELECT region, buildings,
NATURAL_BREAKS(5) OVER (ORDER BY buildings) AS class
FROM region_stats;
GEO_DISSOLVE/GEO_UNION_AGG/ST_UNION 通过抵消共享的有向边来合并多边形——适用于拓扑干净、顶点对齐的相邻输入(真实的 GIS 边界数据,或本项目自身的 dissolve 输出回传),而非针对重叠但未对齐输入的通用多边形布尔并集。组内的点/线会被拼接为 MultiPoint/
MultiLineString,而不是被溶解。GEO_CENTROID_AGG 的可选权重
与 GEO_CENTROID 自身的面积/长度权重相结合,因此
GEO_CENTROID_AGG(geom, population) 是已经过面积加权的逐行质心的
人口加权质心。
GEO_SEARCH 构建了一个惰性的、按表划分的网格索引(在写入时自动失效),对于 Point 列是精确的;对于多边形/线列,它按质心进行索引,因此如果一个大型形状的边缘——而非其质心——切入查询窗口,则在那里会产生假阴性。ST_INTERSECTS(上文)
仍然是直接测试形状重叠的精确、无索引方式。
分位数分类(NTILE)已经存在;EQUAL_INTERVAL 和
NATURAL_BREAKS 是新增的。NATURAL_BREAKS 的 Jenks 优化在每个分区上是 O(rows ×
classes²)(计算一次,而非每行计算)——对于现实中的分级统计图分区大小(市镇、邮政编码、区)来说是可以接受的,
但未针对数万行进行调优。
Map tiles and MBTiles
对于多吉字节、以读为主的数据集,请参阅
docs/mbtiles-artifacts.md 以了解有界的
dataset.tinysql 导入器、经过验证的工件格式、TMS 读取 API 以及
SQLite 比较流程。
地图演示在 WebAssembly 中通过实时 SQL 获取每个瓦片。其源代码位于
cmd/mbtilesdemo。
Web 地图使用顶部原点 XYZ 行,而 MBTiles 存储底部原点 TMS 行。
在 SQL 边界处使用 TILE_FLIP_Y:
SELECT tile_data FROM tiles
WHERE zoom_level = 14
AND tile_column = TILE_X(13.405, 14)
AND tile_row = TILE_FLIP_Y(TILE_Y(52.520, 14), 14);
其他瓦片辅助函数包括 TILE_ZXY、TILE_BBOX、TILE_LON、TILE_LAT、
TILE_QUADKEY、TILE_FROM_QUADKEY、TILE_PARENT、TILE_CONTAINS 和
TILE_COUNT。在提供常规瓦片表之前,添加一个索引:
CREATE INDEX tile_index ON tiles (zoom_level, tile_column, tile_row);
MBTiles 导入/导出使用可选的 sqliteimport 构建标签:
importer.ImportMBTiles(ctx, db, "default", "tiles", "city.mbtiles",
&importer.ImportOptions{CreateTable: true, BatchSize: 1000})
importer.ExportMBTiles(ctx, db, "default", "out.mbtiles",
&importer.ExportMBTilesOptions{TileRowIsTMS: true})
对于 HTTP 交付,运行 tinysqld -tiles:
GET /tiles/{tileset}/{z}/{x}/{y}.{ext}
GET /tiles/{tileset}.json
GET /tiles/{tileset}/metadata
瓦片路由有意不进行身份验证,因为普通的地图客户端 无法在瓦片请求中附加 bearer token。如果需要访问限制,请在前面 放置一个进行身份验证的代理。请参阅 存储指南 以了解大型、分页索引的瓦片集。
导入、导出和可选构建标签
核心引擎没有 SQLite 或 Shapefile 运行时依赖。仅在需要它们的 构建中启用这些读写路径:
# SQLite files and MBTiles via pure-Go modernc SQLite (also required for
# ModeSQLite — a real .sqlite file as tinySQL's native storage; see the
# storage guide)
go build -tags=sqliteimport ./...
# ESRI Shapefile and Shapefile ZIP imports
go build -tags=shapefile ./...
# Both profiles
go build -tags=sqliteimport,shapefile ./...
没有标签时,相应的导入 API 仍然可用,并返回功能禁用的错误。对于已加载到 TinySQL 中的瓦片集,提供其服务时无需标签。
exporter 包将结果集写入为 CSV、TSV、JSON、NDJSON、XML、GOB、SQL、Excel (XLSX)、GeoJSON 或 TopoJSON。它使用自识别编码保留二进制值,并且可以输出包含模式、行数以及带类型行 SHA-256 指纹的表清单。参见
ExampleExportJSON。
ExportGeoJSON/ExportTopoJSON 将查询结果的几何列(显式命名,或在恰好存在一个候选列时自动检测)以及所有其他选定的列转换为 GeoJSON FeatureCollection 或 TopoJSON
Topology —— 即 Power BI Shape Maps、D3 和大多数映射工具所期望的格式,可直接将计算出的 KPI 放置到地图上。tinysql CLI
将两者暴露为 -mode geojson/-mode topojson(使用 -geom-col 可
覆盖自动检测)。TopoJSON 导出会将整个共享边界环去重为一个弧(由一个要素正向引用,由其相邻要素反向引用)—— 由于无论如何每个环都会为弧表进行哈希,因此成本较低,
并且正是 GEO_DISSOLVE 生成的或拓扑上干净的数据集所受益之处 —— 但不会拆分仅部分重合的边界(像 mapshaper 这样的真实拓扑构建工具会这样做;
这是文档中记录的 v1 范围裁剪,而非 bug)。ImportTopoJSON(或在 .topojson 文件上使用 ImportFile/
.import)将弧引用解析 —— 包括
反向(~i)约定以及由其他工具构建的拓扑中每个环的多弧拼接 —— 还原为普通的几何行。
存储、事务和运维
| 模式 | 最佳适用场景 |
|---|---|
ModeMemory | 测试、浏览器/WASM 以及临时本地数据 |
ModeWAL | 带有预写日志恢复的内存表 |
ModeDisk | 每表一个 GOB 文件,支持延迟加载 |
ModeJSON | 人类可读、支持差异比较的每表文件 |
ModeIndex / ModeHybrid | 带有有界缓存的磁盘后端表 |
ModePagedIndex | 大型等值查找工作负载,例如 MBTiles |
ModeSQLite | 真实的 .sqlite 文件,可被任何 SQLite 工具读取(需要 sqliteimport 构建标签) |
使用 OpenDB 配合 StorageConfig 进行持久化存储。针对嵌入式服务,提供了健康检查、
只读操作、审计日志以及生命周期辅助功能。静态加密支持受支持的磁盘
后端中的表文件;有关确切范围,请参阅 存储指南。
对于高重复率的向量搜索,ConfigureVectorCache 可以启用有界的、
进程本地的结果缓存以及匿名的形状/时序分析。在结合向量指标和重排序之前,
请参阅 RAG 指南。
指南与开发
| 指南 | 适用场景 |
|---|---|
| 开发者集成 | Go、database/sql 及浏览器嵌入 |
| CLI 指南 | REPL、服务器及文件查询工具 |
| 存储指南 | 后端、DSN、只读模式及大型瓦片集 |
| RAG 指南 | 向量、混合检索、重排序及上下文 |
| TinyGo 指南 | TinyGo、嵌入式目标及 WASM |
| 架构 | 解析器、执行器、存储及不变量 |
| 开发指南 | 测试、Make 目标及发布演示 |
| 基准测试 | 可复现的性能测量 |
运行完整测试套件:
go test ./...
构建浏览器游乐场:
cd cmd/query_files_wasm
./build.sh --build-only
局限性
- 仅支持单进程:没有内置的复制、集群、分片、 分布式事务或故障转移。
- 不支持复合主键/外键、
CHECK、UPSERT/ON CONFLICT、SAVEPOINT、ATTACH/DETACH、VACUUM、部分索引、生成 列或持久化 ANN 向量索引文件。 - 二级索引优化等值/前缀查找和数值范围;文本
和 BLOB 范围谓词仍需扫描。没有 R 树,且普通的
WHERE GEO_DWITHIN(...)风格谓词未获得规划器加速; 显式的GEO_SEARCH表函数是大规模点列 bbox/半径查询的索引替代方案。 ModeIndex和ModeHybrid在缓存未命中时仍使用全表遗留编解码器; 对于严格的大瓦片服务需求,请使用 SQLite 或ModePagedIndex。- RBAC 较为粗糙且面向单表。加密尚未覆盖 基于 WAL 的模式或元数据文件。
- GIS 有效性是结构性的,而非完整的拓扑验证(没有
自相交检测)。
GEO_DISSOLVE/GEO_UNION_AGG仅处理 拓扑干净、顶点对齐的相邻多边形,而非通用的 多边形布尔并集;不支持 CRS/投影转换,且ST_TOUCHES/ST_CROSSES/ST_OVERLAPS(需要完整的 DE-9IM 计算)尚未实现。
TinySQL 主要是一个用于教育和可嵌入的 SQL 引擎。它旨在使 解析器、规划器、执行器、存储后端和实用扩展易于检查、测试和适配。