Skip to content

Commit

Permalink
spanconfig: introduce the spanconfig.SQLTranslator
Browse files Browse the repository at this point in the history
This patch introduces the spanconfig.SQLTranslator which translates
zone configurations to span configurations.

The zone config and span config protos look similar, but they differ
from each other in a couple of ways:
- zone configurations correspond to {descriptor, zone} IDs whereas span
configurations correspond to keyspans.
- zone configurations have a notion of inheritance which span
configurations do not. Instead, they're fully hydrated and flattened
out.

When the SQLTranslator is given a {zone,descriptor} ID it generates
span configurations for all objects under the given ID in the zone
configuration hierarchy.

The SQLTranslator fills in any missing fields by following up the
inheritance chain to fully hydrate span configs. It also accounts
for subzones (and subzone spans) when constructing (span, span config)
tuples.

Split out from #69661.

Release note: None
  • Loading branch information
arulajmani committed Oct 9, 2021
1 parent f612208 commit 6cc313a
Show file tree
Hide file tree
Showing 32 changed files with 1,503 additions and 30 deletions.
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

/pkg/ccl/backupccl/ @cockroachdb/bulk-prs
/pkg/ccl/importccl/ @cockroachdb/bulk-prs
/pkg/ccl/spanconfigccl/ @cockroachdb/kv-prs
/pkg/ccl/storageccl/ @cockroachdb/bulk-prs
/pkg/cloud/ @cockroachdb/bulk-prs
/pkg/sql/distsql_plan_csv.go @cockroachdb/bulk-prs
Expand Down Expand Up @@ -221,7 +222,7 @@
/pkg/scheduledjobs/ @cockroachdb/bulk-prs
/pkg/security/ @cockroachdb/server-prs
/pkg/settings/ @cockroachdb/server-prs
/pkg/spanconfig/ @cockroachdb/multiregion
/pkg/spanconfig/ @cockroachdb/kv-prs
/pkg/startupmigrations/ @cockroachdb/server-prs @cockroachdb/sql-schema
/pkg/streaming/ @cockroachdb/bulk-prs
/pkg/testutils/ @cockroachdb/test-eng-noreview
Expand Down
1 change: 1 addition & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ALL_TESTS = [
"//pkg/ccl/partitionccl:partitionccl_test",
"//pkg/ccl/serverccl/diagnosticsccl:diagnosticsccl_test",
"//pkg/ccl/serverccl:serverccl_test",
"//pkg/ccl/spanconfigccl:spanconfigccl_test",
"//pkg/ccl/sqlproxyccl/denylist:denylist_test",
"//pkg/ccl/sqlproxyccl/idle:idle_test",
"//pkg/ccl/sqlproxyccl/tenant:tenant_test",
Expand Down
1 change: 1 addition & 0 deletions pkg/ccl/changefeedccl/helpers_tenant_shim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func (t *testServerShim) Clock() *hlc.Clock { panic(unsuppor
func (t *testServerShim) DistSenderI() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) MigrationServer() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) SpanConfigAccessor() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) SpanConfigSQLTranslator() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) SQLServer() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) SQLLivenessProvider() interface{} { panic(unsupportedShimMethod) }
func (t *testServerShim) StartupMigrationsManager() interface{} { panic(unsupportedShimMethod) }
Expand Down
32 changes: 32 additions & 0 deletions pkg/ccl/spanconfigccl/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")

go_test(
name = "spanconfigccl_test",
srcs = [
"datadriven_test.go",
"main_test.go",
],
data = glob(["testdata/**"]),
deps = [
"//pkg/base",
"//pkg/ccl/partitionccl",
"//pkg/ccl/utilccl",
"//pkg/config/zonepb",
"//pkg/roachpb:with-mocks",
"//pkg/security",
"//pkg/security/securitytest",
"//pkg/server",
"//pkg/spanconfig",
"//pkg/sql",
"//pkg/sql/catalog/catalogkv",
"//pkg/sql/catalog/descpb",
"//pkg/testutils/serverutils",
"//pkg/testutils/sqlutils",
"//pkg/testutils/testcluster",
"//pkg/util/leaktest",
"//pkg/util/log",
"//pkg/util/randutil",
"@com_github_cockroachdb_datadriven//:datadriven",
"@com_github_stretchr_testify//require",
],
)
219 changes: 219 additions & 0 deletions pkg/ccl/spanconfigccl/datadriven_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed as a CockroachDB Enterprise file under the Cockroach Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt

package spanconfigccl_test

import (
"context"
"fmt"
"reflect"
"strings"
"testing"

"github.com/cockroachdb/cockroach/pkg/base"
_ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl"
"github.com/cockroachdb/cockroach/pkg/config/zonepb"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/spanconfig"
"github.com/cockroachdb/cockroach/pkg/sql"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catalogkv"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"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/cockroachdb/datadriven"
"github.com/stretchr/testify/require"
)

// TestSQLTranslatorDataDriven is a data-driven test for the
// spanconfigsqltranslator.SQLTranslator. It allows users to set up zone config
// hierarchies and validate their translation to SpanConfigs is as expected.
// Only fields that are different from the (default) RANGE DEFAULT are printed
// in the test output for readability.
//
// It offers the following commands:
//
// "exec-sql": executes the input SQL query.
//
// "query-sql": executes the input SQL query and prints the results.
//
// "translate [database=<string>] [table=<string>] [named-zone=<string>]
// [id=<int>]:
// translates the SQL zone config state to the span config state starting from
// the referenced object (named zone, database, database + table, or id) as the
// root. Also see spanconfig.SQLTranslator.Translate().
//
// "full-translate": performs a full translation of the SQL zone config state
// to the implied span config state. Also see
// spanconfig.SQLTranslator.FullTranslate().
// TODO(arul): Add a secondary tenant configuration for this test as well.
func TestSQLTranslatorDataDriven(t *testing.T) {
defer leaktest.AfterTest(t)()
defer log.Scope(t).Close(t)

ctx := context.Background()
datadriven.Walk(t, "testdata/", func(t *testing.T, path string) {
tc := testcluster.StartTestCluster(t, 1, base.TestClusterArgs{
ServerArgs: base.TestServerArgs{
EnableSpanConfigs: true,
Knobs: base.TestingKnobs{
SpanConfig: &spanconfig.TestingKnobs{
ManagerDisableJobCreation: true,
},
},
},
})
defer tc.Stopper().Stop(ctx)
sqlDB := tc.ServerConn(0 /* idx */)

datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
switch d.Cmd {
case "exec-sql":
_, err := sqlDB.Exec(d.Input)
if err != nil {
return err.Error()
}
case "query-sql":
rows, err := sqlDB.Query(d.Input)
if err != nil {
return err.Error()
}
output, err := sqlutils.RowsToDataDrivenOutput(rows)
require.NoError(t, err)
return output
case "translate":
// Parse the args to get the object ID we're interested in translating.
objID := descpb.InvalidID
if d.HasArg(namedZone) {
var zone string
d.ScanArgs(t, namedZone, &zone)
if namedZoneID, found := zonepb.NamedZones[zonepb.NamedZone(zone)]; found {
objID = descpb.ID(namedZoneID)
} else {
t.Fatalf("unkown named zone: %s", zone)
}
} else if d.HasArg(database) {
var dbName string
d.ScanArgs(t, database, &dbName)
if d.HasArg(table) {
var tbName string
d.ScanArgs(t, table, &tbName)
tableDesc := catalogkv.TestingGetTableDescriptor(
tc.Server(0).DB(),
tc.Server(0).ExecutorConfig().(sql.ExecutorConfig).Codec,
dbName,
tbName,
)
objID = tableDesc.GetID()
} else {
dbDesc := catalogkv.TestingGetDatabaseDescriptor(
tc.Server(0).DB(),
tc.Server(0).ExecutorConfig().(sql.ExecutorConfig).Codec,
dbName,
)
objID = dbDesc.GetID()
}
} else if d.HasArg(id) {
var scanID int
d.ScanArgs(t, id, &scanID)
objID = descpb.ID(scanID)
} else {
t.Fatal("insufficient args provided to translate")
}
sqlTranslator := tc.Server(0).SpanConfigSQLTranslator().(spanconfig.SQLTranslator)
entries, err := sqlTranslator.Translate(ctx, descpb.IDs{objID})
if err != nil {
return err.Error()
}
require.NoError(t, err)
return datadrivenTranslationResult(entries)
case "full-translate":
sqlTranslator := tc.Server(0).SpanConfigSQLTranslator().(spanconfig.SQLTranslator)
entries, _, err := sqlTranslator.FullTranslate(ctx)
require.NoError(t, err)
return datadrivenTranslationResult(entries)
default:
t.Fatalf("unknown command: %s", d.Cmd)
}
return ""
})
})
}

// Constants for data-driven args.
const (
id = "id"
namedZone = "named-zone"
table = "table"
database = "database"
)

// datadrivenTranslationResult constructs the datadriven output for a given
// slice of (translated) span config entries.
func datadrivenTranslationResult(entries []roachpb.SpanConfigEntry) string {
var output strings.Builder
for _, entry := range entries {
res := diffEntryAgainstRangeDefault(entry)
output.WriteString(res)
output.WriteByte('\n')
}
return output.String()
}

// diffEntryAgainstRangeDefault computes the difference between the given config
// and RANGE DEFAULT. It then constructs a (span<->mismatching field(s)) string
// and returns it. If there config is same as RANGE DEFAULT, a (span, DEFAULT)
// string is returned instead.
func diffEntryAgainstRangeDefault(entry roachpb.SpanConfigEntry) string {
var res strings.Builder
defaultSpanConfig := zonepb.DefaultZoneConfig().AsSpanConfig()
var diffs []string

if entry.Config.RangeMaxBytes != defaultSpanConfig.RangeMaxBytes {
diffs = append(diffs, fmt.Sprintf("range_max_bytes=%d", entry.Config.RangeMaxBytes))
}
if entry.Config.RangeMinBytes != defaultSpanConfig.RangeMinBytes {
diffs = append(diffs, fmt.Sprintf("range_min_bytes=%d", entry.Config.RangeMinBytes))
}
if entry.Config.GCPolicy.TTLSeconds != defaultSpanConfig.GCPolicy.TTLSeconds {
diffs = append(diffs, fmt.Sprintf("ttl_seconds=%d", entry.Config.GCPolicy.TTLSeconds))
}
if entry.Config.GlobalReads != defaultSpanConfig.GlobalReads {
diffs = append(diffs, fmt.Sprintf("global_reads=%v", entry.Config.GlobalReads))
}
if entry.Config.NumReplicas != defaultSpanConfig.NumReplicas {
diffs = append(diffs, fmt.Sprintf("num_replicas=%d", entry.Config.NumReplicas))
}
if entry.Config.NumVoters != defaultSpanConfig.NumVoters {
diffs = append(diffs, fmt.Sprintf("num_voters=%d", entry.Config.NumVoters))
}
if !reflect.DeepEqual(entry.Config.Constraints, defaultSpanConfig.Constraints) {
diffs = append(diffs, fmt.Sprintf("constraints=%v", entry.Config.Constraints))
}
if !reflect.DeepEqual(entry.Config.VoterConstraints, defaultSpanConfig.VoterConstraints) {
diffs = append(diffs, fmt.Sprintf("voter_constraints=%v", entry.Config.VoterConstraints))
}
if !reflect.DeepEqual(entry.Config.LeasePreferences, defaultSpanConfig.LeasePreferences) {
diffs = append(diffs, fmt.Sprintf("lease_preferences=%v", entry.Config.VoterConstraints))
}

if len(diffs) != 0 {
for i, diff := range diffs {
if i == 0 {
res.WriteString(fmt.Sprintf("%-30s %s", entry.Span.String(), diff))
} else {
res.WriteByte(' ')
res.WriteString(diff)
}
}
} else {
res.WriteString(fmt.Sprintf("%-30s %s", entry.Span.String(), "DEFAULT"))
}
return res.String()
}
33 changes: 33 additions & 0 deletions pkg/ccl/spanconfigccl/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed as a CockroachDB Enterprise file under the Cockroach Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt

package spanconfigccl_test

import (
"os"
"testing"

"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
"github.com/cockroachdb/cockroach/pkg/security"
"github.com/cockroachdb/cockroach/pkg/security/securitytest"
"github.com/cockroachdb/cockroach/pkg/server"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
"github.com/cockroachdb/cockroach/pkg/util/randutil"
)

//go:generate ../../util/leaktest/add-leaktest.sh *_test.go

func TestMain(m *testing.M) {
defer utilccl.TestingEnableEnterprise()()
security.SetAssetLoader(securitytest.EmbeddedAssets)
randutil.SeedForTests()
serverutils.InitTestServerFactory(server.TestServerFactory)
serverutils.InitTestClusterFactory(testcluster.TestClusterFactory)
os.Exit(m.Run())
}
49 changes: 49 additions & 0 deletions pkg/ccl/spanconfigccl/testdata/databases
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Create a database with some tables, types, and schemas. Check that span
# configurations are as we expect.

exec-sql
CREATE DATABASE db;
CREATE TABLE db.t1();
CREATE TYPE db.typ AS ENUM();
CREATE SCHEMA db.sc;
CREATE TABLE db.t2();
----

query-sql
SELECT id FROM system.namespace WHERE name='t1'
----
53

query-sql
SELECT id FROM system.namespace WHERE name='t2'
----
57

# We only expect there to be span config entries for tables t1 and t2.
translate database=db
----
/Table/5{3-4} DEFAULT
/Table/5{7-8} DEFAULT

# Alter zone config fields on the database and one of the tables to ensure
# things are cascading.
exec-sql
ALTER DATABASE db CONFIGURE ZONE USING num_replicas=7;
ALTER TABLE db.t1 CONFIGURE ZONE USING num_voters=5;
----

translate database=db
----
/Table/5{3-4} num_replicas=7 num_voters=5
/Table/5{7-8} num_replicas=7

# Translating the tables in the database individually should result in the same
# config as above.

translate database=db table=t1
----
/Table/5{3-4} num_replicas=7 num_voters=5

translate database=db table=t2
----
/Table/5{7-8} num_replicas=7
Loading

0 comments on commit 6cc313a

Please sign in to comment.