SQLAlchemy 2.0.47__cp313-cp313t-win32.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 +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# dialects/mysql/expression.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
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ... import exc
|
|
13
|
+
from ... import util
|
|
14
|
+
from ...sql import coercions
|
|
15
|
+
from ...sql import elements
|
|
16
|
+
from ...sql import operators
|
|
17
|
+
from ...sql import roles
|
|
18
|
+
from ...sql.base import _generative
|
|
19
|
+
from ...sql.base import Generative
|
|
20
|
+
from ...util.typing import Self
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class match(Generative, elements.BinaryExpression[Any]):
|
|
24
|
+
"""Produce a ``MATCH (X, Y) AGAINST ('TEXT')`` clause.
|
|
25
|
+
|
|
26
|
+
E.g.::
|
|
27
|
+
|
|
28
|
+
from sqlalchemy import desc
|
|
29
|
+
from sqlalchemy.dialects.mysql import match
|
|
30
|
+
|
|
31
|
+
match_expr = match(
|
|
32
|
+
users_table.c.firstname,
|
|
33
|
+
users_table.c.lastname,
|
|
34
|
+
against="Firstname Lastname",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
stmt = (
|
|
38
|
+
select(users_table)
|
|
39
|
+
.where(match_expr.in_boolean_mode())
|
|
40
|
+
.order_by(desc(match_expr))
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
Would produce SQL resembling:
|
|
44
|
+
|
|
45
|
+
.. sourcecode:: sql
|
|
46
|
+
|
|
47
|
+
SELECT id, firstname, lastname
|
|
48
|
+
FROM user
|
|
49
|
+
WHERE MATCH(firstname, lastname) AGAINST (:param_1 IN BOOLEAN MODE)
|
|
50
|
+
ORDER BY MATCH(firstname, lastname) AGAINST (:param_2) DESC
|
|
51
|
+
|
|
52
|
+
The :func:`_mysql.match` function is a standalone version of the
|
|
53
|
+
:meth:`_sql.ColumnElement.match` method available on all
|
|
54
|
+
SQL expressions, as when :meth:`_expression.ColumnElement.match` is
|
|
55
|
+
used, but allows to pass multiple columns
|
|
56
|
+
|
|
57
|
+
:param cols: column expressions to match against
|
|
58
|
+
|
|
59
|
+
:param against: expression to be compared towards
|
|
60
|
+
|
|
61
|
+
:param in_boolean_mode: boolean, set "boolean mode" to true
|
|
62
|
+
|
|
63
|
+
:param in_natural_language_mode: boolean , set "natural language" to true
|
|
64
|
+
|
|
65
|
+
:param with_query_expansion: boolean, set "query expansion" to true
|
|
66
|
+
|
|
67
|
+
.. versionadded:: 1.4.19
|
|
68
|
+
|
|
69
|
+
.. seealso::
|
|
70
|
+
|
|
71
|
+
:meth:`_expression.ColumnElement.match`
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
__visit_name__ = "mysql_match"
|
|
76
|
+
|
|
77
|
+
inherit_cache = True
|
|
78
|
+
modifiers: util.immutabledict[str, Any]
|
|
79
|
+
|
|
80
|
+
def __init__(self, *cols: elements.ColumnElement[Any], **kw: Any):
|
|
81
|
+
if not cols:
|
|
82
|
+
raise exc.ArgumentError("columns are required")
|
|
83
|
+
|
|
84
|
+
against = kw.pop("against", None)
|
|
85
|
+
|
|
86
|
+
if against is None:
|
|
87
|
+
raise exc.ArgumentError("against is required")
|
|
88
|
+
against = coercions.expect(
|
|
89
|
+
roles.ExpressionElementRole,
|
|
90
|
+
against,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
left = elements.BooleanClauseList._construct_raw(
|
|
94
|
+
operators.comma_op,
|
|
95
|
+
clauses=cols,
|
|
96
|
+
)
|
|
97
|
+
left.group = False
|
|
98
|
+
|
|
99
|
+
flags = util.immutabledict(
|
|
100
|
+
{
|
|
101
|
+
"mysql_boolean_mode": kw.pop("in_boolean_mode", False),
|
|
102
|
+
"mysql_natural_language": kw.pop(
|
|
103
|
+
"in_natural_language_mode", False
|
|
104
|
+
),
|
|
105
|
+
"mysql_query_expansion": kw.pop("with_query_expansion", False),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if kw:
|
|
110
|
+
raise exc.ArgumentError("unknown arguments: %s" % (", ".join(kw)))
|
|
111
|
+
|
|
112
|
+
super().__init__(left, against, operators.match_op, modifiers=flags)
|
|
113
|
+
|
|
114
|
+
@_generative
|
|
115
|
+
def in_boolean_mode(self) -> Self:
|
|
116
|
+
"""Apply the "IN BOOLEAN MODE" modifier to the MATCH expression.
|
|
117
|
+
|
|
118
|
+
:return: a new :class:`_mysql.match` instance with modifications
|
|
119
|
+
applied.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
self.modifiers = self.modifiers.union({"mysql_boolean_mode": True})
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
@_generative
|
|
126
|
+
def in_natural_language_mode(self) -> Self:
|
|
127
|
+
"""Apply the "IN NATURAL LANGUAGE MODE" modifier to the MATCH
|
|
128
|
+
expression.
|
|
129
|
+
|
|
130
|
+
:return: a new :class:`_mysql.match` instance with modifications
|
|
131
|
+
applied.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
self.modifiers = self.modifiers.union({"mysql_natural_language": True})
|
|
135
|
+
return self
|
|
136
|
+
|
|
137
|
+
@_generative
|
|
138
|
+
def with_query_expansion(self) -> Self:
|
|
139
|
+
"""Apply the "WITH QUERY EXPANSION" modifier to the MATCH expression.
|
|
140
|
+
|
|
141
|
+
:return: a new :class:`_mysql.match` instance with modifications
|
|
142
|
+
applied.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
self.modifiers = self.modifiers.union({"mysql_query_expansion": True})
|
|
146
|
+
return self
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# dialects/mysql/json.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
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from ... import types as sqltypes
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ...engine.interfaces import Dialect
|
|
16
|
+
from ...sql.type_api import _BindProcessorType
|
|
17
|
+
from ...sql.type_api import _LiteralProcessorType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JSON(sqltypes.JSON):
|
|
21
|
+
"""MySQL JSON type.
|
|
22
|
+
|
|
23
|
+
MySQL supports JSON as of version 5.7.
|
|
24
|
+
MariaDB supports JSON (as an alias for LONGTEXT) as of version 10.2.
|
|
25
|
+
|
|
26
|
+
:class:`_mysql.JSON` is used automatically whenever the base
|
|
27
|
+
:class:`_types.JSON` datatype is used against a MySQL or MariaDB backend.
|
|
28
|
+
|
|
29
|
+
.. seealso::
|
|
30
|
+
|
|
31
|
+
:class:`_types.JSON` - main documentation for the generic
|
|
32
|
+
cross-platform JSON datatype.
|
|
33
|
+
|
|
34
|
+
The :class:`.mysql.JSON` type supports persistence of JSON values
|
|
35
|
+
as well as the core index operations provided by :class:`_types.JSON`
|
|
36
|
+
datatype, by adapting the operations to render the ``JSON_EXTRACT``
|
|
37
|
+
function at the database level.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _FormatTypeMixin:
|
|
45
|
+
def _format_value(self, value: Any) -> str:
|
|
46
|
+
raise NotImplementedError()
|
|
47
|
+
|
|
48
|
+
def bind_processor(self, dialect: Dialect) -> _BindProcessorType[Any]:
|
|
49
|
+
super_proc = self.string_bind_processor(dialect) # type: ignore[attr-defined] # noqa: E501
|
|
50
|
+
|
|
51
|
+
def process(value: Any) -> Any:
|
|
52
|
+
value = self._format_value(value)
|
|
53
|
+
if super_proc:
|
|
54
|
+
value = super_proc(value)
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
return process
|
|
58
|
+
|
|
59
|
+
def literal_processor(
|
|
60
|
+
self, dialect: Dialect
|
|
61
|
+
) -> _LiteralProcessorType[Any]:
|
|
62
|
+
super_proc = self.string_literal_processor(dialect) # type: ignore[attr-defined] # noqa: E501
|
|
63
|
+
|
|
64
|
+
def process(value: Any) -> str:
|
|
65
|
+
value = self._format_value(value)
|
|
66
|
+
if super_proc:
|
|
67
|
+
value = super_proc(value)
|
|
68
|
+
return value # type: ignore[no-any-return]
|
|
69
|
+
|
|
70
|
+
return process
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
|
|
74
|
+
def _format_value(self, value: Any) -> str:
|
|
75
|
+
if isinstance(value, int):
|
|
76
|
+
formatted_value = "$[%s]" % value
|
|
77
|
+
else:
|
|
78
|
+
formatted_value = '$."%s"' % value
|
|
79
|
+
return formatted_value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
|
|
83
|
+
def _format_value(self, value: Any) -> str:
|
|
84
|
+
return "$%s" % (
|
|
85
|
+
"".join(
|
|
86
|
+
[
|
|
87
|
+
"[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
|
|
88
|
+
for elem in value
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# dialects/mysql/mariadb.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
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .base import MariaDBIdentifierPreparer
|
|
13
|
+
from .base import MySQLDialect
|
|
14
|
+
from .base import MySQLIdentifierPreparer
|
|
15
|
+
from .base import MySQLTypeCompiler
|
|
16
|
+
from ...sql import sqltypes
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class INET4(sqltypes.TypeEngine[str]):
|
|
20
|
+
"""INET4 column type for MariaDB
|
|
21
|
+
|
|
22
|
+
.. versionadded:: 2.0.37
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__visit_name__ = "INET4"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class INET6(sqltypes.TypeEngine[str]):
|
|
29
|
+
"""INET6 column type for MariaDB
|
|
30
|
+
|
|
31
|
+
.. versionadded:: 2.0.37
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
__visit_name__ = "INET6"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MariaDBTypeCompiler(MySQLTypeCompiler):
|
|
38
|
+
def visit_INET4(self, type_: INET4, **kwargs: Any) -> str:
|
|
39
|
+
return "INET4"
|
|
40
|
+
|
|
41
|
+
def visit_INET6(self, type_: INET6, **kwargs: Any) -> str:
|
|
42
|
+
return "INET6"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MariaDBDialect(MySQLDialect):
|
|
46
|
+
is_mariadb = True
|
|
47
|
+
supports_statement_cache = True
|
|
48
|
+
name = "mariadb"
|
|
49
|
+
preparer: type[MySQLIdentifierPreparer] = MariaDBIdentifierPreparer
|
|
50
|
+
type_compiler_cls = MariaDBTypeCompiler
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def loader(driver: str) -> type[MariaDBDialect]:
|
|
54
|
+
dialect_mod = __import__(
|
|
55
|
+
"sqlalchemy.dialects.mysql.%s" % driver
|
|
56
|
+
).dialects.mysql
|
|
57
|
+
|
|
58
|
+
driver_mod = getattr(dialect_mod, driver)
|
|
59
|
+
if hasattr(driver_mod, "mariadb_dialect"):
|
|
60
|
+
driver_cls = driver_mod.mariadb_dialect
|
|
61
|
+
return driver_cls # type: ignore[no-any-return]
|
|
62
|
+
else:
|
|
63
|
+
driver_cls = driver_mod.dialect
|
|
64
|
+
|
|
65
|
+
return type(
|
|
66
|
+
"MariaDBDialect_%s" % driver,
|
|
67
|
+
(
|
|
68
|
+
MariaDBDialect,
|
|
69
|
+
driver_cls,
|
|
70
|
+
),
|
|
71
|
+
{"supports_statement_cache": True},
|
|
72
|
+
)
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# dialects/mysql/mariadbconnector.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
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
.. dialect:: mysql+mariadbconnector
|
|
11
|
+
:name: MariaDB Connector/Python
|
|
12
|
+
:dbapi: mariadb
|
|
13
|
+
:connectstring: mariadb+mariadbconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
|
14
|
+
:url: https://pypi.org/project/mariadb/
|
|
15
|
+
|
|
16
|
+
Driver Status
|
|
17
|
+
-------------
|
|
18
|
+
|
|
19
|
+
MariaDB Connector/Python enables Python programs to access MariaDB and MySQL
|
|
20
|
+
databases using an API which is compliant with the Python DB API 2.0 (PEP-249).
|
|
21
|
+
It is written in C and uses MariaDB Connector/C client library for client server
|
|
22
|
+
communication.
|
|
23
|
+
|
|
24
|
+
Note that the default driver for a ``mariadb://`` connection URI continues to
|
|
25
|
+
be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
|
|
26
|
+
|
|
27
|
+
.. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python
|
|
28
|
+
|
|
29
|
+
""" # noqa
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
from typing import Any
|
|
34
|
+
from typing import Optional
|
|
35
|
+
from typing import Sequence
|
|
36
|
+
from typing import Tuple
|
|
37
|
+
from typing import TYPE_CHECKING
|
|
38
|
+
from typing import Union
|
|
39
|
+
from uuid import UUID as _python_UUID
|
|
40
|
+
|
|
41
|
+
from .base import MySQLCompiler
|
|
42
|
+
from .base import MySQLDialect
|
|
43
|
+
from .base import MySQLExecutionContext
|
|
44
|
+
from ... import sql
|
|
45
|
+
from ... import util
|
|
46
|
+
from ...sql import sqltypes
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from ...engine.base import Connection
|
|
50
|
+
from ...engine.interfaces import ConnectArgsType
|
|
51
|
+
from ...engine.interfaces import DBAPIConnection
|
|
52
|
+
from ...engine.interfaces import DBAPICursor
|
|
53
|
+
from ...engine.interfaces import DBAPIModule
|
|
54
|
+
from ...engine.interfaces import Dialect
|
|
55
|
+
from ...engine.interfaces import IsolationLevel
|
|
56
|
+
from ...engine.interfaces import PoolProxiedConnection
|
|
57
|
+
from ...engine.url import URL
|
|
58
|
+
from ...sql.compiler import SQLCompiler
|
|
59
|
+
from ...sql.type_api import _ResultProcessorType
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
mariadb_cpy_minimum_version = (1, 0, 1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]):
|
|
66
|
+
# work around JIRA issue
|
|
67
|
+
# https://jira.mariadb.org/browse/CONPY-270. When that issue is fixed,
|
|
68
|
+
# this type can be removed.
|
|
69
|
+
def result_processor(
|
|
70
|
+
self, dialect: Dialect, coltype: object
|
|
71
|
+
) -> Optional[_ResultProcessorType[Any]]:
|
|
72
|
+
if self.as_uuid:
|
|
73
|
+
|
|
74
|
+
def process(value: Any) -> Any:
|
|
75
|
+
if value is not None:
|
|
76
|
+
if hasattr(value, "decode"):
|
|
77
|
+
value = value.decode("ascii")
|
|
78
|
+
value = _python_UUID(value)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
return process
|
|
82
|
+
else:
|
|
83
|
+
|
|
84
|
+
def process(value: Any) -> Any:
|
|
85
|
+
if value is not None:
|
|
86
|
+
if hasattr(value, "decode"):
|
|
87
|
+
value = value.decode("ascii")
|
|
88
|
+
value = str(_python_UUID(value))
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
return process
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext):
|
|
95
|
+
_lastrowid: Optional[int] = None
|
|
96
|
+
|
|
97
|
+
def create_server_side_cursor(self) -> DBAPICursor:
|
|
98
|
+
return self._dbapi_connection.cursor(buffered=False)
|
|
99
|
+
|
|
100
|
+
def create_default_cursor(self) -> DBAPICursor:
|
|
101
|
+
return self._dbapi_connection.cursor(buffered=True)
|
|
102
|
+
|
|
103
|
+
def post_exec(self) -> None:
|
|
104
|
+
super().post_exec()
|
|
105
|
+
|
|
106
|
+
self._rowcount = self.cursor.rowcount
|
|
107
|
+
|
|
108
|
+
if TYPE_CHECKING:
|
|
109
|
+
assert isinstance(self.compiled, SQLCompiler)
|
|
110
|
+
if self.isinsert and self.compiled.postfetch_lastrowid:
|
|
111
|
+
self._lastrowid = self.cursor.lastrowid
|
|
112
|
+
|
|
113
|
+
def get_lastrowid(self) -> int:
|
|
114
|
+
if TYPE_CHECKING:
|
|
115
|
+
assert self._lastrowid is not None
|
|
116
|
+
return self._lastrowid
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class MySQLCompiler_mariadbconnector(MySQLCompiler):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class MySQLDialect_mariadbconnector(MySQLDialect):
|
|
124
|
+
driver = "mariadbconnector"
|
|
125
|
+
supports_statement_cache = True
|
|
126
|
+
|
|
127
|
+
# set this to True at the module level to prevent the driver from running
|
|
128
|
+
# against a backend that server detects as MySQL. currently this appears to
|
|
129
|
+
# be unnecessary as MariaDB client libraries have always worked against
|
|
130
|
+
# MySQL databases. However, if this changes at some point, this can be
|
|
131
|
+
# adjusted, but PLEASE ADD A TEST in test/dialect/mysql/test_dialect.py if
|
|
132
|
+
# this change is made at some point to ensure the correct exception
|
|
133
|
+
# is raised at the correct point when running the driver against
|
|
134
|
+
# a MySQL backend.
|
|
135
|
+
# is_mariadb = True
|
|
136
|
+
|
|
137
|
+
supports_unicode_statements = True
|
|
138
|
+
encoding = "utf8mb4"
|
|
139
|
+
convert_unicode = True
|
|
140
|
+
supports_sane_rowcount = True
|
|
141
|
+
supports_sane_multi_rowcount = True
|
|
142
|
+
supports_native_decimal = True
|
|
143
|
+
default_paramstyle = "qmark"
|
|
144
|
+
execution_ctx_cls = MySQLExecutionContext_mariadbconnector
|
|
145
|
+
statement_compiler = MySQLCompiler_mariadbconnector
|
|
146
|
+
|
|
147
|
+
supports_server_side_cursors = True
|
|
148
|
+
|
|
149
|
+
colspecs = util.update_copy(
|
|
150
|
+
MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
@util.memoized_property
|
|
154
|
+
def _dbapi_version(self) -> Tuple[int, ...]:
|
|
155
|
+
if self.dbapi and hasattr(self.dbapi, "__version__"):
|
|
156
|
+
return tuple(
|
|
157
|
+
[
|
|
158
|
+
int(x)
|
|
159
|
+
for x in re.findall(
|
|
160
|
+
r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
return (99, 99, 99)
|
|
166
|
+
|
|
167
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
168
|
+
super().__init__(**kwargs)
|
|
169
|
+
self.paramstyle = "qmark"
|
|
170
|
+
if self.dbapi is not None:
|
|
171
|
+
if self._dbapi_version < mariadb_cpy_minimum_version:
|
|
172
|
+
raise NotImplementedError(
|
|
173
|
+
"The minimum required version for MariaDB "
|
|
174
|
+
"Connector/Python is %s"
|
|
175
|
+
% ".".join(str(x) for x in mariadb_cpy_minimum_version)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def import_dbapi(cls) -> DBAPIModule:
|
|
180
|
+
return __import__("mariadb")
|
|
181
|
+
|
|
182
|
+
def is_disconnect(
|
|
183
|
+
self,
|
|
184
|
+
e: DBAPIModule.Error,
|
|
185
|
+
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
|
|
186
|
+
cursor: Optional[DBAPICursor],
|
|
187
|
+
) -> bool:
|
|
188
|
+
if super().is_disconnect(e, connection, cursor):
|
|
189
|
+
return True
|
|
190
|
+
elif isinstance(e, self.loaded_dbapi.Error):
|
|
191
|
+
str_e = str(e).lower()
|
|
192
|
+
return "not connected" in str_e or "isn't valid" in str_e
|
|
193
|
+
else:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
def create_connect_args(self, url: URL) -> ConnectArgsType:
|
|
197
|
+
opts = url.translate_connect_args()
|
|
198
|
+
opts.update(url.query)
|
|
199
|
+
|
|
200
|
+
int_params = [
|
|
201
|
+
"connect_timeout",
|
|
202
|
+
"read_timeout",
|
|
203
|
+
"write_timeout",
|
|
204
|
+
"client_flag",
|
|
205
|
+
"port",
|
|
206
|
+
"pool_size",
|
|
207
|
+
]
|
|
208
|
+
bool_params = [
|
|
209
|
+
"local_infile",
|
|
210
|
+
"ssl_verify_cert",
|
|
211
|
+
"ssl",
|
|
212
|
+
"pool_reset_connection",
|
|
213
|
+
"compress",
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
for key in int_params:
|
|
217
|
+
util.coerce_kw_type(opts, key, int)
|
|
218
|
+
for key in bool_params:
|
|
219
|
+
util.coerce_kw_type(opts, key, bool)
|
|
220
|
+
|
|
221
|
+
# FOUND_ROWS must be set in CLIENT_FLAGS to enable
|
|
222
|
+
# supports_sane_rowcount.
|
|
223
|
+
client_flag = opts.get("client_flag", 0)
|
|
224
|
+
if self.dbapi is not None:
|
|
225
|
+
try:
|
|
226
|
+
CLIENT_FLAGS = __import__(
|
|
227
|
+
self.dbapi.__name__ + ".constants.CLIENT"
|
|
228
|
+
).constants.CLIENT
|
|
229
|
+
client_flag |= CLIENT_FLAGS.FOUND_ROWS
|
|
230
|
+
except (AttributeError, ImportError):
|
|
231
|
+
self.supports_sane_rowcount = False
|
|
232
|
+
opts["client_flag"] = client_flag
|
|
233
|
+
return [], opts
|
|
234
|
+
|
|
235
|
+
def _extract_error_code(self, exception: DBAPIModule.Error) -> int:
|
|
236
|
+
try:
|
|
237
|
+
rc: int = exception.errno
|
|
238
|
+
except:
|
|
239
|
+
rc = -1
|
|
240
|
+
return rc
|
|
241
|
+
|
|
242
|
+
def _detect_charset(self, connection: Connection) -> str:
|
|
243
|
+
return "utf8mb4"
|
|
244
|
+
|
|
245
|
+
def get_isolation_level_values(
|
|
246
|
+
self, dbapi_conn: DBAPIConnection
|
|
247
|
+
) -> Sequence[IsolationLevel]:
|
|
248
|
+
return (
|
|
249
|
+
"SERIALIZABLE",
|
|
250
|
+
"READ UNCOMMITTED",
|
|
251
|
+
"READ COMMITTED",
|
|
252
|
+
"REPEATABLE READ",
|
|
253
|
+
"AUTOCOMMIT",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
|
|
257
|
+
return bool(dbapi_conn.autocommit)
|
|
258
|
+
|
|
259
|
+
def set_isolation_level(
|
|
260
|
+
self, dbapi_connection: DBAPIConnection, level: IsolationLevel
|
|
261
|
+
) -> None:
|
|
262
|
+
if level == "AUTOCOMMIT":
|
|
263
|
+
dbapi_connection.autocommit = True
|
|
264
|
+
else:
|
|
265
|
+
dbapi_connection.autocommit = False
|
|
266
|
+
super().set_isolation_level(dbapi_connection, level)
|
|
267
|
+
|
|
268
|
+
def do_begin_twophase(self, connection: Connection, xid: Any) -> None:
|
|
269
|
+
connection.execute(
|
|
270
|
+
sql.text("XA BEGIN :xid").bindparams(
|
|
271
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def do_prepare_twophase(self, connection: Connection, xid: Any) -> None:
|
|
276
|
+
connection.execute(
|
|
277
|
+
sql.text("XA END :xid").bindparams(
|
|
278
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
connection.execute(
|
|
282
|
+
sql.text("XA PREPARE :xid").bindparams(
|
|
283
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def do_rollback_twophase(
|
|
288
|
+
self,
|
|
289
|
+
connection: Connection,
|
|
290
|
+
xid: Any,
|
|
291
|
+
is_prepared: bool = True,
|
|
292
|
+
recover: bool = False,
|
|
293
|
+
) -> None:
|
|
294
|
+
if not is_prepared:
|
|
295
|
+
connection.execute(
|
|
296
|
+
sql.text("XA END :xid").bindparams(
|
|
297
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
connection.execute(
|
|
301
|
+
sql.text("XA ROLLBACK :xid").bindparams(
|
|
302
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def do_commit_twophase(
|
|
307
|
+
self,
|
|
308
|
+
connection: Connection,
|
|
309
|
+
xid: Any,
|
|
310
|
+
is_prepared: bool = True,
|
|
311
|
+
recover: bool = False,
|
|
312
|
+
) -> None:
|
|
313
|
+
if not is_prepared:
|
|
314
|
+
self.do_prepare_twophase(connection, xid)
|
|
315
|
+
connection.execute(
|
|
316
|
+
sql.text("XA COMMIT :xid").bindparams(
|
|
317
|
+
sql.bindparam("xid", xid, literal_execute=True)
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
dialect = MySQLDialect_mariadbconnector
|