SQL parser for InfluxElixir.Client.Local.
Recognises the SQL subset documented on InfluxElixir.Client.Local and
produces a parsed_query/0 for the executor. It is deliberately strict:
anything the real InfluxDB v3 engine would reject — or that the double
cannot execute faithfully — is refused with a Client.Local:-prefixed 400
so a query cannot pass tests here and fail in production.
Pure functions only: no ETS, no connection state.
Summary
Types
Plain aggregates; :stddev/:var are the sample forms, as in InfluxDB.
CAST(expr AS INTEGER | DOUBLE | VARCHAR) targets (and their synonyms).
:asc / :desc (nulls last / first), or a direction with explicit NULLS placement.
An arithmetic expression inside an aggregate: a field reference, a numeric literal, or a binary operation over two expressions.
A WHERE operand: a column name, or an arithmetic expression over columns
and literals (price <= med * 3).
ORDER BY terms in order; a target is time, a column, an output alias or an expression.
One SELECT. ctes holds the WITH name AS (...) queries that precede
it, in order; measurement may name one of them.
A projected column: {source, output} where source is a column name or
an arithmetic expr/0 ((bid + ask) / 2 AS mid).
A time comparand: nanoseconds since the epoch, or now() plus an offset
in nanoseconds, resolved when the query runs.
A WHERE conjunction is a list of nodes: a predicate, an {:or, branches}
node whose branches are conjunctions, or a {:not, conjunction} node.
Functions
Parses a statement — an optional WITH list of non-recursive CTEs followed
by one SELECT — into a parsed_query/0.
Parses the WHERE ... clause (if any) out of the text after FROM <table>.
Substitutes $name placeholders with SQL literals built from params.
The first $name placeholder left in sql after substitution, or nil.
String literals are ignored: a substituted value that happens to contain
$ is text.
Types
@type aggregate() ::
:avg
| :sum
| :count
| :min
| :max
| :median
| :stddev
| :stddev_pop
| :var
| :var_pop
Plain aggregates; :stddev/:var are the sample forms, as in InfluxDB.
@type cast_type() :: :integer | :float | :string
CAST(expr AS INTEGER | DOUBLE | VARCHAR) targets (and their synonyms).
@type direction() :: :asc | :desc | {:asc | :desc, :nulls_first | :nulls_last}
:asc / :desc (nulls last / first), or a direction with explicit NULLS placement.
@type expr() :: {:field, binary()} | {:lit, number() | binary()} | {:op, :+ | :- | :* | :/ | :rem, expr(), expr()} | {:neg, expr()} | {:cast, expr(), cast_type()}
An arithmetic expression inside an aggregate: a field reference, a numeric literal, or a binary operation over two expressions.
A WHERE operand: a column name, or an arithmetic expression over columns
and literals (price <= med * 3).
ORDER BY terms in order; a target is time, a column, an output alias or an expression.
@type parsed_query() :: %{ measurement: binary(), where: [where_node()], order_by: order_by(), limit: non_neg_integer() | nil, offset: non_neg_integer() | nil, group_by_interval: pos_integer() | nil, group_by_columns: [binary()] | nil, select_columns: [select_column()] | nil, distinct_columns: [binary()] | nil, distinct_on: [binary()] | nil, projection_columns: [projection()] | nil, ctes: [{binary(), parsed_query()}], cross_join: {binary(), [binary()]} | nil }
One SELECT. ctes holds the WITH name AS (...) queries that precede
it, in order; measurement may name one of them.
A projected column: {source, output} where source is a column name or
an arithmetic expr/0 ((bid + ask) / 2 AS mid).
@type select_column() :: {:time_bucket, binary()} | {:aggregate, aggregate(), expr(), binary()} | {:count_star, binary()} | {:count_distinct, binary(), binary()} | {:ordered_aggregate, :first | :last, binary(), binary(), binary()} | {:selector, :first | :last | :min | :max, binary(), binary(), :value | :time | :struct, binary()} | {:grouping_column, binary(), binary()} | {:constant, term(), binary()}
A time comparand: nanoseconds since the epoch, or now() plus an offset
in nanoseconds, resolved when the query runs.
@type where_node() :: where_clause() | {:or, [[where_node()]]} | {:not, [where_node()]}
A WHERE conjunction is a list of nodes: a predicate, an {:or, branches}
node whose branches are conjunctions, or a {:not, conjunction} node.
@type where_op() ::
:eq
| :gt
| :lt
| :gte
| :lte
| :ne
| :in
| :not_in
| :is_null
| :is_not_null
| :between
| :not_between
| :like
| :not_like
Functions
@spec parse_select( binary(), keyword() ) :: {:ok, parsed_query()} | {:error, term()}
Parses a statement — an optional WITH list of non-recursive CTEs followed
by one SELECT — into a parsed_query/0.
Identifiers follow DataFusion's rules (see
InfluxElixir.Client.Local.SQLIdentifiers) unless identifiers: :exact
is given — for SQL the double writes itself, from InfluxQL, whose
identifiers are case-sensitive.
@spec parse_where(binary()) :: {:ok, [where_node()]} | {:error, map()}
Parses the WHERE ... clause (if any) out of the text after FROM <table>.
Substitutes $name placeholders with SQL literals built from params.
The first $name placeholder left in sql after substitution, or nil.
String literals are ignored: a substituted value that happens to contain
$ is text.