noiseless 0.0.0 → 0.1.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 +4 -4
- data/LICENSE.txt +28 -0
- data/README.md +214 -0
- data/lib/application_search.rb +15 -0
- data/lib/noiseless/adapter.rb +313 -0
- data/lib/noiseless/adapters/elasticsearch.rb +70 -0
- data/lib/noiseless/adapters/execution_modules/elasticsearch_execution.rb +188 -0
- data/lib/noiseless/adapters/execution_modules/opensearch_execution.rb +377 -0
- data/lib/noiseless/adapters/execution_modules/pgvector_support.rb +219 -0
- data/lib/noiseless/adapters/execution_modules/postgresql_execution.rb +461 -0
- data/lib/noiseless/adapters/execution_modules/typesense_execution.rb +472 -0
- data/lib/noiseless/adapters/open_search.rb +208 -0
- data/lib/noiseless/adapters/postgresql.rb +171 -0
- data/lib/noiseless/adapters/typesense.rb +70 -0
- data/lib/noiseless/adapters.rb +14 -0
- data/lib/noiseless/ast/aggregation.rb +56 -0
- data/lib/noiseless/ast/bool.rb +16 -0
- data/lib/noiseless/ast/bulk.rb +18 -0
- data/lib/noiseless/ast/collapse.rb +16 -0
- data/lib/noiseless/ast/combined_fields.rb +33 -0
- data/lib/noiseless/ast/conversation.rb +29 -0
- data/lib/noiseless/ast/filter.rb +15 -0
- data/lib/noiseless/ast/hybrid.rb +35 -0
- data/lib/noiseless/ast/image_query.rb +29 -0
- data/lib/noiseless/ast/join.rb +31 -0
- data/lib/noiseless/ast/match.rb +15 -0
- data/lib/noiseless/ast/multi_match.rb +24 -0
- data/lib/noiseless/ast/paginate.rb +15 -0
- data/lib/noiseless/ast/prefix.rb +15 -0
- data/lib/noiseless/ast/range.rb +18 -0
- data/lib/noiseless/ast/root.rb +69 -0
- data/lib/noiseless/ast/search_after.rb +14 -0
- data/lib/noiseless/ast/sort.rb +15 -0
- data/lib/noiseless/ast/vector.rb +27 -0
- data/lib/noiseless/ast/wildcard.rb +15 -0
- data/lib/noiseless/ast.rb +30 -0
- data/lib/noiseless/bulk_importer.rb +195 -0
- data/lib/noiseless/callbacks.rb +138 -0
- data/lib/noiseless/connection_manager.rb +26 -0
- data/lib/noiseless/document_manager.rb +137 -0
- data/lib/noiseless/dsl.rb +107 -0
- data/lib/noiseless/generators/application_search_generator.rb +24 -0
- data/lib/noiseless/instrumentation.rb +174 -0
- data/lib/noiseless/introspection/console.rb +228 -0
- data/lib/noiseless/introspection/query_visualizer.rb +533 -0
- data/lib/noiseless/introspection.rb +221 -0
- data/lib/noiseless/mapping.rb +253 -0
- data/lib/noiseless/mapping_definition_processor.rb +231 -0
- data/lib/noiseless/model.rb +111 -0
- data/lib/noiseless/model_registry.rb +77 -0
- data/lib/noiseless/multi_search.rb +244 -0
- data/lib/noiseless/pagination.rb +375 -0
- data/lib/noiseless/query_builder.rb +284 -0
- data/lib/noiseless/railtie.rb +35 -0
- data/lib/noiseless/response/aggregations.rb +46 -0
- data/lib/noiseless/response/empty.rb +20 -0
- data/lib/noiseless/response/records.rb +94 -0
- data/lib/noiseless/response/results.rb +110 -0
- data/lib/noiseless/response/suggestions.rb +55 -0
- data/lib/noiseless/response.rb +98 -0
- data/lib/noiseless/response_factory.rb +32 -0
- data/lib/noiseless/runtime_reset_middleware.rb +15 -0
- data/lib/noiseless/search_index_update_job.rb +84 -0
- data/lib/noiseless/test_case.rb +230 -0
- data/lib/noiseless/test_helper.rb +295 -0
- data/lib/noiseless/version.rb +2 -2
- data/lib/noiseless.rb +130 -2
- data/lib/tasks/benchmark.rake +35 -0
- data/lib/tasks/release.rake +22 -0
- data/lib/tasks/test.rake +11 -0
- metadata +260 -14
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Noiseless
|
|
6
|
+
module Adapters
|
|
7
|
+
module ExecutionModules
|
|
8
|
+
module ElasticsearchExecution
|
|
9
|
+
def close
|
|
10
|
+
@clients&.each_value(&:close)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def execute_search(query_hash, indexes: [], **_opts)
|
|
16
|
+
path = indexes.any? ? "/#{indexes.join(',')}/_search" : "/_search"
|
|
17
|
+
body = JSON.generate(query_hash)
|
|
18
|
+
|
|
19
|
+
response = post_request(path, body)
|
|
20
|
+
JSON.parse(response.read)
|
|
21
|
+
ensure
|
|
22
|
+
response&.close
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute_bulk(actions, **_opts)
|
|
26
|
+
body = "#{actions.map { |action| JSON.generate(action) }.join("\n")}\n"
|
|
27
|
+
|
|
28
|
+
response = post_request("/_bulk", body, content_type: "application/x-ndjson")
|
|
29
|
+
JSON.parse(response.read)
|
|
30
|
+
ensure
|
|
31
|
+
response&.close
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def execute_create_index(index_name, mappings: nil, settings: nil, **_opts)
|
|
35
|
+
body = {}
|
|
36
|
+
body[:mappings] = mappings if mappings
|
|
37
|
+
body[:settings] = settings if settings
|
|
38
|
+
|
|
39
|
+
response = put_request("/#{index_name}", body.any? ? JSON.generate(body) : nil)
|
|
40
|
+
JSON.parse(response.read)
|
|
41
|
+
ensure
|
|
42
|
+
response&.close
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def execute_delete_index(index_name, **_opts)
|
|
46
|
+
response = delete_request("/#{index_name}")
|
|
47
|
+
JSON.parse(response.read)
|
|
48
|
+
ensure
|
|
49
|
+
response&.close
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def execute_refresh_index(index_name)
|
|
53
|
+
response = post_request("/#{index_name}/_refresh", nil)
|
|
54
|
+
JSON.parse(response.read)
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
{
|
|
57
|
+
"_shards" => {
|
|
58
|
+
"total" => 0,
|
|
59
|
+
"successful" => 0,
|
|
60
|
+
"failed" => 0
|
|
61
|
+
},
|
|
62
|
+
"error" => {
|
|
63
|
+
"type" => e.class.name,
|
|
64
|
+
"reason" => e.message
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
ensure
|
|
68
|
+
response&.close
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def execute_index_exists?(index_name)
|
|
72
|
+
response = head_request("/#{index_name}")
|
|
73
|
+
response.success?
|
|
74
|
+
rescue StandardError
|
|
75
|
+
false
|
|
76
|
+
ensure
|
|
77
|
+
response&.close
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def execute_index_document(index, id, document, **_opts)
|
|
81
|
+
path = id ? "/#{index}/_doc/#{id}" : "/#{index}/_doc"
|
|
82
|
+
body = JSON.generate(document)
|
|
83
|
+
|
|
84
|
+
response = id ? put_request(path, body) : post_request(path, body)
|
|
85
|
+
JSON.parse(response.read)
|
|
86
|
+
ensure
|
|
87
|
+
response&.close
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def execute_update_document(index, id, changes, **_opts)
|
|
91
|
+
body = JSON.generate(doc: changes)
|
|
92
|
+
|
|
93
|
+
response = post_request("/#{index}/_update/#{id}", body)
|
|
94
|
+
JSON.parse(response.read)
|
|
95
|
+
ensure
|
|
96
|
+
response&.close
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def execute_delete_document(index, id, **_opts)
|
|
100
|
+
response = delete_request("/#{index}/_doc/#{id}")
|
|
101
|
+
JSON.parse(response.read)
|
|
102
|
+
ensure
|
|
103
|
+
response&.close
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def execute_document_exists?(index, id)
|
|
107
|
+
response = head_request("/#{index}/_doc/#{id}")
|
|
108
|
+
response.success?
|
|
109
|
+
rescue StandardError
|
|
110
|
+
false
|
|
111
|
+
ensure
|
|
112
|
+
response&.close
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def execute_cluster_health(**_opts)
|
|
116
|
+
response = get_request("/_cluster/health")
|
|
117
|
+
JSON.parse(response.read)
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
{
|
|
120
|
+
"cluster_name" => "unknown",
|
|
121
|
+
"status" => "red",
|
|
122
|
+
"timed_out" => false,
|
|
123
|
+
"number_of_nodes" => 0,
|
|
124
|
+
"number_of_data_nodes" => 0,
|
|
125
|
+
"active_primary_shards" => 0,
|
|
126
|
+
"active_shards" => 0,
|
|
127
|
+
"error" => {
|
|
128
|
+
"type" => e.class.name,
|
|
129
|
+
"reason" => e.message
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
ensure
|
|
133
|
+
response&.close
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# HTTP helpers using Async::HTTP with connection pooling
|
|
137
|
+
def get_request(path)
|
|
138
|
+
with_client do |client|
|
|
139
|
+
client.get(path, default_headers)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def post_request(path, body, content_type: "application/json")
|
|
144
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
145
|
+
|
|
146
|
+
with_client do |client|
|
|
147
|
+
client.post(path, headers, body)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def put_request(path, body, content_type: "application/json")
|
|
152
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
153
|
+
|
|
154
|
+
with_client do |client|
|
|
155
|
+
client.put(path, headers, body)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def delete_request(path)
|
|
160
|
+
with_client do |client|
|
|
161
|
+
client.delete(path, default_headers)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def head_request(path)
|
|
166
|
+
with_client do |client|
|
|
167
|
+
client.head(path, default_headers)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def with_client
|
|
172
|
+
# Select a random host for load balancing
|
|
173
|
+
host = @hosts.sample
|
|
174
|
+
client = @clients[host]
|
|
175
|
+
|
|
176
|
+
yield(client)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def default_headers
|
|
180
|
+
[
|
|
181
|
+
["accept", "application/json"],
|
|
182
|
+
["user-agent", "Noiseless/#{Noiseless::VERSION} (Ruby/#{RUBY_VERSION})"]
|
|
183
|
+
]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Noiseless
|
|
6
|
+
module Adapters
|
|
7
|
+
module ExecutionModules
|
|
8
|
+
module OpensearchExecution
|
|
9
|
+
def close
|
|
10
|
+
@clients&.each_value(&:close)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def execute_search(query_hash, indexes: [], **_opts)
|
|
16
|
+
index_path = indexes.any? ? indexes.join(",") : "_all"
|
|
17
|
+
path = "/#{index_path}/_search"
|
|
18
|
+
body = JSON.generate(query_hash)
|
|
19
|
+
|
|
20
|
+
response = post_request(path, body)
|
|
21
|
+
JSON.parse(response.read)
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
# Return empty response on error to maintain compatibility
|
|
24
|
+
{
|
|
25
|
+
took: 0,
|
|
26
|
+
timed_out: false,
|
|
27
|
+
_shards: { total: 0, successful: 0, skipped: 0, failed: 0 },
|
|
28
|
+
hits: {
|
|
29
|
+
total: { value: 0, relation: "eq" },
|
|
30
|
+
max_score: nil,
|
|
31
|
+
hits: []
|
|
32
|
+
},
|
|
33
|
+
error: {
|
|
34
|
+
type: e.class.name,
|
|
35
|
+
reason: e.message
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
ensure
|
|
39
|
+
response&.close
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def execute_bulk(actions, **_opts)
|
|
43
|
+
# Build bulk request body
|
|
44
|
+
bulk_body = actions.map do |action|
|
|
45
|
+
if action[:index]
|
|
46
|
+
action_line = { index: { _index: action[:index][:_index], _id: action[:index][:_id] } }
|
|
47
|
+
data_line = action[:index][:data]
|
|
48
|
+
"#{JSON.generate(action_line)}\n#{JSON.generate(data_line)}\n"
|
|
49
|
+
else
|
|
50
|
+
"#{JSON.generate(action)}\n"
|
|
51
|
+
end
|
|
52
|
+
end.join
|
|
53
|
+
|
|
54
|
+
response = post_request("/_bulk", bulk_body, content_type: "application/x-ndjson")
|
|
55
|
+
JSON.parse(response.read)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
{ items: [], errors: true, error: { type: e.class.name, reason: e.message } }
|
|
58
|
+
ensure
|
|
59
|
+
response&.close
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def execute_create_index(index_name, mappings: nil, settings: nil, **opts)
|
|
63
|
+
body = opts.dup
|
|
64
|
+
body[:mappings] = mappings if mappings
|
|
65
|
+
body[:settings] = settings if settings
|
|
66
|
+
|
|
67
|
+
response = put_request("/#{index_name}", body.any? ? JSON.generate(body) : nil)
|
|
68
|
+
JSON.parse(response.read)
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
71
|
+
ensure
|
|
72
|
+
response&.close
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def execute_delete_index(index_name, **_opts)
|
|
76
|
+
response = delete_request("/#{index_name}")
|
|
77
|
+
JSON.parse(response.read)
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
80
|
+
ensure
|
|
81
|
+
response&.close
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def execute_refresh_index(index_name)
|
|
85
|
+
response = post_request("/#{index_name}/_refresh", nil)
|
|
86
|
+
JSON.parse(response.read)
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
{
|
|
89
|
+
"_shards" => {
|
|
90
|
+
"total" => 0,
|
|
91
|
+
"successful" => 0,
|
|
92
|
+
"failed" => 0
|
|
93
|
+
},
|
|
94
|
+
"error" => {
|
|
95
|
+
"type" => e.class.name,
|
|
96
|
+
"reason" => e.message
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
ensure
|
|
100
|
+
response&.close
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def execute_index_exists?(index_name)
|
|
104
|
+
response = head_request("/#{index_name}")
|
|
105
|
+
response.success?
|
|
106
|
+
rescue StandardError
|
|
107
|
+
false
|
|
108
|
+
ensure
|
|
109
|
+
response&.close
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def execute_index_document(index, id, document, **_opts)
|
|
113
|
+
path = "/#{index}/_doc/#{id}"
|
|
114
|
+
body = JSON.generate(document)
|
|
115
|
+
|
|
116
|
+
response = put_request(path, body)
|
|
117
|
+
JSON.parse(response.read)
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
{ _index: index, _id: id, result: "error", error: { type: e.class.name, reason: e.message } }
|
|
120
|
+
ensure
|
|
121
|
+
response&.close
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def execute_update_document(index, id, changes, **_opts)
|
|
125
|
+
body = JSON.generate(doc: changes)
|
|
126
|
+
|
|
127
|
+
response = post_request("/#{index}/_update/#{id}", body)
|
|
128
|
+
JSON.parse(response.read)
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
{ _index: index, _id: id, result: "error", error: { type: e.class.name, reason: e.message } }
|
|
131
|
+
ensure
|
|
132
|
+
response&.close
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def execute_delete_document(index, id, **_opts)
|
|
136
|
+
response = delete_request("/#{index}/_doc/#{id}")
|
|
137
|
+
JSON.parse(response.read)
|
|
138
|
+
rescue StandardError => e
|
|
139
|
+
{ _index: index, _id: id, result: "error", error: { type: e.class.name, reason: e.message } }
|
|
140
|
+
ensure
|
|
141
|
+
response&.close
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def execute_document_exists?(index, id)
|
|
145
|
+
response = head_request("/#{index}/_doc/#{id}")
|
|
146
|
+
response.success?
|
|
147
|
+
rescue StandardError
|
|
148
|
+
false
|
|
149
|
+
ensure
|
|
150
|
+
response&.close
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def execute_cluster_health(**_opts)
|
|
154
|
+
response = get_request("/_cluster/health")
|
|
155
|
+
JSON.parse(response.read)
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
{
|
|
158
|
+
cluster_name: "unknown",
|
|
159
|
+
status: "red",
|
|
160
|
+
timed_out: false,
|
|
161
|
+
number_of_nodes: 0,
|
|
162
|
+
number_of_data_nodes: 0,
|
|
163
|
+
active_primary_shards: 0,
|
|
164
|
+
active_shards: 0,
|
|
165
|
+
relocating_shards: 0,
|
|
166
|
+
initializing_shards: 0,
|
|
167
|
+
unassigned_shards: 0,
|
|
168
|
+
error: { type: e.class.name, reason: e.message }
|
|
169
|
+
}
|
|
170
|
+
ensure
|
|
171
|
+
response&.close
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# OpenSearch-specific features
|
|
175
|
+
def execute_point_in_time_search(query_hash, pit_id:, **_opts)
|
|
176
|
+
# Point-in-time search for consistent pagination
|
|
177
|
+
enhanced_query = query_hash.merge(pit: { id: pit_id })
|
|
178
|
+
body = JSON.generate(enhanced_query)
|
|
179
|
+
|
|
180
|
+
response = post_request("/_search", body)
|
|
181
|
+
JSON.parse(response.read)
|
|
182
|
+
rescue StandardError => e
|
|
183
|
+
{
|
|
184
|
+
pit_id: pit_id,
|
|
185
|
+
error: { type: e.class.name, reason: e.message },
|
|
186
|
+
hits: { total: { value: 0 }, hits: [] }
|
|
187
|
+
}
|
|
188
|
+
ensure
|
|
189
|
+
response&.close
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def execute_search_template(template_id:, params: {}, **_opts)
|
|
193
|
+
# OpenSearch search templates
|
|
194
|
+
template_query = {
|
|
195
|
+
id: template_id,
|
|
196
|
+
params: params
|
|
197
|
+
}
|
|
198
|
+
body = JSON.generate(template_query)
|
|
199
|
+
|
|
200
|
+
response = post_request("/_search/template", body)
|
|
201
|
+
JSON.parse(response.read)
|
|
202
|
+
rescue StandardError => e
|
|
203
|
+
{
|
|
204
|
+
error: { type: e.class.name, reason: e.message },
|
|
205
|
+
hits: { total: { value: 0 }, hits: [] }
|
|
206
|
+
}
|
|
207
|
+
ensure
|
|
208
|
+
response&.close
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# ============================================
|
|
212
|
+
# Search Pipeline API (OpenSearch 3.x)
|
|
213
|
+
# ============================================
|
|
214
|
+
|
|
215
|
+
def execute_create_pipeline(name, request_processors:, response_processors:, description: nil)
|
|
216
|
+
body = {
|
|
217
|
+
description: description,
|
|
218
|
+
request_processors: request_processors,
|
|
219
|
+
response_processors: response_processors
|
|
220
|
+
}.compact
|
|
221
|
+
|
|
222
|
+
response = put_request("/_search/pipeline/#{name}", JSON.generate(body))
|
|
223
|
+
JSON.parse(response.read)
|
|
224
|
+
rescue StandardError => e
|
|
225
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
226
|
+
ensure
|
|
227
|
+
response&.close
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def execute_get_pipeline(name)
|
|
231
|
+
response = get_request("/_search/pipeline/#{name}")
|
|
232
|
+
JSON.parse(response.read)
|
|
233
|
+
rescue StandardError => e
|
|
234
|
+
{ error: { type: e.class.name, reason: e.message } }
|
|
235
|
+
ensure
|
|
236
|
+
response&.close
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def execute_list_pipelines
|
|
240
|
+
response = get_request("/_search/pipeline")
|
|
241
|
+
JSON.parse(response.read)
|
|
242
|
+
rescue StandardError => e
|
|
243
|
+
{ error: { type: e.class.name, reason: e.message } }
|
|
244
|
+
ensure
|
|
245
|
+
response&.close
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def execute_delete_pipeline(name)
|
|
249
|
+
response = delete_request("/_search/pipeline/#{name}")
|
|
250
|
+
JSON.parse(response.read)
|
|
251
|
+
rescue StandardError => e
|
|
252
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
253
|
+
ensure
|
|
254
|
+
response&.close
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def execute_pipeline_exists?(name)
|
|
258
|
+
response = head_request("/_search/pipeline/#{name}")
|
|
259
|
+
response.success?
|
|
260
|
+
rescue StandardError
|
|
261
|
+
false
|
|
262
|
+
ensure
|
|
263
|
+
response&.close
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# ============================================
|
|
267
|
+
# Query Rules API (OpenSearch 3.x)
|
|
268
|
+
# ============================================
|
|
269
|
+
|
|
270
|
+
def execute_create_rule(feature_type, rule_id, attributes:, feature_value:)
|
|
271
|
+
body = {
|
|
272
|
+
match_criteria: {
|
|
273
|
+
query: attributes
|
|
274
|
+
},
|
|
275
|
+
feature_value: feature_value
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
response = put_request("/_rules/#{feature_type}/#{rule_id}", JSON.generate(body))
|
|
279
|
+
JSON.parse(response.read)
|
|
280
|
+
rescue StandardError => e
|
|
281
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
282
|
+
ensure
|
|
283
|
+
response&.close
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def execute_get_rule(feature_type, rule_id)
|
|
287
|
+
response = get_request("/_rules/#{feature_type}/#{rule_id}")
|
|
288
|
+
JSON.parse(response.read)
|
|
289
|
+
rescue StandardError => e
|
|
290
|
+
{ error: { type: e.class.name, reason: e.message } }
|
|
291
|
+
ensure
|
|
292
|
+
response&.close
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def execute_list_rules(feature_type, search_after: nil)
|
|
296
|
+
path = "/_rules/#{feature_type}"
|
|
297
|
+
path += "?search_after=#{search_after}" if search_after
|
|
298
|
+
|
|
299
|
+
response = get_request(path)
|
|
300
|
+
JSON.parse(response.read)
|
|
301
|
+
rescue StandardError => e
|
|
302
|
+
{ rules: [], error: { type: e.class.name, reason: e.message } }
|
|
303
|
+
ensure
|
|
304
|
+
response&.close
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def execute_delete_rule(feature_type, rule_id)
|
|
308
|
+
response = delete_request("/_rules/#{feature_type}/#{rule_id}")
|
|
309
|
+
JSON.parse(response.read)
|
|
310
|
+
rescue StandardError => e
|
|
311
|
+
{ acknowledged: false, error: { type: e.class.name, reason: e.message } }
|
|
312
|
+
ensure
|
|
313
|
+
response&.close
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def execute_rule_exists?(feature_type, rule_id)
|
|
317
|
+
response = head_request("/_rules/#{feature_type}/#{rule_id}")
|
|
318
|
+
response.success?
|
|
319
|
+
rescue StandardError
|
|
320
|
+
false
|
|
321
|
+
ensure
|
|
322
|
+
response&.close
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# HTTP helpers using Async::HTTP with connection pooling
|
|
326
|
+
def get_request(path)
|
|
327
|
+
with_client do |client|
|
|
328
|
+
client.get(path, default_headers)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def post_request(path, body, content_type: "application/json")
|
|
333
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
334
|
+
|
|
335
|
+
with_client do |client|
|
|
336
|
+
client.post(path, headers, body)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def put_request(path, body, content_type: "application/json")
|
|
341
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
342
|
+
|
|
343
|
+
with_client do |client|
|
|
344
|
+
client.put(path, headers, body)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def delete_request(path)
|
|
349
|
+
with_client do |client|
|
|
350
|
+
client.delete(path, default_headers)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def head_request(path)
|
|
355
|
+
with_client do |client|
|
|
356
|
+
client.head(path, default_headers)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def with_client
|
|
361
|
+
# Select a random host for load balancing
|
|
362
|
+
host = @hosts.sample
|
|
363
|
+
client = @clients[host]
|
|
364
|
+
|
|
365
|
+
yield(client)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def default_headers
|
|
369
|
+
[
|
|
370
|
+
["accept", "application/json"],
|
|
371
|
+
["user-agent", "Noiseless/#{Noiseless::VERSION} (Ruby/#{RUBY_VERSION})"]
|
|
372
|
+
]
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|