| layout | default |
|---|---|
| title | SQL 参考 |
| description | 当前版本真实支持的数据面与控制面 SQL 语法、限制和示例。 |
| permalink | /sql-reference/ |
想直接复制完整场景化示例,可先看 [SQL Cookbook]({{ '/sql-cookbook/' | relative_url }});本页更偏向能力边界与精确语法说明。
关系表与 measurement 保留创建时的名称拼写,不折叠大小写。普通 SQL 引用按 OrdinalIgnoreCase 匹配;双引号引用按 Ordinal 精确匹配,与当前语言区域及数据 collation 无关。
| 创建名称 | catalog 名称 | 引用方式 |
|---|---|---|
CREATE TABLE Device (...) |
Device |
Device、DEVICE、device 或 "Device";"device" 不匹配 |
CREATE TABLE "Device" (...) |
Device |
与上行相同;双引号在引用时要求精确拼写 |
已存在 Device 时创建 device 或 "device" |
拒绝 | 禁止新增仅大小写不同的同作用域名称 |
该规则适用于 SQL 表名、measurement 名称、视图及物化视图名称、关系表列名、measurement 的 TAG/FIELD 列名,以及别名、CTE 名和相应的限定符。表、measurement、视图和物化视图共享 SQL 数据源命名空间。双引号还允许空格、特殊字符和保留字;其中 "" 表示名称内的一个 "。SHOW、DESCRIBE 和结果列元数据返回已保存的原名。旧 catalog 若已有仅大小写不同的名称,普通引用报歧义,须用双引号精确检查并显式迁移;数据库不自动改写或合并。跨模型精确同名无法由引号消歧时,须先用指明对象类型的 DDL 迁移冲突对象。
Point、Line Protocol 等摄取方式的 measurement/TAG/FIELD 名称解析到已有 schema 的拼写,避免仅因大小写变化新增列或 series。此规则不改变字符串数据值、列数据 collation、JSON 文档属性键或其他数据模型的数据键语义。单引号表示字符串值,双引号表示 SQL 标识符。选择依据与 PostgreSQL/Oracle 的差别见标识符决策记录。
SELECT 投影和关系表 UPDATE ... SET 支持由列、字面量、括号、标量函数及以下运算符组成的数值表达式:
SELECT 2 * 5 + 1 AS constant_value;
SELECT value + 1 AS next_value, (high - low) * 0.5 AS adjusted FROM readings;
UPDATE counters SET value = value + 1 WHERE id = 1;- 支持二元
+、-、*、/、%、整数按位&/|和一元+、-;优先级为一元运算 > 乘除取模 > 加减 > 按位与&> 按位或|,括号可显式改变顺序。 - 整数加、减、乘、取模保留
Int64;任一操作数为浮点时返回Float64;除法始终返回Float64,所以5 / 2返回2.5。 - 关系表
SUM(INT)保留Int64返回类型;任何累加中间结果超出有符号 Int64 范围时返回执行错误,不会转为Float64。需要更大的有限精度结果时,可显式对输入使用CAST(value AS DECIMAL),但DECIMAL也受其精度范围约束。 DECIMAL/NUMERIC操作数保持System.Decimal精度;DECIMAL(p,s)的精度范围为1..38且0 <= s <= p,省略括号时使用(38,28)。关系表中的值以 16 字节 decimal payload 持久化,schema format v9 保存声明的 precision/scale;超出 Decimal 范围或声明精度/小数位无效时拒绝执行。- 按位
&/|只接受整数操作数并返回Int64,任一操作数为NULL时结果为NULL;浮点、字符串或其他非整数操作数会返回包含“只支持整数操作数”的中文执行错误。 - 任一算术操作数为
NULL时结果为NULL。除数或模数为0时抛出执行错误,不返回Infinity/NaN。 +只做数值加法,不把字符串隐式转成数字,也不做字符串拼接。字符串连接使用concat(...);其中NULL参数按空字符串处理。- 支持聚合结果外包算术或标量函数,例如
count(*) + 1、round(avg(value) + 0.25, 2);关系表、measurement 和 document collection 查询遵循同一规则。 - 普通关系表和 measurement 投影还支持 searched
CASE WHEN、比较、inclusiveBETWEEN/NOT BETWEEN、LIKE/ILIKE、AND/OR/NOT、IS [NOT] NULL及不含子查询的IN/NOT IN,并按 SQL 三值逻辑保留UNKNOWN;BOOL 列也可在UPDATE SET右值中直接接收这类谓词结果。 - 支持无
FROM的常量表达式查询,例如SELECT 2 * 5 + 1,便于探活和计算。 - 相同基础投影语义也适用于 JOIN、JSON 虚拟表、向量/混合搜索、
INFORMATION_SCHEMA以及内置forecast(...)/knn(...)表值函数。 a++、a--、a += 1、a -= 1不是 SQL 赋值语法,不支持;应写SET a = a + 1。SET a = +2合法,但含义只是把正数2赋给a。
使用 CAST(expr AS type) 进行显式转换。当前支持 INT、FLOAT、DECIMAL/NUMERIC、BOOL、STRING、DATETIME、TIME、BLOB 和 JSON:
SELECT CAST('42' AS INT), CAST(1 AS STRING), CAST(1704067200000 AS DATETIME);
SELECT CAST('9007199254740993.125' AS DECIMAL(20,3));
SELECT CAST('12:34:56.1234567' AS TIME);
SELECT CAST(text_value AS INT) FROM values_table WHERE CAST(text_value AS INT) > 0;NULL 转换到任一已支持目标类型仍为 NULL。DECIMAL / NUMERIC 转换保持十进制值,不经过 Float64;括号中的 precision/scale 仅用于校验目标声明,未声明时使用 (38,28)。TIME 只接受不带日期/时区的 HH:mm[:ss[.fffffff]] 文本、TimeOnly 或小于 24 小时的 TimeSpan,以 ticks 精度保存;24:00:00、负值、跨日值以及 DATETIME/DateTimeOffset 到 TIME 的隐式取时钟转换均拒绝。整数转换使用不变量格式并截断有限浮点的小数部分;布尔转换接受 TRUE/FALSE 或数值 0/1;DATETIME 输入可以是 UTC/ISO-8601 文本或 Unix 毫秒,结果统一为 UTC;BLOB 的字符串输入按 UTF-8 编码,JSON 当前保留字符串文本。格式错误、溢出、非有限浮点和非法布尔值会返回执行错误。VECTOR 与 GEOPOINT 作为 CAST 目标暂不支持,不会隐式构造对应模型值。
关系表列可声明 TIME,返回 CLR TimeOnly,并按 TimeOnly.Ticks 以 8 字节持久化;schema format v10 兼容读取旧版 schema。取值范围为 [00:00:00, 24:00:00),支持 7 位小数秒。ORDER BY、等值/范围谓词使用当天的时间顺序,不携带日期或时区;跨日区间、24:00:00 和远程协议中无法携带列类型的 typed TIME 恢复暂不属于合同。
关系表查询支持 row_number()、count、sum、avg、min、max 的 ANSI OVER 规格:
SELECT id, tenant_id,
row_number() OVER (PARTITION BY tenant_id ORDER BY created_at, id) AS row_no,
count(*) OVER (PARTITION BY tenant_id) AS tenant_count,
sum(value) OVER (PARTITION BY tenant_id ORDER BY created_at) AS running_total
FROM devices ORDER BY id;PARTITION BY 可包含多个关系行标量表达式;省略时全结果集是一个分区。窗口按 WHERE 过滤后的行计算,最终 ORDER BY、LIMIT 和 OFFSET 在窗口求值之后生效。row_number() 必须指定窗口内 ORDER BY;多个排序项和升降序均可使用。升序排序时 NULL 在前,降序时在后。排序键完全相同的行按本次关系输入顺序编号,该顺序不是跨执行保证;需要稳定编号时把唯一键加入窗口排序。
无窗口 ORDER BY 的聚合为每行返回完整分区的聚合结果。指定排序时使用默认 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 语义,相同排序键的所有同行共享同一个累计结果。COUNT(*) 计入 NULL 行,COUNT(expr) 忽略 NULL;其他聚合沿用关系聚合的 NULL、Int64 与 DECIMAL 语义。空输入返回零行。当前仅支持顶层窗口投影,不支持窗口函数嵌套表达式、与普通聚合/GROUP BY/HAVING 同层混用或显式 ROWS/RANGE frame。执行会物化过滤后的关系行并按窗口分区排序;有序累计聚合逐 peer 前缀复算,最坏为 O(n²),不适用于大分区低延迟工作负载。
时序 measurement 查询也支持显式 ANSI 窗口规格,并复用每个 series 的时间升序行流:
SELECT time,
row_number() OVER (ORDER BY time ASC) AS row_no,
running_sum(value) OVER () AS running_total
FROM readings
ORDER BY time;measurement 合同包括空 OVER ()、OVER (ORDER BY time ASC) 以及 row_number()(必须带 OVER)。已有 difference、running_sum、moving_average 等时序窗口函数也可以使用显式规格;省略 OVER 的旧语法保持兼容。每个 measurement series 天然构成一个分区,因此 row_number() 会在每个 series 从 1 重新编号。
measurement 路径仍拒绝 PARTITION BY、非 time 或降序排序,以及 ROWS/RANGE frame 子句。关系表与 measurement 窗口各自独立;文档集合和向量搜索路径不支持窗口投影。上述不支持形状返回错误,不会静默退化为另一种窗口语义。
关系表单条 UPDATE 还遵循以下规则:
- 同一个
SET中的所有右值都读取更新前的原行,因此SET a = b, b = a会正确交换两列。 - 表达式先对所有命中行求值并校验,再统一提交;任一行除零、溢出或类型不匹配时,整条语句不留下部分更新。
- 非显式事务中的单条
UPDATE把候选扫描、表达式求值和提交放在同一个表管理锁内,并发执行SET value = value + 1不会丢更新。 - 通过
Tsdb.Functions注册的用户标量函数属于任意应用回调,UPDATE 会在表管理锁外执行它,避免回调等待其他 SQL 线程时死锁;该分支需要并发冲突检测时应使用ROWVERSION。 ROWVERSION由数据库自动维护,不能出现在SET左侧;需要乐观并发控制时在WHERE中携带旧版本值。
关系表不提供用户可见的悲观行锁:SELECT ... FOR UPDATE、FOR UPDATE NOWAIT、FOR UPDATE SKIP LOCKED 和 FOR SHARE 均在解析阶段返回 sql_locking_read_unsupported,不会退化为普通 SELECT。单独尾随的 NOWAIT、SKIP LOCKED 也拒绝。不存在可供业务依赖的行锁粒度、持有生命周期、锁等待/超时、死锁检测或提交/回滚释放行为;不存在的目标行同样不能锁定。取消已请求的命令仍遵循普通 ADO.NET 取消合同,锁定读本身不会进入等待状态。
需要“读取后更新”时,使用 INT ROWVERSION 列和条件写入:
CREATE TABLE jobs (id INT, status STRING, version INT ROWVERSION, PRIMARY KEY (id));
SELECT status, version FROM jobs WHERE id = @id;
UPDATE jobs SET status = 'running' WHERE id = @id AND version = @version;调用方先读版本,再把同一版本放入 UPDATE 或 DELETE 的谓词;版本过期时可能返回 table_concurrency_conflict,目标行消失时可返回 0 受影响行。此时应重读并重试整个业务事务,而不是把先前 SELECT 当作锁定快照。成功 UPDATE 自动递增版本,INSERT 初值为 1。BeginTransaction 的 ReadCommitted/Serializable 轻事务不提供悲观锁;队列写入在提交时仍校验最初读取的行状态,冲突可能在写入或 COMMIT 暴露。ADO.NET 同步和异步锁定读在嵌入式及远程模式均抛 SqlParseException,其 Code 为 sql_locking_read_unsupported;直接 REST SQL 返回同一错误码。锁定读被拒绝后事务仍可由调用方正常 ROLLBACK 或 COMMIT。
以下标量函数把两只已经由 Modbus 协议层解析为 0..65535 的 16 位寄存器,还原成一个 32 位值:
| 函数 | 返回类型 | 说明 |
|---|---|---|
modbus_int32(first_register, second_register, byte_order) |
Int64 |
按有符号 32 位二补码解码 |
modbus_uint32(first_register, second_register, byte_order) |
Int64 |
按无符号 32 位解码;完整覆盖 0..4294967295 |
modbus_float32(first_register, second_register, byte_order) |
Float64 |
按 IEEE-754 binary32 解码,再提升为 SQL Float64 |
byte_order 不区分大小写,表示设备送来的四字节源布局:
| 源布局 | 第一个寄存器 | 第二个寄存器 | 还原动作 |
|---|---|---|---|
ABCD |
AB |
CD |
保持原序 |
BADC |
BA |
DC |
各 16 位字内交换字节 |
CDAB |
CD |
AB |
交换两个 16 位字 |
DCBA |
DC |
BA |
反转全部四字节 |
例如以下四个表达式都把源寄存器还原为 0x12345678,返回十进制 305419896:
SELECT modbus_uint32(4660, 22136, 'ABCD');
SELECT modbus_uint32(13330, 30806, 'BADC');
SELECT modbus_uint32(22136, 4660, 'CDAB');
SELECT modbus_uint32(30806, 13330, 'DCBA');有符号与浮点示例:
SELECT modbus_int32(65535, 65534, 'ABCD'); -- -2
SELECT modbus_float32(16256, 0, 'ABCD'); -- 1.0任一参数为 NULL 时结果为 NULL;寄存器越界、寄存器含小数、顺序名未知或参数类型错误时会抛出执行错误。
当前版本已经支持 CREATE MODBUS SOURCE、CREATE MODBUS ENDPOINT、列级 FROM MODBUS / EXPOSE AS MODBUS、表级 USING MODBUS,以及 SHOW/DESCRIBE MODBUS 本地元数据查询。DDL 会把连接定义和规范化地址持久化到独立的版本化 catalog,并在建表时校验四类地址空间、跨度、访问权限、字节序、缩放及 wire type。
Server 的 Modbus runtime 默认关闭;只有 SonnetDBServer:Modbus:Enabled=true 与对象自身 ENABLED TRUE 同时满足时,对应 worker 才会运行。TCP master 会批量轮询四类地址并把采样写入本地 LATEST/HISTORY 表;source timeout、取消、按 RETRY 指数退避、断线重连和指标均在后台执行。INT QUALITY 列公开 GOOD/STALE/BAD/PARTIAL/NO_VALUE 位,最终失败按 KEEP_LAST/NULL/SKIP/MARK_BAD 落表并更新稳定诊断。TCP slave 会按 endpoint 固定 ROW KEY 响应 0x01~0x04 读取,并接收 0x05/0x06/0x0F/0x10 写请求;REJECT 直接拒绝,默认 STAGED 只有 durable 入队后才返回协议成功,表值须经 Write + Admin 审批后按 STAGE_ONLY/UPDATE_TABLE 处理。普通 SELECT 始终只读取本地状态,不同步访问 PLC。Source 受限写提供 preview/confirm,SHOW MODBUS WRITE AUDIT 统一查询两条写路径的脱敏事件。创建 source、endpoint 或映射表需要当前数据库 Admin,SHOW/DESCRIBE MODBUS 与 Modbus 概览只需 Read;Endpoint 队列和审计需要 Admin。
完整语法、安全边界、地址归一化、类型表和示例见 [Modbus TCP 内建映射表合同]({{ site.docs_baseurl | default: '/help' }}/modbus-tcp/)。
定义关系表 schema。关系表 MVP 使用 KV-backed rowstore 存放在数据库目录的 tables/ 下,不修改时序 .SDBWAL / .SDBSEG 格式。
CREATE TABLE devices (
id INT AUTO_INCREMENT,
site_id INT NULL,
name STRING NOT NULL,
enabled BOOL NOT NULL DEFAULT TRUE,
retry_count INT DEFAULT 0,
version INT ROWVERSION,
installed_at DATETIME NULL,
metadata JSON NULL,
payload BLOB NULL,
PRIMARY KEY (id),
FOREIGN KEY (site_id) REFERENCES sites (id),
CONSTRAINT ck_devices_name CHECK (name IN ('pump', 'fan', 'valve'))
)规则:
- 当前必须声明
PRIMARY KEY (...);主键列会强制为NOT NULL。 - 支持类型:
INT、FLOAT、DECIMAL(p,s)/NUMERIC(p,s)、BOOL、STRING、DATETIME、TIME、BLOB、JSON。DECIMAL/NUMERIC的 precision 范围为1..38,scale 范围为0..precision;省略(p,s)时默认为(38,28)。TIME以TimeOnlyticks 保存,不含日期/时区。 DATETIME可写 Unix 毫秒整数或 ISO-8601 字符串,查询时返回 UTCDateTime。BLOB可写 base64 字符串;ADO.NET 参数可直接传byte[]。JSON当前按 UTF-8 字符串存储;可用json_value(json_col, '$.path')做 path 投影和过滤。VECTOR(dim)与GEOPOINT只作为 measurement 的FIELDSQL 类型提供,不支持关系表CREATE TABLE、ALTER TABLE ADD COLUMN或ALTER COLUMN ... TYPE;这些声明会确定性拒绝且不创建半成品表/列。文档集合 JSON 数组和专用向量 API 不构成关系类型支持。ADO.NETGetSchema("DataTypes")通过SupportsRelationalColumn=false、SupportsMeasurementField=true明示这两个类型的边界;该表是 SDK 本地能力元数据,不是远端版本协商。完整[类型矩阵与官方关系实体替代模型]({{ site.docs_baseurl | default: '/help' }}/relation-type-boundary/)给出标量经纬度加显式 JSON 载荷,以及关系主数据加 companion measurement 两种建模方式;ORM 不得静默改为 JSON 或时序字段。- 普通列可声明
DEFAULT <expr>;默认表达式支持字面量、常量算术和内置标量函数,不能引用列、参数、聚合或子查询。ROWVERSION列不能声明默认值。 INSERT省略带默认值的列,或在VALUES对应位置写DEFAULT时,会在每一行写入时求值并应用目标列的默认表达式;显式写入NULL不会改用默认值。目标列没有声明显式默认值时,DEFAULT按 SQL 常规语义产生隐式NULL:可空列成功,非空列由现有约束拒绝。INSERT INTO table DEFAULT VALUES会为每个非ROWVERSION列使用其默认值;没有显式默认值的列产生隐式NULL。UPDATE table SET column = DEFAULT WHERE ...会按每个命中行重新求值默认表达式,轻事务路径保持相同语义。VALUES(DEFAULT)、DEFAULT VALUES和UPDATE SET ... = DEFAULT只适用于关系表;measurement 与文档集合会明确拒绝。- 二级索引使用
CREATE INDEX单独声明。 FOREIGN KEY (...) REFERENCES parent (...)第一版只支持表级声明,引用列必须等于被引用表PRIMARY KEY;外键列任一为NULL时跳过校验。CHECK (expression)支持命名或未命名表级约束;表达式可引用当前表列、字面量、基础运算、IN、IS NULL、CASE和当前关系执行器支持的标量函数,不支持限定列名、参数、聚合或子查询。- CHECK 按 SQL 三值逻辑执行:只有明确
FALSE拒绝写入,TRUE和由NULL传播得到的UNKNOWN均通过。 ROWVERSION只能声明在一个INT列上;INSERT自动写入1,UPDATE自动递增,禁止通过SET显式赋值,可用WHERE id = ... AND version = ...获得乐观并发冲突检测。- 每张表最多可有一个
INT AUTO_INCREMENT列;兼容拼写AUTOINCREMENT和IDENTITY。该列隐式为NOT NULL,不能同时声明NULL、DEFAULT或ROWVERSION。 INSERT省略自增列、写入NULL或DEFAULT时,从1开始分配单调递增值;显式整数仍可写入,且高于当前高水位时会推进后续分配。自增属性本身不创建唯一约束,需要唯一性时仍应把该列加入PRIMARY KEY或唯一索引。- 自增列由数据库维护,不允许通过
UPDATE SET显式修改;底层TableStore批量 mutation 若写入更大的显式值,仍会推进高水位以保护后续分配。 - 自增高水位持久化在表的 KV/WAL 中,并在并发写入前预留;约束失败、触发器失败或事务回滚可能留下间隙,已分配值不会复用。
DELETE不重置序列,TRUNCATE TABLE切换 generation 后从1重新开始;超过INT的Int64上限会明确报错。 - 普通
INSERT在提交阶段的表管理锁内分配自增值;事务或触发器为了向NEW行提前暴露生成值而进行的预留会记录当时的 generation。若并发TRUNCATE TABLE已切换 generation,陈旧事务会在提交前明确失败,不会把重置前的预留值写入新 generation。 - 关系表
INSERT支持RETURNING column [, ...]和RETURNING *;返回行使用完成默认值、ROWVERSION与AUTO_INCREMENT生成后的最终值,多行结果保持VALUES的插入顺序。首版只允许列名或*,不支持表达式和别名;measurement 与文档集合会明确拒绝RETURNING。 - ADO.NET 可用
ExecuteScalar("INSERT ... RETURNING id")取得本条语句生成的首个 ID,作为语句级 last-insert-id;ExecuteReader可读取完整返回行,同时RecordsAffected保留实际插入行数。SonnetDB 不维护连接级LAST_INSERT_ID()状态。 - 完整的
INSERT ... RETURNING类型、列属性和跨嵌入式/REST/Frame HTTP/2 ADO 回落合同属于下一未发布版本,首次交付版本号待发布标签确定;已发布的 3.1.0 不应据此视为具备该完整合同。Frame SQL 查询端点仍只读,Protocol=frame-http2的 ADO 写入经同一 HTTP/2 连接回落 REST SQL 端点。 - EF Core provider 会把常规
int/longValueGenerated.OnAdd列建为INT AUTO_INCREMENT,INSERT 不发送临时跟踪键,并通过RETURNING把数据库生成值回填实体;ValueGeneratedNever()仍按显式客户端键处理。
关系查询可在投影和谓词中使用以下日期标量函数:
SELECT DATE_ONLY(installed_at),
DATE_PART('year', installed_at),
DATE_ADD(installed_at, 7, 'day'),
DATE_DIFF('day', installed_at, CURRENT_UTC_DATETIME()),
DATE_FORMAT(installed_at, 'yyyy-MM-dd HH:mm:ss'),
TO_UNIX_MILLISECONDS(installed_at)
FROM devices
WHERE installed_at <= CURRENT_UTC_DATETIME();CURRENT_DATETIME()/CURRENT_UTC_DATETIME()返回服务器本地时间 / UTC 时间;CURRENT_DATETIME_OFFSET()/CURRENT_UTC_DATETIME_OFFSET()返回对应的DateTimeOffset。DATE_ONLY(value)返回日期零点。DATE_PART(part, value)支持year、quarter、month、day、day_of_year、day_of_week、hour、minute、second、millisecond、microsecond、nanosecond;day_of_week与 .NET 一致,星期日为0。DATE_ADD(value, amount, part)支持year、month、day、hour、minute、second、millisecond、microsecond、tick;year、month、tick要求整数增量。DATE_DIFF(part, start, end)(兼容DATE_DIFF(start, end, part))返回两个时间之间按指定分量截断的有符号整数;支持year、quarter、month、week、day、hour、minute、second、millisecond、microsecond、tick。DATEDIFF为同义别名。DATE_FORMAT(value, format)/FORMAT_DATETIME(value, format)/TO_CHAR(value, format)使用 invariant .NET 日期格式;STRFTIME(format, value)额外支持%Y、%m、%d、%H、%i、%s、%f等常用标记。格式字符串最多 128 个字符,未知%标记会拒绝。TO_UNIX_MILLISECONDS(value)/TO_UNIX_SECONDS(value)返回 Unix 时间。- 日期函数接受
DATETIME或 Unix 毫秒;输入为NULL时结果为NULL。时序分桶仍使用GROUP BY time(1m)等语法,不使用DATE_TRUNC。
关系表支持新增、修改、删除和重命名列,以及重命名表:
ALTER TABLE devices ADD COLUMN region STRING NOT NULL DEFAULT 'north';
ALTER TABLE devices ALTER COLUMN retry_count TYPE FLOAT;
ALTER TABLE devices ALTER COLUMN region SET DATA TYPE STRING;
ALTER TABLE devices ALTER COLUMN region SET NOT NULL;
ALTER TABLE devices ALTER COLUMN region DROP NOT NULL;
ALTER TABLE devices ALTER COLUMN region SET DEFAULT 'east';
ALTER TABLE devices ALTER COLUMN region DROP DEFAULT;
ALTER TABLE devices RENAME COLUMN region TO site;
ALTER TABLE devices DROP COLUMN site;
ALTER TABLE devices RENAME TO managed_devices;ALTER COLUMN 的完整语法为:
ALTER TABLE table_name ALTER [COLUMN] column_name
[TYPE data_type | SET DATA TYPE data_type | data_type]
[NULL | NOT NULL | SET NOT NULL | DROP NOT NULL]
[SET DEFAULT expression | DROP DEFAULT];- 每条语句至少要指定一种变更;类型、空值约束和默认值动作各自最多出现一次,可以组合在同一条语句中。
TYPE FLOAT与SET DATA TYPE FLOAT是两种等价的类型变更写法;省略TYPE的FLOAT形式用于 SQL Server 风格组合,例如ALTER TABLE devices ALTER COLUMN retry_count FLOAT NOT NULL。- 类型变更会转换全部存量非空值并重建派生索引。当前不支持
USING expression自定义转换;任一值无法转换,或转换后违反唯一索引、CHECK、NOT NULL 等约束时,在进程内整条 DDL 恢复原 schema、数据和索引。行 payload 与 catalog 发布之间尚无迁移 journal,因此不将该恢复边界解释为进程终止或掉电原子性。 - 真正改变物理类型的迁移当前使用单个 KV 原子 batch,受
KvOptions.MaxOverlayEntries和KvOptions.MaxWalBytes限制(默认分别为 100,000 条 mutation 和 256 MiB WAL)。超出预算时会在写 WAL 前拒绝;应先提高预算,或通过新表分批迁移。SET/DROP DEFAULT、DROP NOT NULL和列重命名只发布元数据,不受该 batch 行数限制。 SET NOT NULL会分页扫描并验证全部存量行,但不会重写 row payload;DROP NOT NULL与直接写NULL等价,NOT NULL与SET NOT NULL等价。SET DEFAULT只影响之后省略该列的INSERT,不会回填存量行;DROP DEFAULT只移除后续插入的默认行为。目标类型或空值约束发生变化时,保留或新设的默认表达式也会按新定义重新校验。- PRIMARY KEY 列不能修改类型或改为可空;ROWVERSION 列不能修改类型、空值约束或默认值;参与外键的引用列或被引用列不能修改类型,必须先删除相应外键约束。
- 被逻辑视图、物化视图、存储过程或触发器依赖的表仍受既有依赖保护,必须先移除依赖对象再修改 schema。
ADD COLUMN ... NOT NULL当前即使目标表为空也要求DEFAULT;有入站外键引用的父表不能RENAME TABLE或DROP TABLE,必须先删除子表外键。
现有关系表可在事务外追加生成列,并在空表上重定义主键:
ALTER TABLE devices ADD COLUMN id INT AUTO_INCREMENT;
ALTER TABLE devices ADD COLUMN version INT ROWVERSION;
ALTER TABLE devices ADD CONSTRAINT pk_devices PRIMARY KEY (id);
-- 等价的无约束名写法:ALTER TABLE devices ALTER PRIMARY KEY (id);ADD COLUMN ... AUTO_INCREMENT对已有行按旧主键编码顺序分配从1开始的值;新插入行从现有最大值加一继续。序列高水位与回填行在同一 KV batch 持久化,正常关闭并重开后继续增长。每张表只能有一个INT AUTO_INCREMENT;显式NULL、DEFAULT、ROWVERSION组合及非INT类型被拒绝。ADD COLUMN ... ROWVERSION把已有行初始化为1,后续UPDATE自动加一;每张表只能有一个INT ROWVERSION,且该列不可空、不可声明默认值或手动赋值。CREATE TABLE可暂不声明主键,形成仅供 schema 演进的无主键空表;该表可以查询和追加列,但补齐主键前任何行写入均以table_schema_evolution_unsupported拒绝,不分配身份序列。上述CREATE TABLE devices (code STRING NOT NULL)后追加id、主键与version的路线可执行;补齐主键后才能开始写入。ADD [CONSTRAINT pk_<table>] PRIMARY KEY (...)和ALTER PRIMARY KEY (...)仅支持空表。复合主键可用;有任意存量行、入站外键或自定义约束名时返回table_schema_evolution_unsupported。已有行需要在事务外创建带目标主键的新表、分批回填并验证唯一性及约束,再切换应用访问;当前没有原子切换协议。- 生成列追加复用行改写和索引重建路径,保留既有默认值、CHECK 和外键约束;DDL 不允许放在活动轻事务中。行改写受单个 KV batch 预算限制,并沿用上述进程终止/掉电非原子性边界。成功后新 schema 对新的
DESCRIBE TABLE和 ADO.NETGetSchema("Columns")读取可见;失败不更新 catalog。
已有表还可追加或删除外键和检查约束。追加约束前会扫描存量行;任一行违反约束时 DDL 失败且 catalog 保持原状。
ALTER TABLE devices
ADD CONSTRAINT fk_devices_site FOREIGN KEY (site_id) REFERENCES sites (id);
ALTER TABLE devices
ADD CONSTRAINT ck_devices_name CHECK (name IN ('pump', 'fan', 'valve'));
ALTER TABLE devices DROP CONSTRAINT fk_devices_site;
ALTER TABLE devices DROP CONSTRAINT ck_devices_name;逻辑视图保存一条 SELECT 定义,不保存查询结果。读取视图时,SonnetDB 将定义展开为派生表并复用现有查询执行器,因此视图可以基于关系表、measurement、document collection、其他视图以及当前已经支持的 JOIN、UNION 和子查询。
CREATE VIEW active_devices AS
SELECT id, name, site_id
FROM devices
WHERE enabled = TRUE;
CREATE VIEW IF NOT EXISTS north_devices AS
SELECT d.id, d.name
FROM active_devices d
JOIN sites s ON d.site_id = s.id
WHERE s.name = 'north';
SELECT name FROM north_devices ORDER BY name;管理语法:
SHOW VIEWS;
DESCRIBE VIEW active_devices;
DROP VIEW active_devices;
DROP VIEW IF EXISTS active_devices;SHOW VIEWS 返回 name、created_utc。DESCRIBE VIEW 返回单行 name、definition、逗号分隔的直接 dependencies 和 created_utc。information_schema.tables 以 table_type = 'VIEW' 列出视图,information_schema.views 提供 table_schema、table_name、view_definition、created_utc。
当前行为与限制:
- 定义以独立的
views/views.sdbview目录文件持久化,重启后重新解析;不修改表、measurement、document 或 Segment 的现有二进制格式。 - 视图采用读取时展开和晚绑定数据语义,基础数据变更会立即反映到后续查询;视图本身不能作为
INSERT、UPDATE或DELETE目标。 - 持久化定义不能包含
?、@name或:name参数占位符;调用方参数只能用于查询视图的外层 SELECT。 - 创建时会拒绝不存在的数据源、与基础对象重名的视图和自引用;运行时还会拒绝直接或间接循环,并限制最多 32 层展开。
- 被视图引用的基础对象不能执行
DROP或 schemaALTER;被其他视图引用的视图也不能删除。首版不支持CASCADE、OR REPLACE或跨数据库依赖,应先按依赖顺序显式删除视图。 EXPLAIN SELECT ... FROM view将访问路径标记为view_expansion;当前不会把展开后各基础扫描的估算值汇总到视图层。
物化视图保存 SELECT 定义和最近一次成功刷新的物理结果。创建只登记定义,不隐式执行查询;首次读取前必须显式刷新:
CREATE MATERIALIZED VIEW active_device_cache AS
SELECT id, name, site_id
FROM active_devices;
REFRESH MATERIALIZED VIEW active_device_cache;
SELECT name FROM active_device_cache ORDER BY id;基础数据后续变化不会自动进入已发布快照。再次执行 REFRESH MATERIALIZED VIEW 会全量计算一个新代际;新代际完整写入并落盘后才原子切换读指针。刷新期间读者继续读取旧代际,刷新失败也保留旧代际,并把状态改为 failed、记录错误。尚无成功代际时读取会明确提示先刷新。
管理语法:
CREATE MATERIALIZED VIEW IF NOT EXISTS active_device_cache AS SELECT * FROM active_devices;
SHOW MATERIALIZED VIEWS;
DESCRIBE MATERIALIZED VIEW active_device_cache;
DROP MATERIALIZED VIEW active_device_cache;
DROP MATERIALIZED VIEW IF EXISTS active_device_cache;SHOW MATERIALIZED VIEWS 返回 name、status、definition_version、active_generation、row_count、最近成功刷新的 refreshed_utc 和 error。DESCRIBE MATERIALIZED VIEW 另返回 SELECT definition、直接 dependencies、created_utc、最近尝试结束时间 last_refresh_utc 与 last_successful_refresh_utc。状态为 uninitialized、refreshing、ready 或 failed。
information_schema.tables 以 table_type = 'MATERIALIZED VIEW' 列出物化视图;information_schema.materialized_views 提供相同定义和刷新元数据。EXPLAIN SELECT ... FROM materialized_view 使用 materialized_view_snapshot 访问路径并按活动代际行数估算扫描。
当前行为与限制:
- 定义目录和物理结果位于独立的
materialized-views/;目录和快照均有版本与 CRC,不修改已有表、measurement、document 或 Segment 格式。 - 物化快照是只读关系源,可参与外层过滤、投影、聚合、子查询和关系 JOIN;定义本身仍复用现有表、measurement、document、逻辑视图和当前 SELECT 执行路径。
- 定义不能包含参数占位符、未知数据源或自引用;物化视图与基础对象、逻辑视图共用名称空间和依赖删除/ALTER 保护。
REFRESH需要数据库写权限,不能在活动轻事务内执行,同一物化视图不允许并发刷新;SHOW、DESCRIBE和读取只需要读权限并继续经过现有 Frame、MCP、Copilot 与审计入口。- 首版只支持显式全量刷新。不支持增量刷新、定时调度、后台自动刷新、
OR REPLACE、CASCADE或跨数据库依赖。
存储过程首版只支持 LANGUAGE SQL、有序 IN 参数和静态 SQL body。参数类型为 INT、FLOAT、BOOL、STRING,body 通过 @参数名 引用参数;绑定发生在 AST 上,不执行文本替换。
CREATE PROCEDURE add_device (
IN p_id INT,
IN p_name STRING,
IN p_enabled BOOL
)
LANGUAGE SQL AS BEGIN
INSERT INTO devices (id, name, enabled)
VALUES (@p_id, @p_name, @p_enabled);
SELECT id, name, enabled
FROM devices
WHERE id = @p_id;
END;
CALL add_device(1, 'pump-01', TRUE);管理语法:
SHOW PROCEDURES;
DESCRIBE PROCEDURE add_device;
DROP PROCEDURE add_device;
DROP PROCEDURE IF EXISTS add_device;SHOW PROCEDURES 返回 name、parameters、language、传递计算后的 requires_write 和 created_utc。DESCRIBE PROCEDURE 返回 name、parameters、language、body、object_dependencies、procedure_dependencies、requires_write、created_utc。
执行合同:
- body 允许
SELECT、INSERT、UPDATE、DELETE和CALL;写目标必须是关系表,首版不允许过程写 measurement 或 document collection。SELECT 继续复用当前支持的数据源与查询执行器。 - 定义时解析全部语句并校验命名参数、数据对象和已存在的被调用过程。过程不能重载,不支持默认参数、OUT/INOUT、动态 SQL、DDL 或外部语言运行时。
- 多语句过程只向调用方返回 body 最后一条语句的结果;中间 SELECT 参与结果行数治理,但不形成多个远程结果集。
- 直接或传递包含写入的过程自动使用轻事务;失败时整次调用回滚。位于调用方已有事务中时使用保存点,只撤销该次失败调用新增的 mutation。
- 默认单次调用链最多执行 64 条 body 语句、嵌套 8 层、累计产生 10,000 行结果(包含 INSERT RETURNING);嵌套 CALL 的最终结果只计一次。SELECT 按剩余行预算加一行探测下推分页,超限失败,不静默截断;阻塞算子的中间数据仍受独立 SQL 内存预算约束。拒绝直接或间接递归,在语句边界、关系行扫描和提交前检查取消。
- 写权限按完整调用图传递计算。只读凭据可调用只读过程,不能通过外层只读过程调用内层写过程提升权限;Frame SQL query 通道固定为只读,因此也只能调用只读过程。
CREATE/DROP PROCEDURE不能在活动轻事务中执行。基础对象DROP/ALTER和被调用过程DROP会在仍有依赖时返回routine_dependency。
过程与触发器定义共同保存在数据库目录的 routines/routines.sdbrtn。该目录使用独立版本、little-endian 编码、CRC32、大小/数量上限和临时文件原子替换;备份恢复自动包含该目录,打开时拒绝损坏或未知版本。当前 v4 在启用状态、执行顺序、时机、粒度和 transition table 别名之外持久化约束/延迟标志,兼容读取 v1/v2/v3(旧定义默认为立即触发器)。修改目录后写入 v4,旧引擎会拒绝打开,不可直接降级;未知标志或不一致组合也会被拒绝。主数据文件、KV/WAL 及关系事务 journal 格式不变。
关系表触发器支持 AFTER INSERT/UPDATE/DELETE FOR EACH ROW、AFTER INSERT/UPDATE/DELETE FOR EACH STATEMENT、受限 BEFORE INSERT/UPDATE FOR EACH ROW,以及下述提交阶段执行的延迟约束 AFTER ROW 触发器。每个定义只绑定一个事件;AFTER 中的 OLD / NEW 是只读行上下文,BEFORE 仅能通过下述受限赋值修改 NEW。
CREATE TRIGGER audit_device_insert
AFTER INSERT ON devices
FOR EACH ROW
WHEN (NEW.enabled = TRUE)
LANGUAGE SQL AS BEGIN
INSERT INTO device_audit (event_id, device_id, action, old_name, new_name)
VALUES (NEW.id * 10 + 1, NEW.id, 'insert', NULL, NEW.name);
END;
CREATE TRIGGER audit_device_update
AFTER UPDATE ON devices
FOR EACH ROW
WHEN (OLD.name != NEW.name)
LANGUAGE SQL AS BEGIN
INSERT INTO device_audit (event_id, device_id, action, old_name, new_name)
VALUES (NEW.id * 10 + 2, NEW.id, 'update', OLD.name, NEW.name);
END;
CREATE TRIGGER audit_device_delete
AFTER DELETE ON devices
FOR EACH ROW
LANGUAGE SQL AS BEGIN
INSERT INTO device_audit (event_id, device_id, action, old_name, new_name)
VALUES (OLD.id * 10 + 3, OLD.id, 'delete', OLD.name, NULL);
END;管理语法:
SHOW TRIGGERS;
SHOW TRIGGERS ON devices;
DESCRIBE TRIGGER audit_device_insert;
ALTER TRIGGER audit_device_insert DISABLE;
ALTER TRIGGER audit_device_insert ENABLE;
ALTER TRIGGER audit_device_insert RENAME TO audit_insert;
EXPLAIN TRIGGER audit_insert;
EXPLAIN PROCEDURE add_device;
SHOW ROUTINE AUDIT FOR TRIGGER audit_insert;
SHOW ROUTINE STATS FOR PROCEDURE add_device;
DROP TRIGGER audit_insert;
DROP TRIGGER IF EXISTS audit_insert;SHOW TRIGGERS 返回 name、table_name、event、when、created_utc;ON table 只保留指定关系表。DESCRIBE TRIGGER 返回 name、table_name、event、when、language、body、dependencies、created_utc。两者在末尾追加 enabled、execution_order。
当前语义与限制:
- INSERT 事件只能引用
NEW,DELETE 事件只能引用OLD,UPDATE 可同时引用两者。WHEN中的列必须显式写为OLD.column/NEW.column,不允许参数或子查询。 - AFTER body 只允许以关系表为目标的
INSERT、UPDATE、DELETE,可在 DML 内嵌 SELECT,不允许独立 SELECT、CALL、DDL 或 measurement/document 写入;BEFORE body 使用下述 SET NEW 合同。 - 执行顺序先按原 DML 行顺序,再按组内持久化执行顺序;新触发器默认追加。
FOR EACH ROW FOLLOWS name/PRECEDES name位于可选 WHEN 之前;也可用ALTER TRIGGER name FOLLOWS other/PRECEDES other原子移动到参照之前或之后。参照必须是另一个同表、同事件、同时机、同粒度、同执行阶段(立即或延迟)的已有触发器,禁用定义仍保留位置;重命名和启停保留创建时间与顺序。这是位置调整,不是随参照后续变化而自动移动的依赖边;没有额外的命名 order group 语法。触发器链共享语句数和深度预算,拒绝递归。 - 原 DML 与全部触发动作使用同一轻事务提交边界。语句执行中的
WHEN或 body 失败会撤销本条 DML 及其动作;调用方已有事务时使用内部保存点。COMMIT 阶段的延迟动作或最终约束失败则结束并回滚整笔事务。 - 目标表或 body 依赖的关系表仍被触发器引用时,
DROP/ALTER会被阻断,包括已禁用的触发器。CREATE/DROP/ALTER TRIGGER需要写权限,不能在活动轻事务中执行。每条 DML 固定使用进入时的定义快照,后续生命周期修改不会改变该条语句中途的触发集合。 - 不支持 BEFORE DELETE、BEFORE STATEMENT、INSTEAD OF、多事件合并、Document 或 measurement 触发器;延迟约束触发器仅支持下述固定语法,不提供通用 deferred、
INITIALLY IMMEDIATE或SET CONSTRAINTS。
CREATE TABLE batch_totals (id INT, amount INT, PRIMARY KEY (id));
INSERT INTO batch_totals (id, amount) VALUES (1, 0);
CREATE TRIGGER sum_inserted AFTER INSERT ON orders
REFERENCING NEW TABLE AS incoming
FOR EACH STATEMENT LANGUAGE SQL AS BEGIN
UPDATE batch_totals
SET amount = amount + (SELECT COALESCE(SUM(amount), 0) FROM incoming)
WHERE id = 1;
END;- 分发顺序为:对全部候选行执行 BEFORE 与缓冲准备、所有 AFTER ROW 动作、所有 AFTER STATEMENT 动作、提交时检查最终约束。同组遵循持久化顺序。后续链上 DML 是独立触发语句。
- AFTER STATEMENT 对空影响集也执行一次;无匹配行的 UPDATE/DELETE 和空
INSERT SELECT均属于空影响集。失败的源语句不继续分发 AFTER。 - UPDATE 可声明
REFERENCING OLD TABLE AS previous NEW TABLE AS incoming;INSERT 只有 NEW TABLE,DELETE 只有 OLD TABLE。REFERENCING 位于 ON table 与 FOR EACH 之间,可省略;别名不能重复或与创建时已有数据对象重名,不能是 OLD、NEW 或目标表名。 - 快照只含本条语句实际影响的行,保留该语句改写前/最终改写后的配对数据,包含 BEFORE 的修改及生成后的 ROWVERSION。它不包含同事务其他语句或后续触发器链的修改。没有隐式行序保证;有顺序需求时使用 ORDER BY。
- 别名仅在所属触发器 body 内可见,嵌套触发器隔离并在返回后恢复调用方快照;不能作为 INSERT/UPDATE/DELETE 目标。可用于 SELECT 聚合、JOIN、子查询,以及
INSERT INTO table (columns) SELECT ...。UPDATE 赋值支持非相关的单列标量子查询,返回多行时报错;INSERT 的查询取值使用INSERT SELECT,不支持VALUES (子查询)。语句级触发器不提供 OLD/NEW 行变量或行级 WHEN,过滤放在查询内。 - transition set 复用事务持有的不可变行图像,不额外复制整个 OLD/NEW 集合。默认调用链同时存活的集合最多 100,000 行(UPDATE 一对计一行)、64 MiB 保守内存估算,包含嵌套集合;超过任一上限返回
trigger_transition_limit并撤销本条语句。此版本采用硬上限拒绝,不将 transition set 溢写到磁盘。 - 关系表 INSERT SELECT 的源查询在普通表扫描、关系 JOIN/聚合/子查询、UNION 结果收集及外排归并输出时,按
MaxTriggerTransitionRows/MaxTriggerTransitionBytes检查保留行数与估算字节;阻塞算子内存上限同时收紧到该字节预算。超限在目标写入前以trigger_transition_limit拒绝,整条 INSERT 只触发一次。多阶段保留会重复计数,因而可能保守拒绝;非关系源执行器和 UDF 内部分配不受此估算预算约束。它不扩展到 measurement 或 Document 写入。
CREATE TRIGGER normalize_order BEFORE INSERT ON orders
FOR EACH ROW LANGUAGE SQL AS BEGIN
SET NEW.customer = LOWER(COALESCE(NEW.customer, 'unknown'));
SET NEW.amount = CASE WHEN NEW.amount < 0 THEN 0 ELSE NEW.amount END;
END;典型用途是把订单标识/金额归一化为满足 NOT NULL、唯一性和 CHECK 的最终值,而不是在 AFTER 中再次写同一行。
- INSERT 输入先按目标列类型转换并计算 DEFAULT,然后预留 AUTO_INCREMENT;UPDATE 的原 SET 右值全部读取原行,先生成候选 NEW。
- 依次执行 BEFORE 的 WHEN 和
SET NEW.column = expression。同一 body 的后续赋值、后续触发器都读取前面修改后的 NEW;OLD 始终为原行。WHEN 为 false/NULL 时跳过该触发器。 - 生成 ROWVERSION 并检查最终 NOT NULL,然后缓冲最终行;INSERT 的版本为 1,UPDATE 在原版本上加一。生成后的 NEW 对 AFTER 可见。BEFORE 不允许读取尚未生成的 NEW.ROWVERSION,可以读取 OLD.ROWVERSION。
- 提交时仍检查主键、唯一索引、外键、CHECK 和乐观并发原行状态;NEW 主键或外键允许改写,但不能绕过这些检查。失败撤销源行及全部触发器动作;已有事务退回本语句保存点。自增预留沿用现有合同,失败可产生序列空洞。
BEFORE body 仅接受逐条 SET NEW.column = expression。表达式限字面量、OLD/NEW 列、算术/比较/逻辑、CASE、IS NULL、常量 IN 列表和 LOWER/UPPER/COALESCE;拒绝子查询、CALL、任意 DML、UDF/外部函数和 OLD 赋值。AUTO_INCREMENT 与 ROWVERSION 都是引擎生成列,不允许改写;任意计算生成列表达式仍未支持。预算、取消、递归保护和审计沿用例程调用链合同。
SqlExecutionOptions.MaxTriggerTransitionRows / MaxTriggerTransitionBytes 可设置嵌入式预算;服务器对应 SonnetDBServer:SqlExecution 下同名属性,分别限制为 1..100000 与 1..134217728,默认 100000/67108864,REST/Frame 使用相同服务端值。SHOW/DESCRIBE TRIGGER 在既有列后追加 timing、level、old_table、new_table。
执行前也检查 transition 别名冲突;若创建触发器后又创建同名数据对象,触发器返回 routine_dependency 并回滚源语句,避免优化器把快照误当成持久表。
下面的一次余额转移由两条 UPDATE 组成。提交时两次检查均读取最终总额 100;若改为普通立即 AFTER 触发器,同一检查体会保存第一条 UPDATE 后的中间总额 90,使最终 CHECK 失败。示例检查表的主键仅用于这一次转移;实际业务应为每个检查事件设计独立键或更新同一检查记录。
CREATE TABLE accounts (id INT, balance INT, PRIMARY KEY (id));
INSERT INTO accounts (id, balance) VALUES (1, 40), (2, 60);
CREATE TABLE balance_checks (
id INT, total INT, PRIMARY KEY (id), CHECK (total = 100));
CREATE CONSTRAINT TRIGGER balanced AFTER UPDATE ON accounts
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
LANGUAGE SQL AS BEGIN
INSERT INTO balance_checks (id, total)
SELECT NEW.id, total
FROM (SELECT SUM(balance) AS total FROM accounts) AS totals;
END;
BEGIN;
UPDATE accounts SET balance = 30 WHERE id = 1;
UPDATE accounts SET balance = 70 WHERE id = 2;
COMMIT;约束触发器必须同时声明 DEFERRABLE INITIALLY DEFERRED,仅支持 AFTER INSERT/UPDATE/DELETE ... FOR EACH ROW;FOR EACH ROW 后可接同阶段 FOLLOWS/PRECEDES 和 WHEN。普通触发器不能声明 DEFERRABLE;拒绝部分声明、INITIALLY IMMEDIATE、SET CONSTRAINTS、BEFORE/STATEMENT 约束触发器及其 transition tables,也没有 AFTER COMMIT SQL 语法。
- 源 DML 按语句顺序、实际行顺序和组内持久化触发器顺序入队;空影响集不产生行事件。COMMIT 在持久化前按 FIFO 执行,body 新产生的事件追加到队尾。同一行被多次更新或最终删除也不会合并此前事件。
- 每个事件捕获自己的 OLD/NEW,包括 BEFORE 改写及对应 ROWVERSION。
WHEN在 COMMIT 求值,但读取捕获图像;body 查询读取当时已提交基线、本事务最终缓冲和前面延迟动作的变更。已有 FK 级联先展开到缓冲,之后新增删除会再展开;自动级联动作仍不派发合成 SQL 行事件。延迟 body 随后重插父行不会恢复已执行 CASCADE/SET NULL 的子行变化。 - 无显式事务的 DML 自动入队并提交。已有事务中的源语句或过程失败,其内部保存点同时撤销新增 mutation、事件和内存计费,保留更早成功语句;未增加 SQL
SAVEPOINT语法。COMMIT 期间的 body、预算、取消、依赖或约束失败回滚整笔事务;ROLLBACK 和被放弃的脚本清空队列。 - 已入队事件保留定义、原 caller 和调用祖先链。随后启停、重命名、排序或删除定义只影响后续 DML;依赖表 schema 被替换、删除或重建时,提交以
routine_dependency拒绝过期依赖。 - body 只允许基础关系表 INSERT/UPDATE/DELETE 及受支持的纯 SQL 查询和内置函数;提交中间接执行的普通触发器也接受此校验。禁止 UDF、被用户覆盖的内置函数、应用表值回调、CALL、DDL、独立 SELECT、视图、文件/JSON_FILE/GRAPH_TABLE 及其他数据模型入口。
SqlExecutionOptions 选项 |
默认值 | 范围与用途 |
|---|---|---|
MaxDeferredTriggerInvocations |
100000 | 1..100000;同事务保留事件数量上限 |
MaxDeferredTriggerBytes |
67108864 | 正值;保守计费 OLD/NEW、定义和祖先链,Server 上限 134217728 |
TransactionCommitTimeoutMilliseconds |
30000 | 1..120000;提交锁等待和协作执行期限 |
Server 使用 SonnetDBServer:SqlExecution 下同名设置,REST/Frame 一致生效。队列使用各源请求的最严格上限,COMMIT 可进一步收紧;延迟执行保留原调用链的语句、深度和结果行预算,不能用新的 COMMIT 重置计费或提高源请求上限。原请求与提交请求的取消均生效。队列或每次级联展开的 100000 行上限触发 trigger_deferred_limit,锁等待或执行期限超限触发 routine_commit_timeout。
最终状态检查与写入在同一 TableManager 提交锁内串行,仍检查 table_concurrency_conflict;这覆盖同实例、同库的 SQL/TableManager 提交路径,直接 TableStore 调用仍绕过 SQL 触发器。锁等待最多 2401 次、每次至多 50 ms,并有独立 120 秒上限;当前没有行锁死锁图。超时与取消为协作检查,不能强制终止同步 I/O。队列字节预算也不是整笔事务的峰值内存上限,既有级联候选扫描仍可能先物化数据,聚合/JOIN/排序使用独立 SQL 内存治理。
SHOW/DESCRIBE TRIGGER 在既有列后追加 is_constraint、initially_deferred。入队不代表 body 已执行成功;最终提交成功后才报告 committed。持久化或补偿结果未知时沿用下述 routine_commit_unknown 合同,直接嵌入式 COMMIT 也可能抛出 TableTransactionRecoveryException,须关闭重开并按幂等键核对。
外部投递由宿主在源 SQL 结束后显式调用 SonnetDB.Routines.SqlOutboxWorker。构造器创建或校验专用 sql_outbox 关系表;生产者只写稳定的 event_id、topic 和 payload,工作器管理其余状态列。以下为执行一次生产事务并处理一个批次的 C# 示例:db 是已打开的 Tsdb,deliverIdempotently 是宿主提供的 Func<SqlOutboxMessage, CancellationToken, ValueTask>,必须按 EventId 幂等并遵守取消令牌。
using SonnetDB.Routines;
using SonnetDB.Sql.Execution;
var worker = new SqlOutboxWorker(db, new SqlOutboxWorkerOptions
{
MaxBatchSize = 32,
BatchTimeout = TimeSpan.FromSeconds(30),
MaxAttempts = 5,
});
SqlExecutor.ExecuteScript(db, """
CREATE TABLE published_orders (id INT, PRIMARY KEY (id));
BEGIN;
INSERT INTO published_orders (id) VALUES (1);
INSERT INTO sql_outbox (event_id, topic, payload)
VALUES ('order-created-1', 'order.created', '{"orderId":1}');
COMMIT;
""");
SqlOutboxBatchResult result = await worker.ProcessBatchAsync(
deliverIdempotently, cancellationToken);outbox 插入也可放入源表 SQL 触发器,与源 DML 原子提交;外部投递和 ACK 在另一个提交边界。构造器及 ProcessBatchAsync 禁止从活动 SQL 事务、SQL 函数或例程回调中重入。工作器在领取已提交并同步 WAL 后、数据库锁之外执行 handler,不启动后台线程或自动轮询。
状态为 pending、leased、done、dead,时间字段为 Unix 毫秒。领取持久化次数和租约,默认租约一分钟;ACK 必须匹配未过期的租约 token。失败按有上限的指数退避重试,默认最多领取五次,达到次数或正文上限进入 dead。领取、ACK、重试状态返回前均显式同步 outbox WAL;源事件持久性仍遵循源事务配置。状态同步失败返回未知结果并禁止继续使用该表,必须重开恢复。
投递成功但 ACK 前崩溃,或租约过期后重新领取,都可能重复投递相同 EventId,因此合同是 at-least-once;消费者须自行去重,旧 handler 不能确认新租约。done/dead 行保留供审计,清理与人工重投由宿主负责。取消后保留未确认租约以待过期恢复;超时只停止等待,无法强制终止不协作的 handler。
每批默认最多 32 个候选,范围 1..1000;默认 30 秒协作期限,最高两分钟。数量限制约束选出的候选和处理尝试,不限制历史行扫描量,查询仍可能扫描保留的 outbox 表。SqlOutboxBatchResult 分别报告 Claimed、Delivered、Retried、DeadLettered、LeaseLost。完整边界和本次验证状态见 #338 验收记录。
诊断中的 outcome 区分 pending、committed、rolled_back、failed、unknown 和无需事务提交的 completed。待提交记录的 succeeded=false 不计作失败;外层事务失败、显式回滚和请求放弃会结算全部下游动作。审计最多保留 256 条,STATS 的样本数和 P50/P95/P99 只统计这个保留窗口,并非自创建以来的完整历史。RoutineManager.Diagnostics.ExportAudit(Stream, ...) 可把脱敏快照保存为 JSON,既不是持久审计后台服务,也不是 exactly-once 增量订阅;导出 pending 后应在提交结束后重新导出其最终状态。EXPLAIN 只校验定义与对象依赖,不执行 body,也不证明最终业务约束一定成立;没有 EXPLAIN ANALYZE 例程写入。其 transaction_boundary 对延迟约束触发器返回 relational_commit_before_persistence,普通写例程返回 relational_transaction_or_caller_savepoint,只读过程返回 caller_read_committed。
嵌入式沿用 SqlExecutionOptions。服务器配置 SonnetDBServer:SqlExecution:MaxRoutineStatements(1..100000)、MaxRoutineDepth(1..32)、MaxRoutineResultRows(1..100000)对 REST/Frame 一致生效,默认仍为 64/8/10000。每行一个触发动作的 10000 行批量请求需要显式提高语句预算,例如 10064;预算包含嵌套触发器链,必须按实际工作负载留量。提交锁等待及延迟执行期间检查取消;持久化决定开始后不再强行中断同步日志操作。
嵌入式调用可通过 RoutineManager.Diagnostics 读取最近 256 条不含参数值/行内容的调用审计和累计指标。Server /metrics 按数据库公开 sonnetdb_procedure_* 与 sonnetdb_trigger_* 调用、失败和累计耗时指标。稳定错误码包括 procedure_not_found、trigger_not_found、routine_invalid_arguments、routine_recursive_call、trigger_recursion、routine_depth_limit、routine_statement_limit、routine_result_row_limit、trigger_transition_limit、trigger_deferred_limit、routine_commit_timeout、routine_cancelled、routine_forbidden、routine_dependency、trigger_context 和 routine_execution_failed;表约束失败保留其 TableConstraintException.ErrorCode。
M39 #333 的证据入口位于 tests/SonnetDB.Benchmarks:--m39-trigger-evidence 会固定审计 outbox、
派生汇总和状态流转保护 journey,并以 1/100/10,000 行 INSERT、UPDATE、DELETE 记录无触发器、V1 行触发器和客户端候选
statement 参考的吞吐、关系表 WAL/rowstore 有符号差值、托管内存、分配与失败回滚成本。成功样本会核对源表和审计表
的精确内容,失败样本会逐行确认源值与审计 sentinel 均已恢复;候选路径仍只是显式事务参考实现,不增加 SQL 语法。
各关系表仍使用独立 WAL,多表提交现在由 tables/transaction.sdbtxn 协调:先同步带 CRC 的原值日志,
再应用并同步各表 WAL,最后同步完成标记。启动先撤销没有完整完成标记的事务,恢复可重复执行。
日志上限 128 MiB、1024 张表、每表 100 万个行/索引键;超限在应用事务数据前拒绝,生成键预留仍允许留下间隙。
提交或补偿 I/O 结果不明时返回 routine_commit_unknown,停止当前表管理器及已有表句柄的读写,
必须重启恢复并按业务幂等键核对结果;不要直接重试有外部副作用的操作。完成后的日志不得手工删改。
真进程强杀测试证明的是进程崩溃恢复,不替代物理断电、存储控制器故障或分布式 exactly-once 验证。
关系表支持普通二级索引和唯一索引。索引声明随 table schema 持久化,索引内容从 rowstore 派生,打开表或 schema 变更时可重建。
CREATE INDEX idx_devices_tenant ON devices (tenant);
CREATE UNIQUE INDEX ux_devices_serial ON devices (serial);
CREATE INDEX IF NOT EXISTS idx_devices_site ON devices (site, name);
DROP INDEX idx_devices_site ON devices;当前行为:
- 索引名在单表内唯一。
- 索引列必须存在,可包含 1 个或多个列。
- 唯一索引会在
INSERT/UPDATE/ 轻事务提交时校验现有数据和同批数据冲突。 SELECT/UPDATE/DELETE会使用联合索引从首列开始的最长连续等值前缀;索引未覆盖的条件继续对候选行执行完整WHERE过滤。- 连续等值前缀后的下一列为
INT或DATETIME时,<、<=、>、>=可继续作为索引范围。例如索引(a, b, c, d)可服务a = ? AND b = ? AND c >= ? AND c < ?,d和其它条件作为残余过滤。 - 联合索引缺少首列等值或范围条件时不能从中间列开始使用;遇到第一个未绑定列后,后续列只作为残余条件。
- 索引内容不作为第二份权威数据保存;rowstore 是主数据,索引可重建。
INSERT INTO devices (id, name, enabled)
VALUES (1, 'pump-01', TRUE), (2, 'fan-02', FALSE);
INSERT INTO devices (name, enabled)
VALUES ('valve-03', TRUE), ('motor-04', FALSE)
RETURNING id, version;
SELECT id, name
FROM devices
WHERE enabled = TRUE AND id > 1
ORDER BY id DESC
LIMIT 10;
UPDATE devices
SET name = 'pump-01b', retry_count = retry_count + 1
WHERE id = 1;
DELETE FROM devices
WHERE id = 2;
DELETE FROM acquisitions
WHERE guid IN (
SELECT guid
FROM acquisitions
WHERE upload_time < @cutoff
ORDER BY capture_time
LIMIT 500
);当前行为:
INSERT按主键插入;主键已存在时返回错误,不会静默覆盖。INSERT ... RETURNING在同一语句中返回成功插入后的列值;RETURNING *按表 schema 顺序返回全部列。未知列会在写入前报错,不留下部分数据。INSERT ... VALUES ... ON CONFLICT (主键或唯一索引列) DO UPDATE SET column = excluded.column [WHERE predicate] [RETURNING ...]对冲突目标行执行原子更新。excluded指当前候选行,未限定列名指冲突前目标行;可在WHERE同时引用两者。谓词为 FALSE 或 NULL 时不更新、不返回该行,且不计入受影响行数。多行语句按输入顺序返回成功插入或更新的行;同一语句重复更新同一目标行会整句拒绝。ROWVERSION更新由引擎递增,候选行的默认值与自动生成列在冲突检查前确定。- 已发布
v3.1.0不支持关系表ON CONFLICT;当前未发布的main提供DO NOTHING及上述DO UPDATE,首次正式发布版本号仍待决定。DO UPDATE必须显式指定主键或唯一索引列,复合键按索引声明的列顺序匹配。当前main的远程 ADO 轻事务使用服务端会话执行DO UPDATE ... RETURNING,返回行来自实际待提交事务,提交不重放 SQL;实际发布前不能作为已发布包能力。服务端会话绑定数据库和凭据,活动租约为 2 分钟,最多 128 个活动会话;终态保留 2 分钟,可通过GET /v1/db/{db}/sql/transactions/{id}查询,重复提交或回滚同一终态不会再次执行。旧服务端无会话端点时,客户端保留旧协议且在入队前拒绝此语句。 - 会话存放于单个 Server 进程内;跨实例路由需固定到创建会话的实例。服务端重启会丢失未提交缓冲及终态缓存,未收到提交响应时须按会话 ID 查询终态,若服务端不可达或缓存已过期则结果未知。已发布包和 FreeSql Provider 仍需按实际版本与部署形态单独验收。
UPDATE支持把列、字面量、算术和标量函数组合成右值表达式;当前不支持更新主键或显式更新ROWVERSION列。- 关系表联接更新支持
UPDATE target AS t JOIN source AS s ON ... SET column = s.column WHERE ...和UPDATE target AS t SET column = s.column FROM source AS s WHERE ...;WHERE必填,来源限关系表及 INNER/LEFT JOIN。重复来源按声明扫描顺序取首个匹配,RETURNING与影响数每个目标主键只计一次;来源保持只读,触发器及约束按普通 UPDATE 执行。轻事务中目标表已有缓冲写时,后续联接更新会明确拒绝。 SELECT支持*、列投影、字面量投影、标量表达式投影,以及WHERE中的AND/OR/NOT和基础比较。- 关系表
JSON列支持json_value(metadata, '$.site')这类 path 表达式;对象或数组结果会以紧凑 JSON 字符串返回。 WHERE覆盖完整主键等值条件时会走主键读取;二级索引按最长连续左前缀选择,并可在首个未绑定的INT/DATETIME列继续做范围扫描;其它条件在候选行上过滤。ORDER BY支持结果集中的任意列名;LIMIT/OFFSET/FETCH语法与 measurement 查询一致。- 关系表
UPDATE/DELETE的WHERE支持非相关IN (SELECT ...);子查询必须只返回一列,可使用参数、JOIN、派生表、UNION、ORDER BY和分页。结果会在修改目标行前物化,并保持IN/NOT IN对NULL和空集的三值逻辑。 - 写操作的
IN子查询当前只接受普通关系表或 measurement 来源,不支持 document、vector/hybrid search、表值函数或引用待修改外层行的相关子查询。轻事务中如果目标表已有缓冲写,也会明确拒绝对该表执行带IN子查询的后续UPDATE/DELETE,避免子查询看到不一致视图。
关系表查询支持 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN 和 CROSS JOIN。连接两侧可以是关系表、关系视图、物化视图或可解析为关系行的派生表;连接结果仍按关系查询的列投影、谓词和分页规则执行。列冲突、结果类型、NULL 排序及 ORM 能力发现见[标准 JOIN 合同]({{ site.docs_baseurl | default: '/help' }}/standard-join-contract/)。
measurement 与关系表只支持单个 INNER JOIN、TAG 与关系列的等值 ON;WHERE、标量投影、多键排序和分页支持参数绑定。ON 参数/附加谓词、外连接、多 JOIN 和聚合须由 Provider 生成前按 DataSourceInformation 的 MeasurementJoin* 字段检查。详见 GH-Issue #196 合同与验收。
SELECT d.id, d.name, s.name AS site_name
FROM devices AS d
RIGHT JOIN sites AS s ON d.site_id = s.id;
SELECT d.id, s.id AS site_id
FROM devices AS d
FULL JOIN sites AS s ON d.site_id = s.id;
SELECT d.id, r.retry_count
FROM devices AS d
CROSS JOIN retry_policies AS r;当前语义与限制:
RIGHT JOIN保留右侧的全部行;没有匹配的左侧列填充NULL。FULL JOIN保留两侧全部行,未匹配的一侧填充NULL。CROSS JOIN不使用连接谓词,返回两侧行的笛卡尔积;两侧任一为空时结果为空。RIGHT/FULL的未匹配行和匹配行按FROM中的声明顺序处理;同一查询不会为了选择算法而重排结果的声明顺序。CROSS JOIN也按左侧行、再按右侧行的声明顺序产生结果。RIGHT JOIN和FULL JOIN当前使用有界的声明顺序嵌套循环,并保留 SQLNULL扩展语义;INNER/LEFT的 hash、索引和 merge 计划仍可按现有条件选择。CROSS JOIN使用恒真连接条件,不应当用来替代缺失的过滤条件。ON连接条件使用 SQL 三值逻辑:条件为UNKNOWN(包括连接列为NULL)不算匹配;外连接随后按上述规则产生NULL扩展行。WHERE在外连接完成后执行,因此对补出的列直接过滤可能把外连接结果收窄为内连接结果。- 单条语句仍受关系查询的行、内存和取消预算约束;笛卡尔积可能快速增长,应在
WHERE、LIMIT或更窄的输入上使用它。
SqlExecutor.ExecuteScript(...) 和服务端 /sql/batch 支持关系表小批量 DML 轻事务:
BEGIN;
INSERT INTO devices (id, name, enabled) VALUES (3, 'valve-03', TRUE);
UPDATE devices SET enabled = FALSE WHERE id = 1;
DELETE FROM devices WHERE id = 2;
COMMIT;也可以显式写 BEGIN TRANSACTION;ROLLBACK 会放弃当前轻事务中排队的变更。
当前边界:
- 轻事务支持同一数据库内多个关系表的
INSERT/UPDATE/DELETE原子提交与回滚。 - 不支持嵌套事务、measurement / document 写入事务、DDL 事务、
IMPORT JSON或跨数据库事务。在事务上下文内执行 measurement(时序)INSERT/DELETE、文档集合写入、DDL 或文件导入会抛NotSupportedException(这些操作不进入关系表事务缓冲,ROLLBACK无法撤销,故显式拒绝而非静默执行造成"假回滚")。 COMMIT前会校验 NOT NULL、主键、唯一索引、外键、CHECK 和 ROWVERSION 乐观并发列;任一失败时,不会留下已应用的 rowstore / index 变更。- 同一轻事务内的后续
UPDATE会读取并合并已缓冲变更,连续两次SET value = value + 1会累计两次。跨事务仍是 ReadCommitted;事务队列的 UPDATE/DELETE 保留最初读取的行状态,即使没有 ROWVERSION,也会在提交前拒绝已变化或消失的行并返回table_concurrency_conflict。调用方应重试整个业务事务;跨请求的编辑冲突仍建议用 ROWVERSION 和显式版本谓词检测。 - 同一轻事务内的
INSERT/UPDATE/DELETE会按主键归并最终净变化,例如INSERT→DELETE不产生提交写入,UPDATE→DELETE只提交删除,DELETE→INSERT提交替换;每条多行 INSERT/DELETE 只有在全部行求值和校验成功后才写入事务缓冲。 - 稳定约束错误码:
table_unique_violation、table_foreign_key_violation、table_check_violation、table_concurrency_conflict。 - 隔离级别边界:ADO.NET 仅接受默认 /
ReadCommitted轻事务;当前语义是单连接排队、提交时获取表管理器锁并一次性校验/应用,不提供 MVCC、可重复读、序列化隔离或跨进程长事务。
MM4 第一版支持把 measurement 与一个关系表做内连接,用设备、资产、租户、站点等维表补充时序结果:
SELECT t.time, d.name, d.site, t.value
FROM temperature AS t
JOIN devices AS d ON t.device_id = d.id
WHERE d.tenant = 'tenant-1'
AND t.time >= 1713676800000
ORDER BY t.time DESC
LIMIT 100;当前行为:
- 支持
JOIN/INNER JOIN,语义为 inner join。 - JOIN 左侧必须是 measurement,右侧必须是关系表。
ON当前仅支持一个等值条件,且 measurement 侧连接键必须是TAG列;关系表侧可连接主键列或普通列。- measurement 侧
tag = '...'和time范围过滤会先下推到时序查询;关系表侧WHERE条件会先用主键、二级索引等值前缀或数值/时间范围取得候选行,再做完整过滤。 - 输出投影支持
time、measurement tag / field、table 列、字面量及标量算术/函数表达式;有歧义的列名必须使用alias.column。 ORDER BY可引用 JOIN 结果中的 measurement 或 table 列,并在分页前执行。EXPLAIN支持 JOIN 查询,access_path会显示 measurement 与 table 双侧下推路径,例如measurement:tag_index;table:secondary_index;join:hash。
当前限制:
- measurement JOIN 仍只支持
measurement INNER JOIN relation的单个内连接;不支持LEFT JOIN/RIGHT JOIN/FULL JOIN/CROSS JOIN,也不把关系表 JOIN 扩展为 measurement 的外连接或笛卡尔积。 - 不支持 measurement 与 measurement JOIN、table 与 table JOIN、多表 JOIN、子查询 JOIN。
- JOIN 查询暂不支持聚合、
GROUP BY或窗口函数。
MM5 第一版支持 JSON 文档集合作为一等数据模型。集合主数据存放在数据库目录的 documents/ 下,使用 KV-backed 存储、独立 schema 文件和可重建 JSON path 索引;不修改时序 .SDBWAL / .SDBSEG 格式。
CREATE DOCUMENT COLLECTION device_docs;
INSERT INTO device_docs (id, document)
VALUES
('dev-1', '{"type":"pump","site":"north","metrics":{"temp":21.5}}'),
('dev-2', '{"type":"fan","site":"south","metrics":{"temp":18}}');
SELECT id,
json_value(document, '$.type') AS type,
json_value(document, '$.metrics.temp') AS temp
FROM device_docs
WHERE json_value(document, '$.site') = 'north';
UPDATE device_docs
SET document = '{"type":"pump","site":"north","metrics":{"temp":22}}'
WHERE id = 'dev-1';
DELETE FROM device_docs
WHERE id = 'dev-2';元数据与索引:
SHOW DOCUMENT COLLECTIONS;
DESCRIBE DOCUMENT COLLECTION device_docs;
CREATE JSON INDEX idx_device_type ON device_docs ('$.type');
SHOW JSON INDEXES ON device_docs;
DROP JSON INDEX idx_device_type ON device_docs;
DROP DOCUMENT COLLECTION device_docs;JSON 标量函数也可用于关系表的 JSON 列;path 可以使用 SQL 参数:
SELECT id,
json_exists(metadata, @path) AS has_path,
json_array_length(metadata, '$.tags') AS tag_count,
json_contains(metadata, '$.tags', @tag) AS has_tag
FROM devices;当前行为:
- 文档集合固定暴露
id和document/json两个伪列;SELECT *展开为id, document。 INSERT需要提供id与document或json,JSON 文本会用System.Text.Json校验并规范化为紧凑 JSON。- SQL
UPDATE使用SET document = '<json>'做整体替换;局部更新操作符通过 Document HTTP API 或SndbDocumentClient.UpdateOneAsync/UpdateManyAsync调用。 - 单条 SQL
UPDATE/DELETE会先完成候选规划,再用一个 KV 原子 batch 提交;取消、校验失败或批次预算拒绝不会留下部分修改。该 batch 受KvOptions.MaxOverlayEntries和KvOptions.MaxWalBytes限制,超限时会在追加 WAL 前整体拒绝;引擎不会为绕过限制自动拆批,因为拆批会破坏语句原子性。 json_value(document, '$.path')支持$、点属性、$['property']和数组下标,例如$.metrics.temp、$['display-name']、$.tags[0]。json_exists(json, path)返回 path 是否存在;path 指向 JSONnull时仍返回TRUE,path 缺失返回FALSE。JSON 或 path 参数为NULL时返回 SQLNULL。json_array_length(json, path)返回 path 指向数组的元素数(BIGINT);path 缺失或指向 JSONnull时返回 SQLNULL,指向非数组值时返回执行错误。JSON 或 path 参数为NULL时返回 SQLNULL。json_contains(json, path, candidate)判断 path 结果是否包含候选值。候选字符串按普通 SQL 字符串比较;以{或[开头的字符串会按 JSON 对象或数组解析。数组候选按无序子集匹配,对象候选按字段子集匹配;对对象传入普通字符串还可判断字段名是否存在。path 缺失或指向 JSONnull返回FALSE,任一参数为NULL返回 SQLNULL。- 这些函数只接受字符串 JSON/path(候选值可为 SQL 标量),并执行固定资源边界:JSON 文本最多 8,388,608 个 UTF-16 字符,path 最多 1024 个字符和 64 个片段,嵌套/比较深度最多 64 层,被计算长度或参与包含比较的单个数组/对象最多 100,000 个元素/字段;每次
json_contains最多执行 1,000,000 次匹配扫描与结构比较。超限或 JSON/path 无效会返回执行错误,不会静默截断。完整的候选编码、NULL、重复元素、访问容器预算和 JSON path 索引可用条件见[JSON 查询合同]({{ site.docs_baseurl | default: '/help' }}/json-query-contract/)。 CREATE JSON INDEX建立基础 path 等值索引;WHERE json_value(document, '$.type') = 'pump'可走该索引,EXPLAIN的access_path会显示json_path_index。id = '...'会走文档 ID 读取;其它条件走集合扫描后过滤。- 不提供 MongoDB wire/Driver 兼容 API 或跨文档复杂事务;集合 validator 支持 SonnetDB 自有 required/type/range/enum/pattern 子集。
Document Store 也提供私有 JSON HTTP API 与 SndbDocumentClient,用于不想拼 SQL 的应用代码。端点统一位于 /v1/db/{db}/documents/{collection} 下,读操作需要数据库 Read 权限,写操作需要 Write 权限:
POST /v1/db/{db}/documents/{collection}
DELETE /v1/db/{db}/documents/{collection}
POST /v1/db/{db}/documents/{collection}/insert-one
POST /v1/db/{db}/documents/{collection}/insert-many
POST /v1/db/{db}/documents/{collection}/find
POST /v1/db/{db}/documents/{collection}/find-one
POST /v1/db/{db}/documents/{collection}/update-one
POST /v1/db/{db}/documents/{collection}/update-many
POST /v1/db/{db}/documents/{collection}/update-preview
POST /v1/db/{db}/documents/{collection}/delete-one
POST /v1/db/{db}/documents/{collection}/delete-many
POST /v1/db/{db}/documents/{collection}/count
POST /v1/db/{db}/documents/{collection}/distinct
POST /v1/db/{db}/documents/{collection}/aggregate
POST /v1/db/{db}/documents/{collection}/indexes
DELETE /v1/db/{db}/documents/{collection}/indexes/{index}
POST /v1/db/{db}/documents/{collection}/indexes/validate
POST /v1/db/{db}/documents/{collection}/change-feed// insert-one
{ "id": "dev-1", "document": { "site": "north", "kind": "pump" } }
// find:支持 id/ids 快捷条件、filter AST、projection、sort、limit/skip
{ "id": "dev-1" }
{ "ids": ["dev-1", "dev-2"] }
{ "limit": 100, "skip": 0 }
{
"filter": {
"and": [
{ "path": "$.site", "op": "eq", "value": "north" },
{ "path": "$.score", "op": "gte", "value": 5 },
{ "path": "$.tags", "op": "contains", "value": "hot" }
]
},
"projection": [
{ "name": "_id", "path": "_id" },
{ "name": "temp", "path": "$.metrics.temp" }
],
"sort": [{ "path": "$.score", "descending": true }],
"limit": 20,
"skip": 0
}
// update-one:整文档替换
{ "id": "dev-1", "document": { "site": "north", "kind": "pump", "status": "ok" } }
// update-preview / update-one / update-many:同一套局部更新执行器
{
"filter": { "path": "$.site", "op": "eq", "value": "north" },
"update": { "set": { "$.status": "active" }, "inc": { "$.revision": 1 } },
"many": true,
"limit": 20,
"upsert": false
}
// compound + unique + sparse + partial index
{
"name": "ux_site_serial",
"paths": ["$.site", "$.serial"],
"isUnique": true,
"isSparse": true,
"partialFilter": { "path": "$.active", "operator": "eq", "valueScalar": "true" }
}
// change feed:首请求从 now 或 beginning 开始,后续原样回传 resumeToken
{ "startAt": "now", "operations": ["insert", "update", "delete"], "limit": 100 }
{ "resumeToken": "<opaque token>", "operations": ["insert", "update", "delete"], "limit": 100 }
// distinct:按 JSON path 返回标量 distinct 值
{ "path": "$.site" }
// aggregate:SonnetDB-native JSON aggregation pipeline
{
"pipeline": [
{ "$match": { "path": "$.score", "op": "gte", "value": 5 } },
{
"$group": {
"keys": [{ "name": "site", "path": "$.site" }],
"accumulators": [
{ "name": "count", "op": "count" },
{ "name": "total", "op": "sum", "path": "$.score" },
{ "name": "avgScore", "op": "avg", "path": "$.score" }
]
}
},
{ "$sort": [{ "path": "$.total", "descending": true }] }
]
}filter 操作符支持 eq/ne/gt/gte/lt/lte/in/nin/exists/contains 与 and/or/not 组合;path 可写 _id / id、document / json 或 JSON path。exists 会区分 path 缺失与 JSON null:path 存在且值为 null 时仍视为存在。
aggregate 支持 $match / $project / $group / $sort / $limit / $skip / $unwind / $count / $distinct 等价阶段;$group.accumulators[].op 支持 count、sum、avg、min、max、first、last、distinct。SQL 侧也可以直接在 document collection 上使用 GROUP BY json_value(document, '$.path') 与 count/sum/avg/min/max/first/last。
find 支持 cursor 分页:首个请求传 limit(服务器最大 batch size 为 1000),响应中的 continuationToken 不为空时,把它原样放入下一次 find 请求即可继续读取;续页 token 绑定 collection、查询形状、只读快照版本和 15 分钟过期时间,不能与 skip 混用。写入导致集合版本变化后,旧 token 会被拒绝,需要重新发起首个 find 请求。
update-preview 使用与真实提交相同的服务端局部更新执行器,但不写 WAL、不维护索引、不推进 change feed;响应提供逐文档 before/after/isUpsert/changed。索引管理契约支持 compound、unique、sparse、partial 与 TTL 声明,indexes/validate 只读比对主文档与索引条目。Change feed 记录所有通过 Document store 写入点产生的 insert/update/delete,持久化于集合 KV/WAL,事件保留 7 天;resume token 绑定数据库、集合、操作过滤和 document ID 过滤,有效期 24 小时,超出保留窗口返回 410 resume_token_expired。大于 256 KiB 的单个前后镜像会标记 payloadTruncated,但事件元数据与续传序号仍保留。
当前 Document API 契约刻意不实现 MongoDB wire protocol / BSON command,也不承诺官方 MongoDB Driver 直连。OpenAPI 片段见 document-api.yaml。
MM5 第二批支持把本地 JSON 文件作为只读虚拟表查询,或导入到 document collection / 关系表。JSON 文件能力用于临时查询、迁移和批量导入;导入完成后的主数据仍由 SonnetDB 的 document collection 或 table 托管。
SELECT id,
json_value(document, '$.site') AS site
FROM json_each('/data/devices.json', 'array', '$.id')
WHERE json_value(document, '$.enabled') = TRUE;
EXPLAIN SELECT id FROM json_each('/data/devices.ndjson', 'lines');json_each(...) 和兼容别名 json_table(...) 暴露三列:
ordinal:文件内从 0 开始的行号。id:默认读取$.id;也可用第 3 个参数指定 ID path;缺失时使用ordinal。document:规范化后的紧凑 JSON 文本。
导入语法:
CREATE DOCUMENT COLLECTION device_docs;
IMPORT JSON '/data/devices.ndjson'
INTO device_docs
FORMAT LINES
ID PATH '$.device.id';
CREATE TABLE devices (
id INT,
name STRING,
metadata JSON,
PRIMARY KEY (id)
);
IMPORT JSON '/data/devices.json'
INTO devices
FORMAT ARRAY;当前行为:
- 格式支持
AUTO、ARRAY和LINES;AUTO会识别顶层数组 / 单对象 / JSON Lines。 - 导入 document collection 时每条记录整体写入
document,ID 来自ID PATH、默认$.id或ordinal。 - 导入 table 时要求每条记录是对象,并按列名映射到表列;对象 / 数组可写入
JSON列。缺失属性按关系 INSERT 的DEFAULT语义处理,显式 JSONnull仍写入NULL。 - table 导入复用普通关系 INSERT 的单批提交和触发器路径,会统一校验 NOT NULL、主键、唯一索引、外键与 CHECK,并自动生成 ROWVERSION;JSON 中显式提供 ROWVERSION 属性会拒绝整批导入。任一记录或触发器失败时不会留下部分关系行。document collection 导入仍按记录执行 upsert,不承诺整文件原子性。
- JSON 文件虚拟表不维护索引;
EXPLAIN的access_path显示json_file_virtual_table。
关系表 JSON 列也支持基础 path 等值索引:
CREATE TABLE devices (
id INT,
metadata JSON,
PRIMARY KEY (id)
);
CREATE JSON INDEX idx_devices_site
ON devices (metadata, '$.site');
SELECT id
FROM devices
WHERE json_value(metadata, '$.site') = 'north';当前行为:
- 关系表 JSON path 索引只能引用一个
JSON列和一个 JSON path。 - 仅支持
json_value(json_col, '$.path') = literal形式的等值下推;其它谓词仍会扫描过滤。 - path 缺失或结果为
null的行不写入 path 索引。 SHOW INDEXES ON <table>的columns会显示为json_col->$.path;EXPLAIN的access_path会显示json_path_index。
MM6 第一批把 SonnetDB 内置全文引擎接入 JSON 文档集合,全文索引是从 document collection 主数据派生出的可重建索引。当前实现的索引目录由 SonnetDB 托管在 documents/fulltext/ 下,主数据仍以文档集合为准。
CREATE FULLTEXT INDEX ft_logs_message
ON logs ('$.message')
USING unicode;
SHOW FULLTEXT INDEXES ON logs;
DROP FULLTEXT INDEX ft_logs_message ON logs;字段和分词器:
- 字段可写
document/json,表示索引整份 JSON;也可写字符串 JSON path,例如'$.message'、'$.title'。 - 支持分词器:
unicode、cjk、jieba。不写USING时默认unicode。jieba使用 SonnetDB.Core 内置中等中文词库;外部词库加载、.dat编译和索引重建要求见 全文中文词库。 - 同一个全文索引可包含多个字段,搜索时可指定某个字段,或用
*搜索该索引内全部字段。
查询示例:
SELECT id, bm25_score() AS score
FROM logs
WHERE match(ft_logs_message, '$.message', 'pump alarm', 20)
ORDER BY score DESC
LIMIT 20;
SELECT id
FROM logs
WHERE match(ft_logs_all, *, 'pump', 20);当前行为:
match(index_name, field, query[, topK])必须作为WHERE中独立的AND谓词使用;当前一个查询只支持一个全文谓词。topK省略时默认取 100;带分页时会按OFFSET + FETCH/LIMIT预取候选,再执行完整 WHERE、排序和分页。bm25_score()只能在包含match(...)的文档集合查询中用于投影或排序,返回 SonnetDB 全文引擎的 BM25 相关性分数。INSERT/UPDATE/DELETE会同步维护全文索引;索引目录缺失时会从 document collection 主数据重建。EXPLAIN SELECT ... WHERE match(...)的access_path会显示fulltext_index,index_name会显示命中的全文索引名。
MM8 第一批支持在 document collection 上用全文 BM25 与 JSON embedding 数组做融合排序。文档主数据仍归 document collection 管理;全文索引由 SonnetDB 内置全文引擎派生维护,JSON 向量字段按查询时计算距离。
SELECT id,
bm25_score() AS text_score,
vector_distance() AS distance,
hybrid_score() AS score
FROM hybrid_search(
source => logs,
text_index => ft_logs_message,
text_field => '$.message',
text => 'pump alarm',
vector_field => '$.embedding',
vector => [1, 0, 0],
k => 20,
text_weight => 0.6,
vector_weight => 0.4
)
WHERE site = 'north'
ORDER BY score DESC;也可以在 measurement KNN + 知识文档融合结果上连接一个关系维表。Planner 会先把 d.tenant = ... 下推给关系表索引,再用命中的 d.id 收窄 measurement measurement_join_tag 的候选 series:
SELECT measurement.device_id AS device,
d.site AS site,
document_id,
hybrid_score() AS score
FROM hybrid_search(
source => incidents,
documents => knowledge,
vector_field => embedding,
vector => [1, 0, 0],
measurement_join_tag => device_id,
document_join_path => '$.device_id',
text => 'pump alarm'
)
JOIN devices d ON measurement.device_id = d.id
WHERE d.tenant = 'tenant-1'
AND measurement.time >= 1713676800000
AND category = 'fault'
ORDER BY score DESC;当前行为:
source必须是 document collection;text_index可省略但集合中必须只有一个全文索引。text是全文查询文本,vector是查询向量;vector_field默认$.embedding,目标 JSON 值必须是 number array。text_field默认*,可指定全文索引中的 JSON path 字段。metric可选,支持'cosine'、'l2'、'inner_product';默认'cosine'。hybrid_score = text_weight * normalized_bm25 + vector_weight * vector_score;不写权重时两者各占 0.5。- 结果伪列支持
bm25_score()、vector_distance()、vector_score()、hybrid_score(),也可直接投影id、document/json和 JSON 顶层字段名。 WHERE支持对结果伪列或 JSON 顶层字段做基础比较,例如site = 'north';复杂文档过滤可用json_value(document, '$.path')。EXPLAIN的access_path会显示hybrid_search,index_name显示使用的全文索引。- document collection 内融合只读取 document collection 主数据和派生全文索引,不会把文档主数据交给外部全文或向量数据库。
vector_search(...) 用于在 document collection 上执行纯向量检索,不要求全文索引或文本查询。它主要服务 SonnetDB.Data.VectorData adapter,也可直接在 SQL 中使用。
SELECT id,
json_value(document, '$.title') AS title,
vector_distance() AS distance,
vector_score() AS score
FROM vector_search(
source => logs,
vector_field => '$.embedding',
vector => [1, 0, 0],
k => 20,
metric => 'cosine'
)
WHERE site = 'north'
ORDER BY distance;当前行为:
source必须是 document collection;vector_search不把通用记录映射到 measurement。vector_field默认$.embedding,目标 JSON 值必须是 number array,并且维度必须与查询向量一致。metric可选,支持'cosine'、'l2'、'inner_product';默认'cosine'。- 结果伪列支持
vector_distance()、vector_score(),也可投影id、document/json和 JSON 顶层字段名。 WHERE支持对结果伪列或 JSON 顶层字段做基础比较;复杂路径可用json_value(document, '$.path')。EXPLAIN的access_path会显示document_vector_scan(全表暴力扫)或document_vector_index(命中持久 ANN 索引),index_name显示使用的 JSON vector path 或索引名。
默认 vector_search 对整个 collection 做 O(N·dim) 暴力扫。为集合声明持久向量索引后,无 WHERE、按距离升序(默认或 ORDER BY distance)的查询会走 HNSW ANN,亚线性加速:
CREATE VECTOR INDEX idx_logs_embedding ON logs ('$.embedding')
WITH (dimensions = 384, metric = 'cosine', m = 16, ef_construction = 200, ef_search = 64);
DROP VECTOR INDEX idx_logs_embedding ON logs;dimensions必填,须与文档向量数组长度一致;metric支持'cosine'/'l2'/'inner_product'(默认 cosine);m/ef_construction/ef_search为可选 HNSW 参数(默认 16/200/64)。- 索引在
insert/update/delete时增量维护,随集合重开从主数据的持久化向量重建图(崩溃自愈)。缺少该 path、维度不匹配或坏向量字段的文档不进索引。 - 走索引的条件:查询
vector_field等于索引 path、metric与维度均匹配、且无WHERE、无自定义降序ORDER BY。有WHERE时回落暴力扫——暴力扫先按谓词过滤再取 Top-K,ANN 先取 Top-K 会漏掉被过滤器排除但更近的行,语义不等价。
MM8 第二批支持以 measurement 的 VECTOR 字段做 KNN 召回,再通过 measurement tag 与 document collection JSON path 关联知识条目,并可叠加知识文档全文 BM25 与可选知识向量评分:
SELECT measurement.device_id AS device,
document_id,
json_value(document, '$.title') AS title,
measurement_distance() AS m_distance,
bm25_score() AS text_score,
hybrid_score() AS score
FROM hybrid_search(
source => incidents,
documents => knowledge,
vector_field => embedding,
vector => [1, 0, 0],
k => 20,
measurement_join_tag => device_id,
document_join_path => '$.device_id',
document_join_index => idx_knowledge_device,
text_index => ft_knowledge_body,
text_field => '$.body',
text => 'pump alarm overheating',
measurement_weight => 0.7,
text_weight => 0.3
)
WHERE time >= 1713676800000 AND category = 'fault'
ORDER BY score DESC;当前行为:
source是带VECTOR字段的 measurement;documents是关联的 document collection。vector_field默认embedding,也可写measurement_vector_field;vector必须与该列维度一致。measurement_join_tag/join_tag指定 measurement TAG,document_join_path指定知识文档 JSON path;若有同 path 的 JSON index 或显式document_join_index,关联会优先走索引。text可选;提供时会用text_index/text_field读取知识文档全文 BM25。未提供text时仅做 measurement KNN + 关联文档融合。document_vector_field可选;提供时会对知识文档中的 JSON number array 再计算一次向量分数。- 结果伪列支持
measurement_distance()、measurement_score()、bm25_score()、text_score()、document_vector_distance()、document_vector_score()和hybrid_score();vector_distance()/vector_score()在该模式下兼容指向 measurement KNN 分数。 WHERE中 measurementtime/ tag 谓词会下推给 KNN;关系维表谓词会先走主键 / 二级索引候选行并收窄 measurement join tag;剩余谓词可过滤知识文档顶层字段、json_value(document, '$.path')或融合分数。EXPLAIN的access_path会显示hybrid_search_measurement_knn_documents;带关系维表过滤时会追加relation_filter:<table_access_path>。
定义 measurement schema:
CREATE MEASUREMENT IF NOT EXISTS cpu (
host TAG,
region TAG STRING,
usage FIELD FLOAT NULL,
count FIELD INT,
ok FIELD BOOL,
label FIELD STRING NOT NULL
)规则:
TAG列默认为字符串,TAG和TAG STRING等价。FIELD列支持FLOAT、INT、BOOL、STRING、VECTOR(N)、GEOPOINT。- schema 中至少要有一个
FIELD列。 time不属于 schema 定义的一部分。IF NOT EXISTS提供并发安全的幂等创建;同名 measurement 已存在时直接成功并保留现有 schema,不使用本次列定义覆盖它。NULL/NOT NULL可作为 DDL 兼容修饰符出现在列类型后;当前仅保留在 SQL AST 中,执行层不把它持久化为 catalog 约束,也不强制NOT NULL。DEFAULT <expr>目前会被 parser 接受,但执行CREATE MEASUREMENT时会返回明确的DEFAULT暂不支持错误。
稀疏字段语义:
- SonnetDB 的 field 是稀疏的:同一个 measurement 的不同时间点可以携带不同 field 集合。
- 如果某个时间点没有写入某个 field,查询该列时结果为
NULL;这表示“该时间点未记录该字段”,不是 schema 约束失败。 - measurement 不支持关系表 DML 的
DEFAULT形式;VALUES(DEFAULT)与DEFAULT VALUES会明确拒绝。表达缺值时请省略该 field,或在应用侧写入具体值;显式NULL也不是 field 的默认值。
ADO.NET VECTOR(N) 参数使用 float[]、Memory<float> 或 ReadOnlyMemory<float>;DbType 与 GetSchemaTable().ProviderType 为 DbType.Object,结果的 GetValue() 为 float[],GetFieldType() 和 GetSchemaTable().DataType 为 typeof(float[])。各元素是有限的 IEEE 754 float32,空数组和 NaN/Infinity 在客户端拒绝;维度与 VECTOR(N) 不一致时服务端报 sql_error,消息包含“维度不匹配”。null/DBNull.Value 绑定为 SQL NULL;measurement 的 VECTOR 写入要求非空向量,缺值应省略该字段,并与同点其他 field 一起查询以取得 DBNull.Value。centroid(VECTOR) 也返回 float[]。
嵌入式参数直接绑定为向量字面量;REST/NDJSON 与 Protocol=frame-http2 ADO 写入将有限向量格式化为 SQL 数值数组。HTTP/2 ADO 写入经 /v1/db/{db}/sql,原生 /v1/frame SQL 端点只读。只读 Frame 请求支持 float32 little-endian VECTOR 命名参数,结果使用 VECTOR 值标记;Frame 单请求 payload 上限为 132 MiB,SQL 文本上限为 1 MiB。ADO 写入的向量会先展开为 SQL 文本,因此还受该路径请求体与服务端资源限制约束;大向量应按目标服务的请求预算验证,不存在独立的 ADO VECTOR 维度硬上限。measurement 的 UPDATE ... SET embedding = @vector 仅支持已有 VECTOR FIELD 点与 TAG/time 条件,单句最多 256 行、存活替换记录最多 4096 条及 128 MiB 估算字节量(不是 CLR 堆峰值硬上限);稀疏目标行和字段残差明确拒绝,同键 INSERT 在替换后也明确拒绝。关系表仍不支持 VECTOR 列;原生 Frame SQL 写入仍只读。
INSERT INTO cpu (time, host, region, usage, count, ok, label)
VALUES
(1713676800000, 'server-01', 'cn-hz', 0.71, 10, TRUE, 'ok'),
(1713676860000, 'server-01', 'cn-hz', 0.73, 11, TRUE, 'ok')规则:
- 未加引号的
time是保留伪列,表示 Unix 毫秒时间戳;双引号名称(例如"Time")按实际 TAG/FIELD 名精确解析,不作为时间戳伪列。 time省略时会使用当前 UTC 毫秒时间。- 每一行至少需要提供一个
FIELD列值。 TAG列必须是字符串字面量。FIELD FLOAT可以接受整数或浮点字面量。- 目标 measurement 不存在时,
INSERT会按列值自动创建 schema;已有 measurement 缺失列时也会自动补齐。 - SQL
INSERT的未知列默认推断为FIELD,包括字符串列。需要新TAG时,在列列表中写host TAG;也可以用message FIELD显式标注字符串 field。已有列始终按 schema 解释,提示与现有角色不一致时拒绝写入,time不能带提示。Bulk VALUES 快路径采用相同规则;关系表和文档集合不接受列角色提示。 - 已有
INT字段遇到浮点值时会提升为FLOAT;已有FLOAT字段接收整数时会转换为浮点保存,不会降级为INT。 NULL不能作为当前INSERT的显式列值;要表达某个 field 在该时间点缺失,请从列列表中省略它。
查询所有列:
SELECT * FROM cpu WHERE host = 'server-01'显式投影:
SELECT time, host, usage
FROM cpu
WHERE host = 'server-01' AND time >= 1713676800000 AND time < 1713677400000
ORDER BY time ASC标量函数与算术投影:
SELECT abs(-usage), round(usage / 3, 2), sqrt(count), log(count, 10), coalesce(label, 'n/a')
FROM cpu
WHERE host = 'server-01'
SELECT usage + 1 AS next_usage, 2 * 5 + 1 AS constant_value
FROM cpu
WHERE host = 'server-01'单表别名与限定列名:
SELECT c.time, c.host, c."usage"
FROM cpu AS c
WHERE c.host = 'server-01'
ORDER BY c.time DESC
LIMIT 10兼容常见探活查询的字面量投影:
SELECT 1 AS ok FROM cpu LIMIT 1当前行为:
SELECT *会展开为time + 所有 tag 列 + 所有 field 列。- 支持字面量投影(如
SELECT 1 ... LIMIT 1),会按匹配到的时间轴返回常量列。 - 当某个时间点缺少某个 field 时,结果列会返回
NULL。 - 标量函数支持
abs、round、sqrt、log、coalesce、concat、lower、upper、trim、ltrim、rtrim、length/char_length、substring/substr、position(search IN value)、replace、left、right、starts_with/ends_with/contains、regexp_like、date_diff/datediff、date_format/format_datetime/strftime/to_char、modbus_int32、modbus_uint32、modbus_float32及上述日期函数。 - 字符串函数对字符串参数使用 Ordinal 规则;除
concat将 NULL 参数视为空字符串外,字符串参数或位置/长度参数为 NULL 时返回 NULL。substring使用从 1 开始的位置,省略长度时截取到末尾;位置必须大于等于 1,长度和left/right的字符数不能为负数。length返回 .NET UTF-16 字符数(long),不是 UTF-8 字节数。position(search IN value)返回按 UTF-16 计数的 1 基位置,未找到返回 0,空搜索串返回 1;关系表、Document 和时序查询可通过共用的标量函数求值器调用。
常用字符串函数示例:
SELECT trim(label) AS clean_label,
substring(label, 1, 12) AS prefix,
replace(label, '-', '_') AS normalized,
length(label) AS label_length
FROM devices
WHERE starts_with(label, 'pump-') AND contains(label, 'east')- 标量函数可嵌套并接收算术表达式参数;算术表达式也可直接作为顶层投影。
- 支持
FROM measurement [AS] alias单表别名,以及alias.column/alias."Column"限定列名;执行前会校验限定符必须匹配当前别名。 coalesce(...)只会在当前结果行存在时参与求值;它不会额外扩展原始查询的时间轴。- 结果按时间升序返回。
关系查询支持一个或多个非递归 CTE。CTE 按声明顺序展开为现有派生表执行路径,
因此可以在主查询或 IN / EXISTS 子查询中使用,也可以让后一个 CTE 引用前一个 CTE:
WITH west (device_id) AS (
SELECT id FROM devices WHERE region = 'west'
), selected (device_id) AS (
SELECT device_id FROM west WHERE device_id > 100
)
SELECT d.id
FROM devices AS d
WHERE d.id IN (SELECT device_id FROM selected)
ORDER BY d.id;当前边界:
- 非递归
WITH ... AS (SELECT ...)不支持自引用;递归形式见下一节。 - CTE 名称按声明顺序解析,后一个 CTE 可以引用前一个 CTE;重复名称会被拒绝。
- 可用
WITH name (column, ...) AS (...)按位置重命名输出列;列数必须等于查询结果列数,名称保留拼写且不得仅大小写不同,普通引用忽略大小写,双引号引用精确匹配。即使查询返回空行也校验列数。重命名发生在 CTE 自身排序、分页和集合运算之后;后续 CTE、JOIN、IN与EXISTS使用新列名。 - CTE 不新增独立 planner,继续复用
FROM (SELECT ...)、关系 JOIN 以及IN/EXISTS子查询的现有执行和相关性语义。 - measurement 和文档 SELECT 可作为 CTE 来源;不支持的来源/查询组合仍遵守各自 SELECT 执行路径的限制。跨服务端 REST/Frame parity 未在此处声明。
一个递归 CTE 可按 anchor UNION ALL recursive_member 逐层遍历关系表。UNION ALL 保留重复行;UNION 对全部已见行去重,可使包含环的图收敛:
WITH RECURSIVE device_tree (id, depth) AS (
SELECT id, 0 AS depth FROM devices WHERE id = 1
UNION ALL
SELECT child.id, device_tree.depth + 1 AS depth
FROM devices AS child
JOIN device_tree ON child.parent_id = device_tree.id
)
SELECT id, depth FROM device_tree ORDER BY id;递归成员每轮只读取上一层结果;最终 SELECT 读取所有层。支持空 anchor、分支、参数绑定、最终结果排序和分页。同一 WITH RECURSIVE 中可在递归定义之前或之后声明普通 CTE;前置普通 CTE 可供 anchor 和递归成员引用,后置普通 CTE 可读取最终递归结果,名称按声明顺序解析。CTE 输出列名列表的列数必须与 anchor 和递归成员一致,非空值的列类型必须保持一致。每个查询最多展开 64 层、累计保留 100,000 行和约 32 MiB 行数据;每轮候选输出最多 100,000 行,超限时报错,不返回部分结果。EXPLAIN 报告递归工作表及这些上限。
当前只支持一个递归 CTE 定义,以及恰好一次直接自引用的普通 SELECT/JOIN/WHERE 递归成员;不支持互递归、成员内子查询、聚合、DISTINCT、ORDER BY 或分页。递归定义内也不能排序或分页;应在最终 SELECT 指定。UNION ALL 遇到环会达到上限并报错;需要收敛时使用 UNION 或在递归成员中限制访问条件。每轮候选上限通过结果分页提前探测。关系表和关系 JOIN 路径在保留投影行、JOIN 构建行和普通 CTE 派生表副本前,还按保守行大小估算检查单轮 32 MiB 与当前查询/数据库内存预算;取消会在逐行处理时生效。此预算不等于 CLR 堆峰值承诺,表读取与表达式求值可在收费前分配临时对象;仍须遵守实例级资源治理与调用方超时。
关系查询支持 UNION、UNION ALL、INTERSECT 和 EXCEPT。各分支必须返回相同的列数;复合结果可以在最后一个分支之后使用统一的 ORDER BY、LIMIT 或 OFFSET/FETCH:
SELECT id FROM active_devices
UNION ALL
SELECT id FROM recently_seen
INTERSECT
SELECT id FROM managed_devices
ORDER BY id;UNION、INTERSECT 和 EXCEPT 会按集合语义去重,UNION ALL 保留重复行。INTERSECT 优先于 UNION / EXCEPT,同优先级运算从左到右应用。INTERSECT ALL 和 EXCEPT ALL 不在本次合同内。
UNION ALL 在没有最终 ORDER BY 时按分支书写顺序保留各分支的行顺序;有最终排序时以排序结果为准。INTERSECT 和 EXCEPT 按整行逐列比较,两个 NULL 在集合去重中视为相等。结果列名取第一个分支;各分支列数必须相同,声明类型或可推断的非 NULL 值类型必须逐列相同。NULL 可与任意已知列类型组合;不同类型请显式 CAST 为同一类型,避免隐式数值转换造成精度损失。空结果分支也按声明类型检查。
集合运算的保留行及去重/交差所用哈希集合受查询或数据库的阻塞算子内存预算约束;超限时报错,不返回部分结果,此路径不使用 spill。预算根据行值与集合结构估算,在分支执行完成后对结果准入,并不是 CLR 堆峰值硬上限;分支扫描与表达式计算仍受各自执行路径的资源约束。
分页子句(兼容两种风格):
-- SQL 标准风格
SELECT time, host, usage
FROM cpu
WHERE host = 'server-01'
ORDER BY time ASC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- MySQL/PostgreSQL 常见风格
SELECT time, host, usage
FROM cpu
WHERE host = 'server-01'
ORDER BY time DESC
LIMIT 10 OFFSET 20;说明:
- 支持
ORDER BY time [ASC|DESC],排序会在分页前应用;当前要求查询结果中包含time列。 - 支持
OFFSET n(仅跳过,不限制返回行数)。 - 支持
FETCH FIRST|NEXT n ROW|ROWS ONLY。 - 支持
LIMIT n [OFFSET m]兼容语法。 ORDER BY/OFFSET/FETCH/LIMIT作用在最终结果集(投影/聚合之后)。
聚合函数按自身语义声明可接受的 FIELD 类型:
| 聚合函数 | 可接受类型 | 结果与规则 |
|---|---|---|
count(*) / count(field) |
所有 FIELD 类型 | 返回行数或字段存在数(long)。 |
first(field) / last(field) |
Float64、Int64、Boolean、String、Vector、GeoPoint | 按时间戳选择首值/末值,保留原始结果类型。 |
min(field) / max(field) |
Float64、Int64、Boolean、String | 字符串固定使用 StringComparison.Ordinal;布尔按 false < true。 |
mode(field) |
Float64、Int64、Boolean、String | 出现次数相同则选择最小值;字符串仍按 Ordinal。 |
distinct_count(field) |
Float64、Int64、Boolean、String | 返回 HyperLogLog 基数估算(long)。 |
sum / avg / stddev / variance / spread |
Float64、Int64、Boolean | Boolean 延续历史数值转换规则(false=0、true=1)。 |
median / percentile / p50 / p90 / p95 / p99 |
Float64、Int64、Boolean | 数值分位统计。 |
histogram / tdigest_agg / pid / pid_estimate |
Float64、Int64、Boolean | 数学统计或控制聚合,不接受字符串。 |
centroid |
Vector | 返回各维均值向量。 |
trajectory_* |
GeoPoint | 返回轨迹距离、中心、边界框或速度统计。 |
选择器与分类聚合可以直接用于状态字符串和布尔遥测:
SELECT time,
first(status) AS first_status,
last(status) AS last_status,
mode(status) AS common_status,
distinct_count(status) AS status_kinds
FROM device_state
WHERE device_id = 'device-01'
AND time >= 1713676800000
AND time <= 1713680400000
GROUP BY time(60s)sum(status)、avg(status) 等数学聚合仍会报错,并明确提示该函数需要数值字段。
示例:
SELECT sum(usage), avg(usage), min(usage), max(usage)
FROM cpu
WHERE host = 'server-01'count(*) 与 SQL 兼容写法 count(1) 也受支持:
SELECT count(*) FROM cpu WHERE host = 'server-01'
SELECT count(1) FROM cpu WHERE host = 'server-01'
SELECT count(*) + 1 AS rows_with_sentinel FROM cpu WHERE host = 'server-01'
SELECT round(avg(usage) + 0.25, 2) AS adjusted_average FROM cpu WHERE host = 'server-01'
count(*)计的是行/时刻数:同一 series 下多个 field 列写在同一时间戳算作一行,不同时间戳(含只写了部分 field 的稀疏行)取并集去重。多 series 场景下不同 series 的同一时间戳属于不同的行。count(field)则只计该 field 列有值的时刻数。
标准聚合支持单字段 DISTINCT 输入:count(DISTINCT field)、sum(DISTINCT field)、avg(DISTINCT field)、min(DISTINCT field) 和 max(DISTINCT field) 会在每个查询结果/时间桶中先按 SQL 值相等规则去重,再忽略 NULL 求值;COUNT(DISTINCT *) 明确拒绝。多表达式 DISTINCT、排序集合聚合和窗口 DISTINCT 不在当前合同内。
按时间桶聚合:
SELECT avg(usage) AS mean, count(usage)
FROM cpu
WHERE host = 'server-01'
GROUP BY time(1m)当前限制和真实行为:
- 仅支持
GROUP BY time(duration)。 - 仅可用于聚合查询。
- 不支持
GROUP BY host这类按列分组。 - 可在投影中显式写
time(或time AS bucket)返回桶起始时间;不会自动添加该列。 - duration 例子:
1000ms、30s、1m。
DELETE FROM cpu
WHERE host = 'server-01' AND time >= 1713676800000 AND time <= 1713677400000也可以只按 tag 或只按时间范围删除:
DELETE FROM cpu WHERE host = 'server-01'
DELETE FROM cpu WHERE time >= 1713676800000 AND time <= 1713677400000当前删除语义:
- 删除底层通过 tombstone 实现,不会原地改写旧 segment。
- 后续查询会过滤 tombstone 覆盖的点。
- compaction 会逐步消化已删除数据。
常见保留策略也可以直接写成相对时间:
DELETE FROM cpu
WHERE time >= now() - 30d虽然解析器支持更多表达式形态,但当前执行器的稳定支持范围是:
- tag 等值条件,例如
host = 'server-01' time的范围比较,例如time >= 1713676800000 AND time < 1713763200000,或者time >= now() - 1d AND time < now() + 1d- 多个条件使用
AND连接 - 关系表和 measurement 过滤支持
value BETWEEN lower AND upper/NOT BETWEEN(上下界 inclusive;任一边或 value 为NULL时结果为UNKNOWN)。 - 字符串过滤支持
ILIKE/NOT ILIKE,使用 invariant、OrdinalIgnoreCase 规则并保留%/_/\\LIKE 通配符语义。
当前不建议在生产示例中使用:
OR- tag 不等式
- field 条件过滤,例如
usage > 0 - 混合聚合列与普通列,例如
SELECT host, sum(usage) ...
这些写法中的不少在当前版本会直接报错。
SHOW MEASUREMENTS / SHOW TABLES / SHOW VIEWS / SHOW MATERIALIZED VIEWS / SHOW PROCEDURES / SHOW TRIGGERS
SHOW MEASUREMENTS 列出当前数据库中所有时序 measurement,SHOW TABLES 列出当前数据库中所有关系表,两者都按字典序升序返回单列 name。SHOW VIEWS 按名称升序返回 name 与 created_utc。SHOW MATERIALIZED VIEWS 返回名称、刷新状态、定义/活动代际、行数、最近成功刷新时间与错误。SHOW PROCEDURES 与 SHOW TRIGGERS [ON table] 的列合同见前文对应章节。
SHOW MEASUREMENTS;
SHOW TABLES;
SHOW VIEWS;
SHOW MATERIALIZED VIEWS;
SHOW PROCEDURES;
SHOW TRIGGERS ON devices;| name |
|---|
| cpu |
| mem |
列出指定关系表的二级索引:
SHOW INDEXES ON devices;| 列 | 类型 | 说明 |
|---|---|---|
index_name |
string | 索引名 |
is_unique |
bool | 是否唯一索引 |
columns |
string | 逗号分隔的索引列 |
created_utc |
string | UTC ISO-8601 创建时间 |
描述指定关系表的列结构,按 CREATE TABLE 声明顺序返回:
| 列 | 类型 | 说明 |
|---|---|---|
column_name |
string | 列名 |
data_type |
string | int64 / float64 / boolean / string / datetime / blob / json |
is_nullable |
bool | 是否允许 NULL |
is_primary_key |
bool | 是否属于主键 |
ordinal |
int64 | 声明顺序 |
column_default |
string/null | 规范化默认表达式;没有默认值时为 NULL |
is_auto_increment |
bool | 是否为数据库自动分配递增整数的列 |
DESCRIBE TABLE devices;返回逻辑视图的名称、SELECT 定义、直接依赖和 UTC 创建时间:
DESCRIBE VIEW active_devices;| 列 | 类型 | 说明 |
|---|---|---|
name |
string | 视图名称 |
definition |
string | 不含 CREATE VIEW ... AS 前缀的 SELECT SQL |
dependencies |
string | 按字典序排列、逗号分隔的直接数据源 |
created_utc |
datetime | UTC 创建时间 |
返回物化视图定义、依赖、活动代际和最近刷新状态;完整列合同见前文“物化视图”。
DESCRIBE MATERIALIZED VIEW active_device_cache;描述指定 measurement 的列结构,按 CREATE MEASUREMENT 声明顺序返回三列:
| 列 | 类型 | 说明 |
|---|---|---|
column_name |
string | 列名 |
column_type |
string | tag 或 field |
data_type |
string | float64 / int64 / boolean / string |
关键字 MEASUREMENT 可省略,DESC 是 DESCRIBE 的兼容别名。
DESCRIBE MEASUREMENT cpu;
DESCRIBE cpu; -- 等价
DESC cpu; -- 等价| column_name | column_type | data_type |
|---|---|---|
| host | tag | string |
| usage | field | float64 |
若指定 measurement 不存在,会抛出 InvalidOperationException。
EXPLAIN 返回一组 key / value 结果行,用于估算查询会扫描的 series、segment、block 与行数。
EXPLAIN SELECT usage
FROM cpu
WHERE host = 'server-01' AND time >= now() - 1d;
EXPLAIN SHOW MEASUREMENTS;
EXPLAIN SHOW INDEXES ON devices;
EXPLAIN DESCRIBE MEASUREMENT cpu;当前支持范围:
SELECT ...SHOW MEASUREMENTS/SHOW TABLES/SHOW VIEWS/SHOW MATERIALIZED VIEWS/SHOW DOCUMENT COLLECTIONSSHOW INDEXES ON <table>/SHOW JSON INDEXES ON <collection>/SHOW FULLTEXT INDEXES ON <collection>DESCRIBE [MEASUREMENT] <name>/DESC <name>DESCRIBE TABLE <name>DESCRIBE VIEW <name>DESCRIBE MATERIALIZED VIEW <name>DESCRIBE DOCUMENT COLLECTION <name>
当前不支持对 INSERT、DELETE、CREATE、DROP、用户/授权/Token 控制面 SQL 做 EXPLAIN。
返回字段包括 database、statement_type、measurement、matched_series_count、estimated_segment_count、estimated_block_count、estimated_scanned_rows、estimated_memtable_rows、estimated_segment_rows、has_time_filter、tag_filter_count、access_path 与 index_name。关系表查询的 access_path 可能是 primary_key、secondary_index、secondary_index_prefix、secondary_index_range、json_path_index 或 table_scan;文档集合查询可能是 document_id、json_path_index、fulltext_index 或 document_scan;JSON 文件虚拟表会显示 json_file_virtual_table。
控制面 SQL 仅在服务端模式可用。
CREATE USER alice WITH PASSWORD 'pa$$'
CREATE USER admin2 WITH PASSWORD 'secret' SUPERUSER
ALTER USER alice WITH PASSWORD 'new-password'
DROP USER aliceCREATE DATABASE metrics
DROP DATABASE metrics
SHOW DATABASESGRANT READ ON DATABASE metrics TO alice
GRANT WRITE ON DATABASE metrics TO alice
GRANT ADMIN ON DATABASE * TO admin2
REVOKE ON DATABASE metrics FROM aliceSHOW USERS
SHOW GRANTS
SHOW GRANTS FOR alice
SHOW TOKENS
SHOW TOKENS FOR alice
ISSUE TOKEN FOR alice
REVOKE TOKEN 'tok_abcdef'说明:
SHOW TOKENS只返回 Token 元数据,不返回明文。ISSUE TOKEN FOR ...会在结果里一次性返回明文 Token。REVOKE TOKEN 'tok_xxx'按 token id 吊销。
| 端点 | 用途 |
|---|---|
POST /v1/db/{db}/sql |
单条 SQL,主要用于数据面;admin 也可通过它执行部分控制面语句 |
POST /v1/db/{db}/sql/batch |
批量 SQL 脚本 |
POST /v1/sql |
专用控制面 SQL 端点,仅 admin |
GET /v1/db/{db}/modbus |
Modbus runtime、source、endpoint 与 binding 概览,需要数据库 Read |
GET /v1/db/{db}/modbus/writes |
Endpoint 外部写待审批队列,需要数据库 Admin |
GET /v1/db/{db}/modbus/write-audit |
Endpoint 写治理事件,需要数据库 Admin |
POST /v1/db/{db}/modbus/writes/{requestId}/approve |
批准 staged 写,需要数据库 Write + Admin |
POST /v1/db/{db}/modbus/writes/{requestId}/reject |
拒绝 staged 写,需要数据库 Admin |
readonly:仅查询readwrite:可写入和查询admin:可管理数据库、执行控制面 SQL、进入完整管理能力
- [批量写入]({{ site.docs_baseurl | default: '/help' }}/bulk-ingest/)
- [ADO.NET 参考]({{ site.docs_baseurl | default: '/help' }}/ado-net/)
- [CLI 参考]({{ site.docs_baseurl | default: '/help' }}/cli-reference/)