Class: OpenSearch::Sugar::Index

Inherits:
Object
  • Object
show all
Defined in:
lib/opensearch/sugar/index.rb,
sig/opensearch/sugar.rbs

Overview

Represents a single OpenSearch index and provides methods for CRUD operations on documents, settings, mappings, aliases, and text analysis.

Instances are obtained via Client#[] or the class-level factory methods Index.open and Index.create — do not call new directly.

Examples:

Open an existing index and search

index = client["products"]
index.count #=> 1500

Create a new index and add a document

index = OpenSearch::Sugar::Index.create(client: client, name: "events", knn: false)
index.index_document({ title: "Launch" }, "evt-001")

Defined Under Namespace

Modules: Include, _EachLine

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client:, name:) ⇒ Index

Returns a new instance of Index.

Parameters:

  • client: (Client)
  • name: (String)


322
323
324
325
326
327
# File 'lib/opensearch/sugar/index.rb', line 322

def initialize(client:, name:)
  @client = client
  @name = name
  # client.indices.create(index: name, body: default_index_body)
  # client[name]
end

Instance Attribute Details

#clientOpenSearch::Sugar::Client

The Client this index belongs to.



23
24
25
# File 'lib/opensearch/sugar/index.rb', line 23

def client
  @client
end

#nameString

The name of this index.

Returns:

  • (String)


27
28
29
# File 'lib/opensearch/sugar/index.rb', line 27

def name
  @name
end

Class Method Details

.create(client:, name:, knn: true) ⇒ Index

Creates a new index. Raises ArgumentError if it already exists.

Parameters:

  • client: (Client)
  • name: (String)
  • knn: (Boolean) (defaults to: true)

Returns:



54
55
56
57
58
# File 'lib/opensearch/sugar/index.rb', line 54

def self.create(client:, name:, knn: true)
  raise ArgumentError.new("Index #{name} already exists") if client.has_index?(name)
  client.indices.create(index: name, body: {settings: {index: {knn: knn}}})
  new(client: client, name: name)
end

.open(client:, name:) ⇒ Index

Opens an existing index. Raises ArgumentError if not found.

Parameters:

  • client: (Client)
  • name: (String)

Returns:



37
38
39
40
# File 'lib/opensearch/sugar/index.rb', line 37

def self.open(client:, name:)
  raise ArgumentError, "Index #{name} not found" unless client.has_index?(name)
  new(client: client, name: name)
end

Instance Method Details

#aliasesArray<String>

Get a (potentially empty) list of aliases of this index

Examples:

index.aliases #=> ["products_v1", "products_current"]

Returns:

  • (Array<String>)

    The aliases for this index



144
145
146
147
# File 'lib/opensearch/sugar/index.rb', line 144

def aliases
  response = client.indices.get_alias(index: name)
  response.dig(name, "aliases")&.keys || []
end

#all_available_analyzersArray<String> Also known as: analyzers

Get a list of all named analyzers available in this index for use when indexing Include those defined at the cluster level as well as those defined for this particular index

Returns:

  • (Array<String>)

    List of analyzer names available for this index



165
166
167
168
169
170
171
# File 'lib/opensearch/sugar/index.rb', line 165

def all_available_analyzers
  settings_response = settings
  index_analyzers = settings_response.dig(name, "settings", "index", "analysis", "analyzer")&.keys || []
  cluster_analyzers = client.cluster.get_settings.dig("persistent", "index", "analysis", "analyzer")&.keys || []

  (index_analyzers + cluster_analyzers).uniq
end

#clear!Integer

Delete all documents from this index by executing a delete_by_query with match_all query.

Examples:

index = client["my_index"]
deleted_count = index.clear! # Deletes all documents and returns count

Returns:

  • (Integer)

    The number of documents that were deleted



263
264
265
266
267
268
269
270
271
272
273
# File 'lib/opensearch/sugar/index.rb', line 263

def clear!
  response = client.delete_by_query(
    index: name,
    body: {
      query: {
        match_all: {}
      }
    }
  )
  response["deleted"].to_i
end

#countInteger

Returns the number of documents in this index.

Examples:

index.count #=> 42

Returns:

  • (Integer)

    Document count



135
136
137
138
# File 'lib/opensearch/sugar/index.rb', line 135

def count
  response = client.count(index: name)
  response["count"].to_i
end

#create_alias(alias_name) ⇒ Array<String>

Create an alias for this index with the given name

Examples:

index.create_alias("products_current")
#=> ["products_current"]

Parameters:

  • alias_name (String)

    the new alias for the index

Returns:

  • (Array<String>)

    the complete list of aliases for this index after adding the new one

Raises:

  • (OpenSearch::Transport::Transport::Errors::BadRequest)

    If the alias already exists on another index



156
157
158
159
# File 'lib/opensearch/sugar/index.rb', line 156

def create_alias(alias_name)
  client.indices.put_alias(index: name, name: alias_name)
  aliases
end

#default_index_bodyHash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


329
330
331
332
333
334
335
336
337
# File 'lib/opensearch/sugar/index.rb', line 329

def default_index_body
  {
    settings: {
      index: {
        number_of_shards: 2
      }
    }
  }
end

#delete!Hash

Permanently deletes this index from the cluster.

Examples:

index.delete!

Returns:

  • (Hash)

    The OpenSearch acknowledgement response

Raises:

  • (OpenSearch::Transport::Transport::Errors::NotFound)

    If the index does not exist



118
119
120
# File 'lib/opensearch/sugar/index.rb', line 118

def delete!
  client.indices.delete(index: name)
end

#delete_by_id(id) ⇒ Hash

Deletes the document with the given ID from this index

Parameters:

  • id (String)

    The ID of the document to delete

Returns:

  • (Hash)

    The response from OpenSearch containing deletion status

Raises:

  • (ArgumentError)

    If the document ID is nil or empty



253
254
255
256
# File 'lib/opensearch/sugar/index.rb', line 253

def delete_by_id(id)
  raise ArgumentError, "Document ID cannot be nil or empty" if id.nil? || id.empty?
  client.delete(index: name, id: id)
end

#index_document(doc, id) ⇒ Hash

TODO:

Replace with a bulk-API implementation for large-scale use.

Index a single document into this index.

This method is intentionally simple and inefficient — it issues one HTTP request per document. For bulk loading, use the raw client.bulk API instead.

Parameters:

  • doc (Hash)

    The document body to index

  • id (String)

    The document ID (_id in OpenSearch)

Returns:

  • (Hash)

    The OpenSearch response



285
286
287
288
# File 'lib/opensearch/sugar/index.rb', line 285

def index_document(doc, id)
  # TODO: inefficient for large-scale use; implement bulk upload API
  client.index(index: name, id: id, body: doc)
end

#index_jsonl_file(source, id_field:) ⇒ void

This method returns an undefined value.

Indexes all documents from a JSONL file path or IO-like object.

Parameters:

  • source (String, _EachLine)
  • id_field: (Symbol, String)


308
309
310
311
312
313
314
315
316
317
318
# File 'lib/opensearch/sugar/index.rb', line 308

def index_jsonl_file(source, id_field:)
  # TODO: inefficient for large-scale use; implement bulk upload API
  io = source.is_a?(String) ? File.open(source) : source
  io.each_line do |line|
    doc = JSON.parse(line, symbolize_names: true)
    id = doc.fetch(id_field.to_sym) {
      raise ArgumentError, "id_field :#{id_field} not found in document: #{line.chomp}"
    }
    index_document(doc, id.to_s)
  end
end

#mappingsHash

Returns the current field mappings for this index.

Examples:

index.mappings
#=> { "products" => { "mappings" => { "properties" => { "title" => { "type" => "text" } } } } }

Returns:

  • (Hash)

    The OpenSearch mappings response, keyed by index name



108
109
110
# File 'lib/opensearch/sugar/index.rb', line 108

def mappings
  client.indices.get_mapping(index: name)
end

#refreshObject

Forces an index refresh so recently indexed documents are immediately searchable.

Returns:

  • (Object)


126
127
128
# File 'lib/opensearch/sugar/index.rb', line 126

def refresh
  client.indices.refresh(index: name)
end

#settingsHash

Returns the current settings for this index.

Examples:

index.settings
#=> { "products" => { "settings" => { "index" => { "number_of_shards" => "1", ... } } } }

Returns:

  • (Hash)

    The OpenSearch settings response, keyed by index name



82
83
84
# File 'lib/opensearch/sugar/index.rb', line 82

def settings
  client.indices.get_settings(index: name)
end

#test_analyzer_by_fieldname(field:, text:) ⇒ Array[String | Array[String]] Also known as: analyze_text_field

Same as test_analyzer_by_name but derives the analyzer from the field mapping.

Parameters:

  • field: (String)
  • text: (String)

Returns:

  • (Array[String | Array[String]])


235
236
237
238
239
240
241
242
243
244
# File 'lib/opensearch/sugar/index.rb', line 235

def test_analyzer_by_fieldname(field:, text:)
  mappings_response = mappings
  field_mapping = mappings_response.dig(name, "mappings", "properties", field)
  raise ArgumentError, "Field '#{field}' does not exist in index '#{name}'" unless field_mapping

  analyzer = field_mapping["analyzer"]
  raise ArgumentError, "No analyzer specified for field '#{field}'" unless analyzer

  test_analyzer_by_name(analyzer: analyzer, text: text)
end

#test_analyzer_by_name(analyzer:, text:) ⇒ Array[String | Array[String]] Also known as: analyze_text

Returns tokens produced by the named analyzer (must be registered on this index). Each element is a String token, or an Array for same-position synonyms.

Parameters:

  • analyzer: (String)
  • text: (String)

Returns:

  • (Array[String | Array[String]])


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/opensearch/sugar/index.rb', line 190

def test_analyzer_by_name(analyzer:, text:)
  # Check if analyzer exists in index settings
  settings_response = settings
  unless settings_response.dig(name, "settings", "index", "analysis", "analyzer", analyzer)
    raise ArgumentError, "Analyzer '#{analyzer}' does not exist in index '#{name}'"
  end

  # Analyze the text
  response = client.indices.analyze(
    index: name,
    body: {
      analyzer: analyzer,
      text: text
    }
  )

  # Process tokens from response, grouping same-position tokens as arrays
  tokens = response["tokens"]
  tokens.each_with_index.map do |token, i|
    if i > 0 && token["position"] == tokens[i - 1]["position"]
      [token["token"]]
    else
      token["token"]
    end
  end
end

#update_mappings(mappings) ⇒ Hash

Applies new field mappings to this index.

Delegates to Client#update_mappings, which closes the index, applies the mappings, then reopens it.

Examples:

index.update_mappings(
  mappings: { properties: { title: { type: "text" }, price: { type: "float" } } }
)

Parameters:

  • mappings (Hash)

    Mappings hash, with or without a top-level :mappings key

Returns:

  • (Hash)

    The OpenSearch response

Raises:



98
99
100
# File 'lib/opensearch/sugar/index.rb', line 98

def update_mappings(mappings)
  client.update_mappings(mappings, name)
end

#update_settings(settings) ⇒ Hash

Applies new settings to this index.

Delegates to Client#update_settings, which closes the index, applies the settings, then reopens it.

Examples:

index.update_settings(
  settings: { analysis: { analyzer: { my_analyzer: { type: "standard" } } } }
)

Parameters:

  • settings (Hash)

    Settings hash, with or without a top-level :settings key

Returns:

  • (Hash)

    The OpenSearch response

Raises:



72
73
74
# File 'lib/opensearch/sugar/index.rb', line 72

def update_settings(settings)
  client.update_settings(settings, name)
end