diff --git a/pkg/sql/catalog/colinfo/result_columns.go b/pkg/sql/catalog/colinfo/result_columns.go index 0754cde5d927..6c911d0ba7bd 100644 --- a/pkg/sql/catalog/colinfo/result_columns.go +++ b/pkg/sql/catalog/colinfo/result_columns.go @@ -351,16 +351,18 @@ var Ranges = append( // The following columns are computed by RangesExtraRenders below. ResultColumn{Name: "lease_holder", Typ: types.Int}, ResultColumn{Name: "range_size", Typ: types.Int}, + ResultColumn{Name: "errors", Typ: types.String}, ) // RangesExtraRenders describes the extra projections in // crdb_internal.ranges not included in crdb_internal.ranges_no_leases. const RangesExtraRenders = ` - crdb_internal.lease_holder(start_key) AS lease_holder, - (crdb_internal.range_stats(start_key)->>'key_bytes')::INT + - (crdb_internal.range_stats(start_key)->>'val_bytes')::INT + - coalesce((crdb_internal.range_stats(start_key)->>'range_key_bytes')::INT, 0) + - coalesce((crdb_internal.range_stats(start_key)->>'range_val_bytes')::INT, 0) AS range_size + (crdb_internal.lease_holder_with_errors(start_key)->>'Leaseholder')::INT AS lease_holder, + (crdb_internal.range_stats_with_errors(start_key)->'RangeStats'->>'key_bytes')::INT + + (crdb_internal.range_stats_with_errors(start_key)->'RangeStats'->>'val_bytes')::INT + + coalesce((crdb_internal.range_stats_with_errors(start_key)->'RangeStats'->>'range_key_bytes')::INT, 0) + + coalesce((crdb_internal.range_stats_with_errors(start_key)->'RangeStats'->>'range_val_bytes')::INT, 0) AS range_size, + concat(crdb_internal.lease_holder_with_errors(start_key)->>'Error', ' ', crdb_internal.range_stats_with_errors(start_key)->>'Error') AS errors ` // IdentifySystemColumns is the schema for IDENTIFY_SYSTEM. diff --git a/pkg/sql/colexec/BUILD.bazel b/pkg/sql/colexec/BUILD.bazel index 53a1f439c068..7d8decab98de 100644 --- a/pkg/sql/colexec/BUILD.bazel +++ b/pkg/sql/colexec/BUILD.bazel @@ -75,6 +75,7 @@ go_library( "//pkg/sql/sem/tree", "//pkg/sql/sqltelemetry", # keep "//pkg/sql/types", + "//pkg/storage/enginepb", "//pkg/util/buildutil", "//pkg/util/duration", # keep "//pkg/util/encoding", # keep diff --git a/pkg/sql/colexec/builtin_funcs.go b/pkg/sql/colexec/builtin_funcs.go index ca3e2f597963..4631125af047 100644 --- a/pkg/sql/colexec/builtin_funcs.go +++ b/pkg/sql/colexec/builtin_funcs.go @@ -133,7 +133,17 @@ func NewBuiltinFunctionOperator( ) } return newRangeStatsOperator( - evalCtx.RangeStatsFetcher, allocator, argumentCols[0], outputIdx, input, + evalCtx.RangeStatsFetcher, allocator, argumentCols[0], outputIdx, input, false, /* withErrors */ + ) + case tree.CrdbInternalRangeStatsWithErrors: + if len(argumentCols) != 1 { + return nil, errors.AssertionFailedf( + "expected 1 input column to crdb_internal.range_stats, got %d", + len(argumentCols), + ) + } + return newRangeStatsOperator( + evalCtx.RangeStatsFetcher, allocator, argumentCols[0], outputIdx, input, true, /* withErrors */ ) default: return &defaultBuiltinFuncOperator{ diff --git a/pkg/sql/colexec/range_stats.go b/pkg/sql/colexec/range_stats.go index 345afe3d9844..4d2638232ef0 100644 --- a/pkg/sql/colexec/range_stats.go +++ b/pkg/sql/colexec/range_stats.go @@ -14,6 +14,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/colexecop" "github.com/cockroachdb/cockroach/pkg/sql/colmem" "github.com/cockroachdb/cockroach/pkg/sql/sem/eval" + "github.com/cockroachdb/cockroach/pkg/storage/enginepb" "github.com/cockroachdb/cockroach/pkg/util/json" "github.com/cockroachdb/errors" ) @@ -24,6 +25,9 @@ type rangeStatsOperator struct { allocator *colmem.Allocator argumentCol int outputIdx int + // withErrors defines if the operator includes any encountered errors in the + // returned JSON struct. If true, these errors will not fail the query. + withErrors bool } var _ colexecop.Operator = (*rangeStatsOperator)(nil) @@ -38,6 +42,7 @@ func newRangeStatsOperator( argumentCol int, outputIdx int, input colexecop.Operator, + withErrors bool, ) (colexecop.Operator, error) { return &rangeStatsOperator{ OneInputHelper: colexecop.MakeOneInputHelper(input), @@ -45,6 +50,7 @@ func newRangeStatsOperator( argumentCol: argumentCol, outputIdx: outputIdx, fetcher: fetcher, + withErrors: withErrors, }, nil } @@ -124,17 +130,36 @@ func (r *rangeStatsOperator) Next() coldata.Batch { // keys plus some constant multiple. // TODO(yuzefovich): add unit tests that use the RunTests test // harness. - res, err := r.fetcher.RangeStats(r.Ctx, keys...) - if err != nil { - colexecerror.ExpectedError(err) + res, rangeStatsErr := r.fetcher.RangeStats(r.Ctx, keys...) + if rangeStatsErr != nil && !r.withErrors { + colexecerror.ExpectedError(rangeStatsErr) } - if len(res) != len(keys) { - colexecerror.InternalError(errors.AssertionFailedf( - "unexpected number of RangeStats responses %d: %d expected", len(res), len(keys), - )) + if len(res) != len(keys) && !r.withErrors { + colexecerror.InternalError( + errors.AssertionFailedf( + "unexpected number of RangeStats responses %d: %d expected", len(res), len(keys), + ), + ) } for i, outputIdx := range keysOutputIdx { - jsonStr, err := gojson.Marshal(&res[i].MVCCStats) + rswe := &rangeStatsWithErrors{} + if rangeStatsErr != nil { + rswe.Error = rangeStatsErr.Error() + } + // Not all keys from the keysOutputIdx are guaranteed to be + // present in res (e.g. some may be missing if there were errors + // in fetcher.RangeStats and r.withErrors = true). + if i < len(res) { + rswe.RangeStats = &res[i].MVCCStats + } + var jsonStr []byte + var err error + if r.withErrors { + jsonStr, err = gojson.Marshal(rswe) + } else { + jsonStr, err = gojson.Marshal(&res[i].MVCCStats) + } + if err != nil { colexecerror.ExpectedError(err) } @@ -144,6 +169,12 @@ func (r *rangeStatsOperator) Next() coldata.Batch { } jsonOutput.Set(outputIdx, jsonDatum) } - }) + }, + ) return batch } + +type rangeStatsWithErrors struct { + RangeStats *enginepb.MVCCStats + Error string +} diff --git a/pkg/sql/delegate/show_ranges.go b/pkg/sql/delegate/show_ranges.go index 3cd25320dfb2..776b1235c959 100644 --- a/pkg/sql/delegate/show_ranges.go +++ b/pkg/sql/delegate/show_ranges.go @@ -707,6 +707,10 @@ all_span_stats AS ( if colinfo.Ranges[i].Name == "lease_holder" { continue } + // Skip the errors column; it's used for internal purposes. + if colinfo.Ranges[i].Name == "errors" { + continue + } fmt.Fprintf(&buf, ",\n %s", tree.NameString(colinfo.Ranges[i].Name)) } buf.WriteString(",\n span_stats") diff --git a/pkg/sql/logictest/testdata/logic_test/crdb_internal b/pkg/sql/logictest/testdata/logic_test/crdb_internal index a182dc183904..cfe9c6bcfd7f 100644 --- a/pkg/sql/logictest/testdata/logic_test/crdb_internal +++ b/pkg/sql/logictest/testdata/logic_test/crdb_internal @@ -531,10 +531,10 @@ SELECT * FROM crdb_internal.node_inflight_trace_spans WHERE span_id < 0 ---- trace_id parent_span_id span_id goroutine_id finished start_time duration operation -query ITTTTITTTTTTI colnames +query ITTTTITTTTTTIT colnames SELECT * FROM crdb_internal.ranges WHERE range_id < 0 ---- -range_id start_key start_pretty end_key end_pretty replicas replica_localities voting_replicas non_voting_replicas learner_replicas split_enforced_until lease_holder range_size +range_id start_key start_pretty end_key end_pretty replicas replica_localities voting_replicas non_voting_replicas learner_replicas split_enforced_until lease_holder range_size errors query ITTTTTTTTTT colnames SELECT * FROM crdb_internal.ranges_no_leases WHERE range_id < 0 diff --git a/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog b/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog index ab1c65f9b63e..5c50f6426df4 100644 --- a/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog +++ b/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog @@ -448,7 +448,7 @@ SELECT id, strip_volatile(descriptor) FROM crdb_internal.kv_catalog_descriptor O 4294967233 {"table": {"columns": [{"id": 1, "name": "span_idx", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "message_idx", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 3, "name": "timestamp", "type": {"family": "TimestampTZFamily", "oid": 1184}}, {"id": 4, "name": "duration", "nullable": true, "type": {"family": "IntervalFamily", "intervalDurationField": {}, "oid": 1186}}, {"id": 5, "name": "operation", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 6, "name": "loc", "type": {"family": "StringFamily", "oid": 25}}, {"id": 7, "name": "tag", "type": {"family": "StringFamily", "oid": 25}}, {"id": 8, "name": "message", "type": {"family": "StringFamily", "oid": 25}}, {"id": 9, "name": "age", "type": {"family": "IntervalFamily", "intervalDurationField": {}, "oid": 1186}}], "formatVersion": 3, "id": 4294967233, "name": "session_trace", "nextColumnId": 10, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} 4294967234 {"table": {"columns": [{"id": 1, "name": "table_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "parent_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 3, "name": "name", "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "type", "type": {"family": "StringFamily", "oid": 25}}, {"id": 5, "name": "target_id", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "target_name", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 7, "name": "state", "type": {"family": "StringFamily", "oid": 25}}, {"id": 8, "name": "direction", "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 4294967234, "name": "schema_changes", "nextColumnId": 9, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} 4294967235 {"table": {"columns": [{"id": 1, "name": "node_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "component", "type": {"family": "StringFamily", "oid": 25}}, {"id": 3, "name": "field", "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "value", "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 4294967235, "name": "node_runtime_info", "nextColumnId": 5, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} -4294967236 {"table": {"columns": [{"id": 1, "name": "range_id", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "start_key", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"id": 3, "name": "start_pretty", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "end_key", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"id": 5, "name": "end_pretty", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 6, "name": "replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 7, "name": "replica_localities", "nullable": true, "type": {"arrayContents": {"family": "StringFamily", "oid": 25}, "arrayElemType": "StringFamily", "family": "ArrayFamily", "oid": 1009}}, {"id": 8, "name": "voting_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 9, "name": "non_voting_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 10, "name": "learner_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 11, "name": "split_enforced_until", "nullable": true, "type": {"family": "TimestampFamily", "oid": 1114}}, {"id": 12, "name": "lease_holder", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 13, "name": "range_size", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 4294967236, "name": "ranges", "nextColumnId": 14, "nextConstraintId": 1, "nextMutationId": 1, "primaryIndex": {"foreignKey": {}, "geoConfig": {}, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1", "viewQuery": "SELECT range_id, start_key, start_pretty, end_key, end_pretty, replicas, replica_localities, voting_replicas, non_voting_replicas, learner_replicas, split_enforced_until, crdb_internal.lease_holder(start_key) AS lease_holder, (((crdb_internal.range_stats(start_key)->>'key_bytes')::INT8 + (crdb_internal.range_stats(start_key)->>'val_bytes')::INT8) + COALESCE((crdb_internal.range_stats(start_key)->>'range_key_bytes')::INT8, 0)) + COALESCE((crdb_internal.range_stats(start_key)->>'range_val_bytes')::INT8, 0) AS range_size FROM crdb_internal.ranges_no_leases"}} +4294967236 {"table": {"columns": [{"id": 1, "name": "range_id", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "start_key", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"id": 3, "name": "start_pretty", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "end_key", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"id": 5, "name": "end_pretty", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 6, "name": "replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 7, "name": "replica_localities", "nullable": true, "type": {"arrayContents": {"family": "StringFamily", "oid": 25}, "arrayElemType": "StringFamily", "family": "ArrayFamily", "oid": 1009}}, {"id": 8, "name": "voting_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 9, "name": "non_voting_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 10, "name": "learner_replicas", "nullable": true, "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 11, "name": "split_enforced_until", "nullable": true, "type": {"family": "TimestampFamily", "oid": 1114}}, {"id": 12, "name": "lease_holder", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 13, "name": "range_size", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 14, "name": "errors", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 4294967236, "name": "ranges", "nextColumnId": 15, "nextConstraintId": 1, "nextMutationId": 1, "primaryIndex": {"foreignKey": {}, "geoConfig": {}, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1", "viewQuery": "SELECT range_id, start_key, start_pretty, end_key, end_pretty, replicas, replica_localities, voting_replicas, non_voting_replicas, learner_replicas, split_enforced_until, (crdb_internal.lease_holder_with_errors(start_key)->>'Leaseholder')::INT8 AS lease_holder, ((((crdb_internal.range_stats_with_errors(start_key)->'RangeStats')->>'key_bytes')::INT8 + ((crdb_internal.range_stats_with_errors(start_key)->'RangeStats')->>'val_bytes')::INT8) + COALESCE(((crdb_internal.range_stats_with_errors(start_key)->'RangeStats')->>'range_key_bytes')::INT8, 0)) + COALESCE(((crdb_internal.range_stats_with_errors(start_key)->'RangeStats')->>'range_val_bytes')::INT8, 0) AS range_size, concat(crdb_internal.lease_holder_with_errors(start_key)->>'Error', ' ', crdb_internal.range_stats_with_errors(start_key)->>'Error') AS errors FROM crdb_internal.ranges_no_leases"}} 4294967237 {"table": {"columns": [{"id": 1, "name": "range_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "start_key", "type": {"family": "BytesFamily", "oid": 17}}, {"id": 3, "name": "start_pretty", "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "end_key", "type": {"family": "BytesFamily", "oid": 17}}, {"id": 5, "name": "end_pretty", "type": {"family": "StringFamily", "oid": 25}}, {"id": 6, "name": "replicas", "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 7, "name": "replica_localities", "type": {"arrayContents": {"family": "StringFamily", "oid": 25}, "arrayElemType": "StringFamily", "family": "ArrayFamily", "oid": 1009}}, {"id": 8, "name": "voting_replicas", "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 9, "name": "non_voting_replicas", "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 10, "name": "learner_replicas", "type": {"arrayContents": {"family": "IntFamily", "oid": 20, "width": 64}, "arrayElemType": "IntFamily", "family": "ArrayFamily", "oid": 1016, "width": 64}}, {"id": 11, "name": "split_enforced_until", "nullable": true, "type": {"family": "TimestampFamily", "oid": 1114}}], "formatVersion": 3, "id": 4294967237, "name": "ranges_no_leases", "nextColumnId": 12, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} 4294967238 {"table": {"columns": [{"id": 1, "name": "table_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "index_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 3, "name": "parent_name", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 4, "name": "name", "type": {"family": "StringFamily", "oid": 25}}, {"id": 5, "name": "columns", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "column_names", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 7, "name": "list_value", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 8, "name": "range_value", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 9, "name": "zone_id", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 10, "name": "subzone_id", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 4294967238, "name": "partitions", "nextColumnId": 11, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} 4294967239 {"table": {"columns": [{"id": 1, "name": "node_id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "application_name", "type": {"family": "StringFamily", "oid": 25}}, {"id": 3, "name": "txn_count", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 4, "name": "txn_time_avg_sec", "type": {"family": "FloatFamily", "oid": 701, "width": 64}}, {"id": 5, "name": "txn_time_var_sec", "type": {"family": "FloatFamily", "oid": 701, "width": 64}}, {"id": 6, "name": "committed_count", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 7, "name": "implicit_count", "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 4294967239, "name": "node_txn_stats", "nextColumnId": 8, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "primaryIndex": {"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "partitioning": {}, "sharded": {}}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "public"}], "version": 3}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 4294967295, "version": "1"}} diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index ea5f6d9afb24..a1f19fb50322 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -5850,6 +5850,54 @@ SELECT }, ), + // Fetches the corresponding lease_holder for the request key. If an error + // occurs, the query still succeeds and the error is included in the output. + "crdb_internal.lease_holder_with_errors": makeBuiltin( + tree.FunctionProperties{ + Category: builtinconstants.CategorySystemInfo, + }, + tree.Overload{ + Types: tree.ParamTypes{{Name: "key", Typ: types.Bytes}}, + ReturnType: tree.FixedReturnType(types.Jsonb), + Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) { + if evalCtx.Txn == nil { // can occur during backfills + return nil, pgerror.Newf(pgcode.FeatureNotSupported, + "cannot use crdb_internal.lease_holder_with_errors in this context") + } + key := []byte(tree.MustBeDBytes(args[0])) + b := evalCtx.Txn.DB().NewBatch() + b.AddRawRequest(&kvpb.LeaseInfoRequest{ + RequestHeader: kvpb.RequestHeader{ + Key: key, + }, + }) + type leaseholderAndError struct { + Leaseholder roachpb.StoreID + Error string + } + lhae := &leaseholderAndError{} + if err := evalCtx.Txn.DB().Run(ctx, b); err != nil { + lhae.Error = err.Error() + } else { + resp := b.RawResponse().Responses[0].GetInner().(*kvpb.LeaseInfoResponse) + lhae.Leaseholder = resp.Lease.Replica.StoreID + } + + jsonStr, err := gojson.Marshal(lhae) + if err != nil { + return nil, err + } + jsonDatum, err := tree.ParseDJSON(string(jsonStr)) + if err != nil { + return nil, err + } + return jsonDatum, nil + }, + Info: "This function is used to fetch the leaseholder corresponding to a request key", + Volatility: volatility.Volatile, + }, + ), + "crdb_internal.trim_tenant_prefix": makeBuiltin( tree.FunctionProperties{ Category: builtinconstants.CategoryMultiTenancy, @@ -6157,6 +6205,27 @@ SELECT }, ), + // Return statistics about a range. + "crdb_internal.range_stats_with_errors": makeBuiltin( + tree.FunctionProperties{ + Category: builtinconstants.CategorySystemInfo, + }, + tree.Overload{ + Types: tree.ParamTypes{ + {Name: "key", Typ: types.Bytes}, + }, + SpecializedVecBuiltin: tree.CrdbInternalRangeStatsWithErrors, + ReturnType: tree.FixedReturnType(types.Jsonb), + Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) { + // This function is a placeholder and will never be called because + // CrdbInternalRangeStatsWithErrors overrides it. + return tree.DNull, nil + }, + Info: "This function is used to retrieve range statistics information as a JSON object.", + Volatility: volatility.Volatile, + }, + ), + // Returns a namespace_id based on parentID and a given name. // Allows a non-admin to query the system.namespace table, but performs // the relevant permission checks to ensure secure access. diff --git a/pkg/sql/sem/builtins/fixed_oids.go b/pkg/sql/sem/builtins/fixed_oids.go index 110ee238a5ff..6b507e59725f 100644 --- a/pkg/sql/sem/builtins/fixed_oids.go +++ b/pkg/sql/sem/builtins/fixed_oids.go @@ -2604,6 +2604,8 @@ var builtinOidsArray = []string{ 2641: `crdb_internal.clear_table_stats_cache() -> void`, 2642: `crdb_internal.get_fully_qualified_table_name(table_descriptor_id: int) -> string`, 2643: `crdb_internal.type_is_indexable(oid: oid) -> bool`, + 2644: `crdb_internal.range_stats_with_errors(key: bytes) -> jsonb`, + 2645: `crdb_internal.lease_holder_with_errors(key: bytes) -> jsonb`, } var builtinOidsBySignature map[string]oid.Oid diff --git a/pkg/sql/sem/tree/overload.go b/pkg/sql/sem/tree/overload.go index 4217ceee3575..1231dffbd2a0 100644 --- a/pkg/sql/sem/tree/overload.go +++ b/pkg/sql/sem/tree/overload.go @@ -38,6 +38,7 @@ const ( _ SpecializedVectorizedBuiltin = iota SubstringStringIntInt CrdbInternalRangeStats + CrdbInternalRangeStatsWithErrors ) // AggregateOverload is an opaque type which is used to box an eval.AggregateOverload. diff --git a/pkg/sql/show_ranges_test.go b/pkg/sql/show_ranges_test.go index 73b68c1ec3c4..0986f065f9e4 100644 --- a/pkg/sql/show_ranges_test.go +++ b/pkg/sql/show_ranges_test.go @@ -231,3 +231,60 @@ func TestShowRangesWithDetails(t *testing.T) { // val_bytes for the whole table. require.Equal(t, valBytesPreSplit, valBytesR1+valBytesR2) } + +// TestShowRangesUnavailableReplicas tests that SHOW RANGES does not return an +// error if it encounters an unavailable range. Moreover, crdb_internal.ranges +// includes the encountered error. +func TestShowRangesUnavailableReplicas(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + const numNodes = 3 + ctx := context.Background() + tc := testcluster.StartTestCluster( + // Manual replication will prevent the leaseholder for the unavailable range + // from moving a different node. + t, numNodes, base.TestClusterArgs{ReplicationMode: base.ReplicationManual}, + ) + defer tc.Stopper().Stop(ctx) + + sqlDB := sqlutils.MakeSQLRunner(tc.Conns[0]) + sqlDB.Exec(t, `SET CLUSTER SETTING kv.replica_circuit_breaker.slow_replication_threshold='1s'`) + sqlDB.Exec(t, `CREATE TABLE t (x INT PRIMARY KEY)`) + // Split the table's range to have a better chance of moving some leaseholders + // off of node 1 in the scatter below. + sqlDB.Exec(t, `ALTER TABLE t SPLIT AT SELECT i FROM generate_series(0, 20) AS g(i)`) + sqlDB.Exec(t, `ALTER TABLE t SCATTER`) + + // Server 0 includes the leaseholders for all system ranges, but the other two + // are safe to stop to create some unavailable ranges that belong to table t. + tc.StopServer(1) + tc.StopServer(2) + + q := `SELECT range_id, lease_holder, range_size FROM [SHOW RANGES FROM TABLE t WITH DETAILS]` + result := sqlDB.QueryStr(t, q) + unavailableRangeID := "" + // Iterate over the results to find an unavailable range. + for _, row := range result { + // crdb_internal.ranges powers the lease_holder and range_size fields in + // SHOW RANGES. If a range is unavailable, the former returns NULL for both + // fields but the latter converts the NULL leaseholder to 0. + if row[1] == "0" { + unavailableRangeID = row[0] + require.Equal(t, "NULL", row[2]) + break + } + } + // Ensure there it at least one unavailable range. + require.NotEqual(t, "", unavailableRangeID) + + // crdb_internal.ranges also has an "errors" field that includes any errors + // encountered while fetching the leaseholder and range stats. For the + // unavailable range, we expect a "replica unavailable" error. + q = fmt.Sprintf(`SELECT errors FROM crdb_internal.ranges WHERE range_id = %s`, unavailableRangeID) + result = sqlDB.QueryStr(t, q) + expectedError := fmt.Sprintf( + "replica unavailable.*unable to serve request to r%s", unavailableRangeID, + ) + require.Regexp(t, expectedError, result[0][0]) +}