Skip to content

Commit 47a9627

Browse files
authored
Merge pull request #28 from ipfs/feat/fast-reverse-query
Fast reverse query
2 parents 253c31f + 272a284 commit 47a9627

File tree

6 files changed

+71
-135
lines changed

6 files changed

+71
-135
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ env:
1010
global:
1111
- GOTFLAGS="-race -cpu=5"
1212
matrix:
13-
- BUILD_DEPTYPE=gx
1413
- BUILD_DEPTYPE=gomod
1514

1615

@@ -24,7 +23,6 @@ script:
2423

2524
cache:
2625
directories:
27-
- $GOPATH/src/gx
2826
- $GOPATH/pkg/mod
2927
- $HOME/.cache/go-build
3028

datastore.go

Lines changed: 22 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66

77
ds "github.com/ipfs/go-datastore"
88
dsq "github.com/ipfs/go-datastore/query"
9-
"github.com/jbenet/goprocess"
109
"github.com/syndtr/goleveldb/leveldb"
1110
"github.com/syndtr/goleveldb/leveldb/errors"
1211
"github.com/syndtr/goleveldb/leveldb/iterator"
@@ -114,25 +113,33 @@ func (a *accessor) Delete(key ds.Key) (err error) {
114113
}
115114

116115
func (a *accessor) Query(q dsq.Query) (dsq.Results, error) {
117-
return a.queryNew(q)
118-
}
119-
120-
func (a *accessor) queryNew(q dsq.Query) (dsq.Results, error) {
121-
if len(q.Filters) > 0 ||
122-
len(q.Orders) > 0 ||
123-
q.Limit > 0 ||
124-
q.Offset > 0 {
125-
return a.queryOrig(q)
126-
}
127116
var rnge *util.Range
117+
118+
// make a copy of the query for the fallback naive query implementation.
119+
// don't modify the original so res.Query() returns the correct results.
120+
qNaive := q
128121
if q.Prefix != "" {
129122
rnge = util.BytesPrefix([]byte(q.Prefix))
123+
qNaive.Prefix = ""
130124
}
131125
i := a.ldb.NewIterator(rnge, nil)
132-
return dsq.ResultsFromIterator(q, dsq.Iterator{
126+
next := i.Next
127+
if len(q.Orders) > 0 {
128+
switch q.Orders[0].(type) {
129+
case dsq.OrderByKey, *dsq.OrderByKey:
130+
qNaive.Orders = nil
131+
case dsq.OrderByKeyDescending, *dsq.OrderByKeyDescending:
132+
next = func() bool {
133+
next = i.Prev
134+
return i.Last()
135+
}
136+
qNaive.Orders = nil
137+
default:
138+
}
139+
}
140+
r := dsq.ResultsFromIterator(q, dsq.Iterator{
133141
Next: func() (dsq.Result, bool) {
134-
ok := i.Next()
135-
if !ok {
142+
if !next() {
136143
return dsq.Result{}, false
137144
}
138145
k := string(i.Key())
@@ -149,86 +156,8 @@ func (a *accessor) queryNew(q dsq.Query) (dsq.Results, error) {
149156
i.Release()
150157
return nil
151158
},
152-
}), nil
153-
}
154-
155-
func (a *accessor) queryOrig(q dsq.Query) (dsq.Results, error) {
156-
// we can use multiple iterators concurrently. see:
157-
// https://godoc.org/github.com/syndtr/goleveldb/leveldb#DB.NewIterator
158-
// advance the iterator only if the reader reads
159-
//
160-
// run query in own sub-process tied to Results.Process(), so that
161-
// it waits for us to finish AND so that clients can signal to us
162-
// that resources should be reclaimed.
163-
qrb := dsq.NewResultBuilder(q)
164-
qrb.Process.Go(func(worker goprocess.Process) {
165-
a.runQuery(worker, qrb)
166159
})
167-
168-
// go wait on the worker (without signaling close)
169-
go qrb.Process.CloseAfterChildren()
170-
171-
// Now, apply remaining things (filters, order)
172-
qr := qrb.Results()
173-
for _, f := range q.Filters {
174-
qr = dsq.NaiveFilter(qr, f)
175-
}
176-
if len(q.Orders) > 0 {
177-
switch q.Orders[0].(type) {
178-
case dsq.OrderByKey, *dsq.OrderByKey:
179-
// Default ordering
180-
default:
181-
qr = dsq.NaiveOrder(qr, q.Orders...)
182-
}
183-
}
184-
return qr, nil
185-
}
186-
187-
func (a *accessor) runQuery(worker goprocess.Process, qrb *dsq.ResultBuilder) {
188-
var rnge *util.Range
189-
if qrb.Query.Prefix != "" {
190-
rnge = util.BytesPrefix([]byte(qrb.Query.Prefix))
191-
}
192-
i := a.ldb.NewIterator(rnge, nil)
193-
defer i.Release()
194-
195-
// advance iterator for offset
196-
if qrb.Query.Offset > 0 {
197-
for j := 0; j < qrb.Query.Offset; j++ {
198-
i.Next()
199-
}
200-
}
201-
202-
// iterate, and handle limit, too
203-
for sent := 0; i.Next(); sent++ {
204-
// end early if we hit the limit
205-
if qrb.Query.Limit > 0 && sent >= qrb.Query.Limit {
206-
break
207-
}
208-
209-
k := string(i.Key())
210-
e := dsq.Entry{Key: k}
211-
212-
if !qrb.Query.KeysOnly {
213-
buf := make([]byte, len(i.Value()))
214-
copy(buf, i.Value())
215-
e.Value = buf
216-
}
217-
218-
select {
219-
case qrb.Output <- dsq.Result{Entry: e}: // we sent it out
220-
case <-worker.Closing(): // client told us to end early.
221-
break
222-
}
223-
}
224-
225-
if err := i.Error(); err != nil {
226-
select {
227-
case qrb.Output <- dsq.Result{Error: err}: // client read our error
228-
case <-worker.Closing(): // client told us to end.
229-
return
230-
}
231-
}
160+
return dsq.NaiveQueryApply(qNaive, r), nil
232161
}
233162

234163
// DiskUsage returns the current disk size used by this levelDB.

ds_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io/ioutil"
77
"os"
8+
"sort"
89
"testing"
910

1011
ds "github.com/ipfs/go-datastore"
@@ -101,6 +102,33 @@ func testQuery(t *testing.T, d *Datastore) {
101102
"/a/b/d",
102103
"/a/c",
103104
}, rs)
105+
106+
// test order
107+
108+
rs, err = d.Query(dsq.Query{Orders: []dsq.Order{dsq.OrderByKey{}}})
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
113+
keys := make([]string, 0, len(testcases))
114+
for k := range testcases {
115+
keys = append(keys, k)
116+
}
117+
sort.Strings(keys)
118+
119+
expectOrderedMatches(t, keys, rs)
120+
121+
rs, err = d.Query(dsq.Query{Orders: []dsq.Order{dsq.OrderByKeyDescending{}}})
122+
if err != nil {
123+
t.Fatal(err)
124+
}
125+
126+
// reverse
127+
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
128+
keys[i], keys[j] = keys[j], keys[i]
129+
}
130+
131+
expectOrderedMatches(t, keys, rs)
104132
}
105133

106134
func TestQuery(t *testing.T) {
@@ -125,6 +153,7 @@ func TestQueryRespectsProcessMem(t *testing.T) {
125153
}
126154

127155
func expectMatches(t *testing.T, expect []string, actualR dsq.Results) {
156+
t.Helper()
128157
actual, err := actualR.Rest()
129158
if err != nil {
130159
t.Error(err)
@@ -146,6 +175,23 @@ func expectMatches(t *testing.T, expect []string, actualR dsq.Results) {
146175
}
147176
}
148177

178+
func expectOrderedMatches(t *testing.T, expect []string, actualR dsq.Results) {
179+
t.Helper()
180+
actual, err := actualR.Rest()
181+
if err != nil {
182+
t.Error(err)
183+
}
184+
185+
if len(actual) != len(expect) {
186+
t.Error("not enough", expect, actual)
187+
}
188+
for i := range expect {
189+
if expect[i] != actual[i].Key {
190+
t.Errorf("expected %q, got %q", expect[i], actual[i].Key)
191+
}
192+
}
193+
}
194+
149195
func testBatching(t *testing.T, d *Datastore) {
150196
b, err := d.Batch()
151197
if err != nil {

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module github.com/ipfs/go-ds-leveldb
22

33
require (
4-
github.com/ipfs/go-datastore v0.0.1
5-
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8
4+
github.com/ipfs/go-datastore v0.0.3
65
github.com/syndtr/goleveldb v1.0.0
76
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
1010
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1111
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
1212
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
13-
github.com/ipfs/go-datastore v0.0.1 h1:AW/KZCScnBWlSb5JbnEnLKFWXL224LBEh/9KXXOrUms=
14-
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
13+
github.com/ipfs/go-datastore v0.0.3 h1:/eP3nMDmLzMJNoWSSYvEkmMTTrm9FFCN+JraP9NdlwU=
14+
github.com/ipfs/go-datastore v0.0.3/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
1515
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
1616
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw=
1717
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=

package.json

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)