// mergeAndSortSpans is used to merge a set of potentially overlapping spans // into a sorted set of non-overlapping spans. func mergeAndSortSpans(s roachpb.Spans) roachpb.Spans { // This is the classic 1D geometry problem of merging overlapping segments // on the X axis. It can be solved using a scan algorithm: we go through all // segment starting and ending points in X order (as "events") and keep // track of how many open segments we have at each point. events := make(spanEvents, 2*len(s)) for i := range s { events[2*i] = spanEvent{start: true, key: s[i].Key} events[2*i+1] = spanEvent{start: false, key: s[i].EndKey} if s[i].Key.Compare(s[i].EndKey) >= 0 { panic(fmt.Sprintf("invalid input span %s", sqlbase.PrettySpan(s[i], 0))) } } sort.Sort(events) openSpans := 0 s = s[:0] for _, e := range events { if e.start { if openSpans == 0 { // Start a new span. Because for equal keys the start events // come first, there can't be end events for this key. // The end of the span will be adjusted as we move forward. s = append(s, roachpb.Span{Key: e.key, EndKey: e.key}) } openSpans++ } else { openSpans-- if openSpans < 0 { panic("end span with no spans started") } else if openSpans == 0 { // Adjust the end of the last span. s[len(s)-1].EndKey = e.key } } } if openSpans != 0 { panic("scan ended with open spans") } return s }
// selectIndex analyzes the scanNode to determine if there is an index // available that can fulfill the query with a more restrictive scan. // // Analysis currently consists of a simplification of the filter expression, // replacing expressions which are not usable by indexes by "true". The // simplified expression is then considered for each index and a set of range // constraints is created for the index. The candidate indexes are ranked using // these constraints and the best index is selected. The constraints are then // transformed into a set of spans to scan within the index. // // The analyzeOrdering function is used to determine how useful the ordering of // an index is. If no particular ordering is desired, it can be nil. // // If preferOrderMatching is true, we prefer an index that matches the desired // ordering completely, even if it is not a covering index. func selectIndex( s *scanNode, analyzeOrdering analyzeOrderingFn, preferOrderMatching bool, ) (planNode, error) { if s.desc.IsEmpty() || (s.filter == nil && analyzeOrdering == nil && s.specifiedIndex == nil) { // No table or no where-clause, no ordering, and no specified index. s.initOrdering(0) return s, nil } candidates := make([]*indexInfo, 0, len(s.desc.Indexes)+1) if s.specifiedIndex != nil { // An explicit secondary index was requested. Only add it to the candidate // indexes list. candidates = append(candidates, &indexInfo{ desc: &s.desc, index: s.specifiedIndex, }) } else { candidates = append(candidates, &indexInfo{ desc: &s.desc, index: &s.desc.PrimaryIndex, }) for i := range s.desc.Indexes { candidates = append(candidates, &indexInfo{ desc: &s.desc, index: &s.desc.Indexes[i], }) } } for _, c := range candidates { c.init(s) } if s.filter != nil { // Analyze the filter expression, simplifying it and splitting it up into // possibly overlapping ranges. exprs, equivalent := analyzeExpr(s.filter) if log.V(2) { log.Infof(s.p.ctx(), "analyzeExpr: %s -> %s [equivalent=%v]", s.filter, exprs, equivalent) } // Check to see if the filter simplified to a constant. if len(exprs) == 1 && len(exprs[0]) == 1 { if d, ok := exprs[0][0].(*parser.DBool); ok && bool(!*d) { // The expression simplified to false. return &emptyNode{}, nil } } // If the simplified expression is equivalent and there is a single // disjunction, use it for the filter instead of the original expression. if equivalent && len(exprs) == 1 { s.filter = joinAndExprs(exprs[0]) } // TODO(pmattis): If "len(exprs) > 1" then we have multiple disjunctive // expressions. For example, "a <= 1 OR a >= 5" will get translated into // "[[a <= 1], [a >= 5]]". // // We currently map all disjunctions onto the same index; this works // well if we can derive constraints for a set of columns from all // disjunctions, e.g. `a < 5 OR a > 10`. // // However, we can't generate any constraints if the disjunctions refer // to different columns, e.g. `a > 1 OR b > 1`. We would need to perform // index selection independently for each of the disjunctive // expressions, and we would need infrastructure to do a // multi-index-join. There are complexities: if there are a large // number of disjunctive expressions we should limit how many indexes we // use. for _, c := range candidates { c.analyzeExprs(exprs) } } if s.noIndexJoin { // Eliminate non-covering indexes. We do this after the check above for // constant false filter. for i := 0; i < len(candidates); { if !candidates[i].covering { candidates[i] = candidates[len(candidates)-1] candidates = candidates[:len(candidates)-1] } else { i++ } } if len(candidates) == 0 { // The primary index is always covering. So the only way this can // happen is if we had a specified index. if s.specifiedIndex == nil { panic("no covering indexes") } return nil, fmt.Errorf("index \"%s\" is not covering and NO_INDEX_JOIN was specified", s.specifiedIndex.Name) } } if analyzeOrdering != nil { for _, c := range candidates { c.analyzeOrdering(s, analyzeOrdering, preferOrderMatching) } } indexInfoByCost(candidates).Sort() if log.V(2) { for i, c := range candidates { log.Infof(s.p.ctx(), "%d: selectIndex(%s): cost=%v constraints=%s reverse=%t", i, c.index.Name, c.cost, c.constraints, c.reverse) } } // After sorting, candidates[0] contains the best index. Copy its info into // the scanNode. c := candidates[0] s.index = c.index s.isSecondaryIndex = (c.index != &s.desc.PrimaryIndex) s.spans = makeSpans(c.constraints, c.desc, c.index) if len(s.spans) == 0 { // There are no spans to scan. return &emptyNode{}, nil } s.filter = applyIndexConstraints(s.filter, c.constraints) s.reverse = c.reverse var plan planNode if c.covering { s.initOrdering(c.exactPrefix) plan = s } else { // Note: makeIndexJoin destroys s and returns a new index scan // node. The filter in that node may be different from the // original table filter. plan, s = s.p.makeIndexJoin(s, c.exactPrefix) } if log.V(3) { log.Infof(s.p.ctx(), "%s: filter=%v", c.index.Name, s.filter) for i, span := range s.spans { log.Infof(s.p.ctx(), "%s/%d: %s", c.index.Name, i, sqlbase.PrettySpan(span, 2)) } } return plan, nil }