1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
|
.. _query-operators:
Query operators
===============
The following types of comparisons are supported by peewee:
================ =======================================
Comparison Meaning
================ =======================================
``==`` x equals y
``<`` x is less than y
``<=`` x is less than or equal to y
``>`` x is greater than y
``>=`` x is greater than or equal to y
``!=`` x is not equal to y
``<<`` x IN y, where y is a list or query
``>>`` x IS y, where y is None/NULL
``%`` x LIKE y where y may contain wildcards
``**`` x ILIKE y where y may contain wildcards
``^`` x XOR y
``~`` Unary negation (e.g., NOT x)
================ =======================================
Because I ran out of operators to override, there are some additional query
operations available as methods:
======================= ===============================================
Method Meaning
======================= ===============================================
``.in_(value)`` IN lookup (identical to ``<<``).
``.not_in(value)`` NOT IN lookup.
``.is_null(is_null)`` IS NULL or IS NOT NULL. Accepts boolean param.
``.contains(substr)`` Wild-card search for substring.
``.startswith(prefix)`` Search for values beginning with ``prefix``.
``.endswith(suffix)`` Search for values ending with ``suffix``.
``.between(low, high)`` Search for values between ``low`` and ``high``.
``.regexp(exp)`` Regular expression match (case-sensitive).
``.iregexp(exp)`` Regular expression match (case-insensitive).
``.bin_and(value)`` Binary AND.
``.bin_or(value)`` Binary OR.
``.concat(other)`` Concatenate two strings or objects using ``||``.
``.distinct()`` Mark column for DISTINCT selection.
``.collate(collation)`` Specify column with the given collation.
``.cast(type)`` Cast the value of the column to the given type.
======================= ===============================================
To combine clauses using logical operators, use:
================ ==================== ======================================================
Operator Meaning Example
================ ==================== ======================================================
``&`` AND ``(User.is_active == True) & (User.is_admin == True)``
``|`` (pipe) OR ``(User.is_admin) | (User.is_superuser)``
``~`` NOT (unary negation) ``~(User.username.contains('admin'))``
================ ==================== ======================================================
Here is how you might use some of these query operators:
.. code-block:: python
# Find the user whose username is "charlie".
User.select().where(User.username == 'charlie')
# Find the users whose username is in [charlie, huey, mickey]
User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
Employee.select().where(Employee.salary.between(50000, 60000))
Employee.select().where(Employee.name.startswith('C'))
Blog.select().where(Blog.title.contains(search_string))
Here is how you might combine expressions. Comparisons can be arbitrarily
complex.
.. note::
Note that the actual comparisons are wrapped in parentheses. Python's operator
precedence necessitates that comparisons be wrapped in parentheses.
.. code-block:: python
# Find any users who are active administrations.
User.select().where(
(User.is_admin == True) &
(User.is_active == True))
# Find any users who are either administrators or super-users.
User.select().where(
(User.is_admin == True) |
(User.is_superuser == True))
# Find any Tweets by users who are not admins (NOT IN).
admins = User.select().where(User.is_admin == True)
non_admin_tweets = Tweet.select().where(Tweet.user.not_in(admins))
# Find any users who are not my friends (strangers).
friends = User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
strangers = User.select().where(User.id.not_in(friends))
.. warning::
Although you may be tempted to use python's ``in``, ``and``, ``or`` and
``not`` operators in your query expressions, these **will not work.** The
return value of an ``in`` expression is always coerced to a boolean value.
Similarly, ``and``, ``or`` and ``not`` all treat their arguments as boolean
values and cannot be overloaded.
So just remember:
* Use ``.in_()`` and ``.not_in()`` instead of ``in`` and ``not in``
* Use ``&`` instead of ``and``
* Use ``|`` instead of ``or``
* Use ``~`` instead of ``not``
* Use ``.is_null()`` instead of ``is None`` or ``== None``.
* **Don't forget to wrap your comparisons in parentheses when using logical operators.**
For more examples, see the :ref:`expressions` section.
.. note::
**LIKE and ILIKE with SQLite**
Because SQLite's ``LIKE`` operation is case-insensitive by default,
peewee will use the SQLite ``GLOB`` operation for case-sensitive searches.
The glob operation uses asterisks for wildcards as opposed to the usual
percent-sign. If you are using SQLite and want case-sensitive partial
string matching, remember to use asterisks for the wildcard.
Three valued logic
------------------
Because of the way SQL handles ``NULL``, there are some special operations
available for expressing:
* ``IS NULL``
* ``IS NOT NULL``
* ``IN``
* ``NOT IN``
While it would be possible to use the ``IS NULL`` and ``IN`` operators with the
negation operator (``~``), sometimes to get the correct semantics you will need
to explicitly use ``IS NOT NULL`` and ``NOT IN``.
The simplest way to use ``IS NULL`` and ``IN`` is to use the operator
overloads:
.. code-block:: python
# Get all User objects whose last login is NULL.
User.select().where(User.last_login >> None)
# Get users whose username is in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username << usernames)
If you don't like operator overloads, you can call the Field methods instead:
.. code-block:: python
# Get all User objects whose last login is NULL.
User.select().where(User.last_login.is_null(True))
# Get users whose username is in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username.in_(usernames))
To negate the above queries, you can use unary negation, but for the correct
semantics you may need to use the special ``IS NOT`` and ``NOT IN`` operators:
.. code-block:: python
# Get all User objects whose last login is *NOT* NULL.
User.select().where(User.last_login.is_null(False))
# Using unary negation instead.
User.select().where(~(User.last_login >> None))
# Get users whose username is *NOT* in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username.not_in(usernames))
# Using unary negation instead.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(~(User.username << usernames))
.. _custom-operators:
Adding user-defined operators
-----------------------------
Because I ran out of python operators to overload, there are some missing
operators in peewee, for instance ``modulo``. If you find that you need to
support an operator that is not in the table above, it is very easy to add your
own.
Here is how you might add support for ``modulo`` in SQLite:
.. code-block:: python
from peewee import *
from peewee import Expression # the building block for expressions
def mod(lhs, rhs):
return Expression(lhs, '%', rhs)
Now you can use these custom operators to build richer queries:
.. code-block:: python
# Users with even ids.
User.select().where(mod(User.id, 2) == 0)
For more examples check out the source to the ``playhouse.postgresql_ext``
module, as it contains numerous operators specific to postgresql's hstore.
.. _expressions:
Expressions
-----------
Peewee is designed to provide a simple, expressive, and pythonic way of
constructing SQL queries. This section will provide a quick overview of some
common types of expressions.
There are two primary types of objects that can be composed to create
expressions:
* :py:class:`Field` instances
* SQL aggregations and functions using :py:class:`fn`
We will assume a simple "User" model with fields for username and other things.
It looks like this:
.. code-block:: python
class User(Model):
username = CharField()
is_admin = BooleanField()
is_active = BooleanField()
last_login = DateTimeField()
login_count = IntegerField()
failed_logins = IntegerField()
Comparisons use the :ref:`query-operators`:
.. code-block:: python
# username is equal to 'charlie'
User.username == 'charlie'
# user has logged in less than 5 times
User.login_count < 5
Comparisons can be combined using **bitwise** *and* and *or*. Operator
precedence is controlled by python and comparisons can be nested to an
arbitrary depth:
.. code-block:: python
# User is both and admin and has logged in today
(User.is_admin == True) & (User.last_login >= today)
# User's username is either charlie or charles
(User.username == 'charlie') | (User.username == 'charles')
Comparisons can be used with functions as well:
.. code-block:: python
# user's username starts with a 'g' or a 'G':
fn.Lower(fn.Substr(User.username, 1, 1)) == 'g'
We can do some fairly interesting things, as expressions can be compared
against other expressions. Expressions also support arithmetic operations:
.. code-block:: python
# users who entered the incorrect more than half the time and have logged
# in at least 10 times
(User.failed_logins > (User.login_count * .5)) & (User.login_count > 10)
Expressions allow us to do *atomic updates*:
.. code-block:: python
# when a user logs in we want to increment their login count:
User.update(login_count=User.login_count + 1).where(User.id == user_id)
Expressions can be used in all parts of a query, so experiment!
Row values
^^^^^^^^^^
Many databases support `row values <https://www.sqlite.org/rowvalue.html>`_,
which are similar to Python `tuple` objects. In Peewee, it is possible to use
row-values in expressions via :py:class:`Tuple`. For example,
.. code-block:: python
# If for some reason your schema stores dates in separate columns ("year",
# "month" and "day"), you can use row-values to find all rows that happened
# in a given month:
Tuple(Event.year, Event.month) == (2019, 1)
The more common use for row-values is to compare against multiple columns from
a subquery in a single expression. There are other ways to express these types
of queries, but row-values may offer a concise and readable approach.
For example, assume we have a table "EventLog" which contains an event type, an
event source, and some metadata. We also have an "IncidentLog", which has
incident type, incident source, and metadata columns. We can use row-values to
correlate incidents with certain events:
.. code-block:: python
class EventLog(Model):
event_type = TextField()
source = TextField()
data = TextField()
timestamp = TimestampField()
class IncidentLog(Model):
incident_type = TextField()
source = TextField()
traceback = TextField()
timestamp = TimestampField()
# Get a list of all the incident types and sources that have occurred today.
incidents = (IncidentLog
.select(IncidentLog.incident_type, IncidentLog.source)
.where(IncidentLog.timestamp >= datetime.date.today()))
# Find all events that correlate with the type and source of the
# incidents that occurred today.
events = (EventLog
.select()
.where(Tuple(EventLog.event_type, EventLog.source).in_(incidents))
.order_by(EventLog.timestamp))
Other ways to express this type of query would be to use a :ref:`join <relationships>`
or to :ref:`join on a subquery <join-subquery>`. The above example is there
just to give you and idea how :py:class:`Tuple` might be used.
You can also use row-values to update multiple columns in a table, when the new
data is derived from a subquery. For an example, see `here <https://www.sqlite.org/rowvalue.html#update_multiple_columns_of_a_table_based_on_a_query>`_.
SQL Functions
-------------
SQL functions, like ``COUNT()`` or ``SUM()``, can be expressed using the
:py:func:`fn` helper:
.. code-block:: python
# Get all users and the number of tweets they've authored. Sort the
# results from most tweets -> fewest tweets.
query = (User
.select(User, fn.COUNT(Tweet.id).alias('tweet_count'))
.join(Tweet, JOIN.LEFT_OUTER)
.group_by(User)
.order_by(fn.COUNT(Tweet.id).desc()))
for user in query:
print('%s -- %s tweets' % (user.username, user.tweet_count))
The ``fn`` helper exposes any SQL function as if it were a method. The
parameters can be fields, values, subqueries, or even nested functions.
Nesting function calls
^^^^^^^^^^^^^^^^^^^^^^
Suppose you need to want to get a list of all users whose username begins with
*a*. There are a couple ways to do this, but one method might be to use some
SQL functions like *LOWER* and *SUBSTR*. To use arbitrary SQL functions, use
the special :py:func:`fn` object to construct queries:
.. code-block:: python
# Select the user's id, username and the first letter of their username, lower-cased
first_letter = fn.LOWER(fn.SUBSTR(User.username, 1, 1))
query = User.select(User, first_letter.alias('first_letter'))
# Alternatively we could select only users whose username begins with 'a'
a_users = User.select().where(first_letter == 'a')
>>> for user in a_users:
... print(user.username)
SQL Helper
----------
There are times when you may want to simply pass in some arbitrary sql. You can
do this using the special :py:class:`SQL` class. One use-case is when
referencing an alias:
.. code-block:: python
# We'll query the user table and annotate it with a count of tweets for
# the given user
query = (User
.select(User, fn.Count(Tweet.id).alias('ct'))
.join(Tweet)
.group_by(User))
# Now we will order by the count, which was aliased to "ct"
query = query.order_by(SQL('ct'))
# You could, of course, also write this as:
query = query.order_by(fn.COUNT(Tweet.id))
There are two ways to execute hand-crafted SQL statements with peewee:
1. :py:meth:`Database.execute_sql` for executing any type of query
2. :py:class:`RawQuery` for executing ``SELECT`` queries and returning model
instances.
Security and SQL Injection
--------------------------
By default peewee will parameterize queries, so any parameters passed in by the
user will be escaped. The only exception to this rule is if you are writing a
raw SQL query or are passing in a ``SQL`` object which may contain untrusted
data. To mitigate this, ensure that any user-defined data is passed in as a
query parameter and not part of the actual SQL query:
.. code-block:: python
# Bad! DO NOT DO THIS!
query = MyModel.raw('SELECT * FROM my_table WHERE data = %s' % (user_data,))
# Good. `user_data` will be treated as a parameter to the query.
query = MyModel.raw('SELECT * FROM my_table WHERE data = %s', user_data)
# Bad! DO NOT DO THIS!
query = MyModel.select().where(SQL('Some SQL expression %s' % user_data))
# Good. `user_data` will be treated as a parameter.
query = MyModel.select().where(SQL('Some SQL expression %s', user_data))
.. note::
MySQL and Postgresql use ``'%s'`` to denote parameters. SQLite, on the
other hand, uses ``'?'``. Be sure to use the character appropriate to your
database. You can also find this parameter by checking
:py:attr:`Database.param`.
|