ChDriver.Params (ch_driver v0.1.0)

Copy Markdown

Turns an Elixir value into a ClickHouse query parameter: the declared {name:Type} type name, the unquoted/unescaped literal text, and the wire-level escaping depth that text needs.

This is the single place that mapping lives -- ChDriver.Protocol's encode_query/2 calls text/1 and escape_rounds/1 to build the wire bytes for a parameter's value, and Ecto.Adapters.ClickHouse.Connection calls type/1 to build the matching {name:Type} placeholder text. Keeping both halves of the mapping in one module (rather than hand-synced across the two projects) is what guarantees a value's declared type and its rendered text always agree.

Summary

Functions

Turns an Elixir term into {type, text, rounds} in one call: the ClickHouse type name for its {name:Type} placeholder (see type/1), the literal text for its value (see text/1), and the escaping depth that text needs on the wire (see escape_rounds/1).

The number of backslash/quote-escaping rounds the wire encoder (ChDriver.Protocol.encode_query/2) must apply to bind value for it to round-trip -- 1 for a list (the Array literal text text/1 renders for it already has its string elements \'-escaped once, see array_element_text/1), 2 for everything else.

Renders an Elixir term as the unquoted, unescaped ClickHouse literal text a query parameter's value should carry -- the same text you'd type after a CAST(..., 'Type') for that value. ChDriver.Protocol's encode_query/2 applies the wire-level quoting/escaping on top of this (see quote_param_value/2); this function only handles turning the value itself into ClickHouse literal syntax.

Maps an Elixir runtime value to the ClickHouse type name used in its {name:Type} placeholder (e.g. 5 -> "Int64").

Functions

encode(term)

@spec encode(term()) :: {binary(), binary(), 1 | 2}

Turns an Elixir term into {type, text, rounds} in one call: the ClickHouse type name for its {name:Type} placeholder (see type/1), the literal text for its value (see text/1), and the escaping depth that text needs on the wire (see escape_rounds/1).

escape_rounds(list)

@spec escape_rounds(term()) :: 1 | 2

The number of backslash/quote-escaping rounds the wire encoder (ChDriver.Protocol.encode_query/2) must apply to bind value for it to round-trip -- 1 for a list (the Array literal text text/1 renders for it already has its string elements \'-escaped once, see array_element_text/1), 2 for everything else.

The server unescapes a scalar parameter's value text twice before parsing it against the declared type, but only once for each string inside an Array(String)/Map value's already-quoted element syntax. A scalar value escaped only once loses backslashes asymmetrically (an odd leftover backslash fuses with the next character into an unintended escape, e.g. \b becomes a backspace byte), while escaping it twice round-trips exactly. An Array(String) value escaped twice, on the other hand, corrupts its already-\'-escaped elements, because the array parses each element with a single-escape "Quoted" reader, one level shallower than the plain scalar path (ReplaceQueryParameterVisitor re-parses a scalar's already-unescaped custom-setting value as escaped text a second time, but does not do so for array elements). This is undocumented ClickHouse behavior, treated here as a black-box wire fact rather than a designed feature.

Takes the same Elixir term text/1 would be called with, not its rendered text.

text(b)

@spec text(term()) :: binary()

Renders an Elixir term as the unquoted, unescaped ClickHouse literal text a query parameter's value should carry -- the same text you'd type after a CAST(..., 'Type') for that value. ChDriver.Protocol's encode_query/2 applies the wire-level quoting/escaping on top of this (see quote_param_value/2); this function only handles turning the value itself into ClickHouse literal syntax.

There is no clause for nil: ClickHouse's query parameters have no type-independent way to express NULL (a Nullable(String)-typed NULL parameter fails to bind against, say, an Int32 column), so callers should inline a literal NULL into the query text instead of routing nil through this.

type(b)

@spec type(term()) :: binary()

Maps an Elixir runtime value to the ClickHouse type name used in its {name:Type} placeholder (e.g. 5 -> "Int64").

There's no clause for nil -- see the moduledoc-adjacent note on text/1 for why.