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

Extending cpp bindings to support Clickhouse params thereby enabling …#24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
auxten merged 1 commit intochdb-io:mainfromrajdude0:BIND_QUERY
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletionsindex.d.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,16 @@
*/
export function query(query: string, format?: string): string;

/**
* Executes a query with parameters using the chdb addon.
*
* @param query The query string to execute.
* @param binding arguments for parameters defined in the query.
* @param format The format for the query result, default is "CSV".
* @returns The query result as a string.
*/
export function queryBind(query:string, args: object, format?:string): string;

/**
* Session class for managing queries and temporary paths.
*/
Expand DownExpand Up@@ -37,6 +47,17 @@ export class Session {
*/
query(query: string, format?: string): string;

/**
* Executes a query with parameters using the chdb addon.
*
* @param query The query string to execute.
* @param binding arguments for parameters defined in the query.
* @param format The format for the query result, default is "CSV".
* @returns The query result as a string.
*/

queryBind(query:string, args: object, format?: string): string;

/**
* Cleans up the session, deleting the temporary directory if one was created.
*/
Expand Down
14 changes: 13 additions & 1 deletionindex.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,13 @@ function query(query, format = "CSV") {
return chdbNode.Query(query, format);
}

function queryBind(query, args = {}, format = "CSV") {
if(!query) {
return "";
}
return chdbNode.QueryBindSession(query, args, format);
}

// Session class with path handling
class Session {
constructor(path = "") {
Expand All@@ -30,10 +37,15 @@ class Session {
return chdbNode.QuerySession(query, format, this.path);
}

queryBind(query, args = {}, format = "CSV") {
if(!query) return "";
return chdbNode.QueryBindSession(query, args, format, this.path)
}

// Cleanup method to delete the temporary directory
cleanup() {
rmSync(this.path, { recursive: true }); // Replaced rmdirSync with rmSync
}
}

module.exports = { query, Session };
module.exports = { query,queryBind,Session };
149 changes: 144 additions & 5 deletionslib/chdb_node.cpp
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,76 @@
#defineMAX_PATH_LENGTH4096
#defineMAX_ARG_COUNT6


static std::stringtoCHLiteral(const Napi::Env& env,const Napi::Value& v);


static std::stringchEscape(const std::string& s)
{
std::string out;
out.reserve(s.size() +4);
out +='\'';
for (char c : s) {
if (c =='\'') out +="\\'";
else out += c;
}
out +='\'';
return out;
}

static std::stringtoCHLiteral(const Napi::Env& env,const Napi::Value& v)
{
if (v.IsNumber() || v.IsBoolean() || v.IsString())
return v.ToString().Utf8Value();

if (v.IsDate()) {
double ms = v.As<Napi::Date>().ValueOf();
std::time_t t =static_cast<std::time_t>(ms /1000);
std::tm tm{};
gmtime_r(&t, &tm);
char buf[32];
strftime(buf,sizeof(buf),"%Y-%m-%d %H:%M:%S", &tm);
returnstd::string(&buf[0],sizeof(buf));
}

if (v.IsTypedArray()) {
Napi::Object arr = env.Global().Get("Array").As<Napi::Object>();
Napi::Function from = arr.Get("from").As<Napi::Function>();
returntoCHLiteral(env, from.Call(arr, { v }));
}

if (v.IsArray()) {
Napi::Array a = v.As<Napi::Array>();
size_t n = a.Length();
std::string out ="[";
for (size_t i =0; i < n; ++i) {
if (i) out +=",";
out +=toCHLiteral(env, a.Get(i));
}
out +="]";
return out;
}

if (v.IsObject()) {
Napi::Object o = v.As<Napi::Object>();
Napi::Array keys = o.GetPropertyNames();
size_t n = keys.Length();
std::string out ="{";
for (size_t i =0; i < n; ++i) {
if (i) out +=",";
std::string k = keys.Get(i).ToString().Utf8Value();
out +=chEscape(k);// escape the map key with single-qoutes for click house query to work i.e 'key' not "key"
out +=":";
out +=toCHLiteral(env, o.Get(keys.Get(i)));
}
out +="}";
return out;
}

/* Fallback – stringify & quote*/
returnchEscape(v.ToString().Utf8Value());
}

// Utility function to construct argument string
voidconstruct_arg(char *dest,constchar *prefix,constchar *value,
size_t dest_size) {
Expand DownExpand Up@@ -92,21 +162,46 @@ char *QuerySession(const char *query, const char *format, const char *path,
return result;
}

char *QueryBindSession(constchar *query,constchar *format,constchar *path,
const std::vector<std::string>& params,char **error_message) {

std::vector<std::string> store;
store.reserve(4 + params.size() + (path && path[0] ?1 :0));

store.emplace_back("clickhouse");
store.emplace_back("--multiquery");
store.emplace_back(std::string("--output-format=") + format);
store.emplace_back(std::string("--query=") + query);

for (constauto& p : params) store.emplace_back(p);
if (path && path[0]) store.emplace_back(std::string("--path=") + path);

std::vector<char*> argv;
argv.reserve(store.size());
for (auto& s : store)
argv.push_back(const_cast<char*>(s.c_str()));

#ifdef CHDB_DEBUG
std::cerr <<"=== chdb argv (" << argv.size() <<") ===\n";
for (char* a : argv) std::cerr << a <<'\n';
#endif

returnquery_stable_v2(static_cast<int>(argv.size()), argv.data())->buf;
}

Napi::StringQueryWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

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

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

char *error_message =nullptr;
// Call the native function

char *result =Query(query.c_str(), format.c_str(), &error_message);

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

// Return the result
returnNapi::String::New(env, result);
}

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

static std::stringjsToParam(const Napi::Env& env,const Napi::Value& v) {
returntoCHLiteral(env, v);
}

Napi::StringQueryBindSessionWrapper(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() <2 || !info[0].IsString() || !info[1].IsObject())
Napi::TypeError::New(env,"Usage: sql, params, [format]").ThrowAsJavaScriptException();

std::string sql = info[0].As<Napi::String>();
Napi::Object obj = info[1].As<Napi::Object>();
std::string format = (info.Length() >2 && info[2].IsString())
? info[2].As<Napi::String>() :std::string("CSV");
std::string path = (info.Length() >3 && info[3].IsString())
? info[3].As<Napi::String>() :std::string("");

// Build param vector
std::vector<std::string> cliParams;
Napi::Array keys = obj.GetPropertyNames();
int len = keys.Length();
for (int i =0; i < len; i++) {
Napi::Value k = keys.Get(i);
if(!k.IsString())continue;

std::string key = k.As<Napi::String>();
std::string val =jsToParam(env, obj.Get(k));
cliParams.emplace_back("--param_" + key +"=" + val);
}

#ifdef CHDB_DEBUG
std::cerr <<"=== cliParams ===\n";
for (constauto& s : cliParams)
std::cerr << s <<'\n';
#endif

char* err =nullptr;
char* out =QueryBindSession(sql.c_str(), format.c_str(), path.c_str(), cliParams, &err);
if (!out) {
Napi::Error::New(env, err ? err :"unknown error").ThrowAsJavaScriptException();
returnNapi::String::New(env,"");
}
returnNapi::String::New(env, out);
}

Napi::ObjectInit(Napi::Env env, Napi::Object exports) {
// Export the functions
exports.Set("Query",Napi::Function::New(env, QueryWrapper));
exports.Set("QuerySession",Napi::Function::New(env, QuerySessionWrapper));
exports.Set("QueryBindSession",Napi::Function::New(env, QueryBindSessionWrapper));
return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
46 changes: 45 additions & 1 deletiontest.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
const { expect } = require('chai');
const { query, Session } = require(".");
const { query,queryBind,Session } = require(".");

describe('chDB Queries', function () {

Expand All@@ -16,6 +16,42 @@ describe('chDB Queries', function () {
}).to.throw(Error, /Unknown table expression identifier/);
});

it('should return version, greeting message, and chDB() using bind query', () => {
const ret = queryBind("SELECT version(), 'Hello chDB', chDB()", {}, "CSV");
console.log("Bind Query Result:", ret);
expect(ret).to.be.a('string');
expect(ret).to.include('Hello chDB');
});

it('binds a numeric parameter (stand-alone query)', () => {
const out = queryBind('SELECT {id:UInt32}', { id: 42 }, 'CSV').trim();
console.log(out)
expect(out).to.equal('42');
});

it('binds a string parameter (stand-alone query)', () => {
const out = queryBind(
`SELECT concat('Hello ', {name:String})`,
{ name: 'Alice' },
'CSV'
).trim();
console.log(out)
expect(out).to.equal('"Hello Alice"');
});

it('binds Date and Map correctly', () => {
const res = queryBind("SELECT {t: DateTime} AS t, {m: Map(String, Array(UInt8))} AS m",
{
t: new Date('2025-05-29T12:00:00Z'),
m: { "abc": Uint8Array.from([1, 2, 3]) }
},
'JSONEachRow'
);
const row = JSON.parse(res.trim());
expect(row.t).to.equal('2025-05-29 12:00:00');
expect(row.m).to.deep.equal({ abc: [1, 2, 3] });
});

describe('Session Queries', function () {
let session;

Expand DownExpand Up@@ -55,6 +91,14 @@ describe('chDB Queries', function () {
session.query("SELECT * FROM non_existent_table;", "CSV");
}).to.throw(Error, /Unknown table expression identifier/);
});

it('should return result of the query made using bind parameters', () => {
const ret = session.queryBind("SELECT * from testtable where id > {id: UInt32}", { id: 2}, "CSV");
console.log("Bind Session result:", ret);
expect(ret).to.not.include('1');
expect(ret).to.not.include('2');
expect(ret).to.include('3');
})
});

});
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp