1# dialects/oracle/vector.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9
10from __future__ import annotations
11
12import array
13from dataclasses import dataclass
14from enum import Enum
15from typing import Optional
16from typing import Union
17
18from ... import types
19from ...sql.operators import OperatorClass
20from ...types import Float
21
22
23class VectorIndexType(Enum):
24 """Enum representing different types of VECTOR index structures.
25
26 See :ref:`oracle_vector_datatype` for background.
27
28 .. versionadded:: 2.0.41
29
30 """
31
32 HNSW = "HNSW"
33 """
34 The HNSW (Hierarchical Navigable Small World) index type.
35 """
36 IVF = "IVF"
37 """
38 The IVF (Inverted File Index) index type
39 """
40
41
42class VectorDistanceType(Enum):
43 """Enum representing different types of vector distance metrics.
44
45 See :ref:`oracle_vector_datatype` for background.
46
47 .. versionadded:: 2.0.41
48
49 """
50
51 EUCLIDEAN = "EUCLIDEAN"
52 """Euclidean distance (L2 norm).
53
54 Measures the straight-line distance between two vectors in space.
55 """
56 DOT = "DOT"
57 """Dot product similarity.
58
59 Measures the algebraic similarity between two vectors.
60 """
61 COSINE = "COSINE"
62 """Cosine similarity.
63
64 Measures the cosine of the angle between two vectors.
65 """
66 MANHATTAN = "MANHATTAN"
67 """Manhattan distance (L1 norm).
68
69 Calculates the sum of absolute differences across dimensions.
70 """
71
72
73class VectorStorageFormat(Enum):
74 """Enum representing the data format used to store vector components.
75
76 See :ref:`oracle_vector_datatype` for background.
77
78 .. versionadded:: 2.0.41
79
80 """
81
82 INT8 = "INT8"
83 """
84 8-bit integer format.
85 """
86 BINARY = "BINARY"
87 """
88 Binary format.
89 """
90 FLOAT32 = "FLOAT32"
91 """
92 32-bit floating-point format.
93 """
94 FLOAT64 = "FLOAT64"
95 """
96 64-bit floating-point format.
97 """
98
99
100class VectorStorageType(Enum):
101 """Enum representing the vector type,
102
103 See :ref:`oracle_vector_datatype` for background.
104
105 .. versionadded:: 2.0.43
106
107 """
108
109 SPARSE = "SPARSE"
110 """
111 A Sparse vector is a vector which has zero value for
112 most of its dimensions.
113 """
114 DENSE = "DENSE"
115 """
116 A Dense vector is a vector where most, if not all, elements
117 hold meaningful values.
118 """
119
120
121@dataclass
122class VectorIndexConfig:
123 """Define the configuration for Oracle VECTOR Index.
124
125 See :ref:`oracle_vector_datatype` for background.
126
127 .. versionadded:: 2.0.41
128
129 :param index_type: Enum value from :class:`.VectorIndexType`
130 Specifies the indexing method. For HNSW, this must be
131 :attr:`.VectorIndexType.HNSW`.
132
133 :param distance: Enum value from :class:`.VectorDistanceType`
134 specifies the metric for calculating distance between VECTORS.
135
136 :param accuracy: integer. Should be in the range 0 to 100
137 Specifies the accuracy of the nearest neighbor search during
138 query execution.
139
140 :param parallel: integer. Specifies degree of parallelism.
141
142 :param hnsw_neighbors: integer. Should be in the range 0 to
143 2048. Specifies the number of nearest neighbors considered
144 during the search. The attribute :attr:`.VectorIndexConfig.hnsw_neighbors`
145 is HNSW index specific.
146
147 :param hnsw_efconstruction: integer. Should be in the range 0
148 to 65535. Controls the trade-off between indexing speed and
149 recall quality during index construction. The attribute
150 :attr:`.VectorIndexConfig.hnsw_efconstruction` is HNSW index
151 specific.
152
153 :param ivf_neighbor_partitions: integer. Should be in the range
154 0 to 10,000,000. Specifies the number of partitions used to
155 divide the dataset. The attribute
156 :attr:`.VectorIndexConfig.ivf_neighbor_partitions` is IVF index
157 specific.
158
159 :param ivf_sample_per_partition: integer. Should be between 1
160 and ``num_vectors / neighbor partitions``. Specifies the
161 number of samples used per partition. The attribute
162 :attr:`.VectorIndexConfig.ivf_sample_per_partition` is IVF index
163 specific.
164
165 :param ivf_min_vectors_per_partition: integer. From 0 (no trimming)
166 to the total number of vectors (results in 1 partition). Specifies
167 the minimum number of vectors per partition. The attribute
168 :attr:`.VectorIndexConfig.ivf_min_vectors_per_partition`
169 is IVF index specific.
170
171 """
172
173 index_type: VectorIndexType = VectorIndexType.HNSW
174 distance: Optional[VectorDistanceType] = None
175 accuracy: Optional[int] = None
176 hnsw_neighbors: Optional[int] = None
177 hnsw_efconstruction: Optional[int] = None
178 ivf_neighbor_partitions: Optional[int] = None
179 ivf_sample_per_partition: Optional[int] = None
180 ivf_min_vectors_per_partition: Optional[int] = None
181 parallel: Optional[int] = None
182
183 def __post_init__(self):
184 self.index_type = VectorIndexType(self.index_type)
185 for field in [
186 "hnsw_neighbors",
187 "hnsw_efconstruction",
188 "ivf_neighbor_partitions",
189 "ivf_sample_per_partition",
190 "ivf_min_vectors_per_partition",
191 "parallel",
192 "accuracy",
193 ]:
194 value = getattr(self, field)
195 if value is not None and not isinstance(value, int):
196 raise TypeError(
197 f"{field} must be an integer if"
198 f"provided, got {type(value).__name__}"
199 )
200
201
202class SparseVector:
203 """
204 Lightweight SQLAlchemy-side version of SparseVector.
205 This mimics oracledb.SparseVector.
206
207 .. versionadded:: 2.0.43
208
209 """
210
211 def __init__(
212 self,
213 num_dimensions: int,
214 indices: Union[list, array.array],
215 values: Union[list, array.array],
216 ):
217 if not isinstance(indices, array.array) or indices.typecode != "I":
218 indices = array.array("I", indices)
219 if not isinstance(values, array.array):
220 values = array.array("d", values)
221 if len(indices) != len(values):
222 raise TypeError("indices and values must be of the same length!")
223
224 self.num_dimensions = num_dimensions
225 self.indices = indices
226 self.values = values
227
228 def __str__(self):
229 return (
230 f"SparseVector(num_dimensions={self.num_dimensions}, "
231 f"size={len(self.indices)}, typecode={self.values.typecode})"
232 )
233
234
235class VECTOR(types.TypeEngine):
236 """Oracle VECTOR datatype.
237
238 For complete background on using this type, see
239 :ref:`oracle_vector_datatype`.
240
241 .. versionadded:: 2.0.41
242
243 """
244
245 cache_ok = True
246
247 operator_classes = OperatorClass.BASE | OperatorClass.MATH
248
249 __visit_name__ = "VECTOR"
250
251 _typecode_map = {
252 VectorStorageFormat.INT8: "b", # Signed int
253 VectorStorageFormat.BINARY: "B", # Unsigned int
254 VectorStorageFormat.FLOAT32: "f", # Float
255 VectorStorageFormat.FLOAT64: "d", # Double
256 }
257
258 def __init__(self, dim=None, storage_format=None, storage_type=None):
259 """Construct a VECTOR.
260
261 :param dim: integer. The dimension of the VECTOR datatype. This
262 should be an integer value.
263
264 :param storage_format: VectorStorageFormat. The VECTOR storage
265 type format. This should be Enum values form
266 :class:`.VectorStorageFormat` INT8, BINARY, FLOAT32, or FLOAT64.
267
268 :param storage_type: VectorStorageType. The Vector storage type. This
269 should be Enum values from :class:`.VectorStorageType` SPARSE or
270 DENSE.
271
272 """
273
274 if dim is not None and not isinstance(dim, int):
275 raise TypeError("dim must be an integer")
276 if storage_format is not None and not isinstance(
277 storage_format, VectorStorageFormat
278 ):
279 raise TypeError(
280 "storage_format must be an enum of type VectorStorageFormat"
281 )
282 if storage_type is not None and not isinstance(
283 storage_type, VectorStorageType
284 ):
285 raise TypeError(
286 "storage_type must be an enum of type VectorStorageType"
287 )
288
289 self.dim = dim
290 self.storage_format = storage_format
291 self.storage_type = storage_type
292
293 def _cached_bind_processor(self, dialect):
294 """
295 Converts a Python-side SparseVector instance into an
296 oracledb.SparseVectormor a compatible array format before
297 binding it to the database.
298 """
299
300 def process(value):
301 if value is None or isinstance(value, array.array):
302 return value
303
304 # Convert list to a array.array
305 elif isinstance(value, list):
306 typecode = self._array_typecode(self.storage_format)
307 value = array.array(typecode, value)
308 return value
309
310 # Convert SqlAlchemy SparseVector to oracledb SparseVector object
311 elif isinstance(value, SparseVector):
312 return dialect.dbapi.SparseVector(
313 value.num_dimensions,
314 value.indices,
315 value.values,
316 )
317
318 else:
319 raise TypeError("""
320 Invalid input for VECTOR: expected a list, an array.array,
321 or a SparseVector object.
322 """)
323
324 return process
325
326 def _cached_result_processor(self, dialect, coltype):
327 """
328 Converts database-returned values into Python-native representations.
329 If the value is an oracledb.SparseVector, it is converted into the
330 SQLAlchemy-side SparseVector class.
331 If the value is a array.array, it is converted to a plain Python list.
332
333 """
334
335 def process(value):
336 if value is None:
337 return None
338
339 elif isinstance(value, array.array):
340 return list(value)
341
342 # Convert Oracledb SparseVector to SqlAlchemy SparseVector object
343 elif isinstance(value, dialect.dbapi.SparseVector):
344 return SparseVector(
345 num_dimensions=value.num_dimensions,
346 indices=value.indices,
347 values=value.values,
348 )
349
350 return process
351
352 def _array_typecode(self, typecode):
353 """
354 Map storage format to array typecode.
355 """
356 return self._typecode_map.get(typecode, "d")
357
358 class comparator_factory(types.TypeEngine.Comparator):
359 def l2_distance(self, other):
360 return self.op("<->", return_type=Float)(other)
361
362 def inner_product(self, other):
363 return self.op("<#>", return_type=Float)(other)
364
365 def cosine_distance(self, other):
366 return self.op("<=>", return_type=Float)(other)