Skip to content

Commit 0f7926d

Browse files
committed
feat: add support for ClickHouse's query parameters, so that users can issue parameterized queries without string interpolation.
1 parent c9103f8 commit 0f7926d

File tree

10 files changed

+372
-21
lines changed

10 files changed

+372
-21
lines changed

README-zh.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ res = chdb.query('select * from file("data.csv", CSV)', 'CSV'); print(res)
6767
print(f"SQL read {res.rows_read()} rows, {res.bytes_read()} bytes, elapsed {res.elapsed()} seconds")
6868
```
6969

70+
### 参数化查询
71+
```python
72+
import chdb
73+
74+
df = chdb.query(
75+
"SELECT toDate({base_date:String}) + number AS date "
76+
"FROM numbers({total_days:UInt64}) "
77+
"LIMIT {items_per_page:UInt64}",
78+
"DataFrame",
79+
params={"base_date": "2025-01-01", "total_days": 10, "items_per_page": 2},
80+
)
81+
print(df)
82+
# date
83+
# 0 2025-01-01
84+
# 1 2025-01-02
85+
```
86+
87+
更多内容请参见:
88+
* [ClickHouse SQL语法: 定义和使用查询参数](https://clickhouse.com/docs/sql-reference/syntax#defining-and-using-query-parameters)
89+
* [ClickHouse中如何使用参数化查询](https://clickhouse.com/videos/how-to-use-query-parameters-in-clickhouse)
90+
7091
### Pandas DataFrame 输出
7192
```python
7293
# 更多内容请参见 https://clickhouse.com/docs/en/interfaces/formats

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ res = chdb.query('select * from file("data.csv", CSV)', 'CSV'); print(res)
116116
print(f"SQL read {res.rows_read()} rows, {res.bytes_read()} bytes, storage read {res.storage_rows_read()} rows, {res.storage_bytes_read()} bytes, elapsed {res.elapsed()} seconds")
117117
```
118118

119+
### Parameterized queries
120+
```python
121+
import chdb
122+
123+
df = chdb.query(
124+
"SELECT toDate({base_date:String}) + number AS date "
125+
"FROM numbers({total_days:UInt64}) "
126+
"LIMIT {items_per_page:UInt64}",
127+
"DataFrame",
128+
params={"base_date": "2025-01-01", "total_days": 10, "items_per_page": 2},
129+
)
130+
print(df)
131+
# date
132+
# 0 2025-01-01
133+
# 1 2025-01-02
134+
```
135+
136+
For more details, see:
137+
* [ClickHouse SQL syntax: defining and using query parameters](https://clickhouse.com/docs/sql-reference/syntax#defining-and-using-query-parameters)
138+
* [How to Use Query Parameters in ClickHouse](https://clickhouse.com/videos/how-to-use-query-parameters-in-clickhouse)
139+
119140
### Pandas dataframe output
120141
```python
121142
# See more in https://clickhouse.com/docs/en/interfaces/formats

chdb/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def to_arrowTable(res):
112112

113113

114114
# wrap _chdb functions
115-
def query(sql, output_format="CSV", path="", udf_path=""):
115+
def query(sql, output_format="CSV", path="", udf_path="", params=None):
116116
"""Execute SQL query using chDB engine.
117117
118118
This is the main query function that executes SQL statements using the embedded
@@ -135,6 +135,8 @@ def query(sql, output_format="CSV", path="", udf_path=""):
135135
path (str, optional): Database file path. Defaults to "" (in-memory database).
136136
Can be a file path or ":memory:" for in-memory database.
137137
udf_path (str, optional): Path to User-Defined Functions directory. Defaults to "".
138+
params (dict, optional): Named query parameters matching placeholders like ``{key:Type}``.
139+
Values are converted to strings and passed to the engine without manual escaping.
138140
139141
Returns:
140142
Query result in the specified format:
@@ -167,6 +169,7 @@ def query(sql, output_format="CSV", path="", udf_path=""):
167169
>>> result = chdb.query("SELECT my_udf('test')", udf_path="/path/to/udfs")
168170
"""
169171
global g_udf_path
172+
params = params or {}
170173
if udf_path != "":
171174
g_udf_path = udf_path
172175
conn_str = ""
@@ -195,11 +198,11 @@ def query(sql, output_format="CSV", path="", udf_path=""):
195198
conn = _chdb.connect(conn_str)
196199

197200
if lower_output_format == "dataframe":
198-
res = conn.query_df(sql)
201+
res = conn.query_df(sql, params=params)
199202
conn.close()
200203
return res
201204

202-
res = conn.query(sql, output_format)
205+
res = conn.query(sql, output_format, params=params)
203206

204207
if res.has_error():
205208
conn.close()

chdb/session/state.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def cleanup(self):
119119
except: # noqa
120120
pass
121121

122-
def query(self, sql, fmt="CSV", udf_path=""):
122+
def query(self, sql, fmt="CSV", udf_path="", params=None):
123123
"""Execute a SQL query and return the results.
124124
125125
This method executes a SQL query against the session's database and returns
@@ -138,9 +138,14 @@ def query(self, sql, fmt="CSV", udf_path=""):
138138
- "JSONCompact" - Compact JSON format
139139
- "Arrow" - Apache Arrow format
140140
- "Parquet" - Parquet format
141+
- "DataFrame" - Pandas DataFrame
142+
- "ArrowTable" - PyArrow Table
141143
142144
udf_path (str, optional): Path to user-defined functions. Defaults to "".
143145
If not specified, uses the UDF path from session initialization.
146+
params (dict, optional): Named parameters for ``{name:Type}`` placeholders.
147+
Values must be compatible with the declared ClickHouse type; otherwise
148+
query execution raises a RuntimeError.
144149
145150
Returns:
146151
Query results in the specified format. The exact return type depends on
@@ -197,12 +202,12 @@ def query(self, sql, fmt="CSV", udf_path=""):
197202
Eg: conn = connect(f"db_path?verbose&log-level=test")"""
198203
)
199204
fmt = "CSV"
200-
return self._conn.query(sql, fmt)
205+
return self._conn.query(sql, fmt, params=params)
201206

202207
# alias sql = query
203208
sql = query
204209

205-
def send_query(self, sql, fmt="CSV") -> StreamingResult:
210+
def send_query(self, sql, fmt="CSV", params=None) -> StreamingResult:
206211
"""Execute a SQL query and return a streaming result iterator.
207212
208213
This method executes a SQL query against the session's database and returns
@@ -221,6 +226,11 @@ def send_query(self, sql, fmt="CSV") -> StreamingResult:
221226
- "JSONCompact" - Compact JSON format
222227
- "Arrow" - Apache Arrow format
223228
- "Parquet" - Parquet format
229+
- "DataFrame" - Pandas DataFrame
230+
- "ArrowTable" - PyArrow Table
231+
params (dict, optional): Named parameters for ``{name:Type}`` placeholders.
232+
Type mismatches or missing required parameters propagate as RuntimeError
233+
when fetching from the stream.
224234
225235
Returns:
226236
StreamingResult: A streaming result iterator that yields query results
@@ -270,4 +280,4 @@ def send_query(self, sql, fmt="CSV") -> StreamingResult:
270280
Eg: conn = connect(f"db_path?verbose&log-level=test")"""
271281
)
272282
fmt = "CSV"
273-
return self._conn.send_query(sql, fmt)
283+
return self._conn.send_query(sql, fmt, params=params)

chdb/state/sqlitelike.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def cursor(self) -> "Cursor":
399399
self._cursor = Cursor(self._conn)
400400
return self._cursor
401401

402-
def query(self, query: str, format: str = "CSV") -> Any:
402+
def query(self, query: str, format: str = "CSV", params=None) -> Any:
403403
"""Execute a SQL query and return the complete results.
404404
405405
This method executes a SQL query synchronously and returns the complete
@@ -461,12 +461,12 @@ def query(self, query: str, format: str = "CSV") -> Any:
461461
format = "Arrow"
462462

463463
if lower_output_format == "dataframe":
464-
result = self._conn.query_df(query)
464+
result = self._conn.query_df(query, params=params or {})
465465
else:
466-
result = self._conn.query(query, format)
466+
result = self._conn.query(query, format, params=params or {})
467467
return result_func(result)
468468

469-
def send_query(self, query: str, format: str = "CSV") -> StreamingResult:
469+
def send_query(self, query: str, format: str = "CSV", params=None) -> StreamingResult:
470470
"""Execute a SQL query and return a streaming result iterator.
471471
472472
This method executes a SQL query and returns a StreamingResult object
@@ -531,7 +531,7 @@ def send_query(self, query: str, format: str = "CSV") -> StreamingResult:
531531
if lower_output_format in _arrow_format:
532532
format = "Arrow"
533533

534-
c_stream_result = self._conn.send_query(query, format)
534+
c_stream_result = self._conn.send_query(query, format, params=params or {})
535535
is_dataframe = lower_output_format == "dataframe"
536536
return StreamingResult(c_stream_result, self._conn, result_func, supports_record_batch, is_dataframe)
537537

programs/local/ChdbClient.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,22 @@ size_t ChdbClient::getStorageBytesRead() const
144144
}
145145

146146
#if USE_PYTHON
147+
void ChdbClient::setQueryParameters(const NameToNameMap & params)
148+
{
149+
std::lock_guard<std::mutex> lock(client_mutex);
150+
query_parameters = params;
151+
if (client_context)
152+
client_context->setQueryParameters(query_parameters);
153+
}
154+
155+
void ChdbClient::clearQueryParameters()
156+
{
157+
std::lock_guard<std::mutex> lock(client_mutex);
158+
query_parameters.clear();
159+
if (client_context)
160+
client_context->setQueryParameters(query_parameters);
161+
}
162+
147163
void ChdbClient::findQueryableObjFromPyCache(const String & query_str) const
148164
{
149165
python_table_cache->findQueryableObjFromQuery(query_str);

programs/local/ChdbClient.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <Client/LocalConnection.h>
55
#include <Interpreters/Session.h>
66
#include <Common/Config/ConfigProcessor.h>
7+
#include <Core/Names.h>
78
#include "QueryResult.h"
89

910
#include <memory>
@@ -44,6 +45,9 @@ class ChdbClient : public ClientBase
4445
size_t getStorageBytesRead() const;
4546

4647
#if USE_PYTHON
48+
void setQueryParameters(const NameToNameMap & params);
49+
void clearQueryParameters();
50+
4751
void findQueryableObjFromPyCache(const String & query_str) const;
4852
#endif
4953

0 commit comments

Comments
 (0)