Skip to content

Commit

Permalink
sql: add voting_replicas, non_voting_replicas columns to SHOW RANGES
Browse files Browse the repository at this point in the history
Fixes #93508

Some of the multitenant admin functions accept VOTERS, NONVOTERS as input.

Add voting_replicas, non_voting_replicas columns to SHOW RANGE(S) to make
working with the admin functions easier.

Release note (sql change): Add voting_replicas, non_voting_replicas columns to
output of SHOW RANGE(S) statements.
  • Loading branch information
ecwall committed Dec 15, 2022
1 parent a8d99a1 commit 4d1eaaa
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 82 deletions.
4 changes: 3 additions & 1 deletion pkg/sql/delegate/show_range_for_row.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ SELECT
lease_holder,
replica_localities[array_position(replicas, lease_holder)] as lease_holder_locality,
replicas,
replica_localities
replica_localities,
voting_replicas,
non_voting_replicas
FROM %[4]s.crdb_internal.ranges AS r
WHERE (r.start_key <= crdb_internal.encode_key(%[1]d, %[2]d, %[3]s))
AND (r.end_key > crdb_internal.encode_key(%[1]d, %[2]d, %[3]s)) ORDER BY r.start_key
Expand Down
8 changes: 6 additions & 2 deletions pkg/sql/delegate/show_ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ func (d *delegator) delegateShowRanges(n *tree.ShowRanges) (tree.Statement, erro
lease_holder,
replica_localities[array_position(replicas, lease_holder)] as lease_holder_locality,
replicas,
replica_localities
replica_localities,
voting_replicas,
non_voting_replicas
FROM %[1]s.crdb_internal.ranges AS r
WHERE database_name=%[2]s
ORDER BY table_name, r.start_key
Expand Down Expand Up @@ -121,7 +123,9 @@ SELECT
lease_holder,
replica_localities[array_position(replicas, lease_holder)] as lease_holder_locality,
replicas,
replica_localities
replica_localities,
voting_replicas,
non_voting_replicas
FROM %[3]s.crdb_internal.ranges AS r
WHERE (r.start_key < x'%[2]s')
AND (r.end_key > x'%[1]s') ORDER BY index_name, r.start_key
Expand Down
141 changes: 76 additions & 65 deletions pkg/sql/logictest/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ import (
// - R for decimal
// - B for boolean
// - O for oid
// - _ to include the column header, but ignore the column results.
// This is useful to verify that a column exists when the results are
// non-deterministic and to avoid projecting all other columns (for
// example `SHOW RANGES FROM TABLE`). A "_" placeholder is written in
// place of actual results.
//
// Options are comma separated strings from the following:
// - nosort (default)
Expand Down Expand Up @@ -3406,81 +3411,87 @@ func (t *logicTest) finishExecQuery(query logicQuery, rows *gosql.Rows, err erro
return err
}
for i, v := range vals {
if val := *v.(*interface{}); val != nil {
valT := reflect.TypeOf(val).Kind()
colT := query.colTypes[i]
switch colT {
case 'T':
if valT != reflect.String && valT != reflect.Slice && valT != reflect.Struct {
return fmt.Errorf("%s: expected text value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
case 'I':
if valT != reflect.Int64 {
if *flexTypes && (valT == reflect.Float64 || valT == reflect.Slice) {
t.signalIgnoredError(
fmt.Errorf("result type mismatch: expected I, got %T", val), query.pos, query.sql,
)
return nil
}
return fmt.Errorf("%s: expected int value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
case 'F', 'R':
if valT != reflect.Float64 && valT != reflect.Slice {
if *flexTypes && (valT == reflect.Int64) {
t.signalIgnoredError(
fmt.Errorf("result type mismatch: expected F or R, got %T", val), query.pos, query.sql,
)
return nil
}
return fmt.Errorf("%s: expected float/decimal value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
case 'B':
if valT != reflect.Bool {
return fmt.Errorf("%s: expected boolean value for column %d, but found %T: %#v",
query.pos, i, val, val,
colT := query.colTypes[i]
// Ignore column - useful for non-deterministic output.
if colT == '_' {
actualResultsRaw = append(actualResultsRaw, "_")
continue
}
val := *v.(*interface{})
if val == nil {
actualResultsRaw = append(actualResultsRaw, "NULL")
continue
}
valT := reflect.TypeOf(val).Kind()
switch colT {
case 'T':
if valT != reflect.String && valT != reflect.Slice && valT != reflect.Struct {
return fmt.Errorf("%s: expected text value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
case 'I':
if valT != reflect.Int64 {
if *flexTypes && (valT == reflect.Float64 || valT == reflect.Slice) {
t.signalIgnoredError(
fmt.Errorf("result type mismatch: expected I, got %T", val), query.pos, query.sql,
)
return nil
}
case 'O':
if valT != reflect.Slice {
return fmt.Errorf("%s: expected oid value for column %d, but found %T: %#v",
query.pos, i, val, val,
return fmt.Errorf("%s: expected int value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
case 'F', 'R':
if valT != reflect.Float64 && valT != reflect.Slice {
if *flexTypes && (valT == reflect.Int64) {
t.signalIgnoredError(
fmt.Errorf("result type mismatch: expected F or R, got %T", val), query.pos, query.sql,
)
return nil
}
default:
return fmt.Errorf("%s: unknown type in type string: %c in %s",
query.pos, colT, query.colTypes,
return fmt.Errorf("%s: expected float/decimal value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}

if byteArray, ok := val.([]byte); ok {
// The postgres wire protocol does not distinguish between
// strings and byte arrays, but our tests do. In order to do
// The Right Thing™, we replace byte arrays which are valid
// UTF-8 with strings. This allows byte arrays which are not
// valid UTF-8 to print as a list of bytes (e.g. `[124 107]`)
// while printing valid strings naturally.
if str := string(byteArray); utf8.ValidString(str) {
val = str
}
case 'B':
if valT != reflect.Bool {
return fmt.Errorf("%s: expected boolean value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
// Empty strings are rendered as "·" (middle dot)
if val == "" {
val = "·"
case 'O':
if valT != reflect.Slice {
return fmt.Errorf("%s: expected oid value for column %d, but found %T: %#v",
query.pos, i, val, val,
)
}
s := fmt.Sprint(val)
if query.roundFloatsInStringsSigFigs > 0 {
s = floatcmp.RoundFloatsInString(s, query.roundFloatsInStringsSigFigs)
default:
return fmt.Errorf("%s: unknown type in type string: %c in %s",
query.pos, colT, query.colTypes,
)
}

if byteArray, ok := val.([]byte); ok {
// The postgres wire protocol does not distinguish between
// strings and byte arrays, but our tests do. In order to do
// The Right Thing™, we replace byte arrays which are valid
// UTF-8 with strings. This allows byte arrays which are not
// valid UTF-8 to print as a list of bytes (e.g. `[124 107]`)
// while printing valid strings naturally.
if str := string(byteArray); utf8.ValidString(str) {
val = str
}
actualResultsRaw = append(actualResultsRaw, s)
} else {
actualResultsRaw = append(actualResultsRaw, "NULL")
}
// Empty strings are rendered as "·" (middle dot).
if val == "" {
val = "·"
}
s := fmt.Sprint(val)
if query.roundFloatsInStringsSigFigs > 0 {
s = floatcmp.RoundFloatsInString(s, query.roundFloatsInStringsSigFigs)
}
actualResultsRaw = append(actualResultsRaw, s)
}
}
if err := rows.Err(); err != nil {
Expand Down
Binary file modified pkg/sql/logictest/testdata/logic_test/ranges
Binary file not shown.
36 changes: 22 additions & 14 deletions pkg/sql/show_ranges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ package sql_test
import (
"context"
"fmt"
"sort"
"strings"
"testing"

"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/sqltestutils"
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestShowRangesWithLocality(t *testing.T) {
Expand All @@ -38,28 +41,35 @@ func TestShowRangesWithLocality(t *testing.T) {
sqlDB.Exec(t, `CREATE TABLE t (x INT PRIMARY KEY)`)
sqlDB.Exec(t, `ALTER TABLE t SPLIT AT SELECT i FROM generate_series(0, 20) AS g(i)`)

const leaseHolderIdx = 0
const leaseHolderLocalityIdx = 1
const replicasColIdx = 2
const localitiesColIdx = 3
const (
leaseHolderIdx = iota
leaseHolderLocalityIdx
replicasColIdx
localitiesColIdx
votingReplicasIdx
nonVotingReplicasIdx
)
replicas := make([]int, 3)

// TestClusters get some localities by default.
q := `SELECT lease_holder, lease_holder_locality, replicas, replica_localities from [SHOW RANGES FROM TABLE t]`
q := `SELECT lease_holder, lease_holder_locality, replicas, replica_localities, voting_replicas, non_voting_replicas FROM [SHOW RANGES FROM TABLE t]`
result := sqlDB.QueryStr(t, q)
for _, row := range result {
// Verify the leaseholder localities.
leaseHolder := row[leaseHolderIdx]
leaseHolderLocalityExpected := fmt.Sprintf(`region=test,dc=dc%s`, leaseHolder)
if row[leaseHolderLocalityIdx] != leaseHolderLocalityExpected {
t.Fatalf("expected %s found %s", leaseHolderLocalityExpected, row[leaseHolderLocalityIdx])
}
require.Equal(t, leaseHolderLocalityExpected, row[leaseHolderLocalityIdx])

// Verify the replica localities.
_, err := fmt.Sscanf(row[replicasColIdx], "{%d,%d,%d}", &replicas[0], &replicas[1], &replicas[2])
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)

votingReplicas := sqltestutils.ArrayStringToSlice(t, row[votingReplicasIdx])
sort.Strings(votingReplicas)
require.Equal(t, []string{"1", "2", "3"}, votingReplicas)
nonVotingReplicas := sqltestutils.ArrayStringToSlice(t, row[nonVotingReplicasIdx])
require.Equal(t, []string{}, nonVotingReplicas)

var builder strings.Builder
builder.WriteString("{")
for i, replica := range replicas {
Expand All @@ -70,9 +80,7 @@ func TestShowRangesWithLocality(t *testing.T) {
}
builder.WriteString("}")
expected := builder.String()
if row[localitiesColIdx] != expected {
t.Fatalf("expected %s found %s", expected, row[localitiesColIdx])
}
require.Equal(t, expected, row[localitiesColIdx])
}
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/sql/sqltestutils/sql_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,18 @@ func IsClientSideQueryCanceledErr(err error) bool {
}
return pgerror.GetPGCode(err) == pgcode.QueryCanceled
}

// ArrayStringToSlice converts a string array column to a string slice.
// "{}" -> {}
// "{a}" -> {"a"}
// "{a,b}" -> {"a", "b"}
func ArrayStringToSlice(t *testing.T, array string, message ...string) []string {
length := len(array)
require.GreaterOrEqual(t, length, 2, message)
require.Equal(t, "{", string(array[0]), message)
require.Equal(t, "}", string(array[length-1]), message)
if length == 2 {
return []string{}
}
return strings.Split(array[1:length-1], ",")
}

0 comments on commit 4d1eaaa

Please sign in to comment.