Expand description
Query executor: translate a QueryNode into parameterized SQL, run
it against the index, and reconstruct LogEntry values from the
result rows.
This module is the bridge between the query AST and the SQLite schema.
It never mixes user-controlled strings into SQL text — every literal
value is bound as a parameter. The one exception is JSON extraction
paths like $.service, which embed the field name directly because
SQLite parameters aren’t allowed inside json_extract path expressions;
safety there comes from the field name having passed
validate_field_name’s strict regex in the parser, which we
defensively re-check at the executor boundary.
§Disjunction (OR) shape
v0.2.0 introduced OR. The AST is QueryNode::Or(Vec<AndGroup>) where
each AndGroup is a conjunction of clauses. The SQL we emit
parenthesizes each AND-group and joins them with OR:
WHERE (level = ? AND json_extract(fields, '$.service') = ?)
OR (level = ?)For queries with no OR (a single AND-group), the parens are still emitted — the alternative is a special-case branch in the SQL builder that adds maintenance cost without performance benefit. SQLite’s planner ignores redundant parens.
§Parenthesized groups (v0.3.0)
Clause::Group wraps an inner QueryNode::Or subtree produced by a
( … ) expression in the query language. The translator recurses
into the subtree via translate_and_group and parenthesizes the
result so it composes correctly with surrounding AND/OR operators:
-- (level=error OR level=warn) AND service=payments
WHERE (((level = ?) OR (level = ?)) AND json_extract(fields, '$.service') = ?)The extra level of parentheses is redundant for correctness but keeps the emitter uniform — every AND-group is parenthesized, whether it came from the top-level OR or from an inner Group clause.
§Pagination (v0.3.0)
QueryOptions bundles limit and offset so callers can request a
specific page of results without separate function variants. Offset
without limit uses LIMIT -1 — SQLite requires a LIMIT clause when
OFFSET is present; -1 means unlimited in SQLite.
§Timestamp handling
Timestamps are compared as TEXT, which works correctly for any ISO-8601
format because those sort lexicographically in chronological order when
all components are fixed-width. Ingested timestamps that aren’t ISO-8601
shaped will compare incorrectly against last/since bounds — a known
limitation of accepting arbitrary timestamp strings at ingestion time.
Structs§
- Query
Options - Options controlling result set size and starting position for
execute.
Functions§
- execute
- Execute a parsed query against the index and return matching entries.
- execute_
at - Variant of
executethat uses a caller-supplied “now” value.