diff --git a/go.mod b/go.mod index eb17f48..34832cd 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/libsql/libsql-shell-go -go 1.22 - -toolchain go1.22.1 +go 1.24.0 require ( github.com/antlr4-go/antlr/v4 v4.13.1 @@ -14,15 +12,14 @@ require ( github.com/knadh/koanf/providers/env v0.1.0 github.com/knadh/koanf/providers/file v0.1.0 github.com/knadh/koanf/v2 v2.0.0 - github.com/mattn/go-sqlite3 v1.14.16 + github.com/libsql/sqlite-antlr4-parser v0.0.0-20240721121621-c0bdc870f11c github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.9.0 - github.com/tursodatabase/libsql-client-go v0.0.0-20251205113610-b69dd6e475fc + github.com/tursodatabase/go-libsql v0.0.0-20251025125656-00da49cd4a6e ) require ( github.com/chzyer/readline v1.5.1 - github.com/coder/websocket v1.8.12 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -45,7 +42,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 // indirect + golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f195ff4..cec41e9 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -51,6 +49,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240721121621-c0bdc870f11c h1:WsJ6G+hkDXIMfQE8FIxnnziT26WmsRgZhdWQ0IQGlcc= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240721121621-c0bdc870f11c/go.mod h1:gIcFddvsvPcRCO6QDmWH9/zcFd5U26QWWRMgZh4ddyo= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -59,8 +59,6 @@ github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -70,6 +68,8 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -91,13 +91,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= -github.com/tursodatabase/libsql-client-go v0.0.0-20251205113610-b69dd6e475fc h1:uhpFwk9G+wp9JpPnaABzwyIUz1P4EYIkEKKivyJVO14= -github.com/tursodatabase/libsql-client-go v0.0.0-20251205113610-b69dd6e475fc/go.mod h1:08inkKyguB6CGGssc/JzhmQWwBgFQBgjlYFjxjRh7nU= +github.com/tursodatabase/go-libsql v0.0.0-20251025125656-00da49cd4a6e h1:fNM9EcbO8TgeJzZbhOzh2nrRKwIPoYWGB++Jvl8oO94= +github.com/tursodatabase/go-libsql v0.0.0-20251025125656-00da49cd4a6e/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 h1:Z+vTUQyBb738QmIhbJx3z4htsxDeI+rd0EHvNm8jHkg= -golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -112,3 +113,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 6b8a0fc..de2f0df 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -4,7 +4,6 @@ import ( "fmt" "os" - _ "github.com/mattn/go-sqlite3" "github.com/spf13/cobra" "github.com/libsql/libsql-shell-go/pkg/shell" diff --git a/internal/db/db.go b/internal/db/db.go index 4a9c3b2..d54eded 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -5,37 +5,29 @@ import ( "database/sql" "fmt" "io" + "net/url" "reflect" "strings" - _ "github.com/mattn/go-sqlite3" - "github.com/tursodatabase/libsql-client-go/libsql" - "github.com/tursodatabase/libsql-client-go/sqliteparserutils" + "github.com/libsql/sqlite-antlr4-parser/sqliteparserutils" + _ "github.com/tursodatabase/go-libsql" "github.com/libsql/libsql-shell-go/pkg/shell/enums" "github.com/libsql/libsql-shell-go/pkg/shell/shellerrors" ) -type driver int - -const ( - libsqlDriver driver = iota - sqlite3Driver -) - type Db struct { Uri string AuthToken string - sqlDb *sql.DB - driver driver - urlScheme string + sqlDb *sql.DB + isRemote bool cancelRunningQuery func() } func (db *Db) IsRemote() bool { - return db.driver == libsqlDriver + return db.isRemote } type StatementsResult struct { @@ -71,40 +63,45 @@ func newRowResultWithError(err error) *rowResult { return &rowResult{Err: treatedErr} } -func NewDb(dbUri, authToken, proxy string, schemaDb bool, remoteEncryptionKey string) (*Db, error) { - var err error - +func NewDb(dbUri, authToken, _proxy string, _schemaDb bool, remoteEncryptionKey string) (*Db, error) { var db = Db{Uri: dbUri, AuthToken: authToken} + // Determine the type of database connection + connStr := dbUri if IsUrl(dbUri) { - var validSqldUrl bool - if validSqldUrl, db.urlScheme = IsValidSqldUrl(dbUri); validSqldUrl { - db.driver = libsqlDriver - var options []libsql.Option - if authToken != "" { - options = append(options, libsql.WithAuthToken(authToken)) - } - if proxy != "" { - options = append(options, libsql.WithProxy(proxy)) - } - if schemaDb { - options = append(options, libsql.WithSchemaDb(schemaDb)) + isRemote, scheme := IsValidSqldUrl(dbUri) + if isRemote { + // Remote URL (libsql://, http://, https://) + db.isRemote = true + if authToken != "" || remoteEncryptionKey != "" { + u, err := url.Parse(dbUri) + if err != nil { + return nil, err + } + q := u.Query() + if authToken != "" { + q.Set("authToken", authToken) + } + if remoteEncryptionKey != "" { + q.Set("remoteEncryptionKey", remoteEncryptionKey) + } + u.RawQuery = q.Encode() + connStr = u.String() } - if remoteEncryptionKey != "" { - options = append(options, libsql.WithRemoteEncryptionKey(remoteEncryptionKey)) - } - connector, err := libsql.NewConnector(dbUri, options...) - if err != nil { - return nil, err - } - db.sqlDb = sql.OpenDB(connector) + } else if scheme == "file" { + // Local file URL - use as-is + db.isRemote = false } else { return nil, &shellerrors.ProtocolError{} } } else { - db.driver = sqlite3Driver - db.sqlDb, err = sql.Open("sqlite3", dbUri) + // Plain path - treat as local file, convert to file: URL for go-libsql + db.isRemote = false + connStr = "file:" + dbUri } + + var err error + db.sqlDb, err = sql.Open("libsql", connStr) if err != nil { return nil, err } @@ -112,7 +109,9 @@ func NewDb(dbUri, authToken, proxy string, schemaDb bool, remoteEncryptionKey st } func (db *Db) TestConnection() error { - _, err := db.sqlDb.Exec("SELECT 1;") + row := db.sqlDb.QueryRow("SELECT 1") + var result int + err := row.Scan(&result) if err != nil { return fmt.Errorf("failed to connect to database. err: %v", err) } @@ -179,20 +178,9 @@ func (db *Db) executeQuery(query string, statementResultCh chan StatementResult) } func (db *Db) prepareStatementsIntoQueries(statementsString string) []string { - // sqlite3 driver just run the first query that we send. So we must split the statements and send them one by one - // e.g If we execute query "select 1; select 2;" with it, just the first one ("select 1;") would be executed - // - // libsql driver doesn't accept multiple statements if using websocket connection - mustSplitStatementsIntoMultipleQueries := - db.driver == sqlite3Driver || - db.driver == libsqlDriver && (db.urlScheme == "libsql" || db.urlScheme == "wss" || db.urlScheme == "ws") - - if mustSplitStatementsIntoMultipleQueries { - stmts, _ := sqliteparserutils.SplitStatement(statementsString) - return stmts - } - - return []string{statementsString} + // TODO: check if this required + stmts, _ := sqliteparserutils.SplitStatement(statementsString) + return stmts } func getColumnNames(rows *sql.Rows) ([]string, error) { diff --git a/internal/db/utils.go b/internal/db/utils.go index b9d04a5..f675360 100644 --- a/internal/db/utils.go +++ b/internal/db/utils.go @@ -19,7 +19,8 @@ func IsValidSqldUrl(uri string) (bool, string) { if err != nil { return false, "" } - return url.Scheme == "libsql" || url.Scheme == "wss" || url.Scheme == "ws" || url.Scheme == "http" || url.Scheme == "https", url.Scheme + // go-libsql supports libsql://, http://, https:// schemes (no websocket) + return url.Scheme == "libsql" || url.Scheme == "http" || url.Scheme == "https", url.Scheme } func EscapeSingleQuotes(value string) string { diff --git a/internal/shell/shell.go b/internal/shell/shell.go index c47b609..6fca307 100644 --- a/internal/shell/shell.go +++ b/internal/shell/shell.go @@ -12,9 +12,9 @@ import ( "github.com/libsql/libsql-shell-go/internal/shellcmd" "github.com/libsql/libsql-shell-go/internal/suggester" "github.com/libsql/libsql-shell-go/pkg/shell/enums" + "github.com/libsql/sqlite-antlr4-parser/sqliteparser" + "github.com/libsql/sqlite-antlr4-parser/sqliteparserutils" "github.com/spf13/cobra" - "github.com/tursodatabase/libsql-client-go/sqliteparser" - "github.com/tursodatabase/libsql-client-go/sqliteparserutils" ) const QUIT_COMMAND = ".quit" diff --git a/internal/shellcmd/databaseRootCommand.go b/internal/shellcmd/databaseRootCommand.go index 326238d..f1bd04d 100644 --- a/internal/shellcmd/databaseRootCommand.go +++ b/internal/shellcmd/databaseRootCommand.go @@ -4,7 +4,6 @@ import ( "context" "io" - _ "github.com/mattn/go-sqlite3" "github.com/spf13/cobra" "github.com/libsql/libsql-shell-go/internal/db" diff --git a/internal/suggester/suggester.go b/internal/suggester/suggester.go index 464a2a1..3be82d3 100644 --- a/internal/suggester/suggester.go +++ b/internal/suggester/suggester.go @@ -5,7 +5,7 @@ import ( "unicode" "github.com/antlr4-go/antlr/v4" - "github.com/tursodatabase/libsql-client-go/sqliteparser" + "github.com/libsql/sqlite-antlr4-parser/sqliteparser" ) func SuggestCompletion(currentInput string) []string { diff --git a/internal/suggester/tokenRulesFinder.go b/internal/suggester/tokenRulesFinder.go index 2f3cb79..d8dbb97 100644 --- a/internal/suggester/tokenRulesFinder.go +++ b/internal/suggester/tokenRulesFinder.go @@ -2,7 +2,7 @@ package suggester import ( "github.com/antlr4-go/antlr/v4" - "github.com/tursodatabase/libsql-client-go/sqliteparser" + "github.com/libsql/sqlite-antlr4-parser/sqliteparser" ) type TokenRulesFinder struct { diff --git a/internal/suggester/tokenStringInspector.go b/internal/suggester/tokenStringInspector.go index 7f56927..e42d74d 100644 --- a/internal/suggester/tokenStringInspector.go +++ b/internal/suggester/tokenStringInspector.go @@ -2,7 +2,7 @@ package suggester import ( "github.com/antlr4-go/antlr/v4" - "github.com/tursodatabase/libsql-client-go/sqliteparser" + "github.com/libsql/sqlite-antlr4-parser/sqliteparser" ) type tokenStreamInspector struct { diff --git a/pkg/shell/shellerrors/errors.go b/pkg/shell/shellerrors/errors.go index 6cf7182..4b89fa2 100644 --- a/pkg/shell/shellerrors/errors.go +++ b/pkg/shell/shellerrors/errors.go @@ -43,5 +43,5 @@ func (e *ProtocolError) Error() string { return e.userError() } func (e *ProtocolError) userError() string { - return "invalid sqld protocol. valid protocols are libsql://, wss://, ws://, https:// and http://" + return "invalid database URL. valid protocols are libsql://, https://, http://, or file://" } diff --git a/test/db_root_command_shell_test.go b/test/db_root_command_shell_test.go index aba4a48..6746243 100644 --- a/test/db_root_command_shell_test.go +++ b/test/db_root_command_shell_test.go @@ -102,8 +102,8 @@ func (s *DBRootCommandShellSuite) Test_WhenCallDotHelpCommand_ExpectAListWithAll func (s *DBRootCommandShellSuite) Test_GivenAEmptyDb_WhenCallDotReadCommand_ExpectToSeeATableWithOneEntry() { content := `CREATE TABLE IF NOT EXISTS testread (name TEXT); /* Comment in the middle of the file.*/ - INSERT INTO testread VALUES("test"); - + INSERT INTO testread VALUES('test'); + SELECT * FROM testread;` file, filePath := s.tc.CreateTempFile(content) @@ -251,7 +251,7 @@ func (s *DBRootCommandShellSuite) Test_GivenATableWithRecords_WhenCreateIndexAnd func (s *DBRootCommandShellSuite) Test_GivenATableWithRecordsWithSingleQuote_WhenCalllDotDumpCommand_ExpectSingleQuoteScape() { s.tc.CreateEmptySimpleTable("t") - _, errS, err := s.tc.Execute("INSERT INTO t VALUES(0, \"x'x\", 0)") + _, errS, err := s.tc.Execute("INSERT INTO t VALUES(0, 'x''x', 0)") s.tc.Assert(err, qt.IsNil) s.tc.Assert(errS, qt.Equals, "") @@ -302,7 +302,7 @@ func (s *DBRootCommandShellSuite) Test_GivenATableNameWithSpecialCharacters_When func (s *DBRootCommandShellSuite) Test_GivenATableWithRecordsWithSingleQuote_WhenCalllSelectAllFromTable_ExpectSingleQuoteScape() { s.tc.CreateEmptySimpleTable("t") - _, errS, err := s.tc.Execute("INSERT INTO t VALUES (0, \"x'x\", 0)") + _, errS, err := s.tc.Execute("INSERT INTO t VALUES (0, 'x''x', 0)") s.tc.Assert(err, qt.IsNil) s.tc.Assert(errS, qt.Equals, "")