feat(14-01): beachcomber DropIndex and EnsureIndex for reindex tooling

- optional IndexDropper reports whether a dropped index existed; DropIndex falls back to Flush
- optional IndexEnsurer creates an empty index from its schema; EnsureIndex is a no-op otherwise
- typesense implements both (404 is already absent; ensure reuses collection creation)
This commit is contained in:
Jakub Zych
2026-10-03 20:01:36 +02:00
parent 7241704e93
commit 58e6324da8
6 changed files with 214 additions and 1 deletions

View File

@@ -78,3 +78,70 @@ func TestSearchPageNullEngine(t *testing.T) {
t.Fatalf("res = %+v err = %v", res, err)
}
}
// flushEngine records Flush calls and can fail them.
type flushEngine struct {
idsOnlyEngine
flushed []string
err error
}
func (e *flushEngine) Flush(_ context.Context, index string) error {
e.flushed = append(e.flushed, index)
return e.err
}
// dropperEngine implements IndexDropper and IndexEnsurer.
type dropperEngine struct {
flushEngine
existing map[string]bool
ensured []string
}
func (e *dropperEngine) DropIndex(_ context.Context, index string) (bool, error) {
existed := e.existing[index]
delete(e.existing, index)
return existed, nil
}
func (e *dropperEngine) EnsureIndex(_ context.Context, index string, _ map[string]any) error {
e.ensured = append(e.ensured, index)
e.existing[index] = true
return nil
}
func TestDropIndex(t *testing.T) {
ctx := context.Background()
fallback := &flushEngine{}
existed, err := DropIndex(ctx, fallback, "albums")
if err != nil || !existed || !reflect.DeepEqual(fallback.flushed, []string{"albums"}) {
t.Fatalf("fallback: existed=%v err=%v flushed=%v", existed, err, fallback.flushed)
}
boom := errors.New("boom")
failing := &flushEngine{err: boom}
if existed, err := DropIndex(ctx, failing, "albums"); !errors.Is(err, boom) || existed {
t.Fatalf("fallback error: existed=%v err=%v", existed, err)
}
d := &dropperEngine{existing: map[string]bool{"albums": true}}
if existed, err := DropIndex(ctx, d, "albums"); err != nil || !existed {
t.Fatalf("dropper first: existed=%v err=%v", existed, err)
}
if existed, err := DropIndex(ctx, d, "albums"); err != nil || existed {
t.Fatalf("dropper second: existed=%v err=%v, want already absent", existed, err)
}
if len(d.flushed) != 0 {
t.Fatalf("dropper path must not call Flush: %v", d.flushed)
}
if err := EnsureIndex(ctx, d, "albums", map[string]any{"fields": []any{}}); err != nil || !reflect.DeepEqual(d.ensured, []string{"albums"}) {
t.Fatalf("EnsureIndex: err=%v ensured=%v", err, d.ensured)
}
if err := EnsureIndex(ctx, fallback, "albums", nil); err != nil {
t.Fatalf("EnsureIndex without IndexEnsurer must be a no-op: %v", err)
}
if err := EnsureIndex(ctx, nullEngine{}, "albums", nil); err != nil {
t.Fatalf("null engine: %v", err)
}
}