The InfluxQL SELECT subset InfluxElixir.Client.Local answers, shaped
the way InfluxDB 3 answers it (verified against the engine; see
docs/design/2026-09-23_local-influxql.md).
InfluxQL is not SQL with other keywords. Every row carries
"iox::measurement" and "time"; rows come back in time order; a row with
no selected field value is dropped; an unknown column or measurement is an
empty result, not an error; aggregates are named after the function
(mean, count, ...) and put time at the epoch, except a lone selector
(MAX, MIN, FIRST, LAST), which returns its point's time and tags;
LIMIT and OFFSET apply per GROUP BY series.
This module is pure. parse/1 turns the statement into a query map;
run/3 shapes rows the caller has already filtered with the statement's
WHERE clause (Client.Local runs it through its SQL engine).
WHERE follows InfluxQL, not SQL (where_sql/2): a missing tag is the
empty string (host != 'a' keeps points without host, host = ''
finds them), =~ /re/ and !~ /re/ are unanchored matches on tags and
false on fields, </> on a tag is false, durations (now() - 30m)
are intervals, double-quoted identifiers are exact, and NOT is the
engine's parse error. SHOW TAG VALUES [FROM m] WITH KEY = | != | =~ | !~ | IN (...) [WHERE ...] lists values as the engine does, over the
last 24 hours unless the WHERE bounds time (parse_show_tag_values/1).
Refused by name, rather than answered wrongly: GROUP BY time(...) (the
engine fills every empty bucket), fill(), INTO, SLIMIT/SOFFSET,
subqueries, GROUP BY *, functions other than
MEAN SUM COUNT MIN MAX FIRST LAST, F(*) other than COUNT(*), plain
columns beside anything but a single selector, arithmetic in the select
list, several measurements in FROM, sub-second durations, and LIMIT /
OFFSET on SHOW TAG VALUES.
Summary
Types
A select item: every column, a column, or a function of a column.
Which tag keys a SHOW TAG VALUES lists.
A parsed SELECT.
Functions
Whether a tag key is one a key_filter/0 lists.
Whether a WHERE names time: then it, not the default window, bounds the rows.
Parses an InfluxQL SELECT. Returns {:error, message} for syntax the
engine rejects and for the constructs listed in the moduledoc.
Parses SHOW TAG VALUES [FROM m] WITH KEY = k | != k | =~ /re/ | !~ /re/ | IN (k, ...) [WHERE ...], or nil when the statement is not one.
LIMIT and OFFSET are refused by name: the engine applies them per
measurement in an order the double does not reproduce.
Shapes rows — the measurement's points already filtered by the
statement's WHERE, as SQL row maps with "time" — into the engine's
answer. tags names the measurement's tag columns; everything else but
time is a field.
Rewrites an InfluxQL WHERE into the caller's SQL, given the
measurement's tag columns. {:error, message} for what the double
refuses by name.
Types
@type item() :: :star | {:column, binary(), binary()} | {:aggregate, binary(), binary() | :star, binary() | nil}
A select item: every column, a column, or a function of a column.
@type key_filter() :: {:eq, binary()} | {:ne, binary()} | {:in, [binary()]} | {:regex, Regex.t(), boolean()}
Which tag keys a SHOW TAG VALUES lists.
@type query() :: %{ items: [item()], measurement: binary(), where: binary() | nil, group_by: [binary()], descending: boolean(), limit: non_neg_integer() | nil, offset: non_neg_integer() }
A parsed SELECT.
Functions
@spec key_listed?(binary(), key_filter()) :: boolean()
Whether a tag key is one a key_filter/0 lists.
Whether a WHERE names time: then it, not the default window, bounds the rows.
Parses an InfluxQL SELECT. Returns {:error, message} for syntax the
engine rejects and for the constructs listed in the moduledoc.
@spec parse_show_tag_values(binary()) :: nil | {:ok, %{measurement: binary() | nil, keys: key_filter(), where: binary() | nil}} | {:error, binary()}
Parses SHOW TAG VALUES [FROM m] WITH KEY = k | != k | =~ /re/ | !~ /re/ | IN (k, ...) [WHERE ...], or nil when the statement is not one.
LIMIT and OFFSET are refused by name: the engine applies them per
measurement in an order the double does not reproduce.
Shapes rows — the measurement's points already filtered by the
statement's WHERE, as SQL row maps with "time" — into the engine's
answer. tags names the measurement's tag columns; everything else but
time is a field.
Rewrites an InfluxQL WHERE into the caller's SQL, given the
measurement's tag columns. {:error, message} for what the double
refuses by name.