Skip to content
This repository was archived by the owner on Feb 21, 2024. It is now read-only.

Commit a3a0de2

Browse files
committed
rework and consolidate view name cleanup
We had two different versions of this, and a comment referring to a third which doesn't exist, so I've consolidated them and made them slightly pickier, to avoid problems like the one I ran into developing the existence tracking where one of these optimistically transformed names it actually shouldn't have. Now if we don't expect a view name, we yield an error, rather than silently performing a transformation. This also implies updating the ImportRoaring_MultiView test to use two valid view names.
1 parent 54dbeec commit a3a0de2

3 files changed

Lines changed: 52 additions & 43 deletions

File tree

api.go

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -499,22 +499,9 @@ func importWorker(importWork chan importJob) {
499499
for j := range importWork {
500500
err := func() (err0 error) {
501501
for viewName, viewData := range j.req.Views {
502-
// The logic here corresponds to the logic in fragment.cleanViewName().
503-
// Unfortunately, the logic in that method is not completely exclusive
504-
// (i.e. an "other" view named with format YYYYMMDD would be handled
505-
// incorrectly). One way to address this would be to change the logic
506-
// overall so there weren't conflicts. For now, we just
507-
// rely on the field type to inform the intended view name.
508-
// contrast with cleanupView, which is similar but unfortunately not quite identical
509-
switch viewName {
510-
case "":
511-
viewName = viewStandard
512-
case viewStandard, viewExistence:
513-
// do nothing, these are fine
514-
default: // possibly a time view
515-
if j.field.Type() == FieldTypeTime && !strings.HasPrefix(viewName, viewStandard) {
516-
viewName = viewStandard + "_" + viewName
517-
}
502+
viewName, err0 = j.field.cleanupViewName(viewName)
503+
if err0 != nil {
504+
return err0
518505
}
519506
if len(viewData) == 0 {
520507
return fmt.Errorf("no data to import for view: %s", viewName)
@@ -1687,7 +1674,7 @@ func (api *API) ImportRoaringShard(ctx context.Context, indexName string, shard
16871674
}
16881675

16891676
fieldType := field.Options().Type
1690-
if err1 = cleanupView(fieldType, &viewUpdate); err1 != nil {
1677+
if viewUpdate.View, err1 = field.cleanupViewName(viewUpdate.View); err1 != nil {
16911678
return err1
16921679
}
16931680

@@ -1779,30 +1766,6 @@ func (api *API) ImportRoaringShard(ctx context.Context, indexName string, shard
17791766
return nil
17801767
}
17811768

1782-
func cleanupView(fieldType string, viewUpdate *RoaringUpdate) error {
1783-
// TODO wouldn't hurt to have consolidated logic somewhere for validating view names.
1784-
switch fieldType {
1785-
case FieldTypeSet, FieldTypeTime:
1786-
switch viewUpdate.View {
1787-
case "":
1788-
viewUpdate.View = viewStandard
1789-
case viewStandard, viewExistence:
1790-
// do nothing, these are fine
1791-
default:
1792-
if fieldType == FieldTypeTime && !strings.HasPrefix(viewUpdate.View, viewStandard) {
1793-
viewUpdate.View = viewStandard + "_" + viewUpdate.View
1794-
}
1795-
}
1796-
case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp:
1797-
if viewUpdate.View == "" {
1798-
viewUpdate.View = "bsig_" + viewUpdate.Field
1799-
} else if viewUpdate.View != "bsig_"+viewUpdate.Field {
1800-
return NewBadRequestError(errors.Errorf("invalid view name (%s) for field %s of type %s", viewUpdate.View, viewUpdate.Field, fieldType))
1801-
}
1802-
}
1803-
return nil
1804-
}
1805-
18061769
// ImportValue is a wrapper around the common code in ImportValueWithTx, which
18071770
// currently just translates req.Clear into a clear ImportOption.
18081771
func (api *API) ImportValue(ctx context.Context, qcx *Qcx, req *ImportValueRequest, opts ...ImportOption) error {

field.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"sync"
1717
"time"
18+
"unicode"
1819

1920
"github.com/featurebasedb/featurebase/v3/pql"
2021
"github.com/featurebasedb/featurebase/v3/roaring"
@@ -982,6 +983,51 @@ func (f *Field) hasBSIGroup(name string) bool {
982983
return false
983984
}
984985

986+
// cleanupViewName yields a "corrected" view name, handling some
987+
// idioms we used elsewhere in code. Given an empty string,
988+
// it yields a default view name (either "standard" or the BSI view
989+
// for BSI fields). Given a string starting with numbers, it
990+
// yields the corresponding time quantum view (prefixing "standard_").
991+
// It yields an error if the view name given does not correspond
992+
// to a view which should exist. For instance, the "standard" or
993+
// "existence" views for a BSI field, or a time quantum view for
994+
// a non-time field.
995+
func (f *Field) cleanupViewName(viewName string) (string, error) {
996+
if viewName == "" {
997+
switch f.options.Type {
998+
case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp:
999+
return "bsig_" + f.name, nil
1000+
default:
1001+
return viewStandard, nil
1002+
}
1003+
}
1004+
switch f.options.Type {
1005+
case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp:
1006+
if viewName == "bsig_"+f.name {
1007+
return viewName, nil
1008+
}
1009+
return viewName, fmt.Errorf("BSI-type field view should be named bsig_[fieldname], got %q", viewName)
1010+
case FieldTypeTime:
1011+
switch {
1012+
case viewName == viewStandard, viewName == viewExistence:
1013+
return viewName, nil
1014+
case strings.HasPrefix(viewName, viewStandard):
1015+
return viewName, nil
1016+
case unicode.IsDigit(rune(viewName[0])):
1017+
return viewStandard + "_" + viewName, nil
1018+
default:
1019+
return viewName, fmt.Errorf("time field views are %q, %q, or %q_[digits], got %q", viewStandard, viewExistence, viewStandard, viewName)
1020+
}
1021+
default:
1022+
switch viewName {
1023+
case viewStandard, viewExistence:
1024+
return viewName, nil
1025+
default:
1026+
return viewName, fmt.Errorf("unexpected view name %q, expecting %q or %q", viewName, viewStandard, viewExistence)
1027+
}
1028+
}
1029+
}
1030+
9851031
// createBSIGroup creates a new bsiGroup on the field.
9861032
func (f *Field) createBSIGroup(bsig *bsiGroup) error {
9871033
// Append bsiGroup.

internal_client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,8 +664,8 @@ func TestClient_ImportRoaring_MultiView(t *testing.T) {
664664
host := cluster.GetNode(0).URL()
665665
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
666666
req := &pilosa.ImportRoaringRequest{Views: map[string][]byte{}}
667-
req.Views["a"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
668-
req.Views["b"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
667+
req.Views["standard"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
668+
req.Views["existence"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
669669
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, req); err != nil {
670670
t.Fatal(err)
671671
}

0 commit comments

Comments
 (0)