Skip to content
Open

wip #186

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
21 changes: 12 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
1 change: 0 additions & 1 deletion internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"os"

_ "github.com/mattn/go-sqlite3"
"github.com/spf13/cobra"

"github.com/libsql/libsql-shell-go/pkg/shell"
Expand Down
96 changes: 42 additions & 54 deletions internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -71,48 +63,55 @@ 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
}
return &db, nil
}

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)
}
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion internal/db/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions internal/shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion internal/shellcmd/databaseRootCommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"io"

_ "github.com/mattn/go-sqlite3"
"github.com/spf13/cobra"

"github.com/libsql/libsql-shell-go/internal/db"
Expand Down
2 changes: 1 addition & 1 deletion internal/suggester/suggester.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion internal/suggester/tokenRulesFinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion internal/suggester/tokenStringInspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/shell/shellerrors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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://"
}
8 changes: 4 additions & 4 deletions test/db_root_command_shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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, "")

Expand Down Expand Up @@ -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, "")

Expand Down
Loading