mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
adds NoStandardView field option. Fixes #1710
This commit is contained in:
parent
8e58fe3541
commit
7913a419ae
7 changed files with 746 additions and 2665 deletions
76
field.go
76
field.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue