Skip to content

Commit

Permalink
comprules: support completion for qualified functions
Browse files Browse the repository at this point in the history
The syntax completion engine now generates suggestion for user-defined
functions. It restricts the suggestions to only functions in the schema
name if one was provided, or else to the schemas in the search_path.

No release note since this functionality is new in v23.1.

Release note: None
  • Loading branch information
rafiss committed Jan 13, 2023
1 parent 992fd5d commit 83fca32
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 96 deletions.
4 changes: 2 additions & 2 deletions pkg/ccl/logictestccl/testdata/logic_test/crdb_internal_tenant
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ SELECT * FROM crdb_internal.cluster_contention_events WHERE table_id < 0
----
table_id index_id num_contention_events cumulative_contention_time key txn_id count

query TTTT colnames
query TTTTT colnames
SELECT * FROM crdb_internal.builtin_functions WHERE function = ''
----
function signature category details
function signature category details schema

query ITTITTTTTTTBBBB colnames
SELECT * FROM crdb_internal.create_statements WHERE database_name = ''
Expand Down
17 changes: 16 additions & 1 deletion pkg/cli/clisqlshell/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,22 @@ func (c *cliState) doHandleCliCmd(loopState, nextState cliStateEnum) cliStateEnu

case `\hf`:
if len(cmd) == 1 {
c.concatLines = `SELECT DISTINCT proname AS function FROM pg_proc ORDER BY 1`
// The following query lists all functions. It prefixes
// functions with their schema but only if the schema is not in
// the search path. This ensures that "common" functions
// are not prefixed by "pg_catalog", but crdb_internal functions
// get prefixed with "crdb_internal."
//
// TODO(knz): Replace this by the \df logic when that is implemented;
// see: https://github.com/cockroachdb/cockroach/pull/88061
c.concatLines = `
SELECT DISTINCT
IF(n.nspname = ANY current_schemas(TRUE), '',
pg_catalog.quote_ident(n.nspname) || '.') ||
pg_catalog.quote_ident(p.proname) AS function
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
ORDER BY 1`
return cliRunStatement
}
return c.handleFunctionHelp(cmd[1:], loopState, errState)
Expand Down
52 changes: 52 additions & 0 deletions pkg/cli/clisqlshell/testdata/complete/composite_names
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,55 @@ msg: ""
completions:
- "functions":
"crdb_internal.force_error(" (This function is used only by CockroachDB's developers for testing purposes.) -> "crdb_internal.force_error(" (0, 25)

sql
create schema my_schema
----
ok

sql
CREATE FUNCTION my_func(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT i'
----
ok

sql
CREATE FUNCTION my_schema.my_other_func(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT i'
----
ok

complete
select my@
----
complete 0 9
msg: ""
completions:
- "functions":
"my_func(" () -> "my_func(" (0, 2)
- "schema":
"my_schema" () -> "my_schema" (0, 2)

complete
select my_schema.@
----
complete 0 17
msg: ""
completions:
- "functions":
"my_schema.my_other_func(" () -> "my_schema.my_other_func(" (0, 10)

complete
select pg_catalog.array_l@
----
complete 0 25
msg: ""
completions:
- "functions":
"pg_catalog.array_length(" (Calculates the length of `input` on the provided `array_dimension`. However, bec) -> "pg_catalog.array_length(" (0, 18)
"pg_catalog.array_lower(" (Calculates the minimum value of `input` on the provided `array_dimension`. Howev) -> "pg_catalog.array_lower(" (0, 18)

complete
select "PG_CATALOG".@
----
complete 0 20
msg: ""
(no completions generated)
10 changes: 10 additions & 0 deletions pkg/cli/clisqlshell/testdata/complete/sql
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,13 @@ msg: ""
completions:
- "relation":
"mytable" () -> "mytable" (0, 0)

complete
select array_l@
----
complete 0 14
msg: ""
completions:
- "functions":
"array_length(" (Calculates the length of `input` on the provided `array_dimension`. However, bec) -> "array_length(" (0, 7)
"array_lower(" (Calculates the minimum value of `input` on the provided `array_dimension`. Howev) -> "array_lower(" (0, 7)
54 changes: 24 additions & 30 deletions pkg/sql/comprules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
func GetCompMethods() []compengine.Method {
return []compengine.Method{
method("keywords", completeKeyword),
method("builtins", completeBuiltin),
method("functions", completeFunction),
method("objects", completeObjectInCurrentDatabase),
method("schemas", completeSchemaInCurrentDatabase),
method("dbs", completeDatabase),
Expand Down Expand Up @@ -96,41 +96,27 @@ var compNotQualProcRe = regexp.MustCompile(`[^.](i'|_)`)
// A qualified possible builtin name.
var compMaybeQualProcRe = regexp.MustCompile(`i\.['_]|i\.i'`)

var compVSchemaRe = regexp.MustCompile(`pg_catalog|crdb_internal|information_schema`)

func completeBuiltin(ctx context.Context, c compengine.Context) (compengine.Rows, error) {
// Complete builtin names:
func completeFunction(ctx context.Context, c compengine.Context) (compengine.Rows, error) {
// Complete function names:
//
// - at whitespace after keywords.
// - after a period, if the identifier before the period is a vschema.
// - after a period.
//
var prefix string
var start, end int
var extraPrefix string
var schemaName string
atWord := c.AtWord()
sketch := c.Sketch()
switch {
case compMaybeQualProcRe.MatchString(sketch) &&
((!atWord && compVSchemaRe.MatchString(c.RelToken(-1).Str)) ||
(atWord && compVSchemaRe.MatchString(c.RelToken(-2).Str))):
case compMaybeQualProcRe.MatchString(sketch):
start = int(c.RelToken(-1).Start)
prefix = c.RelToken(-1).Str + "."
schemaName = c.RelToken(-1).Str
if atWord {
start = int(c.RelToken(-2).Start)
prefix = c.RelToken(-2).Str + "."
}
// crdb has this weird thing where every unqualified built-in "X"
// also exists as "pg_catalog.X". So when we search for
// completions after "pg_catalog.", we can strip that prefix from
// the search. However, we must be careful to add it back in the
// completion results, so that the prefix does not get stripped
// when the completion occurs.
if prefix == "pg_catalog." {
prefix = ""
extraPrefix = "pg_catalog."
schemaName = c.RelToken(-2).Str
}
if atWord {
prefix += c.RelToken(0).Str
prefix = c.RelToken(0).Str
}
end = int(c.RelToken(0).End)

Expand All @@ -152,23 +138,31 @@ func completeBuiltin(ctx context.Context, c compengine.Context) (compengine.Rows
return nil, nil
}

c.Trace("completing for %q (%d,%d)", prefix, start, end)
c.Trace("completing for %q (%d,%d) with schema %q", prefix, start, end, schemaName)
// TODO(knz): use the comment extraction functions from pg_catalog
// instead of crdb_internal. This requires exposing comments for
// built-in functions through pg_catalog.
const query = `
WITH p AS (SELECT DISTINCT proname FROM pg_catalog.pg_proc)
SELECT $4:::STRING || proname || '(' AS completion,
WITH p AS (
SELECT DISTINCT
proname, nspname
FROM pg_catalog.pg_proc
JOIN pg_catalog.pg_namespace n ON n.oid = pronamespace)
SELECT IF(length($4) > 0, pg_catalog.quote_ident($4:::STRING) || '.', '') ||
pg_catalog.quote_ident(proname) || '(' AS completion,
'functions' AS category,
substr(COALESCE((SELECT details
substr(COALESCE((
SELECT details
FROM "".crdb_internal.builtin_functions f2
WHERE f2.function = p.proname
WHERE f2.function = p.proname AND f2.schema = p.nspname
LIMIT 1), ''), e'[^\n]{0,80}') AS description,
$2:::INT AS start,
$3:::INT AS end
FROM p
WHERE left(proname, length($1:::STRING)) = $1:::STRING`
iter, err := c.Query(ctx, query, prefix, start, end, extraPrefix)
WHERE left(proname, length($1:::STRING)) = $1:::STRING
AND ((length($4) > 0 AND $4 = nspname)
OR (length($4) = 0 AND nspname = ANY current_schemas(true)))`
iter, err := c.Query(ctx, query, prefix, start, end, schemaName)
return iter, err
}

Expand Down
Loading

0 comments on commit 83fca32

Please sign in to comment.