@@ -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,76 @@ 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+ // 
152+ // Deprecated: Use NewConnectionFromConnString instead. This function will be removed in a future version. 
153+ // 
144154// Session will keep the state of query. 
145155// If path is None, it will create a temporary directory and use it as the database path 
146156// and the temporary directory will be removed when the session is closed. 
147157// 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"} 
148162// 
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. 
150219// Examples: 
151220//   - ":memory:" (for in-memory database) 
152221//   - "test.db" (for relative path) 
@@ -169,10 +238,99 @@ func (c *connection) Ready() bool {
169238// Important: 
170239//   - There can be only one session at a time. If you want to create a new session, you need to close the existing one. 
171240//   - 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 {})
176244	}
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 )
178336}
0 commit comments