|
33 | 33 | text, |
34 | 34 | TypeDecorator, |
35 | 35 | ) |
36 | | -from sqlalchemy.dialects import mysql, postgresql, sqlite |
| 36 | +from sqlalchemy.dialects import postgresql, sqlite |
37 | 37 | from sqlalchemy.dialects.postgresql import JSONB |
38 | 38 | from sqlalchemy.engine import Connection |
39 | 39 | from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncEngine, create_async_engine |
@@ -153,9 +153,6 @@ def func_json_extract( |
153 | 153 | expression indexes keep matching. |
154 | 154 | - SQLite: ``json_extract(col, '$.a.b')``. SQLite auto-unquotes scalars, so |
155 | 155 | the result is directly comparable to a string. |
156 | | - - MySQL: ``json_extract(col, '$.a.b')``. No functional index is created on |
157 | | - MySQL because a width-limited ``CAST`` would introduce comparison |
158 | | - truncation; MySQL dev environments fall back to non-indexed filtering. |
159 | 156 |
|
160 | 157 | :param db_engine: The database engine type (e.g., ``"postgresql"``). |
161 | 158 | :type db_engine: str |
@@ -183,39 +180,29 @@ def func_json_extract( |
183 | 180 | def idempotent_insert(engine_name: str, table: Any) -> GenericInsert: |
184 | 181 | """Return a dialect-specific INSERT that ignores duplicate-key conflicts. |
185 | 182 |
|
186 | | - PostgreSQL and SQLite use ``INSERT ... ON CONFLICT DO NOTHING``; MySQL uses |
187 | | - ``INSERT IGNORE ...``. The caller chains ``.values(...)`` and passes the |
188 | | - result to ``session.execute``. |
| 183 | + PostgreSQL and SQLite use ``INSERT ... ON CONFLICT DO NOTHING``. The caller |
| 184 | + chains ``.values(...)`` and passes the result to ``session.execute``. |
189 | 185 |
|
190 | | - :param engine_name: SQLAlchemy engine ``name`` (``"postgresql"``, ``"sqlite"``, |
191 | | - or ``"mysql"``). |
192 | | - :type engine_name: str |
| 186 | + :param engine_name: SQLAlchemy engine ``name`` (``"postgresql"`` or |
| 187 | + ``"sqlite"``). |
193 | 188 | :param table: The target table or ORM model class. |
194 | | - :type table: Any |
195 | 189 | :return: A dialect-specific insert construct. |
196 | | - :rtype: GenericInsert |
197 | 190 | :raises NotImplementedError: If the dialect is not supported. |
198 | 191 | """ |
199 | 192 | if engine_name == DatabaseDialect.POSTGRESQL: |
200 | 193 | return postgresql.insert(table).on_conflict_do_nothing() |
201 | 194 | if engine_name == DatabaseDialect.SQLITE: |
202 | 195 | return sqlite.insert(table).on_conflict_do_nothing() |
203 | | - if engine_name == DatabaseDialect.MYSQL: |
204 | | - return mysql.insert(table).prefix_with("IGNORE") |
205 | 196 | raise NotImplementedError(f"idempotent_insert: unsupported dialect {engine_name!r}") |
206 | 197 |
|
207 | 198 |
|
208 | 199 | class NullsLastOrdering(ColumnElement): |
209 | 200 | """Render an ``ORDER BY`` term that places NULLs last on every supported dialect. |
210 | 201 |
|
211 | | - PostgreSQL and SQLite render the standard ``NULLS LAST`` clause. MySQL has no |
212 | | - such syntax, so its hook prepends ``ISNULL(<expr>) ASC`` -- ``ISNULL`` yields |
213 | | - ``1`` for NULL and ``0`` otherwise, pinning NULLs last independently of the |
214 | | - primary direction. |
| 202 | + PostgreSQL and SQLite render the standard ``NULLS LAST`` clause. |
215 | 203 |
|
216 | 204 | Takes the direction as a flag rather than a pre-directed expression: wrapping an |
217 | | - already-``desc()``-ed expression would make the MySQL hook emit the invalid |
218 | | - ``ISNULL(<expr> DESC)``. |
| 205 | + already-``desc()``-ed expression would render ``<expr> DESC ASC NULLS LAST``. |
219 | 206 |
|
220 | 207 | Participates in SQLAlchemy's compiled-statement cache, with a key that |
221 | 208 | discriminates both column and direction. |
@@ -253,30 +240,6 @@ def _compile_nulls_last_ordering( |
253 | 240 | return f"{compiler.process(element.column, **kw)} {direction} NULLS LAST" |
254 | 241 |
|
255 | 242 |
|
256 | | -@compiles(NullsLastOrdering, DatabaseDialect.MYSQL) |
257 | | -def _compile_nulls_last_ordering_mysql( |
258 | | - element: NullsLastOrdering, compiler: SQLCompiler, **kw: Any |
259 | | -) -> str: |
260 | | - """Render MySQL's ``ISNULL(<expr>) ASC, <expr> <direction>`` equivalent. |
261 | | -
|
262 | | - The interpolated text is the compiler's own rendering of the wrapped |
263 | | - expression, never a client-supplied value: sort keys are allowlisted by |
264 | | - :attr:`~app.core.db.list_query.ListQuerySpec.sortable` before they reach the |
265 | | - construct, and :class:`NullsLastOrdering` coerces a raw string argument into a |
266 | | - bound parameter rather than SQL text. Path literals carried by |
267 | | - :func:`func_json_extract` use ``literal_execute``, so the dialect's literal |
268 | | - processor inlines them at execution -- the same rendering that function |
269 | | - documents, unchanged by the wrapper. |
270 | | -
|
271 | | - :param element: The ordering construct being compiled. |
272 | | - :param compiler: The active SQL compiler. |
273 | | - :return: The rendered pair of ``ORDER BY`` terms. |
274 | | - """ |
275 | | - rendered = compiler.process(element.column, **kw) |
276 | | - direction = "DESC" if element.descending else "ASC" |
277 | | - return f"ISNULL({rendered}) ASC, {rendered} {direction}" |
278 | | - |
279 | | - |
280 | 243 | def prepare_unsafe_value_for_json_comparison(db_engine: str, value: Any) -> Any: |
281 | 244 | """Prepare a value for JSON comparison based on the database engine. |
282 | 245 |
|
|
0 commit comments