ChDriver.Query (ch_driver v0.1.0)

Copy Markdown

A query for ChDriver.DBConnection -- a raw SQL string, optionally containing ? positional placeholders (rewritten by parse/2 into ClickHouse native {name:Type} parameter placeholders at execute time) or literal {name:Type} placeholders written directly by the caller.

params passed to DBConnection.execute/3,4 is the list of raw Elixir values to bind, in the same order the ?s appear in statement. DBConnection.Query.parse/2 (see the defimpl below) lexes statement for ? placeholders exactly once -- the result is cached by DBConnection/Ecto.Adapters.SQL's own query cache across repeated executions of the same prepared query, so this quote-aware scan happens once per distinct query shape rather than on every execute.

Why param_types can't be baked in at parse time

ClickHouse's native protocol has no untyped placeholder syntax -- every bound parameter must be written as {name:Type} with a concrete type, and a nil value can't be bound as a typed parameter at all (see ChDriver.Params's moduledoc): it has to be inlined as the literal NULL token instead. Both the concrete type and the nil-ness of a value are properties of this call's actual arguments, which parse/2 never sees (DBConnection.Query.parse/2 runs before any params exist -- see DBConnection.prepare_execute/4's implementation). Two executions of the very same cached/prepared query can legitimately bind different shapes at the same position -- e.g. Repo.insert!/1 on a schema with a nullable column, called once with a value and once with nil for that column, reuses the identical cached insert statement both times.

So parse/2 only resolves where the placeholders are (the expensive, quote-aware part) and stores that as segments; encode/3 resolves what to bind each execution's actual values as (via ChDriver.Params.encode/1, cheap and per-value); and ChDriver.DBConnection's handle_execute/4 splices the two together into the final wire statement + wire params for this specific call. This keeps the expensive one-time lexing cached while still allowing a per-execution decision between a typed placeholder and an inlined NULL literal at any given position.

Summary

Types

segment()

@type segment() :: binary() | :placeholder

t()

@type t() :: %ChDriver.Query{
  param_names: [binary()] | nil,
  param_types: [binary() | nil] | nil,
  query_id: binary() | nil,
  segments: [segment()] | nil,
  statement: binary()
}