Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bin/couchbaseorm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def usage
couchbaseorm index:migrate
couchbaseorm index:rollback
couchbaseorm index:status
couchbaseorm index:cleanup
couchbaseorm index:schema:dump
couchbaseorm index:schema:load
couchbaseorm index:dump
Expand Down Expand Up @@ -47,6 +48,14 @@ when 'index:rollback'
CouchbaseOrm::IndexMigrator.rollback
when 'index:status'
CouchbaseOrm::IndexMigrator.status
when 'index:cleanup'
removed = CouchbaseOrm::IndexMigrator.cleanup
if removed.any?
puts 'Removed indexes:'
removed.each { |name| puts name }
else
puts 'No secondary indexes found'
end
when 'index:schema:dump'
path = CouchbaseOrm::IndexSchema::Dumper.new.dump
puts path
Expand Down
11 changes: 11 additions & 0 deletions lib/couchbase-orm/index_migrator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def migrate(**options)
new(**options).migrate
end

def cleanup(**options)
new(**options).cleanup
end

def rollback(**options)
new(**options).rollback
end
Expand Down Expand Up @@ -65,5 +69,12 @@ def adopt
@schema_migration.add_version(migration_def.version)
migration_def.version
end

def cleanup
names = IndexMigration::IndexIntrospector.new.indexes.map { |row| row[:name] }.sort
migration = IndexMigration.new
names.each { |name| migration.remove_index(name) }
names
end
end
end
46 changes: 46 additions & 0 deletions spec/index_migrator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
let(:schema_migration) { instance_double(CouchbaseOrm::IndexSchemaMigration) }
let(:out) { StringIO.new }

describe '.cleanup' do
it 'delegates to instance cleanup' do
migrator = instance_double(described_class)
allow(described_class).to receive(:new).and_return(migrator)
allow(migrator).to receive(:cleanup).and_return(['date_on_type'])

result = described_class.cleanup

expect(result).to eq(['date_on_type'])
end
end

context 'when applying and rolling back migrations' do
let(:migration_class) { Class.new { def migrate(_direction); end } }
let(:migration_def) do
Expand Down Expand Up @@ -89,4 +101,38 @@

expect(adopted_version).to be_nil
end

describe '#cleanup' do
it 'drops all introspected non-primary indexes and returns sorted names' do
introspector = instance_double(CouchbaseOrm::IndexMigration::IndexIntrospector)
allow(CouchbaseOrm::IndexMigration::IndexIntrospector).to receive(:new).and_return(introspector)
allow(introspector).to receive(:indexes).and_return([
{ name: 'type_company' },
{ name: 'date_on_type' }
])

migration = instance_double(CouchbaseOrm::IndexMigration)
allow(CouchbaseOrm::IndexMigration).to receive(:new).and_return(migration)
expect(migration).to receive(:remove_index).with('date_on_type').ordered
expect(migration).to receive(:remove_index).with('type_company').ordered

result = described_class.new(context: context, schema_migration: schema_migration, out: out).cleanup

expect(result).to eq(%w[date_on_type type_company])
end

it 'returns empty array when no secondary indexes are found' do
introspector = instance_double(CouchbaseOrm::IndexMigration::IndexIntrospector)
allow(CouchbaseOrm::IndexMigration::IndexIntrospector).to receive(:new).and_return(introspector)
allow(introspector).to receive(:indexes).and_return([])

migration = instance_double(CouchbaseOrm::IndexMigration)
allow(CouchbaseOrm::IndexMigration).to receive(:new).and_return(migration)
expect(migration).not_to receive(:remove_index)

result = described_class.new(context: context, schema_migration: schema_migration, out: out).cleanup

expect(result).to eq([])
end
end
end
139 changes: 139 additions & 0 deletions specs/007-add-index-cleanup/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Implementation Plan: Add Index Cleanup Command

## Goal

Add a CLI command to remove all secondary indexes from the configured bucket:

```bash
bundle exec couchbaseorm index:cleanup
```

The command must:

* delete all non-primary indexes;
* keep primary index untouched;
* operate only on the configured bucket;
* return deterministic output for scripting.

---

## Phase 1 — Add Cleanup API to IndexMigrator

Add class method:

```ruby
CouchbaseOrm::IndexMigrator.cleanup
```

and instance method:

```ruby
IndexMigrator#cleanup
```

`cleanup` should:

1. introspect indexes through `IndexIntrospector`;
2. extract index names;
3. sort names;
4. drop each index;
5. return the sorted names.

---

## Phase 2 — Reuse Existing Remove Path

Implement dropping through existing migration execution flow, not ad-hoc queries.

Expected behavior:

```ruby
migration = CouchbaseOrm::IndexMigration.new
migration.remove_index(index_name)
```

This keeps SQL generation and execution consistent with other operations.

---

## Phase 3 — Add CLI Command in bin/couchbaseorm

Update usage output to include:

```text
couchbaseorm index:cleanup
```

Add command branch:

```ruby
when 'index:cleanup'
removed = CouchbaseOrm::IndexMigrator.cleanup
```

Output:

* if any removed:

```text
Removed indexes:
<name1>
<name2>
```

* if none:

```text
No secondary indexes found
```

---

## Phase 4 — Error Behavior

Preserve existing errors:

* missing bucket config raises the same configuration error from introspection/query builder;
* failures while dropping an index are not swallowed.

No retries or partial-failure handling in v1.

---

## Phase 5 — Unit Tests for Migrator

Add tests for:

* `IndexMigrator.cleanup` delegates to instance method;
* cleanup drops all introspected indexes;
* cleanup returns sorted index names;
* cleanup returns `[]` when there are no secondary indexes.

Use doubles for introspector and migration execution where practical.

---

## Phase 6 — CLI Tests

Add tests for `bin/couchbaseorm` behavior:

* `index:cleanup` calls `CouchbaseOrm::IndexMigrator.cleanup`;
* command prints removed index names when present;
* command prints `No secondary indexes found` when empty;
* usage text includes `index:cleanup`.

---

## V1 Complete

Supported:

* cleanup command for all secondary indexes in configured bucket;
* deterministic deletion order;
* clear CLI output for non-empty and empty cases.

Not supported:

* dry-run mode;
* confirmation prompt;
* selective cleanup filters.
144 changes: 144 additions & 0 deletions specs/007-add-index-cleanup/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Spec: Add Index Cleanup Command

## Motivation

In development and test environments, index state can drift from migration files.

Common situations:

* manually created indexes;
* stale indexes from previous experiments;
* failed migration attempts leaving partial state.

Current commands support migrate, rollback, dump, and adopt, but there is no single command to reset all secondary indexes in one step.

CouchbaseORM should provide an explicit cleanup command to delete all non-primary indexes from the configured bucket.

---

# Goals

* Add a CLI command in bin/couchbaseorm to remove all non-primary indexes.
* Keep primary index untouched.
* Operate only on the configured bucket.
* Reuse existing query/introspection building blocks.
* Provide deterministic output for automation scripts.

---

# Non-Goals

* Removing primary indexes.
* Creating or rebuilding indexes.
* Comparing with migrations or schema files.
* Automatic confirmation prompts.

---

# Command

Introduce:

```bash
bundle exec couchbaseorm index:cleanup
```

Update usage output in:

```text
bin/couchbaseorm
```

to include:

```text
couchbaseorm index:cleanup
```

---

# Behavior

`index:cleanup` performs:

1. Introspect indexes from `system:indexes` for the configured bucket.
2. Ignore primary indexes.
3. Drop each remaining index.
4. Return/print cleaned index names in sorted order.

If no secondary indexes are found, the command is a no-op and returns an empty result.

---

# Internal API

Introduce:

```ruby
CouchbaseOrm::IndexMigrator.cleanup
```

and instance method:

```ruby
IndexMigrator#cleanup
```

Implementation outline:

```ruby
introspected = CouchbaseOrm::IndexMigration::IndexIntrospector.new.indexes
names = introspected.map { |row| row[:name] }.sort

names.each do |name|
migration = CouchbaseOrm::IndexMigration.new
migration.remove_index(name)
end

names
```

Notes:

* `IndexIntrospector` already filters out primary indexes.
* `remove_index` uses the existing query builder and execution path.

---

# CLI Output

When indexes are removed:

```text
Removed indexes:
date_on_type
type_company
```

When there is nothing to remove:

```text
No secondary indexes found
```

---

# Error Handling

* If index bucket configuration is missing, raise the existing bucket configuration error.
* If dropping one index fails, propagate the exception and stop execution.

This keeps behavior consistent with other index commands.

---

# Tests

Add coverage for:

* `IndexMigrator.cleanup` delegates to instance cleanup.
* cleanup drops all introspected non-primary indexes.
* cleanup returns sorted deleted names.
* cleanup returns empty array when no indexes are found.
* CLI command `index:cleanup` triggers cleanup and prints expected output.
* CLI usage text includes `index:cleanup`.
Loading