diff --git a/.travis.yml b/.travis.yml
index fa097a3..5ddc6f5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ sudo: required
services:
- docker
before_install:
- - docker run -d -p 10101:10101 pilosa/pilosa:v0.3.2
+ - docker run -d -p 10101:10101 pilosa/pilosa:v0.4.0
- go get github.com/mattn/goveralls
addons:
apt:
diff --git a/README.md b/README.md
index 98e8689..b6aa429 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Go Client for Pilosa
-
+
@@ -12,9 +12,12 @@ Go client for Pilosa high performance distributed bitmap index.
## Change Log
-* **v0.3.2**:
+* **v0.4.0** (2017-06-09):
+ * Supports Pilosa Server v0.4.0.
* Updated the accepted values for index, frame names and labels to match with the Pilosa server.
* `Union` query now accepts zero or more variadic arguments. `Intersect` and `Difference` queries now accept one or more variadic arguments.
+ * Added `inverse TopN` and `inverse Range` calls.
+ * Inverse enabled status of frames is not checked on the client side.
* **v0.3.1** (2017-05-01):
* Initial version
@@ -173,13 +176,18 @@ Index:
Frame:
* `Bitmap(rowID uint64) *PQLBitmapQuery`
+* `InverseBitmap(rowID uint64) *PQLBitmapQuery`
* `SetBit(rowID uint64, columnID uint64) *PQLBaseQuery`
* `SetBitTimestamp(rowID uint64, columnID uint64, timestamp time.Time) *PQLBaseQuery`
* `ClearBit(rowID uint64, columnID uint64) *PQLBaseQuery`
* `TopN(n uint64) *PQLBitmapQuery`
* `BitmapTopN(n uint64, bitmap *PQLBitmapQuery) *PQLBitmapQuery`
* `FilterFieldTopN(n uint64, bitmap *PQLBitmapQuery, field string, values ...interface{}) *PQLBitmapQuery`
+* `InverseTopN(n uint64) *PQLBitmapQuery`
+* `InverseBitmapTopN(n uint64, bitmap *PQLBitmapQuery) *PQLBitmapQuery`
+* `InverseFilterFieldTopN(n uint64, bitmap *PQLBitmapQuery, field string, values ...interface{}) *PQLBitmapQuery`
* `Range(rowID uint64, start time.Time, end time.Time) *PQLBitmapQuery`
+* `InverseRange(rowID uint64, start time.Time, end time.Time) *PQLBitmapQuery`
* `SetBitmapAttrs(rowID uint64, attrs map[string]interface{}) *PQLBaseQuery`
### Pilosa URI
diff --git a/client_it_test.go b/client_it_test.go
index e93034d..959cbee 100644
--- a/client_it_test.go
+++ b/client_it_test.go
@@ -1,4 +1,4 @@
-// +build integration
+ +build integration
// Copyright 2017 Pilosa Corp.
//
@@ -43,6 +43,7 @@ import (
"reflect"
"strconv"
"testing"
+ "time"
"github.com/golang/protobuf/proto"
"github.com/pilosa/go-pilosa/internal"
@@ -179,6 +180,67 @@ func TestSetRowAttrs(t *testing.T) {
}
}
+func TestOrmCount(t *testing.T) {
+ client := getClient()
+ countFrame, err := index.Frame("count-test", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = client.EnsureFrame(countFrame)
+ if err != nil {
+ t.Fatal(err)
+ }
+ qry := index.BatchQuery(
+ countFrame.SetBit(10, 20),
+ countFrame.SetBit(10, 21),
+ countFrame.SetBit(15, 25),
+ )
+ client.Query(qry, nil)
+ response, err := client.Query(index.Count(countFrame.Bitmap(10)), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if response.Result().Count != 2 {
+ t.Fatalf("Count should be 2")
+ }
+}
+
+func TestTopNReturns(t *testing.T) {
+ client := getClient()
+ frame, err := index.Frame("topn_test", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = client.EnsureFrame(frame)
+ if err != nil {
+ t.Fatal(err)
+ }
+ qry := index.BatchQuery(
+ frame.SetBit(10, 5),
+ frame.SetBit(10, 10),
+ frame.SetBit(10, 15),
+ frame.SetBit(20, 5),
+ frame.SetBit(30, 5),
+ )
+ client.Query(qry, nil)
+ time.Sleep(10 * time.Second)
+ response, err := client.Query(frame.TopN(2), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ items := response.Result().CountItems
+ if len(items) != 2 {
+ t.Fatalf("There should be 2 count items")
+ }
+ item := items[0]
+ if item.ID != 10 {
+ t.Fatalf("Item[0] ID should be 10")
+ }
+ if item.Count != 3 {
+ t.Fatalf("Item[0] Count should be 3")
+ }
+}
+
func TestCreateDeleteIndexFrame(t *testing.T) {
client := getClient()
index1, err := NewIndex("to-be-deleted", nil)
diff --git a/orm.go b/orm.go
index ef62ec7..68faadd 100644
--- a/orm.go
+++ b/orm.go
@@ -358,9 +358,6 @@ func (f *Frame) Bitmap(rowID uint64) *PQLBitmapQuery {
// Bitmap retrieves the indices of all the set bits in a row or column based on whether the row label or column label is given in the query.
// It also retrieves any attributes set on that row or column.
func (f *Frame) InverseBitmap(columnID uint64) *PQLBaseQuery {
- if !f.options.InverseEnabled {
- return NewPQLBaseQuery("", f.index, ErrorInverseBitmapsNotEnabled)
- }
return NewPQLBaseQuery(fmt.Sprintf("Bitmap(%s=%d, frame='%s')",
f.index.options.ColumnLabel, columnID, f.name), f.index, nil)
}
@@ -391,19 +388,44 @@ func (f *Frame) ClearBit(rowID uint64, columnID uint64) *PQLBaseQuery {
// TopN creates a TopN query with the given item count.
// Returns the id and count of the top n bitmaps (by count of bits) in the frame.
func (f *Frame) TopN(n uint64) *PQLBitmapQuery {
- return NewPQLBitmapQuery(fmt.Sprintf("TopN(frame='%s', n=%d)", f.name, n), f.index, nil)
+ return NewPQLBitmapQuery(fmt.Sprintf("TopN(frame='%s', n=%d, inverse=false)", f.name, n), f.index, nil)
+}
+
+// InverseTopN creates a TopN query with the given item count.
+// Returns the id and count of the top n bitmaps (by count of bits) in the frame.
+// This variant sets inverse=true
+func (f *Frame) InverseTopN(n uint64) *PQLBitmapQuery {
+ return NewPQLBitmapQuery(fmt.Sprintf("TopN(frame='%s', n=%d, inverse=true)", f.name, n), f.index, nil)
}
// BitmapTopN creates a TopN query with the given item count and bitmap.
-// This variant supports customizing the bitmap query.
+// This variant supports customizing the bitmap query and sets inverse=true.
func (f *Frame) BitmapTopN(n uint64, bitmap *PQLBitmapQuery) *PQLBitmapQuery {
- return NewPQLBitmapQuery(fmt.Sprintf("TopN(%s, frame='%s', n=%d)",
+ return NewPQLBitmapQuery(fmt.Sprintf("TopN(%s, frame='%s', n=%d, inverse=false)",
+ bitmap.serialize(), f.name, n), f.index, nil)
+}
+
+// InverseBitmapTopN creates a TopN query with the given item count and bitmap.
+// This variant supports customizing the bitmap query and sets inverse=true.
+func (f *Frame) InverseBitmapTopN(n uint64, bitmap *PQLBitmapQuery) *PQLBitmapQuery {
+ return NewPQLBitmapQuery(fmt.Sprintf("TopN(%s, frame='%s', n=%d, inverse=true)",
bitmap.serialize(), f.name, n), f.index, nil)
}
// FilterFieldTopN creates a TopN query with the given item count, bitmap, field and the filter for that field
// The field and filters arguments work together to only return Bitmaps which have the attribute specified by field with one of the values specified in filters.
func (f *Frame) FilterFieldTopN(n uint64, bitmap *PQLBitmapQuery, field string, values ...interface{}) *PQLBitmapQuery {
+ return f.filterFieldTopN(n, bitmap, false, field, values...)
+}
+
+// InverseFilterFieldTopN creates a TopN query with the given item count, bitmap, field and the filter for that field
+// The field and filters arguments work together to only return Bitmaps which have the attribute specified by field with one of the values specified in filters.
+// This variant sets inverse=true.
+func (f *Frame) InverseFilterFieldTopN(n uint64, bitmap *PQLBitmapQuery, field string, values ...interface{}) *PQLBitmapQuery {
+ return f.filterFieldTopN(n, bitmap, true, field, values...)
+}
+
+func (f *Frame) filterFieldTopN(n uint64, bitmap *PQLBitmapQuery, inverse bool, field string, values ...interface{}) *PQLBitmapQuery {
if err := validateLabel(field); err != nil {
return NewPQLBitmapQuery("", f.index, err)
}
@@ -411,14 +433,26 @@ func (f *Frame) FilterFieldTopN(n uint64, bitmap *PQLBitmapQuery, field string,
if err != nil {
return NewPQLBitmapQuery("", f.index, err)
}
- return NewPQLBitmapQuery(fmt.Sprintf("TopN(%s, frame='%s', n=%d, field='%s', %s)",
- bitmap.serialize(), f.name, n, field, string(b)), f.index, nil)
+ inverseStr := "true"
+ if !inverse {
+ inverseStr = "false"
+ }
+ return NewPQLBitmapQuery(fmt.Sprintf("TopN(%s, frame='%s', n=%d, inverse=%s, field='%s', %s)",
+ bitmap.serialize(), f.name, n, inverseStr, field, string(b)), f.index, nil)
}
// Range creates a Range query.
// Similar to Bitmap, but only returns bits which were set with timestamps between the given start and end timestamps.
func (f *Frame) Range(rowID uint64, start time.Time, end time.Time) *PQLBitmapQuery {
- return NewPQLBitmapQuery(fmt.Sprintf("Range(%s=%d, frame='%s', start='%s', end='%s')",
+ return NewPQLBitmapQuery(fmt.Sprintf("Range(%s=%d, frame='%s', start='%s', end='%s', inverse=false)",
+ f.options.RowLabel, rowID, f.name, start.Format(timeFormat), end.Format(timeFormat)), f.index, nil)
+}
+
+// InverseRange creates a Range query.
+// Similar to Bitmap, but only returns bits which were set with timestamps between the given start and end timestamps.
+// This variant sets inverse=true.
+func (f *Frame) InverseRange(rowID uint64, start time.Time, end time.Time) *PQLBitmapQuery {
+ return NewPQLBitmapQuery(fmt.Sprintf("Range(%s=%d, frame='%s', start='%s', end='%s', inverse=true)",
f.options.RowLabel, rowID, f.name, start.Format(timeFormat), end.Format(timeFormat)), f.index, nil)
}
diff --git a/orm_test.go b/orm_test.go
index 5189faf..ef33894 100644
--- a/orm_test.go
+++ b/orm_test.go
@@ -172,14 +172,23 @@ func TestDifference(t *testing.T) {
func TestTopN(t *testing.T) {
comparePQL(t,
- "TopN(frame='sample-frame', n=27)",
+ "TopN(frame='sample-frame', n=27, inverse=false)",
sampleFrame.TopN(27))
comparePQL(t,
- "TopN(Bitmap(project=3, frame='collaboration'), frame='sample-frame', n=10)",
+ "TopN(frame='sample-frame', n=27, inverse=true)",
+ sampleFrame.InverseTopN(27))
+ comparePQL(t,
+ "TopN(Bitmap(project=3, frame='collaboration'), frame='sample-frame', n=10, inverse=false)",
sampleFrame.BitmapTopN(10, collabFrame.Bitmap(3)))
comparePQL(t,
- "TopN(Bitmap(project=7, frame='collaboration'), frame='sample-frame', n=12, field='category', [80,81])",
+ "TopN(Bitmap(project=3, frame='collaboration'), frame='sample-frame', n=10, inverse=true)",
+ sampleFrame.InverseBitmapTopN(10, collabFrame.Bitmap(3)))
+ comparePQL(t,
+ "TopN(Bitmap(project=7, frame='collaboration'), frame='sample-frame', n=12, inverse=false, field='category', [80,81])",
sampleFrame.FilterFieldTopN(12, collabFrame.Bitmap(7), "category", 80, 81))
+ comparePQL(t,
+ "TopN(Bitmap(project=7, frame='collaboration'), frame='sample-frame', n=12, inverse=true, field='category', [80,81])",
+ sampleFrame.InverseFilterFieldTopN(12, collabFrame.Bitmap(7), "category", 80, 81))
}
func TestFilterFieldTopNInvalidField(t *testing.T) {
@@ -296,8 +305,11 @@ func TestRange(t *testing.T) {
start := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2000, time.February, 2, 3, 4, 0, 0, time.UTC)
comparePQL(t,
- "Range(project=10, frame='collaboration', start='1970-01-01T00:00', end='2000-02-02T03:04')",
+ "Range(project=10, frame='collaboration', start='1970-01-01T00:00', end='2000-02-02T03:04', inverse=false)",
collabFrame.Range(10, start, end))
+ comparePQL(t,
+ "Range(project=10, frame='collaboration', start='1970-01-01T00:00', end='2000-02-02T03:04', inverse=true)",
+ collabFrame.InverseRange(10, start, end))
}
func TestInvalidColumnLabelFails(t *testing.T) {
@@ -319,17 +331,6 @@ func TestInvalidRowLabelFails(t *testing.T) {
}
}
-func TestInverseBitmapFailsIfNotEnabled(t *testing.T) {
- frame, err := sampleIndex.Frame("inverse-not-enabled", nil)
- if err != nil {
- t.Fatal(err)
- }
- qry := frame.InverseBitmap(5)
- if qry.Error() == nil {
- t.Fatalf("Creating InverseBitmap query for a frame without inverse frame enabled should fail")
- }
-}
-
func TestFrameOptionsToString(t *testing.T) {
frameOptions := &FrameOptions{
RowLabel: "stargazer_id",