From 06c234d037bdab48e716d6c5f5dc200095269474 Mon Sep 17 00:00:00 2001 From: Federico Caselli Date: Fri, 2 Dec 2022 11:58:40 -0500 Subject: Rewrite positional handling, test for "numeric" Changed how the positional compilation is performed. It's rendered by the compiler the same as the pyformat compilation. The string is then processed to replace the placeholders with the correct ones, and to obtain the correct order of the parameters. This vastly simplifies the computation of the order of the parameters, that in case of nested CTE is very hard to compute correctly. Reworked how numeric paramstyle behavers: - added support for repeated parameter, without duplicating them like in normal positional dialects - implement insertmany support. This requires that the dialect supports out of order placehoders, since all parameters that are not part of the VALUES clauses are placed at the beginning of the parameter tuple - support for different identifiers for a numeric parameter. It's for example possible to use postgresql style placeholder $1, $2, etc Added two new dialect based on sqlite to test "numeric" fully using both :1 style and $1 style. Includes a workaround for SQLite's not-really-correct numeric implementation. Changed parmstyle of asyncpg dialect to use numeric, rendering with its native $ identifiers Fixes: #8926 Fixes: #8849 Change-Id: I7c640467d49adfe6d795cc84296fc7403dcad4d6 --- lib/sqlalchemy/testing/assertsql.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'lib/sqlalchemy/testing/assertsql.py') diff --git a/lib/sqlalchemy/testing/assertsql.py b/lib/sqlalchemy/testing/assertsql.py index d183372c3..45a2496dd 100644 --- a/lib/sqlalchemy/testing/assertsql.py +++ b/lib/sqlalchemy/testing/assertsql.py @@ -11,6 +11,7 @@ from __future__ import annotations import collections import contextlib +import itertools import re from .. import event @@ -285,7 +286,8 @@ class DialectSQL(CompiledSQL): return received_stmt, execute_observed.context.compiled_parameters - def _dialect_adjusted_statement(self, paramstyle): + def _dialect_adjusted_statement(self, dialect): + paramstyle = dialect.paramstyle stmt = re.sub(r"[\n\t]", "", self.statement) # temporarily escape out PG double colons @@ -300,8 +302,14 @@ class DialectSQL(CompiledSQL): repl = "?" elif paramstyle == "format": repl = r"%s" - elif paramstyle == "numeric": - repl = None + elif paramstyle.startswith("numeric"): + counter = itertools.count(1) + + num_identifier = "$" if paramstyle == "numeric_dollar" else ":" + + def repl(m): + return f"{num_identifier}{next(counter)}" + stmt = re.sub(r":([\w_]+)", repl, stmt) # put them back @@ -310,20 +318,20 @@ class DialectSQL(CompiledSQL): return stmt def _compare_sql(self, execute_observed, received_statement): - paramstyle = execute_observed.context.dialect.paramstyle - stmt = self._dialect_adjusted_statement(paramstyle) + stmt = self._dialect_adjusted_statement( + execute_observed.context.dialect + ) return received_statement == stmt def _failure_message(self, execute_observed, expected_params): - paramstyle = execute_observed.context.dialect.paramstyle return ( "Testing for compiled statement\n%r partial params %s, " "received\n%%(received_statement)r with params " "%%(received_parameters)r" % ( - self._dialect_adjusted_statement(paramstyle).replace( - "%", "%%" - ), + self._dialect_adjusted_statement( + execute_observed.context.dialect + ).replace("%", "%%"), repr(expected_params).replace("%", "%%"), ) ) -- cgit v1.2.1