1010#define MAX_PATH_LENGTH 4096
1111#define MAX_ARG_COUNT 6
1212
13+
14+ static std::string toCHLiteral (const Napi::Env& env, const Napi::Value& v);
15+
16+
17+ static std::string chEscape (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::string toCHLiteral (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,
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 (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::String QueryWrapper (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 return 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
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 return Napi::String::New (env, " " );
118213 }
119214
120- // Return the result
121215 return Napi::String::New (env, result);
122216}
123217
@@ -153,11 +247,56 @@ Napi::String QuerySessionWrapper(const Napi::CallbackInfo &info) {
153247 return Napi::String::New (env, result);
154248}
155249
250+ static std::string jsToParam (const Napi::Env& env, const Napi::Value& v) {
251+ return toCHLiteral (env, v);
252+ }
253+
254+ Napi::String QueryBindSessionWrapper (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::Object Init (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)
0 commit comments