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