diff --git a/api.go b/api.go index 65fe6d8db..95ae40f16 100644 --- a/api.go +++ b/api.go @@ -2343,7 +2343,10 @@ func (api *API) GetTranslateEntryReader(ctx context.Context, offsets TranslateOf if field == nil { return nil, newNotFoundError(ErrFieldNotFound, fieldName) } - + store := field.TranslateStore() + if store == nil { + return nil, ErrTranslateStoreNotFound + } r, err := field.TranslateStore().EntryReader(ctx, uint64(offset)) if err != nil { return nil, errors.Wrap(err, "field translate reader") @@ -2643,7 +2646,13 @@ func (api *API) RestoreIDAlloc(r io.Reader) error { // rd is a boltdb file. func (api *API) TranslateIndexDB(ctx context.Context, indexName string, partitionID int, rd io.Reader) error { idx := api.holder.Index(indexName) + if idx == nil { + return fmt.Errorf("index %q not found", indexName) + } store := idx.TranslateStore(partitionID) + if store == nil { + return fmt.Errorf("index %q has no translate store", indexName) + } _, err := store.ReadFrom(rd) return err } @@ -2651,8 +2660,23 @@ func (api *API) TranslateIndexDB(ctx context.Context, indexName string, partitio // TranslateFieldDB is an internal function to load the field keys database func (api *API) TranslateFieldDB(ctx context.Context, indexName, fieldName string, rd io.Reader) error { idx := api.holder.Index(indexName) + if idx == nil { + return fmt.Errorf("index %q not found", indexName) + } field := idx.Field(fieldName) + if field == nil { + // Older versions used to accidentally provide an empty translation + // data file for a nonexistent field called "_keys". To make migration + // easier, we politely ignore that. + if fieldName == "_keys" { + return nil + } + return fmt.Errorf("field %q/%q not found", indexName, fieldName) + } store := field.TranslateStore() + if store == nil { + return fmt.Errorf("field %q/%q has no translate store", indexName, fieldName) + } _, err := store.ReadFrom(rd) return err } diff --git a/api_test.go b/api_test.go index e5989ccb4..cd0cb317f 100644 --- a/api_test.go +++ b/api_test.go @@ -1356,3 +1356,75 @@ func createFieldForTest(index string, field string, coord *test.Command, t *test t.Fatalf("creating field: %v", err) } } + +func TestVariousApiTranslateCalls(t *testing.T) { + for i := 1; i < 8; i += 3 { + m := test.MustRunCluster(t, i) + defer m.Close() + node := m.GetNode(0) + api := node.API + // this should never actually get used because we're testing for errors here + r := strings.NewReader("") + // test index + idx, err := api.Holder().CreateIndex("index", pilosa.IndexOptions{}) + if err != nil { + t.Fatalf("%v: could not create test index", err) + } + _, err = idx.CreateFieldIfNotExistsWithOptions("field", &pilosa.FieldOptions{Keys: false}) + t.Run("translateIndexDbOnNilIndex", + func(t *testing.T) { + err := api.TranslateIndexDB(context.Background(), "nonExistentIndex", 0, r) + expected := fmt.Errorf("index %q not found", "nonExistentIndex") + if !reflect.DeepEqual(err, expected) { + t.Fatalf("expected '%#v', got '%#v'", expected, err) + } + }) + + t.Run("translateIndexDbOnNilTranslateStore", + func(t *testing.T) { + err := api.TranslateIndexDB(context.Background(), "index", 0, r) + expected := fmt.Errorf("index %q has no translate store", "index") + if !reflect.DeepEqual(err, expected) { + t.Fatalf("expected '%#v', got '%#v'", expected, err) + } + }) + + t.Run("translateFieldDbOnNilIndex", + func(t *testing.T) { + err := api.TranslateFieldDB(context.Background(), "nonExistentIndex", "field", r) + expected := fmt.Errorf("index %q not found", "nonExistentIndex") + if !reflect.DeepEqual(err, expected) { + t.Fatalf("expected '%#v', got '%#v'", expected, err) + } + }) + + t.Run("translateFieldDbOnNilField", + func(t *testing.T) { + err := api.TranslateFieldDB(context.Background(), "index", "nonExistentField", r) + expected := fmt.Errorf("field %q/%q not found", "index", "nonExistentField") + if !reflect.DeepEqual(err, expected) { + t.Fatalf("expected '%#v', got '%#v'", expected, err) + } + }) + + t.Run("translateFieldDbNilField_keys", + func(t *testing.T) { + err := api.TranslateFieldDB(context.Background(), "index", "_keys", r) + if err != nil { + t.Fatalf("expected 'nil', got '%#v'", err) + } + }) + /* + TODO: this test will break, bc currently all fields create translate + stores, which is a bug, but one that we will eventually fix. when we do, this + test might come in handy t.Run("translateFieldDbOnNilTranslateStore", + func(t *testing.T) { + err := api.TranslateFieldDB(context.Background(), "index", "field", r) + expected := fmt.Errorf("field %q/%q has no translate store", "index", "field") + if !reflect.DeepEqual(err, expected) { + t.Fatalf("expected '%#v', got '%#v'", expected, err) + } + }) + */ + } +} diff --git a/cluster.go b/cluster.go index 9b161ea41..76910a4bb 100644 --- a/cluster.go +++ b/cluster.go @@ -1480,6 +1480,10 @@ func (c *cluster) matchField(ctx context.Context, field *Field, like string) ([] if c.Node.ID == primary.ID { // The local copy is the authoritative copy. plan := planLike(like) + store := field.TranslateStore() + if store == nil { + return nil, ErrTranslateStoreNotFound + } return field.TranslateStore().Match(func(key []byte) bool { return matchLike(key, plan...) }) @@ -1521,6 +1525,10 @@ func (c *cluster) translateFieldListIDs(field *Field, ids []uint64) (keys []stri } if c.Node.ID == primary.ID { + store := field.TranslateStore() + if store == nil { + return nil, ErrTranslateStoreNotFound + } keys, err = field.TranslateStore().TranslateIDs(ids) } else { keys, err = c.InternalClient.TranslateIDsNode(context.Background(), &primary.URI, field.Index(), field.Name(), ids)