diff --git a/pkg/roachpb/merge_spans.go b/pkg/roachpb/combine_spans.go similarity index 62% rename from pkg/roachpb/merge_spans.go rename to pkg/roachpb/combine_spans.go index bd8ee25e36e7..c7ce14e55343 100644 --- a/pkg/roachpb/merge_spans.go +++ b/pkg/roachpb/combine_spans.go @@ -39,6 +39,7 @@ func (s sortedSpans) Len() int { // MergeSpans sorts the incoming spans and merges overlapping spans. Returns // true iff all of the spans are distinct. +// The input spans is not safe for re-use. func MergeSpans(spans []Span) ([]Span, bool) { if len(spans) == 0 { return spans, true @@ -100,3 +101,67 @@ func MergeSpans(spans []Span) ([]Span, bool) { } return r, distinct } + +// IntersectSpans returns the logical intersection of all spans represented as +// a slice of one Span. +// The input spans is not safe for re-use. +func IntersectSpans(spans []Span) []Span { + if len(spans) == 0 { + return spans + } + + sort.Sort(sortedSpans(spans)) + + // We build up the final one-element slice of spans using the same + // backing slice. This is safe since we are never expanding the final + // slice into the backing slice, thus yet-to-be-processed spans are + // never overwritten. + r := spans[:1] + // The previous element(s) is always folded into the first span. + prev := &r[0] + + for _, cur := range spans[1:] { + if len(cur.EndKey) == 0 && len(prev.EndKey) == 0 { + if cur.Key.Compare(prev.Key) != 0 { + // [a, nil] intersect [b, nil] + return nil + } + // [a, nil] intersect [a, nil] + continue + } + + if len(prev.EndKey) == 0 { + if prev.Key.Compare(cur.Key) < 0 { + // [a, nil] intersect [b, c] + return nil + } + // [a, nil] intersect [a, b] + // [b, nil] intersect [a, c] + continue + } + + if c := prev.EndKey.Compare(cur.Key); c > 0 { + if cur.EndKey != nil { + if prev.EndKey.Compare(cur.EndKey) <= 0 { + // [a, c] intersect [b, d] + // [a, c] intersect [b, c] + prev.Key = cur.Key + } else { + // [a, d] intersect [b, c] + prev.Key = cur.Key + prev.EndKey = cur.EndKey + } + } else { + // [a, c] intersect [b, nil] + prev.Key = cur.Key + prev.EndKey = cur.EndKey + } + continue + } + // [a, b] intersect [b, X] + // [a, b] intersect [c, X] + return nil + } + + return r +} diff --git a/pkg/roachpb/merge_spans_test.go b/pkg/roachpb/combine_spans_test.go similarity index 61% rename from pkg/roachpb/merge_spans_test.go rename to pkg/roachpb/combine_spans_test.go index 86667ceaeda5..b6b95c590c76 100644 --- a/pkg/roachpb/merge_spans_test.go +++ b/pkg/roachpb/combine_spans_test.go @@ -20,24 +20,25 @@ import ( "testing" ) -func TestMergeSpans(t *testing.T) { - makeSpan := func(s string) Span { - parts := strings.Split(s, "-") - if len(parts) == 2 { - return Span{Key: Key(parts[0]), EndKey: Key(parts[1])} - } - return Span{Key: Key(s)} +func makeSpan(s string) Span { + parts := strings.Split(s, "-") + if len(parts) == 2 { + return Span{Key: Key(parts[0]), EndKey: Key(parts[1])} } - makeSpans := func(s string) []Span { - var spans []Span - if len(s) > 0 { - for _, p := range strings.Split(s, ",") { - spans = append(spans, makeSpan(p)) - } + return Span{Key: Key(s)} +} + +func makeSpans(s string) []Span { + var spans []Span + if len(s) > 0 { + for _, p := range strings.Split(s, ",") { + spans = append(spans, makeSpan(p)) } - return spans } + return spans +} +func TestMergeSpans(t *testing.T) { testCases := []struct { spans string expected string @@ -69,3 +70,35 @@ func TestMergeSpans(t *testing.T) { } } } + +func TestIntersectSpans(t *testing.T) { + testCases := []struct { + spans string + expected string + }{ + {"", ""}, + {"a", "a"}, + {"a,b", ""}, + {"b,a", ""}, + {"a,a", "a"}, + {"a-b", "a-b"}, + {"a-b,b-c", ""}, + {"a-c,a-b", "a-b"}, + {"a,b-c", ""}, + {"a,a-c", "a"}, + {"a-c,b", "b"}, + {"a-c,c", ""}, + {"a-c,b-bb", "b-bb"}, + {"b-bb,a-c", "b-bb"}, + {"a-c,b-c", "b-c"}, + {"a-b,c-d", ""}, + {"a-b,a-b,a-b,b", ""}, + } + for i, c := range testCases { + spans := IntersectSpans(makeSpans(c.spans)) + expected := makeSpans(c.expected) + if !reflect.DeepEqual(expected, spans) { + t.Fatalf("%d: expected\n%s\n, but found:\n%s", i, expected, spans) + } + } +} diff --git a/pkg/sql/distsql_join.go b/pkg/sql/distsql_join.go new file mode 100644 index 000000000000..ad3ea1e2000c --- /dev/null +++ b/pkg/sql/distsql_join.go @@ -0,0 +1,393 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package sql + +import ( + "math" + + "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/sql/distsqlplan" + "github.com/cockroachdb/cockroach/pkg/sql/distsqlrun" + "github.com/cockroachdb/cockroach/pkg/sql/parser" + "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" + "github.com/cockroachdb/cockroach/pkg/util/encoding" +) + +func (dsp *DistSQLPlanner) createPlanForInterleaveJoin( + planCtx *planningCtx, n *joinNode, +) (physicalPlan, error) { + leftScan, leftOk := n.left.plan.(*scanNode) + rightScan, rightOk := n.right.plan.(*scanNode) + if !leftOk || !rightOk { + panic("cannot plan interleave join with non-scan left and right nodes") + } + + if leftScan.reverse != rightScan.reverse { + panic("cannot plan interleave join with opposite scan directions") + } + + if len(n.mergeJoinOrdering) <= 0 { + panic("cannot plan interleave join with no equality join columns") + } + + if len(n.mergeJoinOrdering) != len(n.pred.leftEqualityIndices) { + panic("cannot plan interleave join on a subset of equality join columns") + } + + // We iterate through each table and collate their metadata for + // the InterleaveReaderJoinerSpec. + tables := make([]distsqlrun.InterleaveReaderJoinerTable, 2) + plans := make([]physicalPlan, 2) + var totalLimitHint int64 + for i, t := range []struct { + scan *scanNode + eqIndices []int + }{ + { + scan: leftScan, + eqIndices: n.pred.leftEqualityIndices, + }, + { + scan: rightScan, + eqIndices: n.pred.rightEqualityIndices, + }, + } { + // We don't really need to initialize a full-on plan to + // retrieve the metadata for each table reader, but this turns + // out to be very useful to compute ordering and remapping the + // onCond and columns. + var err error + if plans[i], err = dsp.createTableReaders(planCtx, t.scan, nil); err != nil { + return physicalPlan{}, err + } + + eqCols := eqCols(t.eqIndices, plans[i].planToStreamColMap) + ordering := distsqlOrdering(n.mergeJoinOrdering, eqCols) + + // Doesn't matter which processor we choose since the metadata + // for TableReader is independent of node/processor instance. + tr := plans[i].Processors[0].Spec.Core.TableReader + + tables[i] = distsqlrun.InterleaveReaderJoinerTable{ + Desc: tr.Table, + IndexIdx: tr.IndexIdx, + Post: plans[i].GetLastStagePost(), + Ordering: ordering, + } + + if tr.LimitHint >= math.MaxInt64-totalLimitHint { + totalLimitHint = math.MaxInt64 + } else { + totalLimitHint += tr.LimitHint + } + } + + // We need to combine the two set of spans from the left and right side + // since we have one reading processor (InterleaveReaderJoiner). + var spans []roachpb.Span + var joinType distsqlrun.JoinType + switch n.joinType { + case joinTypeInner: + // We can take the intersection of the two set of spans for + // inner joins because we never need rows from either table + // that do not satisfy the ON condition. + // Since equality columns map 1-1 with the columns of the + // interleave prefix, we need only scan interleaves together. + // Spans for interleaved tables/indexes always refer to their + // root ancestor and thus incorporate all interleaves of the + // ancestors between the root and itself. + spans = roachpb.IntersectSpans(append(leftScan.spans, rightScan.spans...)) + joinType = distsqlrun.JoinType_INNER + default: + // TODO(richardwu): For outer joins, we will need to take the + // union of spans and propagate scan.origFilters down into the + // spec. See comment above spans in InterleaveReaderJoinerSpec. + panic("can only plan inner joins with interleave joins") + } + + post, joinToStreamColMap := joinOutColumns(n, plans[0], plans[1]) + onExpr := remapOnExpr(planCtx.evalCtx, n, plans[0], plans[1]) + + // We partition the combined spans to their data nodes (best effort). + spanPartitions, err := dsp.partitionSpans(planCtx, spans) + if err != nil { + return physicalPlan{}, err + } + // For a given parent row, we need to ensure each + // InterleaveReaderJoiner processor reads all children rows. If + // children rows for a given parent row is partitioned into a different + // node, then we will miss joining those children rows to the parent + // row. + // We thus fix the spans such that the end key always includes the last + // children. + if spanPartitions, err = fixInterleavePartitions(n, spanPartitions); err != nil { + return physicalPlan{}, err + } + + var p physicalPlan + stageID := p.NewStageID() + + // We provision a separate InterleaveReaderJoiner per node that has + // rows from both tables. + p.ResultRouters = make([]distsqlplan.ProcessorIdx, len(spanPartitions)) + for _, sp := range spanPartitions { + irj := &distsqlrun.InterleaveReaderJoinerSpec{ + Tables: tables, + // We previously checked that both scans are in the + // same direction. + Reverse: leftScan.reverse, + LimitHint: totalLimitHint, + OnExpr: onExpr, + Type: joinType, + } + + irj.Spans = make([]distsqlrun.TableReaderSpan, len(sp.spans)) + for i, span := range sp.spans { + irj.Spans[i].Span = span + } + + proc := distsqlplan.Processor{ + Node: sp.node, + Spec: distsqlrun.ProcessorSpec{ + Core: distsqlrun.ProcessorCoreUnion{InterleaveReaderJoiner: irj}, + Post: post, + Output: []distsqlrun.OutputRouterSpec{{Type: distsqlrun.OutputRouterSpec_PASS_THROUGH}}, + StageID: stageID, + }, + } + + p.Processors = append(p.Processors, proc) + } + + p.planToStreamColMap = joinToStreamColMap + p.ResultTypes = getTypesForPlanResult(n, joinToStreamColMap) + + p.SetMergeOrdering(dsp.convertOrdering(n.props, p.planToStreamColMap)) + return p, nil +} + +func joinOutColumns( + n *joinNode, leftPlan, rightPlan physicalPlan, +) (post distsqlrun.PostProcessSpec, joinToStreamColMap []int) { + joinToStreamColMap = makePlanToStreamColMap(len(n.columns)) + post.Projection = true + + // addOutCol appends to post.OutputColumns and returns the index + // in the slice of the added column. + addOutCol := func(col uint32) int { + idx := len(post.OutputColumns) + post.OutputColumns = append(post.OutputColumns, col) + return idx + } + + // The join columns are in two groups: + // - the columns on the left side (numLeftCols) + // - the columns on the right side (numRightCols) + joinCol := 0 + for i := 0; i < n.pred.numLeftCols; i++ { + if !n.columns[joinCol].Omitted { + joinToStreamColMap[joinCol] = addOutCol(uint32(leftPlan.planToStreamColMap[i])) + } + joinCol++ + } + for i := 0; i < n.pred.numRightCols; i++ { + if !n.columns[joinCol].Omitted { + joinToStreamColMap[joinCol] = addOutCol( + uint32(rightPlan.planToStreamColMap[i] + len(leftPlan.ResultTypes)), + ) + } + joinCol++ + } + + return post, joinToStreamColMap +} + +// remapOnExpr remaps ordinal references in the on condition (which refer to the +// join columns as described above) to values that make sense in the joiner (0 +// to N-1 for the left input columns, N to N+M-1 for the right input columns). +func remapOnExpr(evalCtx *parser.EvalContext, n *joinNode, leftPlan, rightPlan physicalPlan) distsqlrun.Expression { + if n.pred.onCond == nil { + return distsqlrun.Expression{} + } + + joinColMap := make([]int, len(n.columns)) + idx := 0 + for i := 0; i < n.pred.numLeftCols; i++ { + joinColMap[idx] = leftPlan.planToStreamColMap[i] + idx++ + } + for i := 0; i < n.pred.numRightCols; i++ { + joinColMap[idx] = rightPlan.planToStreamColMap[i] + len(leftPlan.ResultTypes) + idx++ + } + + return distsqlplan.MakeExpression(n.pred.onCond, evalCtx, joinColMap) +} + +// eqCols produces a slice of ordinal references for the plan columns specified +// in eqIndices using planToColMap. +// That is: eqIndices contains a slice of plan column indexes and planToColMap +// maps the plan column indexes to the ordinal references (index of the +// intermediate row produced). +func eqCols(eqIndices, planToColMap []int) []uint32 { + eqCols := make([]uint32, len(eqIndices)) + for i, planCol := range eqIndices { + eqCols[i] = uint32(planToColMap[planCol]) + } + + return eqCols +} + +// distsqlOrdering converts the ordering specified by mergeJoinOrdering in +// terms of the index of eqCols to the ordinal references provided by eqCols. +func distsqlOrdering( + mergeJoinOrdering sqlbase.ColumnOrdering, eqCols []uint32, +) distsqlrun.Ordering { + var ord distsqlrun.Ordering + ord.Columns = make([]distsqlrun.Ordering_Column, len(mergeJoinOrdering)) + for i, c := range mergeJoinOrdering { + ord.Columns[i].ColIdx = eqCols[c.ColIdx] + dir := distsqlrun.Ordering_Column_ASC + if c.Direction == encoding.Descending { + dir = distsqlrun.Ordering_Column_DESC + } + ord.Columns[i].Direction = dir + } + + return ord +} + +// fixInterleavePartitions ensures that every span in every spanPartition +// will be gauranteed to read every descendant row interleaved under the last +// ancestor row, where ancestor and descendant are defined based on the left +// and right children of joinNode. +// +// Given a span with the start and end keys: +// StartKey: /parent/1/2/#/child/2 +// EndKey: /parent/1/42/#/child/4 +// We'd like to fix the EndKey such that the span contains all children keys +// for the given parent ID 42. The end result we want is +// StartKey: /parent/1/2/#/child/2 +// EndKey: /parent/1/43 +// To do this, we must truncate up to the prefix /parent/1/42 to invoke +// encoding.PrefixEnd() to produce /parent/1/43. +// To calculate how long this prefix is, we take a look at the general encoding +// of a descendant EndKey +// /<1st-tbl>/<1st-idx>/<1st-col1>/.../<1st-colN>/#/<2nd-tbl>/<2nd-idx>/<2nd-col1>/.../<2nd-colM>/#/... +// For each ancestor (i.e. 1st, 2nd), we have +// , , '#' (interleave sentinel) +// or 3 values to peek at. +// We need to subtract 1 since we do not want to include the last interleave sentinel '#'. +// Furthermore, we need to count the number of columns in the shared interleave +// prefix (i.e. <1st-colX>, <2nd-colY>). We traverse the InterleaveDescriptor +// and add up SharedPrefixLen. +// Thus we need to peek (encoding.PeekLength()) +// 3 * count(interleave ancestors) + sum(SharedPrefixLen) - 1 +// times. +// +// Example: +// Given the following interleave hierarchy where their primary indexes +// are in parentheses +// parent (pid1) +// child (pid1, cid1, cid2) +// grandchild (pid1, cid1, cid2, gcid1) +// Let ancestor = parent and descendant = grandchild. +// A descendant span key could be +// ////#/////#//... +// We'd like to take the prefix up to and including . To do so, we must +// call encoding.PeekLength() 8 times or +// 3 * nAncestors + sum(SharedPrefixLen) - 1 = 3 * 2 + (1 + 2) - 1 = 8 +func fixInterleavePartitions(n *joinNode, partitions []spanPartition) ([]spanPartition, error) { + ancestor, descendant := n.interleaveNodes() + if ancestor == nil || descendant == nil { + panic("cannot fix interleave join spans since there is no interleave relation") + } + + // The number of times we need to invoke encoding.PeekLength() requires + // the number of ancestors up to and including our ancestor with respect + // to descendant. + // We also need to calculate the number of shared column fields + // (interleave prefixes) from every ancestor. + nAncestors := 0 + sharedPrefixLen := 0 + for _, descAncestor := range descendant.index.Interleave.Ancestors { + nAncestors++ + sharedPrefixLen += int(descAncestor.SharedPrefixLen) + if descAncestor.TableID == ancestor.desc.ID && descAncestor.IndexID == ancestor.index.ID { + break + } + } + + // We iterate over every span in our partitions and adjust their EndKey + // as described above. + for i := range partitions { + for j := 0; j < len(partitions[i].spans); j++ { + span := &partitions[i].spans[j] + endKey := span.EndKey + prefixLen := 0 + earlyTermination := false + // See the formula above for computing the number of + // times to invoke encoding.PeekLength(). + for i := 0; i < 3*nAncestors+sharedPrefixLen-1; i++ { + // It's possible for the span key to not + // contain the full prefix up to the descendant + // (e.g. when no filter is specified on any + // interleave prefix columns, the key will be + // in the form /ancestor/indexid instead of + // /ancestor/indexid/pid1). + // This is fine since we only care about EndKeys + // which terminate in between children rows (e.g. + // /ancestor/indexid/.../#/child/indexid/2). + // Any keys shorter than the maximum prefix + // length are guaranteed to include all + // interleaved descendant rows. + if len(endKey) == 0 { + earlyTermination = true + break + } + valLen, err := encoding.PeekLength(endKey) + if err != nil { + return nil, err + } + prefixLen += valLen + endKey = endKey[valLen:] + } + + // We only need to fix the key if the key is in between + // children rows, which can only happen if the key has + // a prefix up to the ancestor and then some. + if !earlyTermination && len(endKey) > 0 { + span.EndKey = span.EndKey[:prefixLen].PrefixEnd() + + // Adjust the start Key of the next span. + nextIdx := j + 1 + // It's possible that span.EndKey now subsumes + // subsequent spans in the partition. + // We remove those redundant spans and from our partition. + for nextIdx < len(partitions[i].spans) && span.EndKey.Compare(partitions[i].spans[nextIdx].EndKey) >= 0 { + partitions[i].spans = append(partitions[i].spans[:nextIdx], partitions[i].spans[nextIdx+1:]...) + } + + // We adjust the start key of the next span in + // the redacted partition (if there is one). + if nextIdx < len(partitions[i].spans) { + partitions[i].spans[nextIdx].Key = span.EndKey + } + } + } + } + + return partitions, nil +} diff --git a/pkg/sql/distsql_join_test.go b/pkg/sql/distsql_join_test.go new file mode 100644 index 000000000000..030d2e01ee7e --- /dev/null +++ b/pkg/sql/distsql_join_test.go @@ -0,0 +1,256 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package sql + +import ( + "strconv" + "strings" + "testing" + + "github.com/cockroachdb/cockroach/pkg/base" + "github.com/cockroachdb/cockroach/pkg/internal/client" + "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" + "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" + "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" + "github.com/cockroachdb/cockroach/pkg/util/encoding" + "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "golang.org/x/net/context" +) + +var tableNames = map[string]bool{ + "parent1": true, + "child1": true, + "grandchild1": true, + "child2": true, + "parent2": true, +} + +func parseTestKey(kvDB *client.DB, keyStr string) (roachpb.Key, error) { + var key []byte + tokens := strings.Split(keyStr, "/") + + for _, tok := range tokens { + // Encode the table ID if the token is a table name. + if tableNames[tok] { + desc := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, tok) + key = encoding.EncodeUvarintAscending(key, uint64(desc.ID)) + continue + } + + // Interleave sentinel. + if tok == "#" { + key = encoding.EncodeNotNullDescending(key) + continue + } + + // Assume any other value is an unsigned integer. + tokInt, err := strconv.ParseUint(tok, 10, 64) + if err != nil { + return nil, err + } + key = encoding.EncodeUvarintAscending(key, tokInt) + } + + return key, nil +} + +func TestFixInterleavePartitions(t *testing.T) { + defer leaktest.AfterTest(t)() + + s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) + defer s.Stopper().Stop(context.TODO()) + + sqlutils.CreateTestInterleaveHierarchy(t, sqlDB) + + for i, tc := range []struct { + table1 string + table2 string + // each input is a partition: a slice of Start/EndKeys. + input [][2]string + expected [][2]string + }{ + // Full table scan. No fixing should happen. + { + "parent1", "child1", + [][2]string{{"parent1/1", "parent1/2"}}, + [][2]string{{"parent1/1", "parent1/2"}}, + }, + + // Split didn't happen between child1 rows. No need to fix + // EndKey. + { + "parent1", "child1", + [][2]string{{"parent1/1", "parent1/1/42"}}, + [][2]string{{"parent1/1", "parent1/1/42"}}, + }, + + // Split before child1 row (pid1 = 42, cid1 = 13, cid2 = 37). + // We want to fix span to everything up to parent row pid1 = + // 43. + { + "parent1", "child1", + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13/37"}}, + [][2]string{{"parent1/1", "parent1/1/43"}}, + }, + + // Split before a grandchild1 row (with pid1 = 42, cid1 = 13, + // cid2 = 37, gcid1 = 100). + // We want to fix span to everything up to parent row pid1 = + // 43. + { + "parent1", "child1", + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}}, + [][2]string{{"parent1/1", "parent1/1/43"}}, + }, + + // Split on child1 rows with cid1 < 13 (not technically + // possible, but worth a test). + { + "parent1", "child1", + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13"}}, + [][2]string{{"parent1/1", "parent1/1/43"}}, + }, + + // Different child table (child2) split before child2 row. + { + "parent1", "child2", + [][2]string{{"parent1/1", "parent1/1/42/#/child2/1/13/37"}}, + [][2]string{{"parent1/1", "parent1/1/43"}}, + }, + + // Split before grandchild1 row (gcid = 100) where ancestor is + // on parent1 (i.e. a join on pid1). + // We want to include all grandchild1 keys under pid1 = 42, + // thus we need to modify EndKey to be the parent1 row with + // pid1 = 43. + { + "parent1", "grandchild1", + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}}, + [][2]string{{"parent1/1", "parent1/1/43"}}, + }, + + // Verify the fix to EndKey cascades to the next span. + { + "parent1", "grandchild1", + [][2]string{ + {"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}, + {"parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100", "parent1/1/44"}, + }, + [][2]string{ + {"parent1/1", "parent1/1/43"}, + {"parent1/1/43", "parent1/1/44"}, + }, + }, + + // Verify the fix to EndKey cascades and removes any redundant + // spans after cascading. + + { + "parent1", "grandchild1", + [][2]string{ + {"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}, + {"parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100", "parent1/1/42/#/child1/1/13/38"}, + {"parent1/1/42/#/child1/1/13/38", "parent1/1/42/#/child1/1/13/38/#/grandchild1/1/200"}, + {"parent1/1/42/#/child1/1/13/38/#/grandchild1/1/200", "parent1/1/44"}, + }, + [][2]string{ + {"parent1/1", "parent1/1/43"}, + {"parent1/1/43", "parent1/1/44"}, + }, + }, + + // Verify the fix works even if all spans afterwards are + // redacted (because they're redundant). + { + "parent1", "grandchild1", + [][2]string{ + {"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}, + {"parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100", "parent1/1/42/#/child1/1/13/38"}, + {"parent1/1/42/#/child1/1/13/38", "parent1/1/42/#/child1/1/13/38/#/grandchild1/1/200"}, + }, + [][2]string{ + {"parent1/1", "parent1/1/43"}, + }, + }, + + // Split before grandchild1 row (gcid = 100) where ancestor is + // child1. + // We want to include all granchild1 keys under cid1 = 13, cid2 + // = 37. + // There is a further ancestor (parent1) which we should ignore + // since we only care about all grandchild1 rows under our + // last child1 row (with cid1 = 13, cid2 = 37). + { + "child1", "grandchild1", + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13/37/#/grandchild1/1/100"}}, + [][2]string{{"parent1/1", "parent1/1/42/#/child1/1/13/38"}}, + }, + } { + join, err := newTestJoinNode(kvDB, tc.table1, tc.table2) + if err != nil { + t.Fatal(err) + } + + inSpans := make(roachpb.Spans, len(tc.input)) + for j, spanArgs := range tc.input { + start, err := parseTestKey(kvDB, spanArgs[0]) + if err != nil { + t.Fatal(err) + } + end, err := parseTestKey(kvDB, spanArgs[1]) + if err != nil { + t.Fatal(err) + } + inSpans[j] = roachpb.Span{ + Key: start, + EndKey: end, + } + } + input := []spanPartition{{spans: inSpans}} + + actual, err := fixInterleavePartitions(join, input) + + if len(actual) != 1 { + t.Fatalf("%d: expected only one span partition from fixInterleavePartitions, got %d", i, len(actual)) + } + actualSpans := actual[0].spans + expSpans := make(roachpb.Spans, len(tc.expected)) + for j, spanArgs := range tc.expected { + start, err := parseTestKey(kvDB, spanArgs[0]) + if err != nil { + t.Fatal(err) + } + end, err := parseTestKey(kvDB, spanArgs[1]) + if err != nil { + t.Fatal(err) + } + expSpans[j] = roachpb.Span{ + Key: start, + EndKey: end, + } + } + + if len(expSpans) != len(actualSpans) { + t.Fatalf("%d: expected %d spans, got %d spans", i, len(expSpans), len(actualSpans)) + } + + for j, expected := range expSpans { + if !expected.EqualValue(actualSpans[j]) { + t.Errorf("%d: expected span %s, got %s", i, expected, actualSpans[j]) + } + } + } +} diff --git a/pkg/sql/distsql_physical_planner.go b/pkg/sql/distsql_physical_planner.go index 923afe2d309a..9296052c6ea5 100644 --- a/pkg/sql/distsql_physical_planner.go +++ b/pkg/sql/distsql_physical_planner.go @@ -108,6 +108,12 @@ var planMergeJoins = settings.RegisterBoolSetting( true, ) +var planInterleaveJoins = settings.RegisterBoolSetting( + "sql.distsql.interleave_joins.enabled", + "if distsql.merge_joins is enabled and this is set, we plan interleaved table joins in place of merge joins when possible", + true, +) + // NewDistSQLPlanner initializes a DistSQLPlanner func NewDistSQLPlanner( ctx context.Context, @@ -709,7 +715,7 @@ func getOutputColumnsFromScanNode(n *scanNode) []uint32 { return outputColumns } -// convertOrdering maps the columns in ord.ordering to the output columns of a +// convertOrdering maps the columns in props.ordering to the output columns of a // processor. func (dsp *DistSQLPlanner) convertOrdering( props physicalProps, planToStreamColMap []int, @@ -1885,6 +1891,17 @@ func getTypesForPlanResult(node planNode, planToStreamColMap []int) []sqlbase.Co func (dsp *DistSQLPlanner) createPlanForJoin( planCtx *planningCtx, n *joinNode, ) (physicalPlan, error) { + // A hint was provided that the merge join can be performed as an + // interleave join. + if planInterleaveJoins.Get(&dsp.st.SV) && n.joinHint == joinHintInterleave && n.joinType == joinTypeInner { + // TODO(richardwu): We currently only do an interleave join on + // all equality columns. This can be relaxed once a hybrid + // hash-merge join is implemented (see comment below for merge + // joins). + if len(n.mergeJoinOrdering) == len(n.pred.leftEqualityIndices) { + return dsp.createPlanForInterleaveJoin(planCtx, n) + } + } // Outline of the planning process for joins: // @@ -1920,15 +1937,12 @@ func (dsp *DistSQLPlanner) createPlanForJoin( &leftPlan.PhysicalPlan, &rightPlan.PhysicalPlan, ) - joinToStreamColMap := makePlanToStreamColMap(len(n.columns)) - // Nodes where we will run the join processors. var nodes []roachpb.NodeID // We initialize these properties of the joiner. They will then be used to // fill in the processor spec. See descriptions for HashJoinerSpec. var joinType distsqlrun.JoinType - var onExpr distsqlrun.Expression var leftEqCols, rightEqCols []uint32 var leftMergeOrd, rightMergeOrd distsqlrun.Ordering @@ -1970,14 +1984,9 @@ func (dsp *DistSQLPlanner) createPlanForJoin( } // Set up the equality columns. - leftEqCols = make([]uint32, numEq) - for i, leftPlanCol := range n.pred.leftEqualityIndices { - leftEqCols[i] = uint32(leftPlan.planToStreamColMap[leftPlanCol]) - } - rightEqCols = make([]uint32, numEq) - for i, rightPlanCol := range n.pred.rightEqualityIndices { - rightEqCols[i] = uint32(rightPlan.planToStreamColMap[rightPlanCol]) - } + leftEqCols = eqCols(n.pred.leftEqualityIndices, leftPlan.planToStreamColMap) + rightEqCols = eqCols(n.pred.rightEqualityIndices, rightPlan.planToStreamColMap) + if planMergeJoins.Get(&dsp.st.SV) && len(n.mergeJoinOrdering) > 0 && joinType == distsqlrun.JoinType_INNER { // TODO(radu): we currently only use merge joins when we have an ordering on @@ -1988,18 +1997,8 @@ func (dsp *DistSQLPlanner) createPlanForJoin( // - or: adding a sort processor to complete the order if len(n.mergeJoinOrdering) == len(n.pred.leftEqualityIndices) { // Excellent! We can use the merge joiner. - leftMergeOrd.Columns = make([]distsqlrun.Ordering_Column, len(n.mergeJoinOrdering)) - rightMergeOrd.Columns = make([]distsqlrun.Ordering_Column, len(n.mergeJoinOrdering)) - for i, c := range n.mergeJoinOrdering { - leftMergeOrd.Columns[i].ColIdx = leftEqCols[c.ColIdx] - rightMergeOrd.Columns[i].ColIdx = rightEqCols[c.ColIdx] - dir := distsqlrun.Ordering_Column_ASC - if c.Direction == encoding.Descending { - dir = distsqlrun.Ordering_Column_DESC - } - leftMergeOrd.Columns[i].Direction = dir - rightMergeOrd.Columns[i].Direction = dir - } + leftMergeOrd = distsqlOrdering(n.mergeJoinOrdering, leftEqCols) + rightMergeOrd = distsqlOrdering(n.mergeJoinOrdering, rightEqCols) } } } else { @@ -2016,54 +2015,8 @@ func (dsp *DistSQLPlanner) createPlanForJoin( } } - post := distsqlrun.PostProcessSpec{ - Projection: true, - } - // addOutCol appends to post.OutputColumns and returns the index - // in the slice of the added column. - addOutCol := func(col uint32) int { - idx := len(post.OutputColumns) - post.OutputColumns = append(post.OutputColumns, col) - return idx - } - - // The join columns are in two groups: - // - the columns on the left side (numLeftCols) - // - the columns on the right side (numRightCols) - joinCol := 0 - - for i := 0; i < n.pred.numLeftCols; i++ { - if !n.columns[joinCol].Omitted { - joinToStreamColMap[joinCol] = addOutCol(uint32(leftPlan.planToStreamColMap[i])) - } - joinCol++ - } - for i := 0; i < n.pred.numRightCols; i++ { - if !n.columns[joinCol].Omitted { - joinToStreamColMap[joinCol] = addOutCol( - uint32(rightPlan.planToStreamColMap[i] + len(leftTypes)), - ) - } - joinCol++ - } - - if n.pred.onCond != nil { - // We have to remap ordinal references in the on condition (which refer to - // the join columns as described above) to values that make sense in the - // joiner (0 to N-1 for the left input columns, N to N+M-1 for the right - // input columns). - joinColMap := make([]int, len(n.columns)) - idx := 0 - for i := 0; i < n.pred.numLeftCols; i++ { - joinColMap[idx] = leftPlan.planToStreamColMap[i] - idx++ - } - for i := 0; i < n.pred.numRightCols; i++ { - joinColMap[idx] = rightPlan.planToStreamColMap[i] + len(leftTypes) - idx++ - } - onExpr = distsqlplan.MakeExpression(n.pred.onCond, planCtx.evalCtx, joinColMap) - } + post, joinToStreamColMap := joinOutColumns(n, leftPlan, rightPlan) + onExpr := remapOnExpr(planCtx.evalCtx, n, leftPlan, rightPlan) // Create the Core spec. var core distsqlrun.ProcessorCoreUnion @@ -2086,9 +2039,10 @@ func (dsp *DistSQLPlanner) createPlanForJoin( pIdxStart := distsqlplan.ProcessorIdx(len(p.Processors)) stageID := p.NewStageID() - if len(nodes) == 1 { + // Each node has a join processor. + for _, n := range nodes { proc := distsqlplan.Processor{ - Node: nodes[0], + Node: n, Spec: distsqlrun.ProcessorSpec{ Input: []distsqlrun.InputSyncSpec{ {ColumnTypes: leftTypes}, @@ -2101,27 +2055,11 @@ func (dsp *DistSQLPlanner) createPlanForJoin( }, } p.Processors = append(p.Processors, proc) - } else { - // Parallel hash join: we distribute rows (by hash of equality columns) to - // len(nodes) join processors. + } - // Each node has a join processor. - for _, n := range nodes { - proc := distsqlplan.Processor{ - Node: n, - Spec: distsqlrun.ProcessorSpec{ - Input: []distsqlrun.InputSyncSpec{ - {ColumnTypes: leftTypes}, - {ColumnTypes: rightTypes}, - }, - Core: core, - Post: post, - Output: []distsqlrun.OutputRouterSpec{{Type: distsqlrun.OutputRouterSpec_PASS_THROUGH}}, - StageID: stageID, - }, - } - p.Processors = append(p.Processors, proc) - } + if len(nodes) > 1 { + // Parallel hash or merge join: we distribute rows (by hash of + // equality columns) to len(nodes) join processors. // Set up the left routers. for _, resultProc := range leftRouters { diff --git a/pkg/sql/distsqlrun/flow_diagram.go b/pkg/sql/distsqlrun/flow_diagram.go index 55951dcbea7e..12409a795271 100644 --- a/pkg/sql/distsqlrun/flow_diagram.go +++ b/pkg/sql/distsqlrun/flow_diagram.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/cockroachdb/cockroach/pkg/roachpb" + cockroach_sql_sqlbase1 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" humanize "github.com/dustin/go-humanize" "github.com/pkg/errors" ) @@ -101,16 +102,17 @@ func (a *AggregatorSpec) summary() (string, []string) { return "Aggregator", details } -func (tr *TableReaderSpec) summary() (string, []string) { +func indexDetails(indexIdx uint32, desc *cockroach_sql_sqlbase1.TableDescriptor) []string { index := "primary" - if tr.IndexIdx > 0 { - index = tr.Table.Indexes[tr.IndexIdx-1].Name - } - details := []string{ - fmt.Sprintf("%s@%s", index, tr.Table.Name), + if indexIdx > 0 { + index = desc.Indexes[indexIdx-1].Name } + return []string{fmt.Sprintf("%s@%s", index, desc.Name)} +} + +func (tr *TableReaderSpec) summary() (string, []string) { // TODO(radu): a summary of the spans - return "TableReader", details + return "TableReader", indexDetails(tr.IndexIdx, &tr.Table) } func (jr *JoinReaderSpec) summary() (string, []string) { @@ -143,18 +145,38 @@ func (hj *HashJoinerSpec) summary() (string, []string) { return "HashJoiner", details } -func (hj *MergeJoinerSpec) summary() (string, []string) { +func orderedJoinDetails(left, right Ordering, onExpr Expression) []string { details := make([]string, 1, 2) details[0] = fmt.Sprintf( - "left(%s)=right(%s)", hj.LeftOrdering.diagramString(), hj.RightOrdering.diagramString(), + "left(%s)=right(%s)", left.diagramString(), right.diagramString(), ) - if hj.OnExpr.Expr != "" { - details = append(details, fmt.Sprintf("ON %s", hj.OnExpr.Expr)) + if onExpr.Expr != "" { + details = append(details, fmt.Sprintf("ON %s", onExpr.Expr)) } + + return details +} + +func (mj *MergeJoinerSpec) summary() (string, []string) { + details := orderedJoinDetails(mj.LeftOrdering, mj.RightOrdering, mj.OnExpr) return "MergeJoiner", details } +func (irj *InterleaveReaderJoinerSpec) summary() (string, []string) { + details := make([]string, 0, len(irj.Tables)*6+2) + for i, table := range irj.Tables { + // tableX + details = append(details, fmt.Sprintf("table%d", i+1)) + // table@index name + details = append(details, indexDetails(table.IndexIdx, &table.Desc)...) + // Post process (filters, projections, renderExprs, limits/offsets) + details = append(details, table.Post.summary()...) + } + details = append(details, orderedJoinDetails(irj.Tables[0].Ordering, irj.Tables[1].Ordering, irj.OnExpr)...) + return "InterleaveReaderJoiner", details +} + func (s *SorterSpec) summary() (string, []string) { details := []string{s.OutputOrdering.diagramString()} if s.OrderingMatchLen != 0 { diff --git a/pkg/sql/distsqlrun/interleave_reader_joiner_test.go b/pkg/sql/distsqlrun/interleave_reader_joiner_test.go index 1e0ebd50b2c4..b4f38aab220f 100644 --- a/pkg/sql/distsqlrun/interleave_reader_joiner_test.go +++ b/pkg/sql/distsqlrun/interleave_reader_joiner_test.go @@ -58,7 +58,7 @@ func TestInterleaveReaderJoiner(t *testing.T) { // other parent row. sqlutils.CreateTableInterleave(t, sqlDB, "child1", "pid INT, id INT, b INT, PRIMARY KEY (pid, id)", - "test.parent (pid)", + "parent (pid)", 62, sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(30), sqlutils.RowIdxFn, bFn), ) @@ -67,14 +67,14 @@ func TestInterleaveReaderJoiner(t *testing.T) { // interleaved into each parent row. sqlutils.CreateTableInterleave(t, sqlDB, "child2", "pid INT, id INT, s STRING, PRIMARY KEY (pid, id), INDEX (s)", - "test.parent (pid)", + "parent (pid)", 15, sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(30), sqlutils.RowIdxFn, sqlutils.RowEnglishFn), ) - pd := sqlbase.GetTableDescriptor(kvDB, "test", "parent") - cd1 := sqlbase.GetTableDescriptor(kvDB, "test", "child1") - cd2 := sqlbase.GetTableDescriptor(kvDB, "test", "child2") + pd := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, "parent") + cd1 := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, "child1") + cd2 := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, "child2") testCases := []struct { spec InterleaveReaderJoinerSpec @@ -282,8 +282,8 @@ func TestInterleaveReaderJoinerErrors(t *testing.T) { sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(5), sqlutils.RowIdxFn), ) - pd := sqlbase.GetTableDescriptor(kvDB, "test", "parent") - cd := sqlbase.GetTableDescriptor(kvDB, "test", "child") + pd := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, "parent") + cd := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, "child") testCases := []struct { spec InterleaveReaderJoinerSpec diff --git a/pkg/sql/distsqlrun/processors.go b/pkg/sql/distsqlrun/processors.go index f49ca3b11a95..9677e7f654d6 100644 --- a/pkg/sql/distsqlrun/processors.go +++ b/pkg/sql/distsqlrun/processors.go @@ -473,6 +473,14 @@ func newProcessor( flowCtx, core.MergeJoiner, inputs[0], inputs[1], post, outputs[0], ) } + if core.InterleaveReaderJoiner != nil { + if err := checkNumInOut(inputs, outputs, 0, 1); err != nil { + return nil, err + } + return newInterleaveReaderJoiner( + flowCtx, core.InterleaveReaderJoiner, post, outputs[0], + ) + } if core.HashJoiner != nil { if err := checkNumInOut(inputs, outputs, 2, 1); err != nil { return nil, err diff --git a/pkg/sql/distsqlrun/processors.pb.go b/pkg/sql/distsqlrun/processors.pb.go index 654904e3ac8b..6772541930a3 100644 --- a/pkg/sql/distsqlrun/processors.pb.go +++ b/pkg/sql/distsqlrun/processors.pb.go @@ -5958,128 +5958,136 @@ var ( func init() { proto.RegisterFile("sql/distsqlrun/processors.proto", fileDescriptorProcessors) } var fileDescriptorProcessors = []byte{ - // 1971 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x4b, 0x73, 0xe3, 0xc6, - 0x11, 0x16, 0xf8, 0x66, 0xf3, 0x05, 0x4f, 0x25, 0x31, 0xb3, 0x76, 0xa4, 0x35, 0xbc, 0xb6, 0xd7, - 0x8a, 0x43, 0xd6, 0x2a, 0xa9, 0x1c, 0x1c, 0xa7, 0x1c, 0xbe, 0x24, 0xd1, 0x96, 0xc8, 0x18, 0xa4, - 0x64, 0x57, 0x0e, 0x41, 0x41, 0xc0, 0x88, 0x84, 0x17, 0xc4, 0x40, 0x03, 0x70, 0x25, 0xb9, 0x2a, - 0xb7, 0xfc, 0x80, 0x54, 0xe5, 0x96, 0x53, 0x8e, 0xb9, 0xe5, 0x92, 0x43, 0x2e, 0xb9, 0xef, 0x29, - 0x95, 0x5b, 0x5c, 0x39, 0x6c, 0x25, 0xf2, 0xbf, 0xf0, 0x29, 0x35, 0x8d, 0x01, 0x08, 0xee, 0x96, - 0xa4, 0x95, 0x5d, 0x95, 0xdc, 0xc0, 0xee, 0xef, 0xeb, 0xe9, 0xe9, 0xe9, 0xe9, 0xee, 0x21, 0x6c, - 0x05, 0x67, 0x6e, 0xdb, 0x76, 0x82, 0x30, 0x38, 0x73, 0xf9, 0xd2, 0x6b, 0xfb, 0x9c, 0x59, 0x34, - 0x08, 0x18, 0x0f, 0x5a, 0x3e, 0x67, 0x21, 0x23, 0x4d, 0x8b, 0x59, 0x8f, 0x39, 0x33, 0xad, 0x79, - 0x2b, 0x38, 0x73, 0x5b, 0x2b, 0xe8, 0xbd, 0x57, 0x50, 0xea, 0x9f, 0xb4, 0x4d, 0xdf, 0x89, 0xc0, - 0x2b, 0x91, 0x15, 0x3c, 0x91, 0x22, 0x12, 0x8b, 0x6c, 0x33, 0x34, 0xa5, 0xec, 0x3b, 0xb1, 0x8c, - 0x72, 0x9e, 0xac, 0x74, 0xef, 0x75, 0xe1, 0x4a, 0x70, 0xe6, 0x9e, 0x98, 0x01, 0x6d, 0x07, 0x21, - 0x5f, 0x5a, 0xe1, 0x92, 0x53, 0x5b, 0x6a, 0xb7, 0xd2, 0x5a, 0xea, 0x59, 0xcc, 0xa6, 0xb6, 0x61, - 0x9b, 0xe1, 0x72, 0x21, 0x01, 0xdf, 0x7f, 0x6e, 0x27, 0xa9, 0xf5, 0x9a, 0xcb, 0xd0, 0x71, 0xdb, - 0x73, 0xd7, 0x6a, 0x87, 0xce, 0x82, 0x06, 0xa1, 0xb9, 0xf0, 0x63, 0x4f, 0x66, 0x6c, 0xc6, 0xf0, - 0xb3, 0x2d, 0xbe, 0x22, 0xa9, 0xf6, 0xcf, 0x0c, 0xd4, 0x7e, 0x19, 0x07, 0x62, 0xe2, 0x53, 0x8b, - 0xf4, 0x20, 0xef, 0x78, 0xfe, 0x32, 0x6c, 0x2a, 0xf7, 0xb3, 0x0f, 0x2b, 0x3b, 0xef, 0xb4, 0xae, - 0x8b, 0x4a, 0x6b, 0x28, 0x60, 0x93, 0x4b, 0xcf, 0x12, 0xbc, 0x6e, 0xee, 0xe9, 0xb3, 0xad, 0x0d, - 0x3d, 0xe2, 0x92, 0x5d, 0xc8, 0x59, 0x8c, 0xd3, 0x66, 0xe6, 0xbe, 0xf2, 0xb0, 0xb2, 0xf3, 0xde, - 0xf5, 0x36, 0x92, 0xb5, 0x7b, 0x8c, 0xd3, 0x23, 0xcf, 0x61, 0x9e, 0x34, 0x84, 0x7c, 0xb2, 0x0f, - 0x05, 0xb6, 0x0c, 0x85, 0x37, 0x59, 0xf4, 0x66, 0xfb, 0x7a, 0x4b, 0x63, 0xc4, 0xe9, 0x6c, 0x19, - 0x52, 0x9e, 0x72, 0x48, 0xf2, 0x49, 0x0f, 0x72, 0x3e, 0x0b, 0xc2, 0x66, 0x0e, 0x3d, 0x7a, 0xf7, - 0x06, 0x8f, 0x58, 0x10, 0x4a, 0xaf, 0x52, 0x66, 0x90, 0x4c, 0xb6, 0xa1, 0x14, 0x84, 0xe6, 0x8c, - 0x1a, 0x8e, 0xdd, 0xcc, 0xdf, 0x57, 0x1e, 0xe6, 0xbb, 0x0d, 0xa1, 0xbd, 0x7a, 0xb6, 0x55, 0x9c, - 0x08, 0xf9, 0xb0, 0xaf, 0x17, 0x11, 0x30, 0xb4, 0xb5, 0x3f, 0x67, 0xa0, 0xf1, 0x9c, 0x2d, 0xd2, - 0x85, 0xc2, 0xa9, 0xe3, 0x86, 0x94, 0x37, 0x15, 0x74, 0xe3, 0xc1, 0xf5, 0x6e, 0x0c, 0x2e, 0x7c, - 0x4e, 0x83, 0x60, 0x15, 0x10, 0xc9, 0x24, 0x0f, 0x00, 0x7c, 0xce, 0x3e, 0xa7, 0x56, 0xe8, 0x30, - 0x0f, 0x03, 0x5c, 0x92, 0x88, 0x94, 0x9c, 0xbc, 0x0b, 0xf5, 0x68, 0xe3, 0x86, 0xc5, 0xdc, 0xe5, - 0xc2, 0x0b, 0x30, 0x80, 0xb5, 0x6e, 0x46, 0x55, 0xf4, 0x5a, 0xa4, 0xe9, 0x45, 0x0a, 0x72, 0x08, - 0x55, 0x4e, 0x3d, 0x9b, 0x72, 0x83, 0x5e, 0xf8, 0x3c, 0x68, 0xe6, 0x30, 0xd2, 0x77, 0x71, 0xad, - 0x12, 0xf1, 0x85, 0x3c, 0x20, 0xaf, 0x43, 0x81, 0x9d, 0x9e, 0x06, 0x34, 0xc4, 0x08, 0xe5, 0x92, - 0x63, 0x40, 0x19, 0xb9, 0x07, 0x79, 0xd7, 0x59, 0x38, 0x61, 0xb3, 0x90, 0x52, 0x46, 0x22, 0xed, - 0x4f, 0x45, 0x20, 0x2f, 0xe6, 0x03, 0x79, 0x1f, 0x72, 0x1e, 0x63, 0xbe, 0x0c, 0xd9, 0xdb, 0xd7, - 0xfb, 0x35, 0x62, 0xcc, 0x17, 0x34, 0x11, 0x6a, 0x1d, 0x39, 0xe4, 0x63, 0xa8, 0x84, 0xe6, 0x89, - 0x4b, 0x75, 0x6a, 0xda, 0x94, 0xcb, 0x74, 0xbc, 0xe1, 0xf0, 0xa7, 0x2b, 0x30, 0x5a, 0x49, 0xb3, - 0xc9, 0x3e, 0xc0, 0xe7, 0xcc, 0xf1, 0xa4, 0xad, 0x2c, 0xda, 0x7a, 0x78, 0xbd, 0xad, 0x8f, 0x12, - 0x2c, 0x9a, 0x4a, 0x71, 0xc9, 0x07, 0x50, 0x08, 0x18, 0x17, 0x79, 0x90, 0xbb, 0x2d, 0x0f, 0x26, - 0x88, 0x43, 0x0b, 0x92, 0x23, 0xfc, 0x30, 0x67, 0x33, 0x4e, 0x67, 0x66, 0xc8, 0x38, 0x46, 0xf9, - 0x46, 0x3f, 0x3a, 0x09, 0x36, 0xf2, 0x63, 0xc5, 0x25, 0x5d, 0x28, 0x09, 0xa0, 0xe3, 0x59, 0x61, - 0xb3, 0x78, 0x5b, 0x78, 0xfb, 0x12, 0x89, 0x56, 0x12, 0x9e, 0x08, 0xf1, 0x82, 0xf2, 0x19, 0x15, - 0xdb, 0xa5, 0xbc, 0x59, 0xba, 0x2d, 0xc4, 0x87, 0x2b, 0x70, 0x14, 0xe2, 0x14, 0x5b, 0x6c, 0x6d, - 0x6e, 0x06, 0x73, 0x69, 0xab, 0x7c, 0xdb, 0xd6, 0xf6, 0x13, 0x6c, 0xb4, 0xb5, 0x15, 0x97, 0xfc, - 0x02, 0x0a, 0x4f, 0x4c, 0x77, 0x49, 0x83, 0x26, 0xdc, 0x66, 0xe5, 0x18, 0x71, 0x49, 0xe6, 0x48, - 0x9e, 0xf0, 0xe5, 0xc4, 0xb4, 0x1e, 0x9f, 0x3a, 0xae, 0x4b, 0x79, 0xb3, 0x72, 0x9b, 0x95, 0x6e, - 0x82, 0x8d, 0x7c, 0x59, 0x71, 0x49, 0x17, 0xf2, 0x01, 0x0d, 0xc7, 0x7e, 0xb3, 0x7a, 0x5b, 0x39, - 0xec, 0xb8, 0x33, 0x7a, 0xc2, 0x4d, 0xc7, 0x9a, 0x08, 0x3c, 0x1a, 0x8a, 0xa8, 0xe4, 0x43, 0x28, - 0x72, 0x6a, 0xda, 0xbd, 0xc9, 0x71, 0xb3, 0x86, 0x56, 0xde, 0xba, 0xde, 0x8a, 0x1e, 0x01, 0x91, - 0x1e, 0xb3, 0xc8, 0x00, 0xca, 0x93, 0xc9, 0xf4, 0x53, 0xee, 0x88, 0xb4, 0xab, 0xa3, 0x89, 0x1b, - 0x6a, 0x7b, 0x02, 0x45, 0x23, 0x2b, 0xe6, 0xfb, 0xb9, 0xa7, 0x7f, 0xdc, 0x52, 0xb4, 0x3a, 0x54, - 0xd3, 0xb7, 0x4d, 0xe3, 0x50, 0x5f, 0x8f, 0x22, 0xe9, 0x41, 0x31, 0xae, 0x3c, 0x51, 0x23, 0x79, - 0xf3, 0x86, 0xcc, 0x12, 0xbd, 0x6d, 0xe8, 0x9d, 0x32, 0x59, 0x0f, 0x62, 0x26, 0x79, 0x0d, 0xca, - 0xdc, 0x3c, 0x37, 0x4e, 0x2e, 0x43, 0x1a, 0x34, 0x33, 0xf7, 0xb3, 0x0f, 0xab, 0x7a, 0x89, 0x9b, - 0xe7, 0x5d, 0xf1, 0x5b, 0xeb, 0x43, 0x63, 0xed, 0xba, 0x9a, 0x1e, 0x79, 0x04, 0xb9, 0xc0, 0x37, - 0x3d, 0x59, 0x2a, 0x5e, 0x4d, 0xad, 0x28, 0xdb, 0x70, 0x4b, 0xc0, 0xe2, 0x92, 0x2e, 0xa0, 0xda, - 0x6f, 0x33, 0xcf, 0x99, 0xc1, 0x32, 0x9d, 0xc7, 0x7b, 0x7f, 0x4d, 0xc9, 0x91, 0xad, 0x39, 0x2a, - 0x16, 0x7d, 0x1a, 0x58, 0xdc, 0xf1, 0x43, 0xc6, 0xe3, 0x62, 0x86, 0x54, 0xf2, 0x06, 0x94, 0x1d, - 0xcf, 0xa6, 0x17, 0x86, 0x63, 0x5f, 0x60, 0xdd, 0xa9, 0x49, 0x7d, 0x09, 0xc5, 0x43, 0xfb, 0x82, - 0x6c, 0x8a, 0x23, 0x7d, 0x42, 0x79, 0x40, 0xb1, 0x98, 0xc4, 0x65, 0x3c, 0x16, 0x92, 0x01, 0xe4, - 0x85, 0x8b, 0x71, 0x45, 0x7e, 0xd9, 0xb2, 0x95, 0x6c, 0x30, 0x62, 0x93, 0x37, 0x01, 0xb0, 0xbe, - 0x1a, 0x73, 0xc7, 0x8b, 0x8a, 0x72, 0x56, 0x02, 0xca, 0x28, 0xdf, 0x77, 0xbc, 0x50, 0x3b, 0x87, - 0xfa, 0x7a, 0xbd, 0xfa, 0x1f, 0x05, 0x41, 0xfb, 0xbd, 0x02, 0xb0, 0xaa, 0x71, 0xe4, 0x13, 0x68, - 0xc8, 0xbe, 0xc5, 0xb8, 0x4d, 0xb9, 0xe3, 0xcd, 0xe4, 0xfa, 0xda, 0x0d, 0x9d, 0x5f, 0x22, 0xa5, - 0x6d, 0xd9, 0xf8, 0x62, 0x29, 0xd9, 0x01, 0x12, 0xdb, 0x32, 0x16, 0x66, 0x68, 0xcd, 0x0d, 0x97, - 0x7a, 0x6b, 0xde, 0xa8, 0xb1, 0xfe, 0x50, 0xa8, 0x0f, 0xa8, 0xa7, 0x9d, 0x40, 0x35, 0x5d, 0xee, - 0xc8, 0x3b, 0xd0, 0x40, 0x0c, 0xb5, 0x8d, 0x74, 0x56, 0xd7, 0xf4, 0xba, 0x14, 0xc7, 0xcd, 0xf4, - 0x5d, 0x50, 0xe3, 0xca, 0x98, 0x20, 0x33, 0x88, 0x6c, 0xc4, 0x72, 0x09, 0xd5, 0xfe, 0x92, 0x81, - 0xc6, 0x73, 0xc5, 0x90, 0x1c, 0x42, 0xcd, 0xa5, 0xa7, 0xdf, 0x62, 0xf3, 0x55, 0x41, 0x4f, 0xb6, - 0x3e, 0x86, 0x3a, 0x77, 0x66, 0xf3, 0x94, 0xbd, 0xcc, 0x1d, 0xed, 0xd5, 0x90, 0x9f, 0x18, 0xec, - 0x41, 0x91, 0x79, 0x38, 0x27, 0xc8, 0xbe, 0x73, 0xa7, 0x09, 0x86, 0x79, 0x42, 0x46, 0x3e, 0x80, - 0x5c, 0x78, 0xe9, 0x53, 0x1c, 0x01, 0xea, 0x37, 0xf9, 0x22, 0x02, 0x33, 0xbd, 0xf4, 0x69, 0x7c, - 0x61, 0x05, 0x4b, 0xfb, 0x43, 0x06, 0xea, 0xeb, 0x75, 0x9f, 0x6c, 0x43, 0x03, 0xa3, 0x46, 0xcf, - 0xd6, 0x4f, 0x27, 0x9a, 0x76, 0x84, 0x6a, 0x70, 0x16, 0x1f, 0xd0, 0x7b, 0xa0, 0x46, 0x21, 0x49, - 0x81, 0x33, 0x09, 0x38, 0x0a, 0xd7, 0x0a, 0xfd, 0xff, 0xdf, 0x2f, 0xf9, 0x21, 0xd4, 0xb1, 0x43, 0xae, 0x32, 0xaf, 0x98, 0x2a, 0x16, 0xb5, 0x48, 0x17, 0xe7, 0xd4, 0x5f, 0x73, 0x50, 0x5f, 0xef, - 0xf7, 0xe4, 0x0d, 0x80, 0x19, 0x67, 0x4b, 0x5f, 0xd0, 0xd3, 0x5b, 0x2d, 0xa3, 0xb4, 0xc7, 0xdc, - 0x80, 0xfc, 0x1a, 0xaa, 0xf1, 0x50, 0xe0, 0x30, 0x39, 0x2a, 0x56, 0x76, 0x7e, 0xf2, 0xb2, 0x23, - 0x45, 0xf2, 0x73, 0xb5, 0xf5, 0x35, 0x7b, 0xf7, 0xfe, 0xa6, 0x40, 0x25, 0x85, 0x21, 0x7b, 0x90, - 0x3b, 0x5d, 0x7a, 0x16, 0x26, 0x77, 0x7d, 0xe7, 0x47, 0x2f, 0xbd, 0xce, 0xee, 0xd2, 0x4b, 0xe6, - 0x71, 0x61, 0x80, 0xdc, 0x4f, 0xcd, 0x2f, 0xe9, 0x49, 0x78, 0x35, 0x9d, 0x3c, 0x80, 0x7a, 0x34, - 0x37, 0x8b, 0xed, 0x63, 0x19, 0x12, 0x13, 0x57, 0x4d, 0xaf, 0x46, 0xd2, 0x1e, 0x73, 0x45, 0x25, - 0x7e, 0x15, 0x9b, 0x15, 0xaa, 0xf3, 0x78, 0x59, 0x0b, 0x16, 0x2a, 0x3e, 0xca, 0x95, 0xb2, 0x6a, - 0x4e, 0xbb, 0x52, 0x20, 0x27, 0xd6, 0x26, 0x65, 0xc8, 0x0f, 0xfb, 0x83, 0xd1, 0x54, 0xdd, 0x20, - 0x45, 0xc8, 0x76, 0x8e, 0xf7, 0x54, 0x85, 0x54, 0xa1, 0xd4, 0x1d, 0x8f, 0x0f, 0x8c, 0xce, 0xa8, - 0xaf, 0x66, 0x48, 0x05, 0x8a, 0xf8, 0x6b, 0xac, 0xab, 0x59, 0x52, 0x07, 0xe8, 0x8d, 0x47, 0xbd, - 0xce, 0xd4, 0xe8, 0xec, 0xed, 0xa9, 0x39, 0x41, 0xef, 0x8d, 0x8f, 0x46, 0x53, 0x35, 0x2f, 0xe8, - 0x87, 0x9d, 0xcf, 0xd4, 0x22, 0x7e, 0x0c, 0x47, 0x6a, 0x89, 0x00, 0x14, 0x26, 0xd3, 0x7e, 0x7f, - 0x70, 0xac, 0x96, 0x85, 0x70, 0x72, 0x74, 0xa8, 0x82, 0x30, 0x37, 0x39, 0x3a, 0x34, 0x86, 0xa3, - 0xa9, 0x5a, 0x11, 0x2b, 0x1d, 0x77, 0xf4, 0x61, 0x67, 0xd4, 0x1b, 0xa8, 0x55, 0xa1, 0xfa, 0x6c, - 0xac, 0xa3, 0xe5, 0x5a, 0xb4, 0xd2, 0xd1, 0x68, 0x6a, 0xe8, 0xe3, 0x4f, 0x27, 0x6a, 0x1d, 0x79, - 0x9f, 0xe8, 0xfd, 0xe1, 0xee, 0xae, 0xda, 0x20, 0x04, 0xea, 0xbb, 0xc3, 0x51, 0xe7, 0xc0, 0x48, - 0xd8, 0x2a, 0x51, 0xa1, 0x1a, 0xc9, 0xe4, 0x9a, 0xaf, 0x68, 0x5f, 0x67, 0xa1, 0xbe, 0x3e, 0xc3, - 0x88, 0x73, 0xc2, 0xc4, 0xbd, 0xf5, 0x9c, 0xd6, 0x79, 0xad, 0x17, 0x72, 0x38, 0xe9, 0x25, 0x99, - 0x6f, 0xde, 0x4b, 0x92, 0x6e, 0x98, 0xfd, 0x56, 0xdd, 0xf0, 0x11, 0x94, 0xec, 0x25, 0xc7, 0x3c, - 0xc4, 0x54, 0xc8, 0x76, 0xbf, 0x2b, 0xd4, 0x5f, 0x3f, 0xdb, 0xaa, 0x89, 0x17, 0x73, 0xab, 0x2f, - 0x95, 0x7a, 0x02, 0x13, 0x0d, 0xd4, 0x9a, 0x2f, 0xbd, 0xc7, 0x46, 0xe0, 0x7c, 0x41, 0xd7, 0x1b, - 0x28, 0xca, 0x27, 0xce, 0x17, 0x94, 0x8c, 0xa1, 0xca, 0xc2, 0x39, 0xe5, 0x06, 0x7a, 0x1b, 0x34, - 0x0b, 0xe8, 0xe5, 0xdd, 0x76, 0x5a, 0x41, 0x0b, 0xa8, 0x0b, 0xc8, 0x87, 0x50, 0x12, 0xa3, 0x5b, - 0x27, 0x18, 0x9f, 0xca, 0xd9, 0xfc, 0x07, 0x29, 0x63, 0xe2, 0x99, 0xdf, 0x9a, 0xbb, 0x56, 0x6b, - 0x1a, 0x3f, 0xf3, 0xe3, 0xd4, 0x8f, 0x49, 0xda, 0x36, 0xe4, 0xc4, 0x41, 0x88, 0x5c, 0x18, 0x7a, - 0x4f, 0x4c, 0xd7, 0xb1, 0xd5, 0x0d, 0x91, 0x65, 0x51, 0xad, 0x50, 0x15, 0xcc, 0x66, 0xd1, 0x86, - 0xd5, 0x8c, 0xf6, 0x95, 0x02, 0xa5, 0x5d, 0x97, 0x9d, 0xe3, 0xb1, 0x3f, 0x82, 0xe2, 0xa9, 0xcb, - 0xce, 0xc5, 0x23, 0x57, 0x9c, 0x7c, 0xb5, 0xdb, 0x14, 0x96, 0xff, 0xf5, 0x6c, 0xab, 0x20, 0x20, - 0xc3, 0xfe, 0x55, 0xf2, 0xa5, 0x17, 0x04, 0x70, 0x68, 0x93, 0x43, 0x7c, 0x94, 0xca, 0xbf, 0x53, - 0xb0, 0xc8, 0xdc, 0x38, 0x5d, 0xae, 0xfd, 0xe3, 0x90, 0x7a, 0xbd, 0x4a, 0x03, 0xe4, 0x08, 0x8a, - 0x33, 0x33, 0xa4, 0xe7, 0xe6, 0x25, 0x4e, 0x46, 0xf9, 0xee, 0xcf, 0xe4, 0x19, 0xfd, 0x78, 0xe6, - 0x84, 0xf3, 0xe5, 0x49, 0xcb, 0x62, 0x8b, 0x76, 0x62, 0xdd, 0x3e, 0x59, 0x7d, 0xb7, 0xfd, 0xc7, - 0xb3, 0x76, 0x3c, 0xec, 0x8d, 0x98, 0x8d, 0x4f, 0x72, 0x69, 0x4b, 0xfb, 0xbb, 0x02, 0xe4, 0xc5, - 0x09, 0x9b, 0xf4, 0xa1, 0xf4, 0x8d, 0xfb, 0x6d, 0xc2, 0x14, 0x3e, 0x33, 0xdf, 0xc0, 0xfb, 0x92, - 0xc1, 0xfb, 0xf2, 0xd3, 0xbb, 0x8c, 0xf9, 0x2d, 0xfc, 0x4a, 0x5d, 0x9c, 0x02, 0xc3, 0x5f, 0xda, - 0x6b, 0x50, 0x4e, 0x54, 0xe2, 0x9a, 0x0f, 0x2e, 0x2c, 0xea, 0x87, 0x86, 0xe9, 0xba, 0xea, 0x86, - 0xf6, 0xa5, 0x02, 0x95, 0xd4, 0xb0, 0x4f, 0x7e, 0x2e, 0x7c, 0x88, 0x6a, 0xb8, 0xf2, 0x42, 0xca, - 0xc4, 0x51, 0xe9, 0x4d, 0x8e, 0xc7, 0x11, 0x28, 0x1e, 0x38, 0x25, 0x87, 0xbc, 0x05, 0x95, 0xc0, - 0x5c, 0xf8, 0x2e, 0x8d, 0x32, 0x3d, 0x83, 0xa1, 0x97, 0xa7, 0x13, 0x29, 0x30, 0xd5, 0x3f, 0x06, - 0xc0, 0x24, 0x37, 0x6c, 0x1a, 0x58, 0xf2, 0x1d, 0x7c, 0xb7, 0x44, 0x2f, 0x87, 0xb1, 0x98, 0x7c, - 0x0f, 0xb2, 0x4b, 0xee, 0xe0, 0x55, 0x2c, 0x4b, 0xad, 0x10, 0x68, 0xbf, 0x81, 0xda, 0xda, 0x1b, - 0x84, 0xbc, 0x0d, 0x15, 0x9b, 0x8a, 0xaa, 0x1e, 0xdd, 0x5d, 0x25, 0x45, 0x48, 0x2b, 0x48, 0x13, - 0x72, 0x9e, 0xb9, 0x88, 0xbc, 0x8f, 0x01, 0x28, 0x21, 0xdb, 0x50, 0x3b, 0x37, 0x5d, 0x57, 0x5c, - 0xf3, 0x91, 0xe9, 0xb1, 0x00, 0x5d, 0x8f, 0xaf, 0xf2, 0xba, 0x6a, 0x7b, 0x17, 0x4a, 0x71, 0x37, - 0xc6, 0x7b, 0x32, 0x1a, 0x0d, 0x74, 0x75, 0x43, 0x1c, 0xc0, 0xc1, 0x60, 0x77, 0x6a, 0x8c, 0x8f, - 0xa6, 0x03, 0x5d, 0x55, 0x48, 0x03, 0x2a, 0xfa, 0x70, 0x6f, 0x3f, 0x16, 0x64, 0x04, 0x60, 0xf7, - 0xe8, 0xe0, 0x40, 0xfe, 0xce, 0x76, 0x1f, 0x3c, 0xfd, 0xcf, 0xe6, 0xc6, 0xd3, 0xab, 0x4d, 0xe5, - 0x1f, 0x57, 0x9b, 0xca, 0x97, 0x57, 0x9b, 0xca, 0xbf, 0xaf, 0x36, 0x95, 0xdf, 0x7d, 0xb5, 0xb9, - 0xf1, 0x2b, 0x58, 0xe5, 0xc3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xeb, 0xd5, 0x9c, 0xc6, 0x9b, - 0x14, 0x00, 0x00, + // 2086 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x6f, 0x1b, 0xc7, + 0x15, 0xd7, 0x92, 0xcb, 0xaf, 0xc7, 0xaf, 0xcd, 0xa0, 0x4d, 0x58, 0x3b, 0x95, 0x9c, 0x8d, 0x93, + 0xd8, 0x6a, 0x4a, 0xc1, 0xee, 0xc7, 0x21, 0x4d, 0x91, 0xf0, 0x4b, 0x36, 0x13, 0x89, 0x6c, 0x48, + 0x4a, 0x09, 0x7a, 0xe8, 0x62, 0xb5, 0x3b, 0x22, 0x37, 0x5e, 0xed, 0xac, 0x66, 0x97, 0x96, 0x14, + 0xa0, 0x40, 0x0f, 0xbd, 0x16, 0x28, 0x50, 0xa0, 0x87, 0x9e, 0xfa, 0x1f, 0xf4, 0xd2, 0x43, 0x2f, + 0xbd, 0xfb, 0x54, 0xf4, 0xd6, 0xa0, 0x07, 0xa3, 0x55, 0xfe, 0x8b, 0x9c, 0x8a, 0x79, 0x3b, 0xbb, + 0x5c, 0xda, 0x16, 0x65, 0xd9, 0x45, 0x9b, 0x1b, 0xf9, 0xde, 0xfb, 0xfd, 0xe6, 0xcd, 0x9b, 0x37, + 0xef, 0xbd, 0x59, 0xd8, 0x08, 0x8e, 0xdd, 0x2d, 0xdb, 0x09, 0xc2, 0xe0, 0xd8, 0xe5, 0x73, 0x6f, + 0xcb, 0xe7, 0xcc, 0xa2, 0x41, 0xc0, 0x78, 0xd0, 0xf4, 0x39, 0x0b, 0x19, 0x69, 0x58, 0xcc, 0x7a, + 0xc0, 0x99, 0x69, 0xcd, 0x9a, 0xc1, 0xb1, 0xdb, 0x5c, 0x98, 0x5e, 0x7b, 0x05, 0xa5, 0xfe, 0xc1, + 0x96, 0xe9, 0x3b, 0x91, 0xf1, 0x42, 0x64, 0x05, 0x0f, 0xa5, 0x88, 0xc4, 0x22, 0xdb, 0x0c, 0x4d, + 0x29, 0xfb, 0x56, 0x2c, 0xa3, 0x9c, 0x27, 0x2b, 0x5d, 0x7b, 0x5d, 0xb8, 0x12, 0x1c, 0xbb, 0x07, + 0x66, 0x40, 0xb7, 0x82, 0x90, 0xcf, 0xad, 0x70, 0xce, 0xa9, 0x2d, 0xb5, 0x1b, 0x69, 0x2d, 0xf5, + 0x2c, 0x66, 0x53, 0xdb, 0xb0, 0xcd, 0x70, 0x7e, 0x24, 0x0d, 0xbe, 0xf3, 0xc4, 0x4e, 0x52, 0xeb, + 0x35, 0xe6, 0xa1, 0xe3, 0x6e, 0xcd, 0x5c, 0x6b, 0x2b, 0x74, 0x8e, 0x68, 0x10, 0x9a, 0x47, 0x7e, + 0xec, 0xc9, 0x94, 0x4d, 0x19, 0xfe, 0xdc, 0x12, 0xbf, 0x22, 0xa9, 0xfe, 0x8f, 0x0c, 0x54, 0x7f, + 0x16, 0x07, 0x62, 0xec, 0x53, 0x8b, 0x74, 0x20, 0xe7, 0x78, 0xfe, 0x3c, 0x6c, 0x28, 0x37, 0xb2, + 0xb7, 0xca, 0x77, 0xdf, 0x69, 0x5e, 0x14, 0x95, 0x66, 0x5f, 0x98, 0x8d, 0xcf, 0x3c, 0x4b, 0xe0, + 0xda, 0xea, 0xa3, 0xc7, 0x1b, 0x6b, 0xa3, 0x08, 0x4b, 0xb6, 0x41, 0xb5, 0x18, 0xa7, 0x8d, 0xcc, + 0x0d, 0xe5, 0x56, 0xf9, 0xee, 0xbb, 0x17, 0x73, 0x24, 0x6b, 0x77, 0x18, 0xa7, 0x7b, 0x9e, 0xc3, + 0x3c, 0x49, 0x84, 0x78, 0x72, 0x1f, 0xf2, 0x6c, 0x1e, 0x0a, 0x6f, 0xb2, 0xe8, 0xcd, 0xe6, 0xc5, + 0x4c, 0x43, 0xb4, 0x1b, 0xb1, 0x79, 0x48, 0x79, 0xca, 0x21, 0x89, 0x27, 0x1d, 0x50, 0x7d, 0x16, + 0x84, 0x0d, 0x15, 0x3d, 0xba, 0xbd, 0xc2, 0x23, 0x16, 0x84, 0xd2, 0xab, 0x14, 0x0d, 0x82, 0xc9, + 0x26, 0x14, 0x83, 0xd0, 0x9c, 0x52, 0xc3, 0xb1, 0x1b, 0xb9, 0x1b, 0xca, 0xad, 0x5c, 0xbb, 0x2e, + 0xb4, 0xe7, 0x8f, 0x37, 0x0a, 0x63, 0x21, 0xef, 0x77, 0x47, 0x05, 0x34, 0xe8, 0xdb, 0xfa, 0x9f, + 0x32, 0x50, 0x7f, 0x82, 0x8b, 0xb4, 0x21, 0x7f, 0xe8, 0xb8, 0x21, 0xe5, 0x0d, 0x05, 0xdd, 0xb8, + 0x79, 0xb1, 0x1b, 0xbd, 0x53, 0x9f, 0xd3, 0x20, 0x58, 0x04, 0x44, 0x22, 0xc9, 0x4d, 0x00, 0x9f, + 0xb3, 0xcf, 0xa9, 0x15, 0x3a, 0xcc, 0xc3, 0x00, 0x17, 0xa5, 0x45, 0x4a, 0x4e, 0x6e, 0x43, 0x2d, + 0xda, 0xb8, 0x61, 0x31, 0x77, 0x7e, 0xe4, 0x05, 0x18, 0xc0, 0x6a, 0x3b, 0xa3, 0x29, 0xa3, 0x6a, + 0xa4, 0xe9, 0x44, 0x0a, 0xb2, 0x0b, 0x15, 0x4e, 0x3d, 0x9b, 0x72, 0x83, 0x9e, 0xfa, 0x3c, 0x68, + 0xa8, 0x18, 0xe9, 0xab, 0xb8, 0x56, 0x8e, 0xf0, 0x42, 0x1e, 0x90, 0xd7, 0x21, 0xcf, 0x0e, 0x0f, + 0x03, 0x1a, 0x62, 0x84, 0xd4, 0xe4, 0x18, 0x50, 0x46, 0xae, 0x41, 0xce, 0x75, 0x8e, 0x9c, 0xb0, + 0x91, 0x4f, 0x29, 0x23, 0x91, 0xfe, 0xfb, 0x22, 0x90, 0xa7, 0xf3, 0x81, 0xbc, 0x07, 0xaa, 0xc7, + 0x98, 0x2f, 0x43, 0xf6, 0xf6, 0xc5, 0x7e, 0x0d, 0x18, 0xf3, 0x05, 0x4c, 0x84, 0x7a, 0x84, 0x18, + 0xf2, 0x31, 0x94, 0x43, 0xf3, 0xc0, 0xa5, 0x23, 0x6a, 0xda, 0x94, 0xcb, 0x74, 0x5c, 0x71, 0xf8, + 0x93, 0x85, 0x31, 0xb2, 0xa4, 0xd1, 0xe4, 0x3e, 0xc0, 0xe7, 0xcc, 0xf1, 0x24, 0x57, 0x16, 0xb9, + 0x6e, 0x5d, 0xcc, 0xf5, 0x51, 0x62, 0x8b, 0x54, 0x29, 0x2c, 0x79, 0x1f, 0xf2, 0x01, 0xe3, 0x22, + 0x0f, 0xd4, 0xcb, 0xf2, 0x60, 0x8c, 0x76, 0xc8, 0x20, 0x31, 0xc2, 0x0f, 0x73, 0x3a, 0xe5, 0x74, + 0x6a, 0x86, 0x8c, 0x63, 0x94, 0x57, 0xfa, 0xd1, 0x4a, 0x6c, 0x23, 0x3f, 0x16, 0x58, 0xd2, 0x86, + 0xa2, 0x30, 0x74, 0x3c, 0x2b, 0x6c, 0x14, 0x2e, 0x0b, 0x6f, 0x57, 0x5a, 0x22, 0x4b, 0x82, 0x13, + 0x21, 0x3e, 0xa2, 0x7c, 0x4a, 0xc5, 0x76, 0x29, 0x6f, 0x14, 0x2f, 0x0b, 0xf1, 0xee, 0xc2, 0x38, + 0x0a, 0x71, 0x0a, 0x2d, 0xb6, 0x36, 0x33, 0x83, 0x99, 0xe4, 0x2a, 0x5d, 0xb6, 0xb5, 0xfb, 0x89, + 0x6d, 0xb4, 0xb5, 0x05, 0x96, 0x7c, 0x08, 0xf9, 0x87, 0xa6, 0x3b, 0xa7, 0x41, 0x03, 0x2e, 0x63, + 0xd9, 0x47, 0xbb, 0x24, 0x73, 0x24, 0x4e, 0xf8, 0x72, 0x60, 0x5a, 0x0f, 0x0e, 0x1d, 0xd7, 0xa5, + 0xbc, 0x51, 0xbe, 0x8c, 0xa5, 0x9d, 0xd8, 0x46, 0xbe, 0x2c, 0xb0, 0xa4, 0x0d, 0xb9, 0x80, 0x86, + 0x43, 0xbf, 0x51, 0xb9, 0xac, 0x1c, 0xb6, 0xdc, 0x29, 0x3d, 0xe0, 0xa6, 0x63, 0x8d, 0x85, 0x3d, + 0x12, 0x45, 0x50, 0xf2, 0x01, 0x14, 0x38, 0x35, 0xed, 0xce, 0x78, 0xbf, 0x51, 0x45, 0x96, 0xb7, + 0x2e, 0x66, 0x19, 0x45, 0x86, 0x08, 0x8f, 0x51, 0xa4, 0x07, 0xa5, 0xf1, 0x78, 0xf2, 0x29, 0x77, + 0x44, 0xda, 0xd5, 0x90, 0x62, 0x45, 0x6d, 0x4f, 0x4c, 0x91, 0x64, 0x81, 0x24, 0x2e, 0xbc, 0xea, + 0x78, 0x21, 0xe5, 0x2e, 0x35, 0x1f, 0xca, 0x8b, 0x21, 0x4f, 0xab, 0x8e, 0x9c, 0x3f, 0x5c, 0xd5, + 0x2f, 0x9e, 0x85, 0xc3, 0x05, 0x2e, 0xe0, 0x7c, 0x4f, 0x7d, 0xf4, 0xc7, 0x0d, 0x45, 0xaf, 0x41, + 0x25, 0x7d, 0xb7, 0x75, 0x0e, 0xb5, 0xe5, 0x33, 0x23, 0x1d, 0x28, 0xc4, 0x75, 0x2e, 0x6a, 0x5b, + 0x6f, 0xae, 0xc8, 0x63, 0xd1, 0x49, 0xfb, 0xde, 0x21, 0x93, 0xd5, 0x27, 0x46, 0x92, 0xeb, 0x50, + 0xe2, 0xe6, 0x89, 0x71, 0x70, 0x16, 0xd2, 0xa0, 0x91, 0xb9, 0x91, 0xbd, 0x55, 0x19, 0x15, 0xb9, + 0x79, 0xd2, 0x16, 0xff, 0xf5, 0x2e, 0xd4, 0x97, 0x8a, 0x83, 0xe9, 0x91, 0x3b, 0xa0, 0x06, 0xbe, + 0xe9, 0xc9, 0xc2, 0xf4, 0x5a, 0x6a, 0x45, 0xd9, 0xf4, 0x9b, 0xc2, 0x2c, 0x6e, 0x20, 0xc2, 0x54, + 0xff, 0x75, 0xe6, 0x09, 0x1a, 0x6c, 0x0a, 0x39, 0xac, 0x32, 0x17, 0x14, 0x38, 0x39, 0x08, 0x44, + 0xa5, 0xa9, 0x4b, 0x03, 0x8b, 0x3b, 0x7e, 0xc8, 0x78, 0x5c, 0x3a, 0x11, 0x4a, 0xde, 0x80, 0x92, + 0xe3, 0xd9, 0xf4, 0xd4, 0x70, 0xec, 0x53, 0xac, 0x72, 0x55, 0xa9, 0x2f, 0xa2, 0xb8, 0x6f, 0x9f, + 0x92, 0x75, 0x91, 0x40, 0x0f, 0x29, 0x0f, 0x28, 0x96, 0xae, 0xb8, 0x69, 0xc4, 0x42, 0xd2, 0x83, + 0x9c, 0x70, 0x31, 0xae, 0xff, 0xcf, 0x5b, 0x24, 0x93, 0x0d, 0x46, 0x68, 0xf2, 0x26, 0x00, 0x56, + 0x73, 0x63, 0xe6, 0x78, 0x51, 0x0b, 0xc8, 0x4a, 0x83, 0x12, 0xca, 0xef, 0x3b, 0x5e, 0xa8, 0x9f, + 0x40, 0x6d, 0xb9, 0x3a, 0xfe, 0x8f, 0x82, 0xa0, 0xff, 0x4e, 0x01, 0x58, 0x54, 0x54, 0xf2, 0x09, + 0xd4, 0x65, 0x97, 0x64, 0xdc, 0xa6, 0xdc, 0xf1, 0xa6, 0x72, 0x7d, 0x7d, 0xc5, 0x9c, 0x21, 0x2d, + 0x25, 0xb7, 0x6c, 0xb3, 0xb1, 0x94, 0xdc, 0x05, 0x12, 0x73, 0x19, 0x47, 0x66, 0x68, 0xcd, 0x0c, + 0x97, 0x7a, 0x4b, 0xde, 0x68, 0xb1, 0x7e, 0x57, 0xa8, 0x77, 0xa8, 0xa7, 0x1f, 0x40, 0x25, 0x5d, + 0x5c, 0xc9, 0x3b, 0x50, 0x47, 0x1b, 0x6a, 0x1b, 0xe9, 0xac, 0xae, 0x8e, 0x6a, 0x52, 0x1c, 0xb7, + 0xee, 0xdb, 0xa0, 0xc5, 0x75, 0x38, 0xb1, 0xcc, 0xa0, 0x65, 0x3d, 0x96, 0x4b, 0x53, 0xfd, 0xcf, + 0x19, 0xa8, 0x3f, 0x51, 0x7a, 0xc9, 0x2e, 0x54, 0x5d, 0x7a, 0xf8, 0x12, 0x9b, 0xaf, 0x08, 0x78, + 0xb2, 0xf5, 0x21, 0xd4, 0xb8, 0x33, 0x9d, 0xa5, 0xf8, 0x32, 0x57, 0xe4, 0xab, 0x22, 0x3e, 0x21, + 0xec, 0x40, 0x81, 0x79, 0x38, 0x95, 0xc8, 0x2e, 0x77, 0xa5, 0x79, 0x89, 0x79, 0x42, 0x46, 0xde, + 0x07, 0x35, 0x3c, 0xf3, 0x29, 0x0e, 0x1c, 0xb5, 0x55, 0xbe, 0x88, 0xc0, 0x4c, 0xce, 0x7c, 0x1a, + 0x5f, 0x58, 0x81, 0xd2, 0xff, 0x90, 0x81, 0xda, 0x72, 0x97, 0x21, 0x9b, 0x50, 0xc7, 0xa8, 0xd1, + 0xe3, 0xe5, 0xd3, 0x89, 0x66, 0x2b, 0xa1, 0xea, 0x1d, 0xc7, 0x07, 0xf4, 0x2e, 0x68, 0x51, 0x48, + 0x52, 0xc6, 0x99, 0xc4, 0x38, 0x0a, 0xd7, 0xc2, 0xfa, 0xff, 0xbf, 0x5f, 0xf2, 0x3d, 0xa8, 0x61, + 0x3f, 0x5e, 0x64, 0x5e, 0x21, 0x55, 0x2c, 0xaa, 0x91, 0x2e, 0xce, 0xa9, 0xbf, 0xa8, 0x50, 0x5b, + 0x9e, 0x2e, 0xc8, 0x1b, 0x00, 0x53, 0xce, 0xe6, 0xbe, 0x80, 0xa7, 0xb7, 0x5a, 0x42, 0x69, 0x87, + 0xb9, 0x01, 0xf9, 0x05, 0x54, 0xe2, 0x11, 0xc4, 0x61, 0x72, 0x30, 0x5d, 0xd9, 0x37, 0x96, 0x97, + 0x48, 0xfe, 0x2e, 0xb6, 0xbe, 0xc4, 0x77, 0xed, 0xaf, 0x0a, 0x94, 0x53, 0x36, 0xe4, 0x1e, 0xa8, + 0x87, 0x73, 0xcf, 0xc2, 0xe4, 0xae, 0xdd, 0xfd, 0xfe, 0x73, 0xaf, 0xb3, 0x3d, 0xf7, 0x92, 0xe9, + 0x5f, 0x10, 0x90, 0x1b, 0xa9, 0x69, 0x29, 0x3d, 0x77, 0x2f, 0x66, 0xa1, 0x9b, 0x50, 0x8b, 0xa6, + 0x74, 0xb1, 0x7d, 0x2c, 0x43, 0x62, 0xbe, 0xab, 0x8e, 0x2a, 0x91, 0xb4, 0xc3, 0x5c, 0x51, 0x89, + 0x5f, 0xc3, 0x66, 0x85, 0xea, 0x1c, 0x5e, 0xd6, 0xbc, 0x85, 0x8a, 0x8f, 0xd4, 0x62, 0x56, 0x53, + 0xf5, 0x73, 0x05, 0x54, 0xb1, 0x36, 0x29, 0x41, 0xae, 0xdf, 0xed, 0x0d, 0x26, 0xda, 0x1a, 0x29, + 0x40, 0xb6, 0xb5, 0x7f, 0x4f, 0x53, 0x48, 0x05, 0x8a, 0xed, 0xe1, 0x70, 0xc7, 0x68, 0x0d, 0xba, + 0x5a, 0x86, 0x94, 0xa1, 0x80, 0xff, 0x86, 0x23, 0x2d, 0x4b, 0x6a, 0x00, 0x9d, 0xe1, 0xa0, 0xd3, + 0x9a, 0x18, 0xad, 0x7b, 0xf7, 0x34, 0x55, 0xc0, 0x3b, 0xc3, 0xbd, 0xc1, 0x44, 0xcb, 0x09, 0xf8, + 0x6e, 0xeb, 0x33, 0xad, 0x80, 0x3f, 0xfa, 0x03, 0xad, 0x48, 0x00, 0xf2, 0xe3, 0x49, 0xb7, 0xdb, + 0xdb, 0xd7, 0x4a, 0x42, 0x38, 0xde, 0xdb, 0xd5, 0x40, 0xd0, 0x8d, 0xf7, 0x76, 0x8d, 0xfe, 0x60, + 0xa2, 0x95, 0xc5, 0x4a, 0xfb, 0xad, 0x51, 0xbf, 0x35, 0xe8, 0xf4, 0xb4, 0x8a, 0x50, 0x7d, 0x36, + 0x1c, 0x21, 0x73, 0x35, 0x5a, 0x69, 0x6f, 0x30, 0x31, 0x46, 0xc3, 0x4f, 0xc7, 0x5a, 0x0d, 0x71, + 0x9f, 0x8c, 0xba, 0xfd, 0xed, 0x6d, 0xad, 0x4e, 0x08, 0xd4, 0xb6, 0xfb, 0x83, 0xd6, 0x8e, 0x91, + 0xa0, 0x35, 0xa2, 0x41, 0x25, 0x92, 0xc9, 0x35, 0x5f, 0xd1, 0xbf, 0xce, 0x42, 0x6d, 0x79, 0x62, + 0x12, 0xe7, 0x84, 0x89, 0x7b, 0xe9, 0x39, 0x2d, 0xe3, 0x9a, 0x4f, 0xe5, 0x70, 0xd2, 0x4b, 0x32, + 0x2f, 0xde, 0x4b, 0x92, 0x6e, 0x98, 0x7d, 0xa9, 0x6e, 0x78, 0x07, 0x8a, 0xf6, 0x9c, 0x63, 0x1e, + 0x62, 0x2a, 0x64, 0xdb, 0xdf, 0x16, 0xea, 0xaf, 0x1f, 0x6f, 0x54, 0xc5, 0xfb, 0xbc, 0xd9, 0x95, + 0xca, 0x51, 0x62, 0x26, 0x1a, 0xa8, 0x35, 0x9b, 0x7b, 0x0f, 0x8c, 0xc0, 0xf9, 0x82, 0x2e, 0x37, + 0x50, 0x94, 0x8f, 0x9d, 0x2f, 0x28, 0x19, 0x42, 0x85, 0x85, 0x33, 0xca, 0x0d, 0xf4, 0x36, 0x68, + 0xe4, 0xd1, 0xcb, 0xab, 0xed, 0xb4, 0x8c, 0x0c, 0xa8, 0x0b, 0xc8, 0x07, 0x50, 0x14, 0x83, 0x62, + 0x2b, 0x18, 0x1e, 0xca, 0x97, 0xc0, 0x77, 0x53, 0x64, 0xf3, 0xd0, 0x71, 0x9b, 0x33, 0xd7, 0x6a, + 0x4e, 0xe2, 0x8f, 0x0a, 0x71, 0xea, 0xc7, 0x20, 0x7d, 0x13, 0x54, 0x71, 0x10, 0x22, 0x17, 0xfa, + 0xde, 0x43, 0xd3, 0x75, 0x6c, 0x6d, 0x4d, 0x64, 0x59, 0x54, 0x2b, 0x34, 0x05, 0xb3, 0x59, 0xb4, + 0x61, 0x2d, 0xa3, 0x7f, 0xa5, 0x40, 0x71, 0xdb, 0x65, 0x27, 0x78, 0xec, 0x77, 0xa0, 0x70, 0xe8, + 0xb2, 0x13, 0xf1, 0xa4, 0x16, 0x27, 0x5f, 0x69, 0x37, 0x04, 0xf3, 0x3f, 0x1f, 0x6f, 0xe4, 0x85, + 0x49, 0xbf, 0x7b, 0x9e, 0xfc, 0x1a, 0xe5, 0x85, 0x61, 0xdf, 0x26, 0xbb, 0xf8, 0x04, 0x96, 0x1f, + 0x6f, 0xb0, 0xc8, 0xac, 0x9c, 0x65, 0x97, 0xbe, 0x6f, 0xa4, 0xde, 0xca, 0x92, 0x80, 0xec, 0x41, + 0x61, 0x6a, 0x86, 0xf4, 0xc4, 0x3c, 0xc3, 0xc9, 0x28, 0xd7, 0xfe, 0x89, 0x3c, 0xa3, 0x1f, 0x4c, + 0x9d, 0x70, 0x36, 0x3f, 0x68, 0x5a, 0xec, 0x68, 0x2b, 0x61, 0xb7, 0x0f, 0x16, 0xbf, 0xb7, 0xfc, + 0x07, 0xd3, 0xad, 0x78, 0xd8, 0x1b, 0x30, 0x1b, 0x3f, 0x00, 0x48, 0x2e, 0xfd, 0x6f, 0x0a, 0x90, + 0xa7, 0xe7, 0x79, 0xd2, 0x85, 0xe2, 0x0b, 0xf7, 0xdb, 0x04, 0x29, 0x7c, 0x66, 0xbe, 0x81, 0xf7, + 0x25, 0x83, 0xf7, 0xe5, 0xc7, 0x57, 0x79, 0x54, 0x34, 0xf1, 0x57, 0xea, 0xe2, 0xe4, 0x19, 0xfe, + 0xd3, 0xaf, 0x43, 0x29, 0x51, 0x89, 0x6b, 0xde, 0x3b, 0xb5, 0xa8, 0x1f, 0x1a, 0xa6, 0xeb, 0x6a, + 0x6b, 0xfa, 0x97, 0x0a, 0x94, 0x53, 0x4f, 0x0b, 0xf2, 0x53, 0xe1, 0x43, 0x54, 0xc3, 0x95, 0xa7, + 0x52, 0x26, 0x8e, 0x4a, 0x67, 0xbc, 0x3f, 0x8c, 0x8c, 0xe2, 0x81, 0x53, 0x62, 0xc8, 0x5b, 0x50, + 0x0e, 0xcc, 0x23, 0xdf, 0xa5, 0x51, 0xa6, 0x67, 0x30, 0xf4, 0xf2, 0x74, 0x22, 0x05, 0xa6, 0xfa, + 0xc7, 0x00, 0x98, 0xe4, 0x86, 0x4d, 0x03, 0x4b, 0xbe, 0xba, 0xaf, 0x96, 0xe8, 0xa5, 0x30, 0x16, + 0x93, 0x57, 0x21, 0x3b, 0xe7, 0x0e, 0x5e, 0xc5, 0x92, 0xd4, 0x0a, 0x81, 0xfe, 0x4b, 0xa8, 0x2e, + 0xbd, 0x78, 0xc8, 0xdb, 0x50, 0xb6, 0xa9, 0xa8, 0xea, 0xd1, 0xdd, 0x55, 0x52, 0x80, 0xb4, 0x82, + 0x34, 0x40, 0xf5, 0xcc, 0xa3, 0xc8, 0xfb, 0xd8, 0x00, 0x25, 0x64, 0x13, 0xaa, 0x27, 0xa6, 0xeb, + 0x8a, 0x6b, 0x3e, 0x30, 0x3d, 0x16, 0xa0, 0xeb, 0xf1, 0x55, 0x5e, 0x56, 0xe9, 0xbf, 0xc9, 0xc0, + 0xf5, 0x67, 0xbf, 0x8e, 0x70, 0x47, 0xe4, 0x43, 0x50, 0x71, 0xf7, 0x2f, 0x32, 0x1c, 0x23, 0xf2, + 0x79, 0x1e, 0x08, 0xf1, 0x17, 0xb2, 0xec, 0xcb, 0x7c, 0x21, 0x4b, 0x67, 0xb7, 0xfa, 0xa2, 0xd9, + 0xad, 0xff, 0x2a, 0x0b, 0xd7, 0x2e, 0x7e, 0x2d, 0x92, 0x31, 0xe4, 0x65, 0xdd, 0x8b, 0x1e, 0x7b, + 0x3f, 0xba, 0xea, 0x9b, 0x13, 0x23, 0x15, 0xa7, 0x7e, 0x44, 0x95, 0x7e, 0x1f, 0x65, 0x56, 0xbe, + 0x8f, 0xb2, 0xff, 0xc5, 0xf7, 0x91, 0xfa, 0xcc, 0xf7, 0xd1, 0x37, 0x60, 0x10, 0xdc, 0xdc, 0x86, + 0x62, 0x2c, 0xc7, 0xd2, 0x3d, 0x18, 0xf4, 0x46, 0xda, 0x9a, 0xa8, 0x09, 0x3b, 0xbd, 0xed, 0x89, + 0x31, 0xdc, 0x9b, 0xf4, 0x46, 0x9a, 0x42, 0xea, 0x50, 0x1e, 0xf5, 0xef, 0xdd, 0x8f, 0x05, 0x19, + 0x61, 0xb0, 0xbd, 0xb7, 0xb3, 0x23, 0xff, 0x67, 0xdb, 0x37, 0x1f, 0xfd, 0x7b, 0x7d, 0xed, 0xd1, + 0xf9, 0xba, 0xf2, 0xf7, 0xf3, 0x75, 0xe5, 0xcb, 0xf3, 0x75, 0xe5, 0x5f, 0xe7, 0xeb, 0xca, 0x6f, + 0xbf, 0x5a, 0x5f, 0xfb, 0x39, 0x2c, 0x5c, 0xf8, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7e, 0xe1, + 0x29, 0x6b, 0x9c, 0x17, 0x00, 0x00, } diff --git a/pkg/sql/expand_plan.go b/pkg/sql/expand_plan.go index 96391524871b..63a2777f169d 100644 --- a/pkg/sql/expand_plan.go +++ b/pkg/sql/expand_plan.go @@ -129,6 +129,8 @@ func doExpandPlan( ) n.props = n.joinOrdering() + n.joinHint = n.computeJoinHint() + case *ordinalityNode: // There may be too many columns in the required ordering. Filter them. params.desiredOrdering = n.restrictOrdering(params.desiredOrdering) diff --git a/pkg/sql/filter_opt.go b/pkg/sql/filter_opt.go index fc90ddadee0c..b8bf382e3936 100644 --- a/pkg/sql/filter_opt.go +++ b/pkg/sql/filter_opt.go @@ -683,7 +683,8 @@ func (p *planner) addJoinFilter( // There are four steps to the transformation below: // 1. For inner joins, incorporate the extra filter into the ON condition. - // 2. Extract any join equality constraints from the ON condition. + // 2. Extract any join equality constraints from the ON condition and + // append it to the equality indices of the join predicate. // 3. "Expand" the remaining ON condition with new constraints inferred based // on the equality columns (see expandOnCond). // 4. Propagate the filter and ON condition depending on the join type. diff --git a/pkg/sql/join.go b/pkg/sql/join.go index e30b8babdc0b..f30a376f84b4 100644 --- a/pkg/sql/join.go +++ b/pkg/sql/join.go @@ -35,6 +35,13 @@ const ( joinTypeFullOuter ) +type joinHint int + +const ( + joinHintNone joinHint = iota + joinHintInterleave +) + // bucket here is the set of rows for a given group key (comprised of // columns specified by the join constraints), 'seen' is used to determine if // there was a matching row in the opposite stream. @@ -139,6 +146,12 @@ type joinNode struct { // See computeMergeJoinOrdering. This information is used by distsql planning. mergeJoinOrdering sqlbase.ColumnOrdering + // joinHint is set during expandPlan to denote whether a better join + // algorithm can be used. For example, if the mergeJoinOrdering is on + // the entire interleave prefix, then joinHintInterleave will be set. + // This information is currently used by distsql planning. + joinHint joinHint + // ordering is set during expandPlan based on mergeJoinOrdering, but later // trimmed. props physicalProps @@ -820,3 +833,113 @@ func (n *joinNode) joinOrdering() physicalProps { info.ordering = info.reduce(info.ordering) return info } + +// interleaveNodes returns the interleave ancestor and descendant scan node if +// the left and right children of the joinNode are both scan nodes and either +// of them is interleaved into the other. Otherwise, nils are returned. +func (n *joinNode) interleaveNodes() (ancestor *scanNode, descendant *scanNode) { + leftScan, leftOk := n.left.plan.(*scanNode) + rightScan, rightOk := n.right.plan.(*scanNode) + + if !leftOk || !rightOk { + return nil, nil + } + + leftAncestors := leftScan.index.Interleave.Ancestors + rightAncestors := rightScan.index.Interleave.Ancestors + + // TODO(richardwu): The general case where we can have a join + // on a common ancestor's primary key requires traversing both + // ancestor slices. + // The descendant of the two tables must have have more + // interleave ancestors than the other. + // We assign the potential interleave ancestor and descendant. + if len(leftAncestors) > len(rightAncestors) { + descendant = leftScan + ancestor = rightScan + } else { + descendant = rightScan + ancestor = leftScan + } + + hasInterleaveRelation := false + // We check the descendantAncestors of the potential descendant to see + // if any of them is the potential ancestor. + for _, descAncestor := range descendant.index.Interleave.Ancestors { + if descAncestor.TableID == ancestor.desc.ID && descAncestor.IndexID == ancestor.index.ID { + hasInterleaveRelation = true + break + } + } + + // If the ancestor is indeed an interleave ancestor, then we return it. + if hasInterleaveRelation { + return ancestor, descendant + } + + return nil, nil +} + +func (n *joinNode) computeJoinHint() joinHint { + if len(n.mergeJoinOrdering) == 0 { + return joinHintNone + } + + ancestor, descendant := n.interleaveNodes() + + // There is no interleave ancestor/descendant scan node and thus no + // interleave relation. + if ancestor == nil || descendant == nil { + return joinHintNone + } + + var ancestorEqIndices []int + // We are guaranteed that both of the sources are scan nodes from + // n.interleaveNodes(). + if ancestor == n.left.plan.(*scanNode) { + ancestorEqIndices = n.pred.leftEqualityIndices + } else { + ancestorEqIndices = n.pred.rightEqualityIndices + } + + // We want full 1-1 correspondence between our join columns and the + // primary index of the ancestor. + // TODO(richardwu): We can relax this once we implement a hybrid + // hash/merge for interleave joins after forming merge groups with the + // interleave prefix (or when the merge join logic is combined with + // the interleave join logic). + if len(n.mergeJoinOrdering) != len(ancestor.index.ColumnIDs) { + return joinHintNone + } + + // We iterate through the ordering given by n.mergeJoinOrdering and check + // if the columns have a 1-1 correspondence to the interleave + // ancestor's primary index columns (i.e. interleave prefix). We + // naively return joinHintNone if any part of the ordering does not + // correspond. + for i, info := range n.mergeJoinOrdering { + colID := ancestor.index.ColumnIDs[i] + // info.ColIdx refers to i in ancestorEqIndices[i], which refers + // to the index of the source row. This corresponds to + // the index in scanNode.resultColumns. To convert the colID + // from the index descriptor, we can use the map provided by + // colIdxMap. + if ancestorEqIndices[info.ColIdx] != ancestor.colIdxMap[colID] { + // The column in the ordering does not correspond to + // the column in the interleave prefix. + // We should not try to do an interleave join. + return joinHintNone + } + } + + // We cannot do an interleave join if the tables require scanning in + // opposite directions. + if ancestor.reverse != descendant.reverse { + return joinHintNone + } + + // The columns in n.mergeJoinOrdering has a 1-1 correspondence with the + // columns in the interleave ancestor's primary index. We can indeed + // hint at the possibility of an interleave join. + return joinHintInterleave +} diff --git a/pkg/sql/join_predicate.go b/pkg/sql/join_predicate.go index e5cd26b2122e..f71f43bdd081 100644 --- a/pkg/sql/join_predicate.go +++ b/pkg/sql/join_predicate.go @@ -33,13 +33,17 @@ type joinPredicate struct { // operands. numLeftCols, numRightCols int - // The comparison function to use for each column. We need - // different functions because each USING column may have a different - // type (and they may be heterogeneous between left and right). + // The comparison function to use for each column. We need different + // functions because each USING column (or columns in equality ON + // expressions) may have a different type (and they may be + // heterogeneous between left and right). cmpFunctions []func(*parser.EvalContext, parser.Datum, parser.Datum) (parser.Datum, error) // left/rightEqualityIndices give the position of USING columns // on the left and right input row arrays, respectively. + // Left/right columns that have an equality constraint in the ON + // condition also have their indices appended when tryAddEqualityFilter + // is invoked (see planner.addJoinFilter). leftEqualityIndices []int rightEqualityIndices []int @@ -383,6 +387,7 @@ func pickUsingColumn( } if col.Name == colName { idx = j + break } } if idx == invalidColIdx { diff --git a/pkg/sql/join_test.go b/pkg/sql/join_test.go new file mode 100644 index 000000000000..69875c624258 --- /dev/null +++ b/pkg/sql/join_test.go @@ -0,0 +1,265 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package sql + +import ( + "fmt" + "strings" + "testing" + + "github.com/cockroachdb/cockroach/pkg/base" + "github.com/cockroachdb/cockroach/pkg/internal/client" + "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" + "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" + "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" + "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/pkg/errors" + "golang.org/x/net/context" +) + +func newTestScanNode(kvDB *client.DB, tableName string) (*scanNode, error) { + desc := sqlbase.GetTableDescriptor(kvDB, sqlutils.TestDB, tableName) + + scan := &scanNode{desc: desc, p: &planner{}} + err := scan.initDescDefaults(publicColumns, nil) + if err != nil { + return nil, err + } + scan.initOrdering(0) + return scan, nil +} + +func newTestJoinNode(kvDB *client.DB, leftName, rightName string) (*joinNode, error) { + left, err := newTestScanNode(kvDB, leftName) + if err != nil { + return nil, err + } + right, err := newTestScanNode(kvDB, rightName) + if err != nil { + return nil, err + } + return &joinNode{ + left: planDataSource{plan: left}, + right: planDataSource{plan: right}, + }, nil +} + +func setTestEqCols(n *joinNode, colNames []string) error { + left := n.left.plan.(*scanNode).index + right := n.right.plan.(*scanNode).index + + n.pred = &joinPredicate{} + n.mergeJoinOrdering = nil + + for _, colName := range colNames { + if colName == "" { + continue + } + + colFound := false + for i, leftCol := range left.ColumnNames { + if colName == leftCol { + n.pred.leftEqualityIndices = append( + n.pred.leftEqualityIndices, + i, + ) + colFound = true + break + } + } + if !colFound { + return errors.Errorf("column %s not found in index %s", colName, left.Name) + } + + colFound = false + for i, rightCol := range right.ColumnNames { + if colName == rightCol { + n.pred.rightEqualityIndices = append( + n.pred.rightEqualityIndices, + i, + ) + colFound = true + break + } + } + if !colFound { + return errors.Errorf("column %s not found in index %s", colName, left.Name) + } + } + + n.mergeJoinOrdering = computeMergeJoinOrdering( + planPhysicalProps(n.left.plan), + planPhysicalProps(n.right.plan), + n.pred.leftEqualityIndices, + n.pred.rightEqualityIndices, + ) + + return nil +} + +func genPermutations(slice []string) [][]string { + if len(slice) == 0 { + return [][]string{[]string{}} + } + + var out [][]string + for i, str := range slice { + recurse := append([]string{}, slice[:i]...) + recurse = append(recurse, slice[i+1:]...) + for _, subperms := range genPermutations(recurse) { + out = append(out, append([]string{str}, subperms...)) + } + } + + return out +} + +func TestInterleaveNodes(t *testing.T) { + defer leaktest.AfterTest(t)() + + s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) + defer s.Stopper().Stop(context.TODO()) + + sqlutils.CreateTestInterleaveHierarchy(t, sqlDB) + + for _, tc := range []struct { + table1 string + table2 string + ancestor string + descendant string + }{ + {"parent1", "child1", "parent1", "child1"}, + {"parent1", "child2", "parent1", "child2"}, + {"parent1", "grandchild1", "parent1", "grandchild1"}, + {"child1", "child2", "", ""}, + {"child1", "grandchild1", "child1", "grandchild1"}, + {"child2", "grandchild1", "", ""}, + {"parent1", "parent2", "", ""}, + {"parent2", "child1", "", ""}, + {"parent2", "grandchild1", "", ""}, + {"parent2", "child2", "", ""}, + } { + // Run the subtests with the tables in both positions (left + // and right). + for i := 0; i < 2; i++ { + testName := fmt.Sprintf("%s-%s", tc.table1, tc.table2) + t.Run(testName, func(t *testing.T) { + join, err := newTestJoinNode(kvDB, tc.table1, tc.table2) + if err != nil { + t.Fatal(err) + } + + ancestor, descendant := join.interleaveNodes() + + if tc.ancestor == tc.descendant && tc.ancestor == "" { + if ancestor != nil || descendant != nil { + t.Errorf("expected ancestor and descendant to both be nil") + } + return + } + + if ancestor == nil || descendant == nil { + t.Fatalf("expected ancestor and descendant to not be nil") + } + + if tc.ancestor != ancestor.desc.Name || tc.descendant != descendant.desc.Name { + t.Errorf( + "unexpected ancestor and descendant nodes.\nexpected: %s (ancestor), %s (descendant)\nactual: %s (ancestor), %s (descendant)", + tc.ancestor, tc.descendant, + ancestor.desc.Name, descendant.desc.Name, + ) + } + }) + // Rerun the same subtests but flip the tables + tc.table1, tc.table2 = tc.table2, tc.table1 + } + } +} + +func TestComputeJoinHint(t *testing.T) { + defer leaktest.AfterTest(t)() + + s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) + defer s.Stopper().Stop(context.TODO()) + + sqlutils.CreateTestInterleaveHierarchy(t, sqlDB) + + for _, tc := range []struct { + table1 string + table2 string + eqCols string + interleave bool + }{ + // Simple parent-child case. + {"parent1", "child1", "", false}, + {"parent1", "child1", "pid1", true}, + {"parent1", "child1", "v", false}, + {"parent1", "child1", "pid1,v", true}, + // Parent-grandchild case. + {"parent1", "grandchild1", "", false}, + {"parent1", "grandchild1", "pid1", true}, + {"parent1", "grandchild1", "v", false}, + {"parent1", "grandchild1", "pid1,v", true}, + // Multiple-column interleave prefix. + {"child1", "grandchild1", "", false}, + {"child1", "grandchild1", "cid1,cid2", true}, + {"child1", "grandchild1", "v", false}, + {"child1", "grandchild1", "cid1,cid2,v", true}, + // TODO(richardwu): update these once prefix/subset of + // interleave prefixes are permitted. + {"child1", "grandchild1", "cid1", false}, + {"child1", "grandchild1", "cid2", false}, + {"child1", "grandchild1", "cid1,v", false}, + {"child1", "grandchild1", "cid2,v", false}, + // Common ancestor example. + {"child1", "child2", "", false}, + // TODO(richardwu): update this when common ancestor interleave + // joins are possible. + {"child1", "child2", "pid1", false}, + } { + // Run the subtests with the tables in both positions (left and + // right). + for i := 0; i < 2; i++ { + // Run every permutation of the equality columns (just + // to ensure mergeJoinOrdering is invariant since we + // rely on it to correspond with the primary index of + // the ancestor). + eqCols := strings.Split(tc.eqCols, ",") + log.Warningf(context.TODO(), "%#v\n", eqCols) + for _, colNames := range genPermutations(eqCols) { + log.Warningf(context.TODO(), "%#v\n", colNames) + testName := fmt.Sprintf("%s-%s-%s", tc.table1, tc.table2, strings.Join(colNames, ",")) + t.Run(testName, func(t *testing.T) { + join, err := newTestJoinNode(kvDB, tc.table1, tc.table2) + if err != nil { + t.Fatal(err) + } + if err := setTestEqCols(join, colNames); err != nil { + t.Fatal(err) + } + + joinHint := join.computeJoinHint() + + if tc.interleave && joinHint != joinHintInterleave { + t.Errorf("expected join hint to be joinHintInterleave") + } + }) + } + // Rerun the same subtests but flip the tables + tc.table1, tc.table2 = tc.table2, tc.table1 + } + } +} diff --git a/pkg/sql/logictest/testdata/logic_test/interleaved_join b/pkg/sql/logictest/testdata/logic_test/interleaved_join new file mode 100644 index 000000000000..fb39bdbc5466 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/interleaved_join @@ -0,0 +1,89 @@ +# LogicTest: 5node-distsql + +statement ok +CREATE TABLE parent (pid INT PRIMARY KEY, a INT) + +statement ok +CREATE TABLE child ( + pid INT, + cid INT, + a INT, + PRIMARY KEY(pid, cid), + FOREIGN KEY (pid) REFERENCES parent +) +INTERLEAVE IN PARENT parent (pid) + +statement ok +CREATE TABLE grandchild ( + pid INT, + cid INT, + gcid INT, + a INT, + PRIMARY KEY(pid, cid, gcid), + FOREIGN KEY (pid, cid) REFERENCES child +) +INTERLEAVE IN PARENT child (pid, cid) + +# Insert some rows. +statement ok +INSERT INTO parent SELECT + pid, + mod(pid, 42) +FROM + GENERATE_SERIES(1, 200) AS ID(pid) + +statement ok +INSERT INTO child SELECT + mod(cid-1, 200) + 1, + cid, + mod(cid, 100) +FROM + GENERATE_SERIES(1, 1234) AS ID(cid) + +statement ok +INSERT INTO grandchild SELECT + mod(mod(gcid-1, 1234), 200) + 1, + mod(gcid-1, 1234) + 1, + gcid, + mod(gcid, 2017) FROM + GENERATE_SERIES(1, 9999) AS ID(gcid) + +# Split into ten parts. +statement ok +ALTER TABLE parent SPLIT AT SELECT i FROM GENERATE_SERIES(200/10, 200*9/10, 200/10) AS g(i) + +statement ok +ALTER TABLE child SPLIT AT SELECT i FROM GENERATE_SERIES((1234/10)::INT, (1234*9/10)::INT, (1234/10)::INT) AS g(i) + +statement ok +ALTER TABLE grandchild SPLIT AT SELECT i FROM GENERATE_SERIES((9999/10)::INT, (9999*9/10)::INT, (9999/10)::INT) AS g(i) + +# Relocate the ten parts to the five nodes. +statement ok +ALTER TABLE parent TESTING_RELOCATE + SELECT ARRAY[i%5+1], i FROM GENERATE_SERIES(0, 9) AS g(i) + +statement ok +ALTER TABLE child TESTING_RELOCATE + SELECT ARRAY[i%5+1], i FROM GENERATE_SERIES(0, 9) AS g(i) + +# Verify data placement. +# query TTITI colnames +# SHOW TESTING_RANGES FROM TABLE parent +# ---- +# Start Key End Key Range ID Replicas Lease Holder +# NULL /1 1 {1} 1 +# /1 /2 2 {2} 2 +# /2 /3 3 {3} 3 +# /3 /4 4 {4} 4 +# /4 /5 5 {5} 5 +# /5 /6 6 {1} 1 +# /6 /7 7 {2} 2 +# /7 /8 8 {3} 3 +# /8 /9 9 {4} 4 +# /9 NULL 10 {5} 5 + +statement ok +SET CLUSTER SETTING sql.distsql.merge_joins.enabled = true; + + diff --git a/pkg/sql/logictest/testdata/logic_test/show_source b/pkg/sql/logictest/testdata/logic_test/show_source index 3724c597f14e..508aa0ce062f 100644 --- a/pkg/sql/logictest/testdata/logic_test/show_source +++ b/pkg/sql/logictest/testdata/logic_test/show_source @@ -81,6 +81,7 @@ server.web_session_timeout 168h0m0s d the dura sql.defaults.distsql 0 e Default distributed SQL execution mode [off = 0, auto = 1, on = 2] sql.distsql.distribute_index_joins true b if set, for index joins we instantiate a join reader on every node that has a stream; if not set, we use a single join reader sql.distsql.merge_joins.enabled true b if set, we plan merge joins when possible +sql.distsql.interleaved_joins.enabled true b if set, we plan interleaved table joins when possible sql.distsql.temp_storage.joins true b set to true to enable use of disk for distributed sql joins sql.distsql.temp_storage.sorts true b set to true to enable use of disk for distributed sql sorts sql.distsql.temp_storage.workmem 64 MiB z maximum amount of memory in bytes a processor can use before falling back to temp storage diff --git a/pkg/sql/sqlbase/multirowfetcher_test.go b/pkg/sql/sqlbase/multirowfetcher_test.go index d50456ff2653..c1ff38adbe77 100644 --- a/pkg/sql/sqlbase/multirowfetcher_test.go +++ b/pkg/sql/sqlbase/multirowfetcher_test.go @@ -29,8 +29,6 @@ import ( "golang.org/x/net/context" ) -const testDB = "test" - type initFetcherArgs struct { tableDesc *TableDescriptor indexIdx int @@ -147,7 +145,7 @@ func TestNextRowSingle(t *testing.T) { // We try to read rows from each table. for tableName, table := range tables { t.Run(tableName, func(t *testing.T) { - tableDesc := GetTableDescriptor(kvDB, testDB, tableName) + tableDesc := GetTableDescriptor(kvDB, sqlutils.TestDB, tableName) args := []initFetcherArgs{ { tableDesc: tableDesc, @@ -307,7 +305,8 @@ func TestNextRowSecondaryIndex(t *testing.T) { // properly). for i := 1; i <= nNulls; i++ { r.Exec(fmt.Sprintf( - `INSERT INTO test.%s VALUES (%d, NULL, %d, %d);`, + `INSERT INTO %s.%s VALUES (%d, NULL, %d, %d);`, + squtils.TestDB, tableName, table.nRows+i, (table.nRows+i)%storingMods[0], @@ -329,7 +328,7 @@ func TestNextRowSecondaryIndex(t *testing.T) { // We try to read rows from each index. for tableName, table := range tables { t.Run(tableName, func(t *testing.T) { - tableDesc := GetTableDescriptor(kvDB, testDB, tableName) + tableDesc := GetTableDescriptor(kvDB, sqlutils.TestDB, tableName) colsNeeded := valNeededForCol[:table.nCols] if table.valNeededForCol != nil { @@ -504,7 +503,7 @@ func TestNextRowInterleave(t *testing.T) { tableName: "child1", modFactor: 5, schema: "p2 INT, c1 INT, v INT, PRIMARY KEY (p2, c1)", - interleaveSchema: "test.parent2 (p2)", + interleaveSchema: "parent2 (p2)", // child1 has more rows than parent2, thus some parent2 // rows will have multiple child1. nRows: 500, @@ -515,7 +514,7 @@ func TestNextRowInterleave(t *testing.T) { tableName: "grandchild1", modFactor: 7, schema: "p2 INT, c1 INT, gc1 INT, v INT, PRIMARY KEY (p2, c1, gc1)", - interleaveSchema: "test.child1 (p2, c1)", + interleaveSchema: "child1 (p2, c1)", nRows: 2000, nCols: 4, nVals: 4, @@ -524,7 +523,7 @@ func TestNextRowInterleave(t *testing.T) { tableName: "grandgrandchild1", modFactor: 12, schema: "p2 INT, c1 INT, gc1 INT, ggc1 INT, v INT, PRIMARY KEY (p2, c1, gc1, ggc1)", - interleaveSchema: "test.grandchild1 (p2, c1, gc1)", + interleaveSchema: "grandchild1 (p2, c1, gc1)", nRows: 350, nCols: 5, nVals: 5, @@ -533,7 +532,7 @@ func TestNextRowInterleave(t *testing.T) { tableName: "child2", modFactor: 42, schema: "p2 INT, c2 INT, v INT, PRIMARY KEY (p2, c2)", - interleaveSchema: "test.parent2 (p2)", + interleaveSchema: "parent2 (p2)", // child2 has less rows than parent2, thus not all // parent2 rows will have a nested child2 row. nRows: 100, @@ -610,7 +609,11 @@ func TestNextRowInterleave(t *testing.T) { ggc1idx := *tableArgs["grandgrandchild1"] // This is only possible since nrows(ggc1) < nrows(p2) thus c1 is // unique. - ggc1idx.indexSchema = `CREATE UNIQUE INDEX ggc1_unique_idx ON test.grandgrandchild1 (p2) INTERLEAVE IN PARENT test.parent2 (p2);` + ggc1idx.indexSchema = fmt.Sprintf( + `CREATE UNIQUE INDEX ggc1_unique_idx ON %s.grandgrandchild1 (p2) INTERLEAVE IN PARENT %s.parent2 (p2);`, + squtils.TestDB, + squtils.TestDB, + ) ggc1idx.indexName = "ggc1_unique_idx" ggc1idx.indexIdx = 1 // Last column v is not stored in this index. @@ -686,7 +689,7 @@ func TestNextRowInterleave(t *testing.T) { // MultiRowFetcher. idLookups := make(map[uint64]*fetcherEntryArgs, len(entries)) for i, entry := range entries { - tableDesc := GetTableDescriptor(kvDB, testDB, entry.tableName) + tableDesc := GetTableDescriptor(kvDB, sqlutils.TestDB, entry.tableName) var indexID IndexID if entry.indexIdx == 0 { indexID = tableDesc.PrimaryIndex.ID diff --git a/pkg/testutils/sqlutils/table_gen.go b/pkg/testutils/sqlutils/table_gen.go index 32d4a0e5fa08..6b34d0d74cc1 100644 --- a/pkg/testutils/sqlutils/table_gen.go +++ b/pkg/testutils/sqlutils/table_gen.go @@ -27,6 +27,8 @@ import ( const rowsPerInsert = 100 +const TestDB = "test" + // GenRowFn is a function that takes a (1-based) row index and returns a row of // Datums that will be converted to strings to form part of an INSERT statement. type GenRowFn func(row int) []parser.Datum @@ -64,16 +66,16 @@ func CreateTableInterleave( fn GenRowFn, ) { if interleaveSchema != "" { - interleaveSchema = fmt.Sprintf(`INTERLEAVE IN PARENT %s`, interleaveSchema) + interleaveSchema = fmt.Sprintf(`INTERLEAVE IN PARENT %s.%s`, TestDB, interleaveSchema) } r := MakeSQLRunner(t, sqlDB) - stmt := `CREATE DATABASE IF NOT EXISTS test;` - stmt += fmt.Sprintf(`CREATE TABLE test.%s (%s) %s;`, tableName, schema, interleaveSchema) + stmt := fmt.Sprintf(`CREATE DATABASE IF NOT EXISTS %s;`, TestDB) + stmt += fmt.Sprintf(`CREATE TABLE %s.%s (%s) %s;`, TestDB, tableName, schema, interleaveSchema) r.Exec(stmt) for i := 1; i <= numRows; { var buf bytes.Buffer - fmt.Fprintf(&buf, `INSERT INTO test.%s VALUES `, tableName) + fmt.Fprintf(&buf, `INSERT INTO %s.%s VALUES `, TestDB, tableName) batchEnd := i + rowsPerInsert if batchEnd > numRows { batchEnd = numRows @@ -85,6 +87,66 @@ func CreateTableInterleave( } } +// CreateTestInterleaveHierarchy generates the following interleave hierarchy for testing: +// +// parent1 (pid1) 100 +// child1 (pid1, cid1, cid2) 250 +// grandchild1 (pid1, cid1, cid2, gcid1) 1000 +// child2 (pid1, cid3, cid4) 50 +// parent2 (pid1) 20 + +func CreateTestInterleaveHierarchy(t *testing.T, sqlDB *gosql.DB) { + vMod := 42 + CreateTable(t, sqlDB, "parent1", + "pid1 INT PRIMARY KEY, v INT", + 100, + ToRowFn(RowIdxFn, RowModuloFn(vMod)), + ) + + CreateTableInterleave(t, sqlDB, "child1", + "pid1 INT, cid1 INT, cid2 INT, v INT, PRIMARY KEY (pid1, cid1, cid2)", + "parent1 (pid1)", + 250, + ToRowFn( + RowModuloShiftedFn(100), + RowIdxFn, + RowIdxFn, + RowModuloFn(vMod), + ), + ) + + CreateTableInterleave(t, sqlDB, "grandchild1", + "pid1 INT, cid1 INT, cid2 INT, gcid1 INT, v INT, PRIMARY KEY (pid1, cid1, cid2, gcid1)", + "child1 (pid1, cid1, cid2)", + 1000, + ToRowFn( + RowModuloShiftedFn(250, 100), + RowModuloShiftedFn(250), + RowModuloShiftedFn(250), + RowIdxFn, + RowModuloFn(vMod), + ), + ) + + CreateTableInterleave(t, sqlDB, "child2", + "pid1 INT, cid3 INT, cid4 INT, v INT, PRIMARY KEY (pid1, cid3, cid4)", + "parent1 (pid1)", + 50, + ToRowFn( + RowModuloShiftedFn(100), + RowIdxFn, + RowIdxFn, + RowModuloFn(vMod), + ), + ) + + CreateTable(t, sqlDB, "parent2", + "pid1 INT PRIMARY KEY, v INT", + 20, + ToRowFn(RowIdxFn, RowModuloFn(vMod)), + ) +} + // GenValueFn is a function that takes a (1-based) row index and returns a Datum // which will be converted to a string to form part of an INSERT statement. type GenValueFn func(row int) parser.Datum