opensearch-sugar 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.agents/skills/diataxis/SKILL.md +142 -0
- data/.agents/skills/diataxis/references/examples.md +420 -0
- data/.agents/skills/diataxis/references/explanation-template.md +96 -0
- data/.agents/skills/diataxis/references/framework.md +400 -0
- data/.agents/skills/diataxis/references/how-to-guide-template.md +105 -0
- data/.agents/skills/diataxis/references/reference-template.md +110 -0
- data/.agents/skills/diataxis/references/tutorial-template.md +101 -0
- data/.agents/skills/diataxis/scripts/generate_index.py +139 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/AGENTS.md +120 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile.opensearch +4 -0
- data/Increase_Coverage.md +311 -0
- data/README.md +143 -0
- data/Rakefile +27 -0
- data/Steepfile +23 -0
- data/adrs/ADR-000-template.md +87 -0
- data/adrs/ADR-001-simpledelegator-for-client.md +138 -0
- data/adrs/ADR-002-facade-pattern-for-index.md +126 -0
- data/adrs/ADR-003-repository-pattern-for-models.md +148 -0
- data/adrs/ADR-004-integration-tests-no-mocking.md +91 -0
- data/adrs/ADR-005-exceptions-over-result-objects.md +107 -0
- data/adrs/ADR-006-ssl-on-by-default.md +95 -0
- data/adrs/ADR-007-selective-sugar-surface.md +118 -0
- data/adrs/ADR-008-integration-test-design.md +178 -0
- data/compose.yml +2 -0
- data/compose_opensearch.yml +31 -0
- data/docs/HOWTO.md +844 -0
- data/docs/REFERENCE.md +725 -0
- data/docs/TUTORIAL.md +327 -0
- data/docs/alias-api-design-notes.md +119 -0
- data/lib/opensearch/sugar/client.rb +300 -0
- data/lib/opensearch/sugar/index/include/utilities.rb +6 -0
- data/lib/opensearch/sugar/index.rb +339 -0
- data/lib/opensearch/sugar/models.rb +209 -0
- data/lib/opensearch/sugar/version.rb +8 -0
- data/lib/opensearch/sugar.rb +61 -0
- data/old_docs/DELEGATED_METHODS_ANALYSIS.md +361 -0
- data/old_docs/EXPLANATION.md +685 -0
- data/old_docs/README.md +155 -0
- data/old_docs/docs/CLI-PROPOSAL.md +257 -0
- data/old_docs/docs/HOWTO.md +798 -0
- data/old_docs/docs/REFERENCE.md +901 -0
- data/old_docs/docs/TUTORIAL.md +493 -0
- data/sig/opensearch/sugar.rbs +162 -0
- metadata +240 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
# Getting Started with OpenSearch::Sugar
|
|
2
|
+
|
|
3
|
+
*(Documentation written by GitHub Copilot, powered by Claude Sonnet 4.5)*
|
|
4
|
+
|
|
5
|
+
This tutorial will guide you through building your first OpenSearch application using OpenSearch::Sugar. By the end, you'll have created a searchable book catalog with custom analyzers and full-text search capabilities.
|
|
6
|
+
|
|
7
|
+
## What You'll Learn
|
|
8
|
+
|
|
9
|
+
- How to connect to OpenSearch
|
|
10
|
+
- How to create and configure an index
|
|
11
|
+
- How to define custom analyzers
|
|
12
|
+
- How to add and query documents
|
|
13
|
+
- How to use text analysis features
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Ruby 3.1 or higher installed
|
|
18
|
+
- Docker (for running OpenSearch locally)
|
|
19
|
+
- Basic Ruby knowledge
|
|
20
|
+
- Familiarity with command line
|
|
21
|
+
|
|
22
|
+
## Step 1: Set Up Your Environment
|
|
23
|
+
|
|
24
|
+
### Install OpenSearch
|
|
25
|
+
|
|
26
|
+
First, let's start an OpenSearch instance using Docker:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker run -d \
|
|
30
|
+
-p 9200:9200 \
|
|
31
|
+
-p 9600:9600 \
|
|
32
|
+
-e "discovery.type=single-node" \
|
|
33
|
+
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyPassword123!" \
|
|
34
|
+
--name opensearch-tutorial \
|
|
35
|
+
opensearchproject/opensearch:latest
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Wait a few seconds for OpenSearch to start, then verify it's running:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -X GET "https://localhost:9200" -ku admin:MyPassword123!
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You should see a JSON response with cluster information.
|
|
45
|
+
|
|
46
|
+
### Install the Gem
|
|
47
|
+
|
|
48
|
+
Create a new Ruby project:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mkdir book-catalog
|
|
52
|
+
cd book-catalog
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Create a `Gemfile`:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
source 'https://rubygems.org'
|
|
59
|
+
|
|
60
|
+
gem 'opensearch-sugar'
|
|
61
|
+
gem 'dotenv' # For managing environment variables
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Install dependencies:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Configure Connection
|
|
71
|
+
|
|
72
|
+
Create a `.env` file with your OpenSearch connection details:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
OPENSEARCH_URL=https://localhost:9200
|
|
76
|
+
OPENSEARCH_USER=admin
|
|
77
|
+
OPENSEARCH_PASSWORD=MyPassword123!
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Step 2: Connect to OpenSearch
|
|
81
|
+
|
|
82
|
+
Create a file called `catalog.rb` and add:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
require 'opensearch/sugar'
|
|
86
|
+
require 'dotenv/load'
|
|
87
|
+
|
|
88
|
+
# Create a client - it will automatically use environment variables
|
|
89
|
+
client = OpenSearch::Sugar.new
|
|
90
|
+
|
|
91
|
+
puts "Connected to OpenSearch!"
|
|
92
|
+
puts "Available indexes: #{client.index_names.join(', ')}"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Run it:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ruby catalog.rb
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You should see a confirmation that you're connected!
|
|
102
|
+
|
|
103
|
+
## Step 3: Create Your First Index
|
|
104
|
+
|
|
105
|
+
Let's create an index for storing books. Add to your `catalog.rb`:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# Create or open the books index
|
|
109
|
+
index = client.open_or_create_index('books')
|
|
110
|
+
|
|
111
|
+
puts "Index 'books' is ready!"
|
|
112
|
+
puts "Document count: #{index.count}"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run it again. The index is now created!
|
|
116
|
+
|
|
117
|
+
## Step 4: Configure Custom Analyzers
|
|
118
|
+
|
|
119
|
+
Books need good text analysis for searching. Let's add a custom analyzer that handles book titles and descriptions well.
|
|
120
|
+
|
|
121
|
+
Add this to your script:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Define custom analyzers for book text
|
|
125
|
+
settings = {
|
|
126
|
+
settings: {
|
|
127
|
+
analysis: {
|
|
128
|
+
# Custom analyzers
|
|
129
|
+
analyzer: {
|
|
130
|
+
book_title_analyzer: {
|
|
131
|
+
type: 'custom',
|
|
132
|
+
tokenizer: 'standard',
|
|
133
|
+
filter: ['lowercase', 'asciifolding', 'book_stop_filter']
|
|
134
|
+
},
|
|
135
|
+
book_search_analyzer: {
|
|
136
|
+
type: 'custom',
|
|
137
|
+
tokenizer: 'standard',
|
|
138
|
+
filter: ['lowercase', 'asciifolding']
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
# Custom stop words filter
|
|
142
|
+
filter: {
|
|
143
|
+
book_stop_filter: {
|
|
144
|
+
type: 'stop',
|
|
145
|
+
stopwords: ['the', 'a', 'an', 'and', 'or', 'but']
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
index.update_settings(settings)
|
|
153
|
+
puts "Settings updated!"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**What's happening here?**
|
|
157
|
+
- `book_title_analyzer` removes common words like "the" and "a"
|
|
158
|
+
- `book_search_analyzer` keeps all words for broader searching
|
|
159
|
+
- Both handle special characters with `asciifolding`
|
|
160
|
+
|
|
161
|
+
## Step 5: Define Field Mappings
|
|
162
|
+
|
|
163
|
+
Now let's define the structure of our book documents:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
mappings = {
|
|
167
|
+
mappings: {
|
|
168
|
+
properties: {
|
|
169
|
+
title: {
|
|
170
|
+
type: 'text',
|
|
171
|
+
analyzer: 'book_title_analyzer',
|
|
172
|
+
search_analyzer: 'book_search_analyzer',
|
|
173
|
+
fields: {
|
|
174
|
+
keyword: { type: 'keyword' } # For exact matching
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
author: {
|
|
178
|
+
type: 'text',
|
|
179
|
+
fields: {
|
|
180
|
+
keyword: { type: 'keyword' }
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
description: {
|
|
184
|
+
type: 'text',
|
|
185
|
+
analyzer: 'book_title_analyzer'
|
|
186
|
+
},
|
|
187
|
+
isbn: {
|
|
188
|
+
type: 'keyword'
|
|
189
|
+
},
|
|
190
|
+
published_date: {
|
|
191
|
+
type: 'date'
|
|
192
|
+
},
|
|
193
|
+
pages: {
|
|
194
|
+
type: 'integer'
|
|
195
|
+
},
|
|
196
|
+
categories: {
|
|
197
|
+
type: 'keyword'
|
|
198
|
+
},
|
|
199
|
+
rating: {
|
|
200
|
+
type: 'float'
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
index.update_mappings(mappings)
|
|
207
|
+
puts "Mappings updated!"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Step 6: Test Your Analyzer
|
|
211
|
+
|
|
212
|
+
Before adding documents, let's verify our analyzer works correctly:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# Test the analyzer
|
|
216
|
+
sample_title = "The Lord of the Rings: The Fellowship of the Ring"
|
|
217
|
+
tokens = index.analyze_text(
|
|
218
|
+
analyzer: 'book_title_analyzer',
|
|
219
|
+
text: sample_title
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
puts "\nOriginal: #{sample_title}"
|
|
223
|
+
puts "Tokens: #{tokens.join(', ')}"
|
|
224
|
+
# Output: lord, rings, fellowship, ring
|
|
225
|
+
# Notice "the" and "of" are removed!
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This shows how OpenSearch will index book titles - common words are removed to improve search relevance.
|
|
229
|
+
|
|
230
|
+
## Step 7: Add Some Books
|
|
231
|
+
|
|
232
|
+
Now let's add books using the raw client (OpenSearch::Sugar delegates all standard client methods):
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# Add some books
|
|
236
|
+
books = [
|
|
237
|
+
{
|
|
238
|
+
title: "The Hobbit",
|
|
239
|
+
author: "J.R.R. Tolkien",
|
|
240
|
+
description: "A fantasy adventure about a hobbit's unexpected journey.",
|
|
241
|
+
isbn: "978-0547928227",
|
|
242
|
+
published_date: "1937-09-21",
|
|
243
|
+
pages: 310,
|
|
244
|
+
categories: ["fantasy", "adventure", "classic"],
|
|
245
|
+
rating: 4.7
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
title: "1984",
|
|
249
|
+
author: "George Orwell",
|
|
250
|
+
description: "A dystopian novel about totalitarianism and surveillance.",
|
|
251
|
+
isbn: "978-0451524935",
|
|
252
|
+
published_date: "1949-06-08",
|
|
253
|
+
pages: 328,
|
|
254
|
+
categories: ["dystopian", "political", "classic"],
|
|
255
|
+
rating: 4.6
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
title: "To Kill a Mockingbird",
|
|
259
|
+
author: "Harper Lee",
|
|
260
|
+
description: "A novel about racial injustice in the American South.",
|
|
261
|
+
isbn: "978-0061120084",
|
|
262
|
+
published_date: "1960-07-11",
|
|
263
|
+
pages: 324,
|
|
264
|
+
categories: ["classic", "legal", "drama"],
|
|
265
|
+
rating: 4.8
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
books.each do |book|
|
|
270
|
+
# Use the raw client for indexing
|
|
271
|
+
client.index(
|
|
272
|
+
index: 'books',
|
|
273
|
+
id: book[:isbn],
|
|
274
|
+
body: book
|
|
275
|
+
)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Refresh to make documents searchable immediately
|
|
279
|
+
index.refresh
|
|
280
|
+
|
|
281
|
+
puts "\nAdded #{books.size} books!"
|
|
282
|
+
puts "Total documents: #{index.count}"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Step 8: Search Your Catalog
|
|
286
|
+
|
|
287
|
+
Now let's search! Add this to test searching:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# Search for books
|
|
291
|
+
search_response = client.search(
|
|
292
|
+
index: 'books',
|
|
293
|
+
body: {
|
|
294
|
+
query: {
|
|
295
|
+
multi_match: {
|
|
296
|
+
query: 'fantasy adventure',
|
|
297
|
+
fields: ['title^2', 'description', 'categories']
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
puts "\nSearch results for 'fantasy adventure':"
|
|
304
|
+
search_response['hits']['hits'].each do |hit|
|
|
305
|
+
book = hit['_source']
|
|
306
|
+
puts " - #{book['title']} by #{book['author']} (score: #{hit['_score']})"
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Step 9: Analyze Search Behavior
|
|
311
|
+
|
|
312
|
+
Let's see how our analyzer affects search results:
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
# Compare analyzers
|
|
316
|
+
title = "The Complete Adventures of Sherlock Holmes"
|
|
317
|
+
|
|
318
|
+
puts "\nAnalyzing: #{title}"
|
|
319
|
+
|
|
320
|
+
# With stop words removed
|
|
321
|
+
tokens_indexed = index.analyze_text(
|
|
322
|
+
analyzer: 'book_title_analyzer',
|
|
323
|
+
text: title
|
|
324
|
+
)
|
|
325
|
+
puts "Indexed tokens (with stop filter): #{tokens_indexed.join(', ')}"
|
|
326
|
+
|
|
327
|
+
# Without stop words removed
|
|
328
|
+
tokens_search = index.analyze_text(
|
|
329
|
+
analyzer: 'book_search_analyzer',
|
|
330
|
+
text: title
|
|
331
|
+
)
|
|
332
|
+
puts "Search tokens (without stop filter): #{tokens_search.join(', ')}"
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Step 10: Create an Alias
|
|
336
|
+
|
|
337
|
+
Create an alias for easy access:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
# Create an alias
|
|
341
|
+
index.create_alias('current_catalog')
|
|
342
|
+
|
|
343
|
+
puts "\nAliases for 'books': #{index.aliases.join(', ')}"
|
|
344
|
+
|
|
345
|
+
# Now you can use the alias
|
|
346
|
+
alias_index = client['current_catalog']
|
|
347
|
+
puts "Documents via alias: #{alias_index.count}"
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## What You've Accomplished
|
|
351
|
+
|
|
352
|
+
Congratulations! You've built a complete searchable book catalog:
|
|
353
|
+
|
|
354
|
+
- ✅ Connected to OpenSearch
|
|
355
|
+
- ✅ Created a custom index with analyzers
|
|
356
|
+
- ✅ Defined field mappings for structured data
|
|
357
|
+
- ✅ Added documents with proper indexing
|
|
358
|
+
- ✅ Performed full-text searches
|
|
359
|
+
- ✅ Analyzed how text processing works
|
|
360
|
+
- ✅ Created index aliases
|
|
361
|
+
|
|
362
|
+
## Complete Script
|
|
363
|
+
|
|
364
|
+
Here's the complete `catalog.rb` for reference:
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
require 'opensearch/sugar'
|
|
368
|
+
require 'dotenv/load'
|
|
369
|
+
|
|
370
|
+
# Connect
|
|
371
|
+
client = OpenSearch::Sugar.new
|
|
372
|
+
index = client.open_or_create_index('books')
|
|
373
|
+
|
|
374
|
+
# Configure settings
|
|
375
|
+
settings = {
|
|
376
|
+
settings: {
|
|
377
|
+
analysis: {
|
|
378
|
+
analyzer: {
|
|
379
|
+
book_title_analyzer: {
|
|
380
|
+
type: 'custom',
|
|
381
|
+
tokenizer: 'standard',
|
|
382
|
+
filter: ['lowercase', 'asciifolding', 'book_stop_filter']
|
|
383
|
+
},
|
|
384
|
+
book_search_analyzer: {
|
|
385
|
+
type: 'custom',
|
|
386
|
+
tokenizer: 'standard',
|
|
387
|
+
filter: ['lowercase', 'asciifolding']
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
filter: {
|
|
391
|
+
book_stop_filter: {
|
|
392
|
+
type: 'stop',
|
|
393
|
+
stopwords: ['the', 'a', 'an', 'and', 'or', 'but']
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
index.update_settings(settings)
|
|
400
|
+
|
|
401
|
+
# Configure mappings
|
|
402
|
+
mappings = {
|
|
403
|
+
mappings: {
|
|
404
|
+
properties: {
|
|
405
|
+
title: {
|
|
406
|
+
type: 'text',
|
|
407
|
+
analyzer: 'book_title_analyzer',
|
|
408
|
+
search_analyzer: 'book_search_analyzer',
|
|
409
|
+
fields: { keyword: { type: 'keyword' } }
|
|
410
|
+
},
|
|
411
|
+
author: { type: 'text', fields: { keyword: { type: 'keyword' } } },
|
|
412
|
+
description: { type: 'text', analyzer: 'book_title_analyzer' },
|
|
413
|
+
isbn: { type: 'keyword' },
|
|
414
|
+
published_date: { type: 'date' },
|
|
415
|
+
pages: { type: 'integer' },
|
|
416
|
+
categories: { type: 'keyword' },
|
|
417
|
+
rating: { type: 'float' }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
index.update_mappings(mappings)
|
|
422
|
+
|
|
423
|
+
# Add books
|
|
424
|
+
books = [
|
|
425
|
+
{
|
|
426
|
+
title: "The Hobbit",
|
|
427
|
+
author: "J.R.R. Tolkien",
|
|
428
|
+
description: "A fantasy adventure about a hobbit's unexpected journey.",
|
|
429
|
+
isbn: "978-0547928227",
|
|
430
|
+
published_date: "1937-09-21",
|
|
431
|
+
pages: 310,
|
|
432
|
+
categories: ["fantasy", "adventure", "classic"],
|
|
433
|
+
rating: 4.7
|
|
434
|
+
}
|
|
435
|
+
# ... add more books
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
books.each do |book|
|
|
439
|
+
client.index(index: 'books', id: book[:isbn], body: book)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
index.refresh
|
|
443
|
+
puts "Created catalog with #{index.count} books!"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Next Steps
|
|
447
|
+
|
|
448
|
+
Now that you understand the basics, explore:
|
|
449
|
+
|
|
450
|
+
- **[How-to Guides](HOWTO.md)** - Solve specific problems
|
|
451
|
+
- **[Reference Documentation](REFERENCE.md)** - Complete API details
|
|
452
|
+
- **[Explanation](EXPLANATION.md)** - Understand design decisions
|
|
453
|
+
- **[OpenSearch Documentation](https://opensearch.org/docs/latest/)** - Deep dive into OpenSearch features
|
|
454
|
+
|
|
455
|
+
## Cleanup
|
|
456
|
+
|
|
457
|
+
When you're done experimenting:
|
|
458
|
+
|
|
459
|
+
```ruby
|
|
460
|
+
# Delete the index
|
|
461
|
+
index.delete!
|
|
462
|
+
|
|
463
|
+
# Stop the Docker container
|
|
464
|
+
# docker stop opensearch-tutorial
|
|
465
|
+
# docker rm opensearch-tutorial
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Troubleshooting
|
|
469
|
+
|
|
470
|
+
**Connection refused?**
|
|
471
|
+
- Make sure OpenSearch is running: `docker ps`
|
|
472
|
+
- Check the URL in your `.env` file
|
|
473
|
+
|
|
474
|
+
**SSL certificate errors?**
|
|
475
|
+
- The default settings disable SSL verification for development
|
|
476
|
+
- For production, enable proper SSL verification
|
|
477
|
+
|
|
478
|
+
**Index already exists?**
|
|
479
|
+
- Delete it first: `client.indices.delete(index: 'books')`
|
|
480
|
+
- Or use `open_or_create_index` instead of `create`
|
|
481
|
+
|
|
482
|
+
**Settings update fails?**
|
|
483
|
+
- Some settings can't be changed on an open index
|
|
484
|
+
- The gem automatically closes/reopens the index for you
|
|
485
|
+
- If it fails, check the error message
|
|
486
|
+
|
|
487
|
+
## Resources
|
|
488
|
+
|
|
489
|
+
- [OpenSearch Index Settings](https://opensearch.org/docs/latest/install-and-configure/configuring-opensearch/index-settings/)
|
|
490
|
+
- [OpenSearch Analyzers](https://opensearch.org/docs/latest/analyzers/)
|
|
491
|
+
- [OpenSearch Mapping](https://opensearch.org/docs/latest/field-types/)
|
|
492
|
+
- [OpenSearch Query DSL](https://opensearch.org/docs/latest/query-dsl/)
|
|
493
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# RBS type signatures for the OpenSearch::Sugar public API.
|
|
2
|
+
# Keep in sync with lib/opensearch/sugar/**/*.rb.
|
|
3
|
+
|
|
4
|
+
module OpenSearch
|
|
5
|
+
module Sugar
|
|
6
|
+
VERSION: String
|
|
7
|
+
|
|
8
|
+
class Error < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Convenience factory — returns a new Client.
|
|
12
|
+
def self.client: (**untyped kwargs) -> Client
|
|
13
|
+
|
|
14
|
+
# Alias for .client.
|
|
15
|
+
def self.new: (**untyped kwargs) -> Client
|
|
16
|
+
|
|
17
|
+
class Client < ::SimpleDelegator
|
|
18
|
+
attr_reader raw_client: untyped
|
|
19
|
+
attr_reader models: Models
|
|
20
|
+
|
|
21
|
+
def self.raw_client: (*untyped args, **untyped kwargs) -> untyped
|
|
22
|
+
|
|
23
|
+
def initialize: (?host: String, **untyped kwargs) -> void
|
|
24
|
+
|
|
25
|
+
def default_args: () -> Hash[Symbol, untyped]
|
|
26
|
+
|
|
27
|
+
# Returns true if the named index exists.
|
|
28
|
+
def has_index?: (String name) -> bool
|
|
29
|
+
|
|
30
|
+
# Returns the names of all indices in the cluster.
|
|
31
|
+
def index_names: () -> Array[String]
|
|
32
|
+
|
|
33
|
+
# Opens the named index and returns an Index object.
|
|
34
|
+
# Raises ArgumentError if the index does not exist.
|
|
35
|
+
def []: (String index_name) -> Index
|
|
36
|
+
|
|
37
|
+
# Opens the named index if it exists; creates it otherwise.
|
|
38
|
+
def open_or_create_index: (String index_name) -> Index
|
|
39
|
+
|
|
40
|
+
# Deletes the named index.
|
|
41
|
+
# Raises if the index does not exist.
|
|
42
|
+
def delete_index!: (String index_name) -> untyped
|
|
43
|
+
|
|
44
|
+
# Applies settings to an index (close → put_settings → open).
|
|
45
|
+
# Raises OpenSearch::Sugar::Error on failure.
|
|
46
|
+
def update_settings: (Hash[untyped, untyped] settings, String index_name) -> untyped
|
|
47
|
+
|
|
48
|
+
# Applies mappings to an index (close → put_mapping → open).
|
|
49
|
+
# Raises OpenSearch::Sugar::Error on failure.
|
|
50
|
+
def update_mappings: (Hash[untyped, untyped] mappings, String index_name) -> untyped
|
|
51
|
+
|
|
52
|
+
# Sets a cluster-level log level.
|
|
53
|
+
def set_log_level: (?logger: String, ?level: String) -> untyped
|
|
54
|
+
|
|
55
|
+
# Tests a custom transient analyzer defined inline by components, without
|
|
56
|
+
# requiring the analyzer to be registered on any index.
|
|
57
|
+
def test_analyzer_by_definition: (text: String, tokenizer: String, ?filter: Array[String | Hash[untyped, untyped]], ?char_filter: Array[String | Hash[untyped, untyped]]) -> Array[String | Array[String]]
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def reopen_index: (String index_name) -> void
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Index
|
|
65
|
+
attr_accessor client: Client
|
|
66
|
+
attr_accessor name: String
|
|
67
|
+
|
|
68
|
+
module Include
|
|
69
|
+
module Utilities
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Opens an existing index. Raises ArgumentError if not found.
|
|
74
|
+
def self.open: (client: Client, name: String) -> Index
|
|
75
|
+
|
|
76
|
+
# Creates a new index. Raises ArgumentError if it already exists.
|
|
77
|
+
def self.create: (client: Client, name: String, ?knn: bool) -> Index
|
|
78
|
+
|
|
79
|
+
def settings: () -> Hash[String, untyped]
|
|
80
|
+
def update_settings: (Hash[untyped, untyped] settings) -> untyped
|
|
81
|
+
|
|
82
|
+
def mappings: () -> Hash[String, untyped]
|
|
83
|
+
def update_mappings: (Hash[untyped, untyped] mappings) -> untyped
|
|
84
|
+
|
|
85
|
+
def delete!: () -> untyped
|
|
86
|
+
|
|
87
|
+
# Forces an index refresh so recently indexed documents are immediately searchable.
|
|
88
|
+
def refresh: () -> untyped
|
|
89
|
+
|
|
90
|
+
def count: () -> Integer
|
|
91
|
+
|
|
92
|
+
def aliases: () -> Array[String]
|
|
93
|
+
def create_alias: (String alias_name) -> Array[String]
|
|
94
|
+
|
|
95
|
+
def all_available_analyzers: () -> Array[String]
|
|
96
|
+
def analyzers: () -> Array[String]
|
|
97
|
+
|
|
98
|
+
# Returns tokens produced by the named analyzer (must be registered on this index).
|
|
99
|
+
# Each element is a String token, or an Array[String] for same-position synonyms.
|
|
100
|
+
def test_analyzer_by_name: (analyzer: String, text: String) -> Array[String | Array[String]]
|
|
101
|
+
|
|
102
|
+
# Deprecated alias for test_analyzer_by_name.
|
|
103
|
+
def analyze_text: (analyzer: String, text: String) -> Array[String | Array[String]]
|
|
104
|
+
|
|
105
|
+
# Same as test_analyzer_by_name but derives the analyzer from the field mapping.
|
|
106
|
+
def test_analyzer_by_fieldname: (field: String, text: String) -> Array[String | Array[String]]
|
|
107
|
+
|
|
108
|
+
# Deprecated alias for test_analyzer_by_fieldname.
|
|
109
|
+
def analyze_text_field: (field: String, text: String) -> Array[String | Array[String]]
|
|
110
|
+
|
|
111
|
+
def delete_by_id: (String id) -> untyped
|
|
112
|
+
def clear!: () -> Integer
|
|
113
|
+
|
|
114
|
+
def index_document: (Hash[untyped, untyped] doc, String id) -> untyped
|
|
115
|
+
|
|
116
|
+
# Indexes all documents from a JSONL file path or IO-like object.
|
|
117
|
+
def index_jsonl_file: (String | _EachLine source, id_field: Symbol | String) -> void
|
|
118
|
+
|
|
119
|
+
interface _EachLine
|
|
120
|
+
def each_line: () { (String) -> void } -> void
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def initialize: (client: Client, name: String) -> void
|
|
126
|
+
def default_index_body: () -> Hash[Symbol, untyped]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class Models
|
|
130
|
+
# Struct holding name, version, and id for a deployed ML model.
|
|
131
|
+
class ML_INFO
|
|
132
|
+
attr_reader name: String
|
|
133
|
+
attr_reader version: String
|
|
134
|
+
attr_reader id: String
|
|
135
|
+
|
|
136
|
+
def initialize: (String name, String version, String id) -> void
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def initialize: (Client os) -> void
|
|
140
|
+
|
|
141
|
+
# Registers and deploys an ML model. Idempotent — returns existing if found.
|
|
142
|
+
def register: (name: String, version: String, ?format: String) -> ML_INFO?
|
|
143
|
+
|
|
144
|
+
alias deploy register
|
|
145
|
+
|
|
146
|
+
# Looks up a model by exact name, exact id, or case-insensitive partial name.
|
|
147
|
+
def []: (String id_or_fullname_or_nickname) -> ML_INFO?
|
|
148
|
+
|
|
149
|
+
def list: () -> Array[ML_INFO]
|
|
150
|
+
def raw_list: () -> untyped
|
|
151
|
+
|
|
152
|
+
def undeploy!: (String name_or_id) -> untyped
|
|
153
|
+
def delete!: (String name_or_id) -> untyped
|
|
154
|
+
|
|
155
|
+
# Creates an ingest pipeline for text-embedding using the named model.
|
|
156
|
+
def create_pipeline: (name: String, model: String, description: String, field_map: Hash[String, String]) -> untyped
|
|
157
|
+
|
|
158
|
+
# Deletes an ingest pipeline by name.
|
|
159
|
+
def delete_pipeline!: (String pipeline_name) -> untyped
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|