Skip to content

Commit 2663eda

Browse files
committed
Reuse connection
1 parent 24898e1 commit 2663eda

File tree

5 files changed

+257
-15
lines changed

5 files changed

+257
-15
lines changed

index.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function queryBind(query, args = {}, format = "CSV") {
1919
return chdbNode.QueryBindSession(query, args, format);
2020
}
2121

22-
// Session class with path handling
22+
// Session class with connection-based path handling
2323
class Session {
2424
constructor(path = "") {
2525
if (path === "") {
@@ -30,21 +30,38 @@ class Session {
3030
this.path = path;
3131
this.isTemp = false;
3232
}
33+
34+
// Create a connection for this session
35+
this.connection = chdbNode.CreateConnection(this.path);
36+
if (!this.connection) {
37+
throw new Error("Failed to create connection");
38+
}
3339
}
3440

3541
query(query, format = "CSV") {
3642
if (!query) return "";
37-
return chdbNode.QuerySession(query, format, this.path);
43+
if (!this.connection) {
44+
throw new Error("No active connection available");
45+
}
46+
return chdbNode.QueryWithConnection(this.connection, query, format);
3847
}
3948

4049
queryBind(query, args = {}, format = "CSV") {
41-
if(!query) return "";
42-
return chdbNode.QueryBindSession(query, args, format, this.path)
50+
throw new Error("QueryBind is not supported with connection-based sessions. Please use the standalone queryBind function instead.");
4351
}
4452

45-
// Cleanup method to delete the temporary directory
53+
// Cleanup method to close connection and delete directory if temp
4654
cleanup() {
47-
rmSync(this.path, { recursive: true }); // Replaced rmdirSync with rmSync
55+
// Close the connection if it exists
56+
if (this.connection) {
57+
chdbNode.CloseConnection(this.connection);
58+
this.connection = null;
59+
}
60+
61+
// Only delete directory if it's temporary
62+
if (this.isTemp) {
63+
rmSync(this.path, { recursive: true });
64+
}
4865
}
4966
}
5067

lib/chdb_node.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
#include "chdb.h"
22
#include "chdb_node.h"
3+
#include <cstddef>
34
#include <stdio.h>
45
#include <stdlib.h>
56
#include <string.h>
67
#include <iostream>
78
#include <napi.h>
89

10+
typedef void * ChdbConnection;
11+
ChdbConnection CreateConnection(const char * path);
12+
void CloseConnection(ChdbConnection conn);
13+
char * QueryWithConnection(ChdbConnection conn, const char * query, const char * format, char ** error_message);
14+
915
#define MAX_FORMAT_LENGTH 64
1016
#define MAX_PATH_LENGTH 4096
1117
#define MAX_ARG_COUNT 6
@@ -189,6 +195,56 @@ char *QueryBindSession(const char *query, const char *format, const char *path,
189195
return query_stable_v2(static_cast<int>(argv.size()), argv.data())->buf;
190196
}
191197

198+
ChdbConnection CreateConnection(const char * path) {
199+
char dataPath[MAX_PATH_LENGTH];
200+
char * args[MAX_ARG_COUNT] = {"clickhouse", NULL};
201+
int argc = 1;
202+
203+
if (path && path[0]) {
204+
construct_arg(dataPath, "--path=", path, MAX_PATH_LENGTH);
205+
args[1] = dataPath;
206+
argc = 2;
207+
}
208+
209+
return static_cast<ChdbConnection>(chdb_connect(argc, args));
210+
}
211+
212+
void CloseConnection(ChdbConnection conn) {
213+
if (conn) {
214+
chdb_close_conn(static_cast<chdb_connection *>(conn));
215+
}
216+
}
217+
218+
char * QueryWithConnection(ChdbConnection conn, const char * query, const char * format, char ** error_message) {
219+
if (!conn || !query || !format) {
220+
return nullptr;
221+
}
222+
223+
chdb_connection * inner_conn = static_cast<chdb_connection *>(conn);
224+
chdb_result * result = chdb_query(*inner_conn, query, format);
225+
if (!result) {
226+
return nullptr;
227+
}
228+
229+
const char * error = chdb_result_error(result);
230+
if (error) {
231+
if (error_message) {
232+
*error_message = strdup(error);
233+
}
234+
chdb_destroy_query_result(result);
235+
return nullptr;
236+
}
237+
238+
const char * buffer = chdb_result_buffer(result);
239+
char * output = nullptr;
240+
if (buffer) {
241+
output = strdup(buffer);
242+
}
243+
244+
chdb_destroy_query_result(result);
245+
return output;
246+
}
247+
192248
Napi::String QueryWrapper(const Napi::CallbackInfo &info) {
193249
Napi::Env env = info.Env();
194250

@@ -291,11 +347,81 @@ Napi::String QueryBindSessionWrapper(const Napi::CallbackInfo& info) {
291347
return Napi::String::New(env, out);
292348
}
293349

350+
Napi::Value CreateConnectionWrapper(const Napi::CallbackInfo & info) {
351+
Napi::Env env = info.Env();
352+
353+
if (info.Length() < 1 || !info[0].IsString()) {
354+
Napi::TypeError::New(env, "Path string expected").ThrowAsJavaScriptException();
355+
return env.Null();
356+
}
357+
358+
std::string path = info[0].As<Napi::String>().Utf8Value();
359+
ChdbConnection conn = CreateConnection(path.c_str());
360+
361+
if (!conn) {
362+
Napi::Error::New(env, "Failed to create connection").ThrowAsJavaScriptException();
363+
return env.Null();
364+
}
365+
366+
return Napi::External<void>::New(env, conn);
367+
}
368+
369+
Napi::Value CloseConnectionWrapper(const Napi::CallbackInfo & info) {
370+
Napi::Env env = info.Env();
371+
372+
if (info.Length() < 1 || !info[0].IsExternal()) {
373+
Napi::TypeError::New(env, "Connection handle expected").ThrowAsJavaScriptException();
374+
return env.Undefined();
375+
}
376+
377+
ChdbConnection conn = info[0].As<Napi::External<void>>().Data();
378+
CloseConnection(conn);
379+
380+
return env.Undefined();
381+
}
382+
383+
Napi::String QueryWithConnectionWrapper(const Napi::CallbackInfo & info) {
384+
Napi::Env env = info.Env();
385+
386+
if (info.Length() < 3 || !info[0].IsExternal() || !info[1].IsString() || !info[2].IsString()) {
387+
Napi::TypeError::New(env, "Usage: connection, query, format").ThrowAsJavaScriptException();
388+
return Napi::String::New(env, "");
389+
}
390+
391+
ChdbConnection conn = info[0].As<Napi::External<void>>().Data();
392+
std::string query = info[1].As<Napi::String>().Utf8Value();
393+
std::string format = info[2].As<Napi::String>().Utf8Value();
394+
395+
char * error_message = nullptr;
396+
char * result = QueryWithConnection(conn, query.c_str(), format.c_str(), &error_message);
397+
398+
if (error_message) {
399+
std::string error_msg = std::string("Query failed: ") + error_message;
400+
free(error_message);
401+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
402+
return Napi::String::New(env, "");
403+
}
404+
405+
if (!result) {
406+
return Napi::String::New(env, "");
407+
}
408+
409+
Napi::String output = Napi::String::New(env, result);
410+
free(result);
411+
return output;
412+
}
413+
294414
Napi::Object Init(Napi::Env env, Napi::Object exports) {
295415
// Export the functions
296416
exports.Set("Query", Napi::Function::New(env, QueryWrapper));
297417
exports.Set("QuerySession", Napi::Function::New(env, QuerySessionWrapper));
298418
exports.Set("QueryBindSession", Napi::Function::New(env, QueryBindSessionWrapper));
419+
420+
// Export connection management functions
421+
exports.Set("CreateConnection", Napi::Function::New(env, CreateConnectionWrapper));
422+
exports.Set("CloseConnection", Napi::Function::New(env, CloseConnectionWrapper));
423+
exports.Set("QueryWithConnection", Napi::Function::New(env, QueryWithConnectionWrapper));
424+
299425
return exports;
300426
}
301427

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"scripts": {
1111
"install": "npm run libchdb && npm run build",
12-
"test": "mocha --timeout 15000",
12+
"test": "mocha test_basic.js test_connection.js --timeout 15000",
1313
"libchdb": "bash ./update_libchdb.sh",
1414
"fixloaderpath": "bash ./fix_loader_path.sh",
1515
"build": "node-gyp configure build --verbose && npm run fixloaderpath"

test.js renamed to test_basic.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,16 @@ describe('chDB Queries', function () {
5656
let session;
5757

5858
before(function () {
59-
// Create a new session instance before running the tests
60-
session = new Session("./chdb-node-tmp");
59+
// Delete existing directory and create a new session instance
60+
const fs = require('fs');
61+
const path = require('path');
62+
const tmpDir = "./chdb-node-tmp";
63+
64+
if (fs.existsSync(tmpDir)) {
65+
fs.rmSync(tmpDir, { recursive: true, force: true });
66+
}
67+
68+
session = new Session(tmpDir);
6169
});
6270

6371
after(function () {
@@ -92,12 +100,10 @@ describe('chDB Queries', function () {
92100
}).to.throw(Error, /Unknown table expression identifier/);
93101
});
94102

95-
it('should return result of the query made using bind parameters', () => {
96-
const ret = session.queryBind("SELECT * from testdb.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');
103+
it('should throw an error when using queryBind with session', () => {
104+
expect(() => {
105+
session.queryBind("SELECT * from testdb.testtable where id > {id: UInt32}", { id: 2}, "CSV");
106+
}).to.throw(Error, /QueryBind is not supported with connection-based sessions. Please use the standalone queryBind function instead./);
101107
})
102108
});
103109

test_connection.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const { expect } = require('chai');
2+
const { Session } = require(".");
3+
4+
describe('chDB Connection Tests', function () {
5+
6+
describe('Session Connection Management', function () {
7+
let session;
8+
9+
before(function () {
10+
// Delete existing directory and create a new session instance
11+
const fs = require('fs');
12+
const tmpDir = "./test-connection-tmp";
13+
14+
if (fs.existsSync(tmpDir)) {
15+
fs.rmSync(tmpDir, { recursive: true, force: true });
16+
}
17+
18+
session = new Session(tmpDir);
19+
});
20+
21+
after(function () {
22+
// Clean up the session after all tests are done
23+
session.cleanup();
24+
});
25+
26+
it('should create session successfully with path and connection ID', function () {
27+
expect(session.path).to.equal("./test-connection-tmp");
28+
expect(session.connection).to.not.be.null;
29+
expect(session.connection).to.not.be.undefined;
30+
console.log("✓ Session created successfully, path:", session.path);
31+
console.log("✓ Connection:", session.connection);
32+
});
33+
34+
it('should execute simple query and return correct result', function () {
35+
const result = session.query("SELECT 1 as test_col", "CSV");
36+
console.log("Query result:", result.trim());
37+
expect(result).to.be.a('string');
38+
expect(result.trim()).to.equal('1');
39+
});
40+
41+
it('should return version information', function () {
42+
const result = session.query("SELECT version()", "CSV");
43+
console.log("Version info:", result.trim());
44+
expect(result).to.be.a('string');
45+
expect(result).to.include('.');
46+
});
47+
48+
it('should create database and table successfully', function () {
49+
// This should not throw an error
50+
expect(() => {
51+
session.query("CREATE DATABASE IF NOT EXISTS test_conn_db");
52+
session.query("CREATE TABLE IF NOT EXISTS test_conn_db.test_table (id UInt32, name String) ENGINE = MergeTree() ORDER BY id");
53+
}).to.not.throw();
54+
console.log("✓ Database and table created successfully");
55+
});
56+
57+
it('should insert data successfully', function () {
58+
expect(() => {
59+
session.query("INSERT INTO test_conn_db.test_table VALUES (1, 'Alice'), (2, 'Bob')");
60+
}).to.not.throw();
61+
console.log("✓ Data inserted successfully");
62+
});
63+
64+
it('should query inserted data and verify connection reuse', function () {
65+
const result = session.query("SELECT * FROM test_conn_db.test_table ORDER BY id", "CSV");
66+
console.log("Query result:", result.trim());
67+
expect(result).to.be.a('string');
68+
expect(result).to.include('Alice');
69+
expect(result).to.include('Bob');
70+
expect(result).to.include('1');
71+
expect(result).to.include('2');
72+
});
73+
74+
it('should throw error when using queryBind with session', function () {
75+
expect(() => {
76+
session.queryBind("SELECT {id:UInt32}", {id: 42});
77+
}).to.throw(Error, /QueryBind is not supported with connection-based sessions. Please use the standalone queryBind function instead./);
78+
console.log("✓ queryBind correctly throws error");
79+
});
80+
81+
it('should handle multiple queries in sequence (connection persistence)', function () {
82+
const result1 = session.query("SELECT COUNT(*) FROM test_conn_db.test_table", "CSV");
83+
const result2 = session.query("SELECT MAX(id) FROM test_conn_db.test_table", "CSV");
84+
const result3 = session.query("SELECT name FROM test_conn_db.test_table WHERE id = 1", "CSV");
85+
86+
expect(result1.trim()).to.equal('2');
87+
expect(result2.trim()).to.equal('2');
88+
expect(result3.trim()).to.include('Alice');
89+
console.log("✓ Connection persistence test passed");
90+
});
91+
});
92+
93+
});

0 commit comments

Comments
 (0)