diff --git a/clickhouse_driver/client.py b/clickhouse_driver/client.py index 16a8713b..14128d9b 100644 --- a/clickhouse_driver/client.py +++ b/clickhouse_driver/client.py @@ -63,6 +63,11 @@ class Client(object): tuple set ``namedtuple_as_json`` to ``False``. Default: True. New in version *0.2.6*. + * ``server_side_params`` -- Species on which side query parameters + should be rendered into placeholders. + Default: False. Means that parameters are rendered + on driver's side. + New in version *0.2.7*. """ available_client_settings = ( @@ -74,7 +79,8 @@ class Client(object): 'opentelemetry_tracestate', 'quota_key', 'input_format_null_as_default', - 'namedtuple_as_json' + 'namedtuple_as_json', + 'server_side_params' ) def __init__(self, *args, **kwargs): @@ -107,6 +113,9 @@ def __init__(self, *args, **kwargs): ), 'namedtuple_as_json': self.settings.pop( 'namedtuple_as_json', True + ), + 'server_side_params': self.settings.pop( + 'server_side_params', False ) } @@ -766,6 +775,10 @@ def substitute_params(self, query, params, context): # prints: SELECT 1234, 'bar' print(substituted_query) """ + # In case of server side templating we don't substitute here. + if self.connection.context.client_settings['server_side_params']: + return query + if not isinstance(params, dict): raise ValueError('Parameters are expected in dict form') diff --git a/clickhouse_driver/connection.py b/clickhouse_driver/connection.py index eee4a89d..677140fb 100644 --- a/clickhouse_driver/connection.py +++ b/clickhouse_driver/connection.py @@ -714,10 +714,13 @@ def send_query(self, query, query_id=None, params=None): write_binary_str(query, self.fout) if revision >= defines.DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS: - # Always settings_as_strings = True - escaped = escape_params( - params or {}, self.context, for_server=True - ) + if self.context.client_settings['server_side_params']: + # Always settings_as_strings = True + escaped = escape_params( + params or {}, self.context, for_server=True + ) + else: + escaped = {} write_settings(escaped, self.fout, True, SettingsFlags.CUSTOM) logger.debug('Query: %s', query) diff --git a/tests/test_substitution.py b/tests/test_substitution.py index 34dc5631..52de99e6 100644 --- a/tests/test_substitution.py +++ b/tests/test_substitution.py @@ -245,6 +245,8 @@ def test_substitute_object(self): class ServerSideParametersSubstitutionTestCase(BaseTestCase): required_server_version = (22, 8) + client_kwargs = {'settings': {'server_side_params': True}} + def test_int(self): rv = self.client.execute('SELECT {x:Int32}', {'x': 123}) self.assertEqual(rv, [(123, )]) @@ -268,3 +270,11 @@ def test_escaped_str(self): 'SELECT {x:String}, length({x:String})', {'x': "'"} ) self.assertEqual(rv, [("'", 1)]) + + +class NoServerSideParametersSubstitutionTestCase(BaseTestCase): + def test_reserved_keywords(self): + self.client.execute( + 'SELECT * FROM system.events LIMIT %(limit)s OFFSET %(offset)s', + {'limit': 20, 'offset': 30} + )