diff --git a/docs/generated/sql/bnf/show_jobs.bnf b/docs/generated/sql/bnf/show_jobs.bnf index 311ad1aa11fb..16a4b3e82990 100644 --- a/docs/generated/sql/bnf/show_jobs.bnf +++ b/docs/generated/sql/bnf/show_jobs.bnf @@ -1,3 +1,4 @@ show_jobs_stmt ::= 'SHOW' 'AUTOMATIC' 'JOBS' | 'SHOW' 'JOBS' + | 'SHOW' 'JOB' iconst64 diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 65cf7e59c4d7..2e2c87164395 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -494,7 +494,10 @@ show_partitions_stmt ::= | 'SHOW' 'PARTITIONS' 'FROM' 'INDEX' table_index_name show_jobs_stmt ::= - 'SHOW' opt_automatic 'JOBS' + 'SHOW' 'AUTOMATIC' 'JOBS' + | 'SHOW' 'JOBS' + | 'SHOW' 'JOBS' select_stmt + | 'SHOW' 'JOB' a_expr show_queries_stmt ::= 'SHOW' opt_cluster 'QUERIES' @@ -1166,10 +1169,6 @@ table_index_name ::= table_name '@' index_name | standalone_index_name -opt_automatic ::= - 'AUTOMATIC' - | - opt_cluster ::= 'CLUSTER' | 'LOCAL' diff --git a/pkg/cmd/docgen/diagrams.go b/pkg/cmd/docgen/diagrams.go index 469b6f56ade0..fb3b8ff18473 100644 --- a/pkg/cmd/docgen/diagrams.go +++ b/pkg/cmd/docgen/diagrams.go @@ -1127,11 +1127,6 @@ var specs = []stmtSpec{ replace: map[string]string{"var_name": "table_name"}, unlink: []string{"table_name"}, }, - { - name: "show_jobs", - stmt: "show_jobs_stmt", - inline: []string{"opt_automatic"}, - }, { name: "show_keys", stmt: "show_stmt", diff --git a/pkg/sql/delegate/show_jobs.go b/pkg/sql/delegate/show_jobs.go index 9e48c279cec7..145091931394 100644 --- a/pkg/sql/delegate/show_jobs.go +++ b/pkg/sql/delegate/show_jobs.go @@ -18,26 +18,34 @@ import ( ) func (d *delegator) delegateShowJobs(n *tree.ShowJobs) (tree.Statement, error) { - var typePredicate string - if n.Automatic { - typePredicate = fmt.Sprintf("job_type = '%s'", jobspb.TypeAutoCreateStats) + const ( + selectClause = `SELECT job_id, job_type, description, statement, user_name, status, + running_status, created, started, finished, modified, + fraction_completed, error, coordinator_id + FROM crdb_internal.jobs` + ) + var typePredicate, whereClause, orderbyClause string + if n.Jobs == nil { + // Display all [only automatic] jobs without selecting specific jobs. + if n.Automatic { + typePredicate = fmt.Sprintf("job_type = '%s'", jobspb.TypeAutoCreateStats) + } else { + typePredicate = fmt.Sprintf( + "(job_type IS NULL OR job_type != '%s')", jobspb.TypeAutoCreateStats, + ) + } + // The query intends to present: + // - first all the running jobs sorted in order of start time, + // - then all completed jobs sorted in order of completion time. + whereClause = fmt.Sprintf( + `WHERE %s AND (finished IS NULL OR finished > now() - '12h':::interval)`, typePredicate) + // The "ORDER BY" clause below exploits the fact that all + // running jobs have finished = NULL. + orderbyClause = `ORDER BY COALESCE(finished, now()) DESC, started DESC` } else { - typePredicate = fmt.Sprintf( - "(job_type != '%s' OR job_type IS NULL)", jobspb.TypeAutoCreateStats, - ) + // Limit the jobs displayed to the select statement in n.Jobs. + whereClause = fmt.Sprintf(`WHERE job_id in (%s)`, n.Jobs.String()) } - // The query intends to present: - // - first all the running jobs sorted in order of start time, - // - then all completed jobs sorted in order of completion time. - // The "ORDER BY" clause below exploits the fact that all - // running jobs have finished = NULL. - return parse(fmt.Sprintf( - `SELECT job_id, job_type, description, statement, user_name, status, running_status, created, - started, finished, modified, fraction_completed, error, coordinator_id - FROM crdb_internal.jobs - WHERE %s - AND (finished IS NULL OR finished > now() - '12h':::interval) - ORDER BY COALESCE(finished, now()) DESC, started DESC`, typePredicate, - )) + return parse(fmt.Sprintf("%s %s %s", selectClause, whereClause, orderbyClause)) } diff --git a/pkg/sql/explain_test.go b/pkg/sql/explain_test.go index 30558e9a6282..079a55286ce7 100644 --- a/pkg/sql/explain_test.go +++ b/pkg/sql/explain_test.go @@ -117,6 +117,7 @@ func TestStatementReuses(t *testing.T) { `SHOW CONSTRAINTS FROM a`, `SHOW DATABASES`, `SHOW INDEXES FROM a`, + `SHOW JOB 1`, `SHOW JOBS`, `SHOW ROLES`, `SHOW SCHEMAS`, diff --git a/pkg/sql/logictest/testdata/planner_test/explain b/pkg/sql/logictest/testdata/planner_test/explain index fc1d9bb472ee..28510db24cfa 100644 --- a/pkg/sql/logictest/testdata/planner_test/explain +++ b/pkg/sql/logictest/testdata/planner_test/explain @@ -118,7 +118,7 @@ sort · · │ order -"coalesce",-started └── render · · └── filter · · - │ filter ((job_type != 'AUTO CREATE STATS') OR (job_type IS NULL)) AND ((finished IS NULL) OR (finished > (now() - '12:00:00'))) + │ filter ((job_type IS NULL) OR (job_type != 'AUTO CREATE STATS')) AND ((finished IS NULL) OR (finished > (now() - '12:00:00'))) └── values · · · size 16 columns, 0 rows diff --git a/pkg/sql/opt/exec/execbuilder/testdata/explain b/pkg/sql/opt/exec/execbuilder/testdata/explain index 3a502a5b0178..08813593541d 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/explain +++ b/pkg/sql/opt/exec/execbuilder/testdata/explain @@ -106,7 +106,7 @@ render · · │ order -column17,-started └── render · · └── filter · · - │ filter ((job_type != 'AUTO CREATE STATS') OR (job_type IS NULL)) AND ((finished IS NULL) OR (finished > (now() - '12:00:00'))) + │ filter ((job_type IS NULL) OR (job_type != 'AUTO CREATE STATS')) AND ((finished IS NULL) OR (finished > (now() - '12:00:00'))) └── virtual table · · · source · diff --git a/pkg/sql/parser/help_test.go b/pkg/sql/parser/help_test.go index f10a79b2d66a..320e09034fbb 100644 --- a/pkg/sql/parser/help_test.go +++ b/pkg/sql/parser/help_test.go @@ -241,6 +241,7 @@ func TestContextualHelp(t *testing.T) { {`SHOW TRACE FOR SESSION ??`, `SHOW TRACE`}, {`SHOW TRACE FOR ??`, `SHOW TRACE`}, + {`SHOW JOB ??`, `SHOW JOBS`}, {`SHOW JOBS ??`, `SHOW JOBS`}, {`SHOW AUTOMATIC JOBS ??`, `SHOW JOBS`}, diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 9ca8104ab35d..67825437eb39 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -377,6 +377,8 @@ func TestParse(t *testing.T) { {`EXPLAIN RESUME JOBS SELECT a`}, {`PAUSE JOBS SELECT a`}, {`EXPLAIN PAUSE JOBS SELECT a`}, + {`SHOW JOBS SELECT a`}, + {`EXPLAIN SHOW JOBS SELECT a`}, {`EXPLAIN SELECT 1`}, {`EXPLAIN EXPLAIN SELECT 1`}, @@ -1746,8 +1748,13 @@ func TestParse2(t *testing.T) { `DEALLOCATE ALL`}, {`CANCEL JOB a`, `CANCEL JOBS VALUES (a)`}, + {`EXPLAIN CANCEL JOB a`, `EXPLAIN CANCEL JOBS VALUES (a)`}, {`RESUME JOB a`, `RESUME JOBS VALUES (a)`}, + {`EXPLAIN RESUME JOB a`, `EXPLAIN RESUME JOBS VALUES (a)`}, {`PAUSE JOB a`, `PAUSE JOBS VALUES (a)`}, + {`EXPLAIN PAUSE JOB a`, `EXPLAIN PAUSE JOBS VALUES (a)`}, + {`SHOW JOB a`, `SHOW JOBS VALUES (a)`}, + {`EXPLAIN SHOW JOB a`, `EXPLAIN SHOW JOBS VALUES (a)`}, {`CANCEL QUERY a`, `CANCEL QUERIES VALUES (a)`}, {`CANCEL QUERY IF EXISTS a`, `CANCEL QUERIES IF EXISTS VALUES (a)`}, {`CANCEL SESSION a`, `CANCEL SESSIONS VALUES (a)`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index e5244b3feaa6..468dbfe5c9ef 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -903,7 +903,7 @@ func newNameFromStr(s string) *tree.Name { %type sub_type %type numeric_only %type alias_clause opt_alias_clause -%type opt_ordinality opt_compact opt_automatic +%type opt_ordinality opt_compact %type <*tree.Order> sortby %type index_elem create_as_param %type table_ref func_table @@ -3513,18 +3513,35 @@ opt_cluster: // %Help: SHOW JOBS - list background jobs // %Category: Misc -// %Text: SHOW [AUTOMATIC] JOBS +// %Text: +// SHOW [AUTOMATIC] JOBS +// SHOW JOB // %SeeAlso: CANCEL JOBS, PAUSE JOBS, RESUME JOBS show_jobs_stmt: - SHOW opt_automatic JOBS + SHOW AUTOMATIC JOBS { - $$.val = &tree.ShowJobs{Automatic: $2.bool()} + $$.val = &tree.ShowJobs{Automatic: true} } -| SHOW opt_automatic JOBS error // SHOW HELP: SHOW JOBS - -opt_automatic: - AUTOMATIC { $$.val = true } -| /* EMPTY */ { $$.val = false } +| SHOW JOBS + { + $$.val = &tree.ShowJobs{Automatic: false} + } +| SHOW AUTOMATIC JOBS error // SHOW HELP: SHOW JOBS +| SHOW JOBS error // SHOW HELP: SHOW JOBS +| SHOW JOBS select_stmt + { + $$.val = &tree.ShowJobs{Jobs: $3.slct()} + } +| SHOW JOBS select_stmt error // SHOW HELP: SHOW JOBS +| SHOW JOB a_expr + { + $$.val = &tree.ShowJobs{ + Jobs: &tree.Select{ + Select: &tree.ValuesClause{Rows: []tree.Exprs{tree.Exprs{$3.expr()}}}, + }, + } + } +| SHOW JOB error // SHOW HELP: SHOW JOBS // %Help: SHOW TRACE - display an execution trace // %Category: Misc diff --git a/pkg/sql/sem/tree/show.go b/pkg/sql/sem/tree/show.go index 86b2e8329f42..2a96ead3a1d0 100644 --- a/pkg/sql/sem/tree/show.go +++ b/pkg/sql/sem/tree/show.go @@ -193,6 +193,8 @@ func (node *ShowQueries) Format(ctx *FmtCtx) { // ShowJobs represents a SHOW JOBS statement type ShowJobs struct { + // If non-nil, a select statement that provides the job ids to be shown. + Jobs *Select // If Automatic is true, show only automatically-generated jobs such // as automatic CREATE STATISTICS jobs. If Automatic is false, show // only non-automatically-generated jobs. @@ -206,6 +208,10 @@ func (node *ShowJobs) Format(ctx *FmtCtx) { ctx.WriteString("AUTOMATIC ") } ctx.WriteString("JOBS") + if node.Jobs != nil { + ctx.WriteString(" ") + ctx.FormatNode(node.Jobs) + } } // ShowSessions represents a SHOW SESSIONS statement diff --git a/pkg/sql/show_test.go b/pkg/sql/show_test.go index d0a7706e9b7e..54fa84eff3b4 100644 --- a/pkg/sql/show_test.go +++ b/pkg/sql/show_test.go @@ -1039,6 +1039,36 @@ func TestShowAutomaticJobs(t *testing.T) { } var out row + + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOB 1]`).Scan(&out.id, &out.typ) + if out.id != 1 || out.typ != "CREATE STATS" { + t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s", + 1, "CREATE STATS", out.id, out.typ) + } + + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOBS SELECT 1]`).Scan(&out.id, &out.typ) + if out.id != 1 || out.typ != "CREATE STATS" { + t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s", + 1, "CREATE STATS", out.id, out.typ) + } + + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOBS (SELECT 1)]`).Scan(&out.id, &out.typ) + if out.id != 1 || out.typ != "CREATE STATS" { + t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s", + 1, "CREATE STATS", out.id, out.typ) + } + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOB 2]`).Scan(&out.id, &out.typ) + if out.id != 2 || out.typ != "AUTO CREATE STATS" { + t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s", + 2, "AUTO CREATE STATS", out.id, out.typ) + } + + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOBS SELECT 2]`).Scan(&out.id, &out.typ) + if out.id != 2 || out.typ != "AUTO CREATE STATS" { + t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s", + 2, "AUTO CREATE STATS", out.id, out.typ) + } + sqlDB.QueryRow(t, `SELECT job_id, job_type FROM [SHOW JOBS]`).Scan(&out.id, &out.typ) if out.id != 1 || out.typ != "CREATE STATS" { t.Fatalf("Expected id:%d and type:%s but found id:%d and type:%s",