diff --git a/README.md b/README.md index 005a4f38c9cc..8d398cd89646 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,14 @@ Guide](https://www.cockroachlabs.com/docs/stable/architecture/overview.html). For the original design motivation, see our [design doc](https://github.com/cockroachdb/cockroach/blob/master/docs/design.md). +## Licensing + +Current CockroachDB code is released under a combination of two licenses, the [Business Source License (BSL)](https://www.cockroachlabs.com/docs/stable/licensing.html#bsl) and the [Cockroach Community License (CCL)](https://www.cockroachlabs.com/docs/stable/licensing.html#ccl-free). + +When contributing to a CockroachDB feature, you can find the relevant license in the comments at the top of each file. + +For more information, see the [Licensing FAQs](https://www.cockroachlabs.com/docs/stable/licensing-faqs.html). + ## Comparison with Other Databases To see how key features of CockroachDB stack up against other databases, diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index fd830a090539..529a7dcc8745 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -2638,6 +2638,8 @@ SELECT * FROM crdb_internal.check_consistency(true, ‘\x02’, ‘\x04’)
crdb_internal.num_inverted_index_entries(val: jsonb, version: int) → int
This function is used only by CockroachDB’s developers for testing purposes.
crdb_internal.payloads_for_span(span ID: int) → jsonb
Returns the payload(s) of the span whose ID is passed in the argument.
+crdb_internal.pretty_key(raw_key: bytes, skip_fields: int) → string
This function is used only by CockroachDB’s developers for testing purposes.
crdb_internal.range_stats(key: bytes) → jsonb
This function is used to retrieve range statistics information as a JSON object.
diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index 74381ec046d9..2737bfcc126c 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -19,6 +19,7 @@ ALL_TESTS = [ "//pkg/ccl/kvccl/kvfollowerreadsccl:kvfollowerreadsccl_test", "//pkg/ccl/kvccl/kvtenantccl:kvtenantccl_test", "//pkg/ccl/logictestccl:logictestccl_test", + "//pkg/ccl/multiregionccl:multiregionccl_test", "//pkg/ccl/oidcccl:oidcccl_test", "//pkg/ccl/partitionccl:partitionccl_test", "//pkg/ccl/serverccl:serverccl_test", diff --git a/pkg/ccl/BUILD.bazel b/pkg/ccl/BUILD.bazel index a7e53be1281f..c802b884a91c 100644 --- a/pkg/ccl/BUILD.bazel +++ b/pkg/ccl/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//pkg/ccl/gssapiccl", "//pkg/ccl/importccl", "//pkg/ccl/kvccl", + "//pkg/ccl/multiregionccl", "//pkg/ccl/oidcccl", "//pkg/ccl/partitionccl", "//pkg/ccl/storageccl", diff --git a/pkg/ccl/backupccl/restore_job.go b/pkg/ccl/backupccl/restore_job.go index d7ba8059a94e..a6a16f42a213 100644 --- a/pkg/ccl/backupccl/restore_job.go +++ b/pkg/ccl/backupccl/restore_job.go @@ -426,7 +426,7 @@ func WriteDescriptors( } for _, db := range databases { - if err := db.Validate(); err != nil { + if err := db.Validate(ctx, dg); err != nil { return errors.Wrapf(err, "validate database %d", errors.Safe(db.GetID())) } diff --git a/pkg/ccl/ccl_init.go b/pkg/ccl/ccl_init.go index e5a03cc061f3..50ccee16324a 100644 --- a/pkg/ccl/ccl_init.go +++ b/pkg/ccl/ccl_init.go @@ -21,6 +21,7 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl/gssapiccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/importccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/kvccl" + _ "github.com/cockroachdb/cockroach/pkg/ccl/multiregionccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/oidcccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/storageccl" diff --git a/pkg/ccl/changefeedccl/avro_test.go b/pkg/ccl/changefeedccl/avro_test.go index 143ea539011a..0ffac4e65b9a 100644 --- a/pkg/ccl/changefeedccl/avro_test.go +++ b/pkg/ccl/changefeedccl/avro_test.go @@ -59,7 +59,7 @@ func parseTableDesc(createTableStmt string) (catalog.TableDescriptor, error) { if err != nil { return nil, err } - return mutDesc, mutDesc.ValidateTable(ctx) + return mutDesc, mutDesc.ValidateSelf(ctx) } func parseValues(tableDesc catalog.TableDescriptor, values string) ([]rowenc.EncDatumRow, error) { diff --git a/pkg/ccl/logictestccl/testdata/logic_test/alter_table_locality b/pkg/ccl/logictestccl/testdata/logic_test/alter_table_locality index 02cd9db3f722..b00d11f73981 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/alter_table_locality +++ b/pkg/ccl/logictestccl/testdata/logic_test/alter_table_locality @@ -1749,27 +1749,66 @@ DATABASE alter_locality_test ALTER DATABASE alter_locality_test CONFIGURE ZONE voter_constraints = '[+region=ca-central-1]', lease_preferences = '[[+region=ca-central-1]]' -# TODO(#60606): this is flaky because REGIONAL BY ROW CREATE TABLE statements -# do not round trip. - -#statement ok -#CREATE TABLE regional_by_row_to_regional_by_row_as ( -# pk INT PRIMARY KEY, -# i INT, -# cr crdb_internal_region NOT NULL DEFAULT 'ap-southeast-2', -# INDEX(i), -# FAMILY (pk, i) -#) LOCALITY REGIONAL BY ROW; -#INSERT INTO regional_by_row_to_regional_by_row_as (pk, i) VALUES (1, 1); -#ALTER TABLE regional_by_row_to_regional_by_row_as SET LOCALITY REGIONAL BY ROW AS "cr" - -#query TT -#SHOW CREATE TABLE regional_by_row_to_regional_by_row_as -#---- - -#query TT -#SHOW ZONE CONFIGURATION FOR TABLE regional_by_row_to_regional_by_row_as -#---- +statement ok +CREATE TABLE regional_by_row_to_regional_by_row_as ( + pk INT PRIMARY KEY, + i INT, + cr crdb_internal_region NOT NULL DEFAULT 'ap-southeast-2', + INDEX(i), + FAMILY (pk, i) +) LOCALITY REGIONAL BY ROW; +INSERT INTO regional_by_row_to_regional_by_row_as (pk, i) VALUES (1, 1); +ALTER TABLE regional_by_row_to_regional_by_row_as SET LOCALITY REGIONAL BY ROW AS "cr" + +query TT +SHOW CREATE TABLE regional_by_row_to_regional_by_row_as +---- +regional_by_row_to_regional_by_row_as CREATE TABLE public.regional_by_row_to_regional_by_row_as ( + pk INT8 NOT NULL, + i INT8 NULL, + cr public.crdb_internal_region NOT NULL DEFAULT 'ap-southeast-2':::public.crdb_internal_region, + crdb_region public.crdb_internal_region NOT VISIBLE NOT NULL DEFAULT default_to_database_primary_region(gateway_region())::public.crdb_internal_region, + CONSTRAINT "primary" PRIMARY KEY (pk ASC), + INDEX regional_by_row_to_regional_by_row_as_i_idx (i ASC), + FAMILY fam_0_pk_i_cr_crdb_region (pk, i, cr, crdb_region) +) LOCALITY REGIONAL BY ROW AS cr; +ALTER PARTITION "ap-southeast-2" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@primary CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=ap-southeast-2]', + lease_preferences = '[[+region=ap-southeast-2]]'; +ALTER PARTITION "ap-southeast-2" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@regional_by_row_to_regional_by_row_as_i_idx CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=ap-southeast-2]', + lease_preferences = '[[+region=ap-southeast-2]]'; +ALTER PARTITION "ca-central-1" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@primary CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=ca-central-1]', + lease_preferences = '[[+region=ca-central-1]]'; +ALTER PARTITION "ca-central-1" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@regional_by_row_to_regional_by_row_as_i_idx CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=ca-central-1]', + lease_preferences = '[[+region=ca-central-1]]'; +ALTER PARTITION "us-east-1" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@primary CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-east-1]', + lease_preferences = '[[+region=us-east-1]]'; +ALTER PARTITION "us-east-1" OF INDEX alter_locality_test.public.regional_by_row_to_regional_by_row_as@regional_by_row_to_regional_by_row_as_i_idx CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-east-1]', + lease_preferences = '[[+region=us-east-1]]' + +query TT +SHOW ZONE CONFIGURATION FOR TABLE regional_by_row_to_regional_by_row_as +---- +DATABASE alter_locality_test ALTER DATABASE alter_locality_test CONFIGURE ZONE USING + range_min_bytes = 134217728, + range_max_bytes = 536870912, + gc.ttlseconds = 90000, + num_replicas = 5, + num_voters = 3, + constraints = '{+region=ap-southeast-2: 1, +region=ca-central-1: 1, +region=us-east-1: 1}', + voter_constraints = '[+region=ca-central-1]', + lease_preferences = '[[+region=ca-central-1]]' # Altering from REGIONAL BY ROW AS diff --git a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row index 0c2ad06a0d51..750904afb215 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row +++ b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row @@ -16,14 +16,6 @@ CREATE TABLE regional_by_row_table ( PARTITION BY LIST (pk) (PARTITION one VALUES IN ((1))) LOCALITY REGIONAL BY ROW -statement error cannot specify crdb_region column in REGIONAL BY ROW table as the column is implicitly created by the system -CREATE TABLE regional_by_row_table ( - pk int, - a int, - crdb_region crdb_internal_region -) -LOCALITY REGIONAL BY ROW - statement error REGIONAL BY ROW on a TABLE containing PARTITION BY is not supported CREATE TABLE regional_by_row_table ( pk int @@ -72,6 +64,46 @@ CREATE TABLE regional_by_row_table ( ) LOCALITY REGIONAL BY ROW +statement error cannot use column crdb_region which has type INT8 in REGIONAL BY ROW\nDETAIL: Column crdb_internal_region must be of type crdb_internal_region +CREATE TABLE regional_by_row_table ( + pk int, + a int, + crdb_region int +) +LOCALITY REGIONAL BY ROW + +statement ok +CREATE TABLE regional_by_row_table_explicit_crdb_region_column ( + pk int PRIMARY KEY, + a int, + crdb_region crdb_internal_region, + FAMILY (pk, a, crdb_region) +) +LOCALITY REGIONAL BY ROW + +query T +SELECT create_statement FROM [SHOW CREATE TABLE regional_by_row_table_explicit_crdb_region_column] +---- +CREATE TABLE public.regional_by_row_table_explicit_crdb_region_column ( + pk INT8 NOT NULL, + a INT8 NULL, + crdb_region public.crdb_internal_region NOT NULL, + CONSTRAINT "primary" PRIMARY KEY (pk ASC), + FAMILY fam_0_pk_a_crdb_region (pk, a, crdb_region) +) LOCALITY REGIONAL BY ROW; +ALTER PARTITION "ap-southeast-2" OF INDEX multi_region_test_db.public.regional_by_row_table_explicit_crdb_region_column@primary CONFIGURE ZONE USING + num_voters = 5, + voter_constraints = '{+region=ap-southeast-2: 2}', + lease_preferences = '[[+region=ap-southeast-2]]'; +ALTER PARTITION "ca-central-1" OF INDEX multi_region_test_db.public.regional_by_row_table_explicit_crdb_region_column@primary CONFIGURE ZONE USING + num_voters = 5, + voter_constraints = '{+region=ca-central-1: 2}', + lease_preferences = '[[+region=ca-central-1]]'; +ALTER PARTITION "us-east-1" OF INDEX multi_region_test_db.public.regional_by_row_table_explicit_crdb_region_column@primary CONFIGURE ZONE USING + num_voters = 5, + voter_constraints = '{+region=us-east-1: 2}', + lease_preferences = '[[+region=us-east-1]]' + statement ok CREATE TABLE regional_by_row_table ( pk int PRIMARY KEY, @@ -169,8 +201,9 @@ regional_by_row_table_j_idx j false query TTTTIT colnames SHOW TABLES ---- -schema_name table_name type owner estimated_row_count locality -public regional_by_row_table table root 0 REGIONAL BY ROW +schema_name table_name type owner estimated_row_count locality +public regional_by_row_table table root 0 REGIONAL BY ROW +public regional_by_row_table_explicit_crdb_region_column table root 0 REGIONAL BY ROW query TI INSERT INTO regional_by_row_table (pk, pk2, a, b, j) VALUES @@ -843,7 +876,7 @@ ALTER PARTITION "us-east-1" OF INDEX multi_region_test_db.public.regional_by_row lease_preferences = '[[+region=us-east-1]]' # Tests for REGIONAL BY TABLE AS -statement error cannot use column crdb_region_col which has type INT8 in REGIONAL BY ROW AS\nDETAIL:\s+REGIONAL BY ROW AS must reference a column of type crdb_internal_region. +statement error cannot use column crdb_region_col which has type INT8 in REGIONAL BY ROW\nDETAIL: REGIONAL BY ROW AS must reference a column of type crdb_internal_region CREATE TABLE regional_by_row_table_as ( pk int PRIMARY KEY, crdb_region_col int diff --git a/pkg/ccl/multiregionccl/BUILD.bazel b/pkg/ccl/multiregionccl/BUILD.bazel new file mode 100644 index 000000000000..f6d4006e8373 --- /dev/null +++ b/pkg/ccl/multiregionccl/BUILD.bazel @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "multiregionccl", + srcs = ["multiregion.go"], + importpath = "github.com/cockroachdb/cockroach/pkg/ccl/multiregionccl", + visibility = ["//visibility:public"], +) + +go_test( + name = "multiregionccl_test", + srcs = [ + "main_test.go", + "regional_by_row_test.go", + "show_test.go", + ], + deps = [ + "//pkg/base", + "//pkg/ccl/partitionccl", + "//pkg/ccl/utilccl", + "//pkg/jobs", + "//pkg/keys", + "//pkg/roachpb", + "//pkg/security", + "//pkg/security/securitytest", + "//pkg/server", + "//pkg/sql", + "//pkg/sql/catalog/catalogkv", + "//pkg/sql/execinfra", + "//pkg/sql/sqltestutils", + "//pkg/sql/tests", + "//pkg/testutils", + "//pkg/testutils/serverutils", + "//pkg/testutils/testcluster", + "//pkg/util", + "//pkg/util/leaktest", + "//pkg/util/log", + "//pkg/util/randutil", + "@com_github_cockroachdb_errors//:errors", + "@com_github_stretchr_testify//require", + ], +) diff --git a/pkg/ccl/multiregionccl/main_test.go b/pkg/ccl/multiregionccl/main_test.go new file mode 100644 index 000000000000..3074c894b061 --- /dev/null +++ b/pkg/ccl/multiregionccl/main_test.go @@ -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 multiregionccl_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()) +} diff --git a/pkg/ccl/multiregionccl/multiregion.go b/pkg/ccl/multiregionccl/multiregion.go new file mode 100644 index 000000000000..723467949137 --- /dev/null +++ b/pkg/ccl/multiregionccl/multiregion.go @@ -0,0 +1,9 @@ +// 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 multiregionccl diff --git a/pkg/ccl/partitionccl/regional_by_row_test.go b/pkg/ccl/multiregionccl/regional_by_row_test.go similarity index 99% rename from pkg/ccl/partitionccl/regional_by_row_test.go rename to pkg/ccl/multiregionccl/regional_by_row_test.go index 1c5d96a3156a..0fdf86aafd63 100644 --- a/pkg/ccl/partitionccl/regional_by_row_test.go +++ b/pkg/ccl/multiregionccl/regional_by_row_test.go @@ -6,7 +6,7 @@ // // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt -package partitionccl_test +package multiregionccl_test import ( "context" @@ -32,7 +32,7 @@ import ( "github.com/stretchr/testify/require" ) -// REGIONAL BY ROW tests are defined in partitionccl as REGIONAL BY ROW +// REGIONAL BY ROW tests are defined in multiregionccl as REGIONAL BY ROW // requires CCL to operate. // TestAlterTableLocalityRegionalByRowError tests an alteration involving diff --git a/pkg/ccl/multiregionccl/show_test.go b/pkg/ccl/multiregionccl/show_test.go new file mode 100644 index 000000000000..8e5ec639061a --- /dev/null +++ b/pkg/ccl/multiregionccl/show_test.go @@ -0,0 +1,114 @@ +// 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 multiregionccl_test + +import ( + "testing" + + // Blank import partitionccl to install CreatePartitioning hook. + _ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl" + "github.com/cockroachdb/cockroach/pkg/sql/sqltestutils" + "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "github.com/cockroachdb/cockroach/pkg/util/log" +) + +func TestShowCreateTable(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + testCases := []sqltestutils.ShowCreateTableTestCase{ + // Check GLOBAL tables are round trippable. + { + CreateStatement: `CREATE TABLE %s ( + a INT + ) LOCALITY GLOBAL`, + Expect: `CREATE TABLE public.%s ( + a INT8 NULL, + rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(), + CONSTRAINT "primary" PRIMARY KEY (rowid ASC), + FAMILY "primary" (a, rowid) +) LOCALITY GLOBAL`, + Database: "mrdb", + }, + // Check REGIONAL BY TABLE tables are round trippable. + { + CreateStatement: `CREATE TABLE %s ( + a INT + ) LOCALITY REGIONAL BY TABLE`, + Expect: `CREATE TABLE public.%s ( + a INT8 NULL, + rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(), + CONSTRAINT "primary" PRIMARY KEY (rowid ASC), + FAMILY "primary" (a, rowid) +) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION`, + Database: "mrdb", + }, + { + CreateStatement: `CREATE TABLE %s ( + a INT + ) LOCALITY REGIONAL BY TABLE IN "us-west1"`, + Expect: `CREATE TABLE public.%s ( + a INT8 NULL, + rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(), + CONSTRAINT "primary" PRIMARY KEY (rowid ASC), + FAMILY "primary" (a, rowid) +) LOCALITY REGIONAL BY TABLE IN "us-west1"`, + Database: "mrdb", + }, + // Check REGIONAL BY ROW tests are round trippable. + { + CreateStatement: `SET experimental_enable_implicit_column_partitioning = true; CREATE TABLE %s ( + a INT, + INDEX a_idx (a) + ) LOCALITY REGIONAL BY ROW`, + Expect: `CREATE TABLE public.%[1]s ( + a INT8 NULL, + crdb_region public.crdb_internal_region NOT VISIBLE NOT NULL DEFAULT default_to_database_primary_region(gateway_region())::public.crdb_internal_region, + rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(), + CONSTRAINT "primary" PRIMARY KEY (rowid ASC), + INDEX a_idx (a ASC), + FAMILY "primary" (a, crdb_region, rowid) +) LOCALITY REGIONAL BY ROW; +ALTER PARTITION "us-west1" OF INDEX mrdb.public.%[1]s@a_idx CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-west1]', + lease_preferences = '[[+region=us-west1]]'; +ALTER PARTITION "us-west1" OF INDEX mrdb.public.%[1]s@primary CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-west1]', + lease_preferences = '[[+region=us-west1]]'`, + Database: "mrdb", + }, + { + CreateStatement: `SET experimental_enable_implicit_column_partitioning = true; CREATE TABLE %s ( + a INT, + crdb_region_col crdb_internal_region, + INDEX a_idx (a) + ) LOCALITY REGIONAL BY ROW AS crdb_region_col`, + Expect: `CREATE TABLE public.%[1]s ( + a INT8 NULL, + crdb_region_col public.crdb_internal_region NOT NULL, + rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(), + CONSTRAINT "primary" PRIMARY KEY (rowid ASC), + INDEX a_idx (a ASC), + FAMILY "primary" (a, crdb_region_col, rowid) +) LOCALITY REGIONAL BY ROW AS crdb_region_col; +ALTER PARTITION "us-west1" OF INDEX mrdb.public.%[1]s@a_idx CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-west1]', + lease_preferences = '[[+region=us-west1]]'; +ALTER PARTITION "us-west1" OF INDEX mrdb.public.%[1]s@primary CONFIGURE ZONE USING + num_voters = 3, + voter_constraints = '[+region=us-west1]', + lease_preferences = '[[+region=us-west1]]'`, + Database: "mrdb", + }, + } + sqltestutils.ShowCreateTableTest(t, testCases) +} diff --git a/pkg/ccl/partitionccl/BUILD.bazel b/pkg/ccl/partitionccl/BUILD.bazel index 145fc80ed620..b3cce6124459 100644 --- a/pkg/ccl/partitionccl/BUILD.bazel +++ b/pkg/ccl/partitionccl/BUILD.bazel @@ -32,7 +32,6 @@ go_test( "drop_test.go", "main_test.go", "partition_test.go", - "regional_by_row_test.go", "zone_test.go", ], embed = [":partitionccl"], @@ -43,7 +42,6 @@ go_test( "//pkg/ccl/utilccl", "//pkg/config", "//pkg/config/zonepb", - "//pkg/jobs", "//pkg/keys", "//pkg/kv/kvserver", "//pkg/roachpb", @@ -55,12 +53,10 @@ go_test( "//pkg/sql/catalog/catalogkv", "//pkg/sql/catalog/descpb", "//pkg/sql/catalog/tabledesc", - "//pkg/sql/execinfra", "//pkg/sql/gcjob", "//pkg/sql/parser", "//pkg/sql/rowenc", "//pkg/sql/sem/tree", - "//pkg/sql/sqltestutils", "//pkg/sql/tests", "//pkg/sql/types", "//pkg/testutils", @@ -68,7 +64,6 @@ go_test( "//pkg/testutils/skip", "//pkg/testutils/sqlutils", "//pkg/testutils/testcluster", - "//pkg/util", "//pkg/util/encoding", "//pkg/util/hlc", "//pkg/util/leaktest", @@ -78,7 +73,6 @@ go_test( "//pkg/util/uuid", "@com_github_cockroachdb_errors//:errors", "@com_github_gogo_protobuf//proto", - "@com_github_stretchr_testify//require", "@in_gopkg_yaml_v2//:yaml_v2", ], ) diff --git a/pkg/ccl/partitionccl/partition_test.go b/pkg/ccl/partitionccl/partition_test.go index 80673478c7c1..b3daa6209ea5 100644 --- a/pkg/ccl/partitionccl/partition_test.go +++ b/pkg/ccl/partitionccl/partition_test.go @@ -139,7 +139,7 @@ func (pt *partitioningTest) parse() error { return err } pt.parsed.tableDesc = mutDesc - if err := pt.parsed.tableDesc.ValidateTable(ctx); err != nil { + if err := pt.parsed.tableDesc.ValidateSelf(ctx); err != nil { return err } } diff --git a/pkg/sql/catalog/catalogkv/catalogkv.go b/pkg/sql/catalog/catalogkv/catalogkv.go index e1c6c455bdb2..94cd53859a6f 100644 --- a/pkg/sql/catalog/catalogkv/catalogkv.go +++ b/pkg/sql/catalog/catalogkv/catalogkv.go @@ -280,21 +280,6 @@ func (t *oneLevelUncachedDescGetter) GetDescs( var _ catalog.DescGetter = (*oneLevelUncachedDescGetter)(nil) -func validateDescriptor(ctx context.Context, dg catalog.DescGetter, desc catalog.Descriptor) error { - switch desc := desc.(type) { - case catalog.TableDescriptor: - return desc.Validate(ctx, dg) - case catalog.DatabaseDescriptor: - return desc.Validate() - case catalog.TypeDescriptor: - return desc.Validate(ctx, dg) - case catalog.SchemaDescriptor: - return nil - default: - return errors.AssertionFailedf("unknown descriptor type %T", desc) - } -} - // unwrapDescriptor takes a descriptor retrieved using a transaction and unwraps // it into an immutable implementation of Descriptor. It ensures that // the ModificationTime is set properly and will validate the descriptor if @@ -327,7 +312,7 @@ func unwrapDescriptor( return nil, nil } if validate { - if err := validateDescriptor(ctx, dg, unwrapped); err != nil { + if err := unwrapped.Validate(ctx, dg); err != nil { return nil, err } } @@ -350,13 +335,13 @@ func unwrapDescriptorMutable( if err != nil { return nil, err } - if err := mutTable.ValidateTable(ctx); err != nil { + if err := mutTable.ValidateSelf(ctx); err != nil { return nil, err } return mutTable, nil case database != nil: dbDesc := dbdesc.NewExistingMutable(*database) - if err := dbDesc.Validate(); err != nil { + if err := dbDesc.Validate(ctx, dg); err != nil { return nil, err } return dbDesc, nil @@ -432,7 +417,7 @@ func GetAllDescriptors( dg[desc.GetID()] = desc } for _, desc := range descs { - if err := validateDescriptor(ctx, dg, desc); err != nil { + if err := desc.Validate(ctx, dg); err != nil { return nil, err } } @@ -603,6 +588,7 @@ func getDescriptorsFromIDs( if err := txn.Run(ctx, b); err != nil { return nil, err } + dg := NewOneLevelUncachedDescGetter(txn, codec) results := make([]catalog.Descriptor, 0, len(ids)) for i := range b.Results { result := &b.Results[i] @@ -624,7 +610,7 @@ func getDescriptorsFromIDs( var catalogDesc catalog.Descriptor if desc.Union != nil { var err error - catalogDesc, err = unwrapDescriptor(ctx, nil /* descGetter */, result.Rows[0].Value.Timestamp, desc, true) + catalogDesc, err = unwrapDescriptor(ctx, dg, result.Rows[0].Value.Timestamp, desc, true) if err != nil { return nil, err } diff --git a/pkg/sql/catalog/catalogkv/unwrap_validation_test.go b/pkg/sql/catalog/catalogkv/unwrap_validation_test.go index 4f4a53d9adea..1cde4832356b 100644 --- a/pkg/sql/catalog/catalogkv/unwrap_validation_test.go +++ b/pkg/sql/catalog/catalogkv/unwrap_validation_test.go @@ -87,20 +87,6 @@ func (o oneLevelMapDescGetter) GetDesc( return unwrapDescriptorMutable(ctx, nil, mt, &desc) } -func (o oneLevelMapDescGetter) GetDescs( - ctx context.Context, reqs []descpb.ID, -) ([]catalog.Descriptor, error) { - resps := make([]catalog.Descriptor, len(reqs)) - for i, r := range reqs { - var err error - resps[i], err = o.GetDesc(ctx, r) - if err != nil { - return nil, err - } - } - return resps, nil -} - func decodeDescriptorDSV(t *testing.T, descriptorCSVPath string) oneLevelMapDescGetter { f, err := os.Open(descriptorCSVPath) require.NoError(t, err) diff --git a/pkg/sql/catalog/dbdesc/BUILD.bazel b/pkg/sql/catalog/dbdesc/BUILD.bazel index d081c8bbea18..7a8dfb7fb5ec 100644 --- a/pkg/sql/catalog/dbdesc/BUILD.bazel +++ b/pkg/sql/catalog/dbdesc/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//pkg/sql/catalog/descpb", "//pkg/sql/privilege", "//pkg/util/hlc", + "//pkg/util/iterutil", "//pkg/util/protoutil", "@com_github_cockroachdb_errors//:errors", "@com_github_cockroachdb_redact//:redact", diff --git a/pkg/sql/catalog/dbdesc/database_desc.go b/pkg/sql/catalog/dbdesc/database_desc.go index e9c5b9958a4f..1d93197052e3 100644 --- a/pkg/sql/catalog/dbdesc/database_desc.go +++ b/pkg/sql/catalog/dbdesc/database_desc.go @@ -13,6 +13,7 @@ package dbdesc import ( + "context" "fmt" "github.com/cockroachdb/cockroach/pkg/keys" @@ -21,6 +22,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/privilege" "github.com/cockroachdb/cockroach/pkg/util/hlc" + "github.com/cockroachdb/cockroach/pkg/util/iterutil" "github.com/cockroachdb/cockroach/pkg/util/protoutil" "github.com/cockroachdb/errors" "github.com/cockroachdb/redact" @@ -250,10 +252,26 @@ func (desc *Mutable) SetName(name string) { desc.Name = name } -// Validate validates that the database descriptor is well formed. +// ForEachSchemaInfo iterates f over each schema info mapping in the descriptor. +// iterutil.StopIteration is supported. +func (desc *Immutable) ForEachSchemaInfo( + f func(id descpb.ID, name string, isDropped bool) error, +) error { + for name, info := range desc.Schemas { + if err := f(info.ID, name, info.Dropped); err != nil { + if iterutil.Done(err) { + return nil + } + return err + } + } + return nil +} + +// ValidateSelf validates that the database descriptor is well formed. // Checks include validate the database name, and verifying that there // is at least one read and write user. -func (desc *Immutable) Validate() error { +func (desc *Immutable) ValidateSelf(_ context.Context) error { if err := catalog.ValidateName(desc.GetName(), "descriptor"); err != nil { return err } @@ -294,6 +312,16 @@ func (desc *Immutable) Validate() error { return desc.Privileges.Validate(desc.GetID(), privilege.Database) } +// Validate punts to ValidateSelf. +func (desc *Immutable) Validate(ctx context.Context, _ catalog.DescGetter) error { + return desc.ValidateSelf(ctx) +} + +// ValidateTxnCommit punts to Validate. +func (desc *Immutable) ValidateTxnCommit(ctx context.Context, descGetter catalog.DescGetter) error { + return desc.Validate(ctx, descGetter) +} + // SchemaMeta implements the tree.SchemaMeta interface. // TODO (rohany): I don't want to keep this here, but it seems to be used // by backup only for the fake resolution that occurs in backup. Is it possible diff --git a/pkg/sql/catalog/desc_getter.go b/pkg/sql/catalog/desc_getter.go index 1973da85ffbf..00d550082de5 100644 --- a/pkg/sql/catalog/desc_getter.go +++ b/pkg/sql/catalog/desc_getter.go @@ -20,9 +20,32 @@ import ( // is used to look up other descriptors during validation. type DescGetter interface { GetDesc(ctx context.Context, id descpb.ID) (Descriptor, error) +} + +// BatchDescGetter is like DescGetter but retrieves batches of descriptors, +// which for some implementation may make more sense performance-wise. +type BatchDescGetter interface { GetDescs(ctx context.Context, reqs []descpb.ID) ([]Descriptor, error) } +// GetDescs retrieves multiple descriptors using a DescGetter. +// If the latter is also a BatchDescGetter, it will delegate to its GetDescs +// method. +func GetDescs(ctx context.Context, descGetter DescGetter, reqs []descpb.ID) ([]Descriptor, error) { + if bdg, ok := descGetter.(BatchDescGetter); ok { + return bdg.GetDescs(ctx, reqs) + } + ret := make([]Descriptor, len(reqs)) + for i, id := range reqs { + desc, err := descGetter.GetDesc(ctx, id) + if err != nil { + return nil, err + } + ret[i] = desc + } + return ret, nil +} + // GetTypeDescFromID retrieves the type descriptor for the type ID passed // in using an existing descGetter. It returns an error if the descriptor // doesn't exist or if it exists and is not a type descriptor. @@ -62,12 +85,3 @@ func (m MapDescGetter) GetDesc(ctx context.Context, id descpb.ID) (Descriptor, e desc := m[id] return desc, nil } - -// GetDescs implements the catalog.DescGetter interface. -func (m MapDescGetter) GetDescs(ctx context.Context, ids []descpb.ID) ([]Descriptor, error) { - ret := make([]Descriptor, len(ids)) - for i, id := range ids { - ret[i], _ = m.GetDesc(ctx, id) - } - return ret, nil -} diff --git a/pkg/sql/catalog/descriptor.go b/pkg/sql/catalog/descriptor.go index 1172dd990706..fb414d7c1f50 100644 --- a/pkg/sql/catalog/descriptor.go +++ b/pkg/sql/catalog/descriptor.go @@ -72,6 +72,15 @@ type Descriptor interface { // DescriptorProto prepares this descriptor for serialization. DescriptorProto() *descpb.Descriptor + + // ValidateSelf checks the internal consistency of the descriptor. + ValidateSelf(ctx context.Context) error + + // Validate is like ValidateSelf but with additional cross-reference checks. + Validate(ctx context.Context, descGetter DescGetter) error + + // ValidateTxnCommit is like Validate but with additional pre-commit checks. + ValidateTxnCommit(ctx context.Context, descGetter DescGetter) error } // DatabaseDescriptor will eventually be called dbdesc.Descriptor. @@ -90,8 +99,8 @@ type DatabaseDescriptor interface { RegionNames() (descpb.RegionNames, error) IsMultiRegion() bool PrimaryRegionName() (descpb.RegionName, error) - Validate() error MultiRegionEnumID() (descpb.ID, error) + ForEachSchemaInfo(func(id descpb.ID, name string, isDropped bool) error) error } // SchemaDescriptor will eventually be called schemadesc.Descriptor. @@ -245,8 +254,6 @@ type TableDescriptor interface { databaseDesc DatabaseDescriptor, getType func(descpb.ID) (TypeDescriptor, error), ) (descpb.IDs, error) - Validate(ctx context.Context, txn DescGetter) error - ForeachDependedOnBy(f func(dep *descpb.TableDescriptor_Reference) error) error GetDependsOn() []descpb.ID GetConstraintInfoWithLookup(fn TableLookupFn) (map[string]descpb.ConstraintDetail, error) @@ -493,7 +500,6 @@ type TypeDescriptor interface { PrimaryRegionName() (descpb.RegionName, error) RegionNames() (descpb.RegionNames, error) - Validate(ctx context.Context, dg DescGetter) error } // TypeDescriptorResolver is an interface used during hydration of type diff --git a/pkg/sql/catalog/descs/BUILD.bazel b/pkg/sql/catalog/descs/BUILD.bazel index cc3535989933..5dccdb65b64a 100644 --- a/pkg/sql/catalog/descs/BUILD.bazel +++ b/pkg/sql/catalog/descs/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//pkg/base", "//pkg/keys", "//pkg/kv", + "//pkg/settings", "//pkg/settings/cluster", "//pkg/sql/catalog", "//pkg/sql/catalog/bootstrap", @@ -65,6 +66,7 @@ go_test( "//pkg/sql/catalog/tabledesc", "//pkg/sql/sem/tree", "//pkg/sql/sqlutil", + "//pkg/sql/types", "//pkg/testutils/serverutils", "//pkg/testutils/sqlutils", "//pkg/testutils/testcluster", diff --git a/pkg/sql/catalog/descs/collection.go b/pkg/sql/catalog/descs/collection.go index c12a2d31006a..7678d47935c9 100644 --- a/pkg/sql/catalog/descs/collection.go +++ b/pkg/sql/catalog/descs/collection.go @@ -21,6 +21,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/keys" "github.com/cockroachdb/cockroach/pkg/kv" + "github.com/cockroachdb/cockroach/pkg/settings" "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/cockroachdb/cockroach/pkg/sql/catalog" "github.com/cockroachdb/cockroach/pkg/sql/catalog/bootstrap" @@ -1342,13 +1343,25 @@ func (tc *Collection) addUncommittedDescriptor( return ud, nil } +// validateOnWriteEnabled is the cluster setting used to enable or disable +// validating descriptors prior to writing. +var validateOnWriteEnabled = settings.RegisterBoolSetting( + "sql.catalog.descs.validate_on_write.enabled", + "set to true to validate descriptors prior to writing, false to disable; default is true", + true, /* defaultValue */ +) + // WriteDescToBatch calls MaybeIncrementVersion, adds the descriptor to the // collection as an uncommitted descriptor, and writes it into b. func (tc *Collection) WriteDescToBatch( ctx context.Context, kvTrace bool, desc catalog.MutableDescriptor, b *kv.Batch, ) error { desc.MaybeIncrementVersion() - // TODO(ajwerner): Add validation here. + if validateOnWriteEnabled.Get(&tc.settings.SV) { + if err := desc.ValidateSelf(ctx); err != nil { + return err + } + } if err := tc.AddUncommittedDescriptor(desc); err != nil { return err } @@ -1392,6 +1405,43 @@ func (tc *Collection) GetUncommittedTables() (tables []catalog.TableDescriptor) return tables } +type collectionDescGetter struct { + tc *Collection + txn *kv.Txn +} + +var _ catalog.DescGetter = collectionDescGetter{} + +func (cdg collectionDescGetter) GetDesc( + ctx context.Context, id descpb.ID, +) (catalog.Descriptor, error) { + flags := tree.CommonLookupFlags{ + Required: true, + // Include everything, we want to cast the net as wide as we can. + IncludeOffline: true, + IncludeDropped: true, + // Avoid leased descriptors, if we're leasing the previous version then this + // older version may be returned and this may cause validation to fail. + AvoidCached: true, + } + return cdg.tc.getDescriptorByID(ctx, cdg.txn, id, flags, false /* mutable */) +} + +// ValidateUncommittedDescriptors validates all uncommitted descriptors +func (tc *Collection) ValidateUncommittedDescriptors(ctx context.Context, txn *kv.Txn) error { + if !validateOnWriteEnabled.Get(&tc.settings.SV) { + return nil + } + cdg := collectionDescGetter{tc: tc, txn: txn} + for i, n := 0, len(tc.uncommittedDescriptors); i < n; i++ { + desc := tc.uncommittedDescriptors[i].immutable + if err := desc.ValidateTxnCommit(ctx, cdg); err != nil { + return err + } + } + return nil +} + // User defined type accessors. // GetMutableTypeVersionByID is the equivalent of GetMutableTableDescriptorByID diff --git a/pkg/sql/catalog/descs/collection_test.go b/pkg/sql/catalog/descs/collection_test.go index dcd976c2adc8..5c60529d742e 100644 --- a/pkg/sql/catalog/descs/collection_test.go +++ b/pkg/sql/catalog/descs/collection_test.go @@ -19,6 +19,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/kv" "github.com/cockroachdb/cockroach/pkg/kv/kvserver" "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/security" "github.com/cockroachdb/cockroach/pkg/sql/catalog" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descs" @@ -26,6 +27,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqlutil" + "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" "github.com/cockroachdb/cockroach/pkg/util/leaktest" @@ -65,11 +67,36 @@ func TestCollectionWriteDescToBatch(t *testing.T) { // We want to create some descriptors and then ensure that writing them to a // batch works as expected. newTable := tabledesc.NewCreatedMutable(descpb.TableDescriptor{ - ID: 42, + ID: 142, Name: "table2", Version: 1, ParentID: mut.GetParentID(), UnexposedParentSchemaID: mut.GetParentSchemaID(), + Columns: []descpb.ColumnDescriptor{ + {ID: 1, Name: "a", Type: types.Int}, + }, + Families: []descpb.ColumnFamilyDescriptor{ + { + ID: 0, + Name: "primary", + ColumnNames: []string{"a"}, + ColumnIDs: []descpb.ColumnID{1}, + DefaultColumnID: 1, + }, + }, + PrimaryIndex: descpb.IndexDescriptor{ + ID: 1, + Name: "pk", + ColumnIDs: []descpb.ColumnID{1}, + ColumnNames: []string{"a"}, + ColumnDirections: []descpb.IndexDescriptor_Direction{descpb.IndexDescriptor_ASC}, + }, + Privileges: descpb.NewDefaultPrivilegeDescriptor(security.AdminRoleName()), + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextMutationID: 1, + FormatVersion: descpb.FamilyFormatVersion, }) b := txn.NewBatch() diff --git a/pkg/sql/catalog/descs/txn.go b/pkg/sql/catalog/descs/txn.go index a0102c12c949..13d561cb5e2a 100644 --- a/pkg/sql/catalog/descs/txn.go +++ b/pkg/sql/catalog/descs/txn.go @@ -52,6 +52,9 @@ func Txn( if err := f(ctx, txn, descsCol); err != nil { return err } + if err := descsCol.ValidateUncommittedDescriptors(ctx, txn); err != nil { + return err + } retryErr, err := CheckTwoVersionInvariant( ctx, db.Clock(), ie, descsCol, txn, nil /* onRetryBackoff */) if retryErr { diff --git a/pkg/sql/catalog/lease/lease.go b/pkg/sql/catalog/lease/lease.go index 1309f60cede3..9977661bfe7b 100644 --- a/pkg/sql/catalog/lease/lease.go +++ b/pkg/sql/catalog/lease/lease.go @@ -229,7 +229,7 @@ func (s storage) acquire( } // TODO (lucy): Previously this called getTableDescFromID followed by a call - // to ValidateTable() instead of Validate(), to avoid the cross-table + // to ValidateSelf() instead of Validate(), to avoid the cross-table // checks. Does this actually matter? We already potentially do cross-table // checks when populating pre-19.2 foreign keys. desc, err := catalogkv.GetDescriptorByID(ctx, txn, s.codec, id, catalogkv.Immutable, diff --git a/pkg/sql/catalog/schemadesc/BUILD.bazel b/pkg/sql/catalog/schemadesc/BUILD.bazel index fc8394d636b2..e730a535b450 100644 --- a/pkg/sql/catalog/schemadesc/BUILD.bazel +++ b/pkg/sql/catalog/schemadesc/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "//pkg/sql/catalog/descpb", "//pkg/sql/pgwire/pgcode", "//pkg/sql/pgwire/pgerror", + "//pkg/sql/privilege", "//pkg/sql/sessiondata", "//pkg/util/hlc", "//pkg/util/protoutil", diff --git a/pkg/sql/catalog/schemadesc/schema_desc.go b/pkg/sql/catalog/schemadesc/schema_desc.go index 8f2154f1db20..499599ad865b 100644 --- a/pkg/sql/catalog/schemadesc/schema_desc.go +++ b/pkg/sql/catalog/schemadesc/schema_desc.go @@ -11,6 +11,8 @@ package schemadesc import ( + "context" + "fmt" "strings" "github.com/cockroachdb/cockroach/pkg/keys" @@ -18,6 +20,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" + "github.com/cockroachdb/cockroach/pkg/sql/privilege" "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/cockroach/pkg/util/protoutil" @@ -165,6 +168,77 @@ func (desc *Immutable) DescriptorProto() *descpb.Descriptor { } } +// ValidateSelf implements the catalog.Descriptor interface. +func (desc *Immutable) ValidateSelf(_ context.Context) error { + if err := catalog.ValidateName(desc.GetName(), "descriptor"); err != nil { + return err + } + if desc.GetID() == 0 { + return fmt.Errorf("invalid schema ID %d", desc.GetID()) + } + // Validate the privilege descriptor. + return desc.Privileges.Validate(desc.GetID(), privilege.Schema) +} + +// Validate implements the catalog.Descriptor interface. +func (desc *Immutable) Validate(ctx context.Context, descGetter catalog.DescGetter) error { + if err := desc.ValidateSelf(ctx); err != nil { + return err + } + // Don't validate cross-references for dropped schemas. + if desc.Dropped() || descGetter == nil { + return nil + } + + // Check schema parent reference. + foundDesc, err := descGetter.GetDesc(ctx, desc.GetParentID()) + if err != nil { + return err + } + db, isDB := foundDesc.(catalog.DatabaseDescriptor) + if !isDB { + return errors.AssertionFailedf("parent database ID %d does not exist", errors.Safe(desc.GetParentID())) + } + + // Check that parent has correct entry in schemas mapping. + isInDBSchemas := false + err = db.ForEachSchemaInfo(func(id descpb.ID, name string, isDropped bool) error { + if id == desc.GetID() { + if isDropped { + if name == desc.GetName() { + return errors.AssertionFailedf("present in parent database [%d] schemas mapping but marked as dropped", + errors.Safe(desc.GetParentID())) + } + return nil + } + if name != desc.GetName() { + return errors.AssertionFailedf("present in parent database [%d] schemas mapping but under name %q", + errors.Safe(desc.GetParentID()), errors.Safe(name)) + } + isInDBSchemas = true + return nil + } + if !isDropped && name == desc.GetName() { + return errors.AssertionFailedf("present in parent database [%d] schemas mapping but name maps to other schema [%d]", + errors.Safe(desc.GetParentID()), errors.Safe(id)) + } + return nil + }) + if err != nil { + return err + } + if !isInDBSchemas { + return errors.AssertionFailedf("not present in parent database [%d] schemas mapping", + errors.Safe(desc.GetParentID())) + } + return nil +} + +// ValidateTxnCommit punts to Validate. +func (desc *Immutable) ValidateTxnCommit(ctx context.Context, descGetter catalog.DescGetter) error { + return desc.Validate(ctx, descGetter) +} + // NameResolutionResult implements the ObjectDescriptor interface. func (desc *Immutable) NameResolutionResult() {} diff --git a/pkg/sql/catalog/tabledesc/helpers_test.go b/pkg/sql/catalog/tabledesc/helpers_test.go index 024f573dfcef..ccc39c92522c 100644 --- a/pkg/sql/catalog/tabledesc/helpers_test.go +++ b/pkg/sql/catalog/tabledesc/helpers_test.go @@ -22,7 +22,7 @@ func ValidateTable(ctx context.Context, immI catalog.TableDescriptor) error { if !ok { return errors.Errorf("expected immutable descriptor") } - return imm.ValidateTable(ctx) + return imm.ValidateSelf(ctx) } func ValidateCrossReferences( diff --git a/pkg/sql/catalog/tabledesc/safe_format_test.go b/pkg/sql/catalog/tabledesc/safe_format_test.go index 4f841a072d3a..b83870f25d07 100644 --- a/pkg/sql/catalog/tabledesc/safe_format_test.go +++ b/pkg/sql/catalog/tabledesc/safe_format_test.go @@ -265,7 +265,7 @@ func TestSafeMessage(t *testing.T) { td = desc } redacted := string(redact.Sprint(td).Redact()) - require.NoError(t, desc.ValidateTable(ctx)) + require.NoError(t, desc.ValidateSelf(ctx)) require.Equal(t, tc.exp, redacted) var m map[string]interface{} require.NoError(t, yaml.UnmarshalStrict([]byte(redacted), &m), redacted) diff --git a/pkg/sql/catalog/tabledesc/structured.go b/pkg/sql/catalog/tabledesc/structured.go index 8c45360d39db..f312fd02a2f8 100644 --- a/pkg/sql/catalog/tabledesc/structured.go +++ b/pkg/sql/catalog/tabledesc/structured.go @@ -945,7 +945,7 @@ func (desc *Mutable) AllocateIDs(ctx context.Context) error { if desc.ID == 0 { desc.ID = keys.MinUserDescID } - err := desc.ValidateTable(ctx) + err := desc.ValidateSelf(ctx) desc.ID = savedID return err } @@ -1258,17 +1258,39 @@ type testingDescriptorValidation bool // ensure testing specific descriptor validation happens. var PerformTestingDescriptorValidation testingDescriptorValidation = true -// Validate validates that the table descriptor is well formed. Checks include -// both single table and cross table invariants. -func (desc *wrapper) Validate(ctx context.Context, dg catalog.DescGetter) error { - err := desc.ValidateTable(ctx) - if err != nil { +// Validate performs ValidateSelf and then validates that +// each reference to another table is resolvable and that the necessary back +// references exist. +func (desc *wrapper) Validate(ctx context.Context, descGetter catalog.DescGetter) error { + if err := desc.ValidateSelf(ctx); err != nil { + return err + } + if desc.Dropped() || descGetter == nil { + return nil + } + + return errors.Wrapf(desc.validateCrossReferences(ctx, descGetter), "desc %d", desc.GetID()) +} + +// ValidateTxnCommit performs Validate and then performs additional +// pre-transaction-commit checks. +func (desc *wrapper) ValidateTxnCommit(ctx context.Context, descGetter catalog.DescGetter) error { + if err := desc.Validate(ctx, descGetter); err != nil { return err } if desc.Dropped() { return nil } - return errors.Wrapf(desc.validateCrossReferences(ctx, dg), "desc %d", desc.GetID()) + // Pre-transaction commit table validations. + + // Check that primary key exists. + if !desc.HasPrimaryKey() { + return unimplemented.NewWithIssuef(48026, + "primary key of table %s dropped without subsequent addition of new primary key", + desc.GetName()) + } + + return nil } // validateTableIfTesting is similar to validateTable, except it is only invoked @@ -1823,13 +1845,13 @@ func (desc *wrapper) ValidateIndexNameIsUnique(indexName string) error { return nil } -// ValidateTable validates that the table descriptor is well formed. Checks +// ValidateSelf validates that the table descriptor is well formed. Checks // include validating the table, column and index names, verifying that column // names and index names are unique and verifying that column IDs and index IDs // are consistent. Use Validate to validate that cross-table references are // correct. // If version is supplied, the descriptor is checked for version incompatibilities. -func (desc *wrapper) ValidateTable(ctx context.Context) error { +func (desc *wrapper) ValidateSelf(ctx context.Context) error { if err := catalog.ValidateName(desc.Name, "table"); err != nil { return err } diff --git a/pkg/sql/catalog/tabledesc/structured_test.go b/pkg/sql/catalog/tabledesc/structured_test.go index 3b0de3a118b2..27e9ca6cc9f1 100644 --- a/pkg/sql/catalog/tabledesc/structured_test.go +++ b/pkg/sql/catalog/tabledesc/structured_test.go @@ -138,6 +138,7 @@ func TestAllocateIDs(t *testing.T) { } func TestValidateDatabaseDesc(t *testing.T) { defer leaktest.AfterTest(t)() + ctx := context.Background() testData := []struct { err string @@ -195,7 +196,7 @@ func TestValidateDatabaseDesc(t *testing.T) { } for i, d := range testData { t.Run(d.err, func(t *testing.T) { - if err := d.desc.Validate(); err == nil { + if err := d.desc.Validate(ctx, nil /* descGetter */); err == nil { t.Errorf("%d: expected \"%s\", but found success: %+v", i, d.err, d.desc) } else if d.err != err.Error() && "internal error: "+d.err != err.Error() { t.Errorf("%d: expected \"%s\", but found \"%+v\"", i, d.err, err) diff --git a/pkg/sql/catalog/typedesc/type_desc.go b/pkg/sql/catalog/typedesc/type_desc.go index 88ed46ef5379..ce3da9a2aeb4 100644 --- a/pkg/sql/catalog/typedesc/type_desc.go +++ b/pkg/sql/catalog/typedesc/type_desc.go @@ -434,8 +434,8 @@ func isBeingDropped(member *descpb.TypeDescriptor_EnumMember) bool { member.Direction == descpb.TypeDescriptor_EnumMember_REMOVE } -// Validate performs validation on the TypeDescriptor. -func (desc *Immutable) Validate(ctx context.Context, dg catalog.DescGetter) error { +// ValidateSelf performs validation on the TypeDescriptor. +func (desc *Immutable) ValidateSelf(_ context.Context) error { // Validate local properties of the descriptor. if err := catalog.ValidateName(desc.Name, "type"); err != nil { return err @@ -524,8 +524,18 @@ func (desc *Immutable) Validate(ctx context.Context, dg catalog.DescGetter) erro } } + return nil +} + +// Validate performs ValidateSelf followed by +// cross reference checks on the descriptor. +func (desc *Immutable) Validate(ctx context.Context, descGetter catalog.DescGetter) error { + if err := desc.ValidateSelf(ctx); err != nil { + return err + } + // Don't validate cross-references for dropped descriptors. - if desc.Dropped() { + if desc.Dropped() || descGetter == nil { return nil } @@ -663,14 +673,13 @@ func (desc *Immutable) Validate(ctx context.Context, dg catalog.DescGetter) erro } } if !desc.Dropped() { - for _, id := range desc.ReferencingDescriptorIDs { reqs = append(reqs, id) checks = append(checks, tableExists(id)) } } - descs, err := dg.GetDescs(ctx, reqs) + descs, err := catalog.GetDescs(ctx, descGetter, reqs) if err != nil { return err } @@ -685,6 +694,11 @@ func (desc *Immutable) Validate(ctx context.Context, dg catalog.DescGetter) erro return nil } +// ValidateTxnCommit punts to Validate. +func (desc *Immutable) ValidateTxnCommit(ctx context.Context, descGetter catalog.DescGetter) error { + return desc.Validate(ctx, descGetter) +} + // TypeLookupFunc is a type alias for a function that looks up a type by ID. type TypeLookupFunc func(ctx context.Context, id descpb.ID) (tree.TypeName, catalog.TypeDescriptor, error) diff --git a/pkg/sql/conn_executor_exec.go b/pkg/sql/conn_executor_exec.go index af1bd1544032..d54eeaf6d254 100644 --- a/pkg/sql/conn_executor_exec.go +++ b/pkg/sql/conn_executor_exec.go @@ -38,7 +38,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/util" "github.com/cockroachdb/cockroach/pkg/util/cancelchecker" "github.com/cockroachdb/cockroach/pkg/util/duration" - "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" "github.com/cockroachdb/cockroach/pkg/util/fsm" "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/cockroach/pkg/util/log" @@ -749,7 +748,7 @@ func (ex *connExecutor) commitSQLTransactionInternal( } } - if err := validatePrimaryKeys(&ex.extraTxnState.descCollection); err != nil { + if err := ex.extraTxnState.descCollection.ValidateUncommittedDescriptors(ctx, ex.state.mu.txn); err != nil { return err } @@ -770,22 +769,6 @@ func (ex *connExecutor) commitSQLTransactionInternal( return nil } -// validatePrimaryKeys verifies that all tables modified in the transaction have -// an enabled primary key after potentially undergoing DROP PRIMARY KEY, which -// is required to be followed by ADD PRIMARY KEY. -func validatePrimaryKeys(tc *descs.Collection) error { - tables := tc.GetUncommittedTables() - for _, table := range tables { - if !table.HasPrimaryKey() { - return unimplemented.NewWithIssuef(48026, - "primary key of table %s dropped without subsequent addition of new primary key", - table.GetName(), - ) - } - } - return nil -} - // rollbackSQLTransaction executes a ROLLBACK statement: the KV transaction is // rolled-back and an event is produced. func (ex *connExecutor) rollbackSQLTransaction(ctx context.Context) (fsm.Event, fsm.EventPayload) { diff --git a/pkg/sql/crdb_internal.go b/pkg/sql/crdb_internal.go index 040584710734..439e90b1acc8 100644 --- a/pkg/sql/crdb_internal.go +++ b/pkg/sql/crdb_internal.go @@ -66,7 +66,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/tracing" "github.com/cockroachdb/errors" - ptypes "github.com/gogo/protobuf/types" ) // CrdbInternalName is the name of the crdb_internal schema. @@ -1149,7 +1148,6 @@ CREATE TABLE crdb_internal.node_inflight_trace_spans ( duration INTERVAL, -- The span's duration, measured from start to Finish(). -- A span whose recording is collected before it's finished will -- have the duration set as the "time of collection - start time". - num_payloads INT NOT NULL, -- The number of structured payloads in this span. operation STRING NULL -- The span's operation. )`, populate: func(ctx context.Context, p *planner, _ *dbdesc.Immutable, addRow func(...tree.Datum) error) error { @@ -1177,11 +1175,6 @@ CREATE TABLE crdb_internal.node_inflight_trace_spans ( spanDuration := rec.Duration operation := rec.Operation - var numStructured int - rec.Structured(func(any *ptypes.Any) { - numStructured++ - }) - if err := addRow( // TODO(angelapwen): we're casting uint64s to int64 here, // is that ok? @@ -1195,7 +1188,6 @@ CREATE TABLE crdb_internal.node_inflight_trace_spans ( duration.MakeDuration(spanDuration.Nanoseconds(), 0, 0), types.DefaultIntervalTypeMetadata, ), - tree.NewDInt(tree.DInt(numStructured)), tree.NewDString(operation), ); err != nil { return err diff --git a/pkg/sql/create_sequence.go b/pkg/sql/create_sequence.go index b8dc2c0050a0..2fe3b3ed610c 100644 --- a/pkg/sql/create_sequence.go +++ b/pkg/sql/create_sequence.go @@ -128,7 +128,7 @@ func doCreateSequence( } // makeSequenceTableDesc already validates the table. No call to - // desc.ValidateTable() needed here. + // desc.ValidateSelf() needed here. key := catalogkv.MakeObjectNameKey( params.ctx, @@ -231,7 +231,7 @@ func NewSequenceTableDesc( // immediately. desc.State = descpb.DescriptorState_PUBLIC - if err := desc.ValidateTable(ctx); err != nil { + if err := desc.ValidateSelf(ctx); err != nil { return nil, err } return &desc, nil diff --git a/pkg/sql/create_table.go b/pkg/sql/create_table.go index 199e787027ce..15876424efe3 100644 --- a/pkg/sql/create_table.go +++ b/pkg/sql/create_table.go @@ -1543,7 +1543,7 @@ func NewTableDesc( if t.Oid() != typedesc.TypeIDToOID(dbDesc.RegionConfig.RegionEnumID) { err = pgerror.Newf( pgcode.InvalidTableDefinition, - "cannot use column %s which has type %s in REGIONAL BY ROW AS", + "cannot use column %s which has type %s in REGIONAL BY ROW", d.Name, t.SQLString(), ) @@ -1551,11 +1551,24 @@ func NewTableDesc( ctx, typedesc.TypeIDToOID(dbDesc.RegionConfig.RegionEnumID), ); terr == nil { - err = errors.WithDetailf( - err, - "REGIONAL BY ROW AS must reference a column of type %s.", - t.Name(), - ) + if n.Locality.RegionalByRowColumn != tree.RegionalByRowRegionNotSpecifiedName { + // In this case, someone used REGIONAL BY ROW AS