@@ -3,7 +3,12 @@ package chdbpurego
3
3
import (
4
4
"errors"
5
5
"fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
6
9
"unsafe"
10
+
11
+ "golang.org/x/sys/unix"
7
12
)
8
13
9
14
type result struct {
@@ -141,12 +146,66 @@ func (c *connection) Ready() bool {
141
146
return false
142
147
}
143
148
149
+ // NewConnection is the low level function to create a new connection to the chdb server.
150
+ // using NewConnectionFromConnString is recommended.
151
+ //
144
152
// Session will keep the state of query.
145
153
// If path is None, it will create a temporary directory and use it as the database path
146
154
// and the temporary directory will be removed when the session is closed.
147
155
// 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"}
148
160
//
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.
150
209
// Examples:
151
210
// - ":memory:" (for in-memory database)
152
211
// - "test.db" (for relative path)
@@ -169,10 +228,99 @@ func (c *connection) Ready() bool {
169
228
// Important:
170
229
// - There can be only one session at a time. If you want to create a new session, you need to close the existing one.
171
230
// - 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 {})
176
234
}
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 )
178
326
}
0 commit comments