Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 23 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function queryBind(query, args = {}, format = "CSV") {
return chdbNode.QueryBindSession(query, args, format);
}

// Session class with path handling
// Session class with connection-based path handling
class Session {
constructor(path = "") {
if (path === "") {
Expand All @@ -30,21 +30,38 @@ class Session {
this.path = path;
this.isTemp = false;
}

// Create a connection for this session
this.connection = chdbNode.CreateConnection(this.path);
if (!this.connection) {
throw new Error("Failed to create connection");
}
}

query(query, format = "CSV") {
if (!query) return "";
return chdbNode.QuerySession(query, format, this.path);
if (!this.connection) {
throw new Error("No active connection available");
}
return chdbNode.QueryWithConnection(this.connection, query, format);
}

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

// Cleanup method to delete the temporary directory
// Cleanup method to close connection and delete directory if temp
cleanup() {
rmSync(this.path, { recursive: true }); // Replaced rmdirSync with rmSync
// Close the connection if it exists
if (this.connection) {
chdbNode.CloseConnection(this.connection);
this.connection = null;
}

// Only delete directory if it's temporary
if (this.isTemp) {
rmSync(this.path, { recursive: true });
}
}
}

Expand Down
125 changes: 125 additions & 0 deletions lib/chdb_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
#include <iostream>
#include <napi.h>

typedef void * ChdbConnection;
ChdbConnection CreateConnection(const char * path);
void CloseConnection(ChdbConnection conn);
char * QueryWithConnection(ChdbConnection conn, const char * query, const char * format, char ** error_message);

#define MAX_FORMAT_LENGTH 64
#define MAX_PATH_LENGTH 4096
#define MAX_ARG_COUNT 6
Expand Down Expand Up @@ -189,6 +194,56 @@ char *QueryBindSession(const char *query, const char *format, const char *path,
return query_stable_v2(static_cast<int>(argv.size()), argv.data())->buf;
}

ChdbConnection CreateConnection(const char * path) {
char dataPath[MAX_PATH_LENGTH];
char * args[MAX_ARG_COUNT] = {"clickhouse", NULL};
int argc = 1;

if (path && path[0]) {
construct_arg(dataPath, "--path=", path, MAX_PATH_LENGTH);
args[1] = dataPath;
argc = 2;
}

return static_cast<ChdbConnection>(chdb_connect(argc, args));
}

void CloseConnection(ChdbConnection conn) {
if (conn) {
chdb_close_conn(static_cast<chdb_connection *>(conn));
}
}

char * QueryWithConnection(ChdbConnection conn, const char * query, const char * format, char ** error_message) {
if (!conn || !query || !format) {
return nullptr;
}

chdb_connection * inner_conn = static_cast<chdb_connection *>(conn);
chdb_result * result = chdb_query(*inner_conn, query, format);
if (!result) {
return nullptr;
}

const char * error = chdb_result_error(result);
if (error) {
if (error_message) {
*error_message = strdup(error);
}
chdb_destroy_query_result(result);
return nullptr;
}

const char * buffer = chdb_result_buffer(result);
char * output = nullptr;
if (buffer) {
output = strdup(buffer);
}

chdb_destroy_query_result(result);
return output;
}

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

Expand Down Expand Up @@ -291,11 +346,81 @@ Napi::String QueryBindSessionWrapper(const Napi::CallbackInfo& info) {
return Napi::String::New(env, out);
}

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

if (info.Length() < 1 || !info[0].IsString()) {
Napi::TypeError::New(env, "Path string expected").ThrowAsJavaScriptException();
return env.Null();
}

std::string path = info[0].As<Napi::String>().Utf8Value();
ChdbConnection conn = CreateConnection(path.c_str());

if (!conn) {
Napi::Error::New(env, "Failed to create connection").ThrowAsJavaScriptException();
return env.Null();
}

return Napi::External<void>::New(env, conn);
}

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

if (info.Length() < 1 || !info[0].IsExternal()) {
Napi::TypeError::New(env, "Connection handle expected").ThrowAsJavaScriptException();
return env.Undefined();
}

ChdbConnection conn = info[0].As<Napi::External<void>>().Data();
CloseConnection(conn);

return env.Undefined();
}

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

if (info.Length() < 3 || !info[0].IsExternal() || !info[1].IsString() || !info[2].IsString()) {
Napi::TypeError::New(env, "Usage: connection, query, format").ThrowAsJavaScriptException();
return Napi::String::New(env, "");
}

ChdbConnection conn = info[0].As<Napi::External<void>>().Data();
std::string query = info[1].As<Napi::String>().Utf8Value();
std::string format = info[2].As<Napi::String>().Utf8Value();

char * error_message = nullptr;
char * result = QueryWithConnection(conn, query.c_str(), format.c_str(), &error_message);

if (error_message) {
std::string error_msg = std::string("Query failed: ") + error_message;
free(error_message);
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
return Napi::String::New(env, "");
}

if (!result) {
return Napi::String::New(env, "");
}

Napi::String output = Napi::String::New(env, result);
free(result);
return output;
}

Napi::Object Init(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));

// Export connection management functions
exports.Set("CreateConnection", Napi::Function::New(env, CreateConnectionWrapper));
exports.Set("CloseConnection", Napi::Function::New(env, CloseConnectionWrapper));
exports.Set("QueryWithConnection", Napi::Function::New(env, QueryWithConnectionWrapper));

return exports;
}

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chdb",
"version": "1.4.0",
"version": "1.5.0",
"description": "chDB bindings for nodejs",
"main": "index.js",
"repository": {
Expand All @@ -9,7 +9,7 @@
},
"scripts": {
"install": "npm run libchdb && npm run build",
"test": "mocha --timeout 15000",
"test": "mocha test_basic.js test_connection.js --timeout 15000",
"libchdb": "bash ./update_libchdb.sh",
"fixloaderpath": "bash ./fix_loader_path.sh",
"build": "node-gyp configure build --verbose && npm run fixloaderpath"
Expand Down
22 changes: 14 additions & 8 deletions test.js → test_basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,16 @@ describe('chDB Queries', function () {
let session;

before(function () {
// Create a new session instance before running the tests
session = new Session("./chdb-node-tmp");
// Delete existing directory and create a new session instance
const fs = require('fs');
const path = require('path');
const tmpDir = "./chdb-node-tmp";

if (fs.existsSync(tmpDir)) {
fs.rmSync(tmpDir, { recursive: true, force: true });
}

session = new Session(tmpDir);
});

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

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

Expand Down
Loading