Skip to content

Commit 4140837

Browse files
committed
Fix connectChdb argv and add NewConnectionFromConnString
1 parent 27ee3a9 commit 4140837

File tree

2 files changed

+155
-7
lines changed

2 files changed

+155
-7
lines changed

chdb-purego/binding.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var (
3939
freeResult func(result *local_result)
4040
queryStableV2 func(argc int, argv []string) *local_result_v2
4141
freeResultV2 func(result *local_result_v2)
42-
connectChdb func(argc int, argv []string) **chdb_conn
42+
connectChdb func(argc int, argv []*byte) **chdb_conn
4343
closeConn func(conn **chdb_conn)
4444
queryConn func(conn *chdb_conn, query string, format string) *local_result_v2
4545
)

chdb-purego/chdb.go

+154-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ package chdbpurego
33
import (
44
"errors"
55
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
69
"unsafe"
10+
11+
"golang.org/x/sys/unix"
712
)
813

914
type result struct {
@@ -141,12 +146,66 @@ func (c *connection) Ready() bool {
141146
return false
142147
}
143148

149+
// NewConnection is the low level function to create a new connection to the chdb server.
150+
// using NewConnectionFromConnString is recommended.
151+
//
144152
// Session will keep the state of query.
145153
// If path is None, it will create a temporary directory and use it as the database path
146154
// and the temporary directory will be removed when the session is closed.
147155
// You can also pass in a path to create a database at that path where will keep your data.
156+
// This is a thin wrapper around the connect_chdb C API.
157+
// the argc and argv should be like:
158+
// - argc = 1, argv = []string{"--path=/tmp/chdb"}
159+
// - argc = 2, argv = []string{"--path=/tmp/chdb", "--readonly=1"}
148160
//
149-
// You can also use a connection string to pass in the path and other parameters.
161+
// Important:
162+
// - There can be only one session at a time. If you want to create a new session, you need to close the existing one.
163+
// - Creating a new session will close the existing one.
164+
// - You need to ensure that the path exists before creating a new session. Or you can use NewConnectionFromConnString.
165+
func NewConnection(argc int, argv []string) (ChdbConn, error) {
166+
var new_argv []string
167+
if (argc > 0 && argv[0] != "clickhouse") || argc == 0 {
168+
new_argv = make([]string, argc+1)
169+
new_argv[0] = "clickhouse"
170+
copy(new_argv[1:], argv)
171+
} else {
172+
new_argv = argv
173+
}
174+
175+
// Convert string slice to C-style char pointers in one step
176+
c_argv := make([]*byte, len(new_argv))
177+
for i, str := range new_argv {
178+
c_argv[i] = (*byte)(unsafe.Pointer(unsafe.StringData(str)))
179+
}
180+
181+
// debug print new_argv
182+
for _, arg := range new_argv {
183+
fmt.Println("arg: ", arg)
184+
}
185+
186+
var conn **chdb_conn
187+
var err error
188+
func() {
189+
defer func() {
190+
if r := recover(); r != nil {
191+
err = fmt.Errorf("C++ exception: %v", r)
192+
}
193+
}()
194+
conn = connectChdb(len(new_argv), c_argv)
195+
}()
196+
197+
if err != nil {
198+
return nil, err
199+
}
200+
201+
if conn == nil {
202+
return nil, fmt.Errorf("could not create a chdb connection")
203+
}
204+
return newChdbConn(conn), nil
205+
}
206+
207+
// NewConnectionFromConnString creates a new connection to the chdb server using a connection string.
208+
// You can use a connection string to pass in the path and other parameters.
150209
// Examples:
151210
// - ":memory:" (for in-memory database)
152211
// - "test.db" (for relative path)
@@ -169,10 +228,99 @@ func (c *connection) Ready() bool {
169228
// Important:
170229
// - There can be only one session at a time. If you want to create a new session, you need to close the existing one.
171230
// - Creating a new session will close the existing one.
172-
func NewConnection(argc int, argv []string) (ChdbConn, error) {
173-
conn := connectChdb(argc, argv)
174-
if conn == nil {
175-
return nil, fmt.Errorf("could not create a chdb connection")
231+
func NewConnectionFromConnString(conn_string string) (ChdbConn, error) {
232+
if conn_string == "" || conn_string == ":memory:" {
233+
return NewConnection(0, []string{})
176234
}
177-
return newChdbConn(conn), nil
235+
236+
// Handle file: prefix
237+
workingStr := conn_string
238+
if strings.HasPrefix(workingStr, "file:") {
239+
workingStr = workingStr[5:]
240+
// Handle triple slash for absolute paths
241+
if strings.HasPrefix(workingStr, "///") {
242+
workingStr = workingStr[2:] // Remove two slashes, keep one
243+
}
244+
}
245+
246+
// Split path and parameters
247+
var path string
248+
var params []string
249+
if queryPos := strings.Index(workingStr, "?"); queryPos != -1 {
250+
path = workingStr[:queryPos]
251+
paramStr := workingStr[queryPos+1:]
252+
253+
// Parse parameters
254+
for _, param := range strings.Split(paramStr, "&") {
255+
if param == "" {
256+
continue
257+
}
258+
if eqPos := strings.Index(param, "="); eqPos != -1 {
259+
key := param[:eqPos]
260+
value := param[eqPos+1:]
261+
if key == "mode" && value == "ro" {
262+
params = append(params, "--readonly=1")
263+
} else if key == "udf_path" && value != "" {
264+
params = append(params, "--")
265+
params = append(params, "--user_scripts_path="+value)
266+
params = append(params, "--user_defined_executable_functions_config="+value+"/*.xml")
267+
} else {
268+
params = append(params, "--"+key+"="+value)
269+
}
270+
} else {
271+
params = append(params, "--"+param)
272+
}
273+
}
274+
} else {
275+
path = workingStr
276+
}
277+
278+
// Convert relative paths to absolute if needed
279+
if path != "" && !strings.HasPrefix(path, "/") && path != ":memory:" {
280+
absPath, err := filepath.Abs(path)
281+
if err != nil {
282+
return nil, fmt.Errorf("failed to resolve path: %s", path)
283+
}
284+
path = absPath
285+
}
286+
287+
// Check if path exists and handle directory creation/permissions
288+
if path != "" && path != ":memory:" {
289+
// Check if path exists
290+
_, err := os.Stat(path)
291+
if os.IsNotExist(err) {
292+
// Create directory if it doesn't exist
293+
if err := os.MkdirAll(path, 0755); err != nil {
294+
return nil, fmt.Errorf("failed to create directory: %s", path)
295+
}
296+
} else if err != nil {
297+
return nil, fmt.Errorf("failed to check directory: %s", path)
298+
}
299+
300+
// Check write permissions if not in readonly mode
301+
isReadOnly := false
302+
for _, param := range params {
303+
if param == "--readonly=1" {
304+
isReadOnly = true
305+
break
306+
}
307+
}
308+
309+
if !isReadOnly {
310+
// Check write permissions by attempting to create a file
311+
if err := unix.Access(path, unix.W_OK); err != nil {
312+
return nil, fmt.Errorf("no write permission for directory: %s", path)
313+
}
314+
}
315+
}
316+
317+
// Build arguments array
318+
argv := make([]string, 0, len(params)+2)
319+
argv = append(argv, "clickhouse")
320+
if path != "" && path != ":memory:" {
321+
argv = append(argv, "--path="+path)
322+
}
323+
argv = append(argv, params...)
324+
325+
return NewConnection(len(argv), argv)
178326
}

0 commit comments

Comments
 (0)