Skip to content

Commit

Permalink
cli: support autocomplete
Browse files Browse the repository at this point in the history
Release note (cli change): CLI now auto completes on tab
by using `SHOW COMPLETIONS AT OFFSET`.
  • Loading branch information
RichardJCai committed Feb 25, 2022
1 parent f58b5af commit 1e64f18
Show file tree
Hide file tree
Showing 11 changed files with 602 additions and 473 deletions.
1 change: 1 addition & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ ALL_TESTS = [
"//pkg/sql/contention/txnidcache:txnidcache_test",
"//pkg/sql/contention:contention_test",
"//pkg/sql/covering:covering_test",
"//pkg/sql/delegate:delegate_test",
"//pkg/sql/distsql:distsql_test",
"//pkg/sql/doctor:doctor_test",
"//pkg/sql/enum:enum_test",
Expand Down
37 changes: 27 additions & 10 deletions pkg/cli/clisqlshell/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -900,18 +900,35 @@ func (c *cliState) GetCompletions(_ string) []string {
sql, _ := c.ins.GetLineInfo()

if !strings.HasSuffix(sql, "??") {
fmt.Fprintf(c.iCtx.stdout,
"\ntab completion not supported; append '??' and press tab for contextual help\n\n")
} else {
helpText, err := c.serverSideParse(sql)
if helpText != "" {
// We have a completion suggestion. Use that.
fmt.Fprintf(c.iCtx.stdout, "\nSuggestion:\n%s\n", helpText)
} else if err != nil {
// Some other error. Display it.
fmt.Fprintln(c.iCtx.stdout)
query := fmt.Sprintf(`SHOW COMPLETIONS AT OFFSET %d FOR %s`, len(sql), lexbase.EscapeSQLString(sql))
var rows [][]string
var err error
err = c.runWithInterruptableCtx(func(ctx context.Context) error {
_, rows, err = c.sqlExecCtx.RunQuery(ctx, c.conn,
clisqlclient.MakeQuery(query), true)
return err
})

if err != nil {
clierror.OutputError(c.iCtx.stdout, err, true /*showSeverity*/, false /*verbose*/)
}

var completions []string
for _, row := range rows {
completions = append(completions, row[0])
}

return completions
}

helpText, err := c.serverSideParse(sql)
if helpText != "" {
// We have a completion suggestion. Use that.
fmt.Fprintf(c.iCtx.stdout, "\nSuggestion:\n%s\n", helpText)
} else if err != nil {
// Some other error. Display it.
fmt.Fprintln(c.iCtx.stdout)
clierror.OutputError(c.iCtx.stdout, err, true /*showSeverity*/, false /*verbose*/)
}

// After the suggestion or error, re-display the prompt and current entry.
Expand Down
31 changes: 31 additions & 0 deletions pkg/sql/conn_executor_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"encoding/base64"
"fmt"
"runtime/pprof"
"strconv"
"strings"
"sync/atomic"
"time"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
"github.com/cockroachdb/cockroach/pkg/sql/contentionpb"
"github.com/cockroachdb/cockroach/pkg/sql/delegate"
"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
"github.com/cockroachdb/cockroach/pkg/sql/execstats"
"github.com/cockroachdb/cockroach/pkg/sql/opt/exec/explain"
Expand Down Expand Up @@ -1656,6 +1658,8 @@ func (ex *connExecutor) runObserverStatement(
return ex.runShowLastQueryStatistics(ctx, res, sqlStmt)
case *tree.ShowTransferState:
return ex.runShowTransferState(ctx, res, sqlStmt)
case *tree.ShowCompletions:
return ex.runShowCompletions(ctx, sqlStmt, res)
default:
res.SetError(errors.AssertionFailedf("unrecognized observer statement type %T", ast))
return nil
Expand Down Expand Up @@ -1779,6 +1783,33 @@ func (ex *connExecutor) runShowTransferState(
return res.AddRow(ctx, row)
}

// runShowCompletions executes a SHOW COMPLETIONS statement.
func (ex *connExecutor) runShowCompletions(
ctx context.Context, n *tree.ShowCompletions, res RestrictedCommandResult,
) error {
res.SetColumns(ctx, colinfo.ResultColumns{{Name: "COMPLETIONS", Typ: types.String}})
offsetVal, ok := n.Offset.AsConstantInt()
if !ok {
return errors.Newf("invalid offset %v", n.Offset)
}
offset, err := strconv.Atoi(offsetVal.String())
if err != nil {
return err
}
completions, err := delegate.RunShowCompletions(n.Statement.RawString(), offset)
if err != nil {
return err
}

for _, completion := range completions {
err = res.AddRow(ctx, tree.Datums{tree.NewDString(completion)})
if err != nil {
return err
}
}
return nil
}

// showQueryStatsFns maps column names as requested by the SQL clients
// to timing retrieval functions from the execution phase times.
var showQueryStatsFns = map[tree.Name]func(*sessionphase.Times) time.Duration{
Expand Down
9 changes: 8 additions & 1 deletion pkg/sql/delegate/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "delegate",
Expand All @@ -7,6 +7,7 @@ go_library(
"job_control.go",
"show_all_cluster_settings.go",
"show_changefeed_jobs.go",
"show_completions.go",
"show_database_indexes.go",
"show_databases.go",
"show_default_privileges.go",
Expand Down Expand Up @@ -57,3 +58,9 @@ go_library(
"@com_github_cockroachdb_errors//:errors",
],
)

go_test(
name = "delegate_test",
srcs = ["show_completions_test.go"],
embed = [":delegate"],
)
23 changes: 17 additions & 6 deletions pkg/sql/delegate/show_completions.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package delegate

import (
Expand All @@ -24,7 +34,7 @@ func (d *delegator) delegateShowCompletions(n *tree.ShowCompletions) (tree.State
return nil, err
}

completions, err := runShowCompletions(n.Statement, offset)
completions, err := RunShowCompletions(n.Statement.RawString(), offset)
if err != nil {
return nil, err
}
Expand All @@ -49,14 +59,15 @@ func (d *delegator) delegateShowCompletions(n *tree.ShowCompletions) (tree.State
return parse(query.String())
}

func runShowCompletions(stmt string, offset int) ([]string, error) {
// RunShowCompletions returns a list of completion keywords for the given
// statement and offset.
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
// Currently, 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
Expand All @@ -81,8 +92,8 @@ func runShowCompletions(stmt string, offset int) ([]string, error) {
// 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]
allSQLTokens := parser.TokensIgnoreErrors(stmt)
lastWordFull := allSQLTokens[len(sqlTokenStrings)-1]
if lastWordFull.Str != lastWordTruncated {
return []string{strings.ToUpper(lastWordFull.Str)}, nil
}
Expand Down
12 changes: 11 additions & 1 deletion pkg/sql/delegate/show_completions_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package delegate

import (
Expand Down Expand Up @@ -83,7 +93,7 @@ func TestCompletions(t *testing.T) {
if tc.offset == 0 {
offset = len(tc.stmt)
}
completions, err := runShowCompletions(tc.stmt, offset)
completions, err := RunShowCompletions(tc.stmt, offset)
if err != nil {
t.Error(err)
}
Expand Down
20 changes: 9 additions & 11 deletions pkg/sql/logictest/testdata/logic_test/show_completions
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
query T
show completions at offset 1 for 'select 1'
SHOW COMPLETIONS AT OFFSET 1 FOR 'select 1'
----
SELECT

query T
show completions at offset 7 for 'select 1'
SHOW COMPLETIONS AT OFFSET 7 FOR 'select 1'
----
·

query T
show completions at offset 7 for 'select 2'
SHOW COMPLETIONS AT OFFSET 7 FOR 'select 2'
----
·

query T
show completions at offset 10 for 'select * fro'
SHOW COMPLETIONS AT OFFSET 10 FOR 'select * fro'
----
FRO

query T
show completions at offset 11 for 'select * fro'
SHOW COMPLETIONS AT OFFSET 11 FOR 'select * fro'
----
FRO

query T
show completions at offset 12 for 'select * fro'
SHOW COMPLETIONS AT OFFSET 12 FOR 'select * fro'
----
FROM

query T
show completions at offset 10 for 'select * from'
SHOW COMPLETIONS AT OFFSET 10 FOR 'select * from'
----
FROM

query T
show completions at offset 11 for 'select * from'
SHOW COMPLETIONS AT OFFSET 11 FOR 'select * from'
----
FROM

Expand All @@ -43,7 +41,7 @@ FROM
# 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\'';
SHOW COMPLETIONS AT OFFSET 4 FOR e'\'se\'';
----
SEARCH
SECOND
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -6114,7 +6114,7 @@ show_completions_stmt:
{
/* SKIP DOC */
$$.val = &tree.ShowCompletions{
Statement: $7,
Statement: tree.NewStrVal($7),
Offset: $5.numVal(),
}
}
Expand Down
47 changes: 47 additions & 0 deletions pkg/sql/parser/testdata/show_completions
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
parse
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat'
----
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat'
SHOW COMPLETIONS AT OFFSET 1 FOR ('creat') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 1 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat' -- identifiers removed

parse
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat'
----
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat'
SHOW COMPLETIONS AT OFFSET 1 FOR ('creat') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 1 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 1 FOR 'creat' -- identifiers removed

parse
SHOW COMPLETIONS AT OFFSET 1 FOR 'CREAT'
----
SHOW COMPLETIONS AT OFFSET 1 FOR 'CREAT'
SHOW COMPLETIONS AT OFFSET 1 FOR ('CREAT') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 1 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 1 FOR 'CREAT' -- identifiers removed

parse
SHOW COMPLETIONS AT OFFSET 7 FOR 'SELECT 1'
----
SHOW COMPLETIONS AT OFFSET 7 FOR 'SELECT 1'
SHOW COMPLETIONS AT OFFSET 7 FOR ('SELECT 1') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 7 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 7 FOR 'SELECT 1' -- identifiers removed

parse
show completions at offset 10 for 'select * fro'
----
SHOW COMPLETIONS AT OFFSET 10 FOR 'select * fro' -- normalized!
SHOW COMPLETIONS AT OFFSET 10 FOR ('select * fro') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 10 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 10 FOR 'select * fro' -- identifiers removed

parse
SHOW COMPLETIONS AT OFFSET 4 FOR e'\'se\'';
----
SHOW COMPLETIONS AT OFFSET 4 FOR e'\'se\'' -- normalized!
SHOW COMPLETIONS AT OFFSET 4 FOR (e'\'se\'') -- fully parenthesized
SHOW COMPLETIONS AT OFFSET 4 FOR '_' -- literals removed
SHOW COMPLETIONS AT OFFSET 4 FOR e'\'se\'' -- identifiers removed
Loading

0 comments on commit 1e64f18

Please sign in to comment.