noiseless 0.1.0 → 0.2.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/lib/noiseless/adapter.rb +26 -0
- data/lib/noiseless/adapters/cluster_api.rb +18 -0
- data/lib/noiseless/adapters/elasticsearch.rb +5 -45
- data/lib/noiseless/adapters/execution_modules/elasticsearch_execution.rb +5 -125
- data/lib/noiseless/adapters/execution_modules/es_compatible_execution.rb +83 -0
- data/lib/noiseless/adapters/execution_modules/http_transport.rb +83 -0
- data/lib/noiseless/adapters/execution_modules/opensearch_execution.rb +7 -175
- data/lib/noiseless/adapters/execution_modules/typesense_execution.rb +3 -50
- data/lib/noiseless/adapters/indices_api.rb +26 -0
- data/lib/noiseless/adapters/open_search.rb +8 -48
- data/lib/noiseless/adapters/typesense.rb +8 -42
- data/lib/noiseless/ast/field_value_node.rb +16 -0
- data/lib/noiseless/ast/filter.rb +1 -8
- data/lib/noiseless/ast/match.rb +1 -8
- data/lib/noiseless/ast/prefix.rb +1 -8
- data/lib/noiseless/ast/wildcard.rb +1 -8
- data/lib/noiseless/version.rb +1 -1
- data/lib/noiseless.rb +17 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fb5d9afa49be27d359d92a2fdf24999d488f8a7d865547452ea10b7aae67850
|
|
4
|
+
data.tar.gz: da2fb4b847d7c1b53bb17b7b0a588d2a1660006cbe9413af0b657282087ad681
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32b345fcc0ac56e25765f628c1ab8dd7dd03740e9a1a12bbcf5e13e9ce5078ef9ad57581f2b0a0f445f4d4b543559e01ceb50dd1a0dae86e7db5d35440b8e3c8
|
|
7
|
+
data.tar.gz: da9518b7381e61a16d7efef22e2d8d585b895b2e644820099403781c86ee9c7b541cf2ee00bad6cac7c40830764fd30aa841dd685dd9c63cb4ed227a1ed7ce8a
|
data/lib/noiseless/adapter.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "async"
|
|
4
|
+
require "json"
|
|
4
5
|
require_relative "introspection"
|
|
5
6
|
|
|
6
7
|
module Noiseless
|
|
@@ -208,6 +209,31 @@ module Noiseless
|
|
|
208
209
|
}
|
|
209
210
|
end
|
|
210
211
|
|
|
212
|
+
# Parses a backend HTTP response, raising when the backend reported an error.
|
|
213
|
+
# Success responses return the parsed JSON payload; error responses raise
|
|
214
|
+
# `error_class` with the backend's error type/reason so failures are never
|
|
215
|
+
# silently converted into empty results.
|
|
216
|
+
def parse_json_response!(response, error_class: Noiseless::RequestError, context: nil)
|
|
217
|
+
body = response.read
|
|
218
|
+
return JSON.parse(body) if response.success?
|
|
219
|
+
|
|
220
|
+
payload = begin
|
|
221
|
+
JSON.parse(body)
|
|
222
|
+
rescue JSON::ParserError, TypeError
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
error = payload.is_a?(Hash) ? payload["error"] : nil
|
|
226
|
+
reason = if error.is_a?(Hash)
|
|
227
|
+
[ error["type"], error["reason"] ].compact.join(": ")
|
|
228
|
+
elsif error
|
|
229
|
+
error.to_s
|
|
230
|
+
else
|
|
231
|
+
"HTTP #{response.status}"
|
|
232
|
+
end
|
|
233
|
+
message = context ? "#{context}: #{reason}" : reason
|
|
234
|
+
raise error_class.new(message, status: response.status, error_type: error.is_a?(Hash) ? error["type"] : nil)
|
|
235
|
+
end
|
|
236
|
+
|
|
211
237
|
# Override in subclasses
|
|
212
238
|
def execute_search(_query_hash, **_opts)
|
|
213
239
|
{
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Noiseless
|
|
4
|
+
module Adapters
|
|
5
|
+
# Cluster health API - needed for Rails healthcheck
|
|
6
|
+
class ClusterAPI
|
|
7
|
+
def initialize(adapter)
|
|
8
|
+
@adapter = adapter
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def health(**)
|
|
12
|
+
Sync do
|
|
13
|
+
@adapter.send(:execute_cluster_health, **)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -7,22 +7,8 @@ module Noiseless
|
|
|
7
7
|
class Elasticsearch < Adapter
|
|
8
8
|
include ExecutionModules::ElasticsearchExecution
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
hosts_array = Array(hosts)
|
|
13
|
-
default_port = ENV["ELASTICSEARCH_PORT"] || 9200
|
|
14
|
-
@hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
|
|
15
|
-
@connection_params = connection_params
|
|
16
|
-
|
|
17
|
-
# Initialize HTTP clients for each host
|
|
18
|
-
@clients = {}
|
|
19
|
-
@hosts.each do |host|
|
|
20
|
-
endpoint = Async::HTTP::Endpoint.parse(host)
|
|
21
|
-
@clients[host] = Async::HTTP::Client.new(endpoint)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
super(hosts: @hosts, **connection_params)
|
|
25
|
-
end
|
|
10
|
+
ClusterAPI = Adapters::ClusterAPI
|
|
11
|
+
IndicesAPI = Adapters::IndicesAPI
|
|
26
12
|
|
|
27
13
|
# Cluster health API - needed for Rails healthcheck
|
|
28
14
|
def cluster
|
|
@@ -34,36 +20,10 @@ module Noiseless
|
|
|
34
20
|
@indices ||= IndicesAPI.new(self)
|
|
35
21
|
end
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
def initialize(adapter)
|
|
39
|
-
@adapter = adapter
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def health(**)
|
|
43
|
-
Sync do
|
|
44
|
-
@adapter.send(:execute_cluster_health, **)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
class IndicesAPI
|
|
50
|
-
def initialize(adapter)
|
|
51
|
-
@adapter = adapter
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def get(index:)
|
|
55
|
-
@adapter.execute_index_exists?(index) ? { index => {} } : raise("Index not found")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def stats(index:)
|
|
59
|
-
# Return basic stats structure
|
|
60
|
-
{ "indices" => { index => {} } }
|
|
61
|
-
end
|
|
23
|
+
private
|
|
62
24
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@adapter.send(:execute_refresh_index, index)
|
|
66
|
-
end
|
|
25
|
+
def default_port
|
|
26
|
+
ENV["ELASTICSEARCH_PORT"] || 9200
|
|
67
27
|
end
|
|
68
28
|
end
|
|
69
29
|
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "es_compatible_execution"
|
|
4
5
|
|
|
5
6
|
module Noiseless
|
|
6
7
|
module Adapters
|
|
7
8
|
module ExecutionModules
|
|
8
9
|
module ElasticsearchExecution
|
|
9
|
-
|
|
10
|
-
@clients&.each_value(&:close)
|
|
11
|
-
end
|
|
10
|
+
include EsCompatibleExecution
|
|
12
11
|
|
|
13
12
|
private
|
|
14
13
|
|
|
@@ -17,16 +16,7 @@ module Noiseless
|
|
|
17
16
|
body = JSON.generate(query_hash)
|
|
18
17
|
|
|
19
18
|
response = post_request(path, body)
|
|
20
|
-
|
|
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)
|
|
19
|
+
parse_json_response!(response, error_class: Noiseless::SearchError, context: "search")
|
|
30
20
|
ensure
|
|
31
21
|
response&.close
|
|
32
22
|
end
|
|
@@ -37,42 +27,7 @@ module Noiseless
|
|
|
37
27
|
body[:settings] = settings if settings
|
|
38
28
|
|
|
39
29
|
response = put_request("/#{index_name}", body.any? ? JSON.generate(body) : nil)
|
|
40
|
-
|
|
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
|
|
30
|
+
parse_json_response!(response, context: "create index #{index_name}")
|
|
76
31
|
ensure
|
|
77
32
|
response&.close
|
|
78
33
|
end
|
|
@@ -82,32 +37,7 @@ module Noiseless
|
|
|
82
37
|
body = JSON.generate(document)
|
|
83
38
|
|
|
84
39
|
response = id ? put_request(path, body) : post_request(path, body)
|
|
85
|
-
|
|
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
|
|
40
|
+
parse_json_response!(response, context: "index document #{index}/#{id}")
|
|
111
41
|
ensure
|
|
112
42
|
response&.close
|
|
113
43
|
end
|
|
@@ -132,56 +62,6 @@ module Noiseless
|
|
|
132
62
|
ensure
|
|
133
63
|
response&.close
|
|
134
64
|
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
65
|
end
|
|
186
66
|
end
|
|
187
67
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "http_transport"
|
|
5
|
+
|
|
6
|
+
module Noiseless
|
|
7
|
+
module Adapters
|
|
8
|
+
module ExecutionModules
|
|
9
|
+
# Document and index operations shared by the wire-compatible
|
|
10
|
+
# Elasticsearch and OpenSearch HTTP APIs.
|
|
11
|
+
module EsCompatibleExecution
|
|
12
|
+
include HttpTransport
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def execute_bulk(actions, **_opts)
|
|
17
|
+
body = actions.map do |action|
|
|
18
|
+
if action[:index]
|
|
19
|
+
action_line = { index: { _index: action[:index][:_index], _id: action[:index][:_id] } }
|
|
20
|
+
data_line = action[:index][:data]
|
|
21
|
+
"#{JSON.generate(action_line)}\n#{JSON.generate(data_line)}\n"
|
|
22
|
+
else
|
|
23
|
+
"#{JSON.generate(action)}\n"
|
|
24
|
+
end
|
|
25
|
+
end.join
|
|
26
|
+
|
|
27
|
+
response = post_request("/_bulk", body, content_type: "application/x-ndjson")
|
|
28
|
+
parse_json_response!(response, context: "bulk")
|
|
29
|
+
ensure
|
|
30
|
+
response&.close
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute_delete_index(index_name, **_opts)
|
|
34
|
+
response = delete_request("/#{index_name}")
|
|
35
|
+
parse_json_response!(response, context: "delete index #{index_name}")
|
|
36
|
+
ensure
|
|
37
|
+
response&.close
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def execute_refresh_index(index_name)
|
|
41
|
+
response = post_request("/#{index_name}/_refresh", nil)
|
|
42
|
+
parse_json_response!(response, context: "refresh index #{index_name}")
|
|
43
|
+
ensure
|
|
44
|
+
response&.close
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def execute_index_exists?(index_name)
|
|
48
|
+
response = head_request("/#{index_name}")
|
|
49
|
+
response.success?
|
|
50
|
+
rescue StandardError
|
|
51
|
+
false
|
|
52
|
+
ensure
|
|
53
|
+
response&.close
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def execute_update_document(index, id, changes, **_opts)
|
|
57
|
+
body = JSON.generate(doc: changes)
|
|
58
|
+
|
|
59
|
+
response = post_request("/#{index}/_update/#{id}", body)
|
|
60
|
+
parse_json_response!(response, context: "update document #{index}/#{id}")
|
|
61
|
+
ensure
|
|
62
|
+
response&.close
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def execute_delete_document(index, id, **_opts)
|
|
66
|
+
response = delete_request("/#{index}/_doc/#{id}")
|
|
67
|
+
parse_json_response!(response, context: "delete document #{index}/#{id}")
|
|
68
|
+
ensure
|
|
69
|
+
response&.close
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def execute_document_exists?(index, id)
|
|
73
|
+
response = head_request("/#{index}/_doc/#{id}")
|
|
74
|
+
response.success?
|
|
75
|
+
rescue StandardError
|
|
76
|
+
false
|
|
77
|
+
ensure
|
|
78
|
+
response&.close
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Noiseless
|
|
4
|
+
module Adapters
|
|
5
|
+
module ExecutionModules
|
|
6
|
+
# Shared Async::HTTP connection handling for HTTP-based adapters.
|
|
7
|
+
# Host classes must provide a private +default_port+ method.
|
|
8
|
+
module HttpTransport
|
|
9
|
+
def initialize(hosts: [], **connection_params)
|
|
10
|
+
# Ensure we always have at least one host
|
|
11
|
+
hosts_array = Array(hosts)
|
|
12
|
+
@hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
|
|
13
|
+
@connection_params = connection_params
|
|
14
|
+
|
|
15
|
+
# Initialize HTTP clients for each host
|
|
16
|
+
@clients = {}
|
|
17
|
+
@hosts.each do |host|
|
|
18
|
+
endpoint = Async::HTTP::Endpoint.parse(host)
|
|
19
|
+
@clients[host] = Async::HTTP::Client.new(endpoint)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
super(hosts: @hosts, **connection_params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def close
|
|
26
|
+
@clients&.each_value(&:close)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# HTTP helpers using Async::HTTP with connection pooling
|
|
32
|
+
def get_request(path)
|
|
33
|
+
with_client do |client|
|
|
34
|
+
client.get(path, default_headers)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def post_request(path, body, content_type: "application/json")
|
|
39
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
40
|
+
|
|
41
|
+
with_client do |client|
|
|
42
|
+
client.post(path, headers, body)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def put_request(path, body, content_type: "application/json")
|
|
47
|
+
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
48
|
+
|
|
49
|
+
with_client do |client|
|
|
50
|
+
client.put(path, headers, body)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def delete_request(path)
|
|
55
|
+
with_client do |client|
|
|
56
|
+
client.delete(path, default_headers)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def head_request(path)
|
|
61
|
+
with_client do |client|
|
|
62
|
+
client.head(path, default_headers)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def with_client
|
|
67
|
+
# Select a random host for load balancing
|
|
68
|
+
host = @hosts.sample
|
|
69
|
+
client = @clients[host]
|
|
70
|
+
|
|
71
|
+
yield(client)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def default_headers
|
|
75
|
+
[
|
|
76
|
+
["accept", "application/json"],
|
|
77
|
+
["user-agent", "Noiseless/#{Noiseless::VERSION} (Ruby/#{RUBY_VERSION})"]
|
|
78
|
+
]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "es_compatible_execution"
|
|
4
5
|
|
|
5
6
|
module Noiseless
|
|
6
7
|
module Adapters
|
|
7
8
|
module ExecutionModules
|
|
8
9
|
module OpensearchExecution
|
|
9
|
-
|
|
10
|
-
@clients&.each_value(&:close)
|
|
11
|
-
end
|
|
10
|
+
include EsCompatibleExecution
|
|
12
11
|
|
|
13
12
|
private
|
|
14
13
|
|
|
@@ -18,43 +17,7 @@ module Noiseless
|
|
|
18
17
|
body = JSON.generate(query_hash)
|
|
19
18
|
|
|
20
19
|
response = post_request(path, body)
|
|
21
|
-
|
|
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 } }
|
|
20
|
+
parse_json_response!(response, error_class: Noiseless::SearchError, context: "search #{index_path}")
|
|
58
21
|
ensure
|
|
59
22
|
response&.close
|
|
60
23
|
end
|
|
@@ -65,46 +28,7 @@ module Noiseless
|
|
|
65
28
|
body[:settings] = settings if settings
|
|
66
29
|
|
|
67
30
|
response = put_request("/#{index_name}", body.any? ? JSON.generate(body) : nil)
|
|
68
|
-
|
|
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
|
|
31
|
+
parse_json_response!(response, context: "create index #{index_name}")
|
|
108
32
|
ensure
|
|
109
33
|
response&.close
|
|
110
34
|
end
|
|
@@ -114,38 +38,7 @@ module Noiseless
|
|
|
114
38
|
body = JSON.generate(document)
|
|
115
39
|
|
|
116
40
|
response = put_request(path, body)
|
|
117
|
-
|
|
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
|
|
41
|
+
parse_json_response!(response, context: "index document #{index}/#{id}")
|
|
149
42
|
ensure
|
|
150
43
|
response&.close
|
|
151
44
|
end
|
|
@@ -178,13 +71,7 @@ module Noiseless
|
|
|
178
71
|
body = JSON.generate(enhanced_query)
|
|
179
72
|
|
|
180
73
|
response = post_request("/_search", body)
|
|
181
|
-
|
|
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
|
-
}
|
|
74
|
+
parse_json_response!(response, error_class: Noiseless::SearchError, context: "point-in-time search")
|
|
188
75
|
ensure
|
|
189
76
|
response&.close
|
|
190
77
|
end
|
|
@@ -198,12 +85,7 @@ module Noiseless
|
|
|
198
85
|
body = JSON.generate(template_query)
|
|
199
86
|
|
|
200
87
|
response = post_request("/_search/template", body)
|
|
201
|
-
|
|
202
|
-
rescue StandardError => e
|
|
203
|
-
{
|
|
204
|
-
error: { type: e.class.name, reason: e.message },
|
|
205
|
-
hits: { total: { value: 0 }, hits: [] }
|
|
206
|
-
}
|
|
88
|
+
parse_json_response!(response, error_class: Noiseless::SearchError, context: "search template #{template_id}")
|
|
207
89
|
ensure
|
|
208
90
|
response&.close
|
|
209
91
|
end
|
|
@@ -321,56 +203,6 @@ module Noiseless
|
|
|
321
203
|
ensure
|
|
322
204
|
response&.close
|
|
323
205
|
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
206
|
end
|
|
375
207
|
end
|
|
376
208
|
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "http_transport"
|
|
4
5
|
|
|
5
6
|
module Noiseless
|
|
6
7
|
module Adapters
|
|
7
8
|
module ExecutionModules
|
|
8
9
|
module TypesenseExecution
|
|
9
|
-
|
|
10
|
-
@clients&.each_value(&:close)
|
|
11
|
-
end
|
|
10
|
+
include HttpTransport
|
|
12
11
|
|
|
13
12
|
private
|
|
14
13
|
|
|
@@ -391,54 +390,8 @@ module Noiseless
|
|
|
391
390
|
response&.close
|
|
392
391
|
end
|
|
393
392
|
|
|
394
|
-
# HTTP helpers using Async::HTTP with connection pooling
|
|
395
|
-
def get_request(path)
|
|
396
|
-
with_client do |client|
|
|
397
|
-
client.get(path, default_headers)
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
def post_request(path, body, content_type: "application/json")
|
|
402
|
-
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
403
|
-
|
|
404
|
-
with_client do |client|
|
|
405
|
-
client.post(path, headers, body)
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
def put_request(path, body, content_type: "application/json")
|
|
410
|
-
headers = body ? default_headers + [["content-type", content_type]] : default_headers
|
|
411
|
-
|
|
412
|
-
with_client do |client|
|
|
413
|
-
client.put(path, headers, body)
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
def delete_request(path)
|
|
418
|
-
with_client do |client|
|
|
419
|
-
client.delete(path, default_headers)
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
def head_request(path)
|
|
424
|
-
with_client do |client|
|
|
425
|
-
client.head(path, default_headers)
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
def with_client
|
|
430
|
-
# Select a random host for load balancing
|
|
431
|
-
host = @hosts.sample
|
|
432
|
-
client = @clients[host]
|
|
433
|
-
|
|
434
|
-
yield(client)
|
|
435
|
-
end
|
|
436
|
-
|
|
437
393
|
def default_headers
|
|
438
|
-
headers =
|
|
439
|
-
["accept", "application/json"],
|
|
440
|
-
["user-agent", "Noiseless/#{Noiseless::VERSION} (Ruby/#{RUBY_VERSION})"]
|
|
441
|
-
]
|
|
394
|
+
headers = super
|
|
442
395
|
|
|
443
396
|
# Add Typesense API key if configured
|
|
444
397
|
if @connection_params && @connection_params[:api_key]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Noiseless
|
|
4
|
+
module Adapters
|
|
5
|
+
# Indices API - needed for index management operations
|
|
6
|
+
class IndicesAPI
|
|
7
|
+
def initialize(adapter)
|
|
8
|
+
@adapter = adapter
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(index:)
|
|
12
|
+
@adapter.execute_index_exists?(index) ? { index => {} } : raise("Index not found")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def stats(index:)
|
|
16
|
+
# Return basic stats structure
|
|
17
|
+
{ "indices" => { index => {} } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def refresh(index:)
|
|
21
|
+
# Refresh the index to make documents immediately searchable
|
|
22
|
+
@adapter.send(:execute_refresh_index, index)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -7,22 +7,8 @@ module Noiseless
|
|
|
7
7
|
class OpenSearch < Adapter
|
|
8
8
|
include ExecutionModules::OpensearchExecution
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
hosts_array = Array(hosts)
|
|
13
|
-
default_port = ENV["OPENSEARCH_PORT"] || 9200
|
|
14
|
-
@hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
|
|
15
|
-
@connection_params = connection_params
|
|
16
|
-
|
|
17
|
-
# Initialize HTTP clients for each host
|
|
18
|
-
@clients = {}
|
|
19
|
-
@hosts.each do |host|
|
|
20
|
-
endpoint = Async::HTTP::Endpoint.parse(host)
|
|
21
|
-
@clients[host] = Async::HTTP::Client.new(endpoint)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
super(hosts: @hosts, **connection_params)
|
|
25
|
-
end
|
|
10
|
+
ClusterAPI = Adapters::ClusterAPI
|
|
11
|
+
IndicesAPI = Adapters::IndicesAPI
|
|
26
12
|
|
|
27
13
|
# OpenSearch-specific features
|
|
28
14
|
def point_in_time_search(ast_node, pit_id:, **)
|
|
@@ -65,38 +51,6 @@ module Noiseless
|
|
|
65
51
|
end
|
|
66
52
|
end
|
|
67
53
|
|
|
68
|
-
class ClusterAPI
|
|
69
|
-
def initialize(adapter)
|
|
70
|
-
@adapter = adapter
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def health(**)
|
|
74
|
-
Sync do
|
|
75
|
-
@adapter.send(:execute_cluster_health, **)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
class IndicesAPI
|
|
81
|
-
def initialize(adapter)
|
|
82
|
-
@adapter = adapter
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def get(index:)
|
|
86
|
-
@adapter.execute_index_exists?(index) ? { index => {} } : raise("Index not found")
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def stats(index:)
|
|
90
|
-
# Return basic stats structure
|
|
91
|
-
{ "indices" => { index => {} } }
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def refresh(index:)
|
|
95
|
-
# Refresh the index to make documents immediately searchable
|
|
96
|
-
@adapter.send(:execute_refresh_index, index)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
54
|
# Search Pipelines API for OpenSearch 3.x
|
|
101
55
|
# Pipelines can include request and response processors for neural search, reranking, etc.
|
|
102
56
|
class PipelinesAPI
|
|
@@ -203,6 +157,12 @@ module Noiseless
|
|
|
203
157
|
end
|
|
204
158
|
end
|
|
205
159
|
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def default_port
|
|
164
|
+
ENV["OPENSEARCH_PORT"] || 9200
|
|
165
|
+
end
|
|
206
166
|
end
|
|
207
167
|
end
|
|
208
168
|
end
|
|
@@ -7,22 +7,7 @@ module Noiseless
|
|
|
7
7
|
class Typesense < Adapter
|
|
8
8
|
include ExecutionModules::TypesenseExecution
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
# Ensure we always have at least one host
|
|
12
|
-
hosts_array = Array(hosts)
|
|
13
|
-
default_port = ENV["TYPESENSE_PORT"] || 8108
|
|
14
|
-
@hosts = hosts_array.empty? ? ["http://localhost:#{default_port}"] : hosts_array
|
|
15
|
-
@connection_params = connection_params
|
|
16
|
-
|
|
17
|
-
# Initialize HTTP clients for each host
|
|
18
|
-
@clients = {}
|
|
19
|
-
@hosts.each do |host|
|
|
20
|
-
endpoint = Async::HTTP::Endpoint.parse(host)
|
|
21
|
-
@clients[host] = Async::HTTP::Client.new(endpoint)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
super(hosts: @hosts, **connection_params)
|
|
25
|
-
end
|
|
10
|
+
ClusterAPI = Adapters::ClusterAPI
|
|
26
11
|
|
|
27
12
|
# Cluster health API - needed for Rails healthcheck
|
|
28
13
|
def cluster
|
|
@@ -34,37 +19,18 @@ module Noiseless
|
|
|
34
19
|
@indices ||= IndicesAPI.new(self)
|
|
35
20
|
end
|
|
36
21
|
|
|
37
|
-
class
|
|
38
|
-
def initialize(adapter)
|
|
39
|
-
@adapter = adapter
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def health(**)
|
|
43
|
-
Sync do
|
|
44
|
-
@adapter.send(:execute_cluster_health, **)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
class IndicesAPI
|
|
50
|
-
def initialize(adapter)
|
|
51
|
-
@adapter = adapter
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def get(index:)
|
|
55
|
-
@adapter.execute_index_exists?(index) ? { index => {} } : raise("Index not found")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def stats(index:)
|
|
59
|
-
# Return basic stats structure
|
|
60
|
-
{ "indices" => { index => {} } }
|
|
61
|
-
end
|
|
62
|
-
|
|
22
|
+
class IndicesAPI < Adapters::IndicesAPI
|
|
63
23
|
def refresh(index: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
64
24
|
# Typesense doesn't require explicit refresh - documents are immediately available
|
|
65
25
|
{ "_shards" => { "total" => 1, "successful" => 1, "failed" => 0 } }
|
|
66
26
|
end
|
|
67
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def default_port
|
|
32
|
+
ENV["TYPESENSE_PORT"] || 8108
|
|
33
|
+
end
|
|
68
34
|
end
|
|
69
35
|
end
|
|
70
36
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Noiseless
|
|
4
|
+
module AST
|
|
5
|
+
# Base for leaf nodes that pair a single field with a value.
|
|
6
|
+
class FieldValueNode < Node
|
|
7
|
+
attr_reader :field, :value
|
|
8
|
+
|
|
9
|
+
def initialize(field, value)
|
|
10
|
+
super()
|
|
11
|
+
@field = field
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/noiseless/ast/filter.rb
CHANGED
data/lib/noiseless/ast/match.rb
CHANGED
data/lib/noiseless/ast/prefix.rb
CHANGED
data/lib/noiseless/version.rb
CHANGED
data/lib/noiseless.rb
CHANGED
|
@@ -17,6 +17,21 @@ require_relative "noiseless/version"
|
|
|
17
17
|
module Noiseless
|
|
18
18
|
class Error < StandardError; end
|
|
19
19
|
|
|
20
|
+
# HTTP-level failure from the search backend (non-2xx response).
|
|
21
|
+
class RequestError < Error
|
|
22
|
+
attr_reader :status, :error_type
|
|
23
|
+
|
|
24
|
+
def initialize(message, status: nil, error_type: nil)
|
|
25
|
+
super(message)
|
|
26
|
+
@status = status
|
|
27
|
+
@error_type = error_type
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Raised when a search query is rejected by the backend
|
|
32
|
+
# (malformed query, missing index, shard failures).
|
|
33
|
+
class SearchError < RequestError; end
|
|
34
|
+
|
|
20
35
|
class Configuration
|
|
21
36
|
attr_accessor :connections_config, :default_connection, :default_adapter, :config_path
|
|
22
37
|
|
|
@@ -85,7 +100,8 @@ module Noiseless
|
|
|
85
100
|
|
|
86
101
|
# Setup Zeitwerk autoloader
|
|
87
102
|
loader = Zeitwerk::Loader.for_gem
|
|
88
|
-
loader.inflector.inflect("ast" => "AST", "dsl" => "DSL", "open_search" => "OpenSearch"
|
|
103
|
+
loader.inflector.inflect("ast" => "AST", "dsl" => "DSL", "open_search" => "OpenSearch",
|
|
104
|
+
"cluster_api" => "ClusterAPI", "indices_api" => "IndicesAPI")
|
|
89
105
|
loader.ignore("#{__dir__}/application_search.rb")
|
|
90
106
|
loader.ignore("#{__dir__}/noiseless/test_helper.rb")
|
|
91
107
|
loader.ignore("#{__dir__}/noiseless/test_case.rb")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: noiseless
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdelkader Boudih
|
|
@@ -206,12 +206,16 @@ files:
|
|
|
206
206
|
- lib/noiseless.rb
|
|
207
207
|
- lib/noiseless/adapter.rb
|
|
208
208
|
- lib/noiseless/adapters.rb
|
|
209
|
+
- lib/noiseless/adapters/cluster_api.rb
|
|
209
210
|
- lib/noiseless/adapters/elasticsearch.rb
|
|
210
211
|
- lib/noiseless/adapters/execution_modules/elasticsearch_execution.rb
|
|
212
|
+
- lib/noiseless/adapters/execution_modules/es_compatible_execution.rb
|
|
213
|
+
- lib/noiseless/adapters/execution_modules/http_transport.rb
|
|
211
214
|
- lib/noiseless/adapters/execution_modules/opensearch_execution.rb
|
|
212
215
|
- lib/noiseless/adapters/execution_modules/pgvector_support.rb
|
|
213
216
|
- lib/noiseless/adapters/execution_modules/postgresql_execution.rb
|
|
214
217
|
- lib/noiseless/adapters/execution_modules/typesense_execution.rb
|
|
218
|
+
- lib/noiseless/adapters/indices_api.rb
|
|
215
219
|
- lib/noiseless/adapters/open_search.rb
|
|
216
220
|
- lib/noiseless/adapters/postgresql.rb
|
|
217
221
|
- lib/noiseless/adapters/typesense.rb
|
|
@@ -222,6 +226,7 @@ files:
|
|
|
222
226
|
- lib/noiseless/ast/collapse.rb
|
|
223
227
|
- lib/noiseless/ast/combined_fields.rb
|
|
224
228
|
- lib/noiseless/ast/conversation.rb
|
|
229
|
+
- lib/noiseless/ast/field_value_node.rb
|
|
225
230
|
- lib/noiseless/ast/filter.rb
|
|
226
231
|
- lib/noiseless/ast/hybrid.rb
|
|
227
232
|
- lib/noiseless/ast/image_query.rb
|
|
@@ -289,7 +294,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
289
294
|
- !ruby/object:Gem::Version
|
|
290
295
|
version: '0'
|
|
291
296
|
requirements: []
|
|
292
|
-
rubygems_version:
|
|
297
|
+
rubygems_version: 4.0.10
|
|
293
298
|
specification_version: 4
|
|
294
299
|
summary: Async-first Rails search abstraction with multi-backend support
|
|
295
300
|
test_files: []
|