activerecord-turbopuffer-adapter 0.1.4 → 0.1.6
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/active_record/connection_adapters/turbopuffer_adapter.rb +7 -2
- data/lib/turbopuffer/active_record/schema/attribute.rb +4 -1
- data/lib/turbopuffer/active_record/schema/model.rb +5 -0
- data/lib/turbopuffer/active_record/type/bytes.rb +21 -0
- data/lib/turbopuffer/active_record/type/sparse_vector.rb +21 -0
- data/lib/turbopuffer/active_record/type/vector.rb +16 -2
- data/lib/turbopuffer/active_record/type.rb +2 -0
- data/lib/turbopuffer/active_record/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 735feec18b7fd1ae5ded62e4ed1be93fc11c72c7f26e4297cdad2959e364f5ae
|
|
4
|
+
data.tar.gz: 463e7962478cac322980777e1af86edd02ccf6109e4114afcbdf0738cb1ec635
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 961a01ae0f07741adf56396ef8df18ddb94896627d9ccee153b3f191ff34fda091136133ecc52e3c8e09b3726e9a45d70cde91957f1d9241f2cf5c122caabd38
|
|
7
|
+
data.tar.gz: ed62e9af80fd247549aecfe079f9d45f05037411dc631e310ef1faae981ba2581682335be96bfee6b3877cad4b8ae2d59936637a9179525349c375e9596671df
|
|
@@ -32,12 +32,17 @@ module ActiveRecord
|
|
|
32
32
|
m.register_type "float", Type::Float.new
|
|
33
33
|
m.register_type "bool", Type::Boolean.new
|
|
34
34
|
m.register_type "datetime", ::Turbopuffer::ActiveRecord::Type::DateTime.new
|
|
35
|
+
m.register_type "bytes", ::Turbopuffer::ActiveRecord::Type::Bytes.new
|
|
36
|
+
m.register_type "{}f16", ::Turbopuffer::ActiveRecord::Type::SparseVector.new
|
|
35
37
|
m.register_type(%r{\A\[\].+\z}) do |type|
|
|
36
38
|
::Turbopuffer::ActiveRecord::Type::Array.new(m.lookup(type.delete_prefix("[]")))
|
|
37
39
|
end
|
|
38
40
|
m.register_type(%r{\A\[\d+\]f(?:16|32)\z}) do |type|
|
|
39
41
|
::Turbopuffer::ActiveRecord::Type::Vector.new(type[/\d+/].to_i)
|
|
40
42
|
end
|
|
43
|
+
m.register_type(%r{\A\[\d+\]i8\z}) do |type|
|
|
44
|
+
::Turbopuffer::ActiveRecord::Type::Vector.new(type[/\d+/].to_i, integer: true)
|
|
45
|
+
end
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
|
|
@@ -96,7 +101,7 @@ module ActiveRecord
|
|
|
96
101
|
|
|
97
102
|
def type_cast(value)
|
|
98
103
|
case value
|
|
99
|
-
when Array
|
|
104
|
+
when Array, Hash
|
|
100
105
|
value
|
|
101
106
|
else
|
|
102
107
|
super
|
|
@@ -267,7 +272,7 @@ module ActiveRecord
|
|
|
267
272
|
|
|
268
273
|
def turbopuffer_select(namespace, query)
|
|
269
274
|
fields = if query.include_attributes == [ "*" ]
|
|
270
|
-
column_definitions(query.namespace).map(&:name)
|
|
275
|
+
column_definitions(query.namespace).reject(&:vector?).map(&:name)
|
|
271
276
|
else
|
|
272
277
|
query.include_attributes
|
|
273
278
|
end
|
|
@@ -2,7 +2,8 @@ module Turbopuffer
|
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module Schema
|
|
4
4
|
class Attribute
|
|
5
|
-
TYPES = %r{\A(?:\[\])?(?:string|uuid|int|uint|float|bool|datetime)\z|\A\[\d+\]
|
|
5
|
+
TYPES = %r{\A(?:\[\])?(?:string|uuid|int|uint|float|bool|datetime)\z|\Abytes\z|\A\[\d+\](?:f16|f32|i8)\z|\A\{\}f16\z}
|
|
6
|
+
VECTOR_TYPES = %r{\A\[\d+\]|\A\{\}}
|
|
6
7
|
|
|
7
8
|
attr_reader :name, :type, :filterable, :full_text_search, :ann, :glob, :regex, :notnull
|
|
8
9
|
|
|
@@ -24,6 +25,8 @@ module Turbopuffer
|
|
|
24
25
|
|
|
25
26
|
@notnull = not_null
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
def vector? = VECTOR_TYPES.match?(type)
|
|
27
30
|
end
|
|
28
31
|
end
|
|
29
32
|
end
|
|
@@ -78,6 +78,10 @@ module Turbopuffer
|
|
|
78
78
|
order(::Arel::Nodes::RankByNode.new(expression))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
def with_vectors
|
|
82
|
+
select(*turbopuffer_attributes.map(&:name))
|
|
83
|
+
end
|
|
84
|
+
|
|
81
85
|
def glob(pattern, case_sensitive: true)
|
|
82
86
|
::Turbopuffer::ActiveRecord::Glob.new(pattern, case_sensitive:)
|
|
83
87
|
end
|
|
@@ -115,6 +119,7 @@ module Turbopuffer
|
|
|
115
119
|
attrs[:ann] = true if attribute.ann
|
|
116
120
|
attrs[:glob] = true if attribute.glob
|
|
117
121
|
attrs[:regex] = true if attribute.regex
|
|
122
|
+
attrs[:sparse_knn] = { distance_metric: "dot_product" } if attribute.type == "{}f16"
|
|
118
123
|
|
|
119
124
|
schema[attribute.name] = attrs
|
|
120
125
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
|
|
3
|
+
module Turbopuffer
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Type
|
|
6
|
+
class Bytes < ::ActiveModel::Type::Value
|
|
7
|
+
def serialize(value)
|
|
8
|
+
return if value.nil?
|
|
9
|
+
|
|
10
|
+
Base64.strict_encode64(value)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def deserialize(value)
|
|
14
|
+
return if value.nil?
|
|
15
|
+
|
|
16
|
+
Base64.decode64(value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Turbopuffer
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Type
|
|
4
|
+
class SparseVector < ::ActiveModel::Type::Value
|
|
5
|
+
include ::ActiveModel::Type::Helpers::Mutable
|
|
6
|
+
|
|
7
|
+
def serialize(value)
|
|
8
|
+
return if value.nil?
|
|
9
|
+
|
|
10
|
+
value.to_h { |key, weight| [ key.to_s, Float(weight) ] }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def deserialize(value)
|
|
14
|
+
return if value.nil?
|
|
15
|
+
|
|
16
|
+
value.to_h.transform_keys(&:to_s)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,15 +4,16 @@ module Turbopuffer
|
|
|
4
4
|
class Vector < ::ActiveModel::Type::Value
|
|
5
5
|
include ::ActiveModel::Type::Helpers::Mutable
|
|
6
6
|
|
|
7
|
-
def initialize(dimensions)
|
|
7
|
+
def initialize(dimensions, integer: false)
|
|
8
8
|
@dimensions = dimensions
|
|
9
|
+
@integer = integer
|
|
9
10
|
super()
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def serialize(value)
|
|
13
14
|
return if value.nil?
|
|
14
15
|
|
|
15
|
-
vector = ::Array.wrap(value).map { |component|
|
|
16
|
+
vector = ::Array.wrap(value).map { |component| component(component) }
|
|
16
17
|
|
|
17
18
|
unless vector.size == @dimensions
|
|
18
19
|
raise ArgumentError, "expected a vector of #{@dimensions} dimensions, got #{vector.size}"
|
|
@@ -24,6 +25,19 @@ module Turbopuffer
|
|
|
24
25
|
def deserialize(value)
|
|
25
26
|
value
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def component(value)
|
|
32
|
+
number = Float(value)
|
|
33
|
+
return number unless @integer
|
|
34
|
+
|
|
35
|
+
unless number == number.to_i
|
|
36
|
+
raise ArgumentError, "expected an integer vector component, got #{value.inspect}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
number.to_i
|
|
40
|
+
end
|
|
27
41
|
end
|
|
28
42
|
end
|
|
29
43
|
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require "turbopuffer/active_record/type/date_time"
|
|
2
2
|
require "turbopuffer/active_record/type/unsigned_integer"
|
|
3
|
+
require "turbopuffer/active_record/type/bytes"
|
|
3
4
|
require "turbopuffer/active_record/type/array"
|
|
4
5
|
require "turbopuffer/active_record/type/vector"
|
|
6
|
+
require "turbopuffer/active_record/type/sparse_vector"
|
|
5
7
|
|
|
6
8
|
module Turbopuffer
|
|
7
9
|
module ActiveRecord
|
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.6
|
|
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-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '8.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: base64
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: railties
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -81,7 +95,9 @@ files:
|
|
|
81
95
|
- lib/turbopuffer/active_record/schema/model.rb
|
|
82
96
|
- lib/turbopuffer/active_record/type.rb
|
|
83
97
|
- lib/turbopuffer/active_record/type/array.rb
|
|
98
|
+
- lib/turbopuffer/active_record/type/bytes.rb
|
|
84
99
|
- lib/turbopuffer/active_record/type/date_time.rb
|
|
100
|
+
- lib/turbopuffer/active_record/type/sparse_vector.rb
|
|
85
101
|
- lib/turbopuffer/active_record/type/unsigned_integer.rb
|
|
86
102
|
- lib/turbopuffer/active_record/type/vector.rb
|
|
87
103
|
- lib/turbopuffer/active_record/version.rb
|