Class: Zvec::Doc

Inherits:
Object
  • Object
show all
Defined in:
lib/zvec/doc.rb

Overview

A document (row) in a zvec collection. Wraps the C++ Doc object and provides Ruby-friendly field access with automatic type coercion.

Documents can be created with or without a schema. With a schema, values are coerced and validated against declared field types and vector dimensions. Without a schema, types are auto-detected.

Examples:

Creating a document with a schema

doc = Zvec::Doc.new(pk: "doc-1", schema: schema)
doc["title"] = "Hello World"
doc["embedding"] = [0.1, 0.2, 0.3, 0.4]

Schema-less document (types auto-detected)

doc = Zvec::Doc.new(pk: "doc-2")
doc["name"] = "Alice"       # stored as string
doc["age"] = 30             # stored as int64
doc["score"] = 0.95         # stored as double
doc["active"] = true        # stored as bool
doc["vec"] = [1.0, 2.0]     # stored as float vector
doc["tags"] = ["a", "b"]    # stored as string array

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pk: nil, fields: {}, schema: nil) ⇒ Doc

Create a new document.

Examples:

doc = Zvec::Doc.new(pk: "abc", fields: { "title" => "Hello" }, schema: schema)

Parameters:

  • pk (String, Integer, nil) (defaults to: nil)

    primary key (converted to String)

  • fields (Hash{String, Symbol => Object}) (defaults to: {})

    initial field values

  • schema (Zvec::Schema, nil) (defaults to: nil)

    optional schema for type validation



35
36
37
38
39
40
# File 'lib/zvec/doc.rb', line 35

def initialize(pk: nil, fields: {}, schema: nil)
  @ext_doc = Ext::Doc.new
  @ext_doc.pk = pk.to_s if pk
  @schema = schema
  fields.each { |k, v| set(k, v) } if schema
end

Instance Attribute Details

#ext_docExt::Doc (readonly)

Returns the underlying C++ document object.

Returns:

  • (Ext::Doc)

    the underlying C++ document object



25
26
27
# File 'lib/zvec/doc.rb', line 25

def ext_doc
  @ext_doc
end

Class Method Details

.from_ext(ext_doc, schema: nil) ⇒ Zvec::Doc

Wrap a C++ Doc::Ptr into a Ruby Doc.

Parameters:

  • ext_doc (Ext::Doc)

    the C++ document to wrap

  • schema (Zvec::Schema, nil) (defaults to: nil)

    optional schema for type-aware access

Returns:



209
210
211
212
213
214
# File 'lib/zvec/doc.rb', line 209

def self.from_ext(ext_doc, schema: nil)
  doc = allocate
  doc.instance_variable_set(:@ext_doc, ext_doc)
  doc.instance_variable_set(:@schema, schema)
  doc
end

Instance Method Details

#[](field_name) ⇒ Object?

Read a field value by name (bracket accessor).

Examples:

doc["title"]  #=> "Hello"

Parameters:

  • field_name (String, Symbol)

    the field name

Returns:

  • (Object, nil)

    the field value, or nil if not set



67
68
69
# File 'lib/zvec/doc.rb', line 67

def [](field_name)
  get(field_name)
end

#[]=(field_name, value) ⇒ void

This method returns an undefined value.

Write a field value by name (bracket accessor).

Examples:

doc["title"] = "Hello"

Parameters:

  • field_name (String, Symbol)

    the field name

  • value (Object)

    the value to set



79
80
81
# File 'lib/zvec/doc.rb', line 79

def []=(field_name, value)
  set(field_name, value)
end

#empty?Boolean

Returns true if no fields have been set.

Returns:

  • (Boolean)

    true if no fields have been set



183
184
185
# File 'lib/zvec/doc.rb', line 183

def empty?
  @ext_doc.empty?
end

#field_namesArray<String>

Returns names of all fields set on this document.

Returns:

  • (Array<String>)

    names of all fields set on this document



178
179
180
# File 'lib/zvec/doc.rb', line 178

def field_names
  @ext_doc.field_names
end

#get(field_name) ⇒ Object?

Get a field value by name. Uses the schema getter if available, otherwise tries common types in order.

Examples:

doc.get("title")      #=> "Hello"
doc.get(:embedding)   #=> [0.1, 0.2, 0.3]
doc.get("missing")    #=> nil

Parameters:

  • field_name (String, Symbol)

    the field name

Returns:

  • (Object, nil)

    the value, or nil if not found or null



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/zvec/doc.rb', line 155

def get(field_name)
  field_name = field_name.to_s
  return nil unless @ext_doc.has?(field_name)
  return nil unless @ext_doc.has_value?(field_name)

  if @schema
    type = @schema.field_type(field_name)
    if type
      getter = DataTypes::GETTER_FOR[type]
      return @ext_doc.send(getter, field_name) if getter
    end
  end

  # Try common types in order
  %i[get_string get_int64 get_float get_double get_bool
     get_float_vector get_string_array].each do |m|
    val = @ext_doc.send(m, field_name)
    return val unless val.nil?
  end
  nil
end

#pkString

Returns the primary key.

Returns:

  • (String)

    the primary key



43
44
45
# File 'lib/zvec/doc.rb', line 43

def pk
  @ext_doc.pk
end

#pk=(value) ⇒ void

This method returns an undefined value.

Set the primary key.

Parameters:

  • value (String, Integer)

    the new primary key (converted to String)



51
52
53
# File 'lib/zvec/doc.rb', line 51

def pk=(value)
  @ext_doc.pk = value.to_s
end

#scoreFloat

Returns the similarity score (set after search queries).

Returns:

  • (Float)

    the similarity score (set after search queries)



56
57
58
# File 'lib/zvec/doc.rb', line 56

def score
  @score || @ext_doc.score
end

#set(field_name, value) ⇒ void

This method returns an undefined value.

Set a field value. When a schema is present, the value is coerced to the declared type and validated. Without a schema, the type is auto-detected from the Ruby value.

Examples:

doc.set("title", "Hello")
doc.set(:count, 42)
doc.set("embedding", [0.1, 0.2, 0.3])

Parameters:

  • field_name (String, Symbol)

    the field name (must be non-empty)

  • value (Object)

    the value to set (nil sets the field to null)

Raises:

  • (ArgumentError)

    if field_name is blank or value type is unsupported

  • (Zvec::DimensionError)

    if vector dimension doesn't match schema



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/zvec/doc.rb', line 97

def set(field_name, value)
  field_name = field_name.to_s
  raise ArgumentError, "Field name must be a non-empty string" if field_name.strip.empty?

  return @ext_doc.set_null(field_name) if value.nil?

  if @schema
    type = @schema.field_type(field_name)
    if type
      coerced = DataTypes.coerce_value(value, type, field_name: field_name)
      setter = DataTypes::SETTER_FOR[type]
      if setter
        # Validate vector dimension if schema has dimension info
        if DataTypes::VECTOR_TYPES.include?(type) && coerced.is_a?(Array)
          expected_dim = @schema.field_dimension(field_name)
          if expected_dim && !coerced.empty? && coerced.size != expected_dim
            raise DimensionError,
              "Vector dimension mismatch for field '#{field_name}': " \
              "expected #{expected_dim}, got #{coerced.size}"
          end
        end
        return @ext_doc.send(setter, field_name, coerced)
      end
    end
  end

  # Auto-detect type (schema-less mode)
  case value
  when String                then @ext_doc.set_string(field_name, value)
  when Integer               then @ext_doc.set_int64(field_name, value)
  when Float                 then @ext_doc.set_double(field_name, value)
  when TrueClass, FalseClass then @ext_doc.set_bool(field_name, value)
  when Array
    detected = DataTypes.detect_type(value)
    case detected
    when Ext::DataType::ARRAY_STRING
      @ext_doc.set_string_array(field_name, value.map { |v| v.nil? ? "" : v.to_s })
    else
      # Default: treat as float vector
      coerced = value.map { |v| v.nil? ? 0.0 : v.to_f }
      @ext_doc.set_float_vector(field_name, coerced)
    end
  else
    raise ArgumentError,
      "Unsupported value type #{value.class} for field '#{field_name}'"
  end
end

#to_hHash{String => Object}

Convert the document to a plain Ruby Hash.

Examples:

doc.to_h  #=> {"pk" => "doc-1", "score" => 0.95, "title" => "Hello"}

Returns:

  • (Hash{String => Object})

    includes "pk", "score", and all fields



193
194
195
196
197
# File 'lib/zvec/doc.rb', line 193

def to_h
  h = { "pk" => pk, "score" => score }
  field_names.each { |f| h[f] = get(f) }
  h
end

#to_sString

Returns human-readable representation.

Returns:

  • (String)

    human-readable representation



200
201
202
# File 'lib/zvec/doc.rb', line 200

def to_s
  @ext_doc.to_s
end