ZH Queries - AlanDevise/TsGate GitHub Wiki

Home · GitHub

English | 简体中文

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 发布说明。