10
10
#define MAX_PATH_LENGTH 4096
11
11
#define MAX_ARG_COUNT 6
12
12
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
+
13
83
// Utility function to construct argument string
14
84
void construct_arg (char *dest, const char *prefix, const char *value,
15
85
size_t dest_size) {
@@ -92,21 +162,46 @@ char *QuerySession(const char *query, const char *format, const char *path,
92
162
return result;
93
163
}
94
164
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
+
95
192
Napi::String QueryWrapper (const Napi::CallbackInfo &info) {
96
193
Napi::Env env = info.Env ();
97
194
98
- // Check argument types and count
99
195
if (info.Length () < 2 || !info[0 ].IsString () || !info[1 ].IsString ()) {
100
196
Napi::TypeError::New (env, " String expected" ).ThrowAsJavaScriptException ();
101
197
return Napi::String::New (env, " " );
102
198
}
103
199
104
- // Get the arguments
105
200
std::string query = info[0 ].As <Napi::String>().Utf8Value ();
106
201
std::string format = info[1 ].As <Napi::String>().Utf8Value ();
107
202
108
203
char *error_message = nullptr ;
109
- // Call the native function
204
+
110
205
char *result = Query (query.c_str (), format.c_str (), &error_message);
111
206
112
207
if (result == NULL ) {
@@ -117,7 +212,6 @@ Napi::String QueryWrapper(const Napi::CallbackInfo &info) {
117
212
return Napi::String::New (env, " " );
118
213
}
119
214
120
- // Return the result
121
215
return Napi::String::New (env, result);
122
216
}
123
217
@@ -153,11 +247,56 @@ Napi::String QuerySessionWrapper(const Napi::CallbackInfo &info) {
153
247
return Napi::String::New (env, result);
154
248
}
155
249
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
+
156
294
Napi::Object Init (Napi::Env env, Napi::Object exports) {
157
295
// Export the functions
158
296
exports.Set (" Query" , Napi::Function::New (env, QueryWrapper));
159
297
exports.Set (" QuerySession" , Napi::Function::New (env, QuerySessionWrapper));
298
+ exports.Set (" QueryBindSession" , Napi::Function::New (env, QueryBindSessionWrapper));
160
299
return exports;
161
300
}
162
301
163
- NODE_API_MODULE (NODE_GYP_MODULE_NAME, Init)
302
+ NODE_API_MODULE (NODE_GYP_MODULE_NAME, Init)
0 commit comments