Skip to content

Commit

Permalink
Merge pull request #110 from ipfs/fix/datastore-order
Browse files Browse the repository at this point in the history
query: make datastore ordering act like a user would expect
  • Loading branch information
Stebalien authored Jan 24, 2019
2 parents eec3759 + 5b9ebb5 commit 3a9490a
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .gx/lastpubver
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.4.1: Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM
3.5.0: QmPGYyi1DtuWyUkG3PtvLz1xb4ScjnUvwJMCoX3cxeyxNr
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"license": "MIT",
"name": "go-datastore",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "3.4.1"
"version": "3.5.0"
}

62 changes: 22 additions & 40 deletions query/order.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,48 @@
package query

import (
"sort"
"bytes"
"strings"
)

// Order is an object used to order objects
type Order interface {

// Sort sorts the Entry slice according to
// the Order criteria.
Sort([]Entry)
Compare(a, b Entry) int
}

// OrderByValue is used to signal to datastores they
// should apply internal orderings. unfortunately, there
// is no way to apply order comparisons to interface{} types
// in Go, so if the datastore doesnt have a special way to
// handle these comparisons, you must provide an Order
// implementation that casts to the correct type.
type OrderByValue struct {
TypedOrder Order
// OrderByFunction orders the results based on the result of the given function.
type OrderByFunction func(a, b Entry) int

func (o OrderByFunction) Compare(a, b Entry) int {
return o(a, b)
}

func (o OrderByValue) Sort(res []Entry) {
if o.TypedOrder == nil {
panic("cannot order interface{} by value. see query docs.")
}
o.TypedOrder.Sort(res)
// OrderByValue is used to signal to datastores they should apply internal
// orderings.
type OrderByValue struct{}

func (o OrderByValue) Compare(a, b Entry) int {
return bytes.Compare(a.Value, b.Value)
}

// OrderByValueDescending is used to signal to datastores they
// should apply internal orderings. unfortunately, there
// is no way to apply order comparisons to interface{} types
// in Go, so if the datastore doesnt have a special way to
// handle these comparisons, you are SOL.
type OrderByValueDescending struct {
TypedOrder Order
}
// should apply internal orderings.
type OrderByValueDescending struct{}

func (o OrderByValueDescending) Sort(res []Entry) {
if o.TypedOrder == nil {
panic("cannot order interface{} by value. see query docs.")
}
o.TypedOrder.Sort(res)
func (o OrderByValueDescending) Compare(a, b Entry) int {
return -bytes.Compare(a.Value, b.Value)
}

// OrderByKey
type OrderByKey struct{}

func (o OrderByKey) Sort(res []Entry) {
sort.Stable(reByKey(res))
func (o OrderByKey) Compare(a, b Entry) int {
return strings.Compare(a.Key, b.Key)
}

// OrderByKeyDescending
type OrderByKeyDescending struct{}

func (o OrderByKeyDescending) Sort(res []Entry) {
sort.Stable(sort.Reverse(reByKey(res)))
func (o OrderByKeyDescending) Compare(a, b Entry) int {
return -strings.Compare(a.Key, b.Key)
}

type reByKey []Entry

func (s reByKey) Len() int { return len(s) }
func (s reByKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s reByKey) Less(i, j int) bool { return s[i].Key < s[j].Key }
2 changes: 1 addition & 1 deletion query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ cost of the layer of abstraction.
type Query struct {
Prefix string // namespaces the query to results whose keys have Prefix
Filters []Filter // filter results. apply sequentially
Orders []Order // order results. apply sequentially
Orders []Order // order results. apply hierarchically
Limit int // maximum number of results
Offset int // skip given number of results
KeysOnly bool // return only keys.
Expand Down
34 changes: 29 additions & 5 deletions query/query_impl.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package query

import "sort"

func DerivedResults(qr Results, ch <-chan Result) Results {
return &results{
query: qr.Query(),
Expand Down Expand Up @@ -73,9 +75,14 @@ func NaiveOffset(qr Results, offset int) Results {
return DerivedResults(qr, ch)
}

// NaiveOrder reorders results according to given Order.
// NaiveOrder reorders results according to given orders.
// WARNING: this is the only non-stream friendly operation!
func NaiveOrder(qr Results, o Order) Results {
func NaiveOrder(qr Results, orders ...Order) Results {
// Short circuit.
if len(orders) == 0 {
return qr
}

ch := make(chan Result)
var entries []Entry
go func() {
Expand All @@ -89,8 +96,25 @@ func NaiveOrder(qr Results, o Order) Results {

entries = append(entries, e.Entry)
}
sort.Slice(entries, func(i int, j int) bool {
a, b := entries[i], entries[j]

for _, cmp := range orders {
switch cmp.Compare(a, b) {
case 0:
case -1:
return true
case 1:
return false
}
}

// This gives us a *stable* sort for free. We don't care
// preserving the order from the underlying datastore
// because it's undefined.
return a.Key < b.Key
})

o.Sort(entries)
for _, e := range entries {
ch <- Result{Entry: e}
}
Expand All @@ -106,8 +130,8 @@ func NaiveQueryApply(q Query, qr Results) Results {
for _, f := range q.Filters {
qr = NaiveFilter(qr, f)
}
for _, o := range q.Orders {
qr = NaiveOrder(qr, o)
if len(q.Orders) > 0 {
qr = NaiveOrder(qr, q.Orders...)
}
if q.Offset != 0 {
qr = NaiveOffset(qr, q.Offset)
Expand Down

0 comments on commit 3a9490a

Please sign in to comment.