Skip to content

Commit 4ff42d3

Browse files
authored
Merge pull request piccolo-orm#57 from piccolo-orm/raw_sql_where_clause
Added WhereRaw
2 parents fcf58ac + dcfd541 commit 4ff42d3

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

docs/src/piccolo/query_clauses/where.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Equal / Not Equal
3030
b.name != 'Rustaceans'
3131
).run_sync()
3232
33+
-------------------------------------------------------------------------------
34+
3335
Greater than / less than
3436
------------------------
3537

@@ -42,6 +44,8 @@ You can use the ``<, >, <=, >=`` operators, which work as you expect.
4244
b.popularity >= 100
4345
).run_sync()
4446
47+
-------------------------------------------------------------------------------
48+
4549
like / ilike
4650
-------------
4751

@@ -64,6 +68,8 @@ The percentage operator is required to designate where the match should occur.
6468
6569
``ilike`` is identical, except it's case insensitive.
6670

71+
-------------------------------------------------------------------------------
72+
6773
not_like
6874
--------
6975

@@ -76,6 +82,8 @@ Usage is the same as ``like`` excepts it excludes matching rows.
7682
b.name.not_like('Py%')
7783
).run_sync()
7884
85+
-------------------------------------------------------------------------------
86+
7987
is_in / not_in
8088
--------------
8189

@@ -93,6 +101,8 @@ is_in / not_in
93101
b.name.not_in(['Rustaceans'])
94102
).run_sync()
95103
104+
-------------------------------------------------------------------------------
105+
96106
Complex queries - and / or
97107
---------------------------
98108

@@ -151,3 +161,42 @@ Rather than using the ``|`` and ``&`` characters, you can use the ``And`` and
151161
b.name == 'Pythonistas'
152162
)
153163
).run_sync()
164+
165+
-------------------------------------------------------------------------------
166+
167+
WhereRaw
168+
--------
169+
170+
In certain situations you may want to have raw SQL in your where clause.
171+
172+
.. code-block:: python
173+
174+
from piccolo.columns.combination import WhereRaw
175+
176+
Band.select().where(
177+
WhereRaw("name = 'Pythonistas'")
178+
).run_sync()
179+
180+
It's important to parameterise your SQL statements if the values come from an
181+
untrusted source, otherwise it could lead to a SQL injection attack.
182+
183+
.. code-block:: python
184+
185+
from piccolo.columns.combination import WhereRaw
186+
187+
value = "Could be dangerous"
188+
189+
Band.select().where(
190+
WhereRaw("name = {}", value)
191+
).run_sync()
192+
193+
``WhereRaw`` can be combined into complex queries, just as you'd expect:
194+
195+
.. code-block:: python
196+
197+
from piccolo.columns.combination import WhereRaw
198+
199+
b = Band
200+
b.select().where(
201+
WhereRaw("name = 'Pythonistas'") | (b.popularity > 1000)
202+
).run_sync()

piccolo/columns/combination.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ class Undefined:
5454
UNDEFINED = Undefined()
5555

5656

57+
class WhereRaw(CombinableMixin):
58+
def __init__(self, sql: str, *args: t.Any) -> None:
59+
"""
60+
Execute raw SQL queries in your where clause. Use with caution!
61+
62+
await Band.where(
63+
WhereRaw("name = 'Pythonistas'")
64+
)
65+
66+
Or passing in parameters:
67+
68+
await Band.where(
69+
WhereRaw("name = {}", 'Pythonistas')
70+
)
71+
"""
72+
self.querystring = QueryString(sql, *args)
73+
74+
def __str__(self):
75+
return self.querystring.__str__()
76+
77+
5778
class Where(CombinableMixin):
5879

5980
__slots__ = ("column", "value", "values", "operator")

tests/table/test_select.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from unittest import TestCase
22

33
from piccolo.apps.user.tables import BaseUser
4+
from piccolo.columns.combination import WhereRaw
45
from piccolo.query.methods.select import Count
56

67
from ..base import DBTestCase, postgres_only, sqlite_only
@@ -116,6 +117,59 @@ def test_where_less_equal_than(self):
116117
response, [{"name": "Pythonistas"}, {"name": "CSharps"}]
117118
)
118119

120+
def test_where_raw(self):
121+
"""
122+
Make sure raw SQL passed in to a where clause works as expected.
123+
"""
124+
self.insert_rows()
125+
126+
response = (
127+
Band.select(Band.name)
128+
.where(WhereRaw("name = 'Pythonistas'"))
129+
.run_sync()
130+
)
131+
132+
print(f"response = {response}")
133+
134+
self.assertEqual(response, [{"name": "Pythonistas"}])
135+
136+
def test_where_raw_with_args(self):
137+
"""
138+
Make sure raw SQL with args, passed in to a where clause, works
139+
as expected.
140+
"""
141+
self.insert_rows()
142+
143+
response = (
144+
Band.select(Band.name)
145+
.where(WhereRaw("name = {}", "Pythonistas"))
146+
.run_sync()
147+
)
148+
149+
print(f"response = {response}")
150+
151+
self.assertEqual(response, [{"name": "Pythonistas"}])
152+
153+
def test_where_raw_combined_with_where(self):
154+
"""
155+
Make sure WhereRaw can be combined with Where.
156+
"""
157+
self.insert_rows()
158+
159+
response = (
160+
Band.select(Band.name)
161+
.where(
162+
WhereRaw("name = 'Pythonistas'") | (Band.name == "Rustaceans")
163+
)
164+
.run_sync()
165+
)
166+
167+
print(f"response = {response}")
168+
169+
self.assertEqual(
170+
response, [{"name": "Pythonistas"}, {"name": "Rustaceans"}]
171+
)
172+
119173
def test_where_and(self):
120174
self.insert_rows()
121175

0 commit comments

Comments
 (0)