Skip to content

Commit

Permalink
sql: support FORCE_INDEX and NO_INDEX_JOIN hints
Browse files Browse the repository at this point in the history
Add support for `SELECT FROM table@{FORCE_INDEX=idx,NO_INDEX_JOIN}`, as
described in the index_hints RFC.

Closes #5625.
  • Loading branch information
RaduBerinde committed Apr 1, 2016
1 parent b329564 commit bc57d75
Show file tree
Hide file tree
Showing 13 changed files with 3,582 additions and 3,341 deletions.
4 changes: 2 additions & 2 deletions docs/RFCS/index_hints.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
- Feature Name: index_hints
- Status: draft
- Status: completed
- Start Date: 2014-03-31
- Authors: Radu
- RFC PR: (PR # after acceptance of initial draft)
- RFC PR: #5762
- Cockroach Issue: #5625

# Summary
Expand Down
5 changes: 4 additions & 1 deletion sql/backfill.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ func (p *planner) backfillBatch(b *client.Batch, tableDesc *TableDescriptor) *ro
desc: *tableDesc,
}
scan.initDescDefaults()
rows := selectIndex(scan, nil, false)
rows, err := selectIndex(scan, nil, false)
if err != nil {
return roachpb.NewError(err)
}

// Construct a map from column ID to the index the value appears at within a
// row.
Expand Down
35 changes: 30 additions & 5 deletions sql/index_selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/cockroachdb/cockroach/roachpb"
"github.com/cockroachdb/cockroach/sql/parser"
"github.com/cockroachdb/cockroach/util"
"github.com/cockroachdb/cockroach/util/encoding"
"github.com/cockroachdb/cockroach/util/log"
)
Expand Down Expand Up @@ -81,11 +82,13 @@ type analyzeOrderingFn func(indexOrdering orderingInfo) (matchingCols, totalCols
//
// 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 {
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
return s, nil
}

candidates := make([]*indexInfo, 0, len(s.desc.Indexes)+1)
Expand Down Expand Up @@ -125,7 +128,7 @@ func selectIndex(s *scanNode, analyzeOrdering analyzeOrderingFn, preferOrderMatc
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{}
return &emptyNode{}, nil
}
}

Expand Down Expand Up @@ -156,6 +159,28 @@ func selectIndex(s *scanNode, analyzeOrdering analyzeOrderingFn, preferOrderMatc
}
}

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, util.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)
Expand All @@ -179,7 +204,7 @@ func selectIndex(s *scanNode, analyzeOrdering analyzeOrderingFn, preferOrderMatc
s.spans = makeSpans(c.constraints, c.desc.ID, c.index)
if len(s.spans) == 0 {
// There are no spans to scan.
return &emptyNode{}
return &emptyNode{}, nil
}
s.filter = applyConstraints(s.filter, c.constraints)
noFilter := (s.filter == nil)
Expand Down Expand Up @@ -210,7 +235,7 @@ func selectIndex(s *scanNode, analyzeOrdering analyzeOrderingFn, preferOrderMatc
}
}

return plan
return plan, nil
}

type indexConstraint struct {
Expand Down
17 changes: 14 additions & 3 deletions sql/parser/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func (n *QualifiedName) NormalizeTableName(database string) error {
return fmt.Errorf("invalid table name: %s", n)
}
if len(n.Indirect) == 2 {
if _, ok := n.Indirect[1].(IndexIndirection); !ok {
if _, ok := n.Indirect[1].(*IndexIndirection); !ok {
return fmt.Errorf("invalid table name: %s", n)
}
}
Expand Down Expand Up @@ -352,7 +352,7 @@ func (n *QualifiedName) QualifyWithDatabase(database string) error {
switch n.Indirect[0].(type) {
case NameIndirection:
// Nothing to do.
case IndexIndirection:
case *IndexIndirection:
// table@index -> database.table@index
// * -> database.*
//
Expand Down Expand Up @@ -476,11 +476,22 @@ func (n *QualifiedName) Index() string {
panic(fmt.Sprintf("%s is not a table name", n))
}
if len(n.Indirect) == 2 {
return string(n.Indirect[1].(IndexIndirection))
return string(n.Indirect[1].(*IndexIndirection).Index)
}
return ""
}

// NoIndexJoin returns whether a NO_INDEX_JOIN hint was given as part of the table.
func (n *QualifiedName) NoIndexJoin() bool {
if n.normalized != tableName {
panic(fmt.Sprintf("%s is not a table name", n))
}
if len(n.Indirect) == 2 {
return n.Indirect[1].(*IndexIndirection).NoIndexJoin
}
return false
}

// Column returns the column portion of the name. Note that the returned string
// is not quoted even if the name is a keyword.
func (n *QualifiedName) Column() string {
Expand Down
4 changes: 4 additions & 0 deletions sql/parser/expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func TestNormalizeTableName(t *testing.T) {
{`test.foo`, `test.foo`, ``, ``},
{`bar.foo`, `bar.foo`, `test`, ``},
{`foo@bar`, `test.foo@bar`, `test`, ``},
{`foo@{FORCE_INDEX=bar}`, `test.foo@bar`, `test`, ``},
{`foo@{NO_INDEX_JOIN}`, `test.foo@{NO_INDEX_JOIN}`, `test`, ``},
{`foo@{FORCE_INDEX=bar,NO_INDEX_JOIN}`, `test.foo@{FORCE_INDEX=bar,NO_INDEX_JOIN}`,
`test`, ``},
{`test.foo@bar`, `test.foo@bar`, ``, ``},

{`""`, ``, ``, `empty table name`},
Expand Down
18 changes: 14 additions & 4 deletions sql/parser/indirection.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,21 @@ func (n NameIndirection) String() string {
return fmt.Sprintf(".%s", Name(n))
}

// IndexIndirection represents ":<name>" in an indirection expression.
type IndexIndirection Name
// IndexIndirection represents "@<name>" or "@{param[,param]}" where param is
// one of "FORCE_INDEX=<name>" and "NO_INDEX_JOIN" in an indirection expression.
type IndexIndirection struct {
Index Name
NoIndexJoin bool
}

func (n IndexIndirection) String() string {
return fmt.Sprintf("@%s", Name(n))
func (n *IndexIndirection) String() string {
if !n.NoIndexJoin {
return fmt.Sprintf("@%s", n.Index)
}
if n.Index == "" {
return "@{NO_INDEX_JOIN}"
}
return fmt.Sprintf("@{FORCE_INDEX=%s,NO_INDEX_JOIN}", n.Index)
}

// StarIndirection represents ".*" in an indirection expression.
Expand Down
2 changes: 2 additions & 0 deletions sql/parser/keywords.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ func TestParse(t *testing.T) {
{`SELECT a.b[1 + 1:4][3] FROM t`},
{`SELECT 'a' FROM t`},
{`SELECT 'a' FROM t@bar`},
{`SELECT 'a' FROM t@{NO_INDEX_JOIN}`},
{`SELECT 'a' FROM t@{FORCE_INDEX=bar,NO_INDEX_JOIN}`},

{`SELECT 'a' AS "12345"`},
{`SELECT 'a' AS clnm`},
Expand Down Expand Up @@ -438,6 +440,10 @@ func TestParse2(t *testing.T) {
{`SELECT INTERVAL 'foo'`, `SELECT CAST('foo' AS INTERVAL)`},
{`SELECT CHAR 'foo'`, `SELECT CAST('foo' AS CHAR)`},

{`SELECT 'a' FROM t@{FORCE_INDEX=bar}`, `SELECT 'a' FROM t@bar`},
{`SELECT 'a' FROM t@{NO_INDEX_JOIN,FORCE_INDEX=bar}`,
`SELECT 'a' FROM t@{FORCE_INDEX=bar,NO_INDEX_JOIN}`},

{`SELECT FROM t WHERE a IS UNKNOWN`, `SELECT FROM t WHERE a IS NULL`},
{`SELECT FROM t WHERE a IS NOT UNKNOWN`, `SELECT FROM t WHERE a IS NOT NULL`},

Expand Down
Loading

0 comments on commit bc57d75

Please sign in to comment.