Skip to content

maybe_enquote_for_server decorator incorrectly quotes arrays/tuples for server-side parameters #497

Description

@chriswithers-fuse

Description

When using server_side_params=True, the @maybe_enquote_for_server decorator in clickhouse_driver/util/escape.py incorrectly wraps arrays and tuples in quotes, converting them to strings that ClickHouse cannot parse.

Reproduction

from clickhouse_driver import Client

client = Client('localhost')

# This fails with: Cannot parse input: expected ']' at end of stream
client.execute(
    "SELECT {arr:Array(String)}",
    {'arr': ['a', 'b', 'c']},
    settings={'server_side_params': True}
)

Error:

clickhouse_driver.errors.ServerException: Code: 27.
DB::Exception: Cannot parse input: expected ']' at end of stream: 
value [ cannot be parsed as Array(String) for query parameter 'arr'

Root Cause

In escape_param(), the function correctly formats arrays as ['a','b','c'], but then the @maybe_enquote_for_server decorator sees it doesn't start with a quote and wraps the entire thing: "['a','b','c']" - turning it into a string instead of an array.

The decorator applies to ALL return values, but arrays/tuples already have their own delimiters ([...] and (...)) and should not be quoted.

Current code in clickhouse_driver/util/escape.py:

def maybe_enquote_for_server(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        rv = f(*args, **kwargs)

        if kwargs.get('for_server'):
            is_str = isinstance(rv, str)

            if not is_str or (is_str and not rv.startswith("'")):
                rv = "'%s'" % rv  # <-- This quotes arrays too!

        return rv

    return wrapper

Proposed Fix

The decorator should only quote scalar types, not collections:

def maybe_enquote_for_server(f):
    @wraps(f)
    def wrapper(item, *args, **kwargs):
        rv = f(item, *args, **kwargs)
        
        # Only quote non-collection types - arrays/tuples have their own delimiters
        if kwargs.get('for_server') and not isinstance(item, (list, tuple)):
            is_str = isinstance(rv, str)
            if not is_str or (is_str and not rv.startswith("'")):
                rv = "'%s'" % rv
        
        return rv
    return wrapper

Impact

This bug affects any use of server_side_params=True with:

  • Array(T) parameters
  • Tuple(...) parameters
  • Array(Tuple(...)) parameters

These are common patterns in real-world ClickHouse queries.

Environment

  • clickhouse-driver: 0.2.10
  • Python: 3.12
  • ClickHouse server: 24.x

Workaround

We're currently working around this with a monkeypatch that reimplements the decorator logic correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions