Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit16db1f8

Browse files
authored
Merge pull request#24 from rajdude0/BIND_QUERYz
Extending cpp bindings to support Clickhouse params thereby enabling …
2 parents469372c +9b7bd29 commit16db1f8

File tree

4 files changed

+223
-7
lines changed

4 files changed

+223
-7
lines changed

‎index.d.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
*/
88
exportfunctionquery(query:string,format?:string):string;
99

10+
/**
11+
* Executes a query with parameters using the chdb addon.
12+
*
13+
*@param query The query string to execute.
14+
*@param binding arguments for parameters defined in the query.
15+
*@param format The format for the query result, default is "CSV".
16+
*@returns The query result as a string.
17+
*/
18+
exportfunctionqueryBind(query:string,args:object,format?:string):string;
19+
1020
/**
1121
* Session class for managing queries and temporary paths.
1222
*/
@@ -37,6 +47,17 @@ export class Session {
3747
*/
3848
query(query:string,format?:string):string;
3949

50+
/**
51+
* Executes a query with parameters using the chdb addon.
52+
*
53+
*@param query The query string to execute.
54+
*@param binding arguments for parameters defined in the query.
55+
*@param format The format for the query result, default is "CSV".
56+
*@returns The query result as a string.
57+
*/
58+
59+
queryBind(query:string,args:object,format?:string):string;
60+
4061
/**
4162
* Cleans up the session, deleting the temporary directory if one was created.
4263
*/

‎index.js‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ function query(query, format = "CSV") {
1212
returnchdbNode.Query(query,format);
1313
}
1414

15+
functionqueryBind(query,args={},format="CSV"){
16+
if(!query){
17+
return"";
18+
}
19+
returnchdbNode.QueryBindSession(query,args,format);
20+
}
21+
1522
// Session class with path handling
1623
classSession{
1724
constructor(path=""){
@@ -30,10 +37,15 @@ class Session {
3037
returnchdbNode.QuerySession(query,format,this.path);
3138
}
3239

40+
queryBind(query,args={},format="CSV"){
41+
if(!query)return"";
42+
returnchdbNode.QueryBindSession(query,args,format,this.path)
43+
}
44+
3345
// Cleanup method to delete the temporary directory
3446
cleanup(){
3547
rmSync(this.path,{recursive:true});// Replaced rmdirSync with rmSync
3648
}
3749
}
3850

39-
module.exports={ query, Session};
51+
module.exports={ query,queryBind,Session};

‎lib/chdb_node.cpp‎

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,76 @@
1010
#defineMAX_PATH_LENGTH4096
1111
#defineMAX_ARG_COUNT6
1212

13+
14+
static std::stringtoCHLiteral(const Napi::Env& env,const Napi::Value& v);
15+
16+
17+
static std::stringchEscape(const std::string& s)
18+
{
19+
std::string out;
20+
out.reserve(s.size() +4);
21+
out +='\'';
22+
for (char c : s) {
23+
if (c =='\'') out +="\\'";
24+
else out += c;
25+
}
26+
out +='\'';
27+
return out;
28+
}
29+
30+
static std::stringtoCHLiteral(const Napi::Env& env,const Napi::Value& v)
31+
{
32+
if (v.IsNumber() || v.IsBoolean() || v.IsString())
33+
return v.ToString().Utf8Value();
34+
35+
if (v.IsDate()) {
36+
double ms = v.As<Napi::Date>().ValueOf();
37+
std::time_t t =static_cast<std::time_t>(ms /1000);
38+
std::tm tm{};
39+
gmtime_r(&t, &tm);
40+
char buf[32];
41+
strftime(buf,sizeof(buf),"%Y-%m-%d %H:%M:%S", &tm);
42+
returnstd::string(&buf[0],sizeof(buf));
43+
}
44+
45+
if (v.IsTypedArray()) {
46+
Napi::Object arr = env.Global().Get("Array").As<Napi::Object>();
47+
Napi::Function from = arr.Get("from").As<Napi::Function>();
48+
returntoCHLiteral(env, from.Call(arr, { v }));
49+
}
50+
51+
if (v.IsArray()) {
52+
Napi::Array a = v.As<Napi::Array>();
53+
size_t n = a.Length();
54+
std::string out ="[";
55+
for (size_t i =0; i < n; ++i) {
56+
if (i) out +=",";
57+
out +=toCHLiteral(env, a.Get(i));
58+
}
59+
out +="]";
60+
return out;
61+
}
62+
63+
if (v.IsObject()) {
64+
Napi::Object o = v.As<Napi::Object>();
65+
Napi::Array keys = o.GetPropertyNames();
66+
size_t n = keys.Length();
67+
std::string out ="{";
68+
for (size_t i =0; i < n; ++i) {
69+
if (i) out +=",";
70+
std::string k = keys.Get(i).ToString().Utf8Value();
71+
out +=chEscape(k);// escape the map key with single-qoutes for click house query to work i.e 'key' not "key"
72+
out +=":";
73+
out +=toCHLiteral(env, o.Get(keys.Get(i)));
74+
}
75+
out +="}";
76+
return out;
77+
}
78+
79+
/* Fallback – stringify & quote*/
80+
returnchEscape(v.ToString().Utf8Value());
81+
}
82+
1383
// Utility function to construct argument string
1484
voidconstruct_arg(char *dest,constchar *prefix,constchar *value,
1585
size_t dest_size) {
@@ -92,21 +162,46 @@ char *QuerySession(const char *query, const char *format, const char *path,
92162
return result;
93163
}
94164

165+
char *QueryBindSession(constchar *query,constchar *format,constchar *path,
166+
const std::vector<std::string>& params,char **error_message) {
167+
168+
std::vector<std::string> store;
169+
store.reserve(4 + params.size() + (path && path[0] ?1 :0));
170+
171+
store.emplace_back("clickhouse");
172+
store.emplace_back("--multiquery");
173+
store.emplace_back(std::string("--output-format=") + format);
174+
store.emplace_back(std::string("--query=") + query);
175+
176+
for (constauto& p : params) store.emplace_back(p);
177+
if (path && path[0]) store.emplace_back(std::string("--path=") + path);
178+
179+
std::vector<char*> argv;
180+
argv.reserve(store.size());
181+
for (auto& s : store)
182+
argv.push_back(const_cast<char*>(s.c_str()));
183+
184+
#ifdef CHDB_DEBUG
185+
std::cerr <<"=== chdb argv (" << argv.size() <<") ===\n";
186+
for (char* a : argv) std::cerr << a <<'\n';
187+
#endif
188+
189+
returnquery_stable_v2(static_cast<int>(argv.size()), argv.data())->buf;
190+
}
191+
95192
Napi::StringQueryWrapper(const Napi::CallbackInfo &info) {
96193
Napi::Env env = info.Env();
97194

98-
// Check argument types and count
99195
if (info.Length() <2 || !info[0].IsString() || !info[1].IsString()) {
100196
Napi::TypeError::New(env,"String expected").ThrowAsJavaScriptException();
101197
returnNapi::String::New(env,"");
102198
}
103199

104-
// Get the arguments
105200
std::string query = info[0].As<Napi::String>().Utf8Value();
106201
std::string format = info[1].As<Napi::String>().Utf8Value();
107202

108203
char *error_message =nullptr;
109-
// Call the native function
204+
110205
char *result =Query(query.c_str(), format.c_str(), &error_message);
111206

112207
if (result ==NULL) {
@@ -117,7 +212,6 @@ Napi::String QueryWrapper(const Napi::CallbackInfo &info) {
117212
returnNapi::String::New(env,"");
118213
}
119214

120-
// Return the result
121215
returnNapi::String::New(env, result);
122216
}
123217

@@ -153,11 +247,56 @@ Napi::String QuerySessionWrapper(const Napi::CallbackInfo &info) {
153247
returnNapi::String::New(env, result);
154248
}
155249

250+
static std::stringjsToParam(const Napi::Env& env,const Napi::Value& v) {
251+
returntoCHLiteral(env, v);
252+
}
253+
254+
Napi::StringQueryBindSessionWrapper(const Napi::CallbackInfo& info) {
255+
Napi::Env env = info.Env();
256+
if (info.Length() <2 || !info[0].IsString() || !info[1].IsObject())
257+
Napi::TypeError::New(env,"Usage: sql, params, [format]").ThrowAsJavaScriptException();
258+
259+
std::string sql = info[0].As<Napi::String>();
260+
Napi::Object obj = info[1].As<Napi::Object>();
261+
std::string format = (info.Length() >2 && info[2].IsString())
262+
? info[2].As<Napi::String>() :std::string("CSV");
263+
std::string path = (info.Length() >3 && info[3].IsString())
264+
? info[3].As<Napi::String>() :std::string("");
265+
266+
// Build param vector
267+
std::vector<std::string> cliParams;
268+
Napi::Array keys = obj.GetPropertyNames();
269+
int len = keys.Length();
270+
for (int i =0; i < len; i++) {
271+
Napi::Value k = keys.Get(i);
272+
if(!k.IsString())continue;
273+
274+
std::string key = k.As<Napi::String>();
275+
std::string val =jsToParam(env, obj.Get(k));
276+
cliParams.emplace_back("--param_" + key +"=" + val);
277+
}
278+
279+
#ifdef CHDB_DEBUG
280+
std::cerr <<"=== cliParams ===\n";
281+
for (constauto& s : cliParams)
282+
std::cerr << s <<'\n';
283+
#endif
284+
285+
char* err =nullptr;
286+
char* out =QueryBindSession(sql.c_str(), format.c_str(), path.c_str(), cliParams, &err);
287+
if (!out) {
288+
Napi::Error::New(env, err ? err :"unknown error").ThrowAsJavaScriptException();
289+
returnNapi::String::New(env,"");
290+
}
291+
returnNapi::String::New(env, out);
292+
}
293+
156294
Napi::ObjectInit(Napi::Env env, Napi::Object exports) {
157295
// Export the functions
158296
exports.Set("Query",Napi::Function::New(env, QueryWrapper));
159297
exports.Set("QuerySession",Napi::Function::New(env, QuerySessionWrapper));
298+
exports.Set("QueryBindSession",Napi::Function::New(env, QueryBindSessionWrapper));
160299
return exports;
161300
}
162301

163-
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
302+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

‎test.js‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const{ expect}=require('chai');
2-
const{ query, Session}=require(".");
2+
const{ query,queryBind,Session}=require(".");
33

44
describe('chDB Queries',function(){
55

@@ -16,6 +16,42 @@ describe('chDB Queries', function () {
1616
}).to.throw(Error,/Unknowntableexpressionidentifier/);
1717
});
1818

19+
it('should return version, greeting message, and chDB() using bind query',()=>{
20+
constret=queryBind("SELECT version(), 'Hello chDB', chDB()",{},"CSV");
21+
console.log("Bind Query Result:",ret);
22+
expect(ret).to.be.a('string');
23+
expect(ret).to.include('Hello chDB');
24+
});
25+
26+
it('binds a numeric parameter (stand-alone query)',()=>{
27+
constout=queryBind('SELECT {id:UInt32}',{id:42},'CSV').trim();
28+
console.log(out)
29+
expect(out).to.equal('42');
30+
});
31+
32+
it('binds a string parameter (stand-alone query)',()=>{
33+
constout=queryBind(
34+
`SELECT concat('Hello ', {name:String})`,
35+
{name:'Alice'},
36+
'CSV'
37+
).trim();
38+
console.log(out)
39+
expect(out).to.equal('"Hello Alice"');
40+
});
41+
42+
it('binds Date and Map correctly',()=>{
43+
constres=queryBind("SELECT {t: DateTime} AS t, {m: Map(String, Array(UInt8))} AS m",
44+
{
45+
t:newDate('2025-05-29T12:00:00Z'),
46+
m:{"abc":Uint8Array.from([1,2,3])}
47+
},
48+
'JSONEachRow'
49+
);
50+
constrow=JSON.parse(res.trim());
51+
expect(row.t).to.equal('2025-05-29 12:00:00');
52+
expect(row.m).to.deep.equal({abc:[1,2,3]});
53+
});
54+
1955
describe('Session Queries',function(){
2056
letsession;
2157

@@ -55,6 +91,14 @@ describe('chDB Queries', function () {
5591
session.query("SELECT * FROM non_existent_table;","CSV");
5692
}).to.throw(Error,/Unknowntableexpressionidentifier/);
5793
});
94+
95+
it('should return result of the query made using bind parameters',()=>{
96+
constret=session.queryBind("SELECT * from testtable where id > {id: UInt32}",{id:2},"CSV");
97+
console.log("Bind Session result:",ret);
98+
expect(ret).to.not.include('1');
99+
expect(ret).to.not.include('2');
100+
expect(ret).to.include('3');
101+
})
58102
});
59103

60104
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp