Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/chdb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
run: ./chdb-go "SELECT 12345"

build_mac:
runs-on: macos-12
runs-on: macos-15
steps:
- uses: actions/checkout@v3
- name: Fetch library
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ func main() {
fmt.Println(result)

tmp_path := filepath.Join(os.TempDir(), "chdb_test")
defer os.RemoveAll(tmp_path)
// Stateful Query (persistent)
session, _ := chdb.NewSession(tmp_path)
// session cleanup will also delete the folder
defer session.Cleanup()

_, err = session.Query("CREATE DATABASE IF NOT EXISTS testdb; " +
Expand Down
62 changes: 62 additions & 0 deletions chdb-purego/binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package chdbpurego

import (
"os"
"os/exec"

"github.com/ebitengine/purego"
)

func findLibrary() string {
// Env var
if envPath := os.Getenv("CHDB_LIB_PATH"); envPath != "" {
return envPath
}

// ldconfig with Linux
if path, err := exec.LookPath("libchdb.so"); err == nil {
return path
}

// default path
commonPaths := []string{
"/usr/local/lib/libchdb.so",
"/opt/homebrew/lib/libchdb.dylib",
}

for _, p := range commonPaths {
if _, err := os.Stat(p); err == nil {
return p
}
}

//should be an error ?
return "libchdb.so"
}

var (
queryStable func(argc int, argv []string) *local_result
freeResult func(result *local_result)
queryStableV2 func(argc int, argv []string) *local_result_v2
freeResultV2 func(result *local_result_v2)
connectChdb func(argc int, argv []string) **chdb_conn
closeConn func(conn **chdb_conn)
queryConn func(conn *chdb_conn, query string, format string) *local_result_v2
)

func init() {
path := findLibrary()
libchdb, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
purego.RegisterLibFunc(&queryStable, libchdb, "query_stable")
purego.RegisterLibFunc(&freeResult, libchdb, "free_result")
purego.RegisterLibFunc(&queryStableV2, libchdb, "query_stable_v2")

purego.RegisterLibFunc(&freeResultV2, libchdb, "free_result_v2")
purego.RegisterLibFunc(&connectChdb, libchdb, "connect_chdb")
purego.RegisterLibFunc(&closeConn, libchdb, "close_conn")
purego.RegisterLibFunc(&queryConn, libchdb, "query_conn")

}
178 changes: 178 additions & 0 deletions chdb-purego/chdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package chdbpurego

import (
"errors"
"fmt"
"unsafe"
)

type result struct {
localResv2 *local_result_v2
}

func newChdbResult(cRes *local_result_v2) ChdbResult {
res := &result{
localResv2: cRes,
}
// runtime.SetFinalizer(res, res.Free)
return res

}

// Buf implements ChdbResult.
func (c *result) Buf() []byte {
if c.localResv2 != nil {
if c.localResv2.buf != nil && c.localResv2.len > 0 {
return unsafe.Slice(c.localResv2.buf, c.localResv2.len)
}
}
return nil
}

// BytesRead implements ChdbResult.
func (c *result) BytesRead() uint64 {
if c.localResv2 != nil {
return c.localResv2.bytes_read
}
return 0
}

// Elapsed implements ChdbResult.
func (c *result) Elapsed() float64 {
if c.localResv2 != nil {
return c.localResv2.elapsed
}
return 0
}

// Error implements ChdbResult.
func (c *result) Error() error {
if c.localResv2 != nil {
if c.localResv2.error_message != nil {
return errors.New(ptrToGoString(c.localResv2.error_message))
}
}
return nil
}

// Free implements ChdbResult.
func (c *result) Free() {
if c.localResv2 != nil {
freeResultV2(c.localResv2)
c.localResv2 = nil
}

}

// Len implements ChdbResult.
func (c *result) Len() int {
if c.localResv2 != nil {
return int(c.localResv2.len)
}
return 0
}

// RowsRead implements ChdbResult.
func (c *result) RowsRead() uint64 {
if c.localResv2 != nil {
return c.localResv2.rows_read
}
return 0
}

// String implements ChdbResult.
func (c *result) String() string {
ret := c.Buf()
if ret == nil {
return ""
}
return string(ret)
}

type connection struct {
conn **chdb_conn
}

func newChdbConn(conn **chdb_conn) ChdbConn {
c := &connection{
conn: conn,
}
// runtime.SetFinalizer(c, c.Close)
return c
}

// Close implements ChdbConn.
func (c *connection) Close() {
if c.conn != nil {
closeConn(c.conn)
}
}

// Query implements ChdbConn.
func (c *connection) Query(queryStr string, formatStr string) (result ChdbResult, err error) {

if c.conn == nil {
return nil, fmt.Errorf("invalid connection")
}

rawConn := *c.conn

res := queryConn(rawConn, queryStr, formatStr)
if res == nil {
// According to the C ABI of chDB v1.2.0, the C function query_stable_v2
// returns nil if the query returns no data. This is not an error. We
// will change this behavior in the future.
return newChdbResult(res), nil
}
if res.error_message != nil {
return nil, errors.New(ptrToGoString(res.error_message))
}

return newChdbResult(res), nil
}

func (c *connection) Ready() bool {
if c.conn != nil {
deref := *c.conn
if deref != nil {
return deref.connected
}
}
return false
}

// Session will keep the state of query.
// If path is None, it will create a temporary directory and use it as the database path
// and the temporary directory will be removed when the session is closed.
// You can also pass in a path to create a database at that path where will keep your data.
//
// You can also use a connection string to pass in the path and other parameters.
// Examples:
// - ":memory:" (for in-memory database)
// - "test.db" (for relative path)
// - "file:test.db" (same as above)
// - "/path/to/test.db" (for absolute path)
// - "file:/path/to/test.db" (same as above)
// - "file:test.db?param1=value1&param2=value2" (for relative path with query params)
// - "file::memory:?verbose&log-level=test" (for in-memory database with query params)
// - "///path/to/test.db?param1=value1&param2=value2" (for absolute path)
//
// Connection string args handling:
//
// Connection string can contain query params like "file:test.db?param1=value1&param2=value2"
// "param1=value1" will be passed to ClickHouse engine as start up args.
//
// For more details, see `clickhouse local --help --verbose`
// Some special args handling:
// - "mode=ro" would be "--readonly=1" for clickhouse (read-only mode)
//
// Important:
// - There can be only one session at a time. If you want to create a new session, you need to close the existing one.
// - Creating a new session will close the existing one.
func NewConnection(argc int, argv []string) (ChdbConn, error) {
conn := connectChdb(argc, argv)
if conn == nil {
return nil, fmt.Errorf("could not create a chdb connection")
}
return newChdbConn(conn), nil
}
21 changes: 21 additions & 0 deletions chdb-purego/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package chdbpurego

import (
"unsafe"
)

func ptrToGoString(ptr *byte) string {
if ptr == nil {
return ""
}

var length int
for {
if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(length))) == 0 {
break
}
length++
}

return string(unsafe.Slice(ptr, length))
}
60 changes: 60 additions & 0 deletions chdb-purego/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package chdbpurego

import "unsafe"

// old local result struct. for reference:
// https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L29
type local_result struct {
buf *byte
len uintptr
_vec unsafe.Pointer
elapsed float64
rows_read uint64
bytes_read uint64
}

// new local result struct. for reference: https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L40
type local_result_v2 struct {
buf *byte
len uintptr
_vec unsafe.Pointer
elapsed float64
rows_read uint64
bytes_read uint64
error_message *byte
}

// clickhouse background server connection.for reference: https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L82
type chdb_conn struct {
server unsafe.Pointer
connected bool
queue unsafe.Pointer
}

type ChdbResult interface {
// Raw bytes result buffer, used for reading the result of clickhouse query
Buf() []byte
// String rapresentation of the the buffer
String() string
// Lenght in bytes of the buffer
Len() int
// Number of seconds elapsed for the query execution
Elapsed() float64
// Amount of rows returned by the query
RowsRead() uint64
// Amount of bytes returned by the query
BytesRead() uint64
// If the query had any error during execution, here you can retrieve the cause.
Error() error
// Free the query result and all the allocated memory
Free()
}

type ChdbConn interface {
//Query executes the given queryStr in the underlying clickhouse connection, and output the result in the given formatStr
Query(queryStr string, formatStr string) (result ChdbResult, err error)
//Ready returns a boolean indicating if the connections is successfully established.
Ready() bool
//Close the connection and free the underlying allocated memory
Close()
}
Loading