SQLAlchemy 2.0.42__py3-none-any.whl → 2.0.44__py3-none-any.whl
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.
- sqlalchemy/__init__.py +1 -1
- sqlalchemy/connectors/asyncio.py +81 -3
- sqlalchemy/connectors/pyodbc.py +5 -0
- sqlalchemy/dialects/mssql/base.py +5 -0
- sqlalchemy/dialects/mysql/aiomysql.py +13 -4
- sqlalchemy/dialects/mysql/asyncmy.py +13 -4
- sqlalchemy/dialects/mysql/mariadbconnector.py +3 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +3 -0
- sqlalchemy/dialects/mysql/mysqldb.py +3 -0
- sqlalchemy/dialects/oracle/__init__.py +4 -0
- sqlalchemy/dialects/oracle/base.py +79 -24
- sqlalchemy/dialects/oracle/cx_oracle.py +3 -0
- sqlalchemy/dialects/oracle/oracledb.py +2 -4
- sqlalchemy/dialects/oracle/vector.py +104 -6
- sqlalchemy/dialects/postgresql/_psycopg_common.py +3 -0
- sqlalchemy/dialects/postgresql/array.py +15 -5
- sqlalchemy/dialects/postgresql/asyncpg.py +21 -27
- sqlalchemy/dialects/postgresql/json.py +28 -0
- sqlalchemy/dialects/postgresql/pg8000.py +3 -0
- sqlalchemy/dialects/postgresql/psycopg.py +3 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +3 -0
- sqlalchemy/dialects/sqlite/base.py +44 -13
- sqlalchemy/dialects/sqlite/pysqlite.py +69 -36
- sqlalchemy/engine/base.py +11 -5
- sqlalchemy/engine/create.py +15 -0
- sqlalchemy/engine/default.py +7 -0
- sqlalchemy/engine/interfaces.py +32 -0
- sqlalchemy/engine/result.py +1 -1
- sqlalchemy/event/__init__.py +1 -0
- sqlalchemy/event/attr.py +35 -14
- sqlalchemy/event/legacy.py +15 -3
- sqlalchemy/ext/asyncio/engine.py +4 -2
- sqlalchemy/ext/asyncio/result.py +3 -0
- sqlalchemy/ext/asyncio/scoping.py +0 -14
- sqlalchemy/ext/asyncio/session.py +0 -14
- sqlalchemy/ext/hybrid.py +8 -6
- sqlalchemy/ext/indexable.py +37 -19
- sqlalchemy/ext/mutable.py +26 -37
- sqlalchemy/orm/__init__.py +1 -0
- sqlalchemy/orm/decl_api.py +95 -11
- sqlalchemy/orm/descriptor_props.py +5 -1
- sqlalchemy/orm/events.py +60 -77
- sqlalchemy/orm/loading.py +13 -9
- sqlalchemy/orm/persistence.py +8 -2
- sqlalchemy/orm/properties.py +77 -26
- sqlalchemy/orm/query.py +16 -10
- sqlalchemy/orm/scoping.py +0 -14
- sqlalchemy/orm/session.py +0 -14
- sqlalchemy/orm/writeonly.py +2 -2
- sqlalchemy/sql/dml.py +19 -6
- sqlalchemy/sql/elements.py +36 -1
- sqlalchemy/sql/functions.py +2 -4
- sqlalchemy/sql/schema.py +7 -3
- sqlalchemy/sql/selectable.py +40 -24
- sqlalchemy/sql/sqltypes.py +3 -3
- sqlalchemy/testing/plugin/pytestplugin.py +9 -3
- sqlalchemy/testing/profiling.py +3 -0
- sqlalchemy/testing/requirements.py +19 -1
- sqlalchemy/testing/suite/test_dialect.py +37 -1
- sqlalchemy/testing/suite/test_reflection.py +20 -0
- sqlalchemy/util/__init__.py +2 -0
- sqlalchemy/util/compat.py +13 -0
- sqlalchemy/util/concurrency.py +2 -0
- sqlalchemy/util/langhelpers.py +8 -5
- sqlalchemy/util/typing.py +3 -2
- {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.44.dist-info}/METADATA +2 -2
- {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.44.dist-info}/RECORD +70 -70
- {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.44.dist-info}/WHEEL +0 -0
- {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.44.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.44.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
sqlalchemy/connectors/asyncio.py
CHANGED
|
@@ -19,11 +19,15 @@ from typing import Iterator
|
|
|
19
19
|
from typing import NoReturn
|
|
20
20
|
from typing import Optional
|
|
21
21
|
from typing import Sequence
|
|
22
|
+
from typing import Tuple
|
|
23
|
+
from typing import Type
|
|
22
24
|
from typing import TYPE_CHECKING
|
|
23
25
|
|
|
24
26
|
from ..engine import AdaptedConnection
|
|
27
|
+
from ..util import EMPTY_DICT
|
|
25
28
|
from ..util.concurrency import await_fallback
|
|
26
29
|
from ..util.concurrency import await_only
|
|
30
|
+
from ..util.concurrency import in_greenlet
|
|
27
31
|
from ..util.typing import Protocol
|
|
28
32
|
|
|
29
33
|
if TYPE_CHECKING:
|
|
@@ -129,8 +133,11 @@ class AsyncAdapt_dbapi_cursor:
|
|
|
129
133
|
"await_",
|
|
130
134
|
"_cursor",
|
|
131
135
|
"_rows",
|
|
136
|
+
"_soft_closed_memoized",
|
|
132
137
|
)
|
|
133
138
|
|
|
139
|
+
_awaitable_cursor_close: bool = True
|
|
140
|
+
|
|
134
141
|
_cursor: AsyncIODBAPICursor
|
|
135
142
|
_adapt_connection: AsyncAdapt_dbapi_connection
|
|
136
143
|
_connection: AsyncIODBAPIConnection
|
|
@@ -144,7 +151,7 @@ class AsyncAdapt_dbapi_cursor:
|
|
|
144
151
|
|
|
145
152
|
cursor = self._make_new_cursor(self._connection)
|
|
146
153
|
self._cursor = self._aenter_cursor(cursor)
|
|
147
|
-
|
|
154
|
+
self._soft_closed_memoized = EMPTY_DICT
|
|
148
155
|
if not self.server_side:
|
|
149
156
|
self._rows = collections.deque()
|
|
150
157
|
|
|
@@ -158,6 +165,8 @@ class AsyncAdapt_dbapi_cursor:
|
|
|
158
165
|
|
|
159
166
|
@property
|
|
160
167
|
def description(self) -> Optional[_DBAPICursorDescription]:
|
|
168
|
+
if "description" in self._soft_closed_memoized:
|
|
169
|
+
return self._soft_closed_memoized["description"] # type: ignore[no-any-return] # noqa: E501
|
|
161
170
|
return self._cursor.description
|
|
162
171
|
|
|
163
172
|
@property
|
|
@@ -176,11 +185,40 @@ class AsyncAdapt_dbapi_cursor:
|
|
|
176
185
|
def lastrowid(self) -> int:
|
|
177
186
|
return self._cursor.lastrowid
|
|
178
187
|
|
|
188
|
+
async def _async_soft_close(self) -> None:
|
|
189
|
+
"""close the cursor but keep the results pending, and memoize the
|
|
190
|
+
description.
|
|
191
|
+
|
|
192
|
+
.. versionadded:: 2.0.44
|
|
193
|
+
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
if not self._awaitable_cursor_close or self.server_side:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
self._soft_closed_memoized = self._soft_closed_memoized.union(
|
|
200
|
+
{
|
|
201
|
+
"description": self._cursor.description,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
await self._cursor.close()
|
|
205
|
+
|
|
179
206
|
def close(self) -> None:
|
|
180
|
-
# note we aren't actually closing the cursor here,
|
|
181
|
-
# we are just letting GC do it. see notes in aiomysql dialect
|
|
182
207
|
self._rows.clear()
|
|
183
208
|
|
|
209
|
+
# updated as of 2.0.44
|
|
210
|
+
# try to "close" the cursor based on what we know about the driver
|
|
211
|
+
# and if we are able to. otherwise, hope that the asyncio
|
|
212
|
+
# extension called _async_soft_close() if the cursor is going into
|
|
213
|
+
# a sync context
|
|
214
|
+
if self._cursor is None or bool(self._soft_closed_memoized):
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if not self._awaitable_cursor_close:
|
|
218
|
+
self._cursor.close() # type: ignore[unused-coroutine]
|
|
219
|
+
elif in_greenlet():
|
|
220
|
+
self.await_(self._cursor.close())
|
|
221
|
+
|
|
184
222
|
def execute(
|
|
185
223
|
self,
|
|
186
224
|
operation: Any,
|
|
@@ -349,3 +387,43 @@ class AsyncAdaptFallback_dbapi_connection(AsyncAdapt_dbapi_connection):
|
|
|
349
387
|
__slots__ = ()
|
|
350
388
|
|
|
351
389
|
await_ = staticmethod(await_fallback)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class AsyncAdapt_terminate:
|
|
393
|
+
"""Mixin for a AsyncAdapt_dbapi_connection to add terminate support."""
|
|
394
|
+
|
|
395
|
+
__slots__ = ()
|
|
396
|
+
|
|
397
|
+
def terminate(self) -> None:
|
|
398
|
+
if in_greenlet():
|
|
399
|
+
# in a greenlet; this is the connection was invalidated case.
|
|
400
|
+
try:
|
|
401
|
+
# try to gracefully close; see #10717
|
|
402
|
+
self.await_(asyncio.shield(self._terminate_graceful_close())) # type: ignore[attr-defined] # noqa: E501
|
|
403
|
+
except self._terminate_handled_exceptions() as e:
|
|
404
|
+
# in the case where we are recycling an old connection
|
|
405
|
+
# that may have already been disconnected, close() will
|
|
406
|
+
# fail. In this case, terminate
|
|
407
|
+
# the connection without any further waiting.
|
|
408
|
+
# see issue #8419
|
|
409
|
+
self._terminate_force_close()
|
|
410
|
+
if isinstance(e, asyncio.CancelledError):
|
|
411
|
+
# re-raise CancelledError if we were cancelled
|
|
412
|
+
raise
|
|
413
|
+
else:
|
|
414
|
+
# not in a greenlet; this is the gc cleanup case
|
|
415
|
+
self._terminate_force_close()
|
|
416
|
+
|
|
417
|
+
def _terminate_handled_exceptions(self) -> Tuple[Type[BaseException], ...]:
|
|
418
|
+
"""Returns the exceptions that should be handled when
|
|
419
|
+
calling _graceful_close.
|
|
420
|
+
"""
|
|
421
|
+
return (asyncio.TimeoutError, asyncio.CancelledError, OSError)
|
|
422
|
+
|
|
423
|
+
async def _terminate_graceful_close(self) -> None:
|
|
424
|
+
"""Try to close connection gracefully"""
|
|
425
|
+
raise NotImplementedError
|
|
426
|
+
|
|
427
|
+
def _terminate_force_close(self) -> None:
|
|
428
|
+
"""Terminate the connection"""
|
|
429
|
+
raise NotImplementedError
|
sqlalchemy/connectors/pyodbc.py
CHANGED
|
@@ -243,3 +243,8 @@ class PyODBCConnector(Connector):
|
|
|
243
243
|
else:
|
|
244
244
|
dbapi_connection.autocommit = False
|
|
245
245
|
super().set_isolation_level(dbapi_connection, level)
|
|
246
|
+
|
|
247
|
+
def detect_autocommit_setting(
|
|
248
|
+
self, dbapi_conn: interfaces.DBAPIConnection
|
|
249
|
+
) -> bool:
|
|
250
|
+
return bool(dbapi_conn.autocommit)
|
|
@@ -3503,6 +3503,9 @@ join sys.schemas as sch on
|
|
|
3503
3503
|
where
|
|
3504
3504
|
tab.name = :tabname
|
|
3505
3505
|
and sch.name = :schname
|
|
3506
|
+
order by
|
|
3507
|
+
ind_col.index_id,
|
|
3508
|
+
ind_col.key_ordinal
|
|
3506
3509
|
"""
|
|
3507
3510
|
)
|
|
3508
3511
|
.bindparams(
|
|
@@ -3998,6 +4001,8 @@ index_info AS (
|
|
|
3998
4001
|
index_info.index_schema = fk_info.unique_constraint_schema
|
|
3999
4002
|
AND index_info.index_name = fk_info.unique_constraint_name
|
|
4000
4003
|
AND index_info.ordinal_position = fk_info.ordinal_position
|
|
4004
|
+
AND NOT (index_info.table_schema = fk_info.table_schema
|
|
4005
|
+
AND index_info.table_name = fk_info.table_name)
|
|
4001
4006
|
|
|
4002
4007
|
ORDER BY fk_info.constraint_schema, fk_info.constraint_name,
|
|
4003
4008
|
fk_info.ordinal_position
|
|
@@ -45,6 +45,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
|
|
|
45
45
|
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
46
46
|
from ...connectors.asyncio import AsyncAdapt_dbapi_module
|
|
47
47
|
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
|
48
|
+
from ...connectors.asyncio import AsyncAdapt_terminate
|
|
48
49
|
from ...util.concurrency import await_fallback
|
|
49
50
|
from ...util.concurrency import await_only
|
|
50
51
|
|
|
@@ -82,7 +83,9 @@ class AsyncAdapt_aiomysql_ss_cursor(
|
|
|
82
83
|
)
|
|
83
84
|
|
|
84
85
|
|
|
85
|
-
class AsyncAdapt_aiomysql_connection(
|
|
86
|
+
class AsyncAdapt_aiomysql_connection(
|
|
87
|
+
AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
|
|
88
|
+
):
|
|
86
89
|
__slots__ = ()
|
|
87
90
|
|
|
88
91
|
_cursor_cls = AsyncAdapt_aiomysql_cursor
|
|
@@ -98,13 +101,19 @@ class AsyncAdapt_aiomysql_connection(AsyncAdapt_dbapi_connection):
|
|
|
98
101
|
def autocommit(self, value: Any) -> None:
|
|
99
102
|
self.await_(self._connection.autocommit(value))
|
|
100
103
|
|
|
101
|
-
def
|
|
102
|
-
#
|
|
103
|
-
self._connection.close()
|
|
104
|
+
def get_autocommit(self) -> bool:
|
|
105
|
+
return self._connection.get_autocommit() # type: ignore
|
|
104
106
|
|
|
105
107
|
def close(self) -> None:
|
|
106
108
|
self.await_(self._connection.ensure_closed())
|
|
107
109
|
|
|
110
|
+
async def _terminate_graceful_close(self) -> None:
|
|
111
|
+
await self._connection.ensure_closed()
|
|
112
|
+
|
|
113
|
+
def _terminate_force_close(self) -> None:
|
|
114
|
+
# it's not awaitable.
|
|
115
|
+
self._connection.close()
|
|
116
|
+
|
|
108
117
|
|
|
109
118
|
class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
|
|
110
119
|
__slots__ = ()
|
|
@@ -42,6 +42,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
|
|
|
42
42
|
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
43
43
|
from ...connectors.asyncio import AsyncAdapt_dbapi_module
|
|
44
44
|
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
|
45
|
+
from ...connectors.asyncio import AsyncAdapt_terminate
|
|
45
46
|
from ...util.concurrency import await_fallback
|
|
46
47
|
from ...util.concurrency import await_only
|
|
47
48
|
|
|
@@ -73,7 +74,9 @@ class AsyncAdapt_asyncmy_ss_cursor(
|
|
|
73
74
|
)
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
class AsyncAdapt_asyncmy_connection(
|
|
77
|
+
class AsyncAdapt_asyncmy_connection(
|
|
78
|
+
AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
|
|
79
|
+
):
|
|
77
80
|
__slots__ = ()
|
|
78
81
|
|
|
79
82
|
_cursor_cls = AsyncAdapt_asyncmy_cursor
|
|
@@ -104,13 +107,19 @@ class AsyncAdapt_asyncmy_connection(AsyncAdapt_dbapi_connection):
|
|
|
104
107
|
def autocommit(self, value: Any) -> None:
|
|
105
108
|
self.await_(self._connection.autocommit(value))
|
|
106
109
|
|
|
107
|
-
def
|
|
108
|
-
#
|
|
109
|
-
self._connection.close()
|
|
110
|
+
def get_autocommit(self) -> bool:
|
|
111
|
+
return self._connection.get_autocommit() # type: ignore
|
|
110
112
|
|
|
111
113
|
def close(self) -> None:
|
|
112
114
|
self.await_(self._connection.ensure_closed())
|
|
113
115
|
|
|
116
|
+
async def _terminate_graceful_close(self) -> None:
|
|
117
|
+
await self._connection.ensure_closed()
|
|
118
|
+
|
|
119
|
+
def _terminate_force_close(self) -> None:
|
|
120
|
+
# it's not awaitable.
|
|
121
|
+
self._connection.close()
|
|
122
|
+
|
|
114
123
|
|
|
115
124
|
class AsyncAdaptFallback_asyncmy_connection(AsyncAdapt_asyncmy_connection):
|
|
116
125
|
__slots__ = ()
|
|
@@ -253,6 +253,9 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
|
|
253
253
|
"AUTOCOMMIT",
|
|
254
254
|
)
|
|
255
255
|
|
|
256
|
+
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
|
|
257
|
+
return bool(dbapi_conn.autocommit)
|
|
258
|
+
|
|
256
259
|
def set_isolation_level(
|
|
257
260
|
self, dbapi_connection: DBAPIConnection, level: IsolationLevel
|
|
258
261
|
) -> None:
|
|
@@ -277,6 +277,9 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
277
277
|
"AUTOCOMMIT",
|
|
278
278
|
)
|
|
279
279
|
|
|
280
|
+
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
|
|
281
|
+
return bool(dbapi_conn.autocommit)
|
|
282
|
+
|
|
280
283
|
def set_isolation_level(
|
|
281
284
|
self, dbapi_connection: DBAPIConnection, level: IsolationLevel
|
|
282
285
|
) -> None:
|
|
@@ -298,6 +298,9 @@ class MySQLDialect_mysqldb(MySQLDialect):
|
|
|
298
298
|
"AUTOCOMMIT",
|
|
299
299
|
)
|
|
300
300
|
|
|
301
|
+
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
|
|
302
|
+
return dbapi_conn.get_autocommit() # type: ignore[no-any-return]
|
|
303
|
+
|
|
301
304
|
def set_isolation_level(
|
|
302
305
|
self, dbapi_connection: DBAPIConnection, level: IsolationLevel
|
|
303
306
|
) -> None:
|
|
@@ -35,8 +35,10 @@ from .base import VARCHAR2
|
|
|
35
35
|
from .base import VECTOR
|
|
36
36
|
from .base import VectorIndexConfig
|
|
37
37
|
from .base import VectorIndexType
|
|
38
|
+
from .vector import SparseVector
|
|
38
39
|
from .vector import VectorDistanceType
|
|
39
40
|
from .vector import VectorStorageFormat
|
|
41
|
+
from .vector import VectorStorageType
|
|
40
42
|
|
|
41
43
|
# Alias oracledb also as oracledb_async
|
|
42
44
|
oracledb_async = type(
|
|
@@ -74,4 +76,6 @@ __all__ = (
|
|
|
74
76
|
"VectorIndexType",
|
|
75
77
|
"VectorIndexConfig",
|
|
76
78
|
"VectorStorageFormat",
|
|
79
|
+
"VectorStorageType",
|
|
80
|
+
"SparseVector",
|
|
77
81
|
)
|
|
@@ -729,8 +729,22 @@ VECTOR Datatype
|
|
|
729
729
|
|
|
730
730
|
Oracle Database 23ai introduced a new VECTOR datatype for artificial intelligence
|
|
731
731
|
and machine learning search operations. The VECTOR datatype is a homogeneous array
|
|
732
|
-
of 8-bit signed integers, 8-bit unsigned integers (binary), 32-bit floating-point
|
|
733
|
-
or 64-bit floating-point numbers.
|
|
732
|
+
of 8-bit signed integers, 8-bit unsigned integers (binary), 32-bit floating-point
|
|
733
|
+
numbers, or 64-bit floating-point numbers.
|
|
734
|
+
|
|
735
|
+
A vector's storage type can be either DENSE or SPARSE. A dense vector contains
|
|
736
|
+
meaningful values in most or all of its dimensions. In contrast, a sparse vector
|
|
737
|
+
has non-zero values in only a few dimensions, with the majority being zero.
|
|
738
|
+
|
|
739
|
+
Sparse vectors are represented by the total number of vector dimensions, an array
|
|
740
|
+
of indices, and an array of values where each value’s location in the vector is
|
|
741
|
+
indicated by the corresponding indices array position. All other vector values are
|
|
742
|
+
treated as zero.
|
|
743
|
+
|
|
744
|
+
The storage formats that can be used with sparse vectors are float32, float64, and
|
|
745
|
+
int8. Note that the binary storage format cannot be used with sparse vectors.
|
|
746
|
+
|
|
747
|
+
Sparse vectors are supported when you are using Oracle Database 23.7 or later.
|
|
734
748
|
|
|
735
749
|
.. seealso::
|
|
736
750
|
|
|
@@ -738,17 +752,26 @@ or 64-bit floating-point numbers.
|
|
|
738
752
|
<https://python-oracledb.readthedocs.io/en/latest/user_guide/vector_data_type.html>`_ - in the documentation
|
|
739
753
|
for the :ref:`oracledb` driver.
|
|
740
754
|
|
|
741
|
-
.. versionadded:: 2.0.41
|
|
755
|
+
.. versionadded:: 2.0.41 - Added VECTOR datatype
|
|
756
|
+
|
|
757
|
+
.. versionadded:: 2.0.43 - Added DENSE/SPARSE support
|
|
742
758
|
|
|
743
759
|
CREATE TABLE support for VECTOR
|
|
744
760
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
745
761
|
|
|
746
|
-
With the :class:`.VECTOR` datatype, you can specify the
|
|
747
|
-
|
|
748
|
-
:class:`.VectorStorageFormat`.
|
|
749
|
-
:class:`.
|
|
762
|
+
With the :class:`.VECTOR` datatype, you can specify the number of dimensions,
|
|
763
|
+
the storage format, and the storage type for the data. Valid values for the
|
|
764
|
+
storage format are enum members of :class:`.VectorStorageFormat`. Valid values
|
|
765
|
+
for the storage type are enum members of :class:`.VectorStorageType`. If
|
|
766
|
+
storage type is not specified, a DENSE vector is created by default.
|
|
767
|
+
|
|
768
|
+
To create a table that includes a :class:`.VECTOR` column::
|
|
750
769
|
|
|
751
|
-
from sqlalchemy.dialects.oracle import
|
|
770
|
+
from sqlalchemy.dialects.oracle import (
|
|
771
|
+
VECTOR,
|
|
772
|
+
VectorStorageFormat,
|
|
773
|
+
VectorStorageType,
|
|
774
|
+
)
|
|
752
775
|
|
|
753
776
|
t = Table(
|
|
754
777
|
"t1",
|
|
@@ -756,7 +779,11 @@ and the storage format. Valid values for storage format are enum values from
|
|
|
756
779
|
Column("id", Integer, primary_key=True),
|
|
757
780
|
Column(
|
|
758
781
|
"embedding",
|
|
759
|
-
VECTOR(
|
|
782
|
+
VECTOR(
|
|
783
|
+
dim=3,
|
|
784
|
+
storage_format=VectorStorageFormat.FLOAT32,
|
|
785
|
+
storage_type=VectorStorageType.SPARSE,
|
|
786
|
+
),
|
|
760
787
|
),
|
|
761
788
|
Column(...),
|
|
762
789
|
...,
|
|
@@ -764,31 +791,40 @@ and the storage format. Valid values for storage format are enum values from
|
|
|
764
791
|
|
|
765
792
|
Vectors can also be defined with an arbitrary number of dimensions and formats.
|
|
766
793
|
This allows you to specify vectors of different dimensions with the various
|
|
767
|
-
storage formats mentioned
|
|
794
|
+
storage formats mentioned below.
|
|
768
795
|
|
|
769
796
|
**Examples**
|
|
770
797
|
|
|
771
|
-
* In this case, the storage format is flexible, allowing any vector type data to be
|
|
772
|
-
such as INT8 or BINARY etc::
|
|
798
|
+
* In this case, the storage format is flexible, allowing any vector type data to be
|
|
799
|
+
inserted, such as INT8 or BINARY etc::
|
|
773
800
|
|
|
774
801
|
vector_col: Mapped[array.array] = mapped_column(VECTOR(dim=3))
|
|
775
802
|
|
|
776
|
-
* The dimension is flexible in this case, meaning that any dimension vector can
|
|
803
|
+
* The dimension is flexible in this case, meaning that any dimension vector can
|
|
804
|
+
be used::
|
|
777
805
|
|
|
778
806
|
vector_col: Mapped[array.array] = mapped_column(
|
|
779
807
|
VECTOR(storage_format=VectorStorageType.INT8)
|
|
780
808
|
)
|
|
781
809
|
|
|
782
|
-
* Both the dimensions and the storage format are flexible::
|
|
810
|
+
* Both the dimensions and the storage format are flexible. It creates a DENSE vector::
|
|
783
811
|
|
|
784
812
|
vector_col: Mapped[array.array] = mapped_column(VECTOR)
|
|
785
813
|
|
|
814
|
+
* To create a SPARSE vector with both dimensions and the storage format as flexible,
|
|
815
|
+
use the :attr:`.VectorStorageType.SPARSE` storage type::
|
|
816
|
+
|
|
817
|
+
vector_col: Mapped[array.array] = mapped_column(
|
|
818
|
+
VECTOR(storage_type=VectorStorageType.SPARSE)
|
|
819
|
+
)
|
|
820
|
+
|
|
786
821
|
Python Datatypes for VECTOR
|
|
787
822
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
788
823
|
|
|
789
824
|
VECTOR data can be inserted using Python list or Python ``array.array()`` objects.
|
|
790
|
-
Python arrays of type FLOAT (32-bit), DOUBLE (64-bit),
|
|
791
|
-
are used as bind values when inserting
|
|
825
|
+
Python arrays of type FLOAT (32-bit), DOUBLE (64-bit), INT (8-bit signed integers),
|
|
826
|
+
or BINARY (8-bit unsigned integers) are used as bind values when inserting
|
|
827
|
+
VECTOR columns::
|
|
792
828
|
|
|
793
829
|
from sqlalchemy import insert, select
|
|
794
830
|
|
|
@@ -798,6 +834,21 @@ are used as bind values when inserting VECTOR columns::
|
|
|
798
834
|
{"id": 1, "embedding": [1, 2, 3]},
|
|
799
835
|
)
|
|
800
836
|
|
|
837
|
+
Data can be inserted into a sparse vector using the :class:`_oracle.SparseVector`
|
|
838
|
+
class, creating an object consisting of the number of dimensions, an array of indices, and a
|
|
839
|
+
corresponding array of values::
|
|
840
|
+
|
|
841
|
+
from sqlalchemy import insert, select
|
|
842
|
+
from sqlalchemy.dialects.oracle import SparseVector
|
|
843
|
+
|
|
844
|
+
sparse_val = SparseVector(10, [1, 2], array.array("d", [23.45, 221.22]))
|
|
845
|
+
|
|
846
|
+
with engine.begin() as conn:
|
|
847
|
+
conn.execute(
|
|
848
|
+
insert(t1),
|
|
849
|
+
{"id": 1, "embedding": sparse_val},
|
|
850
|
+
)
|
|
851
|
+
|
|
801
852
|
VECTOR Indexes
|
|
802
853
|
~~~~~~~~~~~~~~
|
|
803
854
|
|
|
@@ -805,6 +856,8 @@ The VECTOR feature supports an Oracle-specific parameter ``oracle_vector``
|
|
|
805
856
|
on the :class:`.Index` construct, which allows the construction of VECTOR
|
|
806
857
|
indexes.
|
|
807
858
|
|
|
859
|
+
SPARSE vectors cannot be used in the creation of vector indexes.
|
|
860
|
+
|
|
808
861
|
To utilize VECTOR indexing, set the ``oracle_vector`` parameter to True to use
|
|
809
862
|
the default values provided by Oracle. HNSW is the default indexing method::
|
|
810
863
|
|
|
@@ -1157,14 +1210,16 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1157
1210
|
return "ROWID"
|
|
1158
1211
|
|
|
1159
1212
|
def visit_VECTOR(self, type_, **kw):
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1213
|
+
dim = type_.dim if type_.dim is not None else "*"
|
|
1214
|
+
storage_format = (
|
|
1215
|
+
type_.storage_format.value
|
|
1216
|
+
if type_.storage_format is not None
|
|
1217
|
+
else "*"
|
|
1218
|
+
)
|
|
1219
|
+
storage_type = (
|
|
1220
|
+
type_.storage_type.value if type_.storage_type is not None else "*"
|
|
1221
|
+
)
|
|
1222
|
+
return f"VECTOR({dim},{storage_format},{storage_type})"
|
|
1168
1223
|
|
|
1169
1224
|
|
|
1170
1225
|
class OracleCompiler(compiler.SQLCompiler):
|
|
@@ -1234,6 +1234,9 @@ class OracleDialect_cx_oracle(OracleDialect):
|
|
|
1234
1234
|
with dbapi_connection.cursor() as cursor:
|
|
1235
1235
|
cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}")
|
|
1236
1236
|
|
|
1237
|
+
def detect_autocommit_setting(self, dbapi_conn) -> bool:
|
|
1238
|
+
return bool(dbapi_conn.autocommit)
|
|
1239
|
+
|
|
1237
1240
|
def _detect_decimal_char(self, connection):
|
|
1238
1241
|
# we have the option to change this setting upon connect,
|
|
1239
1242
|
# or just look at what it is upon connect and convert.
|
|
@@ -736,6 +736,8 @@ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
|
|
|
736
736
|
|
|
737
737
|
class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
|
|
738
738
|
_cursor: AsyncCursor
|
|
739
|
+
_awaitable_cursor_close: bool = False
|
|
740
|
+
|
|
739
741
|
__slots__ = ()
|
|
740
742
|
|
|
741
743
|
@property
|
|
@@ -749,10 +751,6 @@ class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
|
|
|
749
751
|
def var(self, *args, **kwargs):
|
|
750
752
|
return self._cursor.var(*args, **kwargs)
|
|
751
753
|
|
|
752
|
-
def close(self):
|
|
753
|
-
self._rows.clear()
|
|
754
|
-
self._cursor.close()
|
|
755
|
-
|
|
756
754
|
def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
|
|
757
755
|
return self._cursor.setinputsizes(*args, **kwargs)
|
|
758
756
|
|