activerecord-turbopuffer-adapter 0.1.2 → 0.1.3
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc9b47b80bd694b53052b54ca2647989e6bfd911c1a0f2e59056d61cfccf075e
|
|
4
|
+
data.tar.gz: a306ba71a2c62db295b438c6583f61a765d425a8f2888c328b831df31bf9840b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f73376471edf444dfc0f2c5dd702d2727a280c307f440da3a367d7d594074a9654e8b20fa58c19c2c6e57a4ab393dbacf17bb8044be82867e9fe7528d5b78874
|
|
7
|
+
data.tar.gz: 8988590d661ac5c872df12f3e467f939bdb71f291d9485eab31227d3ea3afa3773f8ad8d885f2a91e3b8d3856e92835fc1a2381ceee79978185206b6f52e5479
|
data/README.md
CHANGED
|
@@ -105,6 +105,8 @@ Document.find("018f...")
|
|
|
105
105
|
Document.rank_by("vector", "ANN", query_vector).limit(10)
|
|
106
106
|
Document.rank_by("text", "BM25", "quick walrus").limit(10)
|
|
107
107
|
|
|
108
|
+
Document.consistency(:eventual).where(published: true)
|
|
109
|
+
|
|
108
110
|
Document
|
|
109
111
|
.where(published: true)
|
|
110
112
|
.rank_by(["Sum", [
|
|
@@ -116,6 +118,8 @@ Document
|
|
|
116
118
|
|
|
117
119
|
> Note that turbopuffer has a limit on the maximum number of documents returned (https://turbopuffer.com/docs/query#param-limit), so Document.all.to_a etc. will only return at most 10,000 items
|
|
118
120
|
|
|
121
|
+
> Eventual reads are cheaper but may not include writes from the last few seconds. Set a default with `turbopuffer_consistency "eventual"` in a model or `consistency: eventual` in `database.yml`. Strong is the default.
|
|
122
|
+
|
|
119
123
|
### Using alongside Postgres
|
|
120
124
|
|
|
121
125
|
While something of a lark, aspirationally the idea of this gem is to make turbopuffer conveniently usable as the primary db in a Rails app. In practice, however, using turbopuffer alongside a traditional primary db (such as postgres) is a supported, potentially more practical solution.
|
|
@@ -225,6 +225,14 @@ module ActiveRecord
|
|
|
225
225
|
0
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
+
def consistency_for(query)
|
|
229
|
+
model = ::Turbopuffer::ActiveRecord::Schema::Model.for_table(query.namespace)
|
|
230
|
+
level = query.consistency || model.turbopuffer_consistency || @config[:consistency]
|
|
231
|
+
return if level.nil?
|
|
232
|
+
|
|
233
|
+
{ level: ::Turbopuffer::ActiveRecord::Schema::Model.validate_consistency(level).to_sym }
|
|
234
|
+
end
|
|
235
|
+
|
|
228
236
|
def turbopuffer_aggregate(namespace, query)
|
|
229
237
|
aggregate_alias, aggregate = query.aggregate_by
|
|
230
238
|
|
|
@@ -233,6 +241,8 @@ module ActiveRecord
|
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
tpuf_query_args[:filters] = query.filters if query.filters
|
|
244
|
+
consistency = consistency_for(query)
|
|
245
|
+
tpuf_query_args[:consistency] = consistency if consistency
|
|
236
246
|
|
|
237
247
|
if query.group_by.present?
|
|
238
248
|
tpuf_query_args[:group_by] = query.group_by
|
|
@@ -269,6 +279,8 @@ module ActiveRecord
|
|
|
269
279
|
|
|
270
280
|
tpuf_query_args[:rank_by] = query.rank_by.first if query.rank_by.present?
|
|
271
281
|
tpuf_query_args[:filters] = query.filters if query.filters
|
|
282
|
+
consistency = consistency_for(query)
|
|
283
|
+
tpuf_query_args[:consistency] = consistency if consistency
|
|
272
284
|
|
|
273
285
|
tpuf_result = namespace.query(tpuf_query_args)
|
|
274
286
|
|
|
@@ -10,7 +10,8 @@ module Arel::Visitors
|
|
|
10
10
|
rank_by: @rank_by,
|
|
11
11
|
include_attributes: @include_attributes,
|
|
12
12
|
aggregate_by: @aggregate_by,
|
|
13
|
-
upsert_rows: @upsert_rows
|
|
13
|
+
upsert_rows: @upsert_rows,
|
|
14
|
+
consistency: @consistency
|
|
14
15
|
}
|
|
15
16
|
end
|
|
16
17
|
def to_s = JSON.generate(to_h)
|
|
@@ -19,10 +20,11 @@ module Arel::Visitors
|
|
|
19
20
|
def eql?(other) = other.is_a?(self.class) && to_h == other.to_h
|
|
20
21
|
alias == eql?
|
|
21
22
|
|
|
22
|
-
attr_reader :op, :namespace, :filters, :top_k, :rank_by, :include_attributes, :upsert_rows, :aggregate_by, :group_by
|
|
23
|
+
attr_reader :op, :namespace, :filters, :top_k, :rank_by, :include_attributes, :upsert_rows, :aggregate_by, :group_by,
|
|
24
|
+
:consistency
|
|
23
25
|
|
|
24
|
-
def initialize(op:, namespace:, filters: nil, top_k: nil, rank_by: nil,
|
|
25
|
-
|
|
26
|
+
def initialize(op:, namespace:, filters: nil, top_k: nil, rank_by: nil, include_attributes: nil,
|
|
27
|
+
upsert_rows: nil, aggregate_by: nil, group_by: nil, consistency: nil)
|
|
26
28
|
@op = op
|
|
27
29
|
@namespace = namespace
|
|
28
30
|
@filters = filters
|
|
@@ -32,6 +34,7 @@ module Arel::Visitors
|
|
|
32
34
|
@aggregate_by = aggregate_by
|
|
33
35
|
@group_by = group_by
|
|
34
36
|
@upsert_rows = upsert_rows
|
|
37
|
+
@consistency = consistency
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
@@ -120,10 +123,18 @@ module Arel::Visitors
|
|
|
120
123
|
rank_by: o.orders.map { |ord| visit(ord) },
|
|
121
124
|
include_attributes: attributes.flat_map { |p| visit(p) },
|
|
122
125
|
group_by: core.groups.map { |g| visit(g) },
|
|
123
|
-
aggregate_by: aggregate && visit(aggregate)
|
|
126
|
+
aggregate_by: aggregate && visit(aggregate),
|
|
127
|
+
consistency: consistency_hint(core.optimizer_hints)
|
|
124
128
|
)
|
|
125
129
|
end
|
|
126
130
|
|
|
131
|
+
def consistency_hint(hints)
|
|
132
|
+
return unless hints
|
|
133
|
+
|
|
134
|
+
hint = hints.expr.reverse.find { |h| h.start_with?("consistency=") }
|
|
135
|
+
hint&.delete_prefix("consistency=")
|
|
136
|
+
end
|
|
137
|
+
|
|
127
138
|
def visit_Arel_Nodes_InsertStatement(o)
|
|
128
139
|
raise NotImplementedError, "INSERT ... SELECT" if o.select
|
|
129
140
|
|
|
@@ -5,10 +5,12 @@ module Turbopuffer
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
DISTANCE_METRICS = [ "cosine_distance", "euclidean_squared" ].freeze
|
|
8
|
+
CONSISTENCY_LEVELS = [ "strong", "eventual" ].freeze
|
|
8
9
|
|
|
9
10
|
included do
|
|
10
11
|
class_attribute :turbopuffer_attributes, instance_accessor: false, default: [].freeze
|
|
11
12
|
class_attribute :_turbopuffer_distance_metric, instance_accessor: false, default: "cosine_distance"
|
|
13
|
+
class_attribute :_turbopuffer_consistency, instance_accessor: false, default: nil
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
class << self
|
|
@@ -32,6 +34,17 @@ module Turbopuffer
|
|
|
32
34
|
|
|
33
35
|
model
|
|
34
36
|
end
|
|
37
|
+
|
|
38
|
+
def validate_consistency(level)
|
|
39
|
+
level = level.to_s
|
|
40
|
+
|
|
41
|
+
unless CONSISTENCY_LEVELS.include?(level)
|
|
42
|
+
raise ArgumentError,
|
|
43
|
+
"consistency must be one of #{CONSISTENCY_LEVELS.join(", ")}, got #{level.inspect}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
level
|
|
47
|
+
end
|
|
35
48
|
end
|
|
36
49
|
|
|
37
50
|
class_methods do
|
|
@@ -50,6 +63,8 @@ module Turbopuffer
|
|
|
50
63
|
def turbopuffer_distance_metric(value = nil)
|
|
51
64
|
return _turbopuffer_distance_metric if value.nil?
|
|
52
65
|
|
|
66
|
+
value = value.to_s
|
|
67
|
+
|
|
53
68
|
unless DISTANCE_METRICS.include?(value)
|
|
54
69
|
raise ArgumentError,
|
|
55
70
|
"distance metric must be one of #{DISTANCE_METRICS.join(", ")}, got #{value.inspect}"
|
|
@@ -67,6 +82,16 @@ module Turbopuffer
|
|
|
67
82
|
::Turbopuffer::ActiveRecord::Glob.new(pattern, case_sensitive:)
|
|
68
83
|
end
|
|
69
84
|
|
|
85
|
+
def consistency(level)
|
|
86
|
+
optimizer_hints("consistency=#{Model.validate_consistency(level)}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def turbopuffer_consistency(value = nil)
|
|
90
|
+
return _turbopuffer_consistency if value.nil?
|
|
91
|
+
|
|
92
|
+
self._turbopuffer_consistency = Model.validate_consistency(value)
|
|
93
|
+
end
|
|
94
|
+
|
|
70
95
|
def predicate_builder
|
|
71
96
|
@predicate_builder ||= super.tap do |builder|
|
|
72
97
|
builder.register_handler(::Regexp, lambda { |attribute, regexp|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-turbopuffer-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Monette
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|