Skip to content

Commit

Permalink
Add support for ORDER BY.
Browse files Browse the repository at this point in the history
Sorting is currently performed in memory. Ordering is not taken into
consideration during index selection, but sorting is not performed if
the output is already in the correct order.

Fixes #2096.
  • Loading branch information
petermattis committed Aug 19, 2015
1 parent 2928f5e commit fab23e0
Show file tree
Hide file tree
Showing 8 changed files with 516 additions and 14 deletions.
4 changes: 1 addition & 3 deletions sql/logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ func TestLogic(t *testing.T) {
logicTestPath + "/test/index/delete/*/*.test",
logicTestPath + "/test/index/in/*/*.test",
logicTestPath + "/test/index/orderby/*/*.test",
logicTestPath + "/test/index/orderby_nosort/*/*.test",

// TODO(pmattis): We don't support aggregate functions.
// logicTestPath + "/test/random/expr/*.test",
Expand All @@ -478,9 +479,6 @@ func TestLogic(t *testing.T) {
// TODO(pmattis): We don't support views.
// logicTestPath + "/test/index/view/*/*.test",

// TODO(pmattis): We don't support order by.
// logicTestPath + "/test/index/orderby_nosort/*/*.test",

// TODO(pmattis): We don't support joins.
// [uses joins] logicTestPath + "/test/index/random/*/*.test",
// [uses joins] logicTestPath + "/test/random/aggregates/*.test",
Expand Down
101 changes: 101 additions & 0 deletions sql/parser/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ var errZeroModulus = errors.New("zero modulus")
type Datum interface {
Expr
Type() string
// Compare returns -1 if the receiver is less than other, 0 if receiver is
// equal to other and +1 if receiver is greater than other.
Compare(other Datum) int
}

var _ Datum = DBool(false)
Expand All @@ -65,6 +68,22 @@ func (d DBool) Type() string {
return "bool"
}

// Compare implements the Datum interface.
func (d DBool) Compare(other Datum) int {
v, ok := other.(DBool)
if !ok {
// Anything other than a DBool compares greater (e.g. `true > NULL`).
return 1
}
if d && !v {
return -1
}
if !v && d {
return 1
}
return 0
}

func (d DBool) String() string {
return BoolVal(d).String()
}
Expand All @@ -77,6 +96,22 @@ func (d DInt) Type() string {
return "int"
}

// Compare implements the Datum interface.
func (d DInt) Compare(other Datum) int {
v, ok := other.(DInt)
if !ok {
// Anything other than a DInt compares greater (e.g. `1 > NULL`).
return 1
}
if d < v {
return -1
}
if d > v {
return 1
}
return 0
}

func (d DInt) String() string {
return strconv.FormatInt(int64(d), 10)
}
Expand All @@ -89,6 +124,22 @@ func (d DFloat) Type() string {
return "float"
}

// Compare implements the Datum interface.
func (d DFloat) Compare(other Datum) int {
v, ok := other.(DFloat)
if !ok {
// Anything other than a DFloat compares greater (e.g. `1.0 > NULL`).
return 1
}
if d < v {
return -1
}
if d > v {
return 1
}
return 0
}

func (d DFloat) String() string {
return strconv.FormatFloat(float64(d), 'g', -1, 64)
}
Expand All @@ -101,6 +152,22 @@ func (d DString) Type() string {
return "string"
}

// Compare implements the Datum interface.
func (d DString) Compare(other Datum) int {
v, ok := other.(DString)
if !ok {
// Anything other than a DString compares greater (e.g. `"hello" > NULL`).
return 1
}
if d < v {
return -1
}
if d > v {
return 1
}
return 0
}

func (d DString) String() string {
return StrVal(d).String()
}
Expand All @@ -113,6 +180,32 @@ func (d DTuple) Type() string {
return "tuple"
}

// Compare implements the Datum interface.
func (d DTuple) Compare(other Datum) int {
v, ok := other.(DTuple)
if !ok {
// Anything other than a DTuple compares greater (e.g. `(1, 2) > NULL`).
return 1
}
n := len(d)
if n > len(v) {
n = len(v)
}
for i := 0; i < n; i++ {
c := d[i].Compare(v[i])
if c != 0 {
return c
}
}
if len(d) < len(v) {
return -1
}
if len(d) > len(v) {
return 1
}
return 0
}

func (d DTuple) String() string {
var buf bytes.Buffer
_ = buf.WriteByte('(')
Expand All @@ -136,6 +229,14 @@ func (d dNull) Type() string {
return "NULL"
}

// Compare implements the Datum interface.
func (d dNull) Compare(other Datum) int {
if other == DNull {
return 0
}
return -1
}

func (d dNull) String() string {
return "NULL"
}
Expand Down
7 changes: 6 additions & 1 deletion sql/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ type planNode interface {
// Columns returns the column names. The length of the returned slice is
// guaranteed to be equal to the length of the tuple returned by Values().
Columns() []string
// The indexes of the columns the output is ordered by. Indexes are 1-based
// and negative indexes indicate descending ordering. The []int result may be
// nil if no ordering has been performed.
Ordering() []int
// Values returns the values at the current row. The result is only valid
// until the next call to Next().
Values() parser.DTuple
Expand All @@ -133,6 +137,7 @@ type planNode interface {
}

var _ planNode = &scanNode{}
var _ planNode = &sortNode{}
var _ planNode = &valuesNode{}

// TODO(pmattis): orderByNode, groupByNode, joinNode.
// TODO(pmattis): groupByNode, joinNode.
36 changes: 31 additions & 5 deletions sql/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type scanNode struct {
isSecondaryIndex bool
columns []string
columnIDs []ColumnID
ordering []int
err error
indexKey []byte // the index key of the current row
kvs []client.KeyValue // the raw key/value pairs
Expand All @@ -90,6 +91,10 @@ func (n *scanNode) Columns() []string {
return n.columns
}

func (n *scanNode) Ordering() []int {
return n.ordering
}

func (n *scanNode) Values() parser.DTuple {
return n.row
}
Expand Down Expand Up @@ -219,11 +224,6 @@ func (n *scanNode) initScan() bool {
}

// Prepare our index key vals slice.
n.columnIDs = n.index.ColumnIDs
if !n.index.Unique {
// Non-unique indexes have the primary key columns appended to their key.
n.columnIDs = append(n.columnIDs, n.desc.PrimaryIndex.ColumnIDs...)
}
n.vals, n.err = makeKeyVals(n.desc, n.columnIDs)
if n.err != nil {
return false
Expand Down Expand Up @@ -262,6 +262,32 @@ func (n *scanNode) initTargets(targets parser.SelectExprs) error {
return nil
}

// initOrdering initializes the ordering info using the selected index. This
// must be called after index selection is performed.
func (n *scanNode) initOrdering() {
if n.index == nil {
return
}

n.columnIDs = n.index.ColumnIDs
if !n.index.Unique {
// Non-unique indexes have the primary key columns appended to their key.
n.columnIDs = append(n.columnIDs, n.desc.PrimaryIndex.ColumnIDs...)
}

// Loop over the column IDs and determine if they are used for any of the
// render targets.
n.ordering = nil
for _, colID := range n.columnIDs {
for i, r := range n.render {
if qval, ok := r.(*qvalue); ok && qval.col.ID == colID {
n.ordering = append(n.ordering, i+1)
break
}
}
}
}

func (n *scanNode) addRender(target parser.SelectExpr) error {
// If a QualifiedName has a StarIndirection suffix we need to match the
// prefix of the qualified name to one of the tables in the query and
Expand Down
21 changes: 16 additions & 5 deletions sql/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,26 @@ import (
// Notes: postgres requires SELECT. Also requires UPDATE on "FOR UPDATE".
// mysql requires SELECT.
func (p *planner) Select(n *parser.Select) (planNode, error) {
s := &scanNode{txn: p.txn}
if err := s.initFrom(p, n.From); err != nil {
scan := &scanNode{txn: p.txn}
if err := scan.initFrom(p, n.From); err != nil {
return nil, err
}
if err := s.initWhere(n.Where); err != nil {
if err := scan.initWhere(n.Where); err != nil {
return nil, err
}
if err := s.initTargets(n.Exprs); err != nil {
if err := scan.initTargets(n.Exprs); err != nil {
return nil, err
}
return p.selectIndex(s)
sort, err := p.orderBy(n, scan)
if err != nil {
return nil, err
}
// TODO(pmattis): Consider ORDER BY during index selection.
plan, err := p.selectIndex(scan)
if err != nil {
return nil, err
}
return sort.wrap(plan), nil
}

type subqueryVisitor struct {
Expand Down Expand Up @@ -115,6 +124,7 @@ func (p *planner) expandSubqueries(stmt parser.Statement) error {
func (p *planner) selectIndex(s *scanNode) (planNode, error) {
if s.desc == nil || s.filter == nil {
// No table or where-clause.
s.initOrdering()
return s, nil
}

Expand Down Expand Up @@ -163,6 +173,7 @@ func (p *planner) selectIndex(s *scanNode) (planNode, error) {
s.isSecondaryIndex = (s.index != &s.desc.PrimaryIndex)
s.startKey = candidates[0].makeStartKey()
s.endKey = candidates[0].makeEndKey()
s.initOrdering()
return s, nil
}

Expand Down
Loading

0 comments on commit fab23e0

Please sign in to comment.