Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 5 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"targets": [
{
"target_name": "chdb_node",
"sources": [ "lib/chdb_node.cpp" ],
"sources": [ "lib/chdb_node.cpp",
"lib/chdb_connect_api.cpp",
"lib/LocalResultV2Wrapper.cpp",
"lib/module.cpp"
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")",
"."
Expand Down
61 changes: 61 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LocalChDB } from ".";

/**
* Executes a query using the chdb addon.
*
Expand All @@ -7,6 +9,64 @@
*/
export function query(query: string, format?: string): string;


export function queryBuffer(query: string, format?: string): Buffer;

export class LocalResultV2Wrapper {
/**
* Retrieves the buffer containing the result data.
* @returns A `Buffer` containing the query result.
*/
getBuffer(): Buffer;

/**
* Retrieves the length of the buffer.
* @returns The length of the buffer as a `number`.
*/
getLength(): number;

/**
* Retrieves the elapsed time for the query execution.
* @returns The elapsed time in seconds as a `number`.
*/
getElapsed(): number;

/**
* Retrieves the number of rows read during the query execution.
* @returns The number of rows read as a `number`.
*/
getRowsRead(): number;

/**
* Retrieves the number of bytes read during the query execution.
* @returns The number of bytes read as a `number`.
*/
getBytesRead(): number;

/**
* Retrieves the error message, if any.
* @returns The error message as a `string`, or `null` if no error occurred.
*/
getErrorMessage(): string | null;
}


export class LocalChDB {
conn: any;
/**
* The path used for the session. This could be a temporary path or a provided path.
*/
path: string;

/**
* Indicates whether the path is a temporary directory or not.
*/
isTemp: boolean;
constructor(path?: string);
query(query: string|Buffer, format?: string): LocalResultV2Wrapper;

}

/**
* Session class for managing queries and temporary paths.
*/
Expand Down Expand Up @@ -36,6 +96,7 @@ export class Session {
* @returns The query result as a string.
*/
query(query: string, format?: string): string;
queryBuffer(query: string, format?: string): Buffer;

/**
* Cleans up the session, deleting the temporary directory if one was created.
Expand Down
68 changes: 66 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,63 @@ function query(query, format = "CSV") {
return chdbNode.Query(query, format);
}

// Standalone exported query function
function queryBuffer(query, format = "CSV") {
if (!query) {
return "";
}
return chdbNode.QueryBuffer(query, format);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the chdbNode.QueryBuffer seems not exist

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Your right, I've not cleaned up this code :(

}

class LocalChDB {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refer to the libchdb function called here, Connect is a better name than LocalChDB

constructor(path = ":memory:") {
let args = []
if (path === ":memory:") {
this.in_memory = true;
this.isTemp = false;
} else {
this.in_memory = false;
if (path === "") {
// Create a temporary directory
this.path = mkdtempSync(join(os.tmpdir(), 'tmp-chdb-node'));
this.isTemp = true;
} else {
this.path = path;
this.isTemp = false;
}
args.push(["--path", this.path]);
}
this.conn = chdbNode.connectChdb(args);
if (this.in_memory) {
this.query("CREATE DATABASE IF NOT EXISTS default ENGINE = Memory; USE default; SHOW DATABASES;");
} else if (this.isTemp) {
this.query("CREATE DATABASE IF NOT EXISTS default ENGINE = Atomic; USE default; SHOW DATABASES;");
}
}

query(query, format = "CSV") {

if (!query) return "";
let res = chdbNode.queryConn(this.conn, query, format);
if (res.getErrorMessage()) {
throw new Error(res.getErrorMessage());
}
return res;
}

cleanup() {
console.log("cleanup: ", this.isTemp, this.in_memory);

if (this.isTemp) {
console.log("removing: ", this.path);
rmSync(this.path, { recursive: true }); // Replaced rmdirSync with rmSync
}
chdbNode.closeConn(this.conn);
}


}

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

queryBuffer(query, format = "CSV") {
if (!query) return "";
return chdbNode.QuerySessionBuffer(query, format, this.path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the QuerySessionBuffer seems not defined

}

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

module.exports = { query, Session };
module.exports = { query, queryBuffer, Session, LocalChDB };
68 changes: 68 additions & 0 deletions lib/LocalResultV2Wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "LocalResultV2Wrapper.h"

#include "chdb.h"

Napi::FunctionReference LocalResultV2Wrapper::constructor;

Napi::Object LocalResultV2Wrapper::Init(Napi::Env env, Napi::Object exports) {
Napi::Function func = DefineClass(
env, "LocalResultV2Wrapper",
{
InstanceMethod("getBuffer", &LocalResultV2Wrapper::GetBuffer),
InstanceMethod("getLength", &LocalResultV2Wrapper::GetLength),
InstanceMethod("getElapsed", &LocalResultV2Wrapper::GetElapsed),
InstanceMethod("getRowsRead", &LocalResultV2Wrapper::GetRowsRead),
InstanceMethod("getBytesRead", &LocalResultV2Wrapper::GetBytesRead),
InstanceMethod("getErrorMessage", &LocalResultV2Wrapper::GetErrorMessage),
});

constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
exports.Set("LocalResultV2Wrapper", func);

return exports;
}

LocalResultV2Wrapper::LocalResultV2Wrapper(const Napi::CallbackInfo &info)
: Napi::ObjectWrap<LocalResultV2Wrapper>(info) {
result_ = info[0].As<Napi::External<local_result_v2>>().Data();
}

LocalResultV2Wrapper::~LocalResultV2Wrapper() {
if (result_ != nullptr) {
free_result_v2(result_);
}
}

Napi::Object LocalResultV2Wrapper::NewInstance(
Napi::Env env, Napi::External<local_result_v2> external) {
return constructor.New({external});
}

// Accessor Implementations
Napi::Value LocalResultV2Wrapper::GetBuffer(const Napi::CallbackInfo &info) {
return Napi::Buffer<char>::New(info.Env(), result_->buf, result_->len);
}

Napi::Value LocalResultV2Wrapper::GetLength(const Napi::CallbackInfo &info) {
return Napi::Number::New(info.Env(), result_->len);
}

Napi::Value LocalResultV2Wrapper::GetElapsed(const Napi::CallbackInfo &info) {
return Napi::Number::New(info.Env(), result_->elapsed);
}

Napi::Value LocalResultV2Wrapper::GetRowsRead(const Napi::CallbackInfo &info) {
return Napi::Number::New(info.Env(), result_->rows_read);
}

Napi::Value LocalResultV2Wrapper::GetBytesRead(const Napi::CallbackInfo &info) {
return Napi::Number::New(info.Env(), result_->bytes_read);
}

Napi::Value LocalResultV2Wrapper::GetErrorMessage(
const Napi::CallbackInfo &info) {
return result_->error_message == nullptr
? info.Env().Null()
: Napi::String::New(info.Env(), result_->error_message);
}
30 changes: 30 additions & 0 deletions lib/LocalResultV2Wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef LOCAL_RESULT_V2_WRAPPER_H
#define LOCAL_RESULT_V2_WRAPPER_H

#include <napi.h>

#include "chdb.h"

class LocalResultV2Wrapper : public Napi::ObjectWrap<LocalResultV2Wrapper> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
static Napi::Object NewInstance(Napi::Env env,
Napi::External<local_result_v2> external);

LocalResultV2Wrapper(const Napi::CallbackInfo &info);
~LocalResultV2Wrapper();

private:
static Napi::FunctionReference constructor;
local_result_v2 *result_;

// Accessors
Napi::Value GetBuffer(const Napi::CallbackInfo &info);
Napi::Value GetLength(const Napi::CallbackInfo &info);
Napi::Value GetElapsed(const Napi::CallbackInfo &info);
Napi::Value GetRowsRead(const Napi::CallbackInfo &info);
Napi::Value GetBytesRead(const Napi::CallbackInfo &info);
Napi::Value GetErrorMessage(const Napi::CallbackInfo &info);
};

#endif // LOCAL_RESULT_V2_WRAPPER_H
93 changes: 93 additions & 0 deletions lib/chdb_connect_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

#include <napi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>

#include "LocalResultV2Wrapper.h"
#include "chdb.h"
#include "chdb_node.h"

// Wrapper to free_result_v2
void FreeResultV2(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() != 1 || !info[0].IsExternal()) {
Napi::TypeError::New(env, "Expected an external local_result_v2").ThrowAsJavaScriptException();
return;
}

auto result = info[0].As<Napi::External<local_result_v2>>().Data();
free_result_v2(result);
}

// Wrapper to connect_chdb
Napi::Value ConnectChdb(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() < 1 || !info[0].IsArray()) {
Napi::TypeError::New(env, "Expected an array of arguments").ThrowAsJavaScriptException();
return env.Null();
}

Napi::Array args = info[0].As<Napi::Array>();
std::vector<char *> argv;
for (size_t i = 0; i < args.Length(); i++) {
argv.push_back((char *)args.Get(i).As<Napi::String>().Utf8Value().c_str());
}

auto conn = connect_chdb(argv.size(), argv.data());
return Napi::External<chdb_conn>::New(env, *conn);
}

// Wrapper to close_conn
void CloseConn(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() != 1 || !info[0].IsExternal()) {
Napi::TypeError::New(env, "Expected an external chdb_conn")
.ThrowAsJavaScriptException();
return;
}

auto conn = info[0].As<Napi::External<chdb_conn>>().Data();
close_conn(&conn);
}

// Wrapper to query_conn
Napi::Value QueryConn(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();

if (info.Length() != 3 || !info[0].IsExternal() || !info[2].IsString()) {
Napi::TypeError::New(env, "Expected a connection, query (string or Buffer), and format string")
.ThrowAsJavaScriptException();
return env.Null();
}

auto conn = info[0].As<Napi::External<chdb_conn>>().Data();

// Extract query
const char *queryData;
local_result_v2 *result;
std::string format = info[2].As<Napi::String>();
std::cout << "buffer: " << std::endl;

if (info[1].IsString()) {
std::string query = info[1].As<Napi::String>();
result = query_conn(conn, query.c_str(), format.c_str());
} else if (info[1].IsBuffer()) {
Napi::Buffer<char> buffer = info[1].As<Napi::Buffer<char>>();
result = query_conn(conn, buffer.Data(), format.c_str());
} else {
Napi::TypeError::New(env, "Query must be a string or a Buffer").ThrowAsJavaScriptException();
return env.Null();
}



Napi::Object wrapper = LocalResultV2Wrapper::NewInstance(
env, Napi::External<local_result_v2>::New(env, result));
return wrapper;
}
10 changes: 10 additions & 0 deletions lib/chdb_connect_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef SRC_CONNECT_API_H_
#define SRC_CONNECT_API_H_

#include <napi.h>
void FreeResultV2(const Napi::CallbackInfo &info);
Napi::Value ConnectChdb(const Napi::CallbackInfo &info);
Napi::Value CloseConn(const Napi::CallbackInfo &info);
Napi::Value QueryConn(const Napi::CallbackInfo &info);

#endif
Loading