SQLAlchemy 2.0.39__py3-none-any.whl → 2.0.40__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/pyodbc.py +2 -4
- sqlalchemy/dialects/mysql/base.py +15 -8
- sqlalchemy/dialects/mysql/mariadb.py +17 -11
- sqlalchemy/dialects/mysql/mysqlconnector.py +84 -19
- sqlalchemy/dialects/mysql/provision.py +4 -0
- sqlalchemy/dialects/mysql/reflection.py +1 -1
- sqlalchemy/dialects/mysql/types.py +4 -5
- sqlalchemy/dialects/postgresql/array.py +132 -58
- sqlalchemy/dialects/postgresql/base.py +44 -3
- sqlalchemy/dialects/postgresql/json.py +1 -1
- sqlalchemy/dialects/sqlite/base.py +15 -4
- sqlalchemy/engine/cursor.py +6 -1
- sqlalchemy/engine/default.py +27 -12
- sqlalchemy/engine/interfaces.py +11 -4
- sqlalchemy/ext/asyncio/base.py +7 -7
- sqlalchemy/ext/asyncio/engine.py +5 -3
- sqlalchemy/ext/asyncio/session.py +21 -2
- sqlalchemy/orm/decl_api.py +1 -1
- sqlalchemy/orm/decl_base.py +1 -1
- sqlalchemy/orm/exc.py +9 -0
- sqlalchemy/orm/properties.py +12 -3
- sqlalchemy/orm/util.py +6 -5
- sqlalchemy/pool/base.py +1 -1
- sqlalchemy/sql/_elements_constructors.py +14 -4
- sqlalchemy/sql/_selectable_constructors.py +2 -0
- sqlalchemy/sql/coercions.py +1 -1
- sqlalchemy/sql/compiler.py +237 -131
- sqlalchemy/sql/crud.py +12 -3
- sqlalchemy/sql/ddl.py +49 -46
- sqlalchemy/sql/elements.py +24 -17
- sqlalchemy/sql/functions.py +42 -7
- sqlalchemy/sql/schema.py +23 -5
- sqlalchemy/sql/sqltypes.py +98 -34
- sqlalchemy/sql/type_api.py +4 -0
- sqlalchemy/sql/util.py +1 -1
- sqlalchemy/testing/assertions.py +2 -2
- sqlalchemy/testing/requirements.py +13 -0
- sqlalchemy/testing/suite/test_reflection.py +44 -0
- sqlalchemy/testing/suite/test_results.py +2 -0
- sqlalchemy/util/_collections.py +3 -1
- sqlalchemy/util/typing.py +64 -21
- {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.40.dist-info}/METADATA +10 -9
- {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.40.dist-info}/RECORD +47 -47
- {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.40.dist-info}/WHEEL +1 -1
- {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.40.dist-info/licenses}/LICENSE +0 -0
- {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.40.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
sqlalchemy/connectors/pyodbc.py
CHANGED
|
@@ -227,11 +227,9 @@ class PyODBCConnector(Connector):
|
|
|
227
227
|
)
|
|
228
228
|
|
|
229
229
|
def get_isolation_level_values(
|
|
230
|
-
self,
|
|
230
|
+
self, dbapi_conn: interfaces.DBAPIConnection
|
|
231
231
|
) -> List[IsolationLevel]:
|
|
232
|
-
return super().get_isolation_level_values(
|
|
233
|
-
"AUTOCOMMIT"
|
|
234
|
-
]
|
|
232
|
+
return [*super().get_isolation_level_values(dbapi_conn), "AUTOCOMMIT"]
|
|
235
233
|
|
|
236
234
|
def set_isolation_level(
|
|
237
235
|
self,
|
|
@@ -1928,12 +1928,13 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
|
|
1928
1928
|
colspec.append("AUTO_INCREMENT")
|
|
1929
1929
|
else:
|
|
1930
1930
|
default = self.get_column_default_string(column)
|
|
1931
|
+
|
|
1931
1932
|
if default is not None:
|
|
1932
1933
|
if (
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
and
|
|
1934
|
+
self.dialect._support_default_function
|
|
1935
|
+
and not re.match(r"^\s*[\'\"\(]", default)
|
|
1936
|
+
and "ON UPDATE" not in default
|
|
1937
|
+
and re.match(r".*\W.*", default)
|
|
1937
1938
|
):
|
|
1938
1939
|
colspec.append(f"DEFAULT ({default})")
|
|
1939
1940
|
else:
|
|
@@ -2520,6 +2521,10 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
2520
2521
|
# allow for the "true" and "false" keywords, however
|
|
2521
2522
|
supports_native_boolean = False
|
|
2522
2523
|
|
|
2524
|
+
# support for BIT type; mysqlconnector coerces result values automatically,
|
|
2525
|
+
# all other MySQL DBAPIs require a conversion routine
|
|
2526
|
+
supports_native_bit = False
|
|
2527
|
+
|
|
2523
2528
|
# identifiers are 64, however aliases can be 255...
|
|
2524
2529
|
max_identifier_length = 255
|
|
2525
2530
|
max_index_name_length = 64
|
|
@@ -2721,10 +2726,12 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
2721
2726
|
% (".".join(map(str, server_version_info)),)
|
|
2722
2727
|
)
|
|
2723
2728
|
if is_mariadb:
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2729
|
+
|
|
2730
|
+
if not issubclass(self.preparer, MariaDBIdentifierPreparer):
|
|
2731
|
+
self.preparer = MariaDBIdentifierPreparer
|
|
2732
|
+
# this would have been set by the default dialect already,
|
|
2733
|
+
# so set it again
|
|
2734
|
+
self.identifier_preparer = self.preparer(self)
|
|
2728
2735
|
|
|
2729
2736
|
# this will be updated on first connect in initialize()
|
|
2730
2737
|
# if using older mariadb version
|
|
@@ -46,16 +46,22 @@ class MariaDBDialect(MySQLDialect):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def loader(driver):
|
|
49
|
-
|
|
49
|
+
dialect_mod = __import__(
|
|
50
50
|
"sqlalchemy.dialects.mysql.%s" % driver
|
|
51
51
|
).dialects.mysql
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
|
|
53
|
+
driver_mod = getattr(dialect_mod, driver)
|
|
54
|
+
if hasattr(driver_mod, "mariadb_dialect"):
|
|
55
|
+
driver_cls = driver_mod.mariadb_dialect
|
|
56
|
+
return driver_cls
|
|
57
|
+
else:
|
|
58
|
+
driver_cls = driver_mod.dialect
|
|
59
|
+
|
|
60
|
+
return type(
|
|
61
|
+
"MariaDBDialect_%s" % driver,
|
|
62
|
+
(
|
|
63
|
+
MariaDBDialect,
|
|
64
|
+
driver_cls,
|
|
65
|
+
),
|
|
66
|
+
{"supports_statement_cache": True},
|
|
67
|
+
)
|
|
@@ -14,24 +14,51 @@ r"""
|
|
|
14
14
|
:connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
|
15
15
|
:url: https://pypi.org/project/mysql-connector-python/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Driver Status
|
|
18
|
+
-------------
|
|
19
|
+
|
|
20
|
+
MySQL Connector/Python is supported as of SQLAlchemy 2.0.39 to the
|
|
21
|
+
degree which the driver is functional. There are still ongoing issues
|
|
22
|
+
with features such as server side cursors which remain disabled until
|
|
23
|
+
upstream issues are repaired.
|
|
24
|
+
|
|
25
|
+
.. versionchanged:: 2.0.39
|
|
26
|
+
|
|
27
|
+
The MySQL Connector/Python dialect has been updated to support the
|
|
28
|
+
latest version of this DBAPI. Previously, MySQL Connector/Python
|
|
29
|
+
was not fully supported.
|
|
30
|
+
|
|
31
|
+
Connecting to MariaDB with MySQL Connector/Python
|
|
32
|
+
--------------------------------------------------
|
|
33
|
+
|
|
34
|
+
MySQL Connector/Python may attempt to pass an incompatible collation to the
|
|
35
|
+
database when connecting to MariaDB. Experimentation has shown that using
|
|
36
|
+
``?charset=utf8mb4&collation=utfmb4_general_ci`` or similar MariaDB-compatible
|
|
37
|
+
charset/collation will allow connectivity.
|
|
18
38
|
|
|
19
|
-
The MySQL Connector/Python DBAPI has had many issues since its release,
|
|
20
|
-
some of which may remain unresolved, and the mysqlconnector dialect is
|
|
21
|
-
**not tested as part of SQLAlchemy's continuous integration**.
|
|
22
|
-
The recommended MySQL dialects are mysqlclient and PyMySQL.
|
|
23
39
|
|
|
24
40
|
""" # noqa
|
|
25
41
|
|
|
26
42
|
import re
|
|
27
43
|
|
|
28
44
|
from .base import BIT
|
|
45
|
+
from .base import MariaDBIdentifierPreparer
|
|
29
46
|
from .base import MySQLCompiler
|
|
30
47
|
from .base import MySQLDialect
|
|
48
|
+
from .base import MySQLExecutionContext
|
|
31
49
|
from .base import MySQLIdentifierPreparer
|
|
50
|
+
from .mariadb import MariaDBDialect
|
|
32
51
|
from ... import util
|
|
33
52
|
|
|
34
53
|
|
|
54
|
+
class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
|
|
55
|
+
def create_server_side_cursor(self):
|
|
56
|
+
return self._dbapi_connection.cursor(buffered=False)
|
|
57
|
+
|
|
58
|
+
def create_default_cursor(self):
|
|
59
|
+
return self._dbapi_connection.cursor(buffered=True)
|
|
60
|
+
|
|
61
|
+
|
|
35
62
|
class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
|
36
63
|
def visit_mod_binary(self, binary, operator, **kw):
|
|
37
64
|
return (
|
|
@@ -41,7 +68,7 @@ class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
|
|
41
68
|
)
|
|
42
69
|
|
|
43
70
|
|
|
44
|
-
class
|
|
71
|
+
class IdentifierPreparerCommon_mysqlconnector:
|
|
45
72
|
@property
|
|
46
73
|
def _double_percents(self):
|
|
47
74
|
return False
|
|
@@ -55,6 +82,18 @@ class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
|
|
|
55
82
|
return value
|
|
56
83
|
|
|
57
84
|
|
|
85
|
+
class MySQLIdentifierPreparer_mysqlconnector(
|
|
86
|
+
IdentifierPreparerCommon_mysqlconnector, MySQLIdentifierPreparer
|
|
87
|
+
):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MariaDBIdentifierPreparer_mysqlconnector(
|
|
92
|
+
IdentifierPreparerCommon_mysqlconnector, MariaDBIdentifierPreparer
|
|
93
|
+
):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
58
97
|
class _myconnpyBIT(BIT):
|
|
59
98
|
def result_processor(self, dialect, coltype):
|
|
60
99
|
"""MySQL-connector already converts mysql bits, so."""
|
|
@@ -71,9 +110,16 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
71
110
|
|
|
72
111
|
supports_native_decimal = True
|
|
73
112
|
|
|
113
|
+
supports_native_bit = True
|
|
114
|
+
|
|
115
|
+
# not until https://bugs.mysql.com/bug.php?id=117548
|
|
116
|
+
supports_server_side_cursors = False
|
|
117
|
+
|
|
74
118
|
default_paramstyle = "format"
|
|
75
119
|
statement_compiler = MySQLCompiler_mysqlconnector
|
|
76
120
|
|
|
121
|
+
execution_ctx_cls = MySQLExecutionContext_mysqlconnector
|
|
122
|
+
|
|
77
123
|
preparer = MySQLIdentifierPreparer_mysqlconnector
|
|
78
124
|
|
|
79
125
|
colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
|
|
@@ -111,9 +157,13 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
111
157
|
util.coerce_kw_type(opts, "use_pure", bool)
|
|
112
158
|
util.coerce_kw_type(opts, "use_unicode", bool)
|
|
113
159
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
|
|
160
|
+
# note that "buffered" is set to False by default in MySQL/connector
|
|
161
|
+
# python. If you set it to True, then there is no way to get a server
|
|
162
|
+
# side cursor because the logic is written to disallow that.
|
|
163
|
+
|
|
164
|
+
# leaving this at True until
|
|
165
|
+
# https://bugs.mysql.com/bug.php?id=117548 can be fixed
|
|
166
|
+
opts["buffered"] = True
|
|
117
167
|
|
|
118
168
|
# FOUND_ROWS must be set in ClientFlag to enable
|
|
119
169
|
# supports_sane_rowcount.
|
|
@@ -128,6 +178,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
128
178
|
opts["client_flags"] = client_flags
|
|
129
179
|
except Exception:
|
|
130
180
|
pass
|
|
181
|
+
|
|
131
182
|
return [[], opts]
|
|
132
183
|
|
|
133
184
|
@util.memoized_property
|
|
@@ -145,7 +196,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
145
196
|
|
|
146
197
|
def is_disconnect(self, e, connection, cursor):
|
|
147
198
|
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
|
|
148
|
-
exceptions = (
|
|
199
|
+
exceptions = (
|
|
200
|
+
self.dbapi.OperationalError,
|
|
201
|
+
self.dbapi.InterfaceError,
|
|
202
|
+
self.dbapi.ProgrammingError,
|
|
203
|
+
)
|
|
149
204
|
if isinstance(e, exceptions):
|
|
150
205
|
return (
|
|
151
206
|
e.errno in errnos
|
|
@@ -161,20 +216,30 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
161
216
|
def _compat_fetchone(self, rp, charset=None):
|
|
162
217
|
return rp.fetchone()
|
|
163
218
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
219
|
+
def get_isolation_level_values(self, dbapi_connection):
|
|
220
|
+
return (
|
|
221
|
+
"SERIALIZABLE",
|
|
222
|
+
"READ UNCOMMITTED",
|
|
223
|
+
"READ COMMITTED",
|
|
224
|
+
"REPEATABLE READ",
|
|
225
|
+
"AUTOCOMMIT",
|
|
226
|
+
)
|
|
171
227
|
|
|
172
|
-
def
|
|
228
|
+
def set_isolation_level(self, connection, level):
|
|
173
229
|
if level == "AUTOCOMMIT":
|
|
174
230
|
connection.autocommit = True
|
|
175
231
|
else:
|
|
176
232
|
connection.autocommit = False
|
|
177
|
-
super().
|
|
233
|
+
super().set_isolation_level(connection, level)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class MariaDBDialect_mysqlconnector(
|
|
237
|
+
MariaDBDialect, MySQLDialect_mysqlconnector
|
|
238
|
+
):
|
|
239
|
+
supports_statement_cache = True
|
|
240
|
+
_allows_uuid_binds = False
|
|
241
|
+
preparer = MariaDBIdentifierPreparer_mysqlconnector
|
|
178
242
|
|
|
179
243
|
|
|
180
244
|
dialect = MySQLDialect_mysqlconnector
|
|
245
|
+
mariadb_dialect = MariaDBDialect_mysqlconnector
|
|
@@ -42,6 +42,10 @@ def generate_driver_url(url, driver, query_str):
|
|
|
42
42
|
|
|
43
43
|
if driver == "mariadbconnector":
|
|
44
44
|
new_url = new_url.difference_update_query(["charset"])
|
|
45
|
+
elif driver == "mysqlconnector":
|
|
46
|
+
new_url = new_url.update_query_pairs(
|
|
47
|
+
[("collation", "utf8mb4_general_ci")]
|
|
48
|
+
)
|
|
45
49
|
|
|
46
50
|
try:
|
|
47
51
|
new_url.get_dialect()
|
|
@@ -451,7 +451,7 @@ class MySQLTableDefinitionParser:
|
|
|
451
451
|
r"(?: +COLLATE +(?P<collate>[\w_]+))?"
|
|
452
452
|
r"(?: +(?P<notnull>(?:NOT )?NULL))?"
|
|
453
453
|
r"(?: +DEFAULT +(?P<default>"
|
|
454
|
-
r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+"
|
|
454
|
+
r"(?:NULL|'(?:''|[^'])*'|\(.+?\)|[\-\w\.\(\)]+"
|
|
455
455
|
r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
|
|
456
456
|
r"))?"
|
|
457
457
|
r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
|
|
@@ -374,12 +374,11 @@ class BIT(sqltypes.TypeEngine):
|
|
|
374
374
|
self.length = length
|
|
375
375
|
|
|
376
376
|
def result_processor(self, dialect, coltype):
|
|
377
|
-
"""Convert a MySQL's 64 bit, variable length binary string to a
|
|
377
|
+
"""Convert a MySQL's 64 bit, variable length binary string to a
|
|
378
|
+
long."""
|
|
378
379
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
"""
|
|
380
|
+
if dialect.supports_native_bit:
|
|
381
|
+
return None
|
|
383
382
|
|
|
384
383
|
def process(value):
|
|
385
384
|
if value is not None:
|
|
@@ -4,15 +4,18 @@
|
|
|
4
4
|
#
|
|
5
5
|
# This module is part of SQLAlchemy and is released under
|
|
6
6
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
-
# mypy: ignore-errors
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
from __future__ import annotations
|
|
11
10
|
|
|
12
11
|
import re
|
|
13
|
-
from typing import Any
|
|
12
|
+
from typing import Any as typing_Any
|
|
13
|
+
from typing import Iterable
|
|
14
14
|
from typing import Optional
|
|
15
|
+
from typing import Sequence
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
15
17
|
from typing import TypeVar
|
|
18
|
+
from typing import Union
|
|
16
19
|
|
|
17
20
|
from .operators import CONTAINED_BY
|
|
18
21
|
from .operators import CONTAINS
|
|
@@ -21,28 +24,52 @@ from ... import types as sqltypes
|
|
|
21
24
|
from ... import util
|
|
22
25
|
from ...sql import expression
|
|
23
26
|
from ...sql import operators
|
|
24
|
-
from ...sql.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
from ...sql.visitors import InternalTraversal
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from ...engine.interfaces import Dialect
|
|
31
|
+
from ...sql._typing import _ColumnExpressionArgument
|
|
32
|
+
from ...sql._typing import _TypeEngineArgument
|
|
33
|
+
from ...sql.elements import ColumnElement
|
|
34
|
+
from ...sql.elements import Grouping
|
|
35
|
+
from ...sql.expression import BindParameter
|
|
36
|
+
from ...sql.operators import OperatorType
|
|
37
|
+
from ...sql.selectable import _SelectIterable
|
|
38
|
+
from ...sql.type_api import _BindProcessorType
|
|
39
|
+
from ...sql.type_api import _LiteralProcessorType
|
|
40
|
+
from ...sql.type_api import _ResultProcessorType
|
|
41
|
+
from ...sql.type_api import TypeEngine
|
|
42
|
+
from ...sql.visitors import _TraverseInternalsType
|
|
43
|
+
from ...util.typing import Self
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_T = TypeVar("_T", bound=typing_Any)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def Any(
|
|
50
|
+
other: typing_Any,
|
|
51
|
+
arrexpr: _ColumnExpressionArgument[_T],
|
|
52
|
+
operator: OperatorType = operators.eq,
|
|
53
|
+
) -> ColumnElement[bool]:
|
|
31
54
|
"""A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.any` method.
|
|
32
55
|
See that method for details.
|
|
33
56
|
|
|
34
57
|
"""
|
|
35
58
|
|
|
36
|
-
return arrexpr.any(other, operator)
|
|
59
|
+
return arrexpr.any(other, operator) # type: ignore[no-any-return, union-attr] # noqa: E501
|
|
37
60
|
|
|
38
61
|
|
|
39
|
-
def All(
|
|
62
|
+
def All(
|
|
63
|
+
other: typing_Any,
|
|
64
|
+
arrexpr: _ColumnExpressionArgument[_T],
|
|
65
|
+
operator: OperatorType = operators.eq,
|
|
66
|
+
) -> ColumnElement[bool]:
|
|
40
67
|
"""A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.all` method.
|
|
41
68
|
See that method for details.
|
|
42
69
|
|
|
43
70
|
"""
|
|
44
71
|
|
|
45
|
-
return arrexpr.all(other, operator)
|
|
72
|
+
return arrexpr.all(other, operator) # type: ignore[no-any-return, union-attr] # noqa: E501
|
|
46
73
|
|
|
47
74
|
|
|
48
75
|
class array(expression.ExpressionClauseList[_T]):
|
|
@@ -66,11 +93,32 @@ class array(expression.ExpressionClauseList[_T]):
|
|
|
66
93
|
ARRAY[%(param_3)s, %(param_4)s, %(param_5)s]) AS anon_1
|
|
67
94
|
|
|
68
95
|
An instance of :class:`.array` will always have the datatype
|
|
69
|
-
:class:`_types.ARRAY`. The "inner" type of the array is inferred from
|
|
70
|
-
|
|
96
|
+
:class:`_types.ARRAY`. The "inner" type of the array is inferred from the
|
|
97
|
+
values present, unless the :paramref:`_postgresql.array.type_` keyword
|
|
98
|
+
argument is passed::
|
|
71
99
|
|
|
72
100
|
array(["foo", "bar"], type_=CHAR)
|
|
73
101
|
|
|
102
|
+
When constructing an empty array, the :paramref:`_postgresql.array.type_`
|
|
103
|
+
argument is particularly important as PostgreSQL server typically requires
|
|
104
|
+
a cast to be rendered for the inner type in order to render an empty array.
|
|
105
|
+
SQLAlchemy's compilation for the empty array will produce this cast so
|
|
106
|
+
that::
|
|
107
|
+
|
|
108
|
+
stmt = array([], type_=Integer)
|
|
109
|
+
print(stmt.compile(dialect=postgresql.dialect()))
|
|
110
|
+
|
|
111
|
+
Produces:
|
|
112
|
+
|
|
113
|
+
.. sourcecode:: sql
|
|
114
|
+
|
|
115
|
+
ARRAY[]::INTEGER[]
|
|
116
|
+
|
|
117
|
+
As required by PostgreSQL for empty arrays.
|
|
118
|
+
|
|
119
|
+
.. versionadded:: 2.0.40 added support to render empty PostgreSQL array
|
|
120
|
+
literals with a required cast.
|
|
121
|
+
|
|
74
122
|
Multidimensional arrays are produced by nesting :class:`.array` constructs.
|
|
75
123
|
The dimensionality of the final :class:`_types.ARRAY`
|
|
76
124
|
type is calculated by
|
|
@@ -105,18 +153,33 @@ class array(expression.ExpressionClauseList[_T]):
|
|
|
105
153
|
__visit_name__ = "array"
|
|
106
154
|
|
|
107
155
|
stringify_dialect = "postgresql"
|
|
108
|
-
inherit_cache = True
|
|
109
156
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
_traverse_internals: _TraverseInternalsType = [
|
|
158
|
+
("clauses", InternalTraversal.dp_clauseelement_tuple),
|
|
159
|
+
("type", InternalTraversal.dp_type),
|
|
160
|
+
]
|
|
113
161
|
|
|
114
|
-
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
clauses: Iterable[_T],
|
|
165
|
+
*,
|
|
166
|
+
type_: Optional[_TypeEngineArgument[_T]] = None,
|
|
167
|
+
**kw: typing_Any,
|
|
168
|
+
):
|
|
169
|
+
r"""Construct an ARRAY literal.
|
|
170
|
+
|
|
171
|
+
:param clauses: iterable, such as a list, containing elements to be
|
|
172
|
+
rendered in the array
|
|
173
|
+
:param type\_: optional type. If omitted, the type is inferred
|
|
174
|
+
from the contents of the array.
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
super().__init__(operators.comma_op, *clauses, **kw)
|
|
115
178
|
|
|
116
179
|
main_type = (
|
|
117
|
-
|
|
118
|
-
if
|
|
119
|
-
else self.
|
|
180
|
+
type_
|
|
181
|
+
if type_ is not None
|
|
182
|
+
else self.clauses[0].type if self.clauses else sqltypes.NULLTYPE
|
|
120
183
|
)
|
|
121
184
|
|
|
122
185
|
if isinstance(main_type, ARRAY):
|
|
@@ -127,15 +190,21 @@ class array(expression.ExpressionClauseList[_T]):
|
|
|
127
190
|
if main_type.dimensions is not None
|
|
128
191
|
else 2
|
|
129
192
|
),
|
|
130
|
-
)
|
|
193
|
+
) # type: ignore[assignment]
|
|
131
194
|
else:
|
|
132
|
-
self.type = ARRAY(main_type)
|
|
195
|
+
self.type = ARRAY(main_type) # type: ignore[assignment]
|
|
133
196
|
|
|
134
197
|
@property
|
|
135
|
-
def _select_iterable(self):
|
|
198
|
+
def _select_iterable(self) -> _SelectIterable:
|
|
136
199
|
return (self,)
|
|
137
200
|
|
|
138
|
-
def _bind_param(
|
|
201
|
+
def _bind_param(
|
|
202
|
+
self,
|
|
203
|
+
operator: OperatorType,
|
|
204
|
+
obj: typing_Any,
|
|
205
|
+
type_: Optional[TypeEngine[_T]] = None,
|
|
206
|
+
_assume_scalar: bool = False,
|
|
207
|
+
) -> BindParameter[_T]:
|
|
139
208
|
if _assume_scalar or operator is operators.getitem:
|
|
140
209
|
return expression.BindParameter(
|
|
141
210
|
None,
|
|
@@ -154,16 +223,18 @@ class array(expression.ExpressionClauseList[_T]):
|
|
|
154
223
|
)
|
|
155
224
|
for o in obj
|
|
156
225
|
]
|
|
157
|
-
)
|
|
226
|
+
) # type: ignore[return-value]
|
|
158
227
|
|
|
159
|
-
def self_group(
|
|
228
|
+
def self_group(
|
|
229
|
+
self, against: Optional[OperatorType] = None
|
|
230
|
+
) -> Union[Self, Grouping[_T]]:
|
|
160
231
|
if against in (operators.any_op, operators.all_op, operators.getitem):
|
|
161
232
|
return expression.Grouping(self)
|
|
162
233
|
else:
|
|
163
234
|
return self
|
|
164
235
|
|
|
165
236
|
|
|
166
|
-
class ARRAY(sqltypes.ARRAY):
|
|
237
|
+
class ARRAY(sqltypes.ARRAY[_T]):
|
|
167
238
|
"""PostgreSQL ARRAY type.
|
|
168
239
|
|
|
169
240
|
The :class:`_postgresql.ARRAY` type is constructed in the same way
|
|
@@ -237,7 +308,7 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
237
308
|
|
|
238
309
|
def __init__(
|
|
239
310
|
self,
|
|
240
|
-
item_type: _TypeEngineArgument[
|
|
311
|
+
item_type: _TypeEngineArgument[_T],
|
|
241
312
|
as_tuple: bool = False,
|
|
242
313
|
dimensions: Optional[int] = None,
|
|
243
314
|
zero_indexes: bool = False,
|
|
@@ -286,7 +357,7 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
286
357
|
self.dimensions = dimensions
|
|
287
358
|
self.zero_indexes = zero_indexes
|
|
288
359
|
|
|
289
|
-
class Comparator(sqltypes.ARRAY.Comparator):
|
|
360
|
+
class Comparator(sqltypes.ARRAY.Comparator[_T]):
|
|
290
361
|
"""Define comparison operations for :class:`_types.ARRAY`.
|
|
291
362
|
|
|
292
363
|
Note that these operations are in addition to those provided
|
|
@@ -296,7 +367,9 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
296
367
|
|
|
297
368
|
"""
|
|
298
369
|
|
|
299
|
-
def contains(
|
|
370
|
+
def contains(
|
|
371
|
+
self, other: typing_Any, **kwargs: typing_Any
|
|
372
|
+
) -> ColumnElement[bool]:
|
|
300
373
|
"""Boolean expression. Test if elements are a superset of the
|
|
301
374
|
elements of the argument array expression.
|
|
302
375
|
|
|
@@ -305,7 +378,7 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
305
378
|
"""
|
|
306
379
|
return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
|
|
307
380
|
|
|
308
|
-
def contained_by(self, other):
|
|
381
|
+
def contained_by(self, other: typing_Any) -> ColumnElement[bool]:
|
|
309
382
|
"""Boolean expression. Test if elements are a proper subset of the
|
|
310
383
|
elements of the argument array expression.
|
|
311
384
|
"""
|
|
@@ -313,7 +386,7 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
313
386
|
CONTAINED_BY, other, result_type=sqltypes.Boolean
|
|
314
387
|
)
|
|
315
388
|
|
|
316
|
-
def overlap(self, other):
|
|
389
|
+
def overlap(self, other: typing_Any) -> ColumnElement[bool]:
|
|
317
390
|
"""Boolean expression. Test if array has elements in common with
|
|
318
391
|
an argument array expression.
|
|
319
392
|
"""
|
|
@@ -321,35 +394,26 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
321
394
|
|
|
322
395
|
comparator_factory = Comparator
|
|
323
396
|
|
|
324
|
-
@property
|
|
325
|
-
def hashable(self):
|
|
326
|
-
return self.as_tuple
|
|
327
|
-
|
|
328
|
-
@property
|
|
329
|
-
def python_type(self):
|
|
330
|
-
return list
|
|
331
|
-
|
|
332
|
-
def compare_values(self, x, y):
|
|
333
|
-
return x == y
|
|
334
|
-
|
|
335
397
|
@util.memoized_property
|
|
336
|
-
def _against_native_enum(self):
|
|
398
|
+
def _against_native_enum(self) -> bool:
|
|
337
399
|
return (
|
|
338
400
|
isinstance(self.item_type, sqltypes.Enum)
|
|
339
|
-
and self.item_type.native_enum
|
|
401
|
+
and self.item_type.native_enum # type: ignore[attr-defined]
|
|
340
402
|
)
|
|
341
403
|
|
|
342
|
-
def literal_processor(
|
|
404
|
+
def literal_processor(
|
|
405
|
+
self, dialect: Dialect
|
|
406
|
+
) -> Optional[_LiteralProcessorType[_T]]:
|
|
343
407
|
item_proc = self.item_type.dialect_impl(dialect).literal_processor(
|
|
344
408
|
dialect
|
|
345
409
|
)
|
|
346
410
|
if item_proc is None:
|
|
347
411
|
return None
|
|
348
412
|
|
|
349
|
-
def to_str(elements):
|
|
413
|
+
def to_str(elements: Iterable[typing_Any]) -> str:
|
|
350
414
|
return f"ARRAY[{', '.join(elements)}]"
|
|
351
415
|
|
|
352
|
-
def process(value):
|
|
416
|
+
def process(value: Sequence[typing_Any]) -> str:
|
|
353
417
|
inner = self._apply_item_processor(
|
|
354
418
|
value, item_proc, self.dimensions, to_str
|
|
355
419
|
)
|
|
@@ -357,12 +421,16 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
357
421
|
|
|
358
422
|
return process
|
|
359
423
|
|
|
360
|
-
def bind_processor(
|
|
424
|
+
def bind_processor(
|
|
425
|
+
self, dialect: Dialect
|
|
426
|
+
) -> Optional[_BindProcessorType[Sequence[typing_Any]]]:
|
|
361
427
|
item_proc = self.item_type.dialect_impl(dialect).bind_processor(
|
|
362
428
|
dialect
|
|
363
429
|
)
|
|
364
430
|
|
|
365
|
-
def process(
|
|
431
|
+
def process(
|
|
432
|
+
value: Optional[Sequence[typing_Any]],
|
|
433
|
+
) -> Optional[list[typing_Any]]:
|
|
366
434
|
if value is None:
|
|
367
435
|
return value
|
|
368
436
|
else:
|
|
@@ -372,12 +440,16 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
372
440
|
|
|
373
441
|
return process
|
|
374
442
|
|
|
375
|
-
def result_processor(
|
|
443
|
+
def result_processor(
|
|
444
|
+
self, dialect: Dialect, coltype: object
|
|
445
|
+
) -> _ResultProcessorType[Sequence[typing_Any]]:
|
|
376
446
|
item_proc = self.item_type.dialect_impl(dialect).result_processor(
|
|
377
447
|
dialect, coltype
|
|
378
448
|
)
|
|
379
449
|
|
|
380
|
-
def process(
|
|
450
|
+
def process(
|
|
451
|
+
value: Sequence[typing_Any],
|
|
452
|
+
) -> Optional[Sequence[typing_Any]]:
|
|
381
453
|
if value is None:
|
|
382
454
|
return value
|
|
383
455
|
else:
|
|
@@ -392,11 +464,13 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
392
464
|
super_rp = process
|
|
393
465
|
pattern = re.compile(r"^{(.*)}$")
|
|
394
466
|
|
|
395
|
-
def handle_raw_string(value):
|
|
396
|
-
inner = pattern.match(value).group(1)
|
|
467
|
+
def handle_raw_string(value: str) -> list[str]:
|
|
468
|
+
inner = pattern.match(value).group(1) # type: ignore[union-attr] # noqa: E501
|
|
397
469
|
return _split_enum_values(inner)
|
|
398
470
|
|
|
399
|
-
def process(
|
|
471
|
+
def process(
|
|
472
|
+
value: Sequence[typing_Any],
|
|
473
|
+
) -> Optional[Sequence[typing_Any]]:
|
|
400
474
|
if value is None:
|
|
401
475
|
return value
|
|
402
476
|
# isinstance(value, str) is required to handle
|
|
@@ -411,7 +485,7 @@ class ARRAY(sqltypes.ARRAY):
|
|
|
411
485
|
return process
|
|
412
486
|
|
|
413
487
|
|
|
414
|
-
def _split_enum_values(array_string):
|
|
488
|
+
def _split_enum_values(array_string: str) -> list[str]:
|
|
415
489
|
if '"' not in array_string:
|
|
416
490
|
# no escape char is present so it can just split on the comma
|
|
417
491
|
return array_string.split(",") if array_string else []
|