-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchdb.go
266 lines (219 loc) · 6.2 KB
/
chdb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package chdb
import (
"errors"
"os/exec"
"os"
"runtime"
"unsafe"
"github.com/ebitengine/purego"
)
type local_result struct {
buf *byte
len uintptr
_vec unsafe.Pointer
elapsed float64
rows_read uint64
bytes_read uint64
}
type local_result_v2 struct {
buf *byte
len uintptr
_vec unsafe.Pointer
elapsed float64
rows_read uint64
bytes_read uint64
error_message *byte
}
type chdb_conn struct {
server unsafe.Pointer
connected bool
queue unsafe.Pointer
}
// TODO remove old API ?
var (
queryStable func(argc int, argv **byte) *local_result
freeResult func(result *local_result)
queryStableV2 func(argc int, argv **byte) *local_result_v2
freeResultV2 func(result *local_result_v2)
connectChdb func(argc int, argv **byte) **chdb_conn
closeConn func(conn **chdb_conn)
queryConn func(conn *chdb_conn, query *byte, format *byte) *local_result_v2
)
// Result represent the results from a query.
// Data is byte formatted
// Don't forget to free memory with freeFunc.
type Result struct {
Data []byte
Elapsed float64
RowsRead uint64
BytesRead uint64
Error string
freeFunc func()
}
// Conn is the connection to the database.
type Conn struct {
cConn **chdb_conn
}
func init() {
// TODO: move to internal
libchdb_path := findLibrary()
libchdb, err := purego.Dlopen(libchdb_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")
}
// NewResultFromV2 is the wrapper of Result from the low-level local_result_v2.
func NewResultFromV2(cRes *local_result_v2) *Result {
res := &Result{
Elapsed: cRes.elapsed,
RowsRead: cRes.rows_read,
BytesRead: cRes.bytes_read,
freeFunc: func() { freeResultV2(cRes) },
}
if cRes.buf != nil && cRes.len > 0 {
res.Data = unsafe.Slice(cRes.buf, cRes.len)
}
if cRes.error_message != nil {
res.Error = ptrToGoString(cRes.error_message)
}
return res
}
// Free release memory from Result
func (r *Result) Free() {
if r.freeFunc != nil {
r.freeFunc()
}
}
// Connect manage the connection.
func Connect(connStr string) (*Conn, error) {
// string can not be empty
if connStr == "" {
return nil, errors.New("connection string cannot be empty")
}
// transform uri to args for connect
args := []string{connStr}
argc, argv := convertArgs(args)
cConn := connectChdb(argc, argv)
if cConn == nil {
return nil, errors.New("connection failed for: " + connStr)
}
// check is the connection works
conn := &Conn{cConn: cConn}
if !(*cConn).connected {
return nil, errors.New("connection not properly initialized")
}
return conn, nil
}
// Query execute the SQL query with a format as parameters.
// format can be found at the clickhouse documentation :
// https://clickhouse.com/docs/en/interfaces/formats
// it return Result.
func (c *Conn) Query(query, format string) *Result {
//TODO verify format if avaiable in clickhouse
var pinner runtime.Pinner
defer pinner.Unpin()
qPtr := stringToPtr(query, &pinner)
fPtr := stringToPtr(format, &pinner)
conn := *c.cConn
cRes := queryConn(conn, qPtr, fPtr)
return NewResultFromV2(cRes)
}
// Close close the connection
func (c *Conn) Close() {
if c.cConn != nil {
closeConn(c.cConn)
c.cConn = nil
}
}
// Helpers
// TODO: move to internal
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))
}
func stringToPtr(s string, pinner *runtime.Pinner) *byte {
// Pinne for convert string to bytes
// maybe there is simpler solution but it was late when I write this code.
data := make([]byte, len(s)+1)
copy(data, s)
data[len(s)] = 0 // Null-terminator
ptr := &data[0]
pinner.Pin(ptr)
return ptr
}
func convertArgs(args []string) (int, **byte) {
// maybe there is simpler solution but it was late when I write this code.
if len(args) == 0 {
return 0, nil
}
var pinner runtime.Pinner
defer pinner.Unpin()
// Création d'un tableau C de pointeurs
cArgs := make([]*byte, len(args))
for i, arg := range args {
argData := make([]byte, len(arg)+1)
copy(argData, arg)
argData[len(arg)] = 0
ptr := &argData[0]
pinner.Pin(ptr)
cArgs[i] = ptr
}
if len(cArgs) > 0 {
pinner.Pin(&cArgs[0])
}
return len(args), (**byte)(unsafe.Pointer(&cArgs[0]))
}
// find the library libchdb.so
// TODO find a better solution
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"
}
/**
* libchdb have the name on linux and macos.
* This code is not needed for now
func getSystemLibrary() string {
switch runtime.GOOS {
case "darwin":
return ""
case "linux":
return ""
default:
panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
}
}
**/