Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/vector.py: 58%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

113 statements  

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)