From c1086e57867b82aa065182f60afab89cdf6ef24a Mon Sep 17 00:00:00 2001 From: rharding6373 Date: Fri, 1 Apr 2022 08:06:51 -0700 Subject: [PATCH] sql: enables distributed distsql queries for multi-tenant This change allows SQL queries to be distributed in multi-tenant environments. The distribution algorithm randomly assigns spans to SQL instances, but if only one instance is used the spans are assigned instead to the gateway instance. Distribution does not take locality into account, which will be implemented in a future PR. This change also supports running execbuilder tests with the 3node-tenant configuration, which is under CCL. These tests can be run in the following manner: ``` make test PKG=./pkg/ccl/logictestccl TESTS=TestTenantExecBuild ./dev test pkg/ccl/logictestccl -f=TestTenantExecBuild ``` Fixes: #80680 Release note: None --- pkg/ccl/logictestccl/BUILD.bazel | 4 +- pkg/ccl/logictestccl/logic_test.go | 22 +++++++ pkg/keys/keys.go | 20 ++++++ pkg/keys/keys_test.go | 37 ++++++----- pkg/sql/apply_join.go | 2 +- pkg/sql/conn_executor_exec.go | 2 +- pkg/sql/distsql_physical_planner.go | 62 ++++++++++++------- pkg/sql/distsql_running.go | 4 +- pkg/sql/exec_util.go | 3 - pkg/sql/explain_vec.go | 2 +- pkg/sql/logictest/logic.go | 11 ++-- .../logic_test/distsql_automatic_stats | 7 +-- .../testdata/logic_test/distsql_tenant | 22 +++++++ pkg/sql/logictest/testdata/logic_test/role | 4 +- pkg/sql/opt/exec/execbuilder/BUILD.bazel | 6 ++ .../exec/execbuilder/testdata/distsql_tenant | 36 +++++++++++ pkg/util/encoding/encoding.go | 30 +++++++-- pkg/util/encoding/encoding_test.go | 25 ++++++++ 18 files changed, 235 insertions(+), 64 deletions(-) create mode 100644 pkg/sql/logictest/testdata/logic_test/distsql_tenant create mode 100644 pkg/sql/opt/exec/execbuilder/testdata/distsql_tenant diff --git a/pkg/ccl/logictestccl/BUILD.bazel b/pkg/ccl/logictestccl/BUILD.bazel index 9511073e1a35..cb79cfd3612b 100644 --- a/pkg/ccl/logictestccl/BUILD.bazel +++ b/pkg/ccl/logictestccl/BUILD.bazel @@ -15,8 +15,9 @@ go_test( "main_test.go", ], data = glob(["testdata/**"]) + [ - "//pkg/sql/logictest:testdata", "//c-deps:libgeos", + "//pkg/sql/logictest:testdata", + "//pkg/sql/opt/exec/execbuilder:testdata", ], embed = [":logictestccl"], deps = [ @@ -29,6 +30,7 @@ go_test( "//pkg/sql/logictest", "//pkg/testutils", "//pkg/testutils/serverutils", + "//pkg/testutils/skip", "//pkg/testutils/testcluster", "//pkg/util/leaktest", "//pkg/util/randutil", diff --git a/pkg/ccl/logictestccl/logic_test.go b/pkg/ccl/logictestccl/logic_test.go index d9031d6d79ad..2ed0085e24bb 100644 --- a/pkg/ccl/logictestccl/logic_test.go +++ b/pkg/ccl/logictestccl/logic_test.go @@ -16,6 +16,7 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl" "github.com/cockroachdb/cockroach/pkg/sql/logictest" "github.com/cockroachdb/cockroach/pkg/testutils" + "github.com/cockroachdb/cockroach/pkg/testutils/skip" "github.com/cockroachdb/cockroach/pkg/util/leaktest" ) @@ -54,3 +55,24 @@ func TestTenantSQLLiteLogic(t *testing.T) { defer leaktest.AfterTest(t)() logictest.RunSQLLiteLogicTest(t, "3node-tenant") } + +// TestTenantExecBuild runs execbuilder test files under the 3node-tenant +// configuration, which constructs a secondary tenant and runs the test within +// that secondary tenant's sandbox. +func TestTenantExecBuild(t *testing.T) { + defer leaktest.AfterTest(t)() + skip.UnderDeadlock(t, "times out and/or hangs") + + testdataDir := "../../sql/opt/exec/execbuilder/testdata/" + if bazel.BuiltWithBazel() { + runfile, err := bazel.Runfile("pkg/sql/opt/exec/execbuilder/testdata/") + if err != nil { + t.Fatal(err) + } + testdataDir = runfile + } + + logictest.RunLogicTestWithDefaultConfig( + t, logictest.TestServerArgs{DisableWorkmemRandomization: true}, "3node-tenant", true, /* runCCLConfigs */ + filepath.Join(testdataDir, "[^.]*")) +} diff --git a/pkg/keys/keys.go b/pkg/keys/keys.go index 8e768cc8eafa..87d1b4b91a96 100644 --- a/pkg/keys/keys.go +++ b/pkg/keys/keys.go @@ -835,10 +835,30 @@ func GetRowPrefixLength(key roachpb.Key) (int, error) { } sqlN := len(sqlKey) + // Check that the prefix contains a valid TableID. if encoding.PeekType(sqlKey) != encoding.Int { // Not a table key, so the row prefix is the entire key. return n, nil } + tableIDLen, err := encoding.GetUvarintLen(sqlKey) + if err != nil { + return 0, err + } + + // Check whether the prefix contains a valid IndexID after the TableID. Not + // all keys contain an index ID. + if encoding.PeekType(sqlKey[tableIDLen:]) != encoding.Int { + return n, nil + } + indexIDLen, err := encoding.GetUvarintLen(sqlKey[tableIDLen:]) + if err != nil { + return 0, err + } + // If the IndexID is the last part of the key, the entire key is the prefix. + if tableIDLen+indexIDLen == sqlN { + return n, nil + } + // The column family ID length is encoded as a varint and we take advantage // of the fact that the column family ID itself will be encoded in 0-9 bytes // and thus the length of the column family ID data will fit in a single diff --git a/pkg/keys/keys_test.go b/pkg/keys/keys_test.go index 890a61ea17ea..72058eaad20e 100644 --- a/pkg/keys/keys_test.go +++ b/pkg/keys/keys_test.go @@ -622,6 +622,8 @@ func TestEnsureSafeSplitKey(t *testing.T) { expected roachpb.Key }{ {es(), es()}, // Not a table key + {es(1), es(1)}, // /Table/1 -> /Table/1 + {es(1, 2), es(1, 2)}, // /Table/1/2 -> /Table/1/2 {es(1, 2, 0), es(1, 2)}, // /Table/1/2/0 -> /Table/1/2 {es(1, 2, 1), es(1)}, // /Table/1/2/1 -> /Table/1 {es(1, 2, 2), es()}, // /Table/1/2/2 -> /Table @@ -631,6 +633,8 @@ func TestEnsureSafeSplitKey(t *testing.T) { {es(1, 2, 3, 4, 1), es(1, 2, 3)}, // /Table/1/2/3/4/1 -> /Table/1/2/3 // Same test cases, but for tenant 5. {e5(), e5()}, // Not a table key + {e5(1), e5(1)}, // /Tenant/5/Table/1 -> /Tenant/5/Table/1 + {e5(1, 2), e5(1, 2)}, // /Tenant/5/Table/1/2 -> /Tenant/5/Table/1/2 {e5(1, 2, 0), e5(1, 2)}, // /Tenant/5/Table/1/2/0 -> /Tenant/5/Table/1/2 {e5(1, 2, 1), e5(1)}, // /Tenant/5/Table/1/2/1 -> /Tenant/5/Table/1 {e5(1, 2, 2), e5()}, // /Tenant/5/Table/1/2/2 -> /Tenant/5/Table @@ -639,14 +643,16 @@ func TestEnsureSafeSplitKey(t *testing.T) { {e5(1, 2, 200, 2), e5(1, 2)}, // /Tenant/5/Table/1/2/200/2 -> /Tenant/5/Table/1/2 {e5(1, 2, 3, 4, 1), e5(1, 2, 3)}, // /Tenant/5/Table/1/2/3/4/1 -> /Tenant/5/Table/1/2/3 // Test cases using SQL encoding functions. - {MakeFamilyKey(tenSysCodec.IndexPrefix(1, 2), 0), es(1, 2)}, // /Table/1/2/0 -> /Table/1/2 - {MakeFamilyKey(tenSysCodec.IndexPrefix(1, 2), 1), es(1, 2)}, // /Table/1/2/1 -> /Table/1/2 - {MakeFamilyKey(encInt(tenSysCodec.IndexPrefix(1, 2), 3), 0), es(1, 2, 3)}, // /Table/1/2/3/0 -> /Table/1/2/3 - {MakeFamilyKey(encInt(tenSysCodec.IndexPrefix(1, 2), 3), 1), es(1, 2, 3)}, // /Table/1/2/3/1 -> /Table/1/2/3 - {MakeFamilyKey(ten5Codec.IndexPrefix(1, 2), 0), e5(1, 2)}, // /Tenant/5/Table/1/2/0 -> /Table/1/2 - {MakeFamilyKey(ten5Codec.IndexPrefix(1, 2), 1), e5(1, 2)}, // /Tenant/5/Table/1/2/1 -> /Table/1/2 - {MakeFamilyKey(encInt(ten5Codec.IndexPrefix(1, 2), 3), 0), e5(1, 2, 3)}, // /Tenant/5/Table/1/2/3/0 -> /Table/1/2/3 - {MakeFamilyKey(encInt(ten5Codec.IndexPrefix(1, 2), 3), 1), e5(1, 2, 3)}, // /Tenant/5/Table/1/2/3/1 -> /Table/1/2/3 + {encoding.EncodeStringAscending(es(1), "foo"), encoding.EncodeStringAscending(es(1), "foo")}, // Not a table key + {MakeFamilyKey(tenSysCodec.IndexPrefix(1, 2), 0), es(1, 2)}, // /Table/1/2/0 -> /Table/1/2 + {MakeFamilyKey(tenSysCodec.IndexPrefix(1, 2), 1), es(1, 2)}, // /Table/1/2/1 -> /Table/1/2 + {MakeFamilyKey(encInt(tenSysCodec.IndexPrefix(1, 2), 3), 0), es(1, 2, 3)}, // /Table/1/2/3/0 -> /Table/1/2/3 + {MakeFamilyKey(encInt(tenSysCodec.IndexPrefix(1, 2), 3), 1), es(1, 2, 3)}, // /Table/1/2/3/1 -> /Table/1/2/3 + {encoding.EncodeStringAscending(e5(1), "foo"), encoding.EncodeStringAscending(e5(1), "foo")}, // Not a table key + {MakeFamilyKey(ten5Codec.IndexPrefix(1, 2), 0), e5(1, 2)}, // /Tenant/5/Table/1/2/0 -> /Table/1/2 + {MakeFamilyKey(ten5Codec.IndexPrefix(1, 2), 1), e5(1, 2)}, // /Tenant/5/Table/1/2/1 -> /Table/1/2 + {MakeFamilyKey(encInt(ten5Codec.IndexPrefix(1, 2), 3), 0), e5(1, 2, 3)}, // /Tenant/5/Table/1/2/3/0 -> /Table/1/2/3 + {MakeFamilyKey(encInt(ten5Codec.IndexPrefix(1, 2), 3), 1), e5(1, 2, 3)}, // /Tenant/5/Table/1/2/3/1 -> /Table/1/2/3 } for i, d := range goodData { out, err := EnsureSafeSplitKey(d.in) @@ -673,24 +679,21 @@ func TestEnsureSafeSplitKey(t *testing.T) { err string }{ // Column ID suffix size is too large. - {es(1), "malformed table key"}, - {es(1, 2), "malformed table key"}, + {es(1, 2, 5), "malformed table key"}, // The table ID is invalid. {es(200)[:1], "insufficient bytes to decode uvarint value"}, - // The index ID is invalid. - {es(1, 200)[:2], "insufficient bytes to decode uvarint value"}, // The column ID suffix is invalid. + {es(1, 200)[:2], "insufficient bytes to decode uvarint value"}, {es(1, 2, 200)[:3], "insufficient bytes to decode uvarint value"}, // Exercises a former overflow bug. We decode a uint(18446744073709551610) which, if cast - // to int carelessly, results in -6. - {encoding.EncodeVarintAscending(tenSysCodec.TablePrefix(999), 322434), "malformed table key"}, + // to int carelessly, results in -6 for the column family length. + {encoding.EncodeVarintAscending(es(999, 2), 322434), "malformed table key"}, // Same test cases, but for tenant 5. - {e5(1), "malformed table key"}, - {e5(1, 2), "malformed table key"}, + {e5(1, 2, 5), "malformed table key"}, {e5(200)[:3], "insufficient bytes to decode uvarint value"}, {e5(1, 200)[:4], "insufficient bytes to decode uvarint value"}, {e5(1, 2, 200)[:5], "insufficient bytes to decode uvarint value"}, - {encoding.EncodeVarintAscending(ten5Codec.TablePrefix(999), 322434), "malformed table key"}, + {encoding.EncodeVarintAscending(e5(999, 2), 322434), "malformed table key"}, } for i, d := range errorData { _, err := EnsureSafeSplitKey(d.in) diff --git a/pkg/sql/apply_join.go b/pkg/sql/apply_join.go index 1447709edaa6..fb8812f0fa48 100644 --- a/pkg/sql/apply_join.go +++ b/pkg/sql/apply_join.go @@ -308,7 +308,7 @@ func runPlanInsidePlan(params runParams, plan *planComponents, resultWriter rowR ) distributeType := DistributionType(DistributionTypeNone) if distributePlan.WillDistribute() { - distributeType = DistributionTypeSystemTenantOnly + distributeType = DistributionTypeAlways } planCtx := params.p.extendedEvalCtx.ExecCfg.DistSQLPlanner.NewPlanningCtx( params.ctx, evalCtx, &plannerCopy, params.p.txn, distributeType) diff --git a/pkg/sql/conn_executor_exec.go b/pkg/sql/conn_executor_exec.go index be1844ae402c..c4833c8c936b 100644 --- a/pkg/sql/conn_executor_exec.go +++ b/pkg/sql/conn_executor_exec.go @@ -1142,7 +1142,7 @@ func (ex *connExecutor) dispatchToExecutionEngine( } distribute := DistributionType(DistributionTypeNone) if distributePlan.WillDistribute() { - distribute = DistributionTypeSystemTenantOnly + distribute = DistributionTypeAlways } ex.sessionTracing.TraceExecStart(ctx, "distributed") stats, err := ex.execWithDistSQLEngine( diff --git a/pkg/sql/distsql_physical_planner.go b/pkg/sql/distsql_physical_planner.go index 119402a3ec0c..546cde804918 100644 --- a/pkg/sql/distsql_physical_planner.go +++ b/pkg/sql/distsql_physical_planner.go @@ -437,6 +437,12 @@ func mustWrapValuesNode(planCtx *PlanningCtx, specifiedInQuery bool) bool { func checkSupportForPlanNode(node planNode) (distRecommendation, error) { switch n := node.(type) { // Keep these cases alphabetized, please! + case *createStatsNode: + if n.runAsJob { + return cannotDistribute, planNodeNotSupportedErr + } + return shouldDistribute, nil + case *distinctNode: return checkSupportForPlanNode(n.plan) @@ -652,12 +658,6 @@ func checkSupportForPlanNode(node planNode) (distRecommendation, error) { } return shouldDistribute, nil - case *createStatsNode: - if n.runAsJob { - return cannotDistribute, planNodeNotSupportedErr - } - return shouldDistribute, nil - default: return cannotDistribute, planNodeNotSupportedErr } @@ -1165,7 +1165,7 @@ func (dsp *DistSQLPlanner) partitionSpansTenant( return partitions, nil } if dsp.sqlInstanceProvider == nil { - return nil, errors.New("sql instance provider not available in multi-tenant environment") + return nil, errors.AssertionFailedf("sql instance provider not available in multi-tenant environment") } // GetAllInstances only returns healthy instances. instances, err := dsp.sqlInstanceProvider.GetAllInstances(ctx) @@ -1183,11 +1183,24 @@ func (dsp *DistSQLPlanner) partitionSpansTenant( // nodeMap maps a SQLInstanceID to an index inside the partitions array. nodeMap := make(map[base.SQLInstanceID]int) + var lastKey roachpb.Key + var lastIdx int for i := range spans { span := spans[i] if log.V(1) { log.Infof(ctx, "partitioning span %s", span) } + // Rows with column families may have been split into different spans. These + // spans should be assigned the same pod so that the pod can stitch together + // the rows correctly. Split rows are in adjacent spans. + if safeKey, err := keys.EnsureSafeSplitKey(span.Key); err == nil { + if safeKey.Equal(lastKey) { + partition := &partitions[lastIdx] + partition.Spans = append(partition.Spans, span) + continue + } + lastKey = safeKey + } sqlInstanceID := instances[i%len(instances)].InstanceID partitionIdx, inNodeMap := nodeMap[sqlInstanceID] if !inNodeMap { @@ -1197,6 +1210,13 @@ func (dsp *DistSQLPlanner) partitionSpansTenant( } partition := &partitions[partitionIdx] partition.Spans = append(partition.Spans, span) + lastIdx = partitionIdx + } + // If spans were only assigned to one SQL instance, then assign them all to + // the gateway instance. The primary reason is to avoid an extra hop. + // TODO(harding): Don't do this if using an instance in another locality. + if len(partitions) == 1 && partitions[0].SQLInstanceID != dsp.gatewaySQLInstanceID { + partitions[0].SQLInstanceID = dsp.gatewaySQLInstanceID } return partitions, nil } @@ -2902,6 +2922,20 @@ func (dsp *DistSQLPlanner) createPhysPlanForPlanNode( switch n := node.(type) { // Keep these cases alphabetized, please! + case *createStatsNode: + if n.runAsJob { + plan, err = dsp.wrapPlan(ctx, planCtx, n, false /* allowPartialDistribution */) + } else { + // Create a job record but don't actually start the job. + var record *jobs.Record + record, err = n.makeJobRecord(ctx) + if err != nil { + return nil, err + } + plan, err = dsp.createPlanForCreateStats(ctx, planCtx, 0, /* jobID */ + record.Details.(jobspb.CreateStatsDetails)) + } + case *distinctNode: plan, err = dsp.createPlanForDistinct(ctx, planCtx, n) @@ -3022,20 +3056,6 @@ func (dsp *DistSQLPlanner) createPhysPlanForPlanNode( case *zigzagJoinNode: plan, err = dsp.createPlanForZigzagJoin(planCtx, n) - case *createStatsNode: - if n.runAsJob { - plan, err = dsp.wrapPlan(ctx, planCtx, n, false /* allowPartialDistribution */) - } else { - // Create a job record but don't actually start the job. - var record *jobs.Record - record, err = n.makeJobRecord(ctx) - if err != nil { - return nil, err - } - plan, err = dsp.createPlanForCreateStats(ctx, planCtx, 0, /* jobID */ - record.Details.(jobspb.CreateStatsDetails)) - } - default: // Can't handle a node? We wrap it and continue on our way. plan, err = dsp.wrapPlan(ctx, planCtx, n, false /* allowPartialDistribution */) diff --git a/pkg/sql/distsql_running.go b/pkg/sql/distsql_running.go index eb6360edd756..ea4ba94d90bd 100644 --- a/pkg/sql/distsql_running.go +++ b/pkg/sql/distsql_running.go @@ -1255,7 +1255,7 @@ func (dsp *DistSQLPlanner) planAndRunSubquery( ).WillDistribute() distribute := DistributionType(DistributionTypeNone) if distributeSubquery { - distribute = DistributionTypeSystemTenantOnly + distribute = DistributionTypeAlways } subqueryPlanCtx := dsp.NewPlanningCtx(ctx, evalCtx, planner, planner.txn, distribute) @@ -1602,7 +1602,7 @@ func (dsp *DistSQLPlanner) planAndRunPostquery( ).WillDistribute() distribute := DistributionType(DistributionTypeNone) if distributePostquery { - distribute = DistributionTypeSystemTenantOnly + distribute = DistributionTypeAlways } postqueryPlanCtx := dsp.NewPlanningCtx(ctx, evalCtx, planner, planner.txn, distribute) diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index 11e041d70c87..6a4cc6d9ee1c 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -1627,9 +1627,6 @@ func getPlanDistribution( return physicalplan.LocalPlan } - if _, singleTenant := nodeID.OptionalNodeID(); !singleTenant { - return physicalplan.LocalPlan - } if distSQLMode == sessiondatapb.DistSQLOff { return physicalplan.LocalPlan } diff --git a/pkg/sql/explain_vec.go b/pkg/sql/explain_vec.go index 3f9fd78c9ab6..8f64ff4e7ca7 100644 --- a/pkg/sql/explain_vec.go +++ b/pkg/sql/explain_vec.go @@ -109,7 +109,7 @@ func newPlanningCtxForExplainPurposes( ) *PlanningCtx { distribute := DistributionType(DistributionTypeNone) if distribution.WillDistribute() { - distribute = DistributionTypeSystemTenantOnly + distribute = DistributionTypeAlways } planCtx := distSQLPlanner.NewPlanningCtx(params.ctx, params.extendedEvalCtx, params.p, params.p.txn, distribute) diff --git a/pkg/sql/logictest/logic.go b/pkg/sql/logictest/logic.go index 5909c89ac3b0..c0bf0f8d4ead 100644 --- a/pkg/sql/logictest/logic.go +++ b/pkg/sql/logictest/logic.go @@ -820,10 +820,11 @@ var logicTestConfigs = []testClusterConfig{ // logictest command. // To run a logic test with this config as a directive, run: // make test PKG=./pkg/ccl/logictestccl TESTS=TestTenantLogic// - name: threeNodeTenantConfigName, - numNodes: 3, - useTenant: true, - isCCLConfig: true, + name: threeNodeTenantConfigName, + numNodes: 3, + useTenant: true, + isCCLConfig: true, + overrideDistSQLMode: "on", }, // Regions and zones below are named deliberately, and contain "-"'s to be reflective // of the naming convention in public clouds. "-"'s are handled differently in SQL @@ -3403,7 +3404,7 @@ func (t *logicTest) verifyError( } else { newErr := errors.Errorf("%s: %s\nexpected error code %q, but found success", pos, sql, expectErrCode) - return (err != nil), newErr + return err != nil, newErr } } return true, nil diff --git a/pkg/sql/logictest/testdata/logic_test/distsql_automatic_stats b/pkg/sql/logictest/testdata/logic_test/distsql_automatic_stats index e66e23a31cf3..ccfaa52d4d9c 100644 --- a/pkg/sql/logictest/testdata/logic_test/distsql_automatic_stats +++ b/pkg/sql/logictest/testdata/logic_test/distsql_automatic_stats @@ -1,8 +1,4 @@ -# LogicTest: !metamorphic !3node-tenant - -# Note: this test is disabled on 3node-tenant because it sometimes causes one of -# the UPDATE statements below (where we update more than 20% of the table) to be -# flaky. See comments there for details. +# LogicTest: !metamorphic # Disable automatic stats statement ok @@ -79,6 +75,7 @@ UPDATE data SET d = 12 WHERE d = 10 # For some reason, 3node-tenant occasionally splits the UPDATE into 4 pieces, # with each one affecting at most 88 rows. Since 88 < 205, the refresh is not # guaranteed, making this test flaky. +skipif config 3node-tenant query TTIII colnames,rowsort,retry SELECT DISTINCT ON (column_names) statistics_name, column_names, row_count, distinct_count, null_count FROM [SHOW STATISTICS FOR TABLE data] ORDER BY column_names ASC, created DESC diff --git a/pkg/sql/logictest/testdata/logic_test/distsql_tenant b/pkg/sql/logictest/testdata/logic_test/distsql_tenant new file mode 100644 index 000000000000..317984c361c9 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/distsql_tenant @@ -0,0 +1,22 @@ +# LogicTest: 3node-tenant + +statement ok +CREATE TABLE t (k INT PRIMARY KEY, v INT, w INT, x INT, + FAMILY fam_0 (k), + FAMILY fam_1 (x), + FAMILY fam_2 (v, w) +) + +statement ok +INSERT INTO t VALUES (23, 1, 2, 3), (34, 3, 4, 8); + +query IIII +SELECT * FROM t WHERE k < 10 OR (k > 20 AND k < 29) OR k > 40 +---- +23 1 2 3 + +query II +SELECT v, w FROM t WHERE k = 23 +---- +1 2 + diff --git a/pkg/sql/logictest/testdata/logic_test/role b/pkg/sql/logictest/testdata/logic_test/role index e17be6d35709..adaec0fc1fc5 100644 --- a/pkg/sql/logictest/testdata/logic_test/role +++ b/pkg/sql/logictest/testdata/logic_test/role @@ -292,7 +292,7 @@ role_name member is_admin testrole testuser true testrole testuser2 true -query TTB colnames +query TTB colnames,rowsort SHOW GRANTS ON ROLE FOR testuser, testuser2 ---- role_name member is_admin @@ -300,7 +300,7 @@ admin testuser false testrole testuser true testrole testuser2 true -query TTB colnames +query TTB colnames,rowsort SHOW GRANTS ON ROLE admin, testrole FOR root, testuser2 ---- role_name member is_admin diff --git a/pkg/sql/opt/exec/execbuilder/BUILD.bazel b/pkg/sql/opt/exec/execbuilder/BUILD.bazel index 0cde52877ca6..da6fdeac4803 100644 --- a/pkg/sql/opt/exec/execbuilder/BUILD.bazel +++ b/pkg/sql/opt/exec/execbuilder/BUILD.bazel @@ -80,3 +80,9 @@ go_test( "//pkg/util/randutil", ], ) + +filegroup( + name = "testdata", + srcs = glob(["testdata/**"]), + visibility = ["//pkg/ccl/logictestccl:__subpackages__"], +) diff --git a/pkg/sql/opt/exec/execbuilder/testdata/distsql_tenant b/pkg/sql/opt/exec/execbuilder/testdata/distsql_tenant new file mode 100644 index 000000000000..baf584d6bf8d --- /dev/null +++ b/pkg/sql/opt/exec/execbuilder/testdata/distsql_tenant @@ -0,0 +1,36 @@ +# LogicTest: 3node-tenant +# tenant-cluster-setting-override-opt: allow-zone-configs-for-secondary-tenants allow-multi-region-abstractions-for-secondary-tenants + +statement ok +CREATE TABLE tbl1 (a INT PRIMARY KEY, b INT) + +query T +EXPLAIN SELECT * FROM tbl1 WHERE a < 3 OR (a > 7 AND a < 9) OR a > 14 +---- +distribution: full +vectorized: true +· +• scan + missing stats + table: tbl1@tbl1_pkey + spans: [ - /2] [/8 - /8] [/15 - ] + +statement ok +CREATE TABLE tbl2 (k INT PRIMARY KEY, v INT, w INT, x INT, + FAMILY fam_0 (k), + FAMILY fam_1 (x), + FAMILY fam_2 (v, w) +) + +# This should be a local query, even though it uses two spans due to the column +# family configuration. +query T +EXPLAIN SELECT v, w FROM tbl2 WHERE k = 23 +---- +distribution: local +vectorized: true +· +• scan + missing stats + table: tbl2@tbl2_pkey + spans: [/23 - /23] diff --git a/pkg/util/encoding/encoding.go b/pkg/util/encoding/encoding.go index ebe39b178477..b7f33fc88bb4 100644 --- a/pkg/util/encoding/encoding.go +++ b/pkg/util/encoding/encoding.go @@ -54,11 +54,11 @@ const ( // The gap between floatNaNDesc and bytesMarker was left for // compatibility reasons. bytesMarker byte = 0x12 - bytesDescMarker byte = bytesMarker + 1 - timeMarker byte = bytesDescMarker + 1 - durationBigNegMarker byte = timeMarker + 1 // Only used for durations < MinInt64 nanos. - durationMarker byte = durationBigNegMarker + 1 - durationBigPosMarker byte = durationMarker + 1 // Only used for durations > MaxInt64 nanos. + bytesDescMarker = bytesMarker + 1 + timeMarker = bytesDescMarker + 1 + durationBigNegMarker = timeMarker + 1 // Only used for durations < MinInt64 nanos. + durationMarker = durationBigNegMarker + 1 + durationBigPosMarker = durationMarker + 1 // Only used for durations > MaxInt64 nanos. decimalNaN = durationBigPosMarker + 1 // 24 decimalNegativeInfinity = decimalNaN + 1 @@ -471,6 +471,26 @@ func EncLenUvarintDescending(v uint64) int { return 2 + highestByteIndex(v) } +// GetUvarintLen returns the length of the prefix that encodes a uint64 value +// without actually decoding the value. An error is returned if b does not +// contain a valid encoding of an unsigned int datum. +func GetUvarintLen(b []byte) (int, error) { + if len(b) == 0 { + return 0, errors.Errorf("insufficient bytes to decode uvarint value") + } + length := int(b[0]) - intZero + if length <= intSmall { + return 1, nil + } + length -= intSmall + if length < 0 || length > 8 { + return 0, errors.Errorf("invalid uvarint length of %d", length) + } else if len(b) <= length { + return 0, errors.Errorf("insufficient bytes to decode uvarint value: %q", b) + } + return 1 + length, nil +} + // DecodeUvarintAscending decodes a uint64 encoded uint64 from the input // buffer. The remainder of the input buffer and the decoded uint64 // are returned. diff --git a/pkg/util/encoding/encoding_test.go b/pkg/util/encoding/encoding_test.go index c91c39878734..1ca36827c3f6 100644 --- a/pkg/util/encoding/encoding_test.go +++ b/pkg/util/encoding/encoding_test.go @@ -336,6 +336,31 @@ func TestEncodedLengthUvarintAscending(t *testing.T) { } } +func TestGetUvarintLen(t *testing.T) { + for i := 0; i < 100; i++ { + v := rand.Uint64() + enc := EncodeUvarintAscending(nil, v) + exp := len(enc) + actual, err := GetUvarintLen(enc) + if err != nil { + t.Fatal(err) + } + if actual != exp { + t.Fatalf("incorrect encoded length for %d: %d (expected %d)", v, actual, exp) + } + b, dec, err := DecodeUvarintAscending(enc) + if err != nil { + t.Fatal(err) + } + if dec != v { + t.Fatalf("incorrect decoded value: %d (expected %d)", dec, v) + } + if len(b) != 0 { + t.Fatalf("incorrect decoded length for %d: %d (expected %d)", v, exp-len(b), exp) + } + } +} + func TestEncodeDecodeUvarintDescending(t *testing.T) { testBasicEncodeDecodeUint64(EncodeUvarintDescending, DecodeUvarintDescending, true, true, true, t) testCases := []testCaseUint64{