@@ -3,7 +3,12 @@ package chdbpurego
33import (
44 "errors"
55 "fmt"
6+ "os"
7+ "path/filepath"
8+ "strings"
69 "unsafe"
10+
11+ "golang.org/x/sys/unix"
712)
813
914type 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