From 45e777bde35acd5ed9bbb6da0cac212dc141c558 Mon Sep 17 00:00:00 2001 From: Andrew Werner Date: Tue, 16 Aug 2022 11:23:31 -0400 Subject: [PATCH 1/2] sql/parser: add `sequence_name_list` element for `DROP SEQUENCE` This will hopefully make for better diagrams. Fixes #86059 Release note: None --- docs/generated/sql/bnf/drop_sequence_stmt.bnf | 12 +++---- docs/generated/sql/bnf/drop_table.bnf | 12 +++---- docs/generated/sql/bnf/drop_view.bnf | 8 ++--- docs/generated/sql/bnf/for_locking.bnf | 16 +++++----- docs/generated/sql/bnf/stmt_block.bnf | 23 +++++++++----- pkg/cmd/docgen/diagrams.go | 11 +++---- pkg/sql/parser/sql.y | 31 ++++++++++++------- 7 files changed, 65 insertions(+), 48 deletions(-) diff --git a/docs/generated/sql/bnf/drop_sequence_stmt.bnf b/docs/generated/sql/bnf/drop_sequence_stmt.bnf index 1cd52bdfd48e..212345cf0981 100644 --- a/docs/generated/sql/bnf/drop_sequence_stmt.bnf +++ b/docs/generated/sql/bnf/drop_sequence_stmt.bnf @@ -1,7 +1,7 @@ drop_sequence_stmt ::= - 'DROP' 'SEQUENCE' table_name ( ( ',' table_name ) )* 'CASCADE' - | 'DROP' 'SEQUENCE' table_name ( ( ',' table_name ) )* 'RESTRICT' - | 'DROP' 'SEQUENCE' table_name ( ( ',' table_name ) )* - | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* 'CASCADE' - | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* 'RESTRICT' - | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* + 'DROP' 'SEQUENCE' sequence_name_list 'CASCADE' + | 'DROP' 'SEQUENCE' sequence_name_list 'RESTRICT' + | 'DROP' 'SEQUENCE' sequence_name_list + | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' sequence_name_list 'CASCADE' + | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' sequence_name_list 'RESTRICT' + | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' sequence_name_list diff --git a/docs/generated/sql/bnf/drop_table.bnf b/docs/generated/sql/bnf/drop_table.bnf index dcaf2588e33c..5a713e065da8 100644 --- a/docs/generated/sql/bnf/drop_table.bnf +++ b/docs/generated/sql/bnf/drop_table.bnf @@ -1,7 +1,7 @@ drop_table_stmt ::= - 'DROP' 'TABLE' table_name ( ( ',' table_name ) )* 'CASCADE' - | 'DROP' 'TABLE' table_name ( ( ',' table_name ) )* 'RESTRICT' - | 'DROP' 'TABLE' table_name ( ( ',' table_name ) )* - | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* 'CASCADE' - | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* 'RESTRICT' - | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name ( ( ',' table_name ) )* + 'DROP' 'TABLE' table_name_list 'CASCADE' + | 'DROP' 'TABLE' table_name_list 'RESTRICT' + | 'DROP' 'TABLE' table_name_list + | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name_list 'CASCADE' + | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name_list 'RESTRICT' + | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name_list diff --git a/docs/generated/sql/bnf/drop_view.bnf b/docs/generated/sql/bnf/drop_view.bnf index 654b67e2b7a5..1abb9d84213d 100644 --- a/docs/generated/sql/bnf/drop_view.bnf +++ b/docs/generated/sql/bnf/drop_view.bnf @@ -1,5 +1,5 @@ drop_view_stmt ::= - 'DROP' 'VIEW' ( ( table_name ) ( ( ',' table_name ) )* ) ( 'CASCADE' | 'RESTRICT' | ) - | 'DROP' 'VIEW' 'IF' 'EXISTS' ( ( table_name ) ( ( ',' table_name ) )* ) ( 'CASCADE' | 'RESTRICT' | ) - | 'DROP' 'MATERIALIZED' 'VIEW' ( ( table_name ) ( ( ',' table_name ) )* ) ( 'CASCADE' | 'RESTRICT' | ) - | 'DROP' 'MATERIALIZED' 'VIEW' 'IF' 'EXISTS' ( ( table_name ) ( ( ',' table_name ) )* ) ( 'CASCADE' | 'RESTRICT' | ) + 'DROP' 'VIEW' view_name_list ( 'CASCADE' | 'RESTRICT' | ) + | 'DROP' 'VIEW' 'IF' 'EXISTS' view_name_list ( 'CASCADE' | 'RESTRICT' | ) + | 'DROP' 'MATERIALIZED' 'VIEW' view_name_list ( 'CASCADE' | 'RESTRICT' | ) + | 'DROP' 'MATERIALIZED' 'VIEW' 'IF' 'EXISTS' view_name_list ( 'CASCADE' | 'RESTRICT' | ) diff --git a/docs/generated/sql/bnf/for_locking.bnf b/docs/generated/sql/bnf/for_locking.bnf index 08dc976cfcc3..4e6e557f294a 100644 --- a/docs/generated/sql/bnf/for_locking.bnf +++ b/docs/generated/sql/bnf/for_locking.bnf @@ -1,9 +1,9 @@ for_locking_item ::= - 'FOR' 'UPDATE' 'OF' table_name ( ( ',' table_name ) )* 'SKIP' 'LOCKED' - | 'FOR' 'UPDATE' 'OF' table_name ( ( ',' table_name ) )* 'NOWAIT' - | 'FOR' 'NO' 'KEY' 'UPDATE' 'OF' table_name ( ( ',' table_name ) )* 'SKIP' 'LOCKED' - | 'FOR' 'NO' 'KEY' 'UPDATE' 'OF' table_name ( ( ',' table_name ) )* 'NOWAIT' - | 'FOR' 'SHARE' 'OF' table_name ( ( ',' table_name ) )* 'SKIP' 'LOCKED' - | 'FOR' 'SHARE' 'OF' table_name ( ( ',' table_name ) )* 'NOWAIT' - | 'FOR' 'KEY' 'SHARE' 'OF' table_name ( ( ',' table_name ) )* 'SKIP' 'LOCKED' - | 'FOR' 'KEY' 'SHARE' 'OF' table_name ( ( ',' table_name ) )* 'NOWAIT' + 'FOR' 'UPDATE' 'OF' table_name_list 'SKIP' 'LOCKED' + | 'FOR' 'UPDATE' 'OF' table_name_list 'NOWAIT' + | 'FOR' 'NO' 'KEY' 'UPDATE' 'OF' table_name_list 'SKIP' 'LOCKED' + | 'FOR' 'NO' 'KEY' 'UPDATE' 'OF' table_name_list 'NOWAIT' + | 'FOR' 'SHARE' 'OF' table_name_list 'SKIP' 'LOCKED' + | 'FOR' 'SHARE' 'OF' table_name_list 'NOWAIT' + | 'FOR' 'KEY' 'SHARE' 'OF' table_name_list 'SKIP' 'LOCKED' + | 'FOR' 'KEY' 'SHARE' 'OF' table_name_list 'NOWAIT' diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index b77cd4e5e164..a4f2fb8bd048 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -1764,14 +1764,14 @@ drop_table_stmt ::= | 'DROP' 'TABLE' 'IF' 'EXISTS' table_name_list opt_drop_behavior drop_view_stmt ::= - 'DROP' 'VIEW' table_name_list opt_drop_behavior - | 'DROP' 'VIEW' 'IF' 'EXISTS' table_name_list opt_drop_behavior - | 'DROP' 'MATERIALIZED' 'VIEW' table_name_list opt_drop_behavior - | 'DROP' 'MATERIALIZED' 'VIEW' 'IF' 'EXISTS' table_name_list opt_drop_behavior + 'DROP' 'VIEW' view_name_list opt_drop_behavior + | 'DROP' 'VIEW' 'IF' 'EXISTS' view_name_list opt_drop_behavior + | 'DROP' 'MATERIALIZED' 'VIEW' view_name_list opt_drop_behavior + | 'DROP' 'MATERIALIZED' 'VIEW' 'IF' 'EXISTS' view_name_list opt_drop_behavior drop_sequence_stmt ::= - 'DROP' 'SEQUENCE' table_name_list opt_drop_behavior - | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' table_name_list opt_drop_behavior + 'DROP' 'SEQUENCE' sequence_name_list opt_drop_behavior + | 'DROP' 'SEQUENCE' 'IF' 'EXISTS' sequence_name_list opt_drop_behavior drop_schema_stmt ::= 'DROP' 'SCHEMA' schema_name_list opt_drop_behavior @@ -2479,7 +2479,13 @@ table_index_name_list ::= ( table_index_name ) ( ( ',' table_index_name ) )* table_name_list ::= - ( table_name ) ( ( ',' table_name ) )* + db_object_name_list + +view_name_list ::= + db_object_name_list + +sequence_name_list ::= + db_object_name_list non_reserved_word ::= 'identifier' @@ -3034,6 +3040,9 @@ only_signed_fconst ::= '+' 'FCONST' | '-' 'FCONST' +db_object_name_list ::= + ( db_object_name ) ( ( ',' db_object_name ) )* + scrub_option ::= 'INDEX' 'ALL' | 'INDEX' '(' name_list ')' diff --git a/pkg/cmd/docgen/diagrams.go b/pkg/cmd/docgen/diagrams.go index 0422d3e02fd5..c30238bc116f 100644 --- a/pkg/cmd/docgen/diagrams.go +++ b/pkg/cmd/docgen/diagrams.go @@ -611,7 +611,7 @@ var specs = []stmtSpec{ { name: "for_locking", stmt: "for_locking_item", - inline: []string{"for_locking_strength", "opt_locked_rels", "opt_nowait_or_skip", "table_name_list"}, + inline: []string{"for_locking_strength", "opt_locked_rels", "opt_nowait_or_skip"}, }, { name: "col_qualification", @@ -844,7 +844,7 @@ var specs = []stmtSpec{ }, { name: "drop_sequence_stmt", - inline: []string{"table_name_list", "opt_drop_behavior"}, + inline: []string{"opt_drop_behavior"}, unlink: []string{"sequence_name"}, }, { @@ -855,24 +855,23 @@ var specs = []stmtSpec{ }, { name: "drop_stmt", - inline: []string{"table_name_list", "drop_ddl_stmt"}, + inline: []string{"drop_ddl_stmt"}, }, { name: "drop_table", stmt: "drop_table_stmt", - inline: []string{"opt_drop_behavior", "table_name_list"}, + inline: []string{"opt_drop_behavior"}, match: []*regexp.Regexp{regexp.MustCompile("'DROP' 'TABLE'")}, }, { name: "drop_type", stmt: "drop_type_stmt", - inline: []string{"table_name_list"}, replace: map[string]string{"opt_drop_behavior": ""}, }, { name: "drop_view", stmt: "drop_view_stmt", - inline: []string{"opt_drop_behavior", "table_name_list"}, + inline: []string{"opt_drop_behavior"}, nosplit: true, }, { diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index a66d9e440473..4da5f50adbbf 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -1351,7 +1351,7 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %type from_clause %type from_list rowsfrom_list opt_from_list %type table_pattern_list -%type table_name_list opt_locked_rels +%type db_object_name_list table_name_list view_name_list sequence_name_list opt_locked_rels %type expr_list opt_expr_list tuple1_ambiguous_values tuple1_unambiguous_values %type <*tree.Tuple> expr_tuple1_ambiguous expr_tuple_unambiguous %type attrs @@ -4824,15 +4824,15 @@ drop_ddl_stmt: // %Text: DROP [MATERIALIZED] VIEW [IF EXISTS] [, ...] [CASCADE | RESTRICT] // %SeeAlso: WEBDOCS/drop-index.html drop_view_stmt: - DROP VIEW table_name_list opt_drop_behavior + DROP VIEW view_name_list opt_drop_behavior { $$.val = &tree.DropView{Names: $3.tableNames(), IfExists: false, DropBehavior: $4.dropBehavior()} } -| DROP VIEW IF EXISTS table_name_list opt_drop_behavior +| DROP VIEW IF EXISTS view_name_list opt_drop_behavior { $$.val = &tree.DropView{Names: $5.tableNames(), IfExists: true, DropBehavior: $6.dropBehavior()} } -| DROP MATERIALIZED VIEW table_name_list opt_drop_behavior +| DROP MATERIALIZED VIEW view_name_list opt_drop_behavior { $$.val = &tree.DropView{ Names: $4.tableNames(), @@ -4841,7 +4841,7 @@ drop_view_stmt: IsMaterialized: true, } } -| DROP MATERIALIZED VIEW IF EXISTS table_name_list opt_drop_behavior +| DROP MATERIALIZED VIEW IF EXISTS view_name_list opt_drop_behavior { $$.val = &tree.DropView{ Names: $6.tableNames(), @@ -4857,11 +4857,11 @@ drop_view_stmt: // %Text: DROP SEQUENCE [IF EXISTS] [, ...] [CASCADE | RESTRICT] // %SeeAlso: DROP drop_sequence_stmt: - DROP SEQUENCE table_name_list opt_drop_behavior + DROP SEQUENCE sequence_name_list opt_drop_behavior { $$.val = &tree.DropSequence{Names: $3.tableNames(), IfExists: false, DropBehavior: $4.dropBehavior()} } -| DROP SEQUENCE IF EXISTS table_name_list opt_drop_behavior +| DROP SEQUENCE IF EXISTS sequence_name_list opt_drop_behavior { $$.val = &tree.DropSequence{Names: $5.tableNames(), IfExists: true, DropBehavior: $6.dropBehavior()} } @@ -5005,18 +5005,27 @@ drop_role_stmt: } | DROP role_or_group_or_user error // SHOW HELP: DROP ROLE -table_name_list: - table_name +db_object_name_list: + db_object_name { name := $1.unresolvedObjectName().ToTableName() $$.val = tree.TableNames{name} } -| table_name_list ',' table_name +| db_object_name_list ',' db_object_name { name := $3.unresolvedObjectName().ToTableName() $$.val = append($1.tableNames(), name) } +table_name_list: + db_object_name_list + +sequence_name_list: + db_object_name_list + +view_name_list: + db_object_name_list + // %Help: ANALYZE - collect table statistics // %Category: Misc // %Text: @@ -7225,7 +7234,7 @@ show_create_stmt: /* SKIP DOC */ $$.val = &tree.ShowCreate{Mode: tree.ShowCreateModeView, Name: $4.unresolvedObjectName()} } -| SHOW CREATE SEQUENCE table_name +| SHOW CREATE SEQUENCE sequence_name { /* SKIP DOC */ $$.val = &tree.ShowCreate{Mode: tree.ShowCreateModeSequence, Name: $4.unresolvedObjectName()} From 41e20ce597c15d603b848c000b369bfd923fb9b3 Mon Sep 17 00:00:00 2001 From: Aditya Maru Date: Mon, 15 Aug 2022 14:06:23 -0400 Subject: [PATCH 2/2] externalconn: support `SHOW CREATE EXTERNAL CONNECTION` This change adds support for `SHOW CREATE EXTERNAL CONNECTION` and `SHOW CREATE ALL EXTERNAL CONNECTIONS` that displays the connection name and the statement used to create the external connection. This displays unredacted information of the underlying resource and is therefore restricted to admin only or by the owner of the external connection object. Note, synthetic privileges do not have a concept of external connections at the moment. So, the operations are limited to admin only until that functionality is added. Informs: #85905 Release note (sql change): Add support for `SHOW CREATE EXTERNAL CONNECTION` and `SHOW CREATE ALL EXTERNAL CONNECTIONS` that displays the connection name and the unredacted query used to create the external connection. This can only be run by users of the admin role today. --- docs/generated/sql/bnf/BUILD.bazel | 1 + .../show_create_external_connections_stmt.bnf | 3 + docs/generated/sql/bnf/show_var.bnf | 1 + docs/generated/sql/bnf/stmt_block.bnf | 6 + .../cloudccl/externalconn/datadriven_test.go | 6 + .../show_create_external_connections | 96 ++++++++++++ .../testdata/show_create_external_connections | 92 +++++++++++ pkg/cloud/externalconn/connection.go | 9 ++ .../externalconn/connectionpb/connection.go | 13 +- pkg/cloud/externalconn/record.go | 16 ++ pkg/gen/bnf.bzl | 1 + pkg/gen/diagrams.bzl | 1 + pkg/gen/docs.bzl | 1 + pkg/sql/BUILD.bazel | 1 + pkg/sql/opaque.go | 3 + pkg/sql/parser/help_test.go | 3 + pkg/sql/parser/sql.y | 25 ++- pkg/sql/sem/tree/show.go | 18 +++ pkg/sql/sem/tree/stmt.go | 12 ++ pkg/sql/show_create_external_connection.go | 145 ++++++++++++++++++ pkg/sql/sqltelemetry/show.go | 41 ++--- 21 files changed, 472 insertions(+), 22 deletions(-) create mode 100644 docs/generated/sql/bnf/show_create_external_connections_stmt.bnf create mode 100644 pkg/ccl/cloudccl/externalconn/testdata/multi-tenant/show_create_external_connections create mode 100644 pkg/ccl/cloudccl/externalconn/testdata/show_create_external_connections create mode 100644 pkg/sql/show_create_external_connection.go diff --git a/docs/generated/sql/bnf/BUILD.bazel b/docs/generated/sql/bnf/BUILD.bazel index ec00f869b9d9..4b3e3390dfaf 100644 --- a/docs/generated/sql/bnf/BUILD.bazel +++ b/docs/generated/sql/bnf/BUILD.bazel @@ -211,6 +211,7 @@ FILES = [ "show_constraints_stmt", "show_create_stmt", "show_create_schedules_stmt", + "show_create_external_connections_stmt", "show_databases_stmt", "show_default_privileges_stmt", "show_enums", diff --git a/docs/generated/sql/bnf/show_create_external_connections_stmt.bnf b/docs/generated/sql/bnf/show_create_external_connections_stmt.bnf new file mode 100644 index 000000000000..ea36cc80d64f --- /dev/null +++ b/docs/generated/sql/bnf/show_create_external_connections_stmt.bnf @@ -0,0 +1,3 @@ +show_create_external_connections_stmt ::= + 'SHOW' 'CREATE' 'ALL' 'EXTERNAL' 'CONNECTIONS' + | 'SHOW' 'CREATE' 'EXTERNAL' 'CONNECTION' string_or_placeholder diff --git a/docs/generated/sql/bnf/show_var.bnf b/docs/generated/sql/bnf/show_var.bnf index 62daa214358f..6e34fe612372 100644 --- a/docs/generated/sql/bnf/show_var.bnf +++ b/docs/generated/sql/bnf/show_var.bnf @@ -4,6 +4,7 @@ show_stmt ::= | show_constraints_stmt | show_create_stmt | show_create_schedules_stmt + | show_create_external_connections_stmt | show_local_or_tenant_csettings_stmt | show_databases_stmt | show_enums_stmt diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index b77cd4e5e164..7bb52e9bc7f3 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -257,6 +257,7 @@ show_stmt ::= | show_constraints_stmt | show_create_stmt | show_create_schedules_stmt + | show_create_external_connections_stmt | show_local_or_tenant_csettings_stmt | show_databases_stmt | show_enums_stmt @@ -777,6 +778,10 @@ show_create_schedules_stmt ::= 'SHOW' 'CREATE' 'ALL' 'SCHEDULES' | 'SHOW' 'CREATE' 'SCHEDULE' a_expr +show_create_external_connections_stmt ::= + 'SHOW' 'CREATE' 'ALL' 'EXTERNAL' 'CONNECTIONS' + | 'SHOW' 'CREATE' 'EXTERNAL' 'CONNECTION' string_or_placeholder + show_local_or_tenant_csettings_stmt ::= show_csettings_stmt | show_csettings_stmt 'FOR' 'TENANT' d_expr @@ -1008,6 +1013,7 @@ unreserved_keyword ::= | 'CONFIGURATIONS' | 'CONFIGURE' | 'CONNECTION' + | 'CONNECTIONS' | 'CONSTRAINTS' | 'CONTROLCHANGEFEED' | 'CONTROLJOB' diff --git a/pkg/ccl/cloudccl/externalconn/datadriven_test.go b/pkg/ccl/cloudccl/externalconn/datadriven_test.go index 810c08b6d323..bbdfc5da2089 100644 --- a/pkg/ccl/cloudccl/externalconn/datadriven_test.go +++ b/pkg/ccl/cloudccl/externalconn/datadriven_test.go @@ -104,6 +104,12 @@ func TestDataDriven(t *testing.T) { } case "query-sql": + if d.HasArg("user") { + var user string + d.ScanArgs(t, "user", &user) + resetToRootUser := externalConnTestCluster.SetSQLDBForUser(tenantID, user) + defer resetToRootUser() + } var rows *gosql.Rows var err error if rows, err = tenant.QueryWithErr(d.Input); err != nil { diff --git a/pkg/ccl/cloudccl/externalconn/testdata/multi-tenant/show_create_external_connections b/pkg/ccl/cloudccl/externalconn/testdata/multi-tenant/show_create_external_connections new file mode 100644 index 000000000000..5333cfbc7a67 --- /dev/null +++ b/pkg/ccl/cloudccl/externalconn/testdata/multi-tenant/show_create_external_connections @@ -0,0 +1,96 @@ +subtest basic-show-create-ec + +initialize tenant=10 +---- + +disable-check-external-storage +---- + +disable-check-kms +---- + +exec-sql +CREATE EXTERNAL CONNECTION nodelocal AS 'nodelocal://1/foo'; +---- + +exec-sql +CREATE EXTERNAL CONNECTION kms AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted'; +---- + +exec-sql +CREATE EXTERNAL CONNECTION kafka AS 'kafka://broker.address.com:9092'; +---- + +query-sql +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +kafka CREATE EXTERNAL CONNECTION 'kafka' AS 'kafka://broker.address.com:9092' +kms CREATE EXTERNAL CONNECTION 'kms' AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted' +nodelocal CREATE EXTERNAL CONNECTION 'nodelocal' AS 'nodelocal://1/foo' + +query-sql +SHOW CREATE EXTERNAL CONNECTION nodelocal +---- +nodelocal CREATE EXTERNAL CONNECTION 'nodelocal' AS 'nodelocal://1/foo' + +query-sql +SHOW CREATE EXTERNAL CONNECTION kms +---- +kms CREATE EXTERNAL CONNECTION 'kms' AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted' + +query-sql +SHOW CREATE EXTERNAL CONNECTION kafka +---- +kafka CREATE EXTERNAL CONNECTION 'kafka' AS 'kafka://broker.address.com:9092' + + +enable-check-external-storage +---- + +enable-check-kms +---- + +subtest end + +subtest owner-or-admin + +# Create an external connection as root, only root should be able to SHOW this object. +exec-sql +CREATE EXTERNAL CONNECTION foo AS 'nodelocal://1/foo' +---- + +exec-sql +CREATE USER testuser +---- + +exec-sql +GRANT SYSTEM EXTERNALCONNECTION TO testuser +---- + +query-sql user=testuser +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +pq: must be admin to run `SHOW CREATE ALL EXTERNAL CONNECTIONS + +query-sql user=testuser +SHOW CREATE EXTERNAL CONNECTION foo +---- +pq: must be admin or owner of the External Connection "foo" + +# Create External Connection where testuser is the owner, they should be able to SHOW this object. +exec-sql user=testuser +CREATE EXTERNAL CONNECTION bar AS 'nodelocal://1/foo' +---- + +query-sql user=testuser +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +pq: must be admin to run `SHOW CREATE ALL EXTERNAL CONNECTIONS + +# TODO(aditymaru): Synthetic privileges do not have a concept of owners. Once they do, testuser will +# be able to run this query successfully since they are the owner of the External Connection object. +# query-sql user=testuser +# SHOW CREATE EXTERNAL CONNECTION bar +# ---- + +subtest end diff --git a/pkg/ccl/cloudccl/externalconn/testdata/show_create_external_connections b/pkg/ccl/cloudccl/externalconn/testdata/show_create_external_connections new file mode 100644 index 000000000000..0022bc84422c --- /dev/null +++ b/pkg/ccl/cloudccl/externalconn/testdata/show_create_external_connections @@ -0,0 +1,92 @@ +subtest basic-show-create-ec + +disable-check-external-storage +---- + +disable-check-kms +---- + +exec-sql +CREATE EXTERNAL CONNECTION nodelocal AS 'nodelocal://1/foo'; +---- + +exec-sql +CREATE EXTERNAL CONNECTION kms AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted'; +---- + +exec-sql +CREATE EXTERNAL CONNECTION kafka AS 'kafka://broker.address.com:9092'; +---- + +query-sql +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +kafka CREATE EXTERNAL CONNECTION 'kafka' AS 'kafka://broker.address.com:9092' +kms CREATE EXTERNAL CONNECTION 'kms' AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted' +nodelocal CREATE EXTERNAL CONNECTION 'nodelocal' AS 'nodelocal://1/foo' + +query-sql +SHOW CREATE EXTERNAL CONNECTION nodelocal +---- +nodelocal CREATE EXTERNAL CONNECTION 'nodelocal' AS 'nodelocal://1/foo' + +query-sql +SHOW CREATE EXTERNAL CONNECTION kms +---- +kms CREATE EXTERNAL CONNECTION 'kms' AS 'gs:///cmk?AUTH=implicit&CREDENTIALS=wont-be-redacted' + +query-sql +SHOW CREATE EXTERNAL CONNECTION kafka +---- +kafka CREATE EXTERNAL CONNECTION 'kafka' AS 'kafka://broker.address.com:9092' + +enable-check-external-storage +---- + +enable-check-kms +---- + +subtest end + +subtest owner-or-admin + +# Create an external connection as root, only root should be able to SHOW this object. +exec-sql +CREATE EXTERNAL CONNECTION foo AS 'nodelocal://1/foo' +---- + +exec-sql +CREATE USER testuser +---- + +exec-sql +GRANT SYSTEM EXTERNALCONNECTION TO testuser +---- + +query-sql user=testuser +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +pq: must be admin to run `SHOW CREATE ALL EXTERNAL CONNECTIONS + +query-sql user=testuser +SHOW CREATE EXTERNAL CONNECTION foo +---- +pq: must be admin or owner of the External Connection "foo" + +# Create External Connection where testuser is the owner, they should be able to SHOW this object. +exec-sql user=testuser +CREATE EXTERNAL CONNECTION bar AS 'nodelocal://1/foo' +---- + +query-sql user=testuser +SHOW CREATE ALL EXTERNAL CONNECTIONS +---- +pq: must be admin to run `SHOW CREATE ALL EXTERNAL CONNECTIONS + +# TODO(aditymaru): Synthetic privileges do not have a concept of owners. Once they do, testuser will +# be able to run this query successfully since they are the owner of the External Connection object. +# query-sql user=testuser +# SHOW CREATE EXTERNAL CONNECTION bar +# ---- + +subtest end diff --git a/pkg/cloud/externalconn/connection.go b/pkg/cloud/externalconn/connection.go index 0cec91885e73..b2ea97a910d4 100644 --- a/pkg/cloud/externalconn/connection.go +++ b/pkg/cloud/externalconn/connection.go @@ -22,6 +22,15 @@ import ( // an External Connection object. This interface should expose read-only // methods that are required to interact with the External Connection object. type ExternalConnection interface { + // UnredactedConnectionStatement returns a `CREATE EXTERNAL CONNECTION` + // statement that is functionally equivalent to the statement that created the + // external connection in the first place. + // + // NB: The returned string will contain unredacted secrets and should not be + // persisted. + UnredactedConnectionStatement() string + // ConnectionName returns the label of the connection. + ConnectionName() string // ConnectionType returns the type of the connection. ConnectionType() connectionpb.ConnectionType // ConnectionProto returns an in-memory representation of the diff --git a/pkg/cloud/externalconn/connectionpb/connection.go b/pkg/cloud/externalconn/connectionpb/connection.go index b680ec56cf31..cde32675f3e6 100644 --- a/pkg/cloud/externalconn/connectionpb/connection.go +++ b/pkg/cloud/externalconn/connectionpb/connection.go @@ -22,6 +22,17 @@ func (d *ConnectionDetails) Type() ConnectionType { case ConnectionProvider_kafka: return TypeStorage default: - panic(errors.AssertionFailedf("ConnectionDetails.Type called on a details with an unknown type: %T", d.Provider.String())) + panic(errors.AssertionFailedf("ConnectionDetails.Type called on a details with an unknown type: %s", d.Provider.String())) + } +} + +// UnredactedURI returns the unredacted URI of the resource represented by the +// External Connection. +func (d *ConnectionDetails) UnredactedURI() string { + switch c := d.Details.(type) { + case *ConnectionDetails_SimpleURI: + return c.SimpleURI.URI + default: + panic(errors.AssertionFailedf("ConnectionDetails.UnredactedURI called on details with an unknown type: %s", d.Provider.String())) } } diff --git a/pkg/cloud/externalconn/record.go b/pkg/cloud/externalconn/record.go index 09e2602a80ad..8defdc879239 100644 --- a/pkg/cloud/externalconn/record.go +++ b/pkg/cloud/externalconn/record.go @@ -140,6 +140,22 @@ func (e *MutableExternalConnection) SetConnectionDetails(details connectionpb.Co e.markDirty("connection_details") } +// ConnectionName returns the connection_name. +func (e *MutableExternalConnection) ConnectionName() string { + return e.rec.ConnectionName +} + +// UnredactedConnectionStatement implements the External Connection interface. +func (e *MutableExternalConnection) UnredactedConnectionStatement() string { + ecNode := &tree.CreateExternalConnection{ + ConnectionLabelSpec: tree.LabelSpec{ + Label: tree.NewDString(e.rec.ConnectionName), + }, + As: tree.NewDString(e.rec.ConnectionDetails.UnredactedURI()), + } + return tree.AsString(ecNode) +} + // datumToNative is a helper to convert tree.Datum into Go native types. We // only care about types stored in the system.external_connections table. func datumToNative(datum tree.Datum) (interface{}, error) { diff --git a/pkg/gen/bnf.bzl b/pkg/gen/bnf.bzl index 6d20f2ec536e..53ad4f3c63c6 100644 --- a/pkg/gen/bnf.bzl +++ b/pkg/gen/bnf.bzl @@ -209,6 +209,7 @@ BNF_SRCS = [ "//docs/generated/sql/bnf:show_cluster_setting.bnf", "//docs/generated/sql/bnf:show_columns_stmt.bnf", "//docs/generated/sql/bnf:show_constraints_stmt.bnf", + "//docs/generated/sql/bnf:show_create_external_connections_stmt.bnf", "//docs/generated/sql/bnf:show_create_schedules_stmt.bnf", "//docs/generated/sql/bnf:show_create_stmt.bnf", "//docs/generated/sql/bnf:show_databases_stmt.bnf", diff --git a/pkg/gen/diagrams.bzl b/pkg/gen/diagrams.bzl index b0c493b96e47..de0cdeddd2f4 100644 --- a/pkg/gen/diagrams.bzl +++ b/pkg/gen/diagrams.bzl @@ -208,6 +208,7 @@ DIAGRAMS_SRCS = [ "//docs/generated/sql/bnf:show_columns.html", "//docs/generated/sql/bnf:show_constraints.html", "//docs/generated/sql/bnf:show_create.html", + "//docs/generated/sql/bnf:show_create_external_connections.html", "//docs/generated/sql/bnf:show_create_schedules.html", "//docs/generated/sql/bnf:show_databases.html", "//docs/generated/sql/bnf:show_default_privileges.html", diff --git a/pkg/gen/docs.bzl b/pkg/gen/docs.bzl index 0d09d9fbfa69..dcd3cce1ef44 100644 --- a/pkg/gen/docs.bzl +++ b/pkg/gen/docs.bzl @@ -221,6 +221,7 @@ DOCS_SRCS = [ "//docs/generated/sql/bnf:show_cluster_setting.bnf", "//docs/generated/sql/bnf:show_columns_stmt.bnf", "//docs/generated/sql/bnf:show_constraints_stmt.bnf", + "//docs/generated/sql/bnf:show_create_external_connections_stmt.bnf", "//docs/generated/sql/bnf:show_create_schedules_stmt.bnf", "//docs/generated/sql/bnf:show_create_stmt.bnf", "//docs/generated/sql/bnf:show_databases_stmt.bnf", diff --git a/pkg/sql/BUILD.bazel b/pkg/sql/BUILD.bazel index c73b696d0784..d1ce2cfca85f 100644 --- a/pkg/sql/BUILD.bazel +++ b/pkg/sql/BUILD.bazel @@ -209,6 +209,7 @@ go_library( "show_cluster_setting.go", "show_create.go", "show_create_clauses.go", + "show_create_external_connection.go", "show_create_schedule.go", "show_fingerprints.go", "show_histogram.go", diff --git a/pkg/sql/opaque.go b/pkg/sql/opaque.go index 41a6b16302e6..ac4251d9ba9a 100644 --- a/pkg/sql/opaque.go +++ b/pkg/sql/opaque.go @@ -244,6 +244,8 @@ func planOpaque(ctx context.Context, p *planner, stmt tree.Statement) (planNode, return p.ShowTenantClusterSetting(ctx, n) case *tree.ShowCreateSchedules: return p.ShowCreateSchedule(ctx, n) + case *tree.ShowCreateExternalConnections: + return p.ShowCreateExternalConnection(ctx, n) case *tree.ShowHistogram: return p.ShowHistogram(ctx, n) case *tree.ShowTableStats: @@ -358,6 +360,7 @@ func init() { &tree.ShowClusterSetting{}, &tree.ShowTenantClusterSetting{}, &tree.ShowCreateSchedules{}, + &tree.ShowCreateExternalConnections{}, &tree.ShowHistogram{}, &tree.ShowTableStats{}, &tree.ShowTraceForSession{}, diff --git a/pkg/sql/parser/help_test.go b/pkg/sql/parser/help_test.go index 570544fb35ea..cf29420c3377 100644 --- a/pkg/sql/parser/help_test.go +++ b/pkg/sql/parser/help_test.go @@ -360,6 +360,9 @@ func TestContextualHelp(t *testing.T) { {`SHOW CREATE SCHEDULE blah ??`, `SHOW CREATE SCHEDULES`}, {`SHOW CREATE ALL SCHEDULES ??`, `SHOW CREATE SCHEDULES`}, + {`SHOW CREATE EXTERNAL CONNECTION blah ??`, `SHOW CREATE EXTERNAL CONNECTIONS`}, + {`SHOW CREATE ALL EXTERNAL CONNECTIONS ??`, `SHOW CREATE EXTERNAL CONNECTIONS`}, + {`SHOW DATABASES ??`, `SHOW DATABASES`}, {`SHOW DEFAULT PRIVILEGES ??`, `SHOW DEFAULT PRIVILEGES`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index a66d9e440473..55ccb1810615 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -850,7 +850,7 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %token CHARACTER CHARACTERISTICS CHECK CLOSE %token CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT %token COMMITTED COMPACT COMPLETE COMPLETIONS CONCAT CONCURRENTLY CONFIGURATION CONFIGURATIONS CONFIGURE -%token CONFLICT CONNECTION CONSTRAINT CONSTRAINTS CONTAINS CONTROLCHANGEFEED CONTROLJOB +%token CONFLICT CONNECTION CONNECTIONS CONSTRAINT CONSTRAINTS CONTAINS CONTROLCHANGEFEED CONTROLJOB %token CONVERSION CONVERT COPY COST COVERING CREATE CREATEDB CREATELOGIN CREATEROLE %token CROSS CSV CUBE CURRENT CURRENT_CATALOG CURRENT_DATE CURRENT_SCHEMA %token CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP @@ -1177,6 +1177,7 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %type show_constraints_stmt %type show_create_stmt %type show_create_schedules_stmt +%type show_create_external_connections_stmt %type show_csettings_stmt show_local_or_tenant_csettings_stmt %type show_databases_stmt %type show_default_privileges_stmt @@ -6150,13 +6151,15 @@ zone_value: // SHOW ROLES, SHOW SCHEMAS, SHOW SEQUENCES, SHOW SESSION, SHOW SESSIONS, // SHOW STATISTICS, SHOW SYNTAX, SHOW TABLES, SHOW TRACE, SHOW TRANSACTION, // SHOW TRANSACTIONS, SHOW TRANSFER, SHOW TYPES, SHOW USERS, SHOW LAST QUERY STATISTICS, -// SHOW SCHEDULES, SHOW LOCALITY, SHOW ZONE CONFIGURATION, SHOW FULL TABLE SCANS +// SHOW SCHEDULES, SHOW LOCALITY, SHOW ZONE CONFIGURATION, SHOW FULL TABLE SCANS, +// SHOW CREATE EXTERNAL CONNECTIONS show_stmt: show_backup_stmt // EXTEND WITH HELP: SHOW BACKUP | show_columns_stmt // EXTEND WITH HELP: SHOW COLUMNS | show_constraints_stmt // EXTEND WITH HELP: SHOW CONSTRAINTS | show_create_stmt // EXTEND WITH HELP: SHOW CREATE | show_create_schedules_stmt // EXTEND WITH HELP: SHOW CREATE SCHEDULES +| show_create_external_connections_stmt // EXTEND WITH HELP: SHOW CREATE EXTERNAL CONNECTIONS | show_local_or_tenant_csettings_stmt // EXTEND WITH HELP: SHOW CLUSTER SETTING | show_databases_stmt // EXTEND WITH HELP: SHOW DATABASES | show_enums_stmt // EXTEND WITH HELP: SHOW ENUMS @@ -7276,6 +7279,23 @@ show_create_schedules_stmt: } | SHOW CREATE SCHEDULE error // SHOW HELP: SHOW CREATE SCHEDULES +// %Help: SHOW CREATE EXTERNAL CONNECTIONS - list CREATE statements for external connections +// %Category: DDL +// %Text: +// SHOW CREATE ALL EXTERNAL CONNECTIONS +// SHOW CREATE EXTERNAL CONNECTION +show_create_external_connections_stmt: + SHOW CREATE ALL EXTERNAL CONNECTIONS + { + $$.val = &tree.ShowCreateExternalConnections{} + } +| SHOW CREATE ALL EXTERNAL CONNECTIONS error // SHOW HELP: SHOW CREATE EXTERNAL CONNECTIONS +| SHOW CREATE EXTERNAL CONNECTION string_or_placeholder + { + $$.val = &tree.ShowCreateExternalConnections{ConnectionLabel: $5.expr()} + } +| SHOW CREATE EXTERNAL CONNECTION error // SHOW HELP: SHOW CREATE EXTERNAL CONNECTIONS + // %Help: SHOW USERS - list defined users // %Category: Priv // %Text: SHOW USERS @@ -15009,6 +15029,7 @@ unreserved_keyword: | CONFIGURATIONS | CONFIGURE | CONNECTION +| CONNECTIONS | CONSTRAINTS | CONTROLCHANGEFEED | CONTROLJOB diff --git a/pkg/sql/sem/tree/show.go b/pkg/sql/sem/tree/show.go index c791b25368f5..51a63ee303f1 100644 --- a/pkg/sql/sem/tree/show.go +++ b/pkg/sql/sem/tree/show.go @@ -969,3 +969,21 @@ func (node *ShowCreateFunction) Format(ctx *FmtCtx) { } var _ Statement = &ShowCreateFunction{} + +// ShowCreateExternalConnections represents a SHOW CREATE EXTERNAL CONNECTION +// statement. +type ShowCreateExternalConnections struct { + ConnectionLabel Expr +} + +// Format implements the NodeFormatter interface. +func (node *ShowCreateExternalConnections) Format(ctx *FmtCtx) { + if node.ConnectionLabel != nil { + ctx.WriteString("SHOW CREATE EXTERNAL CONNECTION ") + ctx.FormatNode(node.ConnectionLabel) + return + } + ctx.Printf("SHOW CREATE ALL EXTERNAL CONNECTIONS") +} + +var _ Statement = &ShowCreateExternalConnections{} diff --git a/pkg/sql/sem/tree/stmt.go b/pkg/sql/sem/tree/stmt.go index ad4664df9bb5..bb0fa47c2d76 100644 --- a/pkg/sql/sem/tree/stmt.go +++ b/pkg/sql/sem/tree/stmt.go @@ -1814,6 +1814,17 @@ func (*ShowCreateFunction) StatementType() StatementType { return TypeDML } // StatementTag returns a short string identifying the type of statement. func (*ShowCreateFunction) StatementTag() string { return "SHOW CREATE FUNCTION" } +// StatementReturnType implements the Statement interface. +func (*ShowCreateExternalConnections) StatementReturnType() StatementReturnType { return Rows } + +// StatementType implements the Statement interface. +func (*ShowCreateExternalConnections) StatementType() StatementType { return TypeDML } + +// StatementTag returns a short string identifying the type of statement. +func (*ShowCreateExternalConnections) StatementTag() string { + return "SHOW CREATE EXTERNAL CONNECTIONS" +} + // StatementReturnType implements the Statement interface. func (*Split) StatementReturnType() StatementReturnType { return Rows } @@ -2104,6 +2115,7 @@ func (n *ShowDatabaseIndexes) String() string { return AsString( func (n *ShowEnums) String() string { return AsString(n) } func (n *ShowFullTableScans) String() string { return AsString(n) } func (n *ShowCreateFunction) String() string { return AsString(n) } +func (n *ShowCreateExternalConnections) String() string { return AsString(n) } func (n *ShowGrants) String() string { return AsString(n) } func (n *ShowHistogram) String() string { return AsString(n) } func (n *ShowSchedules) String() string { return AsString(n) } diff --git a/pkg/sql/show_create_external_connection.go b/pkg/sql/show_create_external_connection.go new file mode 100644 index 000000000000..1ec2204602e0 --- /dev/null +++ b/pkg/sql/show_create_external_connection.go @@ -0,0 +1,145 @@ +// 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 sql + +import ( + "context" + "fmt" + + "github.com/cockroachdb/cockroach/pkg/cloud/externalconn" + "github.com/cockroachdb/cockroach/pkg/security/username" + "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" + "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" + "github.com/cockroachdb/cockroach/pkg/sql/syntheticprivilege" + "github.com/cockroachdb/cockroach/pkg/sql/types" +) + +var showCreateExternalConnectionColumns = colinfo.ResultColumns{ + {Name: "connection_name", Typ: types.String}, + {Name: "create_statement", Typ: types.String}, +} + +func loadExternalConnections( + params runParams, n *tree.ShowCreateExternalConnections, +) ([]externalconn.ExternalConnection, error) { + var connections []externalconn.ExternalConnection + var rows []tree.Datums + + if n.ConnectionLabel != nil { + externalConnectionName, err := params.p.TypeAsString(params.ctx, n.ConnectionLabel, + externalConnectionOp) + if err != nil { + return nil, err + } + name, err := externalConnectionName() + if err != nil { + return nil, err + } + rows = append(rows, tree.Datums{tree.NewDString(name)}) + } else { + datums, _, err := params.ExecCfg().InternalExecutor.QueryBufferedExWithCols( + params.ctx, + "load-external-connections", + params.p.Txn(), sessiondata.InternalExecutorOverride{User: username.NodeUserName()}, + "SELECT connection_name FROM system.external_connections") + if err != nil { + return nil, err + } + rows = append(rows, datums...) + } + + for _, row := range rows { + connectionName := tree.MustBeDString(row[0]) + connection, err := externalconn.LoadExternalConnection(params.ctx, string(connectionName), + params.p.ExecCfg().InternalExecutor, params.p.Txn()) + if err != nil { + return nil, err + } + connections = append(connections, connection) + } + return connections, nil +} + +func (p *planner) ShowCreateExternalConnection( + ctx context.Context, n *tree.ShowCreateExternalConnections, +) (planNode, error) { + var hasPrivileges bool + var err error + if hasPrivileges, err = p.UserHasAdminRole(ctx, p.User()); err != nil { + return nil, err + } + + // If the user is not admin, and is running a `SHOW CREATE EXTERNAL CONNECTION foo` + // check if the user is the owner of the object. + if !hasPrivileges && n.ConnectionLabel != nil { + externalConnectionName, err := p.TypeAsString(ctx, n.ConnectionLabel, externalConnectionOp) + if err != nil { + return nil, err + } + name, err := externalConnectionName() + if err != nil { + return nil, err + } + ecPrivilege := &syntheticprivilege.ExternalConnectionPrivilege{ + ConnectionName: name, + } + isOwner, err := IsOwner(ctx, p, ecPrivilege, p.User()) + if err != nil { + return nil, err + } + if !isOwner { + return nil, pgerror.Newf(pgcode.InsufficientPrivilege, "must be admin or owner of the External Connection %q", name) + } + } else if !hasPrivileges { + return nil, pgerror.New(pgcode.InsufficientPrivilege, "must be admin to run `SHOW CREATE ALL EXTERNAL CONNECTIONS") + } + + sqltelemetry.IncrementShowCounter(sqltelemetry.CreateExternalConnection) + + name := `SHOW CREATE ALL EXTERNAL CONNECTIONS` + if n.ConnectionLabel != nil { + name = fmt.Sprintf(`SHOW CREATE EXTERNAL CONNECTION %s`, n.ConnectionLabel.String()) + } + + return &delayedNode{ + name: name, + columns: showCreateExternalConnectionColumns, + constructor: func(ctx context.Context, p *planner) (planNode, error) { + connections, err := loadExternalConnections( + runParams{ctx: ctx, p: p, extendedEvalCtx: &p.extendedEvalCtx}, n) + if err != nil { + return nil, err + } + + var rows []tree.Datums + for _, conn := range connections { + row := tree.Datums{ + scheduleID: tree.NewDString(conn.ConnectionName()), + createStmt: tree.NewDString(conn.UnredactedConnectionStatement()), + } + rows = append(rows, row) + } + + v := p.newContainerValuesNode(showCreateTableColumns, len(rows)) + for _, row := range rows { + if _, err := v.rows.AddRow(ctx, row); err != nil { + v.Close(ctx) + return nil, err + } + } + return v, nil + }, + }, nil +} diff --git a/pkg/sql/sqltelemetry/show.go b/pkg/sql/sqltelemetry/show.go index fdafb2a9a944..b4145c5a625f 100644 --- a/pkg/sql/sqltelemetry/show.go +++ b/pkg/sql/sqltelemetry/show.go @@ -60,28 +60,31 @@ const ( FullTableScans // SuperRegions represents the SHOW SUPER REGIONS command. SuperRegions + // CreateExternalConnection represents the SHOW CREATE EXTERNAL CONNECTION command. + CreateExternalConnection ) var showTelemetryNameMap = map[ShowTelemetryType]string{ - Ranges: "ranges", - Partitions: "partitions", - Locality: "locality", - Create: "create", - CreateSchedule: "create_schedule", - RangeForRow: "rangeforrow", - Regions: "regions", - RegionsFromCluster: "regions_from_cluster", - RegionsFromDatabase: "regions_from_database", - RegionsFromAllDatabases: "regions_from_all_databases", - SurvivalGoal: "survival_goal", - Queries: "queries", - Indexes: "indexes", - Constraints: "constraints", - Jobs: "jobs", - Roles: "roles", - Schedules: "schedules", - FullTableScans: "full_table_scans", - SuperRegions: "super_regions", + Ranges: "ranges", + Partitions: "partitions", + Locality: "locality", + Create: "create", + CreateSchedule: "create_schedule", + RangeForRow: "rangeforrow", + Regions: "regions", + RegionsFromCluster: "regions_from_cluster", + RegionsFromDatabase: "regions_from_database", + RegionsFromAllDatabases: "regions_from_all_databases", + SurvivalGoal: "survival_goal", + Queries: "queries", + Indexes: "indexes", + Constraints: "constraints", + Jobs: "jobs", + Roles: "roles", + Schedules: "schedules", + FullTableScans: "full_table_scans", + SuperRegions: "super_regions", + CreateExternalConnection: "create_external_connection", } func (s ShowTelemetryType) String() string {