Skip to content

Commit b9fda6c

Browse files
authored
Parametrize related field queries (#1797)
* Refactor to use get_parameterized_sql from pypika * Use parametrized queries when fetching related fields
1 parent 57c9ead commit b9fda6c

File tree

10 files changed

+88
-94
lines changed

10 files changed

+88
-94
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Fixed
1919
Changed
2020
^^^^^^^
2121
- Parametrizes UPDATE, DELETE, bulk update and create operations (#1785)
22+
- Parametrizes related field queries (#1797)
2223

2324
0.22.1
2425
------

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ classifiers = [
3636

3737
[tool.poetry.dependencies]
3838
python = "^3.8"
39-
pypika-tortoise = "^0.3.1"
39+
pypika-tortoise = "^0.3.2"
4040
iso8601 = "^2.1.0"
4141
aiosqlite = ">=0.16.0, <0.21.0"
4242
pytz = "*"

tortoise/backends/base/executor.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
from pypika import JoinType, Parameter, Table
2323
from pypika.queries import QueryBuilder
24-
from pypika.terms import Parameterizer
2524

2625
from tortoise.exceptions import OperationalError
2726
from tortoise.expressions import Expression, ResolveContext
@@ -192,10 +191,6 @@ async def _process_insert_result(self, instance: "Model", results: Any) -> None:
192191
def parameter(self, pos: int) -> Parameter:
193192
return Parameter(idx=pos + 1)
194193

195-
@classmethod
196-
def parameterizer(cls) -> Parameterizer:
197-
return Parameterizer()
198-
199194
async def execute_insert(self, instance: "Model") -> None:
200195
if not instance._custom_generated_pk:
201196
values = [
@@ -455,7 +450,7 @@ async def _prefetch_m2m_relation(
455450
if modifier.having_criterion:
456451
query = query.having(modifier.having_criterion)
457452

458-
_, raw_results = await self.db.execute_query(query.get_sql())
453+
_, raw_results = await self.db.execute_query(*query.get_parameterized_sql())
459454
relations: List[Tuple[Any, Any]] = []
460455
related_object_list: List["Model"] = []
461456
model_pk, related_pk = self.model._meta.pk, field_object.related_model._meta.pk

tortoise/backends/psycopg/client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import psycopg.pq
88
import psycopg.rows
99
import psycopg_pool
10+
from pypika.dialects.postgresql import PostgreSQLQuery, PostgreSQLQueryBuilder
11+
from pypika.terms import Parameterizer
1012

1113
import tortoise.backends.base.client as base_client
1214
import tortoise.backends.base_postgres.client as postgres_client
@@ -28,7 +30,26 @@ async def release(self, connection: psycopg.AsyncConnection):
2830
await self.putconn(connection)
2931

3032

33+
class PsycopgSQLQuery(PostgreSQLQuery):
34+
@classmethod
35+
def _builder(cls, **kwargs) -> "PostgreSQLQueryBuilder":
36+
return PsycopgSQLQueryBuilder(**kwargs)
37+
38+
39+
class PsycopgSQLQueryBuilder(PostgreSQLQueryBuilder):
40+
"""
41+
Psycopg opted to use a custom parameter placeholder, so we need to override the default
42+
"""
43+
44+
def get_parameterized_sql(self, **kwargs) -> typing.Tuple[str, list]:
45+
parameterizer = kwargs.pop(
46+
"parameterizer", Parameterizer(placeholder_factory=lambda _: "%s")
47+
)
48+
return super().get_parameterized_sql(parameterizer=parameterizer, **kwargs)
49+
50+
3151
class PsycopgClient(postgres_client.BasePostgresClient):
52+
query_class: typing.Type[PsycopgSQLQuery] = PsycopgSQLQuery
3253
executor_class: typing.Type[executor.PsycopgExecutor] = executor.PsycopgExecutor
3354
schema_generator: typing.Type[PsycopgSchemaGenerator] = PsycopgSchemaGenerator
3455
_pool: typing.Optional[AsyncConnectionPool] = None

tortoise/backends/psycopg/executor.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Optional
44

5-
from pypika import Parameter, Parameterizer
5+
from pypika import Parameter
66

77
from tortoise import Model
88
from tortoise.backends.base_postgres.executor import BasePostgresExecutor
@@ -26,7 +26,3 @@ async def _process_insert_result(
2626

2727
def parameter(self, pos: int) -> Parameter:
2828
return Parameter("%s")
29-
30-
@classmethod
31-
def parameterizer(cls) -> Parameterizer:
32-
return Parameterizer(placeholder_factory=lambda _: "%s")

tortoise/backends/sqlite/executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import datetime
2-
from decimal import Decimal
32
import sqlite3
3+
from decimal import Decimal
44
from typing import Optional, Type, Union
55

66
import pytz

tortoise/expressions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ def __init__(self, query: "AwaitableQuery") -> None:
205205

206206
def get_sql(self, **kwargs: Any) -> str:
207207
self.query._choose_db_if_not_chosen()
208-
return self.query._make_query(**kwargs)[0]
208+
self.query._make_query()
209+
return self.query.query.get_parameterized_sql(**kwargs)[0]
209210

210211
def as_(self, alias: str) -> "Selectable": # type: ignore
211212
self.query._choose_db_if_not_chosen()

tortoise/fields/relational.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ async def add(self, *instances: MODEL, using_db: "Optional[BaseDBAsyncClient]" =
184184
criterion = forward_field == pks_f[0] if len(pks_f) == 1 else forward_field.isin(pks_f)
185185
select_query = select_query.where(criterion)
186186

187-
_, already_existing_relations_raw = await db.execute_query(str(select_query))
187+
_, already_existing_relations_raw = await db.execute_query(
188+
*select_query.get_parameterized_sql()
189+
)
188190
already_existing_forward_pks = {
189191
related_pk_formatting_func(r[forward_key], self.instance)
190192
for r in already_existing_relations_raw
@@ -194,7 +196,7 @@ async def add(self, *instances: MODEL, using_db: "Optional[BaseDBAsyncClient]" =
194196
query = db.query_class.into(through_table).columns(forward_field, backward_field)
195197
for pk_f in pks_f_to_insert:
196198
query = query.insert(pk_f, pk_b)
197-
await db.execute_query(str(query))
199+
await db.execute_query(*query.get_parameterized_sql())
198200

199201
async def clear(self, using_db: "Optional[BaseDBAsyncClient]" = None) -> None:
200202
"""
@@ -237,7 +239,7 @@ async def _remove_or_clear(
237239
[related_pk_formatting_func(i.pk, i) for i in instances]
238240
)
239241
query = db.query_class.from_(through_table).where(condition).delete()
240-
await db.execute_query(str(query))
242+
await db.execute_query(*query.get_parameterized_sql())
241243

242244

243245
class RelationalField(Field[MODEL]):

0 commit comments

Comments
 (0)