Merge branch 'master' into roaring-import

This commit is contained in:
Matt Jaffee 2018-09-13 15:50:16 -05:00
commit 495af54c10
No known key found for this signature in database
GPG key ID: 08A3DFFF987B11BF
25 changed files with 957 additions and 148 deletions

View file

@ -110,7 +110,7 @@ docker-test:
# Run gometalinter with custom flags
gometalinter: require-gometalinter
gometalinter --vendor --disable-all \
--deadline=120s \
--deadline=300s \
--enable=deadcode \
--enable=gochecknoinits \
--enable=gofmt \

30
api.go
View file

@ -110,8 +110,9 @@ func (api *API) Query(ctx context.Context, req *QueryRequest) (QueryResponse, er
}
execOpts := &execOptions{
Remote: req.Remote,
ExcludeRowAttrs: req.ExcludeRowAttrs,
ExcludeColumns: req.ExcludeColumns,
ExcludeRowAttrs: req.ExcludeRowAttrs, // NOTE: Kept for Pilosa 1.x compat.
ExcludeColumns: req.ExcludeColumns, // NOTE: Kept for Pilosa 1.x compat.
ColumnAttrs: req.ColumnAttrs, // NOTE: Kept for Pilosa 1.x compat.
}
results, err := api.server.executor.Execute(ctx, req.Index, q, req.Shards, execOpts)
if err != nil {
@ -120,7 +121,8 @@ func (api *API) Query(ctx context.Context, req *QueryRequest) (QueryResponse, er
resp.Results = results
// Fill column attributes if requested.
if req.ColumnAttrs && !req.ExcludeColumns {
// execOpts.ColumnAttrs may be set by the Execute method if any of the Calls use Options(columnAttrs=true)
if execOpts.ColumnAttrs {
// Consolidate all column ids across all calls.
var columnIDs []uint64
for _, result := range results {
@ -734,6 +736,12 @@ func (api *API) Import(_ context.Context, req *ImportRequest) error {
timestamps[i] = &t
}
// Import columnIDs into existence field.
if err := importExistenceColumns(index, req.ColumnIDs); err != nil {
api.server.logger.Printf("import existence error: index=%s, field=%s, shard=%d, columns=%d, err=%s", req.Index, req.Field, req.Shard, len(req.ColumnIDs), err)
return errors.Wrap(err, "importing existence columns")
}
// Import into fragment.
err = field.Import(req.RowIDs, req.ColumnIDs, timestamps)
if err != nil {
@ -768,6 +776,12 @@ func (api *API) ImportValue(_ context.Context, req *ImportValueRequest) error {
}
}
// Import columnIDs into existence field.
if err := importExistenceColumns(index, req.ColumnIDs); err != nil {
api.server.logger.Printf("import existence error: index=%s, field=%s, shard=%d, columns=%d, err=%s", req.Index, req.Field, req.Shard, len(req.ColumnIDs), err)
return errors.Wrap(err, "importing existence columns")
}
// Import into fragment.
err = field.importValue(req.ColumnIDs, req.Values)
if err != nil {
@ -776,6 +790,16 @@ func (api *API) ImportValue(_ context.Context, req *ImportValueRequest) error {
return errors.Wrap(err, "importing")
}
func importExistenceColumns(index *Index, columnIDs []uint64) error {
ef := index.existenceField()
if ef == nil {
return nil
}
existenceRowIDs := make([]uint64, len(columnIDs))
return ef.Import(existenceRowIDs, columnIDs, nil)
}
// MaxShards returns the maximum shard number for each index in a map.
func (api *API) MaxShards(_ context.Context) map[string]uint64 {
m := make(map[string]uint64)

View file

@ -18,6 +18,8 @@ import (
"context"
"io"
"github.com/pilosa/pilosa"
"github.com/spf13/cobra"
"github.com/pilosa/pilosa/ctl"
@ -51,14 +53,16 @@ omitted. If it is present then its format should be YYYY-MM-DDTHH:MM.
flags.StringVarP(&Importer.Host, "host", "", "localhost:10101", "host:port of Pilosa.")
flags.StringVarP(&Importer.Index, "index", "i", "", "Pilosa index to import into.")
flags.StringVarP(&Importer.Field, "field", "f", "", "Field to import into.")
flags.BoolVar(&Importer.IndexKeys, "index-keys", false, "use keys=true when creating an index")
flags.BoolVar(&Importer.FieldKeys, "field-keys", false, "use keys=true when creating a field")
flags.BoolVar(&Importer.IndexOptions.Keys, "index-keys", false, "Specify keys=true when creating an index")
flags.BoolVar(&Importer.FieldOptions.Keys, "field-keys", false, "Specify keys=true when creating a field")
flags.Int64Var(&Importer.FieldOptions.Min, "field-min", 0, "Specify the minimum for an int field on creation")
flags.Int64Var(&Importer.FieldOptions.Max, "field-max", 0, "Specify the maximum for an int field on creation")
flags.StringVar(&Importer.FieldOptions.CacheType, "field-cache-type", pilosa.CacheTypeRanked, "Specify the cache type for a set field on creation. One of: none, lru, ranked")
flags.Uint32Var(&Importer.FieldOptions.CacheSize, "field-cache-size", 50000, "Specify the cache size for a set field on creation")
flags.Var(&Importer.FieldOptions.TimeQuantum, "field-time-quantum", "Specify the time quantum for a time field on creation. One of: D, DH, H, M, MD, MDH, Y, YM, YMD, YMDH")
flags.IntVarP(&Importer.BufferSize, "buffer-size", "s", 10000000, "Number of bits to buffer/sort before importing.")
flags.BoolVarP(&Importer.Sort, "sort", "", false, "Enables sorting before import.")
flags.BoolVarP(&Importer.CreateSchema, "create", "e", false, "Create the schema if it does not exist before import.")
//flags.Var(&Importer.FieldOptions.TimeQuantum, "field-time-quantum", "Time quantum for the field")
//flags.StringVar(&Importer.FieldOptions.CacheType, "field-cache-type", pilosa.CacheTypeRanked, "Cache type for the field; valid values: none, lru, ranked")
//flags.Uint32Var(&Importer.FieldOptions.CacheSize, "field-cache-size", 50000, "Cache size for the field")
ctl.SetTLSConfig(flags, &Importer.TLS.CertificatePath, &Importer.TLS.CertificateKeyPath, &Importer.TLS.SkipVerify)
return importCmd

View file

@ -18,6 +18,8 @@ import (
"strings"
"testing"
"github.com/pilosa/pilosa"
"github.com/pilosa/pilosa/cmd"
)
@ -47,6 +49,52 @@ field = "f1"
return v.Error()
},
},
{
args: []string{"import", "--index", "i1", "--field", "f1", "--field-keys", "--field-min", "-10", "--field-max", "100"},
env: map[string]string{},
validation: func() error {
v := validator{}
v.Check(cmd.Importer.Index, "i1")
v.Check(cmd.Importer.Field, "f1")
v.Check(cmd.Importer.FieldOptions, pilosa.FieldOptions{
Keys: true,
Max: 100,
Min: -10,
CacheType: pilosa.CacheTypeRanked,
CacheSize: 50000,
})
return v.Error()
},
},
{
args: []string{"import", "--index", "i1", "--field", "f1", "--field-time-quantum", "YMD"},
env: map[string]string{},
validation: func() error {
v := validator{}
v.Check(cmd.Importer.Index, "i1")
v.Check(cmd.Importer.Field, "f1")
v.Check(cmd.Importer.FieldOptions, pilosa.FieldOptions{
TimeQuantum: "YMD",
CacheType: pilosa.CacheTypeRanked,
CacheSize: 50000,
})
return v.Error()
},
},
{
args: []string{"import", "--index", "i1", "--field", "f1", "--field-cache-type", "lru", "--field-cache-size", "100"},
env: map[string]string{},
validation: func() error {
v := validator{}
v.Check(cmd.Importer.Index, "i1")
v.Check(cmd.Importer.Field, "f1")
v.Check(cmd.Importer.FieldOptions, pilosa.FieldOptions{
CacheType: "lru",
CacheSize: 100,
})
return v.Error()
},
},
}
executeDry(t, tests)
}

View file

@ -41,20 +41,14 @@ type ImportCommand struct { // nolint: maligned
Field string `json:"field"`
// Options for the index to be created if it doesn't exist
indexOptions pilosa.IndexOptions
IndexOptions pilosa.IndexOptions
// Options for the field to be created if it doesn't exist
fieldOptions pilosa.FieldOptions
FieldOptions pilosa.FieldOptions
// CreateSchema ensures the schema exists before import
CreateSchema bool
// IndexKeys makes the import command use keys=true when creating an index
IndexKeys bool `json:"indexKeys"`
// FieldKeys makes the import command use keys=true when creating a field
FieldKeys bool `json:"fieldKeys"`
// Filenames to import from.
Paths []string `json:"paths"`
@ -102,11 +96,13 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
cmd.client = client
if cmd.CreateSchema {
cmd.indexOptions = pilosa.IndexOptions{
Keys: cmd.IndexKeys,
}
cmd.fieldOptions = pilosa.FieldOptions{
Keys: cmd.FieldKeys,
// set the correct type for the field
if cmd.FieldOptions.TimeQuantum != "" {
cmd.FieldOptions.Type = "time"
} else if cmd.FieldOptions.Min != 0 || cmd.FieldOptions.Max != 0 {
cmd.FieldOptions.Type = "int"
} else {
cmd.FieldOptions.Type = "set"
}
err := cmd.ensureSchema(ctx)
if err != nil {
@ -148,11 +144,11 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
}
func (cmd *ImportCommand) ensureSchema(ctx context.Context) error {
err := cmd.client.EnsureIndex(ctx, cmd.Index, cmd.indexOptions)
err := cmd.client.EnsureIndex(ctx, cmd.Index, cmd.IndexOptions)
if err != nil {
return fmt.Errorf("Error Creating Index: %s", err)
}
err = cmd.client.EnsureFieldWithOptions(ctx, cmd.Index, cmd.Field, cmd.fieldOptions)
err = cmd.client.EnsureFieldWithOptions(ctx, cmd.Index, cmd.Field, cmd.FieldOptions)
if err != nil {
return fmt.Errorf("Error Creating Field: %s", err)
}

View file

@ -294,6 +294,8 @@ We currently track the following events
- **Union:** Count of Union queries.
- **Intersection:** Count of Intersection queries.
- **Difference:** Count of Difference queries.
- **Xor:** Count of Xor queries.
- **Not:** Count of Not queries.
- **Count:** Count of Count queries.
- **Range:** Count of Range queries.
- **Snapshot:** Event count when the snapshot process is triggered.

View file

@ -46,9 +46,11 @@ curl localhost:10101/index/repository/query \
* `field` The field specifies on which Pilosa [field](../glossary/#field) the query will operate. Valid field names are lower case strings; they start with an alphanumeric character, and contain only alphanumeric characters and `_-`. They must be 64 characters or less in length.
* `TIMESTAMP` This is a timestamp in the following format `YYYY-MM-DDTHH:MM` (e.g. 2006-01-02T15:04)
* `UINT` An unsigned integer (e.g. 42839)
* `BOOL` A boolean value, `true` or `false`
* `ATTR_NAME` Must be a valid identifier `[A-Za-z][A-Za-z0-9._-]*`
* `ATTR_VALUE` Can be a string, float, integer, or bool.
* `ROW_CALL` Any query which returns a row, such as `Row`, `Union`, `Difference`, `Xor`, `Intersect`, `Range`
* `CALL` Any query
* `ROW_CALL` Any query which returns a row, such as `Row`, `Union`, `Difference`, `Xor`, `Intersect`, `Range`, `Not`
* `[]ATTR_VALUE` Denotes an array of `ATTR_VALUE`s. (e.g. `["a", "b", "c"]`)
### Write Operations
@ -94,7 +96,7 @@ Set(10, stargazer=1, 2016-01-01T00:00)
Set multiple bits in a single request:
```request
Set(1, stargazer=10) Set(2, stargazer=10) Set(1, stargazer=20) Set(2, stargazer=30)
Set(10, stargazer=1) Set(20, stargazer=1) Set(10, stargazer=2) Set(30, stargazer=2)
```
```response
{"results":[false,true,true,true]}
@ -112,8 +114,8 @@ Set(10, pullrequests=2)
**Spec:**
```
SetRowAttrs(<FIELD>, <ROW>,
<ATTR_NAME=ATTR_VALUE>,
SetRowAttrs(<FIELD>, <ROW>,
<ATTR_NAME=ATTR_VALUE>,
[ATTR_NAME=ATTR_VALUE ...])
```
@ -150,8 +152,8 @@ SetRowAttrs(stargazer, 10, username=null)
**Spec:**
```
SetColumnAttrs(<COLUMN>,
<ATTR_NAME=ATTR_VALUE>,
SetColumnAttrs(<COLUMN>,
<ATTR_NAME=ATTR_VALUE>,
[ATTR_NAME=ATTR_VALUE ...])
```
@ -262,7 +264,7 @@ Row(stargazer=1)
{"attrs":{"username":"mrpi","active":true},"columns":[10, 20]}
```
* attrs are the attributes for user 1
* attrs are the attributes for user 1
* columns are the repositories which user 1 has starred.
#### Union
@ -358,7 +360,7 @@ Difference(Row(stargazer=2), Row(stargazer=1))
{"attrs":{},"columns":[30]}
```
* columnss are repositories that were starred by user 2 BUT NOT user 1
* columns are repositories that were starred by user 2 BUT NOT user 1
#### Xor
@ -384,11 +386,39 @@ Query columns with a bit set in exactly one of two rows (repositories that are s
Xor(Row(stargazer=2), Row(stargazer=1))
```
```response
{"results":[{"attrs":{},"columns":[10,20,30]}]}
{"results":[{"attrs":{},"columns":[20,30]}]}
```
* columns are repositories that were starred by user 1 XOR user 2 (user 1 or user 2, but not both)
#### Not
**Spec:**
```
Not(<ROW_CALL>)
```
**Description:**
Not returns the inverse of all of the bits from the `ROW_CALL` argument. The Not query requires that `trackExistence` has been enabled on the Index.
**Result Type:** object with attrs and columns
attrs will always be empty
**Examples:**
Query existing columns that do not have a bit set in the given row.
```request
Not(Row(stargazer=1))
```
```response
{"results":[{"attrs":{},"columns":[30]}]}
```
* columns are repositories that were not starred by user 1
#### Count
**Spec:**
@ -497,7 +527,7 @@ Range(<FIELD>=<ROW>, <TIMESTAMP>, <TIMESTAMP>)
**Description:**
Similar to `Row`, but only returns bits which were set with timestamps
between the given `start` (first) and `end` (second) timestamps.
between the given `start` (first) and `end` (second) timestamps.
**Result Type:** object with attrs and bits
@ -548,14 +578,14 @@ Range(commitactivity > 100)
BSI range queries support the following operators:
Operator | Name | Value
Operator | Name | Value
----------|-------------------------------|--------------------
`>` | greater-than, GT | integer
`<` | less-than, LT | integer
`<=` | less-than-or-equal-to, LTE | integer
`>=` | greater-than-or-equal-to, GTE | integer
`==` | equal-to, EQ | integer
`!=` | not-equal-to, NEQ | integer or `null`
`>` | greater-than, GT | integer
`<` | less-than, LT | integer
`<=` | less-than-or-equal-to, LTE | integer
`>=` | greater-than-or-equal-to, GTE | integer
`==` | equal-to, EQ | integer
`!=` | not-equal-to, NEQ | integer or `null`
`<`, and `<=` can be chained together to represent a bounded interval. For example:
@ -645,3 +675,41 @@ Sum(field="diskusage")
```
* Result is the sum of all values (total size of all repositories in kilobytes, here), plus the count of columns.
### Other Operations
#### Options
**Spec:**
```
Options(<CALL>, columnAttrs=<BOOL>, excludeColumns=<BOOL>, excludeRowAttrs=<BOOL>, shards=[UINT ...])
```
**Description:**
Modifies the given query as follows:
* `columnAttrs`: Include column attributes in the result (Default: `false`).
* `excludeColumns`: Exclude column IDs from the result (Default: `false`).
* `excludeRowAttrs`: Exclude row attributes from the result (Default: `false`).
* `shards`: Run the query using only the data from the given shards. By default, the entire data set (i.e. data from all shards) is used.
**Result Type:** Same result type as <CALL>.
**Examples:**
Return column attributes:
```request
Options(Row(f1=10), columnAttrs=true)
```
```response
{"attrs":{},"columns":[100]}],"columnAttrs":[{"id":100,"attrs":{"foo":"bar"}}
```
Run the query against shards 0 and 2 only:
```request
Options(Row(f1=10), shards=[0, 2])
```
```response
{"attrs":{},"columns":[100, 2097152]}
```

View file

@ -509,7 +509,8 @@ func encodeCreateIndexMessage(m *pilosa.CreateIndexMessage) *internal.CreateInde
func encodeIndexMeta(m *pilosa.IndexOptions) *internal.IndexMeta {
return &internal.IndexMeta{
Keys: m.Keys,
Keys: m.Keys,
TrackExistence: m.TrackExistence,
}
}
@ -741,6 +742,7 @@ func decodeCreateIndexMessage(pb *internal.CreateIndexMessage, m *pilosa.CreateI
func decodeIndexMeta(pb *internal.IndexMeta, m *pilosa.IndexOptions) {
m.Keys = pb.Keys
m.TrackExistence = pb.TrackExistence
}
func decodeDeleteIndexMessage(pb *internal.DeleteIndexMessage, m *pilosa.DeleteIndexMessage) {

View file

@ -194,6 +194,8 @@ func (e *executor) executeCall(ctx context.Context, index string, c *pql.Call, s
case "TopN":
e.Holder.Stats.CountWithCustomTags(c.Name, 1, 1.0, []string{indexTag})
return e.executeTopN(ctx, index, c, shards, opt)
case "Options":
return e.executeOptionsCall(ctx, index, c, shards, opt)
default:
e.Holder.Stats.CountWithCustomTags(c.Name, 1, 1.0, []string{indexTag})
return e.executeBitmapCall(ctx, index, c, shards, opt)
@ -219,6 +221,48 @@ func (e *executor) validateCallArgs(c *pql.Call) error {
return nil
}
func (e *executor) executeOptionsCall(ctx context.Context, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
optCopy := &execOptions{}
*optCopy = *opt
if arg, ok := c.Args["columnAttrs"]; ok {
if value, ok := arg.(bool); ok {
opt.ColumnAttrs = value
} else {
return nil, errors.New("Query(): columnAttrs must be a bool")
}
}
if arg, ok := c.Args["excludeRowAttrs"]; ok {
if value, ok := arg.(bool); ok {
optCopy.ExcludeRowAttrs = value
} else {
return nil, errors.New("Query(): excludeRowAttrs must be a bool")
}
}
if arg, ok := c.Args["excludeColumns"]; ok {
if value, ok := arg.(bool); ok {
optCopy.ExcludeColumns = value
} else {
return nil, errors.New("Query(): excludeColumns must be a bool")
}
}
if arg, ok := c.Args["shards"]; ok {
if optShards, ok := arg.([]interface{}); ok {
shards = []uint64{}
for _, s := range optShards {
if shard, ok := s.(int64); ok {
shards = append(shards, uint64(shard))
} else {
return nil, errors.New("Query(): shards must be a list of unsigned integers")
}
}
} else {
return nil, errors.New("Query(): shards must be a list of unsigned integers")
}
}
return e.executeCall(ctx, index, c.Children[0], shards, optCopy)
}
// executeSum executes a Sum() call.
func (e *executor) executeSum(ctx context.Context, index string, c *pql.Call, shards []uint64, opt *execOptions) (ValCount, error) {
if field := c.Args["field"]; field == "" {
@ -399,6 +443,8 @@ func (e *executor) executeBitmapCallShard(ctx context.Context, index string, c *
return e.executeUnionShard(ctx, index, c, shard)
case "Xor":
return e.executeXorShard(ctx, index, c, shard)
case "Not":
return e.executeNotShard(ctx, index, c, shard)
default:
return nil, fmt.Errorf("unknown call: %s", c.Name)
}
@ -963,6 +1009,38 @@ func (e *executor) executeXorShard(ctx context.Context, index string, c *pql.Cal
return other, nil
}
// executeNotShard executes a not() call for a local shard.
func (e *executor) executeNotShard(ctx context.Context, index string, c *pql.Call, shard uint64) (*Row, error) {
if len(c.Children) == 0 {
return nil, errors.New("Not() requires an input row")
} else if len(c.Children) > 1 {
return nil, errors.New("Not() only accepts a single row input")
}
// Make sure the index supports existence tracking.
idx := e.Holder.Index(index)
if idx == nil {
return nil, ErrIndexNotFound
} else if idx.existenceField() == nil {
return nil, errors.Errorf("index does not support existence tracking: %s", index)
}
var existenceRow *Row
existenceFrag := e.Holder.fragment(index, existenceFieldName, viewStandard, shard)
if existenceFrag == nil {
existenceRow = NewRow()
} else {
existenceRow = existenceFrag.row(0)
}
row, err := e.executeBitmapCallShard(ctx, index, c.Children[0], shard)
if err != nil {
return nil, err
}
return existenceRow.Difference(row), nil
}
// executeCount executes a count() call.
func (e *executor) executeCount(ctx context.Context, index string, c *pql.Call, shards []uint64, opt *execOptions) (uint64, error) {
if len(c.Children) == 0 {
@ -1087,6 +1165,13 @@ func (e *executor) executeSet(ctx context.Context, index string, c *pql.Call, op
return false, ErrFieldNotFound
}
// Set column on existence field.
if ef := idx.existenceField(); ef != nil {
if _, err := ef.SetBit(0, colID, nil); err != nil {
return false, errors.Wrap(err, "setting existence column")
}
}
if f.Type() == FieldTypeInt {
// Read remaining fields using labels.
rowVal, ok, err := c.IntArg(fieldName)
@ -1677,6 +1762,7 @@ type execOptions struct {
Remote bool
ExcludeRowAttrs bool
ExcludeColumns bool
ColumnAttrs bool
}
// hasOnlySetRowAttrs returns true if calls only contains SetRowAttrs() calls.

View file

@ -17,6 +17,7 @@ package pilosa_test
import (
"context"
"fmt"
"math/rand"
"reflect"
"strconv"
"strings"
@ -32,8 +33,8 @@ import (
"github.com/pkg/errors"
)
// Ensure a bitmap query can be executed.
func TestExecutor_Execute_Bitmap(t *testing.T) {
// Ensure a row query can be executed.
func TestExecutor_Execute_Row(t *testing.T) {
t.Run("Row", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
@ -282,7 +283,7 @@ func TestExecutor_Execute_SetBit(t *testing.T) {
t.Run("OK", func(t *testing.T) {
hldr.ClearBit("i", "f", 11, 1)
if n := hldr.Row("i", "f", 11).Count(); n != 0 {
t.Fatalf("unexpected bitmap count: %d", n)
t.Fatalf("unexpected row count: %d", n)
}
if res, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(1, f=11)`}); err != nil {
@ -294,7 +295,7 @@ func TestExecutor_Execute_SetBit(t *testing.T) {
}
if n := hldr.Row("i", "f", 11).Count(); n != 1 {
t.Fatalf("unexpected bitmap count: %d", n)
t.Fatalf("unexpected row count: %d", n)
}
if res, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(1, f=11)`}); err != nil {
t.Fatal(err)
@ -327,7 +328,7 @@ func TestExecutor_Execute_SetBit(t *testing.T) {
t.Run("OK", func(t *testing.T) {
hldr.SetBit("i", "f", 1, 0)
if n := hldr.Row("i", "f", 11).Count(); n != 0 {
t.Fatalf("unexpected bitmap count: %d", n)
t.Fatalf("unexpected row count: %d", n)
}
if res, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set("foo", f=11)`}); err != nil {
@ -339,7 +340,7 @@ func TestExecutor_Execute_SetBit(t *testing.T) {
}
if n := hldr.Row("i", "f", 11).Count(); n != 1 {
t.Fatalf("unexpected bitmap count: %d", n)
t.Fatalf("unexpected row count: %d", n)
}
if res, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set("foo", f=11)`}); err != nil {
t.Fatal(err)
@ -476,7 +477,7 @@ func TestExecutor_Execute_SetRowAttrs(t *testing.T) {
}
t.Run("rowID", func(t *testing.T) {
// Set two attrs on f/10.
// Also set attrs on other bitmaps and fields to test isolation.
// Also set attrs on other rows and fields to test isolation.
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `SetRowAttrs(f, 10, foo="bar")`}); err != nil {
t.Fatal(err)
}
@ -494,13 +495,13 @@ func TestExecutor_Execute_SetRowAttrs(t *testing.T) {
if m, err := f.RowAttrStore().Attrs(10); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(m, map[string]interface{}{"foo": "bar", "baz": int64(123), "bat": true}) {
t.Fatalf("unexpected bitmap attr: %#v", m)
t.Fatalf("unexpected row attr: %#v", m)
}
})
t.Run("rowKey", func(t *testing.T) {
// Set two attrs on f/10.
// Also set attrs on other bitmaps and fields to test isolation.
// Also set attrs on other rows and fields to test isolation.
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `SetRowAttrs(kf, "row10", foo="bar")`}); err != nil {
t.Fatal(err)
}
@ -663,7 +664,7 @@ func TestExecutor_Execute_TopN_fill_small(t *testing.T) {
}
}
// Ensure a TopN() query with a source bitmap can be executed.
// Ensure a TopN() query with a source row can be executed.
func TestExecutor_Execute_TopN_Src(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
@ -723,7 +724,7 @@ func TestExecutor_Execute_TopN_Attr(t *testing.T) {
}
//Ensure TopN handles Attribute filters with source bitmap
//Ensure TopN handles Attribute filters with source row
func TestExecutor_Execute_TopN_Attr_Src(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
@ -1352,3 +1353,272 @@ func TestExecutor_Time_Clear_Quantums(t *testing.T) {
}
}
func TestExecutor_QueryCall(t *testing.T) {
t.Run("excludeRowAttrs", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
// Set columns for rows 0, 10, & 20 across two shards.
if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(100, f=10)
SetRowAttrs(f, 10, foo="bar")
`}); err != nil {
t.Fatal(err)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Options(Row(f=10), excludeRowAttrs=true)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{100}) {
t.Fatalf("unexpected columns: %+v", bits)
} else if attrs := res.Results[0].(*pilosa.Row).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{}) {
t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs))
}
})
t.Run("excludeColumns", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
// Set columns for rows 0, 10, & 20 across two shards.
if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(100, f=10)
SetRowAttrs(f, 10, foo="bar")
`}); err != nil {
t.Fatal(err)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Options(Row(f=10), excludeColumns=true)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{}) {
t.Fatalf("unexpected columns: %+v", bits)
} else if attrs := res.Results[0].(*pilosa.Row).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{"foo": "bar"}) {
t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs))
}
})
t.Run("columnAttrs", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
// Set columns for rows 0, 10, & 20 across two shards.
if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(100, f=10)
SetColumnAttrs(100, foo="bar")
`}); err != nil {
t.Fatal(err)
}
targetColAttrSets := []*pilosa.ColumnAttrSet{
{ID: 100, Attrs: map[string]interface{}{"foo": "bar"}},
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Options(Row(f=10), columnAttrs=true)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{100}) {
t.Fatalf("unexpected columns: %+v", bits)
} else if attrs := res.ColumnAttrSets; !reflect.DeepEqual(attrs, targetColAttrSets) {
t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs))
}
})
t.Run("shards", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
// Set columns for rows 0, 10, & 20 across two shards.
if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: fmt.Sprintf(`
Set(100, f=10)
Set(%d, f=10)
Set(%d, f=10)
`, ShardWidth, ShardWidth*2)}); err != nil {
t.Fatal(err)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Options(Row(f=10), shards=[0, 2])`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{100, ShardWidth * 2}) {
t.Fatalf("unexpected columns: %+v", bits)
}
})
t.Run("multipleOpt", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
// Set columns for rows 0, 10, & 20 across two shards.
if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(100, f=10)
SetRowAttrs(f, 10, foo="bar")
`}); err != nil {
t.Fatal(err)
}
req := &pilosa.QueryRequest{
Index: "i",
Query: `Options(Row(f=10), excludeColumns=true)Options(Row(f=10), excludeRowAttrs=true)`,
}
if res, err := c[0].API.Query(context.Background(), req); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{}) {
t.Fatalf("unexpected columns: %+v", bits)
} else if attrs := res.Results[0].(*pilosa.Row).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{"foo": "bar"}) {
t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs))
} else if bits := res.Results[1].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{100}) {
t.Fatalf("unexpected columns: %+v", bits)
} else if attrs := res.Results[1].(*pilosa.Row).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{}) {
t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs))
}
})
}
// Ensure an existence field is maintained.
func TestExecutor_Execute_Existence(t *testing.T) {
t.Run("Row", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{TrackExistence: true})
_, err := index.CreateField("f", pilosa.OptFieldTypeDefault())
if err != nil {
t.Fatal(err)
}
// Set bits.
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `` +
fmt.Sprintf("Set(%d, f=%d)\n", 3, 10) +
fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+1, 10) +
fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+2, 20),
}); err != nil {
t.Fatal(err)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Row(f=10)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{3, ShardWidth + 1}) {
t.Fatalf("unexpected columns: %+v", bits)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Row(exists=0)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{3, ShardWidth + 1, ShardWidth + 2}) {
t.Fatalf("unexpected existence columns: %+v", bits)
}
// Reopen cluster to ensure existence field is reloaded.
if err := c[0].Reopen(); err != nil {
t.Fatal(err)
}
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Row(exists=0)`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{3, ShardWidth + 1, ShardWidth + 2}) {
t.Fatalf("unexpected existence columns after reopen: %+v", bits)
}
})
}
// Ensure a not query can be executed.
func TestExecutor_Execute_Not(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{TrackExistence: true})
_, err := index.CreateField("f", pilosa.OptFieldTypeDefault())
if err != nil {
t.Fatal(err)
}
// Set bits.
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `` +
fmt.Sprintf("Set(%d, f=%d)\n", 3, 10) +
fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+1, 10) +
fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+2, 20),
}); err != nil {
t.Fatal(err)
}
// Populated row.
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Not(Row(f=20))`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{3, ShardWidth + 1}) {
t.Fatalf("unexpected columns: %+v", bits)
}
// Populated row.
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Not(Row(f=0))`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{3, ShardWidth + 1, ShardWidth + 2}) {
t.Fatalf("unexpected columns: %+v", bits)
}
// All existing.
if res, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Not(Union(Row(f=10), Row(f=20)))`}); err != nil {
t.Fatal(err)
} else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{}) {
t.Fatalf("unexpected columns: %+v", bits)
}
}
func benchmarkExistence(nn bool, b *testing.B) {
c := test.MustRunCluster(b, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
indexName := "i"
fieldName := "f"
index := hldr.MustCreateIndexIfNotExists(indexName, pilosa.IndexOptions{TrackExistence: nn})
// Create field.
if _, err := index.CreateFieldIfNotExists(fieldName); err != nil {
b.Fatal(err)
}
bitCount := 10000
req := &pilosa.ImportRequest{
Index: indexName,
Field: fieldName,
Shard: 0,
RowIDs: make([]uint64, bitCount),
ColumnIDs: make([]uint64, bitCount),
}
for i := 0; i < bitCount; i++ {
req.RowIDs[i] = uint64(rand.Intn(100000))
req.ColumnIDs[i] = uint64(rand.Intn(1 << 20))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := c[0].API.Import(context.Background(), req); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkExecutor_Existence_True(b *testing.B) { benchmarkExistence(true, b) }
func BenchmarkExecutor_Existence_False(b *testing.B) { benchmarkExistence(false, b) }

View file

@ -159,12 +159,17 @@ func OptFieldTypeMutex(cacheType string, cacheSize uint32) FieldOption {
func NewField(path, index, name string, opts FieldOption) (*Field, error) {
err := validateName(name)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "validating name")
}
return newField(path, index, name, opts)
}
// newField returns a new instance of field (without name validation).
func newField(path, index, name string, opts FieldOption) (*Field, error) {
// Apply functional option.
fo := FieldOptions{}
err = opts(&fo)
err := opts(&fo)
if err != nil {
return nil, errors.Wrap(err, "applying option")
}

View file

@ -749,7 +749,7 @@ func TestFragment_TopN_CacheSize(t *testing.T) {
cacheSize := uint32(3)
// Create Index.
index := mustOpenIndex()
index := mustOpenIndex(IndexOptions{})
defer index.Close()
// Create field.
@ -915,7 +915,7 @@ func TestFragment_LRUCache_Persistence(t *testing.T) {
// Ensure a fragment's cache can be persisted between restarts.
func TestFragment_RankCache_Persistence(t *testing.T) {
index := mustOpenIndex()
index := mustOpenIndex(IndexOptions{})
defer index.Close()
// Create field.

View file

@ -38,6 +38,9 @@ const (
// fileLimit is the maximum open file limit (ulimit -n) to automatically set.
fileLimit = 262144 // (512^2)
// existenceFieldName is the name of the internal field used to store existence values.
existenceFieldName = "exists"
)
// Holder represents a container for indexes.
@ -354,6 +357,7 @@ func (h *Holder) createIndex(name string, opt IndexOptions) (*Index, error) {
}
index.keys = opt.Keys
index.trackExistence = opt.TrackExistence
if err := index.Open(); err != nil {
return nil, errors.Wrap(err, "opening")

View file

@ -197,7 +197,7 @@ func TestClient_Export(t *testing.T) {
{RowID: 2, ColumnID: 203, RowKey: "row2", ColumnKey: "col203"},
}
t.Run("Import unkeyed,unkeyedf", func(t *testing.T) {
t.Run("Export unkeyed,unkeyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "unkeyed", &pilosa.QueryRequest{
@ -231,7 +231,7 @@ func TestClient_Export(t *testing.T) {
}
})
t.Run("Import unkeyed,keyedf", func(t *testing.T) {
t.Run("Export unkeyed,keyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "unkeyed", &pilosa.QueryRequest{
@ -265,7 +265,7 @@ func TestClient_Export(t *testing.T) {
}
})
t.Run("Import keyed,unkeyedf", func(t *testing.T) {
t.Run("Export keyed,unkeyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "keyed", &pilosa.QueryRequest{
@ -299,7 +299,7 @@ func TestClient_Export(t *testing.T) {
}
})
t.Run("Import keyed,keyedf", func(t *testing.T) {
t.Run("Export keyed,keyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "keyed", &pilosa.QueryRequest{
@ -708,6 +708,83 @@ func TestClient_ImportValue(t *testing.T) {
}
}
// Ensure client can bulk import data while tracking existence.
func TestClient_ImportExistence(t *testing.T) {
cmd := test.MustRunCluster(t, 1)[0]
host := cmd.URL()
holder := cmd.Server.Holder()
hldr := test.Holder{Holder: holder}
t.Run("Set", func(t *testing.T) {
idxName := "iset"
fldName := "fset"
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{TrackExistence: true})
_, err := index.CreateFieldIfNotExists(fldName)
if err != nil {
t.Fatal(err)
}
// Send import request.
c := MustNewClient(host, http.GetHTTPClient(nil))
if err := c.Import(context.Background(), idxName, fldName, 0, []pilosa.Bit{
{RowID: 0, ColumnID: 1},
{RowID: 0, ColumnID: 5},
{RowID: 200, ColumnID: 6},
}); err != nil {
t.Fatal(err)
}
// Verify data.
if a := hldr.Row(idxName, fldName, 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 5}) {
t.Fatalf("unexpected columns: %+v", a)
}
if a := hldr.Row(idxName, fldName, 200).Columns(); !reflect.DeepEqual(a, []uint64{6}) {
t.Fatalf("unexpected columns: %+v", a)
}
// Verify existence.
if a := hldr.ReadRow(idxName, "exists", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 5, 6}) {
t.Fatalf("unexpected existence columns: %+v", a)
}
})
t.Run("Int", func(t *testing.T) {
idxName := "iint"
fldName := "fint"
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{TrackExistence: true})
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
}
// Send import request.
c := MustNewClient(host, http.GetHTTPClient(nil))
if err := c.ImportValue(context.Background(), idxName, fldName, 0, []pilosa.FieldValue{
{ColumnID: 1, Value: -10},
{ColumnID: 2, Value: 20},
{ColumnID: 3, Value: 40},
}); err != nil {
t.Fatal(err)
}
// Verify Sum.
sum, cnt, err := field.Sum(nil, fldName)
if err != nil {
t.Fatal(err)
}
if sum != 50 || cnt != 3 {
t.Fatalf("unexpected values: got sum=%v, count=%v; expected sum=50, cnt=3", sum, cnt)
}
// Verify existence.
if a := hldr.ReadRow(idxName, "exists", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3}) {
t.Fatalf("unexpected existence columns: %+v", a)
}
})
}
// Ensure client can retrieve a list of all checksums for blocks in a fragment.
func TestClient_FragmentBlocks(t *testing.T) {
cmd := test.MustRunCluster(t, 1)[0]

View file

@ -36,6 +36,10 @@ type Index struct {
name string
keys bool // use string keys
// Existence tracking.
trackExistence bool
existenceFld *Field
// Fields by name.
fields map[string]*Field
@ -91,7 +95,10 @@ func (i *Index) Options() IndexOptions {
}
func (i *Index) options() IndexOptions {
return IndexOptions{Keys: i.keys}
return IndexOptions{
Keys: i.keys,
TrackExistence: i.trackExistence,
}
}
// Open opens and initializes the index.
@ -110,6 +117,12 @@ func (i *Index) Open() error {
return errors.Wrap(err, "opening fields")
}
if i.trackExistence {
if err := i.openExistenceField(); err != nil {
return errors.Wrap(err, "opening existence field")
}
}
if err := i.columnAttrs.Open(); err != nil {
return errors.Wrap(err, "opening attrstore")
}
@ -147,6 +160,16 @@ func (i *Index) openFields() error {
return nil
}
// openExistenceField gets or creates the existence field and associates it to the index.
func (i *Index) openExistenceField() error {
f, err := i.createFieldIfNotExists(existenceFieldName, FieldOptions{CacheType: CacheTypeNone, CacheSize: 0})
if err != nil {
return errors.Wrap(err, "creating existence field")
}
i.existenceFld = f
return nil
}
// loadMeta reads meta data for the index, if any.
func (i *Index) loadMeta() error {
var pb internal.IndexMeta
@ -165,6 +188,7 @@ func (i *Index) loadMeta() error {
// Copy metadata fields.
i.keys = pb.Keys
i.trackExistence = pb.TrackExistence
return nil
}
@ -173,7 +197,8 @@ func (i *Index) loadMeta() error {
func (i *Index) saveMeta() error {
// Marshal metadata.
buf, err := proto.Marshal(&internal.IndexMeta{
Keys: i.keys,
Keys: i.keys,
TrackExistence: i.trackExistence,
})
if err != nil {
return errors.Wrap(err, "marshalling")
@ -250,6 +275,14 @@ func (i *Index) Fields() []*Field {
return a
}
// existenceField returns the internal field used to track column existence.
func (i *Index) existenceField() *Field {
i.mu.RLock()
defer i.mu.RUnlock()
return i.existenceFld
}
// recalculateCaches recalculates caches on every field in the index.
func (i *Index) recalculateCaches() {
for _, field := range i.Fields() {
@ -259,6 +292,11 @@ func (i *Index) recalculateCaches() {
// CreateField creates a field.
func (i *Index) CreateField(name string, opts ...FieldOption) (*Field, error) {
err := validateName(name)
if err != nil {
return nil, errors.Wrap(err, "validating name")
}
i.mu.Lock()
defer i.mu.Unlock()
@ -281,6 +319,11 @@ func (i *Index) CreateField(name string, opts ...FieldOption) (*Field, error) {
// CreateFieldIfNotExists creates a field with the given options if it doesn't exist.
func (i *Index) CreateFieldIfNotExists(name string, opts ...FieldOption) (*Field, error) {
err := validateName(name)
if err != nil {
return nil, errors.Wrap(err, "validating name")
}
i.mu.Lock()
defer i.mu.Unlock()
@ -349,7 +392,7 @@ func (i *Index) createField(name string, opt FieldOptions) (*Field, error) {
}
func (i *Index) newField(path, name string) (*Field, error) {
f, err := NewField(path, i.name, name, OptFieldTypeDefault())
f, err := newField(path, i.name, name, OptFieldTypeDefault())
if err != nil {
return nil, err
}
@ -381,6 +424,18 @@ func (i *Index) DeleteField(name string) error {
return errors.Wrap(err, "removing directory")
}
// If the field being deleted is the existence field,
// turn off existence tracking on the index.
if name == existenceFieldName {
i.trackExistence = false
i.existenceFld = nil
// Update meta data on disk.
if err := i.saveMeta(); err != nil {
return errors.Wrap(err, "saving existence meta data")
}
}
// Remove reference.
delete(i.fields, name)
@ -408,7 +463,8 @@ func (p indexInfoSlice) Less(i, j int) bool { return p[i].Name < p[j].Name }
// IndexOptions represents options to set when initializing an index.
type IndexOptions struct {
Keys bool `json:"keys"`
Keys bool `json:"keys"`
TrackExistence bool `json:"trackExistence"`
}
// hasTime returns true if a contains a non-nil time.

View file

@ -16,10 +16,11 @@ package pilosa
import (
"io/ioutil"
"testing"
)
// mustOpenIndex returns a new, opened index at a temporary path. Panic on error.
func mustOpenIndex() *Index {
func mustOpenIndex(opt IndexOptions) *Index {
path, err := ioutil.TempDir("", "pilosa-index-")
if err != nil {
panic(err)
@ -28,6 +29,10 @@ func mustOpenIndex() *Index {
if err != nil {
panic(err)
}
index.keys = opt.Keys
index.trackExistence = opt.TrackExistence
if err := index.Open(); err != nil {
panic(err)
}
@ -44,3 +49,40 @@ func (i *Index) reopen() error {
}
return nil
}
// Ensure that deleting the existence field is handled properly.
func TestIndex_Existence_Delete(t *testing.T) {
// Create Index (with existence tracking).
index := mustOpenIndex(IndexOptions{TrackExistence: true})
defer index.Close()
// Ensure existence field has been created.
ef := index.Field(existenceFieldName)
if ef == nil {
t.Fatalf("expected field to have been created: %s", existenceFieldName)
} else if !index.trackExistence {
t.Fatalf("expected index.trackExistence to be true")
} else if index.existenceFld == nil {
t.Fatalf("expected index.existenceField to be non-nil")
}
// Delete existence field.
if err := index.DeleteField(existenceFieldName); err != nil {
t.Fatal(err)
}
// Re-open index.
if err := index.reopen(); err != nil {
t.Fatal(err)
}
// Ensure existence field no longer exists.
ef = index.Field(existenceFieldName)
if ef != nil {
t.Fatalf("expected field to have been deleted: %s", existenceFieldName)
} else if index.trackExistence {
t.Fatalf("expected index.trackExistence to be false")
} else if index.existenceFld != nil {
t.Fatalf("expected index.existenceField to be nil")
}
}

View file

@ -62,7 +62,8 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type IndexMeta struct {
Keys bool `protobuf:"varint,3,opt,name=Keys,proto3" json:"Keys,omitempty"`
Keys bool `protobuf:"varint,3,opt,name=Keys,proto3" json:"Keys,omitempty"`
TrackExistence bool `protobuf:"varint,4,opt,name=TrackExistence,proto3" json:"TrackExistence,omitempty"`
}
func (m *IndexMeta) Reset() { *m = IndexMeta{} }
@ -77,6 +78,13 @@ func (m *IndexMeta) GetKeys() bool {
return false
}
func (m *IndexMeta) GetTrackExistence() bool {
if m != nil {
return m.TrackExistence
}
return false
}
type FieldOptions struct {
Type string `protobuf:"bytes,8,opt,name=Type,proto3" json:"Type,omitempty"`
CacheType string `protobuf:"bytes,3,opt,name=CacheType,proto3" json:"CacheType,omitempty"`
@ -1051,6 +1059,16 @@ func (m *IndexMeta) MarshalTo(dAtA []byte) (int, error) {
}
i++
}
if m.TrackExistence {
dAtA[i] = 0x20
i++
if m.TrackExistence {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil
}
@ -2300,6 +2318,9 @@ func (m *IndexMeta) Size() (n int) {
if m.Keys {
n += 2
}
if m.TrackExistence {
n += 2
}
return n
}
@ -2902,6 +2923,26 @@ func (m *IndexMeta) Unmarshal(dAtA []byte) error {
}
}
m.Keys = bool(v != 0)
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TrackExistence", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPrivate
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.TrackExistence = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipPrivate(dAtA[iNdEx:])
@ -7143,73 +7184,74 @@ var (
func init() { proto.RegisterFile("private.proto", fileDescriptorPrivate) }
var fileDescriptorPrivate = []byte{
// 1077 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0x1b, 0x45,
0x14, 0x66, 0x7f, 0xec, 0xda, 0xc7, 0x75, 0x9a, 0x6c, 0x69, 0xd8, 0x22, 0x94, 0x9a, 0x51, 0xa5,
0x9a, 0x4a, 0x84, 0xaa, 0xbd, 0xe1, 0xaf, 0x52, 0x49, 0x1c, 0x60, 0x29, 0x09, 0x65, 0x36, 0xc9,
0x5d, 0x2f, 0x26, 0xf6, 0xa8, 0x59, 0x65, 0xbd, 0xb3, 0xec, 0xce, 0x26, 0x71, 0x2f, 0xb8, 0x05,
0x89, 0x17, 0x40, 0x3c, 0x09, 0x8f, 0xc0, 0x25, 0x8f, 0x80, 0xc2, 0x8b, 0xa0, 0x39, 0x33, 0xfb,
0x13, 0xc7, 0x21, 0x55, 0xe0, 0x6e, 0xce, 0x77, 0xce, 0x9c, 0xf3, 0xed, 0xf9, 0x9b, 0x85, 0x7e,
0x9a, 0x45, 0xc7, 0x4c, 0xf2, 0xf5, 0x34, 0x13, 0x52, 0x78, 0x9d, 0x28, 0x91, 0x3c, 0x4b, 0x58,
0x4c, 0xee, 0x41, 0x37, 0x48, 0x26, 0xfc, 0x74, 0x9b, 0x4b, 0xe6, 0x79, 0xe0, 0x3e, 0xe7, 0xb3,
0xdc, 0x77, 0x06, 0xd6, 0xb0, 0x43, 0xf1, 0x4c, 0x7e, 0xb7, 0xe0, 0xe6, 0x97, 0x11, 0x8f, 0x27,
0xdf, 0xa5, 0x32, 0x12, 0x49, 0xee, 0xbd, 0x07, 0xdd, 0x4d, 0x36, 0x3e, 0xe4, 0xbb, 0xb3, 0x94,
0xa3, 0x65, 0x97, 0xd6, 0x40, 0xa5, 0x0d, 0xa3, 0xd7, 0xdc, 0x77, 0x07, 0xd6, 0xb0, 0x4f, 0x6b,
0xc0, 0x1b, 0x40, 0x6f, 0x37, 0x9a, 0xf2, 0xef, 0x0b, 0x96, 0xc8, 0x62, 0xea, 0xb7, 0xf0, 0x76,
0x13, 0x52, 0x14, 0xd0, 0x71, 0x07, 0x55, 0x78, 0xf6, 0x96, 0xc1, 0xd9, 0x8e, 0x12, 0xbf, 0x3b,
0xb0, 0x86, 0x0e, 0x55, 0x47, 0x44, 0xd8, 0xa9, 0x0f, 0x06, 0x61, 0xa7, 0x15, 0xf5, 0x5e, 0x83,
0x3a, 0x81, 0xa5, 0x60, 0x9a, 0x8a, 0x4c, 0x52, 0x9e, 0xa7, 0x22, 0xc9, 0xd1, 0xd3, 0x56, 0x96,
0xf9, 0x16, 0x3a, 0x57, 0x47, 0xf2, 0x23, 0x2c, 0x6f, 0xc4, 0x62, 0x7c, 0x34, 0x62, 0x92, 0x51,
0xfe, 0x43, 0xc1, 0x73, 0xe9, 0xbd, 0x0d, 0x2d, 0xcc, 0x89, 0xb1, 0xd3, 0x82, 0x42, 0x31, 0x0f,
0xbe, 0xad, 0x51, 0x14, 0x14, 0x8a, 0xf7, 0x31, 0x13, 0x2e, 0xd5, 0x82, 0x42, 0xc3, 0x43, 0x96,
0x4d, 0x30, 0x03, 0x2e, 0xd5, 0x82, 0xe2, 0xb8, 0x1f, 0xf1, 0x13, 0xf3, 0xd9, 0x78, 0x26, 0x01,
0xac, 0x34, 0xe2, 0x1b, 0x9a, 0xab, 0xd0, 0xa6, 0xe2, 0x24, 0x18, 0xe5, 0xbe, 0x35, 0x70, 0x86,
0x2e, 0x35, 0x12, 0x26, 0x57, 0xc4, 0xc5, 0x34, 0x51, 0x2a, 0x1b, 0x55, 0x35, 0x40, 0xee, 0x42,
0x0b, 0x33, 0xad, 0xbe, 0xb2, 0xbe, 0xab, 0x8e, 0xe4, 0x27, 0x0b, 0xba, 0xdb, 0xec, 0x14, 0x69,
0xe4, 0xde, 0x53, 0xe8, 0x84, 0x92, 0x25, 0x13, 0x45, 0x50, 0x19, 0xf5, 0x1e, 0xbf, 0xbf, 0x5e,
0x36, 0xc4, 0x7a, 0x65, 0xb6, 0x5e, 0xda, 0x6c, 0x25, 0x32, 0x9b, 0xd1, 0xea, 0xca, 0xbb, 0x9f,
0x41, 0xff, 0x9c, 0x4a, 0xc5, 0x3b, 0xe2, 0xb3, 0x32, 0xab, 0x47, 0x7c, 0xa6, 0xbe, 0xff, 0x98,
0xc5, 0x05, 0xc7, 0x5c, 0xb9, 0x54, 0x0b, 0x9f, 0xda, 0x1f, 0x5b, 0x64, 0x1f, 0xbc, 0xcd, 0x8c,
0x33, 0xc9, 0x31, 0xc8, 0x36, 0xcf, 0x73, 0xf6, 0x8a, 0x5f, 0x9e, 0x71, 0x9d, 0x45, 0xbb, 0x99,
0xc5, 0xaa, 0x0e, 0x4e, 0xa3, 0x0e, 0xe4, 0x21, 0x78, 0x23, 0x1e, 0x73, 0xc9, 0x4d, 0x37, 0xff,
0x8b, 0x5f, 0x12, 0x96, 0x1c, 0xae, 0xb6, 0xf5, 0x1e, 0x80, 0xab, 0x46, 0x03, 0x29, 0xf4, 0x1e,
0xdf, 0xae, 0xf3, 0x54, 0x4d, 0x0d, 0x45, 0x03, 0x12, 0x97, 0x4e, 0x91, 0xcf, 0x95, 0x1f, 0xb6,
0xa0, 0x95, 0x1e, 0x9a, 0x50, 0x0e, 0x86, 0x5a, 0xad, 0x43, 0x35, 0xc7, 0xcf, 0x44, 0x7b, 0x56,
0x7e, 0xee, 0x75, 0xa3, 0x91, 0x97, 0x06, 0x55, 0x5d, 0xb9, 0xc3, 0xa6, 0xdc, 0xdc, 0xc1, 0x73,
0x45, 0xc5, 0xbe, 0x9a, 0x8a, 0x72, 0xaf, 0x3a, 0x59, 0x6d, 0x0d, 0x47, 0xb9, 0x47, 0x81, 0x3c,
0x81, 0x76, 0x38, 0x3e, 0xe4, 0x53, 0xe6, 0x7d, 0x00, 0x37, 0x90, 0x07, 0xcf, 0x4d, 0xb3, 0xdd,
0x9a, 0x4b, 0x22, 0x2d, 0xf5, 0x64, 0x64, 0xf8, 0x2f, 0xe4, 0xf4, 0x00, 0xda, 0x18, 0x3d, 0xf7,
0xdd, 0x79, 0x37, 0x88, 0x53, 0xa3, 0x26, 0x5b, 0xe0, 0xec, 0xd1, 0x40, 0x0d, 0x11, 0x32, 0x28,
0xbd, 0x18, 0x49, 0xf9, 0xfe, 0x5a, 0xe4, 0xd2, 0x64, 0x03, 0xcf, 0x0a, 0x7b, 0x21, 0x32, 0x89,
0xa9, 0xef, 0x53, 0x3c, 0x93, 0x97, 0xe0, 0xee, 0x88, 0x09, 0xf7, 0x96, 0xc0, 0x0e, 0x46, 0xc6,
0x87, 0x1d, 0x8c, 0xbc, 0x7b, 0xe8, 0xde, 0xa4, 0xa6, 0x5f, 0x93, 0xd8, 0xa3, 0x01, 0xc5, 0xc0,
0xf7, 0xa1, 0x1f, 0xe4, 0x9b, 0x42, 0x64, 0x93, 0x28, 0x61, 0x52, 0x64, 0x66, 0x9d, 0x9e, 0x07,
0xc9, 0x33, 0x58, 0x56, 0xee, 0x43, 0xc9, 0x24, 0x2f, 0xeb, 0xb7, 0x0a, 0x6d, 0x85, 0x55, 0xe1,
0x8c, 0x84, 0x83, 0xa0, 0xec, 0xca, 0x0a, 0xa2, 0x40, 0xbe, 0xd5, 0x1e, 0xb6, 0x8e, 0x79, 0x22,
0x1b, 0x1d, 0x80, 0x32, 0x3a, 0xe8, 0x53, 0x2d, 0x78, 0x44, 0x7f, 0x8a, 0xe1, 0xbc, 0x54, 0x73,
0x56, 0x28, 0x45, 0x1d, 0xf9, 0xc5, 0x02, 0x28, 0x09, 0x15, 0x79, 0x75, 0xc5, 0xba, 0xfc, 0x8a,
0x37, 0x2c, 0x6b, 0x6c, 0x5a, 0x76, 0xb9, 0xb6, 0xd2, 0x38, 0x2d, 0x7b, 0xe0, 0xa3, 0xba, 0x07,
0x74, 0xf1, 0xee, 0xcc, 0xf5, 0x80, 0x8e, 0x5a, 0x77, 0xc2, 0x0b, 0xe8, 0x35, 0xf0, 0x85, 0xfd,
0xf0, 0x61, 0xd5, 0x0f, 0xf6, 0xbc, 0x4b, 0xc4, 0x8d, 0xcb, 0xb2, 0x2b, 0x9e, 0x43, 0xaf, 0x01,
0x2f, 0xf4, 0x38, 0x84, 0x5b, 0x5f, 0x1c, 0xb3, 0x28, 0x66, 0x07, 0xb1, 0x5e, 0x4f, 0xe5, 0x92,
0x9d, 0x87, 0x49, 0x04, 0xfd, 0xcd, 0xb8, 0xc8, 0x25, 0xcf, 0x8c, 0x3b, 0xb5, 0x99, 0x35, 0x50,
0x15, 0xaf, 0x06, 0x16, 0xd7, 0xcf, 0xbb, 0x0f, 0x2d, 0x95, 0x46, 0x3d, 0x38, 0x17, 0x73, 0xac,
0x95, 0x64, 0x1f, 0x3a, 0x1b, 0x61, 0xf0, 0x55, 0x26, 0x8a, 0x74, 0x21, 0xe9, 0xf2, 0xc1, 0xb4,
0x2f, 0x3e, 0x98, 0xce, 0x85, 0x07, 0xd3, 0xad, 0x1e, 0x4c, 0x12, 0xc2, 0x8a, 0xde, 0x57, 0x6a,
0x5e, 0xaf, 0xb3, 0xae, 0xca, 0xd7, 0xcc, 0x69, 0xbc, 0x66, 0x21, 0xac, 0xe8, 0xb5, 0xf4, 0x7f,
0x3a, 0xfd, 0xcd, 0x86, 0x15, 0xca, 0xf3, 0xe8, 0x35, 0x0f, 0x92, 0x5c, 0x66, 0xc5, 0x58, 0x6d,
0x1f, 0x75, 0xff, 0x1b, 0x71, 0x60, 0xb2, 0xed, 0x50, 0x2d, 0xbc, 0x49, 0xa7, 0x7b, 0x8f, 0xa0,
0x37, 0x3f, 0x9d, 0x17, 0x4d, 0x9b, 0x26, 0xde, 0x23, 0xb8, 0x11, 0x8a, 0x22, 0x1b, 0x57, 0xed,
0xdb, 0xd8, 0x88, 0x9a, 0x99, 0x56, 0xd3, 0xd2, 0xac, 0x31, 0x1a, 0xad, 0x2b, 0x46, 0xe3, 0xe9,
0x5c, 0x2b, 0xf9, 0x6d, 0xbc, 0xf0, 0x4e, 0x7d, 0xe1, 0x9c, 0x9a, 0x9e, 0xb7, 0x26, 0x3f, 0x5b,
0x70, 0xb3, 0x49, 0xe1, 0x8d, 0x06, 0xb7, 0xaa, 0x88, 0xbd, 0xb0, 0x22, 0xce, 0xa2, 0x8a, 0xb8,
0x75, 0x45, 0xea, 0x87, 0xb9, 0xd5, 0x78, 0x98, 0xc9, 0x11, 0xdc, 0xbd, 0x50, 0xa6, 0x4d, 0x31,
0x4d, 0x55, 0x3f, 0xfc, 0x87, 0x72, 0xa9, 0x95, 0x96, 0x65, 0xa6, 0x50, 0x5d, 0xaa, 0x05, 0xf2,
0x09, 0xdc, 0x09, 0xb9, 0x6c, 0x14, 0xa9, 0xec, 0xb6, 0x01, 0x38, 0x3b, 0xfc, 0xe4, 0x92, 0xcf,
0x57, 0x2a, 0xf2, 0x39, 0xf8, 0x7b, 0xe9, 0x84, 0x49, 0x7e, 0xad, 0xdb, 0x1b, 0xd0, 0xd9, 0x15,
0xa9, 0x88, 0xc5, 0xab, 0xd9, 0x15, 0x53, 0xef, 0xc3, 0x0d, 0xbd, 0xbf, 0xf5, 0x1a, 0xe9, 0xd2,
0x52, 0x24, 0xb7, 0x55, 0x43, 0x8f, 0x59, 0x3c, 0x2e, 0x62, 0x45, 0x43, 0xfd, 0xb4, 0xe5, 0x1b,
0xcb, 0x7f, 0x9c, 0xad, 0x59, 0x7f, 0x9e, 0xad, 0x59, 0x7f, 0x9d, 0xad, 0x59, 0xbf, 0xfe, 0xbd,
0xf6, 0xd6, 0x41, 0x1b, 0x7f, 0xd6, 0x9f, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x7c, 0x6d,
0x22, 0xbd, 0x0b, 0x00, 0x00,
// 1095 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0xdc, 0x44,
0x14, 0xc6, 0x3f, 0xbb, 0xd9, 0x3d, 0xdb, 0x4d, 0x13, 0x97, 0x06, 0x17, 0xa1, 0x10, 0x46, 0x15,
0x0d, 0x95, 0x08, 0x55, 0x7b, 0xc3, 0x5f, 0xa5, 0x92, 0x6c, 0x28, 0xa6, 0x24, 0x94, 0x71, 0x92,
0xbb, 0x5e, 0x4c, 0x76, 0x47, 0x8d, 0x15, 0xaf, 0xc7, 0xd8, 0xe3, 0x24, 0xdb, 0x0b, 0x6e, 0x41,
0xe2, 0x05, 0x10, 0x4f, 0xc2, 0x23, 0x70, 0xc9, 0x23, 0xa0, 0xf0, 0x22, 0x68, 0xce, 0x8c, 0x7f,
0xb2, 0xd9, 0xb0, 0x55, 0xe8, 0xdd, 0x9c, 0xef, 0x9c, 0x39, 0xe7, 0x9b, 0xf3, 0x67, 0x43, 0x3f,
0xcd, 0xa2, 0x13, 0x26, 0xf9, 0x46, 0x9a, 0x09, 0x29, 0xbc, 0x4e, 0x94, 0x48, 0x9e, 0x25, 0x2c,
0x26, 0x4f, 0xa1, 0x1b, 0x24, 0x23, 0x7e, 0xb6, 0xc3, 0x25, 0xf3, 0x3c, 0x70, 0x9f, 0xf1, 0x49,
0xee, 0x3b, 0x6b, 0xd6, 0x7a, 0x87, 0xe2, 0xd9, 0xfb, 0x10, 0x16, 0xf7, 0x32, 0x36, 0x3c, 0xde,
0x3e, 0x8b, 0x72, 0xc9, 0x93, 0x21, 0xf7, 0x5d, 0xd4, 0x4e, 0xa1, 0xe4, 0x0f, 0x0b, 0x6e, 0x7c,
0x1d, 0xf1, 0x78, 0xf4, 0x7d, 0x2a, 0x23, 0x91, 0xe4, 0xde, 0x7b, 0xd0, 0xdd, 0x62, 0xc3, 0x23,
0xbe, 0x37, 0x49, 0x39, 0x7a, 0xec, 0xd2, 0x1a, 0xa8, 0xb4, 0x61, 0xf4, 0x4a, 0x7b, 0xec, 0xd3,
0x1a, 0xf0, 0xd6, 0xa0, 0xb7, 0x17, 0x8d, 0xf9, 0x0f, 0x05, 0x4b, 0x64, 0x31, 0xf6, 0x5b, 0x78,
0xbb, 0x09, 0x29, 0xaa, 0xe8, 0xb8, 0x83, 0x2a, 0x3c, 0x7b, 0x4b, 0xe0, 0xec, 0x44, 0x89, 0xdf,
0x5d, 0xb3, 0xd6, 0x1d, 0xaa, 0x8e, 0x88, 0xb0, 0x33, 0x1f, 0x0c, 0xc2, 0xce, 0xaa, 0x27, 0xf6,
0xea, 0x27, 0x12, 0x02, 0x8b, 0xc1, 0x38, 0x15, 0x99, 0xa4, 0x3c, 0x4f, 0x45, 0x92, 0xa3, 0xa7,
0xed, 0x2c, 0xf3, 0x2d, 0x74, 0xae, 0x8e, 0xe4, 0x27, 0x58, 0xda, 0x8c, 0xc5, 0xf0, 0x78, 0xc0,
0x24, 0xa3, 0xfc, 0xc7, 0x82, 0xe7, 0xd2, 0x7b, 0x1b, 0x5a, 0x98, 0x3b, 0x63, 0xa7, 0x05, 0x85,
0x62, 0x1e, 0x7c, 0x5b, 0xa3, 0x28, 0x28, 0x14, 0xef, 0x63, 0x26, 0x5c, 0xaa, 0x05, 0x85, 0x86,
0x47, 0x2c, 0x1b, 0x61, 0x06, 0x5c, 0xaa, 0x05, 0xc5, 0xf1, 0x20, 0xe2, 0xa7, 0xe6, 0xd9, 0x78,
0x26, 0x01, 0x2c, 0x37, 0xe2, 0x1b, 0x9a, 0x2b, 0xd0, 0xa6, 0xe2, 0x34, 0x18, 0xe4, 0xbe, 0xb5,
0xe6, 0xac, 0xbb, 0xd4, 0x48, 0x98, 0x5c, 0x11, 0x17, 0xe3, 0x44, 0xa9, 0x6c, 0x54, 0xd5, 0x00,
0xb9, 0x03, 0x2d, 0xcc, 0xb4, 0x7a, 0x65, 0x7d, 0x57, 0x1d, 0xc9, 0xcf, 0x16, 0x74, 0x77, 0xd8,
0x19, 0xd2, 0xc8, 0xbd, 0xc7, 0xd0, 0x09, 0x25, 0x4b, 0x46, 0x8a, 0xa0, 0x32, 0xea, 0x3d, 0xfc,
0x60, 0xa3, 0x6c, 0x9c, 0x8d, 0xca, 0x6c, 0xa3, 0xb4, 0xd9, 0x4e, 0x64, 0x36, 0xa1, 0xd5, 0x95,
0x77, 0xbf, 0x80, 0xfe, 0x05, 0x95, 0x8a, 0x77, 0xcc, 0x27, 0x65, 0x56, 0x8f, 0xf9, 0x44, 0xbd,
0xff, 0x84, 0xc5, 0x05, 0xc7, 0x5c, 0xb9, 0x54, 0x0b, 0x9f, 0xdb, 0x9f, 0x5a, 0xe4, 0x00, 0xbc,
0xad, 0x8c, 0x33, 0xc9, 0x31, 0xc8, 0x0e, 0xcf, 0x73, 0xf6, 0x92, 0x5f, 0x9d, 0x71, 0x9d, 0x45,
0xbb, 0x99, 0xc5, 0xaa, 0x0e, 0x4e, 0xa3, 0x0e, 0xe4, 0x3e, 0x78, 0x03, 0x1e, 0x73, 0xc9, 0x4d,
0xd7, 0xff, 0x87, 0x5f, 0x12, 0x96, 0x1c, 0xe6, 0xdb, 0x7a, 0xf7, 0xc0, 0x55, 0x23, 0x84, 0x14,
0x7a, 0x0f, 0x6f, 0xd5, 0x79, 0xaa, 0xa6, 0x8b, 0xa2, 0x01, 0x89, 0x4b, 0xa7, 0xc8, 0x67, 0xee,
0xc3, 0x66, 0xb4, 0xd2, 0x7d, 0x13, 0xca, 0xc1, 0x50, 0x2b, 0x75, 0xa8, 0xe6, 0xf8, 0x99, 0x68,
0x4f, 0xca, 0xe7, 0x5e, 0x37, 0x1a, 0x79, 0x61, 0x50, 0xd5, 0x95, 0xbb, 0x6c, 0xcc, 0xcd, 0x1d,
0x3c, 0x57, 0x54, 0xec, 0xf9, 0x54, 0x94, 0x7b, 0xd5, 0xc9, 0x6a, 0xbb, 0x38, 0xca, 0x3d, 0x0a,
0xe4, 0x11, 0xb4, 0xc3, 0xe1, 0x11, 0x1f, 0x33, 0xef, 0x23, 0x58, 0x40, 0x1e, 0x3c, 0x37, 0xcd,
0x76, 0x73, 0x2a, 0x89, 0xb4, 0xd4, 0x93, 0x81, 0xe1, 0x3f, 0x93, 0xd3, 0x3d, 0x68, 0x63, 0xf4,
0xdc, 0x77, 0xa7, 0xdd, 0x20, 0x4e, 0x8d, 0x9a, 0x6c, 0x83, 0xb3, 0x4f, 0x03, 0x35, 0x44, 0xc8,
0xa0, 0xf4, 0x62, 0x24, 0xe5, 0xfb, 0x1b, 0x91, 0x4b, 0x93, 0x0d, 0x3c, 0x2b, 0xec, 0xb9, 0xc8,
0x24, 0xa6, 0xbe, 0x4f, 0xf1, 0x4c, 0x5e, 0x80, 0xbb, 0x2b, 0x46, 0xdc, 0x5b, 0x04, 0x3b, 0x18,
0x18, 0x1f, 0x76, 0x30, 0xf0, 0xde, 0x47, 0xf7, 0x26, 0x35, 0xfd, 0x9a, 0xc4, 0x3e, 0x0d, 0x28,
0x06, 0xbe, 0x0b, 0xfd, 0x20, 0xdf, 0x12, 0x22, 0x1b, 0x45, 0x09, 0x93, 0x22, 0x33, 0x6b, 0xf7,
0x22, 0x48, 0x9e, 0xc0, 0x92, 0x72, 0x1f, 0x4a, 0x26, 0x79, 0x59, 0xbf, 0x15, 0x68, 0x2b, 0xac,
0x0a, 0x67, 0x24, 0x1c, 0x04, 0x65, 0x57, 0x56, 0x10, 0x05, 0xf2, 0x9d, 0xf6, 0xb0, 0x7d, 0xc2,
0x13, 0xd9, 0xe8, 0x00, 0x94, 0xd1, 0x41, 0x9f, 0x6a, 0xc1, 0x23, 0xfa, 0x29, 0x86, 0xf3, 0x62,
0xcd, 0x59, 0xa1, 0x14, 0x75, 0xe4, 0x57, 0x0b, 0xa0, 0x24, 0x54, 0xe4, 0xd5, 0x15, 0xeb, 0xea,
0x2b, 0xde, 0x7a, 0x59, 0x63, 0xd3, 0xb2, 0x4b, 0xb5, 0x95, 0xc6, 0x69, 0xd9, 0x03, 0x9f, 0xd4,
0x3d, 0xa0, 0x8b, 0x77, 0x7b, 0xaa, 0x07, 0x74, 0xd4, 0xba, 0x13, 0x9e, 0x43, 0xaf, 0x81, 0xcf,
0xec, 0x87, 0x8f, 0xab, 0x7e, 0xb0, 0xa7, 0x5d, 0x22, 0x6e, 0x5c, 0x96, 0x5d, 0xf1, 0x0c, 0x7a,
0x0d, 0x78, 0xa6, 0xc7, 0x75, 0xb8, 0xf9, 0xd5, 0x09, 0x8b, 0x62, 0x76, 0x18, 0xeb, 0xf5, 0x54,
0x2e, 0xd9, 0x69, 0x98, 0x44, 0xd0, 0xdf, 0x8a, 0x8b, 0x5c, 0xf2, 0xcc, 0xb8, 0x53, 0x9b, 0x59,
0x03, 0x55, 0xf1, 0x6a, 0x60, 0x76, 0xfd, 0xbc, 0xbb, 0xd0, 0x52, 0x69, 0xd4, 0x83, 0x73, 0x39,
0xc7, 0x5a, 0x49, 0x0e, 0xa0, 0xb3, 0x19, 0x06, 0x4f, 0x33, 0x51, 0xa4, 0x33, 0x49, 0x97, 0x1f,
0x4c, 0xfb, 0xf2, 0x07, 0xd3, 0xb9, 0xf4, 0xc1, 0x74, 0xab, 0x0f, 0x26, 0x09, 0x61, 0x59, 0xef,
0x2b, 0x35, 0xaf, 0xd7, 0x59, 0x57, 0xe5, 0xd7, 0xcc, 0x69, 0x7c, 0xcd, 0x42, 0x58, 0xd6, 0x6b,
0xe9, 0x4d, 0x3a, 0xfd, 0xdd, 0x86, 0x65, 0xca, 0xf3, 0xe8, 0x15, 0x0f, 0x92, 0x5c, 0x66, 0xc5,
0x50, 0x6d, 0x1f, 0x75, 0xff, 0x5b, 0x71, 0x68, 0xb2, 0xed, 0x50, 0x2d, 0xbc, 0x4e, 0xa7, 0x7b,
0x0f, 0xa0, 0x37, 0x3d, 0x9d, 0x97, 0x4d, 0x9b, 0x26, 0xde, 0x03, 0x58, 0x08, 0x45, 0x91, 0x0d,
0xab, 0xf6, 0x6d, 0x6c, 0x44, 0xcd, 0x4c, 0xab, 0x69, 0x69, 0xd6, 0x18, 0x8d, 0xd6, 0x9c, 0xd1,
0x78, 0x3c, 0xd5, 0x4a, 0x7e, 0x1b, 0x2f, 0xbc, 0x53, 0x5f, 0xb8, 0xa0, 0xa6, 0x17, 0xad, 0xc9,
0x2f, 0x16, 0xdc, 0x68, 0x52, 0x78, 0xad, 0xc1, 0xad, 0x2a, 0x62, 0xcf, 0xac, 0x88, 0x33, 0xab,
0x22, 0x6e, 0x5d, 0x91, 0xfa, 0xc3, 0xdc, 0x6a, 0x7c, 0x98, 0xc9, 0x31, 0xdc, 0xb9, 0x54, 0xa6,
0x2d, 0x31, 0x4e, 0x55, 0x3f, 0xfc, 0x8f, 0x72, 0xa9, 0x95, 0x96, 0x65, 0xa6, 0x50, 0x5d, 0xaa,
0x05, 0xf2, 0x19, 0xdc, 0x0e, 0xb9, 0x6c, 0x14, 0xa9, 0xec, 0xb6, 0x35, 0x70, 0x76, 0xf9, 0xe9,
0x15, 0xcf, 0x57, 0x2a, 0xf2, 0x25, 0xf8, 0xfb, 0xe9, 0x88, 0x49, 0x7e, 0xad, 0xdb, 0x9b, 0xd0,
0xd9, 0x13, 0xa9, 0x88, 0xc5, 0xcb, 0xc9, 0x9c, 0xa9, 0xf7, 0x61, 0x41, 0xef, 0x6f, 0xbd, 0x46,
0xba, 0xb4, 0x14, 0xc9, 0x2d, 0xd5, 0xd0, 0x43, 0x16, 0x0f, 0x8b, 0x58, 0xd1, 0x50, 0x3f, 0x6d,
0xf9, 0xe6, 0xd2, 0x9f, 0xe7, 0xab, 0xd6, 0x5f, 0xe7, 0xab, 0xd6, 0xdf, 0xe7, 0xab, 0xd6, 0x6f,
0xff, 0xac, 0xbe, 0x75, 0xd8, 0xc6, 0x9f, 0xfa, 0x47, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x31,
0x07, 0xf3, 0xac, 0xe5, 0x0b, 0x00, 0x00,
}

View file

@ -4,6 +4,7 @@ package internal;
message IndexMeta {
bool Keys = 3;
bool TrackExistence = 4;
}
message FieldOptions {

View file

@ -1,3 +1,17 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
@ -95,11 +109,17 @@ func Difference(args ...*pql.Call) *pql.Call {
kvargs, children := magic(args)
return &pql.Call{Name: "Difference", Args: kvargs, Children: children}
}
func Xor(args ...*pql.Call) *pql.Call {
kvargs, children := magic(args)
return &pql.Call{Name: "Xor", Args: kvargs, Children: children}
}
func Not(args ...*pql.Call) *pql.Call {
kvargs, children := magic(args)
return &pql.Call{Name: "Not", Args: kvargs, Children: children}
}
func Between(_ string, min, max int) *pql.Call {
return &pql.Call{
Name: "Range",

View file

@ -1,3 +1,17 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
@ -208,6 +222,24 @@ func TestPQL_Generator(t *testing.T) {
},
},
},
{
pql: `Not(Row(aaa=10))`,
calc: PQL(Not(Row("aaa", 10))),
exp: &pql.Query{
Calls: []*pql.Call{
{
Name: "Not",
Args: map[string]interface{}{},
Children: []*pql.Call{
{
Name: "Row",
Args: map[string]interface{}{"frame": "aaa", "row": 10},
},
},
},
},
},
},
} {
if !Compare(u.calc, u.exp) {

View file

@ -49,6 +49,8 @@ var (
ErrName = errors.New("invalid index or field name, must match [a-z0-9_-]")
ErrLabel = errors.New("invalid row or column label, must match [A-Za-z0-9_-]")
ErrReservedName = errors.New("reserved index or field name")
// ErrFragmentNotFound is returned when a fragment does not exist.
ErrFragmentNotFound = errors.New("fragment not found")
ErrQueryRequired = errors.New("query required")
@ -125,6 +127,9 @@ const TimeFormat = "2006-01-02T15:04"
// validateName ensures that the name is a valid format.
func validateName(name string) error {
if name == existenceFieldName {
return ErrReservedName
}
if !nameRegexp.Match([]byte(name)) {
return ErrName
}

View file

@ -34,6 +34,7 @@ func TestValidateNameInvalid(t *testing.T) {
names := []string{
"", "'", "^", "/", "\\", "A", "*", "a:b", "valid?no", "yüce", "1", "_", "-",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1",
"exists",
}
for _, name := range names {
if validateName(name) == nil {

View file

@ -595,6 +595,23 @@ func TestPQLDeepEquality(t *testing.T) {
{Name: "Row"},
},
}},
{
name: "OptionsWrapper",
call: "Options(Row(f1=123), excludeRowAttrs=true)",
exp: &Call{
Name: "Options",
Args: map[string]interface{}{
"excludeRowAttrs": true,
},
Children: []*Call{
{
Name: "Row",
Args: map[string]interface{}{
"f1": int64(123),
},
},
},
}},
}
for i, test := range tests {

View file

@ -91,6 +91,20 @@ func (h *Holder) Row(index, field string, rowID uint64) *pilosa.Row {
return row
}
// ReadRow returns a Row for a given field. If the field does not exist,
// it panics rather than creating the field.
func (h *Holder) ReadRow(index, field string, rowID uint64) *pilosa.Row {
f := h.Holder.Field(index, field)
if f == nil {
panic(pilosa.ErrFieldNotFound)
}
row, err := f.Row(rowID)
if err != nil {
panic(err)
}
return row
}
func (h *Holder) RowAttrStore(index, field string) pilosa.AttrStore {
idx := h.MustCreateIndexIfNotExists(index, pilosa.IndexOptions{})
f, err := idx.CreateFieldIfNotExists(field, pilosa.OptFieldTypeDefault())

View file

@ -18,7 +18,6 @@ import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
gohttp "net/http"
"os"
@ -40,10 +39,6 @@ type Command struct {
*server.Command
commandOptions []server.CommandOption
stdin bytes.Buffer
stdout bytes.Buffer
stderr bytes.Buffer
}
func OptAllowedOrigins(origins []string) server.CommandOption {
@ -65,17 +60,15 @@ func newCommand(opts ...server.CommandOption) *Command {
// beginning of the option slice so that it can be overridden by user-passed
// options.
opts = append([]server.CommandOption{server.OptCommandCloseTimeout(time.Millisecond * 2)}, opts...)
m := &Command{Command: server.NewCommand(os.Stdin, os.Stdout, os.Stderr, opts...), commandOptions: opts}
m := &Command{commandOptions: opts}
m.Command = server.NewCommand(bytes.NewReader(nil), ioutil.Discard, ioutil.Discard, opts...)
m.Config.DataDir = path
m.Config.Bind = "http://localhost:0"
m.Config.Cluster.Disabled = true
m.Command.Stdin = &m.stdin
m.Command.Stdout = &m.stdout
m.Command.Stderr = &m.stderr
if testing.Verbose() {
m.Command.Stdout = io.MultiWriter(os.Stdout, m.Command.Stdout)
m.Command.Stderr = io.MultiWriter(os.Stderr, m.Command.Stderr)
m.Command.Stdout = os.Stdout
m.Command.Stderr = os.Stderr
}
return m
@ -120,7 +113,7 @@ func (m *Command) Reopen() error {
// Create new main with the same config.
config := m.Command.Config
m.Command = server.NewCommand(os.Stdin, os.Stdout, os.Stderr, m.commandOptions...)
m.Command = server.NewCommand(bytes.NewReader(nil), ioutil.Discard, ioutil.Discard, m.commandOptions...)
m.Command.Config = config
// Run new program.