InfluxElixir.Client.Local.SQLParser (InfluxElixir v0.1.40)

Copy Markdown View Source

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.

This module cuts a statement into its parts and hands each to the module that reads it:

A $name placeholder is read as a value wherever a value may stand and is kept as a node; InfluxElixir.Client.Local.SQLBind replaces the nodes with the values the engine would read for them, so a parameter is data and never SQL text.

Pure functions only: no ETS, no connection state.

Summary

Types

Plain aggregates; :stddev/:var are the sample forms, as in InfluxDB.

CAST(expr AS type) targets: an integer width, DOUBLE or text.

:asc / :desc (nulls last / first), or a direction with explicit NULLS placement.

An arithmetic expression; see InfluxElixir.Client.Local.SQLExpr.t/0.

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).

One column of an aggregate or grouped select list.

A time comparand: nanoseconds since the epoch, now() plus an offset in nanoseconds (resolved when the query runs), null, or a string the optimizer cannot read (raised once the type checks have passed).

A predicate: operator, left operand, right side.

A WHERE conjunction is a list of nodes: a predicate, an {:or, branches} node whose branches are conjunctions, or a {:not, conjunction} node.

The comparison, set and pattern operators of a predicate.

Functions

Binds a parsed query's $name placeholders to params; see InfluxElixir.Client.Local.SQLBind.bind/2.

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>, for a statement that binds no parameters (DELETE, InfluxQL). A $name in it is the engine's unbound-placeholder error, and so is a time string the optimizer cannot read.

Types

aggregate()

Plain aggregates; :stddev/:var are the sample forms, as in InfluxDB.

cast_type()

CAST(expr AS type) targets: an integer width, DOUBLE or text.

direction()

:asc / :desc (nulls last / first), or a direction with explicit NULLS placement.

expr()

An arithmetic expression; see InfluxElixir.Client.Local.SQLExpr.t/0.

operand()

A WHERE operand: a column name, or an arithmetic expression over columns and literals (price <= med * 3).

order_by()

ORDER BY terms in order; a target is time, a column, an output alias or an expression.

parsed_query()

@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: non_neg_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,
  qualifier: binary(),
  qualified: %{required(binary()) => binary()},
  plan_error: InfluxElixir.Client.Local.SQLError.t() | nil,
  limit_error: InfluxElixir.Client.Local.SQLError.t() | nil
}

One SELECT. ctes holds the WITH name AS (...) queries that precede it, in order; measurement may name one of them.

projection()

@type projection() :: {binary() | expr(), binary()}

A projected column: {source, output} where source is a column name or an arithmetic expr/0 ((bid + ask) / 2 AS mid).

select_column()

@type select_column() :: InfluxElixir.Client.Local.SQLSelect.column()

One column of an aggregate or grouped select list.

time_value()

@type time_value() ::
  integer()
  | {:now, integer()}
  | nil
  | {:invalid_time, InfluxElixir.Client.Local.SQLError.t()}

A time comparand: nanoseconds since the epoch, now() plus an offset in nanoseconds (resolved when the query runs), null, or a string the optimizer cannot read (raised once the type checks have passed).

where_clause()

@type where_clause() :: InfluxElixir.Client.Local.SQLWhere.clause()

A predicate: operator, left operand, right side.

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.

where_op()

The comparison, set and pattern operators of a predicate.

Functions

bind(query, params)

@spec bind(parsed_query(), %{required(binary()) => term()}) ::
  {:ok, parsed_query()} | {:error, map()}

Binds a parsed query's $name placeholders to params; see InfluxElixir.Client.Local.SQLBind.bind/2.

parse_select(sql, opts \\ [])

@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.

parse_where(rest)

@spec parse_where(binary()) :: {:ok, [where_node()]} | {:error, map()}

Parses the WHERE ... clause (if any) out of the text after FROM <table>, for a statement that binds no parameters (DELETE, InfluxQL). A $name in it is the engine's unbound-placeholder error, and so is a time string the optimizer cannot read.