From 63c9a9d9b020ad1ffd7763228f10f986cb570704 Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 08:18:27 +0000 Subject: [PATCH 1/6] add support index naming outside conventional --- docs/reference/index-migrations.txt | 19 ++++++++++++ lib/couchbase-orm/index_definition.rb | 15 +++++++++- lib/couchbase-orm/index_schema.rb | 4 +-- lib/couchbase-orm/index_schema/dumper.rb | 10 +++---- lib/couchbase-orm/index_schema/loader.rb | 2 +- spec/index_definition_spec.rb | 6 ++++ spec/index_schema_definition_spec.rb | 12 ++++++++ spec/index_schema_dumper_spec.rb | 13 +++++++++ spec/index_schema_loader_spec.rb | 29 +++++++++++++++++++ .../spec.md | 13 +++++++++ 10 files changed, 114 insertions(+), 9 deletions(-) diff --git a/docs/reference/index-migrations.txt b/docs/reference/index-migrations.txt index 087331ee..6cc9ce92 100644 --- a/docs/reference/index-migrations.txt +++ b/docs/reference/index-migrations.txt @@ -133,6 +133,19 @@ Deferred index example: defer_build: true ) +Index names can be provided as symbols or strings. + +Use strings for non-conventional names (for example names containing ``-``): + +.. code-block:: ruby + + add_index( + 'type-company', + keys: [:type, :company_id] + ) + +The same rule applies to ``remove_index`` and ``rename_index``. + Example generated query: .. code-block:: sql @@ -188,6 +201,12 @@ Polling runs every second and does not apply a timeout. remove_index :type_company +For names containing ``-``, pass a string: + +.. code-block:: ruby + + remove_index 'type-company' + Example generated query: .. code-block:: sql diff --git a/lib/couchbase-orm/index_definition.rb b/lib/couchbase-orm/index_definition.rb index 36f0bb69..9c28cd4e 100644 --- a/lib/couchbase-orm/index_definition.rb +++ b/lib/couchbase-orm/index_definition.rb @@ -4,6 +4,8 @@ module CouchbaseOrm class IndexDefinition attr_reader :name, :keys, :where, :defer_build, :num_replica + NAME_SYMBOL_PATTERN = /\A[a-zA-Z_][a-zA-Z0-9_]*\z/ + def initialize(name:, keys:, where: nil, defer_build: false, num_replica: nil) @name = normalize_name(name) @keys = normalize_keys(keys) @@ -27,7 +29,18 @@ def <=>(other) private def normalize_name(name) - name.to_sym + self.class.normalize_name(name) + end + + class << self + def normalize_name(name) + return name if name.is_a?(Symbol) + + value = name.to_s.strip + return value.to_sym if value.match?(NAME_SYMBOL_PATTERN) + + value + end end def normalize_keys(raw_keys) diff --git a/lib/couchbase-orm/index_schema.rb b/lib/couchbase-orm/index_schema.rb index 7dbae5d5..765da8fd 100644 --- a/lib/couchbase-orm/index_schema.rb +++ b/lib/couchbase-orm/index_schema.rb @@ -22,11 +22,11 @@ def add_index(name, keys:, where: nil, num_replica: nil, defer_build: false) end def remove_index(name) - @indexes.delete(name.to_sym) + @indexes.delete(CouchbaseOrm::IndexDefinition.normalize_name(name)) end def rename_index(old_name, new_name) - index_definition = @indexes.delete(old_name.to_sym) + index_definition = @indexes.delete(CouchbaseOrm::IndexDefinition.normalize_name(old_name)) return unless index_definition renamed_index = CouchbaseOrm::IndexDefinition.new( diff --git a/lib/couchbase-orm/index_schema/dumper.rb b/lib/couchbase-orm/index_schema/dumper.rb index 1e918793..2bd6d48b 100644 --- a/lib/couchbase-orm/index_schema/dumper.rb +++ b/lib/couchbase-orm/index_schema/dumper.rb @@ -29,7 +29,7 @@ def source_for(indexes, version: nil) lines = [] lines << definition_header(version) - index_names = indexes.keys.sort + index_names = indexes.keys.sort_by(&:to_s) index_names.each_with_index do |name, index| lines.concat(index_lines(indexes[name])) lines << '' unless index == index_names.length - 1 @@ -70,13 +70,13 @@ def define_add_index(klass, indexes) def define_remove_index(klass, indexes) klass.define_method(:remove_index) do |name| - indexes.delete(name.to_sym) + indexes.delete(CouchbaseOrm::IndexDefinition.normalize_name(name)) end end def define_rename_index(klass, indexes) klass.define_method(:rename_index) do |old_name, new_name| - index_definition = indexes.delete(old_name.to_sym) + index_definition = indexes.delete(CouchbaseOrm::IndexDefinition.normalize_name(old_name)) next unless index_definition renamed_index = CouchbaseOrm::IndexDefinition.new( @@ -105,7 +105,7 @@ def definition_header(version) end def index_lines(index_definition) - lines = [" add_index :#{index_definition.name},"] + lines = [" add_index #{ruby_value(index_definition.name)},"] option_lines = ["keys: #{ruby_array(index_definition.keys)}"] option_lines << "where: #{index_definition.where.inspect}" if index_definition.where @@ -125,7 +125,7 @@ def ruby_array(values) end def ruby_value(value) - value.is_a?(Symbol) ? ":#{value}" : value.inspect + value.inspect end end end diff --git a/lib/couchbase-orm/index_schema/loader.rb b/lib/couchbase-orm/index_schema/loader.rb index c4b6a73e..352c0569 100644 --- a/lib/couchbase-orm/index_schema/loader.rb +++ b/lib/couchbase-orm/index_schema/loader.rb @@ -40,7 +40,7 @@ def apply_definition(definition) create_operation_class = @migration_class::Operations::CreateIndex build_operation_class = @migration_class::Operations::BuildIndexes - definition.keys.sort.each do |name| + definition.keys.sort_by(&:to_s).each do |name| index_definition = definition[name] create_operation_class.new(index_definition).execute(migration) deferred_indexes << name if index_definition.defer_build diff --git a/spec/index_definition_spec.rb b/spec/index_definition_spec.rb index 09b9ce71..b87cd86a 100644 --- a/spec/index_definition_spec.rb +++ b/spec/index_definition_spec.rb @@ -21,6 +21,12 @@ expect(definition.keys).to eq(['LOWER(`name`)']) end + it 'keeps non-conventional names as strings' do + definition = described_class.new(name: 'type-company', keys: [:type]) + + expect(definition.name).to eq('type-company') + end + it 'tracks defer_build and num_replica attributes' do definition = described_class.new( name: :type_company, diff --git a/spec/index_schema_definition_spec.rb b/spec/index_schema_definition_spec.rb index a6339c99..aca2707c 100644 --- a/spec/index_schema_definition_spec.rb +++ b/spec/index_schema_definition_spec.rb @@ -17,4 +17,16 @@ expect(indexes[:date_on_type_v2].where).to eq('type is valued') expect(indexes[:date_on_type_v2].defer_build).to be(true) end + + it 'supports renaming and removing non-conventional names' do + indexes = described_class.define do + add_index('date-on-type', keys: [:date]) + rename_index('date-on-type', 'date-on-type-v2') + remove_index('date-on-type-v2') + add_index('date-on-type-v3', keys: [:date]) + end + + expect(indexes.keys).to eq(['date-on-type-v3']) + expect(indexes['date-on-type-v3']).to be_a(CouchbaseOrm::IndexDefinition) + end end diff --git a/spec/index_schema_dumper_spec.rb b/spec/index_schema_dumper_spec.rb index a515602d..48ef7a70 100644 --- a/spec/index_schema_dumper_spec.rb +++ b/spec/index_schema_dumper_spec.rb @@ -27,6 +27,19 @@ end end + it 'dumps non-conventional names as valid ruby values' do + indexes = { + 'type-company' => CouchbaseOrm::IndexDefinition.new(name: 'type-company', keys: [:type]), + :date_on_type => CouchbaseOrm::IndexDefinition.new(name: :date_on_type, keys: [:date]) + } + + source = described_class.new.source_for(indexes, version: 20260101120000) + + expect(source).to include('add_index :date_on_type,') + expect(source).to include('add_index "type-company",') + expect { RubyVM::InstructionSequence.compile(source) }.not_to raise_error + end + def write_migrations(migrations_path) FileUtils.mkdir_p(migrations_path) diff --git a/spec/index_schema_loader_spec.rb b/spec/index_schema_loader_spec.rb index cd1c31fd..14929b2e 100644 --- a/spec/index_schema_loader_spec.rb +++ b/spec/index_schema_loader_spec.rb @@ -53,6 +53,35 @@ end end + it 'loads schema file containing non-conventional index names' do + schema_with_hyphen_name = <<~RUBY + CouchbaseOrm::IndexSchema.define(version: 20260101120001) do + add_index "type-company", + keys: [:type], + defer_build: true + end + RUBY + + Dir.mktmpdir do |dir| + schema_path = File.join(dir, 'index_schema.rb') + File.write(schema_path, schema_with_hyphen_name) + + cluster = instance_double(Couchbase::Cluster) + allow(CouchbaseOrm::Connection).to receive(:cluster).and_return(cluster) + + expect(cluster).to receive(:query).with(/CREATE INDEX `type-company`/, instance_of(Couchbase::Options::Query)).ordered + expect(cluster).to receive(:query).with(<<~SQL.strip, instance_of(Couchbase::Options::Query)).ordered + BUILD INDEX ON `fleet-prod` + ( + `type-company` + ); + SQL + + version = described_class.new(path: schema_path).load + expect(version).to eq(20260101120001) + end + end + def expect_schema_load_queries(cluster) expect(cluster).to receive(:query).with(/CREATE INDEX `date_on_type`/, instance_of(Couchbase::Options::Query)).ordered expect(cluster).to receive(:query).with(/CREATE INDEX `type_company`/, instance_of(Couchbase::Options::Query)).ordered diff --git a/specs/005-add-index-schema-dump-and-load/spec.md b/specs/005-add-index-schema-dump-and-load/spec.md index ccb12878..8905361b 100644 --- a/specs/005-add-index-schema-dump-and-load/spec.md +++ b/specs/005-add-index-schema-dump-and-load/spec.md @@ -149,6 +149,16 @@ The DSL supports: add_index ``` +Index names can be symbols or strings. +For non-conventional names (for example with a hyphen `-`), use strings: + +```ruby +add_index "type-company", + keys: [:type, :company_id] +``` + +The same applies to `remove_index` and `rename_index`. + --- # Schema Dump @@ -192,6 +202,9 @@ Generated schema: CouchbaseOrm::IndexSchema.define(version: 2026_01_01_120000) do add_index :type_company, keys: [:type, :company_id] + + add_index "type-company", + keys: [:type] end ``` From e0809ff4f39a860470fba8a6d3a66a2ef130307f Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 10:24:35 +0200 Subject: [PATCH 2/6] fix couchbaseorm bin --- bin/couchbaseorm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/couchbaseorm b/bin/couchbaseorm index 49903b55..7bbbe547 100755 --- a/bin/couchbaseorm +++ b/bin/couchbaseorm @@ -64,7 +64,7 @@ when 'index:schema:load' when 'index:dump' name = parse_name_argument(ARGV) || CouchbaseOrm::IndexMigration::MigrationGenerator::DEFAULT_NAME introspected = CouchbaseOrm::IndexMigration::IndexIntrospector.new.indexes - definitions = introspected.map { |index_data| CouchbaseOrm::IndexMigration::IndexDefinition.from_introspected(index_data) } + definitions = introspected.map { |index_data| CouchbaseOrm::IndexDefinition.from_introspected(index_data) } path = CouchbaseOrm::IndexMigration::MigrationGenerator.new.generate(definitions, name: name) puts path when 'index:adopt' From ec87a298fe039f974e65b09d4b2df55abc9e9ab0 Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 10:29:36 +0200 Subject: [PATCH 3/6] fix crash compare symbol with string --- lib/couchbase-orm/index_definition.rb | 2 +- lib/couchbase-orm/index_migration/migration_generator.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/couchbase-orm/index_definition.rb b/lib/couchbase-orm/index_definition.rb index 9c28cd4e..5966ff61 100644 --- a/lib/couchbase-orm/index_definition.rb +++ b/lib/couchbase-orm/index_definition.rb @@ -23,7 +23,7 @@ def self.from_introspected(index_data) end def <=>(other) - name <=> other.name + name.to_s <=> other.name.to_s end private diff --git a/lib/couchbase-orm/index_migration/migration_generator.rb b/lib/couchbase-orm/index_migration/migration_generator.rb index fa48dd62..988bc43f 100644 --- a/lib/couchbase-orm/index_migration/migration_generator.rb +++ b/lib/couchbase-orm/index_migration/migration_generator.rb @@ -25,7 +25,7 @@ def generate(index_definitions, name: DEFAULT_NAME) end def source_for(index_definitions, class_name: DEFAULT_NAME) - definitions = Array(index_definitions).sort_by(&:name) + definitions = Array(index_definitions).sort_by { |d| d.name.to_s } <<~RUBY class #{migration_class_name(class_name)} < CouchbaseOrm::IndexMigration From 3298eed18774101c8b81be0cb296804b8a8388a5 Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 10:32:04 +0200 Subject: [PATCH 4/6] fix naming --- lib/couchbase-orm/index_definition.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/couchbase-orm/index_definition.rb b/lib/couchbase-orm/index_definition.rb index 5966ff61..d87b19d7 100644 --- a/lib/couchbase-orm/index_definition.rb +++ b/lib/couchbase-orm/index_definition.rb @@ -34,8 +34,6 @@ def normalize_name(name) class << self def normalize_name(name) - return name if name.is_a?(Symbol) - value = name.to_s.strip return value.to_sym if value.match?(NAME_SYMBOL_PATTERN) From 26dfdfbb13863f00ddfe4a04ca97edbac36f605c Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 10:34:35 +0200 Subject: [PATCH 5/6] fix naming --- lib/couchbase-orm/index_migration/migration_generator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/couchbase-orm/index_migration/migration_generator.rb b/lib/couchbase-orm/index_migration/migration_generator.rb index 988bc43f..34ca3a35 100644 --- a/lib/couchbase-orm/index_migration/migration_generator.rb +++ b/lib/couchbase-orm/index_migration/migration_generator.rb @@ -66,14 +66,14 @@ def up_body(definitions) end def down_body(definitions) - lines = definitions.reverse.map { |definition| "remove_index :#{definition.name}" } + lines = definitions.reverse.map { |definition| "remove_index #{ruby_value(definition.name)}" } indent(lines) end def add_index_lines(definition) lines = [ 'add_index(', - " :#{definition.name},", + " #{ruby_value(definition.name)},", " keys: #{ruby_array(definition.keys)}," ] lines << " where: #{definition.where.inspect}," if definition.where @@ -86,7 +86,7 @@ def build_indexes_lines(definitions) lines = ['build_indexes('] definitions.each_with_index do |definition, index| suffix = index == definitions.length - 1 ? '' : ',' - lines << " :#{definition.name}#{suffix}" + lines << " #{ruby_value(definition.name)}#{suffix}" end lines << ')' lines From ebf5e2bf594ba78361fa8fba342b4cbeb2352e6d Mon Sep 17 00:00:00 2001 From: Giallombardo Nathan Date: Fri, 5 Jun 2026 10:37:05 +0200 Subject: [PATCH 6/6] fix query naming key --- lib/couchbase-orm/index_migration/query_builder.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/couchbase-orm/index_migration/query_builder.rb b/lib/couchbase-orm/index_migration/query_builder.rb index d3e77259..72507580 100644 --- a/lib/couchbase-orm/index_migration/query_builder.rb +++ b/lib/couchbase-orm/index_migration/query_builder.rb @@ -15,7 +15,7 @@ def create_index(index_definition) raise ArgumentError.new('Missing index keys configuration') if Array(index_definition.keys).empty? query = +"CREATE INDEX `#{index_definition.name}`\n" - query << "ON `#{bucket}`(#{Array(index_definition.keys).map { |key| "`#{key}`" }.join(',')})" + query << "ON `#{bucket}`(#{Array(index_definition.keys).map { |key| format_key(key) }.join(',')})" query << "\nWHERE (#{index_definition.where})" if index_definition.where options = with_options(defer_build: index_definition.defer_build, num_replica: index_definition.num_replica) query << "\nWITH #{JSON.pretty_generate(options)}" unless options.empty? @@ -52,6 +52,10 @@ def states_query(bucket, index_names) private + def format_key(key) + key.is_a?(Symbol) ? "`#{key}`" : key.to_s + end + def with_options(defer_build:, num_replica:) options = {} options['defer_build'] = true if defer_build