Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework chdb-node api #23

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
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
60 changes: 58 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,60 @@ function query(query, format = "CSV") {
return chdbNode.Query(query, format);
}

class Connect {
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;
}

insert_into(query, values, format = "CSV") {
let q = Buffer.concat([Buffer.from(query), values])
return this.query(q, format)
}

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 @@ -32,8 +86,10 @@ class Session {

// 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, Session, Connect };
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