diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index e969160..29d6eba 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -24,4 +24,5 @@ jobs: with: bun-version: latest - run: bun install - - run: bun example.ts + - run: bun run build + - run: bun run example.ts diff --git a/.gitignore b/.gitignore index 4bfd115..0b95b7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules -/lib/libchdb_bun.dylib +*.so +/lib/chdb_bun.dylib /lib/libchdb.so /lib/index.js /lib/index.d.ts diff --git a/README.md b/README.md index f992e5b..e054899 100644 --- a/README.md +++ b/README.md @@ -14,48 +14,34 @@ Experimental [chDB](https://github.com/chdb-io/chdb) FFI bindings for the [bun r #### Build binding ```bash -bun install chdb-bun +bun run build +bun run example.ts ``` #### Usage -#### Query Constructor -```js -import { db } from 'chdb-bun'; - -const conn = new db('CSV') -console.log(conn.query("SELECT version()")); -``` - -#### Query _(query, *format)_ +#### Query(query, *format) (ephemeral) ```javascript -import { db } from 'chdb-bun'; -const conn = new db('CSV') +import { query } from 'chdb-bun'; // Query (ephemeral) -var result = conn.query("SELECT version()", "CSV"); -console.log(result) // 23.10.1.1 +var result = query("SELECT version()", "CSV"); +console.log(result); // 23.10.1.1 ``` -#### Session _(query, *format, *path)_ +#### Session.Query(query, *format) ```javascript -import { db } from 'chdb-bun'; -const conn = new db('CSV', '/tmp') +import { Session } from 'chdb-bun'; +const sess = new Session('./chdb-bun-tmp'); // Query Session (persistent) -conn.session("CREATE FUNCTION IF NOT EXISTS hello AS () -> 'chDB'"); -result = conn.session("SELECT hello()", "CSV"); -console.log(result) -``` +sess.query("CREATE FUNCTION IF NOT EXISTS hello AS () -> 'Hello chDB'", "CSV"); +var result = sess.query("SELECT hello()", "CSV"); +console.log(result); -> ⚠️ Sessions persist table data to disk. You can specify `path` to implement auto-cleanup strategies: -```javascript -const temperment = require("temperment"); -const tmp = temperment.directory(); -conn.session("CREATE FUNCTION IF NOT EXISTS hello AS () -> 'chDB'", "CSV", tmp) -var result = = chdb.Session("SELECT hello();") -console.log(result) // chDB -tmp.cleanup.sync(); +// Before cleanup, you can find the database files in `./chdb-bun-tmp` + +sess.cleanup(); // cleanup session, this will delete the database ```
diff --git a/example.ts b/example.ts index 3f061fd..61bdc96 100644 --- a/example.ts +++ b/example.ts @@ -1,13 +1,17 @@ -import { db, chdb } from "."; +import { query, Session } from "."; -const conn = new db("CSV", "/tmp"); +// Create a new session instance +const session = new Session("./chdb-bun-tmp"); var result; -// Test query -result = conn.query("SELECT version(), chdb()"); +// Test standalone query +result = query("SELECT version(), 'Hello chDB', chdb()", "CSV"); console.log(result); -// Test session -conn.session("CREATE FUNCTION IF NOT EXISTS hello AS () -> 'chDB'"); -result = conn.session("SELECT hello()", "CSV"); +// Test session query +session.query("CREATE FUNCTION IF NOT EXISTS hello AS () -> 'chDB'", "CSV"); +result = session.query("SELECT hello()", "CSV"); console.log(result); + +// Clean up the session +session.cleanup(); diff --git a/index.ts b/index.ts index 9264281..b6ca1aa 100644 --- a/index.ts +++ b/index.ts @@ -1,39 +1,56 @@ -import { dlopen, FFIType, suffix, CString, ptr } from "bun:ffi"; +import { dlopen, FFIType } from "bun:ffi"; +import { rmdirSync, mkdtempSync } from 'fs'; -const path = `lib/libchdb_bun.${suffix}`; +const path = `chdb_bun.so`; const { symbols: chdb } = dlopen(path, { - Execute: { + Query: { args: [FFIType.cstring, FFIType.cstring], returns: FFIType.cstring, }, - ExecuteSession: { + QuerySession: { args: [FFIType.cstring, FFIType.cstring, FFIType.cstring], returns: FFIType.cstring, }, }); -class db { - format: string; +// Standalone exported query function +export function query(query: string, format: string = "CSV") { + if (!query) { + return ""; + } + return chdb.Query(Buffer.from(query + "\0"), Buffer.from(format + "\0")); +} + +// Session class with path handling +class Session { path: string; + isTemp: boolean; + query(query: string, format: string = "CSV") { - if (!query) { - return ""; - } - return chdb.Execute(Buffer.from(query + "\0"), Buffer.from(format + "\0")); - } - session(query: string, format: string = "CSV", path: string = "/tmp") { if (!query) return ""; - return chdb.ExecuteSession( + return chdb.QuerySession( Buffer.from(query + "\0"), Buffer.from(format + "\0"), - Buffer.from(path + "\0") + Buffer.from(this.path + "\0") ); } - constructor(format: string = "JSONCompact", path: string = ".") { - this.format = format; - this.path = path; + + constructor(path: string = "") { + if (path === "") { + // Create a temporary directory + this.path = mkdtempSync("tmp-"); + this.isTemp = true; + } else { + this.path = path; + this.isTemp = false; + } + } + + // Cleanup method to delete the temporary directory + cleanup() { + rmdirSync(this.path, { recursive: true }); } } -export { chdb, db }; +export { chdb, Session }; diff --git a/lib/README.md b/lib/README.md index 8ba8dca..f38c410 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,6 +1,9 @@ -## libchdb `Execute` function binding +## libchdb `Query` function binding ```bash +# install dependencies +./update_libchdb.sh + # build the dynamic library ./build.sh diff --git a/lib/binding.bun b/lib/binding.bun deleted file mode 100644 index 9c2eb2a..0000000 --- a/lib/binding.bun +++ /dev/null @@ -1,9 +0,0 @@ -export function Execute(query: string, format: string): string { - const executeFn = ffi('char *Execute(char *, char *)'); - const result = executeFn(query, format); - if (result === null) { - throw new Error('Out of memory'); - } - defer free_result(result) - return result; -} diff --git a/lib/build.sh b/lib/build.sh index 71e4ffd..e1110d2 100755 --- a/lib/build.sh +++ b/lib/build.sh @@ -1,8 +1,13 @@ +#!/bin/bash + if [ "$(uname)" == "Darwin" ]; then - gcc -dynamiclib -o libchdb_bun.dylib -L. -lchdb libchdb_bun.c + clang -O3 -dynamiclib -o chdb_bun.so -L. -lchdb chdb_bun.c elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then - gcc -shared -fPIC -o libchdb_bun.so libchdb_bun.c -L. -lchdb + clang -O3 -shared -fPIC -o chdb_bun.so -L. -lchdb chdb_bun.c else echo "Unsupported operating system" exit 1 -fi \ No newline at end of file +fi + +mv chdb_bun.so ../chdb_bun.so +mv libchdb.so ../libchdb.so \ No newline at end of file diff --git a/lib/chdb.h b/lib/chdb.h new file mode 100644 index 0000000..48a8380 --- /dev/null +++ b/lib/chdb.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __cplusplus +# include +# include +extern "C" { +#else +# include +# include +#endif + +#define CHDB_EXPORT __attribute__((visibility("default"))) +struct CHDB_EXPORT local_result +{ + char * buf; + size_t len; + void * _vec; // std::vector *, for freeing + double elapsed; + uint64_t rows_read; + uint64_t bytes_read; +}; + +CHDB_EXPORT struct local_result * query_stable(int argc, char ** argv); +CHDB_EXPORT void free_result(struct local_result * result); + +#ifdef __cplusplus +} +#endif diff --git a/lib/chdb_bun.c b/lib/chdb_bun.c new file mode 100644 index 0000000..e64c74d --- /dev/null +++ b/lib/chdb_bun.c @@ -0,0 +1,77 @@ +#include "chdb.h" +#include "chdb_bun.h" +#include +#include +#include + +#define MAX_FORMAT_LENGTH 64 +#define MAX_PATH_LENGTH 4096 +#define MAX_ARG_COUNT 6 + +// Utility function to construct argument string +void construct_arg(char *dest, const char *prefix, const char *value, + size_t dest_size) { + snprintf(dest, dest_size, "%s%s", prefix, value); +} + +// Generalized query function +char *general_query(int argc, char *args[]) { + struct local_result *result = query_stable(argc, args); + + if (result == NULL) { + return NULL; + } else { + return result->buf; + } +} + +// Query function without session +char *Query(const char *query, const char *format) { + char dataFormat[MAX_FORMAT_LENGTH]; + char *dataQuery; + char *args[MAX_ARG_COUNT] = {"clickhouse", "--multiquery", NULL, NULL}; + int argc = 4; + + construct_arg(dataFormat, "--output-format=", format, MAX_FORMAT_LENGTH); + args[2] = dataFormat; + + dataQuery = (char *)malloc(strlen(query) + strlen("--query=") + 1); + if (dataQuery == NULL) { + return NULL; + } + construct_arg(dataQuery, "--query=", query, + strlen(query) + strlen("--query=") + 1); + args[3] = dataQuery; + + char *result = general_query(argc, args); + free(dataQuery); + return result; +} + +// QuerySession function will save the session to the path +// queries with same path will use the same session +char *QuerySession(const char *query, const char *format, const char *path) { + char dataFormat[MAX_FORMAT_LENGTH]; + char dataPath[MAX_PATH_LENGTH]; + char *dataQuery; + char *args[MAX_ARG_COUNT] = {"clickhouse", "--multiquery", NULL, NULL, NULL}; + int argc = 5; + + construct_arg(dataFormat, "--output-format=", format, MAX_FORMAT_LENGTH); + args[2] = dataFormat; + + dataQuery = (char *)malloc(strlen(query) + strlen("--query=") + 1); + if (dataQuery == NULL) { + return NULL; + } + construct_arg(dataQuery, "--query=", query, + strlen(query) + strlen("--query=") + 1); + args[3] = dataQuery; + + construct_arg(dataPath, "--path=", path, MAX_PATH_LENGTH); + args[4] = dataPath; + + char *result = general_query(argc, args); + free(dataQuery); + return result; +} diff --git a/lib/chdb_bun.h b/lib/chdb_bun.h new file mode 100644 index 0000000..24594ad --- /dev/null +++ b/lib/chdb_bun.h @@ -0,0 +1,4 @@ +#pragma once + +char *Query(const char *query, const char *format); +char *QuerySession(const char *query, const char *format, const char *path); diff --git a/lib/libchdb.h b/lib/libchdb.h deleted file mode 100644 index 64e5ff2..0000000 --- a/lib/libchdb.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CHDB_H -#define CHDB_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct local_result -{ - char * buf; - size_t len; - void * _vec; // std::vector *, for freeing -}; - -const char* ares_query(const char* queryStr, const char* format); -struct local_result* query_stable(int arg, char ** argv); -struct local_result* queryToBuffer(const char *queryStr, const char *format); - -#ifdef __cplusplus -} - -void free_result(local_result * result); - -#endif - -#endif diff --git a/lib/libchdb_bun.c b/lib/libchdb_bun.c deleted file mode 100644 index 19f4903..0000000 --- a/lib/libchdb_bun.c +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include "libchdb.h" -#include "libchdb_bun.h" - -char *Execute(char *query, char *format) { - char *argv[] = {(char *)"clickhouse", (char *)"--multiquery", (char *)"--output-format=CSV", (char *)"--query="}; - char dataFormat[100]; - char *localQuery; - int argc = 4; - struct local_result *result; - - snprintf(dataFormat, sizeof(dataFormat), "--format=%s", format); - argv[2] = strdup(dataFormat); - - localQuery = (char *) malloc(strlen(query) + 10); - if (localQuery == NULL) { - return NULL; - } - - sprintf(localQuery, "--query=%s", query); - argv[3] = strdup(localQuery); - free(localQuery); - - result = query_stable(argc, argv); - - free(argv[2]); - free(argv[3]); - - if (result == NULL) { - return NULL; - } else { - return result->buf; - } -} - -char *ExecuteSession(char *query, char *format, char *path) { - char *argv[] = {(char *)"clickhouse", (char *)"--multiquery", (char *)"--output-format=CSV", (char *)"--query=", (char *)"--path=."}; - char dataFormat[100]; - char dataPath[100]; - char *localQuery; - int argc = 5; - struct local_result *result; - - snprintf(dataFormat, sizeof(dataFormat), "--format=%s", format); - argv[2] = strdup(dataFormat); - - snprintf(dataPath, sizeof(dataPath), "--path=%s", path); - argv[4] = strdup(dataPath); - - localQuery = (char *) malloc(strlen(query) + 10); - if (localQuery == NULL) { - return NULL; - } - - sprintf(localQuery, "--query=%s", query); - argv[3] = strdup(localQuery); - free(localQuery); - - result = query_stable(argc, argv); - - free(argv[2]); - free(argv[3]); - free(argv[4]); - - if (result == NULL) { - return NULL; - } else { - return result->buf; - } -} diff --git a/lib/libchdb_bun.h b/lib/libchdb_bun.h deleted file mode 100644 index 1a668c1..0000000 --- a/lib/libchdb_bun.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef LIBCHDB_BUN_H -#define LIBCHDB_BUN_H - -char *Execute(char *query, char *format); -char *ExecuteSession(char *query, char *format, char *path); - -#endif diff --git a/lib/libchdb_bun.so b/lib/libchdb_bun.so deleted file mode 100755 index 61622b0..0000000 Binary files a/lib/libchdb_bun.so and /dev/null differ diff --git a/lib/update_libchdb.sh b/lib/update_libchdb.sh new file mode 100755 index 0000000..fee5ce5 --- /dev/null +++ b/lib/update_libchdb.sh @@ -0,0 +1,43 @@ + +#!/bin/bash + +# Get the newest release version +LATEST_RELEASE=$(curl --silent "https://api.github.com/repos/chdb-io/chdb/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + +# Download the correct version based on the platform +case "$(uname -s)" in + Linux) + if [[ $(uname -m) == "aarch64" ]]; then + PLATFORM="linux-aarch64-libchdb.tar.gz" + else + PLATFORM="linux-x86_64-libchdb.tar.gz" + fi + ;; + Darwin) + if [[ $(uname -m) == "arm64" ]]; then + PLATFORM="macos-arm64-libchdb.tar.gz" + else + PLATFORM="macos-x86_64-libchdb.tar.gz" + fi + ;; + *) + echo "Unsupported platform" + exit 1 + ;; +esac + +DOWNLOAD_URL="https://github.com/chdb-io/chdb/releases/download/$LATEST_RELEASE/$PLATFORM" + +echo "Downloading $PLATFORM from $DOWNLOAD_URL" + +# Download the file +curl -L -o libchdb.tar.gz $DOWNLOAD_URL + +# Untar the file +tar -xzf libchdb.tar.gz + +# Set execute permission for libchdb.so +chmod +x libchdb.so + +# Clean up +rm -f libchdb.tar.gz diff --git a/package.json b/package.json index 350d471..d2da4a2 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,8 @@ { "name": "chdb-bun", - "version": "1.0.4", + "version": "1.1.0", + "author": "Farmer Sun ", "module": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build:lib": "cd lib && ./build.sh", - "build:ts": "bun build index.ts --target=bun --outfile=lib/index.js --sourcemap=inline && tsc --declaration --emitDeclarationOnly --types bun-types --declarationDir lib index.ts", - "build": "bun run build:ts && bun run build:lib" - }, - "type": "module", "devDependencies": { "bun-types": "^1.0.19", "typescript": "^5.3.3" @@ -19,16 +13,28 @@ "files": [ "lib" ], + "license": "Apache2.0", "maintainers": [ { "name": "Farmer Sun", "email": "podpodiumapp@gmail.com" }, + { + "name": "Auxten", + "email": "auxtenwpc@gmail.com" + }, { "name": "Lorenzo Mangani", "email": "lorenzo.mangani@gmail.com" } ], - "author": "Lorenzo Mangani ", - "license": "Apache2.0" + "scripts": { + "build:lib": "cd lib && ./update_libchdb.sh && ./build.sh", + "build:ts": "bun build index.ts --target=bun --outfile=lib/index.js --sourcemap=inline && tsc --declaration --emitDeclarationOnly --types bun-types --declarationDir lib index.ts", + "build": "bun run build:ts && bun run build:lib" + }, + "type": "module", + "types": "lib/index.d.ts", + "dependencies": { + } }