Skip to content

Commit 9c2376b

Browse files
committed
add purego bindings
1 parent b2ba138 commit 9c2376b

File tree

14 files changed

+500
-29
lines changed

14 files changed

+500
-29
lines changed

chdb-purego/binding.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package chdbpurego
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
7+
"github.com/ebitengine/purego"
8+
)
9+
10+
func findLibrary() string {
11+
// Env var
12+
if envPath := os.Getenv("CHDB_LIB_PATH"); envPath != "" {
13+
return envPath
14+
}
15+
16+
// ldconfig with Linux
17+
if path, err := exec.LookPath("libchdb.so"); err == nil {
18+
return path
19+
}
20+
21+
// default path
22+
commonPaths := []string{
23+
"/usr/local/lib/libchdb.so",
24+
"/opt/homebrew/lib/libchdb.dylib",
25+
}
26+
27+
for _, p := range commonPaths {
28+
if _, err := os.Stat(p); err == nil {
29+
return p
30+
}
31+
}
32+
33+
//should be an error ?
34+
return "libchdb.so"
35+
}
36+
37+
var (
38+
queryStable func(argc int, argv **byte) *local_result
39+
freeResult func(result *local_result)
40+
queryStableV2 func(argc int, argv **byte) *local_result_v2
41+
freeResultV2 func(result *local_result_v2)
42+
connectChdb func(argc int, argv **byte) **chdb_conn
43+
closeConn func(conn **chdb_conn)
44+
queryConn func(conn *chdb_conn, query *byte, format *byte) *local_result_v2
45+
queryConnV2 func(conn *chdb_conn, query string, format string) *local_result_v2
46+
)
47+
48+
func init() {
49+
path := findLibrary()
50+
libchdb, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
51+
if err != nil {
52+
panic(err)
53+
}
54+
purego.RegisterLibFunc(&queryStable, libchdb, "query_stable")
55+
purego.RegisterLibFunc(&freeResult, libchdb, "free_result")
56+
purego.RegisterLibFunc(&queryStableV2, libchdb, "query_stable_v2")
57+
purego.RegisterLibFunc(&freeResultV2, libchdb, "free_result_v2")
58+
purego.RegisterLibFunc(&connectChdb, libchdb, "connect_chdb")
59+
purego.RegisterLibFunc(&closeConn, libchdb, "close_conn")
60+
purego.RegisterLibFunc(&queryConn, libchdb, "query_conn")
61+
purego.RegisterLibFunc(&queryConnV2, libchdb, "query_conn")
62+
}

chdb-purego/chdb.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package chdbpurego
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"runtime"
7+
"unsafe"
8+
)
9+
10+
type result struct {
11+
localResv2 *local_result_v2
12+
}
13+
14+
func newChdbResult(cRes *local_result_v2) ChdbResult {
15+
res := &result{
16+
localResv2: cRes,
17+
}
18+
return res
19+
20+
}
21+
22+
// Buf implements ChdbResult.
23+
func (c *result) Buf() []byte {
24+
if c.localResv2 != nil {
25+
if c.localResv2.buf != nil && c.localResv2.len > 0 {
26+
return unsafe.Slice(c.localResv2.buf, c.localResv2.len)
27+
}
28+
}
29+
return nil
30+
}
31+
32+
// BytesRead implements ChdbResult.
33+
func (c *result) BytesRead() uint64 {
34+
if c.localResv2 != nil {
35+
return c.localResv2.bytes_read
36+
}
37+
return 0
38+
}
39+
40+
// Elapsed implements ChdbResult.
41+
func (c *result) Elapsed() float64 {
42+
if c.localResv2 != nil {
43+
return c.localResv2.elapsed
44+
}
45+
return 0
46+
}
47+
48+
// Error implements ChdbResult.
49+
func (c *result) Error() error {
50+
if c.localResv2 != nil {
51+
if c.localResv2.error_message != nil {
52+
return errors.New(ptrToGoString(c.localResv2.error_message))
53+
}
54+
}
55+
return nil
56+
}
57+
58+
// Free implements ChdbResult.
59+
func (c *result) Free() error {
60+
if c.localResv2 != nil {
61+
freeResultV2(c.localResv2)
62+
c.localResv2 = nil
63+
}
64+
return nil
65+
}
66+
67+
// Len implements ChdbResult.
68+
func (c *result) Len() int {
69+
if c.localResv2 != nil {
70+
return int(c.localResv2.len)
71+
}
72+
return 0
73+
}
74+
75+
// RowsRead implements ChdbResult.
76+
func (c *result) RowsRead() uint64 {
77+
if c.localResv2 != nil {
78+
return c.localResv2.rows_read
79+
}
80+
return 0
81+
}
82+
83+
// String implements ChdbResult.
84+
func (c *result) String() string {
85+
ret := c.Buf()
86+
if ret == nil {
87+
return ""
88+
}
89+
return string(ret)
90+
}
91+
92+
type connection struct {
93+
conn **chdb_conn
94+
pinner runtime.Pinner
95+
}
96+
97+
func NewChdbConn(conn **chdb_conn) ChdbConn {
98+
return &connection{
99+
conn: conn,
100+
pinner: runtime.Pinner{},
101+
}
102+
}
103+
104+
// Close implements ChdbConn.
105+
func (c *connection) Close() {
106+
if c.conn != nil {
107+
closeConn(c.conn)
108+
}
109+
}
110+
111+
// Query implements ChdbConn.
112+
func (c *connection) Query(queryStr string, formatStr string) (result ChdbResult, err error) {
113+
114+
if c.conn == nil {
115+
return nil, fmt.Errorf("invalid connection")
116+
}
117+
defer c.pinner.Unpin()
118+
// qPtr := stringToPtr(queryStr, &c.pinner)
119+
// fPtr := stringToPtr(formatStr, &c.pinner)
120+
deref := *c.conn
121+
// fmt.Printf("queryPtr: %p, formatPtr: %p, conn: %p\n", qPtr, fPtr, deref)
122+
// fmt.Printf("query string: %s\n", queryStr)
123+
// fmt.Printf("format string: %s\n", formatStr)
124+
res := queryConnV2(deref, queryStr, formatStr)
125+
if res == nil {
126+
// According to the C ABI of chDB v1.2.0, the C function query_stable_v2
127+
// returns nil if the query returns no data. This is not an error. We
128+
// will change this behavior in the future.
129+
return newChdbResult(res), nil
130+
}
131+
if res.error_message != nil {
132+
return nil, errors.New(ptrToGoString(res.error_message))
133+
}
134+
135+
return newChdbResult(res), nil
136+
}
137+
138+
// Ready implements ChdbConn.
139+
func (c *connection) Ready() bool {
140+
if c.conn != nil {
141+
deref := *c.conn
142+
if deref != nil {
143+
return deref.connected
144+
}
145+
}
146+
return false
147+
}
148+
149+
func RawQuery(argc int, argv []string) (result ChdbResult, err error) {
150+
pinner := runtime.Pinner{}
151+
defer pinner.Unpin()
152+
153+
cArgv := make([]*byte, len(argv))
154+
for idx, arg := range argv {
155+
cArgv[idx] = (*byte)(unsafe.Pointer(&([]byte(arg + "\x00")[0])))
156+
157+
}
158+
cArgvPtr := (**byte)(unsafe.Pointer(&cArgv[0]))
159+
pinner.Pin(cArgvPtr)
160+
161+
res := queryStableV2(argc, cArgvPtr)
162+
if res == nil {
163+
// According to the C ABI of chDB v1.2.0, the C function query_stable_v2
164+
// returns nil if the query returns no data. This is not an error. We
165+
// will change this behavior in the future.
166+
return newChdbResult(res), nil
167+
}
168+
if res.error_message != nil {
169+
return nil, errors.New(ptrToGoString(res.error_message))
170+
}
171+
172+
return newChdbResult(res), nil
173+
}
174+
175+
func NewConnection(argc int, argv []string) (ChdbConn, error) {
176+
pinner := runtime.Pinner{}
177+
defer pinner.Unpin()
178+
179+
cArgv := make([]*byte, len(argv))
180+
for idx, arg := range argv {
181+
cArgv[idx] = (*byte)(unsafe.Pointer(&([]byte(arg + "\x00")[0])))
182+
183+
}
184+
cArgvPtr := (**byte)(unsafe.Pointer(&cArgv[0]))
185+
pinner.Pin(cArgvPtr)
186+
conn := connectChdb(argc, cArgvPtr)
187+
if conn == nil {
188+
return nil, fmt.Errorf("could not create a chdb connection")
189+
}
190+
return NewChdbConn(conn), nil
191+
}

chdb-purego/helpers.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package chdbpurego
2+
3+
import (
4+
"runtime"
5+
"unsafe"
6+
)
7+
8+
func ptrToGoString(ptr *byte) string {
9+
if ptr == nil {
10+
return ""
11+
}
12+
13+
var length int
14+
for {
15+
if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(length))) == 0 {
16+
break
17+
}
18+
length++
19+
}
20+
21+
return string(unsafe.Slice(ptr, length))
22+
}
23+
24+
func stringToPtr(s string, pinner *runtime.Pinner) *byte {
25+
// Pinne for convert string to bytes
26+
// maybe there is simpler solution but it was late when I write this code.
27+
data := make([]byte, len(s)+1)
28+
copy(data, s)
29+
data[len(s)] = 0 // Null-terminator
30+
31+
ptr := &data[0]
32+
pinner.Pin(ptr)
33+
34+
return (*byte)(unsafe.Pointer(ptr))
35+
}
36+
37+
func strToBytePtr(s string) *byte {
38+
b := append([]byte(s), 0) // Convert to []byte and append null terminator
39+
return &b[0] // Get pointer to first byte
40+
}

chdb-purego/types.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package chdbpurego
2+
3+
import "unsafe"
4+
5+
// old local result struct. for reference:
6+
// https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L29
7+
type local_result struct {
8+
buf *byte
9+
len uintptr
10+
_vec unsafe.Pointer
11+
elapsed float64
12+
rows_read uint64
13+
bytes_read uint64
14+
}
15+
16+
// new local result struct. for reference: https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L40
17+
type local_result_v2 struct {
18+
buf *byte
19+
len uintptr
20+
_vec unsafe.Pointer
21+
elapsed float64
22+
rows_read uint64
23+
bytes_read uint64
24+
error_message *byte
25+
}
26+
27+
// clickhouse background server connection.for reference: https://github.com/chdb-io/chdb/blob/main/programs/local/chdb.h#L82
28+
type chdb_conn struct {
29+
server unsafe.Pointer
30+
connected bool
31+
queue unsafe.Pointer
32+
}
33+
34+
type ChdbResult interface {
35+
Buf() []byte
36+
String() string
37+
Len() int
38+
Elapsed() float64
39+
RowsRead() uint64
40+
BytesRead() uint64
41+
Error() error
42+
Free() error
43+
}
44+
45+
type ChdbConn interface {
46+
Query(queryStr string, formatStr string) (result ChdbResult, err error)
47+
Ready() bool
48+
Close()
49+
}

0 commit comments

Comments
 (0)