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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69922615a71c6eefbe25e734208f52099110685db3ad28073dd79e284ec3381b
4
- data.tar.gz: 47e713a1a9336e51dcb6e446e23d3364f2bcf8f23ff944a0b52e2a7d4e49acbf
3
+ metadata.gz: 735feec18b7fd1ae5ded62e4ed1be93fc11c72c7f26e4297cdad2959e364f5ae
4
+ data.tar.gz: 463e7962478cac322980777e1af86edd02ccf6109e4114afcbdf0738cb1ec635
5
5
  SHA512:
6
- metadata.gz: 7b366354338fd3d15e689340dbddf279ebf4b4d3823412ae1ef95d21eca48d1a06b492738b1aa21c9abb87e77906e2d3f07b67e71f80b61241e1f3143126946d
7
- data.tar.gz: fe83fd43d546c5e07e19fcc38a14760b2810955f5e3398ce4b4895c19280fa3c77dc88874a6bb771ff5572fd90a6a9ebd13bb0563c2a69fa40de5375b6fa34fa
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+\]f(?:16|32)\z}
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| Float(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
@@ -1,5 +1,5 @@
1
1
  module Turbopuffer
2
2
  module ActiveRecord
3
- VERSION = "0.1.4"
3
+ VERSION = "0.1.6"
4
4
  end
5
5
  end
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
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-22 00:00:00.000000000 Z
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