ZH Queries - AlanDevise/TsGate GitHub Wiki
2.1.3 已通过 GitHub 源码/标签和 Maven Central 发布,同 SHA CI 与公开制品核验通过。 当前坐标为 2.1.3;详见 2.1.3 发布说明.
2.1.3 修订
2.1.3 收紧结构化过滤参数校验,在整批预检前归并 IoTDB 物理表名的大小写变体,在测试与 CI 中核对后端登记,拒绝 IoTDB/InfluxDB 3 的重复结果键,并在复制输入前检查直接 adapter 批写的记录数。公开 API、Template → SPI → adapter 主干、Java 17/客户端/服务端基线与配置默认值均保持。 2.1.3 发布说明.
四个内置后端在数据库 I/O 前统一收紧结构化过滤参数校验:EQ、NE、GT、GE、LT、LE 必须恰好一个操作数,BETWEEN 必须恰好两个,IN 至少一个。直接构造的 QueryFilter 必须有非空白列名与运算符,操作数不得包含 null、Float.NaN/Double.NaN 或 Float/Double 正负无穷。非法过滤返回 ARGUMENT_ERROR,不再静默丢弃多余操作数、把 null 转成字符串 'null',或向服务端发送非有限数字。字符串 "null"、"NaN"、"Infinity" 仍是普通文字值;后端原有数值精度与范围规则不变。
链式 builder 的可选过滤行为保留:缺失的可选参数与空集合仍跳过,集合中的 null 仍被清理,全为 null 的集合不添加过滤条件;清理后实际保留的条件须满足上述操作数规则。直接模型则按提供的内容校验。统一 query、count,以及链式 list()、offset 分页的计数请求均使用此校验;count() 仍在私有副本中忽略分页和游标设置,原生 SQL 不变。
在公共参数校验阶段,非法过滤先返回 ARGUMENT_ERROR,包括同时请求后端不支持的严格游标的 query。有效参数请求不支持的能力时,仍按原契约返回 UNSUPPORTED_OPERATION;其他已有入口、生命周期及后端专属校验顺序不变。
重复返回名:2.1.3
IoTDB 在读取任何结果行之前拒绝 SDK 输出列名的精确重复,空结果中重复的列元数据也会报错。InfluxDB 3 拒绝成功查询响应中同一 JSON 对象内精确重复的键,包括前值为 null、或重复出现在后续行的情况。这些错误均返回 QUERY_ERROR,整次查询失败,不返回部分行或成功计数。统一 query、count 与适配器 executeQuery(sql) 使用这些解码保护。
解码时,大小写不同的返回名继续作为不同键保留;这不改变 IoTDB 结构化查询输入与写入的小写物理身份规则。原生 SQL 仍原样传递:服务端可能先拒绝重复 alias;如果服务端返回有歧义的输出,适配器解码阶段会拒绝。直接使用借出的原生客户端不经过此适配器解码器。InfluxDB 1.x/openGemini 原有重复列及 tag/field 冲突检查保持不变。范围与验证状态见 2.1.3 发布说明。
2.1.2 的聚合输出名
结构化聚合的输出名在按所选后端规则规范化后必须唯一。检查集合包含分组 tag、每个聚合 alias,以及仅在 groupByTime 生成窗口列时加入的 window_start。重复 alias、重复分组 tag、tag 与 alias 冲突、生成窗口列冲突,均在 HTTP 请求或借用 session 前返回 ARGUMENT_ERROR。直接 TSDBAdapter.query/count、链式 list 和 offset 分页总数使用相同规则。
InfluxDB 1.x、InfluxDB 3 与 OpenGemini 保留带引号标识符的大小写,total 与 TOTAL 可以不同;IoTDB 使用小写物理列身份,因此二者冲突。没有生成窗口时,window_start 可以作为普通聚合 alias。已有不支持的游标、排序及时间区操作保留错误分类与优先级;本校验不增加这些能力,也不改写原生 SQL。对同一字段多次聚合时,应分别使用 temperature_avg、temperature_max 等唯一 alias。
本页涉及的输入校验、配置快照和初始化失败策略修复纳入 2.1.0;详见 2.1.0 变更记录。
InfluxDB 1.x 与 OpenGemini 的聚合响应还包含隐式物理 time 列,因此精确命名为 time 的聚合 alias 或分组 tag 在 I/O 前被拒绝;大小写不同的 Time 仍可使用。此协议专属保留名不扩大到 IoTDB 或 InfluxDB 3。
简单链式查询
TGTemplate.query(Class<T>) 返回 com.alandevise.tsgate.core.TGQueryBuilder<T>。示例使用已注入的 TGTemplate 变量 tgTemplate;每次查询创建新的 builder,该对象可变,不应跨线程共享。
import com.alandevise.tsgate.core.TGQueryBuilder;
TGQueryBuilder<AccrueRecord> query = tgTemplate.query(AccrueRecord.class);
List<AccrueRecord> rows = tgTemplate.query(AccrueRecord.class)
.database("tsdb")
.whereTag("device_code", "device_001")
.timeRange(startTime, endTime)
.orderByTimeDesc()
.limit(20)
.list();
直接调用 adapter 的查询校验
四个后端的 TSDBAdapter.query(database, query) 都执行相同的基础校验,包括调用方自行构造的 TSDBQuery。query 为 null、显式 limit <= 0、offset < 0 或 startTime > endTime 时,在数据库 I/O 前返回 ARGUMENT_ERROR,不会静默丢弃非法分页参数。limit 为 null 表示不在 SQL 中分页,后端结果限额仍然生效;offset 为零、起止时间相等均合法。原生 executeQuery(sql) 保留独立的 SQL 与结果限额契约。
count() 仍先忽略分页和游标设置再计数,但拒绝逆序时间范围。公共 SPI 默认 count(null) 返回 ARGUMENT_ERROR,包括使用该默认实现的第三方 adapter,不再产生未经统一分类的空指针错误。链式页大小、单条分页探测额度及后端专有的不支持操作语义保持原有契约。
OpenGemini 适配仅将服务端精确的查询错误 measurement not found 在明确指定 measurement 的结构化统一 query(...) 中转为空结果,在 count(...) 中转为零;database、retention policy、权限及其他查询错误保留。原生 executeQuery(...) 的单条语句可能涉及多个 measurement 或子查询,因此保留查询错误;直接调用借用的兼容 org.influxdb.InfluxDB 时,也保留服务端错误 DTO。写入确认后,新 measurement 或 tag series 可能暂未对查询可见;业务在读侧轮询时应设置明确截止时间,并匹配具体期望数据点。每次 adapter 查询仍只发送一次请求。详见 OpenGemini 接入指南。
字段子集查询
public class ValueStatusResult {
private Double value;
private Long status;
}
List<ValueStatusResult> rows = tgTemplate.query(AccrueRecord.class)
.database("tsdb")
.select("value", "status")
.whereTag("source", "annotation-pojo")
.timeRange(startTime, endTime)
.orderByTimeAsc()
.limit(100)
.list(ValueStatusResult.class);
FIELD 与多字段排序
以下 FIELD 排序功能适用于 IoTDB 和 InfluxDB 3;InfluxDB 1.x 只支持按时间排序。FIELD 可以作为第一排序列,也可以追加在时间列之后组成多字段排序。排序声明按调用顺序生成 SQL ORDER BY
;后一个排序列只在前面的排序列值相同时参与比较。
List<AccrueRecord> rows = tgTemplate.query(AccrueRecord.class)
.database("tsdb")
.timeRange(startTime, endTime)
.orderByTimeAsc()
.thenByFieldDesc("value")
.thenByFieldAsc("status")
.limit(100)
.list();
上述查询会生成 ORDER BY time ASC, value DESC, status ASC。如果 FIELD 需要作为第一排序列,使用
orderByFieldAsc(field) 或 orderByFieldDesc(field);orderBy... 会替换已有排序,thenByField...
会追加或更新次级排序。
FIELD 排序支持普通 list()、page(pageNum, pageSize) 的 limit/offset 分页和 strictCursorPage(); 单字段时间游标
page() 以及聚合查询不支持 FIELD 排序,并会明确抛出不支持操作异常。 严格游标会按照每个 SortSpec 的方向生成对应的字典序条件,因此支持诸如
ORDER BY time DESC, device_code ASC 的混合排序。严格分页会自动在自定义排序末尾补齐缺失的 time + 全部 @TGTag 列,
补充列沿用第一项排序的方向;已经指定的列保留其方向。POJO 必须声明该表完整的 tag 键,排序与游标列当前仍要求非空。
聚合查询
public class AccrueAggregateResult {
private Long windowStart;
private String deviceCode;
private Double avgValue;
private Double maxValue;
private Long sampleCount;
}
List<AccrueAggregateResult> rows = tgTemplate.query(AccrueRecord.class)
.database("tsdb")
.whereTagIn("device_code", List.of("device_001", "device_002"))
.where("value", OperatorEnum.GE, 0)
.timeRange(startTime, endTime)
.groupByTime("5m")
.groupByTag("device_code")
.aggregate("value", AggregationFunctionEnum.AVG, "avg_value")
.aggregate("value", AggregationFunctionEnum.MAX, "max_value")
.aggregate("value", AggregationFunctionEnum.COUNT, "sample_count")
.timeZone("+08:00")
.orderByTimeAsc()
.limit(100)
.list(AccrueAggregateResult.class);
groupByTime 只接受正整数及 ms/s/m/h/d 单位(例如 5m),纯数字按毫秒解释;小数、负数、零、复合表达式和超出 long 毫秒范围的窗口会在生成 SQL 前拒绝。
链式窗口聚合返回的 window_start 统一为毫秒时间戳,可使用 Long、Instant 或 Date 承接。
InfluxDB 返回不带时区后缀的窗口时间时,适配器按 UTC 解析,不受应用 JVM 默认时区影响。
IoTDB / InfluxDB 3 窗口时区遵循以下规则(InfluxDB 1.x 的限制见后端能力表):
- 默认 UTC。
UTC、+08:00等固定偏移使用固定时长窗口,可不设置时间范围。 America/New_York、Asia/Shanghai等地区时区需要显式且有界的timeRange(start, end)。- 地区时区的
1d按查询日期的本地午夜切分;跨夏令时的一天可以是 23 或 25 小时。Nd从本地 1970-01-01 日期按 N 天对齐,边界使用各自日期的真实偏移。 - 为兼容两种后端,适配层按日历计算边界并生成有界 CASE 分组;不修改借出的 IoTDB Session 时区,也不依赖某个新版数据库特有函数。最多生成 10000 个窗口,超出时报
ARGUMENT_ERROR。 - 地区时区的
ms/s/m/h窗口使用查询开始时的偏移;若时间范围跨越偏移变化则明确报UNSUPPORTED_OPERATION。需要这类跨越查询时,可使用 UTC / 固定偏移,或改用自然日窗口。 - 查询范围仍按
start <= time <= end过滤,窗口边界采用左闭右开。若不希望包含下一日午夜,应将结束值设为下一日午夜前 1 毫秒。
例如 .timeRange(start, end).groupByTime("1d").timeZone("America/New_York") 在 2026-03-08 和 2026-11-01 分别使用 23 小时和 25 小时自然日,不再套用 1970 年的固定偏移。
链式查询注意事项
- 链式方法主要是收集查询条件,整体没有严格顺序限制;
list()、page()、page(pageNum, pageSize)会真正执行查询,应放在最后调用。 database、select、timeRange、limit、orderByTimeAsc/Desc等配置类方法后调用会覆盖前调用。where、whereTag、whereTagIn、groupByTag、aggregate等条件类方法会累加;当前普通条件之间是AND关系。- 时间游标分页使用
page(),页大小来自limit();传统 offset 分页使用page(pageNum, pageSize),不建议再混用limit()、offset()。 - 每次查询建议重新调用
tgTemplate.query(...)创建 builder,不跨线程或跨请求复用。
稳定的默认聚合别名
省略聚合别名时,使用字段名、下划线和经 Locale.ROOT 转小写的函数名,例如 MIN(value) 默认别名始终为 value_min,不随 JVM 默认语言环境变化。业务显式指定的别名保持不变。
← 写入、模型与错误处理 · 分页与原生查询 →
2.1.0 中的 OpenGemini
TsGate 2.1.0 包含独立的 OpenGemini adapter 与 starter。配置、默认引擎边界及准确版本测试见 OpenGemini 接入指南,发行说明见 2.1.0 发布说明。