Skip to content

Commit

Permalink
Fixes issue where table names with special characters are not handled…
Browse files Browse the repository at this point in the history
… correctly in auto-complete and `.inspect`. Closes #1109
  • Loading branch information
binaek authored Nov 24, 2021
1 parent 27892a0 commit d55903d
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 30 deletions.
24 changes: 20 additions & 4 deletions autocomplete/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (

"github.com/c-bata/go-prompt"
"github.com/turbot/go-kit/helpers"
"github.com/turbot/steampipe/db/db_common"
"github.com/turbot/steampipe/schema"
"github.com/turbot/steampipe/steampipeconfig"
"github.com/turbot/steampipe/utils"
)

// GetTableAutoCompleteSuggestions :: derives and returns tables for typeahead
Expand Down Expand Up @@ -47,7 +49,7 @@ func GetTableAutoCompleteSuggestions(schema *schema.Metadata, connectionMap *ste
// add qualified names of all tables
for tableName := range schemaDetails {
if !isTemporarySchema {
qualifiedTablesToAdd = append(qualifiedTablesToAdd, fmt.Sprintf("%s.%s", schemaName, tableName))
qualifiedTablesToAdd = append(qualifiedTablesToAdd, fmt.Sprintf("%s.%s", schemaName, escapeIfRequired(tableName)))
}
}

Expand All @@ -71,15 +73,17 @@ func GetTableAutoCompleteSuggestions(schema *schema.Metadata, connectionMap *ste
sort.Strings(qualifiedTablesToAdd)

for _, schema := range schemasToAdd {
s = append(s, prompt.Suggest{Text: schema, Description: "Schema"})
// we don't need to escape schema names, since schema names are derived from connection names
// which are validated so that we don't end up with names which need it
s = append(s, prompt.Suggest{Text: schema, Description: "Schema", Output: schema})
}

for _, table := range unqualifiedTablesToAdd {
s = append(s, prompt.Suggest{Text: table, Description: "Table"})
s = append(s, prompt.Suggest{Text: table, Description: "Table", Output: escapeIfRequired(table)})
}

for _, table := range qualifiedTablesToAdd {
s = append(s, prompt.Suggest{Text: table, Description: "Table"})
s = append(s, prompt.Suggest{Text: table, Description: "Table", Output: escapeIfRequired(table)})
}

return s
Expand All @@ -88,3 +92,15 @@ func GetTableAutoCompleteSuggestions(schema *schema.Metadata, connectionMap *ste
func stripVersionFromPluginName(pluginName string) string {
return strings.Split(pluginName, "@")[0]
}

func escapeIfRequired(strToEscape string) string {
tokens := utils.SplitByRune(strToEscape, '.')
escaped := []string{}
for _, token := range tokens {
if strings.ContainsAny(token, " -") {
token = db_common.PgEscapeName(token)
}
escaped = append(escaped, token)
}
return strings.Join(escaped, ".")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ require (
sigs.k8s.io/yaml v1.1.0
)

replace github.com/c-bata/go-prompt => github.com/turbot/go-prompt v0.2.6-steampipe.0.20210830083819-c872df2bdcc9
replace github.com/c-bata/go-prompt => github.com/turbot/go-prompt v0.2.6-steampipe.0.20211124090719-0709bc8d8ce2
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -909,8 +909,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA=
github.com/turbot/go-kit v0.3.0 h1:o4zZIO1ovdmJ2bHWOdXnnt8jJMIDGqYSkZvBREzFeMQ=
github.com/turbot/go-kit v0.3.0/go.mod h1:SBdPRngbEfYubiR81iAVtO43oPkg1+ASr+XxvgbH7/k=
github.com/turbot/go-prompt v0.2.6-steampipe.0.20210830083819-c872df2bdcc9 h1:mcDQVuT3E9tQtCB7sdLgEm3uC/eGGtBr27WwQCU6j+s=
github.com/turbot/go-prompt v0.2.6-steampipe.0.20210830083819-c872df2bdcc9/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
github.com/turbot/go-prompt v0.2.6-steampipe.0.20211124090719-0709bc8d8ce2 h1:mydbzShy5MB1XMDu2S4DocAo8/Jl8vRmEa37FvpY2jY=
github.com/turbot/go-prompt v0.2.6-steampipe.0.20211124090719-0709bc8d8ce2/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
github.com/turbot/steampipe-plugin-sdk v1.8.0 h1:bPHlCnAg66UTMz7W7AwR8jCszOwwe6Bfmvpiko7VB2k=
github.com/turbot/steampipe-plugin-sdk v1.8.0/go.mod h1:76H3wr6KB6t+kDS38EEOZAsw61Ie/q7/IV9X0kv5NjI=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
Expand Down
4 changes: 2 additions & 2 deletions interactive/interactive_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ func (c *InteractiveClient) queryCompleter(d prompt.Document) []prompt.Suggest {
//named queries
s = append(s, c.namedQuerySuggestions()...)
// "select"
s = append(s, prompt.Suggest{Text: "select"})
s = append(s, prompt.Suggest{Text: "select", Output: "select"}, prompt.Suggest{Text: "with", Output: "with"})

// metaqueries
s = append(s, metaquery.PromptSuggestions()...)
Expand Down Expand Up @@ -528,7 +528,7 @@ func (c *InteractiveClient) namedQuerySuggestions() []prompt.Suggest {
if q.Description != nil {
description += fmt.Sprintf(": %s", *q.Description)
}
res = append(res, prompt.Suggest{Text: queryName, Description: description})
res = append(res, prompt.Suggest{Text: queryName, Output: queryName, Description: description})
}
// add all the controls in the workspace
for controlName, c := range c.workspace().GetControlMap() {
Expand Down
2 changes: 1 addition & 1 deletion query/metaquery/completers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func completerFromArgsOf(cmd string) completer {
metaQueryDefinition, _ := metaQueryDefinitions[cmd]
suggestions := make([]prompt.Suggest, len(metaQueryDefinition.args))
for idx, arg := range metaQueryDefinition.args {
suggestions[idx] = prompt.Suggest{Text: arg.value, Description: arg.description}
suggestions[idx] = prompt.Suggest{Text: arg.value, Description: arg.description, Output: arg.value}
}
return suggestions
}
Expand Down
9 changes: 6 additions & 3 deletions query/metaquery/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,12 @@ func init() {
completer: completerFromArgsOf(constants.CmdCache),
},
constants.CmdInspect: {
title: constants.CmdInspect,
handler: inspect,
validator: atMostNArgs(1),
title: constants.CmdInspect,
handler: inspect,
// .inspect only supports a single arg, however the arg validation code cannot understand escaped arguments
// e.g. it will treat csv."my table" as 2 args
// the logic to handle this escaping is lower down so we just validate to ensure at least one argument has been provided
validator: atLeastNArgs(0),
description: "View connections, tables & column information",
completer: inspectCompleter,
},
Expand Down
16 changes: 15 additions & 1 deletion query/metaquery/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,23 @@ func inspect(input *HandlerInput) error {
if len(input.args()) == 0 {
return listConnections(input)
}
// arg can be one of <connection_name> or <connection_name>.<table_name>
tableOrConnection := input.args()[0]
if len(input.args()) > 0 {
// this should be one argument, but may have been split by the tokenizer
// because of the escape characters that autocomplete puts in
// join them up
tableOrConnection = strings.Join(input.args(), " ")
}
// arg can be one of <connection_name> or <connection_name>.<table_name>
split := strings.Split(tableOrConnection, ".")
for i, s := range split {
// trim escaping
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, `"`)
s = strings.TrimSuffix(s, `"`)

split[i] = s
}

if len(split) == 1 {
// only a connection name (or maybe unqualified table name)
Expand Down
19 changes: 3 additions & 16 deletions query/metaquery/utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package metaquery

import (
"encoding/csv"
"sort"
"strings"

"github.com/c-bata/go-prompt"
"github.com/turbot/steampipe/utils"
)

// IsMetaQuery :: returns true if the query is a metaquery, false otherwise
Expand All @@ -23,7 +23,7 @@ func IsMetaQuery(query string) bool {

func getCmdAndArgs(query string) (string, []string) {
query = strings.TrimSuffix(query, ";")
split := splitByWhitespace(query)
split := utils.SplitByWhitespace(query)
cmd := split[0]
args := []string{}
if len(split) > 1 {
Expand All @@ -32,24 +32,11 @@ func getCmdAndArgs(query string) (string, []string) {
return cmd, args
}

// splitByWhitespace uses the CSV decoder, using '\s' as the separator rune
// this enables us to parse out the tokens - even if they are quoted and/or escaped
func splitByWhitespace(str string) (s []string) {
csvDecoder := csv.NewReader(strings.NewReader(str))
csvDecoder.Comma = ' '
csvDecoder.LazyQuotes = true
csvDecoder.TrimLeadingSpace = true
// Read can never error, because we are passing in a StringReader
// lookup csv.Reader.Read
split, _ := csvDecoder.Read()
return split
}

// PromptSuggestions :: Returns a list of the suggestions for go-prompt
func PromptSuggestions() []prompt.Suggest {
suggestions := make([]prompt.Suggest, 0, len(metaQueryDefinitions))
for k, definition := range metaQueryDefinitions {
suggestions = append(suggestions, prompt.Suggest{Text: k, Description: definition.description})
suggestions = append(suggestions, prompt.Suggest{Text: k, Description: definition.description, Output: k})
}

sort.SliceStable(suggestions[:], func(i, j int) bool {
Expand Down
12 changes: 12 additions & 0 deletions query/metaquery/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ func validatorFromArgsOf(cmd string) validator {
}
}

var atLeastNArgs = func(n int) validator {
return func(args []string) ValidationResult {
numArgs := len(args)
if numArgs < n {
return ValidationResult{
Err: fmt.Errorf("command needs at least %d argument(s) - got %d", n, numArgs),
}
}
return ValidationResult{ShouldRun: true}
}
}

var atMostNArgs = func(n int) validator {
return func(args []string) ValidationResult {
numArgs := len(args)
Expand Down
21 changes: 21 additions & 0 deletions utils/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package utils

import (
"encoding/csv"
"strings"
)

// SplitByRune uses the CSV decoder to parse out the tokens - even if they are quoted and/or escaped
func SplitByRune(str string, r rune) []string {
csvDecoder := csv.NewReader(strings.NewReader(str))
csvDecoder.Comma = r
csvDecoder.LazyQuotes = true
csvDecoder.TrimLeadingSpace = true
split, _ := csvDecoder.Read()
return split
}

// SplitByWhitespace splits by the ' ' rune
func SplitByWhitespace(str string) []string {
return SplitByRune(str, ' ')
}

0 comments on commit d55903d

Please sign in to comment.