Module: ClickHouse::HTTPClient::ParameterSerializer

Defined in:
lib/clickhouse/http_client/parameter_serializer.rb

Overview

Serializes Ruby placeholder values into the text format ClickHouse expects for query parameters, for binding values into SQL.

ClickHouse parses a top-level parameter value in its escaped text format, where an unquoted \N denotes NULL, while values nested inside Array/Map literals are parsed as SQL-like literals, where NULL is spelled NULL and strings must be quoted.

Constant Summary collapse

ESCAPED_TEXT_REPLACEMENTS =

ClickHouse parses each top-level parameter value with the type's escaped-text deserialization: backslash sequences (\t, \n, \N, \xNN, ...) are decoded, and a literal TAB or LF byte terminates the value, failing the query. Escaping the backslash and terminator bytes makes String values round-trip byte-for-byte. Values serialized from other Ruby types cannot contain these bytes.

{
  "\\" => "\\\\",
  "\t" => "\\t",
  "\n" => "\\n",
  "\r" => "\\r",
}.freeze

Class Method Summary collapse

Class Method Details

.escape_top_level_string(str) ⇒ String

Escapes a top-level String value for ClickHouse's escaped text format.

Parameters:

  • str (String)

    String value to escape.

Returns:

  • (String)

    Escaped parameter text.



107
108
109
# File 'lib/clickhouse/http_client/parameter_serializer.rb', line 107

def self.escape_top_level_string(str)
  str.gsub(/[\\\t\n\r]/, ESCAPED_TEXT_REPLACEMENTS)
end

.quote_and_escape_string(str) ⇒ String

Escapes each single-quote and backslash by prefixing it with a backslash, and wraps the result in single quotes.

Parameters:

  • str (String)

    String value to quote.

Returns:

  • (String)

    Quoted and escaped string literal.



116
117
118
119
120
121
122
123
124
# File 'lib/clickhouse/http_client/parameter_serializer.rb', line 116

def self.quote_and_escape_string(str)
  # Note the double-escaping in the replacement string:
  # - Ruby strings consume one backslash
  #   - ruby syntax "\\" => ruby string "\"
  # - Regexp replacement consumes one backslash
  #   - ruby literal syntax \\\\ => ruby string \\ => regexp replacement \
  #   - ruby literal syntax \\0  => ruby string \0 => regexp replacement match #0
  "'#{str.gsub(/['\\]/, "\\\\\\0")}'"
end

.serialize(input) ⇒ String

Serializes a value for the top/outer/root layer, where e.g. strings don't need to be surrounded by quotes (whereas e.g. strings in an Array do need quotes).

Parameters:

  • input (Object)

    Placeholder value to serialize.

Returns:

  • (String)

    ClickHouse parameter text.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/clickhouse/http_client/parameter_serializer.rb', line 22

def self.serialize(input)
  case input

  when NilClass
    "\\N"

  when String
    escape_top_level_string(input)

  when Symbol
    escape_top_level_string(input.to_s)

  when Array
    elements = input.map { serialize_nested(_1) }
    "[#{elements.join(",")}]"

  when Hash
    elements = input.map { |k, v| serialize_nested(k) + ":" + serialize_nested(v) }
    "{#{elements.join(",")}}"

  when DateTime
    # DateTime is a Date subclass, so it must be matched first and
    # converted to a Time (preserving its offset) to render as UTC
    # wall-clock text like Time below, not as a date-only string.
    serialize(input.to_time)

  when Date
    input.to_s # Ruby and ClickHouse agree on YYYY-MM-DD

  when Time
    # ClickHouse interprets the rendered text in the placeholder's (or
    # server's) timezone, so only UTC wall-clock text is safe to send.
    # Sub-second precision is dropped; when precision matters, callers
    # pre-format the value as a String (as ClickHouse::SQL does for
    # DateTime64 placeholders).
    input.getutc.strftime("%Y-%m-%d %H:%M:%S")

  else
    input.to_s
  end
end

.serialize_nested(input) ⇒ String

Serializes a value nested within a container e.g. an Array or Hash/Map, where strings are surrounded by quotes and NULL is spelled out.

Parameters:

  • input (Object)

    Nested placeholder value to serialize.

Returns:

  • (String)

    ClickHouse literal text.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/clickhouse/http_client/parameter_serializer.rb', line 70

def self.serialize_nested(input)
  case input

  when NilClass
    "NULL"

  when Symbol
    serialize_nested(input.to_s)

  when String
    quote_and_escape_string(input)

  when Date, Time
    quote_and_escape_string(serialize(input))

  else
    serialize(input)
  end
end