-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sql: add SHOW COMPLETIONS AT offset FOR syntax
Release note (sql change): Support SHOW COMPLETIONS AT OFFSET <offset> FOR <stmt> syntax that returns a set of SQL keywords that can complete the keyword at <offset> in the given <stmt>. If the offset is in the middle of a word, then it returns the full word. For example SHOW COMPLETIONS AT OFFSET 1 FOR "SELECT" returns select.
- Loading branch information
1 parent
2de17e7
commit 2a70cdc
Showing
10 changed files
with
336 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package delegate | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
"unicode" | ||
|
||
"github.com/cockroachdb/cockroach/pkg/sql/lexbase" | ||
"github.com/cockroachdb/cockroach/pkg/sql/parser" | ||
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree" | ||
"github.com/cockroachdb/errors" | ||
) | ||
|
||
func (d *delegator) delegateShowCompletions(n *tree.ShowCompletions) (tree.Statement, error) { | ||
offsetVal, ok := n.Offset.AsConstantInt() | ||
if !ok { | ||
return nil, errors.Newf("invalid offset %v", n.Offset) | ||
} | ||
offset, err := strconv.Atoi(offsetVal.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
completions, err := runShowCompletions(n.Statement, offset) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(completions) == 0 { | ||
return parse(`SELECT '' as completions`) | ||
} | ||
|
||
var query bytes.Buffer | ||
fmt.Fprint(&query, "SELECT @1 AS completions FROM (VALUES ") | ||
|
||
comma := "" | ||
for _, completion := range completions { | ||
fmt.Fprintf(&query, "%s(", comma) | ||
lexbase.EncodeSQLString(&query, completion) | ||
query.WriteByte(')') | ||
comma = ", " | ||
} | ||
|
||
fmt.Fprintf(&query, ")") | ||
|
||
return parse(query.String()) | ||
} | ||
|
||
func runShowCompletions(stmt string, offset int) ([]string, error) { | ||
if offset <= 0 || offset > len(stmt) { | ||
return nil, nil | ||
} | ||
|
||
// For simplicity, if we're on a whitespace, return no completions. | ||
// Currently, parser. | ||
// parser.TokensIgnoreErrors does not consider whitespaces | ||
// after the last token. | ||
// Ie "SELECT ", will only return one token being "SELECT". | ||
// If we're at the whitespace, we do not want to return completion | ||
// recommendations for "SELECT". | ||
if unicode.IsSpace([]rune(stmt)[offset-1]) { | ||
return nil, nil | ||
} | ||
|
||
sqlTokens := parser.TokensIgnoreErrors(string([]rune(stmt)[:offset])) | ||
if len(sqlTokens) == 0 { | ||
return nil, nil | ||
} | ||
|
||
sqlTokenStrings := make([]string, len(sqlTokens)) | ||
for i, sqlToken := range sqlTokens { | ||
sqlTokenStrings[i] = sqlToken.Str | ||
} | ||
|
||
lastWordTruncated := sqlTokenStrings[len(sqlTokenStrings)-1] | ||
|
||
// If the offset is in the middle of a word, we return the full word. | ||
// For example if the stmt is SELECT with offset 2, even though SEARCH would | ||
// come first for "SE", we want to return "SELECT". | ||
// Similarly, if we have SEL with offset 2, we want to return "SEL". | ||
allSqlTokens := parser.TokensIgnoreErrors(stmt) | ||
lastWordFull := allSqlTokens[len(sqlTokenStrings)-1] | ||
if lastWordFull.Str != lastWordTruncated { | ||
return []string{strings.ToUpper(lastWordFull.Str)}, nil | ||
} | ||
|
||
return getCompletionsForWord(lastWordTruncated, lexbase.KeywordNames), nil | ||
} | ||
|
||
// Binary search for range with matching prefixes | ||
// Return the range of matching prefixes for w. | ||
func binarySearch(w string, words []string) (int, int) { | ||
// First binary search for the first string in the sorted lexbase.KeywordNames list | ||
// that matches the prefix w. | ||
left := sort.Search(len(words), func(i int) bool { return words[i] >= w }) | ||
|
||
// Binary search for the last string in the sorted lexbase.KeywordNames list that matches | ||
// the prefix w. | ||
right := sort.Search(len(words), func(i int) bool { return words[i][:min(len(words[i]), len(w))] > w }) | ||
|
||
return left, right | ||
} | ||
|
||
func min(a int, b int) int { | ||
if a < b { | ||
return a | ||
} | ||
return b | ||
} | ||
|
||
func getCompletionsForWord(w string, words []string) []string { | ||
left, right := binarySearch(strings.ToLower(w), words) | ||
completions := make([]string, right-left) | ||
|
||
for i, word := range words[left:right] { | ||
completions[i] = strings.ToUpper(word) | ||
} | ||
return completions | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package delegate | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestCompletions(t *testing.T) { | ||
tests := []struct { | ||
stmt string | ||
offset int | ||
expectedCompletions []string | ||
}{ | ||
{ | ||
stmt: "creat", | ||
expectedCompletions: []string{"CREATE", "CREATEDB", "CREATELOGIN", "CREATEROLE"}, | ||
}, | ||
{ | ||
stmt: "CREAT", | ||
expectedCompletions: []string{"CREATE", "CREATEDB", "CREATELOGIN", "CREATEROLE"}, | ||
}, | ||
{ | ||
stmt: "creat ", | ||
expectedCompletions: []string{}, | ||
}, | ||
{ | ||
stmt: "SHOW CREAT", | ||
expectedCompletions: []string{"CREATE", "CREATEDB", "CREATELOGIN", "CREATEROLE"}, | ||
}, | ||
{ | ||
stmt: "show creat", | ||
expectedCompletions: []string{"CREATE", "CREATEDB", "CREATELOGIN", "CREATEROLE"}, | ||
}, | ||
{ | ||
stmt: "se", | ||
expectedCompletions: []string{ | ||
"SEARCH", "SECOND", "SELECT", "SEQUENCE", "SEQUENCES", | ||
"SERIALIZABLE", "SERVER", "SESSION", "SESSIONS", "SESSION_USER", | ||
"SET", "SETS", "SETTING", "SETTINGS", | ||
}, | ||
}, | ||
{ | ||
stmt: "sel", | ||
expectedCompletions: []string{"SELECT"}, | ||
}, | ||
{ | ||
stmt: "create ta", | ||
expectedCompletions: []string{"TABLE", "TABLES", "TABLESPACE"}, | ||
}, | ||
{ | ||
stmt: "create ta", | ||
expectedCompletions: []string{"CREATE"}, | ||
offset: 3, | ||
}, | ||
{ | ||
stmt: "select", | ||
expectedCompletions: []string{"SELECT"}, | ||
offset: 2, | ||
}, | ||
{ | ||
stmt: "select ", | ||
expectedCompletions: []string{}, | ||
offset: 7, | ||
}, | ||
{ | ||
stmt: "你好,我的名字是鲍勃 SELECT", | ||
expectedCompletions: []string{"你好,我的名字是鲍勃"}, | ||
offset: 2, | ||
}, | ||
{ | ||
stmt: "你好,我的名字是鲍勃 SELECT", | ||
expectedCompletions: []string{}, | ||
offset: 11, | ||
}, | ||
{ | ||
stmt: "你好,我的名字是鲍勃 SELECT", | ||
expectedCompletions: []string{"SELECT"}, | ||
offset: 12, | ||
}, | ||
} | ||
for _, tc := range tests { | ||
offset := tc.offset | ||
if tc.offset == 0 { | ||
offset = len(tc.stmt) | ||
} | ||
completions, err := runShowCompletions(tc.stmt, offset) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if !(len(completions) == 0 && len(tc.expectedCompletions) == 0) && | ||
!reflect.DeepEqual(completions, tc.expectedCompletions) { | ||
t.Errorf("expected %v, got %v", tc.expectedCompletions, completions) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
query T | ||
show completions at offset 1 for 'select 1' | ||
---- | ||
SELECT | ||
|
||
query T | ||
show completions at offset 7 for 'select 1' | ||
---- | ||
· | ||
|
||
query T | ||
show completions at offset 7 for 'select 2' | ||
---- | ||
· | ||
|
||
query T | ||
show completions at offset 10 for 'select * fro' | ||
---- | ||
FRO | ||
|
||
query T | ||
show completions at offset 11 for 'select * fro' | ||
---- | ||
FRO | ||
|
||
query T | ||
show completions at offset 12 for 'select * fro' | ||
---- | ||
FROM | ||
|
||
query T | ||
show completions at offset 10 for 'select * from' | ||
---- | ||
FROM | ||
|
||
query T | ||
show completions at offset 11 for 'select * from' | ||
---- | ||
FROM | ||
|
||
# This case doesn't really make sense - completing this as SELECT doesn't | ||
# really make sense but we'll need to add more complex logic to determine | ||
# whether our SQL token is a string const. | ||
# However we do want to test this so we can ensure we handle escaped strings. | ||
query T | ||
show completions at offset 4 for e'\'se\''; | ||
---- | ||
SEARCH | ||
SECOND | ||
SELECT | ||
SEQUENCE | ||
SEQUENCES | ||
SERIALIZABLE | ||
SERVER | ||
SESSION | ||
SESSIONS | ||
SESSION_USER | ||
SET | ||
SETS | ||
SETTING | ||
SETTINGS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.