1010#define MAX_PATH_LENGTH 4096
1111#define MAX_ARG_COUNT 6
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+ return std::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+ return toCHLiteral (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+ return chEscape (v.ToString ().Utf8Value ());
81+ }
82+
1383// Utility function to construct argument string
1484void construct_arg (char *dest,const char *prefix,const char *value,
1585size_t dest_size) {
@@ -92,21 +162,46 @@ char *QuerySession(const char *query, const char *format, const char *path,
92162return result;
93163}
94164
165+ char *QueryBindSession (const char *query,const char *format,const char *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 (const auto & 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+ return query_stable_v2 (static_cast <int >(argv.size ()), argv.data ())->buf ;
190+ }
191+
95192Napi::StringQueryWrapper (const Napi::CallbackInfo &info) {
96193 Napi::Env env = info.Env ();
97194
98- // Check argument types and count
99195if (info.Length () <2 || !info[0 ].IsString () || !info[1 ].IsString ()) {
100196Napi::TypeError::New (env," String expected" ).ThrowAsJavaScriptException ();
101197return Napi::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
108203char *error_message =nullptr ;
109- // Call the native function
204+
110205char *result =Query (query.c_str (), format.c_str (), &error_message);
111206
112207if (result ==NULL ) {
@@ -117,7 +212,6 @@ Napi::String QueryWrapper(const Napi::CallbackInfo &info) {
117212return Napi::String::New (env," " );
118213 }
119214
120- // Return the result
121215return Napi::String::New (env, result);
122216}
123217
@@ -153,11 +247,56 @@ Napi::String QuerySessionWrapper(const Napi::CallbackInfo &info) {
153247return Napi::String::New (env, result);
154248}
155249
250+ static std::stringjsToParam (const Napi::Env& env,const Napi::Value& v) {
251+ return toCHLiteral (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 (const auto & 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+ return Napi::String::New (env," " );
290+ }
291+ return Napi::String::New (env, out);
292+ }
293+
156294Napi::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));
160299return exports;
161300}
162301
163- NODE_API_MODULE (NODE_GYP_MODULE_NAME, Init)
302+ NODE_API_MODULE (NODE_GYP_MODULE_NAME, Init)