weaviate-ruby 0.9.2 → 0.10.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/.standard.yml +1 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +8 -2
- data/Gemfile.lock +56 -68
- data/README.md +220 -29
- data/Rakefile +2 -0
- data/lib/weaviate/aliases.rb +40 -0
- data/lib/weaviate/backups.rb +61 -6
- data/lib/weaviate/classifications.rb +1 -3
- data/lib/weaviate/client.rb +88 -40
- data/lib/weaviate/{schema.rb → collections.rb} +84 -51
- data/lib/weaviate/connection.rb +147 -0
- data/lib/weaviate/error.rb +52 -0
- data/lib/weaviate/graphql.rb +19 -0
- data/lib/weaviate/groups.rb +39 -0
- data/lib/weaviate/health.rb +13 -4
- data/lib/weaviate/nodes.rb +10 -2
- data/lib/weaviate/objects.rb +101 -11
- data/lib/weaviate/query.rb +158 -84
- data/lib/weaviate/roles.rb +65 -0
- data/lib/weaviate/users.rb +94 -0
- data/lib/weaviate/version.rb +1 -1
- data/lib/weaviate.rb +9 -2
- data/sig/weaviate.rbs +419 -1
- metadata +19 -41
data/lib/weaviate/error.rb
CHANGED
|
@@ -3,4 +3,56 @@
|
|
|
3
3
|
module Weaviate
|
|
4
4
|
class Error < StandardError
|
|
5
5
|
end
|
|
6
|
+
|
|
7
|
+
# Raised when Weaviate cannot be reached
|
|
8
|
+
class ConnectionFailed < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Raised when a request times out
|
|
12
|
+
class TimeoutError < ConnectionFailed
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Raised when Weaviate responds with a 4xx or 5xx status
|
|
16
|
+
class HTTPError < Error
|
|
17
|
+
attr_reader :response
|
|
18
|
+
|
|
19
|
+
def initialize(message = nil, response: nil)
|
|
20
|
+
@response = response
|
|
21
|
+
super(message || "the server responded with status #{response&.status}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def status
|
|
25
|
+
response&.status
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def body
|
|
29
|
+
response&.body
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ClientError < HTTPError; end
|
|
34
|
+
|
|
35
|
+
class BadRequestError < ClientError; end
|
|
36
|
+
|
|
37
|
+
class UnauthorizedError < ClientError; end
|
|
38
|
+
|
|
39
|
+
class ForbiddenError < ClientError; end
|
|
40
|
+
|
|
41
|
+
class ResourceNotFound < ClientError; end
|
|
42
|
+
|
|
43
|
+
class ConflictError < ClientError; end
|
|
44
|
+
|
|
45
|
+
class UnprocessableEntityError < ClientError; end
|
|
46
|
+
|
|
47
|
+
class ServerError < HTTPError; end
|
|
48
|
+
|
|
49
|
+
# Raised when a GraphQL query returns errors
|
|
50
|
+
class GraphQLError < Error
|
|
51
|
+
attr_reader :errors
|
|
52
|
+
|
|
53
|
+
def initialize(errors)
|
|
54
|
+
@errors = errors
|
|
55
|
+
super(errors.map { |e| e["message"] }.join(", "))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
6
58
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Weaviate
|
|
4
|
+
class GraphQL < Base
|
|
5
|
+
PATH = "graphql"
|
|
6
|
+
|
|
7
|
+
# Execute a GraphQL query and return the "data" portion of the response.
|
|
8
|
+
def execute(query)
|
|
9
|
+
response = client.connection.post(PATH) do |req|
|
|
10
|
+
req.body = {query: query}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
errors = response.body["errors"]
|
|
14
|
+
raise Weaviate::GraphQLError.new(errors) if errors && !errors.empty?
|
|
15
|
+
|
|
16
|
+
response.body["data"]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Weaviate
|
|
4
|
+
# Manage roles of OIDC groups. Requires RBAC and OIDC to be enabled.
|
|
5
|
+
class Groups < Base
|
|
6
|
+
PATH = "authz/groups"
|
|
7
|
+
GROUP_TYPE = "oidc"
|
|
8
|
+
|
|
9
|
+
# Groups that have roles assigned.
|
|
10
|
+
def list
|
|
11
|
+
response = client.connection.get("#{PATH}/#{GROUP_TYPE}")
|
|
12
|
+
response.body
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def roles(group_id:, include_full_roles: nil)
|
|
16
|
+
response = client.connection.get("#{PATH}/#{group_id}/roles/#{GROUP_TYPE}") do |req|
|
|
17
|
+
req.params["includeFullRoles"] = include_full_roles unless include_full_roles.nil?
|
|
18
|
+
end
|
|
19
|
+
response.body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def assign_roles(group_id:, roles:)
|
|
23
|
+
update_roles(group_id, "assign", roles)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def revoke_roles(group_id:, roles:)
|
|
27
|
+
update_roles(group_id, "revoke", roles)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def update_roles(group_id, action, roles)
|
|
33
|
+
response = client.connection.post("#{PATH}/#{group_id}/#{action}") do |req|
|
|
34
|
+
req.body = {roles: Array(roles), groupType: GROUP_TYPE}
|
|
35
|
+
end
|
|
36
|
+
response.success?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/weaviate/health.rb
CHANGED
|
@@ -4,14 +4,23 @@ module Weaviate
|
|
|
4
4
|
class Health < Base
|
|
5
5
|
PATH = ".well-known"
|
|
6
6
|
|
|
7
|
+
# Whether Weaviate is alive. Returns false when it responds with an error or cannot be reached.
|
|
7
8
|
def live?
|
|
8
|
-
|
|
9
|
-
response.status == 200
|
|
9
|
+
check("#{PATH}/live")
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Whether Weaviate is ready to receive traffic. Returns false when it responds with an error
|
|
13
|
+
# (e.g. 503 while starting up) or cannot be reached.
|
|
12
14
|
def ready?
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
check("#{PATH}/ready")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def check(path)
|
|
21
|
+
client.connection.get(path).status == 200
|
|
22
|
+
rescue Weaviate::HTTPError, Weaviate::ConnectionFailed
|
|
23
|
+
false
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
end
|
data/lib/weaviate/nodes.rb
CHANGED
|
@@ -4,8 +4,16 @@ module Weaviate
|
|
|
4
4
|
class Nodes < Base
|
|
5
5
|
PATH = "nodes"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# @param output [String] "minimal" (default) or "verbose", which includes shard details
|
|
8
|
+
# @param class_name [String] Only return shards of this collection
|
|
9
|
+
# @param shard_name [String] Only return this shard, requires class_name
|
|
10
|
+
def list(output: nil, class_name: nil, shard_name: nil)
|
|
11
|
+
path = class_name.nil? ? PATH : "#{PATH}/#{class_name}"
|
|
12
|
+
|
|
13
|
+
response = client.connection.get(path) do |req|
|
|
14
|
+
req.params["output"] = output unless output.nil?
|
|
15
|
+
req.params["shardName"] = shard_name unless shard_name.nil?
|
|
16
|
+
end
|
|
9
17
|
response.body
|
|
10
18
|
end
|
|
11
19
|
end
|
data/lib/weaviate/objects.rb
CHANGED
|
@@ -66,10 +66,12 @@ module Weaviate
|
|
|
66
66
|
)
|
|
67
67
|
validate_consistency_level!(consistency_level) unless consistency_level.nil?
|
|
68
68
|
|
|
69
|
+
# Weaviate expects the tenant on each object rather than on the batch
|
|
70
|
+
objects = objects.map { |object| {tenant: tenant}.merge(object) } unless tenant.nil?
|
|
71
|
+
|
|
69
72
|
response = client.connection.post("batch/#{PATH}") do |req|
|
|
70
73
|
req.params["consistency_level"] = consistency_level.to_s.upcase unless consistency_level.nil?
|
|
71
74
|
req.body = {objects: objects}
|
|
72
|
-
req.body["tenant"] = tenant unless tenant.nil?
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
response.body
|
|
@@ -109,6 +111,8 @@ module Weaviate
|
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
response.status == 204
|
|
114
|
+
rescue Weaviate::ResourceNotFound
|
|
115
|
+
false
|
|
112
116
|
end
|
|
113
117
|
|
|
114
118
|
# Update an individual data object based on its uuid.
|
|
@@ -175,11 +179,7 @@ module Weaviate
|
|
|
175
179
|
req.params["tenant"] = tenant unless tenant.nil?
|
|
176
180
|
end
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
response.body.empty?
|
|
180
|
-
else
|
|
181
|
-
response.body
|
|
182
|
-
end
|
|
182
|
+
response.body.empty?
|
|
183
183
|
end
|
|
184
184
|
|
|
185
185
|
def batch_delete(
|
|
@@ -212,6 +212,92 @@ module Weaviate
|
|
|
212
212
|
response.body
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
+
# Add a reference to an object's reference property.
|
|
216
|
+
def add_reference(
|
|
217
|
+
class_name:,
|
|
218
|
+
id:,
|
|
219
|
+
property:,
|
|
220
|
+
to_class_name:,
|
|
221
|
+
to_id:,
|
|
222
|
+
tenant: nil,
|
|
223
|
+
consistency_level: nil
|
|
224
|
+
)
|
|
225
|
+
validate_consistency_level!(consistency_level) unless consistency_level.nil?
|
|
226
|
+
|
|
227
|
+
response = client.connection.post(reference_path(class_name, id, property)) do |req|
|
|
228
|
+
req.params["consistency_level"] = consistency_level.to_s.upcase unless consistency_level.nil?
|
|
229
|
+
req.params["tenant"] = tenant unless tenant.nil?
|
|
230
|
+
req.body = {beacon: beacon(to_class_name, to_id)}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
response.success?
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Replace all references of an object's reference property.
|
|
237
|
+
# @param to [Array<Hash>] The objects to reference, e.g. [{ class_name: "Category", id: "..." }]
|
|
238
|
+
def replace_references(
|
|
239
|
+
class_name:,
|
|
240
|
+
id:,
|
|
241
|
+
property:,
|
|
242
|
+
to:,
|
|
243
|
+
tenant: nil,
|
|
244
|
+
consistency_level: nil
|
|
245
|
+
)
|
|
246
|
+
validate_consistency_level!(consistency_level) unless consistency_level.nil?
|
|
247
|
+
|
|
248
|
+
response = client.connection.put(reference_path(class_name, id, property)) do |req|
|
|
249
|
+
req.params["consistency_level"] = consistency_level.to_s.upcase unless consistency_level.nil?
|
|
250
|
+
req.params["tenant"] = tenant unless tenant.nil?
|
|
251
|
+
req.body = to.map { |ref| {beacon: beacon(ref.fetch(:class_name), ref.fetch(:id))} }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
response.success?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Delete a reference from an object's reference property.
|
|
258
|
+
def delete_reference(
|
|
259
|
+
class_name:,
|
|
260
|
+
id:,
|
|
261
|
+
property:,
|
|
262
|
+
to_class_name:,
|
|
263
|
+
to_id:,
|
|
264
|
+
tenant: nil,
|
|
265
|
+
consistency_level: nil
|
|
266
|
+
)
|
|
267
|
+
validate_consistency_level!(consistency_level) unless consistency_level.nil?
|
|
268
|
+
|
|
269
|
+
response = client.connection.delete(reference_path(class_name, id, property)) do |req|
|
|
270
|
+
req.params["consistency_level"] = consistency_level.to_s.upcase unless consistency_level.nil?
|
|
271
|
+
req.params["tenant"] = tenant unless tenant.nil?
|
|
272
|
+
req.body = {beacon: beacon(to_class_name, to_id)}
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
response.success?
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Batch create references.
|
|
279
|
+
# @param references [Array<Hash>] e.g. [{ from_class_name: "Post", from_id: "...", property: "ofCategory", to_class_name: "Category", to_id: "...", tenant: nil }]
|
|
280
|
+
def batch_create_references(
|
|
281
|
+
references:,
|
|
282
|
+
consistency_level: nil
|
|
283
|
+
)
|
|
284
|
+
validate_consistency_level!(consistency_level) unless consistency_level.nil?
|
|
285
|
+
|
|
286
|
+
response = client.connection.post("batch/references") do |req|
|
|
287
|
+
req.params["consistency_level"] = consistency_level.to_s.upcase unless consistency_level.nil?
|
|
288
|
+
req.body = references.map do |ref|
|
|
289
|
+
body = {
|
|
290
|
+
from: "#{beacon(ref.fetch(:from_class_name), ref.fetch(:from_id))}/#{ref.fetch(:property)}",
|
|
291
|
+
to: beacon(ref.fetch(:to_class_name), ref.fetch(:to_id))
|
|
292
|
+
}
|
|
293
|
+
body[:tenant] = ref[:tenant] unless ref[:tenant].nil?
|
|
294
|
+
body
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
response.body
|
|
299
|
+
end
|
|
300
|
+
|
|
215
301
|
# Validate a data object
|
|
216
302
|
def validate(
|
|
217
303
|
class_name:,
|
|
@@ -225,15 +311,19 @@ module Weaviate
|
|
|
225
311
|
req.body["id"] = id unless id.nil?
|
|
226
312
|
end
|
|
227
313
|
|
|
228
|
-
|
|
229
|
-
response.body.empty?
|
|
230
|
-
else
|
|
231
|
-
response.body
|
|
232
|
-
end
|
|
314
|
+
response.body.empty?
|
|
233
315
|
end
|
|
234
316
|
|
|
235
317
|
private
|
|
236
318
|
|
|
319
|
+
def reference_path(class_name, id, property)
|
|
320
|
+
"#{PATH}/#{class_name}/#{id}/references/#{property}"
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def beacon(class_name, id)
|
|
324
|
+
"weaviate://localhost/#{class_name}/#{id}"
|
|
325
|
+
end
|
|
326
|
+
|
|
237
327
|
def validate_consistency_level!(consistency_level)
|
|
238
328
|
unless %w[ONE QUORUM ALL].include?(consistency_level.to_s.upcase)
|
|
239
329
|
raise ArgumentError, 'consistency_level must be either "ONE" or "QUORUM" OR "ALL"'
|
data/lib/weaviate/query.rb
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Weaviate
|
|
4
6
|
class Query < Base
|
|
7
|
+
# Fields whose values are GraphQL enums, so strings are sent unquoted, e.g. operator: "Equal"
|
|
8
|
+
ENUM_KEYS = %w[operator order fusionType].freeze
|
|
9
|
+
|
|
10
|
+
GRAPHQL_NAME = /\A[_A-Za-z][_0-9A-Za-z]*\z/
|
|
11
|
+
|
|
5
12
|
def get(
|
|
6
13
|
class_name:,
|
|
7
14
|
fields:,
|
|
@@ -18,14 +25,18 @@ module Weaviate
|
|
|
18
25
|
near_object: nil,
|
|
19
26
|
with_hybrid: nil,
|
|
20
27
|
bm25: nil,
|
|
21
|
-
ask: nil
|
|
28
|
+
ask: nil,
|
|
29
|
+
group_by: nil
|
|
22
30
|
)
|
|
23
|
-
|
|
31
|
+
data = client.graphql.execute(
|
|
24
32
|
get_query(
|
|
25
33
|
class_name: class_name,
|
|
26
|
-
tenant: tenant,
|
|
27
34
|
fields: fields,
|
|
35
|
+
after: after,
|
|
36
|
+
tenant: tenant,
|
|
37
|
+
limit: limit,
|
|
28
38
|
autocut: autocut,
|
|
39
|
+
offset: offset,
|
|
29
40
|
sort: sort,
|
|
30
41
|
where: where,
|
|
31
42
|
near_text: near_text,
|
|
@@ -34,15 +45,11 @@ module Weaviate
|
|
|
34
45
|
near_object: near_object,
|
|
35
46
|
with_hybrid: with_hybrid,
|
|
36
47
|
bm25: bm25,
|
|
37
|
-
ask: ask
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
limit: limit,
|
|
41
|
-
offset: offset
|
|
48
|
+
ask: ask,
|
|
49
|
+
group_by: group_by
|
|
50
|
+
)
|
|
42
51
|
)
|
|
43
|
-
|
|
44
|
-
rescue Graphlient::Errors::ExecutionError => error
|
|
45
|
-
raise Weaviate::Error.new(error.response.data.get.errors.messages.to_h)
|
|
52
|
+
data.dig("Get", class_name)
|
|
46
53
|
end
|
|
47
54
|
|
|
48
55
|
def aggs(
|
|
@@ -55,27 +62,30 @@ module Weaviate
|
|
|
55
62
|
near_vector: nil,
|
|
56
63
|
near_image: nil,
|
|
57
64
|
near_object: nil,
|
|
58
|
-
group_by: nil
|
|
65
|
+
group_by: nil,
|
|
66
|
+
limit: nil,
|
|
67
|
+
with_hybrid: nil
|
|
59
68
|
)
|
|
60
|
-
|
|
69
|
+
data = client.graphql.execute(
|
|
61
70
|
aggs_query(
|
|
62
71
|
class_name: class_name,
|
|
63
|
-
tenant: tenant,
|
|
64
72
|
fields: fields,
|
|
73
|
+
tenant: tenant,
|
|
65
74
|
where: where,
|
|
75
|
+
object_limit: object_limit,
|
|
66
76
|
near_text: near_text,
|
|
67
77
|
near_vector: near_vector,
|
|
68
78
|
near_image: near_image,
|
|
69
|
-
near_object: near_object
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
near_object: near_object,
|
|
80
|
+
group_by: group_by,
|
|
81
|
+
limit: limit,
|
|
82
|
+
with_hybrid: with_hybrid
|
|
83
|
+
)
|
|
73
84
|
)
|
|
74
|
-
|
|
75
|
-
rescue Graphlient::Errors::ExecutionError => error
|
|
76
|
-
raise Weaviate::Error.new(error.response.data.aggregate.errors.messages.to_h)
|
|
85
|
+
data.dig("Aggregate", class_name)
|
|
77
86
|
end
|
|
78
87
|
|
|
88
|
+
# @deprecated Weaviate disables Explore when more than one vectorizer module is enabled.
|
|
79
89
|
def explore(
|
|
80
90
|
fields:,
|
|
81
91
|
after: nil,
|
|
@@ -88,29 +98,35 @@ module Weaviate
|
|
|
88
98
|
near_image: nil,
|
|
89
99
|
near_object: nil
|
|
90
100
|
)
|
|
91
|
-
|
|
101
|
+
unless @explore_deprecation_warned
|
|
102
|
+
warn "[weaviate-ruby] query.explore is deprecated: Weaviate disables Explore when more than one vectorizer module is enabled, " \
|
|
103
|
+
"which is the default on recent versions and on Weaviate Cloud. Use query.get on each collection instead."
|
|
104
|
+
@explore_deprecation_warned = true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# `after` is accepted for backwards compatibility, but Explore does not support it
|
|
108
|
+
data = client.graphql.execute(
|
|
92
109
|
explore_query(
|
|
93
110
|
fields: fields,
|
|
111
|
+
limit: limit,
|
|
112
|
+
offset: offset,
|
|
94
113
|
sort: sort,
|
|
95
114
|
where: where,
|
|
96
115
|
near_text: near_text,
|
|
97
116
|
near_vector: near_vector,
|
|
98
117
|
near_image: near_image,
|
|
99
118
|
near_object: near_object
|
|
100
|
-
)
|
|
101
|
-
after: after,
|
|
102
|
-
limit: limit,
|
|
103
|
-
offset: offset
|
|
119
|
+
)
|
|
104
120
|
)
|
|
105
|
-
|
|
106
|
-
rescue Graphlient::Errors::ExecutionError => error
|
|
107
|
-
raise Weaviate::Error.new(error.to_s)
|
|
121
|
+
data["Explore"]
|
|
108
122
|
end
|
|
109
123
|
|
|
110
124
|
private
|
|
111
125
|
|
|
112
126
|
def explore_query(
|
|
113
127
|
fields:,
|
|
128
|
+
limit: nil,
|
|
129
|
+
offset: nil,
|
|
114
130
|
where: nil,
|
|
115
131
|
near_text: nil,
|
|
116
132
|
near_vector: nil,
|
|
@@ -118,21 +134,20 @@ module Weaviate
|
|
|
118
134
|
near_object: nil,
|
|
119
135
|
sort: nil
|
|
120
136
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
) {
|
|
137
|
+
arguments = build_arguments(
|
|
138
|
+
limit: integer_value(limit),
|
|
139
|
+
offset: integer_value(offset),
|
|
140
|
+
nearText: near_text,
|
|
141
|
+
nearVector: near_vector,
|
|
142
|
+
nearImage: near_image,
|
|
143
|
+
nearObject: near_object,
|
|
144
|
+
where: where,
|
|
145
|
+
sort: sort
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
<<~GRAPHQL
|
|
149
|
+
{
|
|
150
|
+
Explore#{arguments} {
|
|
136
151
|
#{fields}
|
|
137
152
|
}
|
|
138
153
|
}
|
|
@@ -142,8 +157,11 @@ module Weaviate
|
|
|
142
157
|
def get_query(
|
|
143
158
|
class_name:,
|
|
144
159
|
fields:,
|
|
145
|
-
|
|
160
|
+
after: nil,
|
|
146
161
|
tenant: nil,
|
|
162
|
+
limit: nil,
|
|
163
|
+
autocut: nil,
|
|
164
|
+
offset: nil,
|
|
147
165
|
where: nil,
|
|
148
166
|
near_text: nil,
|
|
149
167
|
near_vector: nil,
|
|
@@ -152,31 +170,31 @@ module Weaviate
|
|
|
152
170
|
with_hybrid: nil,
|
|
153
171
|
bm25: nil,
|
|
154
172
|
ask: nil,
|
|
155
|
-
sort: nil
|
|
173
|
+
sort: nil,
|
|
174
|
+
group_by: nil
|
|
156
175
|
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
176
|
+
arguments = build_arguments(
|
|
177
|
+
after: string_value(after),
|
|
178
|
+
limit: integer_value(limit),
|
|
179
|
+
offset: integer_value(offset),
|
|
180
|
+
autocut: integer_value(autocut),
|
|
181
|
+
tenant: string_value(tenant),
|
|
182
|
+
nearText: near_text,
|
|
183
|
+
nearVector: near_vector,
|
|
184
|
+
nearImage: near_image,
|
|
185
|
+
nearObject: near_object,
|
|
186
|
+
hybrid: with_hybrid,
|
|
187
|
+
bm25: bm25,
|
|
188
|
+
ask: ask,
|
|
189
|
+
where: where,
|
|
190
|
+
sort: sort,
|
|
191
|
+
groupBy: group_by
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
<<~GRAPHQL
|
|
195
|
+
{
|
|
163
196
|
Get {
|
|
164
|
-
#{class_name}
|
|
165
|
-
after: $after,
|
|
166
|
-
limit: $limit,
|
|
167
|
-
offset: $offset,
|
|
168
|
-
#{(!autocut.nil?) ? "autocut: #{autocut}" : ""},
|
|
169
|
-
#{(!tenant.nil?) ? "tenant: \"#{tenant}\"" : ""},
|
|
170
|
-
#{(!near_text.nil?) ? "nearText: #{near_text}" : ""},
|
|
171
|
-
#{(!near_vector.nil?) ? "nearVector: #{near_vector}" : ""},
|
|
172
|
-
#{(!near_image.nil?) ? "nearImage: #{near_image}" : ""},
|
|
173
|
-
#{(!near_object.nil?) ? "nearObject: #{near_object}" : ""},
|
|
174
|
-
#{(!with_hybrid.nil?) ? "hybrid: #{with_hybrid}" : ""},
|
|
175
|
-
#{(!bm25.nil?) ? "bm25: #{bm25}" : ""},
|
|
176
|
-
#{(!ask.nil?) ? "ask: #{ask}" : ""},
|
|
177
|
-
#{(!where.nil?) ? "where: #{where}" : ""},
|
|
178
|
-
#{(!sort.nil?) ? "sort: #{sort}" : ""}
|
|
179
|
-
) {
|
|
197
|
+
#{class_name}#{arguments} {
|
|
180
198
|
#{fields}
|
|
181
199
|
}
|
|
182
200
|
}
|
|
@@ -189,32 +207,88 @@ module Weaviate
|
|
|
189
207
|
fields:,
|
|
190
208
|
tenant: nil,
|
|
191
209
|
where: nil,
|
|
210
|
+
object_limit: nil,
|
|
192
211
|
near_text: nil,
|
|
193
212
|
near_vector: nil,
|
|
194
213
|
near_image: nil,
|
|
195
|
-
near_object: nil
|
|
214
|
+
near_object: nil,
|
|
215
|
+
group_by: nil,
|
|
216
|
+
limit: nil,
|
|
217
|
+
with_hybrid: nil
|
|
196
218
|
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
219
|
+
arguments = build_arguments(
|
|
220
|
+
objectLimit: integer_value(object_limit),
|
|
221
|
+
limit: integer_value(limit),
|
|
222
|
+
groupBy: group_by && Array(group_by).map(&:to_s).to_json,
|
|
223
|
+
tenant: string_value(tenant),
|
|
224
|
+
nearText: near_text,
|
|
225
|
+
nearVector: near_vector,
|
|
226
|
+
nearImage: near_image,
|
|
227
|
+
nearObject: near_object,
|
|
228
|
+
hybrid: with_hybrid,
|
|
229
|
+
where: where
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
<<~GRAPHQL
|
|
233
|
+
{
|
|
202
234
|
Aggregate {
|
|
203
|
-
#{class_name}
|
|
204
|
-
objectLimit: $object_limit,
|
|
205
|
-
groupBy: $group_by,
|
|
206
|
-
#{(!tenant.nil?) ? "tenant: \"#{tenant}\"" : ""},
|
|
207
|
-
#{(!near_text.nil?) ? "nearText: #{near_text}" : ""},
|
|
208
|
-
#{(!near_vector.nil?) ? "nearVector: #{near_vector}" : ""},
|
|
209
|
-
#{(!near_image.nil?) ? "nearImage: #{near_image}" : ""},
|
|
210
|
-
#{(!near_object.nil?) ? "nearObject: #{near_object}" : ""},
|
|
211
|
-
#{(!where.nil?) ? "where: #{where}" : ""}
|
|
212
|
-
) {
|
|
235
|
+
#{class_name}#{arguments} {
|
|
213
236
|
#{fields}
|
|
214
237
|
}
|
|
215
238
|
}
|
|
216
239
|
}
|
|
217
240
|
GRAPHQL
|
|
218
241
|
end
|
|
242
|
+
|
|
243
|
+
# Only arguments that were provided are included in the query.
|
|
244
|
+
# Weaviate fails on explicit nulls, e.g. "interface conversion: interface {} is nil, not int".
|
|
245
|
+
# String arguments are passed through as raw GraphQL, anything else is serialized with {#graphql_value}.
|
|
246
|
+
def build_arguments(**arguments)
|
|
247
|
+
arguments = arguments.reject { |_, value| value.nil? }
|
|
248
|
+
return "" if arguments.empty?
|
|
249
|
+
|
|
250
|
+
arguments = arguments.map do |name, value|
|
|
251
|
+
"#{name}: #{value.is_a?(String) ? value : graphql_value(value)}"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
"(#{arguments.join(", ")})"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Serializes a Ruby value into a GraphQL input value:
|
|
258
|
+
# { path: ["category"], operator: :Equal, valueText: "SCIENCE" }
|
|
259
|
+
# => { path: ["category"], operator: Equal, valueText: "SCIENCE" }
|
|
260
|
+
# Strings are quoted and escaped, symbols become enums.
|
|
261
|
+
def graphql_value(value, key = nil)
|
|
262
|
+
case value
|
|
263
|
+
when Hash
|
|
264
|
+
"{ #{value.map { |k, v| "#{graphql_name(k)}: #{graphql_value(v, k.to_s)}" }.join(", ")} }"
|
|
265
|
+
when Array
|
|
266
|
+
"[#{value.map { |v| graphql_value(v, key) }.join(", ")}]"
|
|
267
|
+
when Symbol
|
|
268
|
+
graphql_name(value)
|
|
269
|
+
when String
|
|
270
|
+
(ENUM_KEYS.include?(key) && value.match?(GRAPHQL_NAME)) ? value : value.to_json
|
|
271
|
+
when Numeric, true, false
|
|
272
|
+
value.to_s
|
|
273
|
+
when nil
|
|
274
|
+
"null"
|
|
275
|
+
else
|
|
276
|
+
raise ArgumentError, "Cannot convert #{value.class} to a GraphQL value"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def graphql_name(name)
|
|
281
|
+
name = name.to_s
|
|
282
|
+
raise ArgumentError, "Invalid GraphQL name: #{name.inspect}" unless name.match?(GRAPHQL_NAME)
|
|
283
|
+
name
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def integer_value(value)
|
|
287
|
+
Integer(value) unless value.nil?
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def string_value(value)
|
|
291
|
+
value.to_s.to_json unless value.nil?
|
|
292
|
+
end
|
|
219
293
|
end
|
|
220
294
|
end
|