adds NoStandardView field option. Fixes #1710

This commit is contained in:
Yuce Tekol 2018-11-08 18:33:39 +03:00
parent 8e58fe3541
commit 7913a419ae
No known key found for this signature in database
GPG key ID: CB59E46D2FB90573
7 changed files with 746 additions and 2665 deletions

View file

@ -132,6 +132,10 @@ func OptFieldTypeInt(min, max int64) FieldOption {
}
func OptFieldTypeTime(timeQuantum TimeQuantum) FieldOption {
return OptFieldTypeTimeOptions(timeQuantum, false)
}
func OptFieldTypeTimeOptions(timeQuantum TimeQuantum, noStandardView bool) FieldOption {
return func(fo *FieldOptions) error {
if fo.Type != "" {
return errors.Errorf("field type is already set to: %s", fo.Type)
@ -141,6 +145,7 @@ func OptFieldTypeTime(timeQuantum TimeQuantum) FieldOption {
}
fo.Type = FieldTypeTime
fo.TimeQuantum = timeQuantum
fo.NoStandardView = noStandardView
return nil
}
}
@ -446,6 +451,7 @@ func (f *Field) loadMeta() error {
f.options.Max = pb.Max
f.options.TimeQuantum = TimeQuantum(pb.TimeQuantum)
f.options.Keys = pb.Keys
f.options.NoStandardView = pb.NoStandardView
return nil
}
@ -512,6 +518,7 @@ func (f *Field) applyOptions(opt FieldOptions) error {
f.options.Min = 0
f.options.Max = 0
f.options.Keys = opt.Keys
f.options.NoStandardView = opt.NoStandardView
// Set the time quantum.
if err := f.setTimeQuantum(opt.TimeQuantum); err != nil {
f.Close()
@ -795,18 +802,19 @@ func (f *Field) Row(rowID uint64) (*Row, error) {
// SetBit sets a bit on a view within the field.
func (f *Field) SetBit(rowID, colID uint64, t *time.Time) (changed bool, err error) {
viewName := viewStandard
if f.options.Type == FieldTypeTime && !f.options.NoStandardView {
// Retrieve view. Exit if it doesn't exist.
view, err := f.createViewIfNotExists(viewName)
if err != nil {
return changed, errors.Wrap(err, "creating view")
}
// Retrieve view. Exit if it doesn't exist.
view, err := f.createViewIfNotExists(viewName)
if err != nil {
return changed, errors.Wrap(err, "creating view")
}
// Set non-time bit.
if v, err := view.setBit(rowID, colID); err != nil {
return changed, errors.Wrap(err, "setting on view")
} else if v {
changed = v
// Set non-time bit.
if v, err := view.setBit(rowID, colID); err != nil {
return changed, errors.Wrap(err, "setting on view")
} else if v {
changed = v
}
}
// Exit early if no timestamp is specified.
@ -1090,9 +1098,11 @@ func (f *Field) Import(rowIDs, columnIDs []uint64, timestamps []*time.Time, opts
standard = []string{viewStandard}
} else {
standard = viewsByTime(viewStandard, *timestamp, q)
// In order to match the logic of `SetBit()`, we want bits
// with timestamps to write to both time and standard views.
standard = append(standard, viewStandard)
if !f.options.NoStandardView {
// In order to match the logic of `SetBit()`, we want bits
// with timestamps to write to both time and standard views.
standard = append(standard, viewStandard)
}
}
// Attach bit to each standard view.
@ -1223,13 +1233,14 @@ func (p fieldInfoSlice) Less(i, j int) bool { return p[i].Name < p[j].Name }
// FieldOptions represents options to set when initializing a field.
type FieldOptions struct {
Min int64 `json:"min,omitempty"`
Max int64 `json:"max,omitempty"`
Keys bool `json:"keys"`
CacheSize uint32 `json:"cacheSize,omitempty"`
CacheType string `json:"cacheType,omitempty"`
Type string `json:"type,omitempty"`
TimeQuantum TimeQuantum `json:"timeQuantum,omitempty"`
Min int64 `json:"min,omitempty"`
Max int64 `json:"max,omitempty"`
Keys bool `json:"keys"`
CacheSize uint32 `json:"cacheSize,omitempty"`
CacheType string `json:"cacheType,omitempty"`
Type string `json:"type,omitempty"`
TimeQuantum TimeQuantum `json:"timeQuantum,omitempty"`
NoStandardView bool `json:"noStandardView,omitempty"`
}
// applyDefaultOptions returns a new FieldOptions object
@ -1255,13 +1266,14 @@ func encodeFieldOptions(o *FieldOptions) *internal.FieldOptions {
return nil
}
return &internal.FieldOptions{
Type: o.Type,
CacheType: o.CacheType,
CacheSize: o.CacheSize,
Min: o.Min,
Max: o.Max,
TimeQuantum: string(o.TimeQuantum),
Keys: o.Keys,
Type: o.Type,
CacheType: o.CacheType,
CacheSize: o.CacheSize,
Min: o.Min,
Max: o.Max,
TimeQuantum: string(o.TimeQuantum),
Keys: o.Keys,
NoStandardView: o.NoStandardView,
}
}
@ -1293,13 +1305,15 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) {
})
case FieldTypeTime:
return json.Marshal(struct {
Type string `json:"type"`
TimeQuantum TimeQuantum `json:"timeQuantum"`
Keys bool `json:"keys"`
Type string `json:"type"`
TimeQuantum TimeQuantum `json:"timeQuantum"`
Keys bool `json:"keys"`
NoStandardView bool `json:"noStandardView"`
}{
o.Type,
o.TimeQuantum,
o.Keys,
o.NoStandardView,
})
case FieldTypeMutex:
return json.Marshal(struct {

View file

@ -24,9 +24,8 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
// Imported for its side-effect of registering pprof endpoints with the server.
_ "net/http/pprof"
"net/url" // Imported for its side-effect of registering pprof endpoints with the server.
"reflect"
"runtime/debug"
"strconv"
@ -36,7 +35,6 @@ import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pilosa/pilosa"
"github.com/pkg/errors"
)
@ -706,7 +704,7 @@ func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) {
case pilosa.FieldTypeInt:
fos = append(fos, pilosa.OptFieldTypeInt(*req.Options.Min, *req.Options.Max))
case pilosa.FieldTypeTime:
fos = append(fos, pilosa.OptFieldTypeTime(*req.Options.TimeQuantum))
fos = append(fos, pilosa.OptFieldTypeTimeOptions(*req.Options.TimeQuantum, req.Options.NoStandardView))
case pilosa.FieldTypeMutex:
fos = append(fos, pilosa.OptFieldTypeMutex(*req.Options.CacheType, *req.Options.CacheSize))
case pilosa.FieldTypeBool:
@ -729,13 +727,14 @@ type postFieldRequest struct {
// fieldOptions tracks pilosa.FieldOptions. It is made up of pointers to values,
// and used for input validation.
type fieldOptions struct {
Type string `json:"type,omitempty"`
CacheType *string `json:"cacheType,omitempty"`
CacheSize *uint32 `json:"cacheSize,omitempty"`
Min *int64 `json:"min,omitempty"`
Max *int64 `json:"max,omitempty"`
TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"`
Keys *bool `json:"keys,omitempty"`
Type string `json:"type,omitempty"`
CacheType *string `json:"cacheType,omitempty"`
CacheSize *uint32 `json:"cacheSize,omitempty"`
Min *int64 `json:"min,omitempty"`
Max *int64 `json:"max,omitempty"`
TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"`
Keys *bool `json:"keys,omitempty"`
NoStandardView bool `json:"noStandardView,omitempty"`
}
func (o *fieldOptions) validate() error {

View file

@ -70,6 +70,22 @@ func TestIndex_CreateField(t *testing.T) {
})
})
// Ensure time quantum can be set appropriately on a new field.
t.Run("TimeQuantumNoStandardView", func(t *testing.T) {
t.Run("Explicit", func(t *testing.T) {
index := test.MustOpenIndex()
defer index.Close()
// Create field with explicit quantum with no standard view
f, err := index.CreateField("f", pilosa.OptFieldTypeTimeOptions(pilosa.TimeQuantum("YMDH"), true))
if err != nil {
t.Fatal(err)
} else if q := f.TimeQuantum(); q != pilosa.TimeQuantum("YMDH") {
t.Fatalf("unexpected field time quantum: %s", q)
}
})
})
// Ensure field can include range columns.
t.Run("BSIFields", func(t *testing.T) {
t.Run("OK", func(t *testing.T) {

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@ message FieldOptions {
int64 Max = 10;
string TimeQuantum = 5;
bool Keys = 11;
bool NoStandardView = 12;
}
message ImportResponse {

File diff suppressed because it is too large Load diff

View file

@ -621,6 +621,58 @@ func TestMain_ImportTimestamp(t *testing.T) {
}
}
func TestMain_ImportTimestampNoStandardView(t *testing.T) {
m := test.MustRunCommand()
defer m.Close()
indexName := "i"
fieldName := "f-no-standard"
// Create index.
if _, err := m.API.CreateIndex(context.Background(), indexName, pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
}
// Create field.
if _, err := m.API.CreateField(context.Background(), indexName, fieldName, pilosa.OptFieldTypeTimeOptions(pilosa.TimeQuantum("YMD"), true)); err != nil {
t.Fatal(err)
}
data := pilosa.ImportRequest{
Index: indexName,
Field: fieldName,
Shard: 0,
RowIDs: []uint64{1, 2},
ColumnIDs: []uint64{1, 2},
Timestamps: []int64{1514764800000000000, 1577833200000000000}, // 2018-01-01T00:00, 2019-12-31T23:00
}
// Import data.
if err := m.API.Import(context.Background(), &data); err != nil {
t.Fatal(err)
}
// Ensure the correct views were created.
dir := fmt.Sprintf("%s/%s/%s/views", m.Config.DataDir, indexName, fieldName)
files, err := ioutil.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
exp := []string{
"standard_2018", "standard_201801", "standard_20180101",
"standard_2019", "standard_201912", "standard_20191231",
}
got := []string{}
for _, f := range files {
got = append(got, f.Name())
}
if !reflect.DeepEqual(got, exp) {
t.Fatalf("expected %v, but got %v", exp, got)
}
}
func TestClusterQueriesAfterRestart(t *testing.T) {
cluster := test.MustRunCluster(t, 3)
defer cluster.Close()