Skip to content

Commit febda9c

Browse files
fix(django-spanner): resolve review feedback for returning_columns and lookups parameter indexing
1 parent b219da1 commit febda9c

4 files changed

Lines changed: 76 additions & 14 deletions

File tree

packages/django-google-spanner/django_spanner/lookups.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ def contains(self, compiler, connection):
4747
params = list(lhs_params)
4848
params.extend(rhs_params)
4949
is_icontains = self.lookup_name.startswith("i")
50-
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
50+
if self.rhs_is_direct_value() and rhs_params and not self.bilateral_transforms:
5151
rhs_sql = self.get_rhs_op(connection, rhs_sql)
52+
rhs_idx = len(lhs_params)
5253
# Chop the leading and trailing percent signs that Django adds to the
5354
# param since this isn't a LIKE query as Django expects.
54-
params[0] = params[0][1:-1]
55+
params[rhs_idx] = params[rhs_idx][1:-1]
5556
# Add the case insensitive flag for icontains.
5657
if is_icontains:
57-
params[0] = "(?i)" + params[0]
58+
params[rhs_idx] = "(?i)" + params[rhs_idx]
5859
# rhs_sql is REGEXP_CONTAINS(%s, %%s), and lhs_sql is the column name.
5960
return rhs_sql % lhs_sql, tuple(params)
6061
else:
@@ -96,8 +97,9 @@ def iexact(self, compiler, connection):
9697
params.extend(rhs_params)
9798
rhs_sql = self.get_rhs_op(connection, rhs_sql)
9899
# Wrap the parameter in ^ and $ to restrict the regex to an exact match.
99-
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
100-
params[0] = "^(?i)%s$" % params[0]
100+
if self.rhs_is_direct_value() and rhs_params and not self.bilateral_transforms:
101+
rhs_idx = len(lhs_params)
102+
params[rhs_idx] = "^(?i)%s$" % params[rhs_idx]
101103
else:
102104
# lhs_sql is the expression/column to use as the regular expression.
103105
# Use concat to make the value case-insensitive.
@@ -143,12 +145,13 @@ def regex(self, compiler, connection):
143145
params = list(lhs_params)
144146
params.extend(rhs_params)
145147
is_iregex = self.lookup_name.startswith("i")
146-
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
148+
if self.rhs_is_direct_value() and rhs_params and not self.bilateral_transforms:
147149
rhs_sql = self.get_rhs_op(connection, rhs_sql)
150+
rhs_idx = len(lhs_params)
148151
if is_iregex:
149-
params[0] = "(?i)%s" % params[0]
152+
params[rhs_idx] = "(?i)%s" % params[rhs_idx]
150153
else:
151-
params[0] = str(params[0])
154+
params[rhs_idx] = str(params[rhs_idx])
152155
# rhs_sql is REGEXP_CONTAINS(%s, %%s), and lhs_sql is the column name.
153156
return rhs_sql % lhs_sql, tuple(params)
154157
else:
@@ -191,15 +194,16 @@ def startswith_endswith(self, compiler, connection):
191194
is_insensitive = self.lookup_name.startswith("i")
192195
# Chop the leading (endswith) or trailing (startswith) percent sign that
193196
# Django adds to the param since this isn't a LIKE query as Django expects.
194-
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
197+
if self.rhs_is_direct_value() and rhs_params and not self.bilateral_transforms:
195198
rhs_sql = self.get_rhs_op(connection, rhs_sql)
199+
rhs_idx = len(lhs_params)
196200
if is_endswith:
197-
params[0] = str(params[0][1:]) + "$"
201+
params[rhs_idx] = str(params[rhs_idx][1:]) + "$"
198202
else:
199-
params[0] = "^" + str(params[0][:-1])
203+
params[rhs_idx] = "^" + str(params[rhs_idx][:-1])
200204
# Add the case insensitive flag for istartswith or iendswith.
201205
if is_insensitive:
202-
params[0] = "(?i)" + params[0]
206+
params[rhs_idx] = "(?i)" + params[rhs_idx]
203207
# rhs_sql is REGEXP_CONTAINS(%s, %%s), and lhs_sql is the column name.
204208
return rhs_sql % lhs_sql, tuple(params)
205209
else:

packages/django-google-spanner/django_spanner/operations.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ class DatabaseOperations(BaseDatabaseOperations):
4646
def returning_columns(self, fields):
4747
if not fields:
4848
return "", ()
49-
columns = [self.quote_name(field.column) for field in fields]
49+
columns = [
50+
self.quote_name(getattr(field, "column", str(field))) for field in fields
51+
]
5052
return "THEN RETURN %s" % ", ".join(columns), ()
5153

5254
# In Django <= 5.2, this method was named return_insert_columns

packages/django-google-spanner/tests/unit/django_spanner/test_lookups.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
from decimal import Decimal
88

9-
from django.db.models import F
9+
from django.db.models import F, Value
10+
from django.db.models.functions import Concat
1011

1112
from django_spanner.compiler import SQLCompiler
1213
from tests.unit.django_spanner.simple_test import SpannerSimpleTestClass
@@ -271,3 +272,53 @@ def test_iexact_sql_query_case_insensitive_value_match(self):
271272
)
272273
self.assertEqual(sql_compiled, expected_sql)
273274
self.assertEqual(params, ("abc",))
275+
276+
def test_icontains_with_lhs_params(self):
277+
qs = (
278+
Author.objects.annotate(greeting=Concat(Value("User: "), "name"))
279+
.filter(greeting__icontains="john")
280+
.values("name")
281+
)
282+
compiler = SQLCompiler(qs.query, self.connection, "default")
283+
sql, params = compiler.as_sql()
284+
self.assertEqual(params, ("User: ", "", "", "(?i)john"))
285+
286+
def test_iexact_with_lhs_params(self):
287+
qs = (
288+
Author.objects.annotate(greeting=Concat(Value("User: "), "name"))
289+
.filter(greeting__iexact="john")
290+
.values("name")
291+
)
292+
compiler = SQLCompiler(qs.query, self.connection, "default")
293+
sql, params = compiler.as_sql()
294+
self.assertEqual(params, ("User: ", "", "", "^(?i)john$"))
295+
296+
def test_istartswith_with_lhs_params(self):
297+
qs = (
298+
Author.objects.annotate(greeting=Concat(Value("User: "), "name"))
299+
.filter(greeting__istartswith="john")
300+
.values("name")
301+
)
302+
compiler = SQLCompiler(qs.query, self.connection, "default")
303+
sql, params = compiler.as_sql()
304+
self.assertEqual(params, ("User: ", "", "", "(?i)^john"))
305+
306+
def test_iendswith_with_lhs_params(self):
307+
qs = (
308+
Author.objects.annotate(greeting=Concat(Value("User: "), "name"))
309+
.filter(greeting__iendswith="john")
310+
.values("name")
311+
)
312+
compiler = SQLCompiler(qs.query, self.connection, "default")
313+
sql, params = compiler.as_sql()
314+
self.assertEqual(params, ("User: ", "", "", "(?i)john$"))
315+
316+
def test_iregex_with_lhs_params(self):
317+
qs = (
318+
Author.objects.annotate(greeting=Concat(Value("User: "), "name"))
319+
.filter(greeting__iregex="^john")
320+
.values("name")
321+
)
322+
compiler = SQLCompiler(qs.query, self.connection, "default")
323+
sql, params = compiler.as_sql()
324+
self.assertEqual(params, ("User: ", "", "", "(?i)^john"))

packages/django-google-spanner/tests/unit/django_spanner/test_operations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ def test_returning_columns(self):
275275
self.assertEqual(sql, "THEN RETURN id, name")
276276
self.assertEqual(params, ())
277277

278+
def test_returning_columns_with_strings(self):
279+
sql, params = self.db_operations.returning_columns(["id", "created_at"])
280+
self.assertEqual(sql, "THEN RETURN id, created_at")
281+
self.assertEqual(params, ())
282+
278283
def test_returning_columns_empty(self):
279284
sql, params = self.db_operations.returning_columns([])
280285
self.assertEqual(sql, "")

0 commit comments

Comments
 (0)