Skip to content

EN Queries

Alan Zhang edited this page Oct 3, 2026 · 3 revisions

Home · GitHub

English | 简体中文

TsGate 2.1.0: This guide uses com.alandevise.tsgate.*. Upgrading from 2.0.0 requires updating imports, reflection names and package scanning, then recompiling. Central 2.0.0 retains com.alandevise.tsdb.*. See migration steps and release status.

The validation, configuration-snapshot and startup-failure-policy fixes documented here are included in 2.1.0. See 2.1.0 change history.

Basic fluent queries

TGTemplate.query(Class<T>) returns com.alandevise.tsgate.core.TGQueryBuilder<T>. The examples use the injected TGTemplate variable tgTemplate. Create a new builder for each query; the builder is mutable and is not intended to be shared across threads.

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();

Direct adapter query validation

The same basic validation applies to TSDBAdapter.query(database, query) for all four backends, including caller-created TSDBQuery models. A null query, nonpositive explicit limit, negative offset, or startTime > endTime produces ARGUMENT_ERROR before database I/O. Invalid pagination is never silently discarded. A null limit leaves SQL unpaged while backend result limits still apply; offset zero and equal time bounds are valid. Native executeQuery(sql) retains its separate SQL and result-limit contract.

count() continues to ignore pagination and cursor settings before counting; reversed time bounds are rejected. The shared SPI default count(null) returns ARGUMENT_ERROR rather than an unchecked null-pointer failure, including for third-party adapters that use the default implementation. Fluent page sizes, pagination-probe allowances and backend-specific unsupported operations retain their existing contracts.

For the OpenGemini adapter, only the exact backend query error measurement not found becomes a successful empty result in structured common query(...), or zero in count(...), whose measurement is explicit. Database, retention-policy, permission and other query errors retain their errors. Native executeQuery(...) preserves query errors, since one statement can involve multiple measurements or subqueries; the borrowed compatible org.influxdb.InfluxDB also retains the server's error DTO. A new measurement or tag series may be temporarily invisible after a write acknowledgment; business read-side polling must have an explicit deadline and match the expected point. Each adapter query remains one request. See OpenGemini integration.

Column projections

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 and multi-column sorting

FIELD sorting applies to IoTDB and InfluxDB 3; InfluxDB 1.x supports time sorting only. A FIELD may be the first sort key or follow time in a multi-column ordering. Calls produce SQL ORDER BY keys in declaration order; later keys are compared only when earlier values are equal.

List<AccrueRecord> rows = tgTemplate.query(AccrueRecord.class)
        .database("tsdb")
        .timeRange(startTime, endTime)
        .orderByTimeAsc()
        .thenByFieldDesc("value")
        .thenByFieldAsc("status")
        .limit(100)
        .list();

The example produces ORDER BY time ASC, value DESC, status ASC. Use orderByFieldAsc(field) or orderByFieldDesc(field) to make a FIELD the first key. orderBy... replaces the current ordering, while thenByField... appends or updates secondary keys.

FIELD sorting works with list(), limit/offset pagination through page(pageNum, pageSize) and strictCursorPage(). The single-time-cursor page() and aggregate queries reject FIELD sorting explicitly. Strict cursors construct lexicographic conditions using each SortSpec direction, supporting mixed ordering such as ORDER BY time DESC, device_code ASC.

Strict pagination appends any missing time + all @TGTag columns to custom ordering. Appended keys inherit the first key's direction; explicitly declared keys retain their own direction. The POJO must declare the table's complete tag-key set. Sort and cursor values must currently be non-null.

Aggregation

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 accepts only a positive integer with ms/s/m/h/d units, such as 5m; a unitless integer means milliseconds. Fractional, negative, zero, compound or overflowing durations outside the long millisecond range are rejected before SQL generation.

Fluent window aggregation returns window_start as an epoch-millisecond timestamp, which may map to Long, Instant or Date. If InfluxDB returns a window timestamp without a timezone suffix, the adapter interprets it as UTC independently of the application's default JVM timezone.

IoTDB / InfluxDB 3 window timezone rules, with InfluxDB 1.x restrictions described in the backend capability table:

  • UTC is the default. Fixed offsets such as UTC and +08:00 use fixed-duration windows and do not require a time range.
  • Regional zones such as America/New_York and Asia/Shanghai require an explicit bounded timeRange(start, end).
  • A regional 1d window follows local midnight on the queried dates. A DST transition day can contain 23 or 25 hours. Nd aligns every N local calendar days from the local date 1970-01-01, using each boundary date's actual offset.
  • For both backends, the adapter computes calendar boundaries and generates bounded CASE grouping. It does not change the borrowed IoTDB Session's timezone or depend on a function unique to a recent database release. More than 10000 generated windows produces ARGUMENT_ERROR.
  • Regional ms/s/m/h windows use the offset at the query start. A range crossing an offset change produces UNSUPPORTED_OPERATION. Use UTC, a fixed offset or calendar-day windows for such a range.
  • The query still filters with start <= time <= end; window boundaries are left-inclusive and right-exclusive. To exclude the next local midnight, set the end to one millisecond before that boundary.

For example, .timeRange(start, end).groupByTime("1d").timeZone("America/New_York") uses 23-hour and 25-hour calendar days on 2026-03-08 and 2026-11-01 respectively, rather than applying a fixed offset taken from 1970.

Fluent-query notes

  • Most fluent methods collect query settings and have no strict overall ordering requirement. Terminal calls such as list(), page() and page(pageNum, pageSize) execute the query and belong at the end.
  • Later calls to settings such as database, select, timeRange, limit and orderByTimeAsc/Desc replace earlier values.
  • Calls to where, whereTag, whereTagIn, groupByTag and aggregate accumulate. Ordinary predicates currently combine with AND.
  • Use page() for time-cursor pagination, with page size from limit(). Use page(pageNum, pageSize) for offset pagination, without additionally mixing in limit() and offset().
  • Create a new builder with tgTemplate.query(...) for each query. Do not share builders across threads or requests.

Stable default aggregate aliases

An omitted aggregate alias uses the field name plus an underscore and the function name lowercased with Locale.ROOT: for example, MIN(value) receives value_min regardless of the JVM's default locale. Explicit aliases are unchanged.


← Writes, models and errors · Pagination and native queries →

OpenGemini in 2.1.0

TsGate 2.1.0 includes the independent OpenGemini adapter and starter. See OpenGemini integration for configuration, default-engine limits and exact-version testing, and 2.1.0 release notes for migration requirements.

Clone this wiki locally