From 1f3f2cfb045d28710b92efbdd0bdec8477ad25b1 Mon Sep 17 00:00:00 2001 From: Satya Kommula Date: Sun, 15 Oct 2023 08:54:52 +0530 Subject: [PATCH] Refactoring into modules and multiline command visibility in prompt (#8) * refactored into small packages * support for multiline visible in prompt --- calcitesql/calcitesql.go | 18 ++ calcitesql/execution.go | 95 +++++++++ cli.go | 205 +++++--------------- go.mod | 11 +- go.sum | 23 ++- keywords/keywords.go => prompt/completer.go | 3 +- prompt/executor.go | 97 +++++++++ prompt/prompt.go | 18 ++ 8 files changed, 301 insertions(+), 169 deletions(-) create mode 100644 calcitesql/calcitesql.go create mode 100644 calcitesql/execution.go rename keywords/keywords.go => prompt/completer.go (99%) create mode 100644 prompt/executor.go create mode 100644 prompt/prompt.go diff --git a/calcitesql/calcitesql.go b/calcitesql/calcitesql.go new file mode 100644 index 0000000..92df7d5 --- /dev/null +++ b/calcitesql/calcitesql.go @@ -0,0 +1,18 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package calcitesql diff --git a/calcitesql/execution.go b/calcitesql/execution.go new file mode 100644 index 0000000..cccb38a --- /dev/null +++ b/calcitesql/execution.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package calcitesql + +import ( + "database/sql" + "fmt" + "log" + "os" + "strings" + "time" + + _ "github.com/apache/calcite-avatica-go/v5" + "github.com/olekukonko/tablewriter" +) + +func ExecuteQuery(db *sql.DB, query string) { + cmd := strings.TrimRight(query, ";") + start := time.Now() + // Execute the query + rows, err := db.Query(cmd) + duration := time.Since(start) + if err != nil { + log.Println("Error executing query:", err) + return + } + defer rows.Close() + + // Get column names + columns, err := rows.Columns() + if err != nil { + log.Println("Error retrieving column names:", err) + return + } + + // Create a new table writer for each query + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoFormatHeaders(true) + table.SetAutoWrapText(false) + table.SetReflowDuringAutoWrap(true) + + // Create a slice to store the query results + values := make([]interface{}, len(columns)) + scanArgs := make([]interface{}, len(columns)) + for i := range values { + scanArgs[i] = &values[i] + } + + // Fetch and print rows + count := 0 + for rows.Next() { + err = rows.Scan(scanArgs...) + if err != nil { + log.Println("Error retrieving row data:", err) + continue + } + + // Prepare row data + rowData := make([]string, len(columns)) + for i, v := range values { + if v != nil { + rowData[i] = fmt.Sprintf("%v", v) + } else { + rowData[i] = "NULL" + } + } + + // Add row to the table + table.Append(rowData) + count++ + } + + // Set the table headers + table.SetHeader(columns) + + // Render the table + table.Render() + + fmt.Printf("Rows: %d\nExecution Time: %s\n\n", count, duration) +} \ No newline at end of file diff --git a/cli.go b/cli.go index 471147e..67ec940 100644 --- a/cli.go +++ b/cli.go @@ -21,22 +21,21 @@ import ( "database/sql" "fmt" "log" - "os" "strings" - "time" _ "github.com/apache/calcite-avatica-go/v5" - "github.com/c-bata/go-prompt" - "github.com/olekukonko/tablewriter" - keywords "github.com/satyakommula96/calcite-cli/keywords" + prompt "github.com/satyakommula96/calcite-cli/prompt" "github.com/spf13/cobra" ) var ( - connectionURL = "http://localhost:8080" - serialization = "protobuf" - enablePartitionPruning = true - distributedExecution = false + connectionURL = "http://localhost:8080" + serialization = "protobuf" + schema string + connectionParams string + user string + passwd string + maxRowsTotal string ) func main() { @@ -48,9 +47,13 @@ func main() { // Define flags for connection URL and additional parameters rootCmd.Flags().StringVar(&connectionURL, "url", connectionURL, "Connection URL") - rootCmd.Flags().StringVar(&serialization, "serialization", serialization, "Serialization parameter") - rootCmd.Flags().BoolVar(&enablePartitionPruning, "enablePartitionPruning", enablePartitionPruning, "Enable Partition Pruning") - rootCmd.Flags().BoolVar(&distributedExecution, "distributedExecution", distributedExecution, "Distributed Execution") + rootCmd.Flags().StringVar(&serialization, "serialization", "", "Serialization parameter") + rootCmd.Flags().StringVar(&connectionParams, "params", "", "Extra parameters for avatica connection (ex: property=value&property=value)") + rootCmd.Flags().StringVarP(&schema, "schema", "s", "", "The schema path sets the default schema to use for this connection.") + rootCmd.Flags().StringVarP(&user, "username", "u", "", "Username (required if password is set)") + rootCmd.Flags().StringVarP(&passwd, "password", "p", "", "Password (required if username is set)") + rootCmd.MarkFlagsRequiredTogether("username", "password") + rootCmd.Flags().StringVarP(&maxRowsTotal, "maxRowsTotal", "m", "", "Serialization parameter") err := rootCmd.Execute() if err != nil { @@ -60,169 +63,65 @@ func main() { func runSQLPrompt(cmd *cobra.Command, args []string) { // Establish a connection to the calcite server - db, err := sql.Open("avatica", buildConnectionURL()) - if err != nil { - log.Fatal(err) - } + db := establishConnection() defer db.Close() - fmt.Println("Welcome! Use SQL to query Apache Calcite.\nUse Ctrl+D, type \"exit\" or \"quit\" to exit.") - fmt.Println() - - p := prompt.New( - executeQueryWrapper(db), - keywords.CustomCompleter, - prompt.OptionLivePrefix(LivePrefix), - prompt.OptionPrefixTextColor(prompt.Yellow), - prompt.OptionPreviewSuggestionTextColor(prompt.Blue), - prompt.OptionSuggestionBGColor(prompt.White), - prompt.OptionSuggestionTextColor(prompt.Black), - prompt.OptionSelectedSuggestionBGColor(prompt.DarkGray), - prompt.OptionSelectedSuggestionTextColor(prompt.White), - prompt.OptionCompletionOnDown(), - prompt.OptionTitle("Calcite CLI Prompt"), // Set a title for the prompt - prompt.OptionInputTextColor(prompt.Fuchsia), // Customize input text color - prompt.OptionDescriptionTextColor(prompt.Black), // Customize description text color - prompt.OptionSelectedSuggestionTextColor(prompt.White), // Customize selected suggestion text color - prompt.OptionSelectedSuggestionBGColor(prompt.LightGray), // Customize selected suggestion background color - prompt.OptionPrefix("calcite \U0001F48E:sql> "), // Set a custom prefix for the prompt - ) - - p.Run() - -} - -var isMultiline bool - -func LivePrefix() (prefix string, useLivePrefix bool) { - if isMultiline { - prefix = "... " - useLivePrefix = true - } else { - prefix = "calcite \U0001F48E:sql> " - useLivePrefix = !isMultiline - } - return prefix, useLivePrefix + // Create and run the SQL prompt + prompt.CreateAndRunPrompt(db) } -func executeQueryWrapper(db *sql.DB) func(string) { - var multiLineQuery strings.Builder - - return func(query string) { - // Check for exit command - if strings.ToLower(query) == "exit" || strings.ToLower(query) == "quit" { - fmt.Println("Exiting calcite CLI Prompt...") - os.Exit(0) - } - - trimmedQuery := strings.TrimSpace(query) - - // Check if it is a multiline query - if strings.HasSuffix(trimmedQuery, ";") { - multiLineQuery.WriteString(trimmedQuery) - executeQuery(db, multiLineQuery.String()) - multiLineQuery.Reset() - isMultiline = false - } else { - if !isMultiline { - multiLineQuery.Reset() - isMultiline = true - } - multiLineQuery.WriteString(trimmedQuery) - multiLineQuery.WriteString(" ") - } - } -} - -func executeQuery(db *sql.DB, query string) { - // Execute the query - start := time.Now() - cmd := strings.TrimRight(query, ";") - rows, err := db.Query(cmd) +func establishConnection() *sql.DB { + parameters := buildConnectionURL() + fmt.Println("Connecting to ", parameters) + db, err := sql.Open("avatica", parameters) if err != nil { - log.Println("Error executing query:", err) - return - } - defer rows.Close() - - // Get column names - columns, err := rows.Columns() - if err != nil { - log.Println("Error retrieving column names:", err) - return - } - - // Create a new table writer for each query - table := tablewriter.NewWriter(os.Stdout) - table.SetAutoFormatHeaders(true) - table.SetAutoWrapText(false) - table.SetReflowDuringAutoWrap(true) - - // Create a slice to store the query results - values := make([]interface{}, len(columns)) - scanArgs := make([]interface{}, len(columns)) - for i := range values { - scanArgs[i] = &values[i] + log.Fatal(err) } - - // Fetch and print rows - count := 0 - for rows.Next() { - err = rows.Scan(scanArgs...) - if err != nil { - log.Println("Error retrieving row data:", err) - continue - } - - // Prepare row data - rowData := make([]string, len(columns)) - for i, v := range values { - if v != nil { - rowData[i] = fmt.Sprintf("%v", v) - } else { - rowData[i] = "NULL" - } - } - - // Add row to the table - table.Append(rowData) - count++ + if err = db.Ping(); err != nil { + log.Fatal(err) } - - duration := time.Since(start) - - // Set the table headers - table.SetHeader(columns) - - // Render the table - table.Render() - - fmt.Printf("Rows: %d\nExecution Time: %s\n\n", count, duration) + fmt.Println("Connected") + return db } func buildConnectionURL() string { + var url strings.Builder + + // Append the connection URL + url.WriteString(connectionURL) + var params []string - // Add serialization parameter + // Add serialization parameter by default protobuf if serialization != "" { params = append(params, "serialization="+serialization) } - // Add enablePartitionPruning parameter - if enablePartitionPruning { - params = append(params, "enablePartitionPruning=true") + // Add username and password as parameter + if user != "" { + params = append(params, "avaticaUser="+user) + params = append(params, "avaticaPassword="+passwd) } - // Add distributedExecution parameter - if !distributedExecution { - params = append(params, "distributedExecution=false") + // Add connection parameters + if connectionParams != "" { + params = append(params, connectionParams) + } + + if maxRowsTotal != "" { + params = append(params, "maxRowsTotal="+maxRowsTotal) } // Combine the connection URL and parameters - url := connectionURL + if schema != "" { + url.WriteString("/") + url.WriteString(schema) + } + if len(params) > 0 { - url += "?" + strings.Join(params, "&") + url.WriteString("?") + url.WriteString(strings.Join(params, "&")) } - return url + return url.String() } diff --git a/go.mod b/go.mod index 3f0275d..a454ccc 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,14 @@ go 1.20 require ( github.com/apache/calcite-avatica-go/v5 v5.2.0 + github.com/c-bata/go-prompt v0.2.6 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.7.0 ) require ( - github.com/mattn/go-colorable v0.1.7 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect golang.org/x/net v0.7.0 // indirect @@ -18,7 +19,6 @@ require ( ) require ( - github.com/c-bata/go-prompt v0.2.6 github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/icholy/digest v0.1.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -28,8 +28,11 @@ require ( github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect google.golang.org/protobuf v1.31.0 // indirect ) + +replace github.com/c-bata/go-prompt v0.2.6 => github.com/aranjan7/go-prompt v0.2.7 \ No newline at end of file diff --git a/go.sum b/go.sum index 01c7a1d..2895fb4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/apache/calcite-avatica-go/v5 v5.2.0 h1:9SQf9qz/iUaC39lvRDqFGmVVtoIWtnDB94YNg2Dy+UY= github.com/apache/calcite-avatica-go/v5 v5.2.0/go.mod h1:R9YlGqS8pPRnWAW1peGPpVax49XcujI7GLebaz9sOfk= -github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= -github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/aranjan7/go-prompt v0.2.7 h1:/A4p4+9+EhyL3Slib6ye0csbjvV3q6IOWKhG2W09JJ4= +github.com/aranjan7/go-prompt v0.2.7/go.mod h1:wFY5NCn46a/drx/6Cy+KgKnXQ/X+4gfEJB7orFYQDZs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -34,15 +34,16 @@ github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -53,6 +54,8 @@ github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= @@ -80,13 +83,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/keywords/keywords.go b/prompt/completer.go similarity index 99% rename from keywords/keywords.go rename to prompt/completer.go index 3202c96..9e210d8 100644 --- a/keywords/keywords.go +++ b/prompt/completer.go @@ -15,8 +15,7 @@ * limitations under the License. */ - -package keywords +package prompt import "github.com/c-bata/go-prompt" diff --git a/prompt/executor.go b/prompt/executor.go new file mode 100644 index 0000000..4c230f6 --- /dev/null +++ b/prompt/executor.go @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prompt + +import ( + "database/sql" + "fmt" + "os" + "strings" + + _ "github.com/apache/calcite-avatica-go/v5" + "github.com/c-bata/go-prompt" + calcitesql "github.com/satyakommula96/calcite-cli/calcitesql" +) + +var isMultiline bool + +func CreateAndRunPrompt(db *sql.DB) { + fmt.Println("Welcome! Use SQL to query Apache Calcite.\nUse Ctrl+D, type \"exit\" or \"quit\" to exit.") + fmt.Println() + + p := prompt.New( + executeQueryWrapper(db), + CustomCompleter, + prompt.OptionLivePrefix(LivePrefix), + prompt.OptionPrefixTextColor(prompt.Yellow), + prompt.OptionPreviewSuggestionTextColor(prompt.Blue), + prompt.OptionSuggestionBGColor(prompt.White), + prompt.OptionSuggestionTextColor(prompt.Black), + prompt.OptionSelectedSuggestionBGColor(prompt.DarkGray), + prompt.OptionSelectedSuggestionTextColor(prompt.White), + prompt.OptionCompletionOnDown(), + prompt.OptionTitle("Calcite CLI Prompt"), // Set a title for the prompt + prompt.OptionInputTextColor(prompt.Fuchsia), // Customize input text color + prompt.OptionDescriptionTextColor(prompt.Black), // Customize description text color + prompt.OptionSelectedSuggestionTextColor(prompt.White), // Customize selected suggestion text color + prompt.OptionSelectedSuggestionBGColor(prompt.LightGray), // Customize selected suggestion background color + prompt.OptionPrefix("calcite \U0001F48E:sql> "), // Set a custom prefix for the prompt + ) + + p.Run() +} + +func LivePrefix() (prefix string, useLivePrefix bool) { + if isMultiline { + prefix = "... " + useLivePrefix = true + } else { + prefix = "calcite \U0001F48E:sql> " + useLivePrefix = !isMultiline + } + return prefix, useLivePrefix +} + +func executeQueryWrapper(db *sql.DB) func(string) { + var multiLineQuery strings.Builder + + return func(query string) { + // Check for exit command + if strings.ToLower(query) == "exit" || strings.ToLower(query) == "quit" { + fmt.Println("Exiting calcite CLI Prompt...") + os.Exit(0) + } + + trimmedQuery := strings.TrimSpace(query) + + // Check if it is a multiline query + if strings.HasSuffix(trimmedQuery, ";") { + multiLineQuery.WriteString(trimmedQuery) + calcitesql.ExecuteQuery(db, multiLineQuery.String()) + multiLineQuery.Reset() + isMultiline = false + } else { + if !isMultiline { + multiLineQuery.Reset() + isMultiline = true + } + multiLineQuery.WriteString(trimmedQuery) + multiLineQuery.WriteString(" ") + } + } +} diff --git a/prompt/prompt.go b/prompt/prompt.go new file mode 100644 index 0000000..240e602 --- /dev/null +++ b/prompt/prompt.go @@ -0,0 +1,18 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prompt \ No newline at end of file