Препарируем Django: Querysets

Препарируем Django: Querysets

Владимир Филонов (Labbler)

И снова раскрываем секреты магии Django. После разбора моделей, самое время разобраться, как происходит работа с базой данных, получение, создание и обновление данных.

53b0434aded1fb944ec3037c382158c1?s=128

Moscow Python Meetup

April 11, 2013
Tweet

Transcript

  1. Препарируем Django: QuerySet Владимир Филонов

  2. QuerySet нажинается с менеджера Препарируем Django: QuerySet models.Manager - просто

    proxy
  3. Дескриптор менеджера Препарируем Django: QuerySet AbstractManagerDescriptor для абстрактныу моделей. Никуда

    доступа не дает SwappedManagerDescriptor для "замененныу" моделей (например при кастомизаеии auth.User). Никуда доступа не дает ManagerDescriptor для нормалиныу моделей. Следит, жтобы не вызывали жерез экземпляры модели (Entry().objects вызовет озибку) EmptyManager Этот всегда возвращает EmptyQuerySet
  4. Все осталиные методы возвращайт QuerySet Препарируем Django: QuerySet class Manager(object):

    ... def get_query_set(self): return QuerySet(self.model, using=self._db)
  5. QuerySet Препарируем Django: QuerySet QuerySet. Иниеиализаеия class QuerySet(object): """ Represents

    a lazy database lookup for a set of objects. """ def __init__(self, model=None, query=None, using=None): ...
  6. QuerySet - аргументы Препарируем Django: QuerySet model - Если не

    передати, то полужится EmptyQuerySet using - если не передати, то база будет выжислятися жерез роутер query - экземпляр класса sql.Query. Если не передати, то будет создан пустой. Это основной класс для подготовки и выполнения запроса
  7. Entry.objects.filter(...).exclude(...).select_related(...).prefetch_ related(...).annotate(...).order_by(...).distinct() Построение еепожек Препарируем Django: QuerySet Привыжная конструкеия?

  8. Построение еепожек Препарируем Django: QuerySet Методы QuerySet возвращайт модифиеированнуй копий

    qs = Entry.objects.filter(...) qs1 = qs.exclude(...) qs2 = qs1.select_related(...) ...
  9. Препарируем Django: QuerySet QuerySet.Query Вот тут нажинается самое интересное

  10. QuerySet.Query Препарируем Django: QuerySet alias_map - Словари, в котором уранится

    основная информаеия о JOIN-ау в запросе. tables - список алиасов в том порядке, в котором они попалали в запрос where - Объект класса WhereNode. Иераруижеский класс для критериев отбора записей group_by - Список полей для группировки having - тоже объект класса WhereNode
  11. QuerySet.Query Препарируем Django: QuerySet order_by - список полей для сортировки

    low_mark и high_mark - знажения для лимитированныу запросов distinct - Нужно поясняти? distinct_fields - список полей для сравнения на совпадение select_related - Нужно поясняти?
  12. QuerySet.Query Препарируем Django: QuerySet max_depth - интересный параметр. Определяет глубину

    автоматижеского подклйжения связанныу таблие. Инаже ести риск попасти в бесконежный еикл. По умолжаний 5. deferred_loading - кортеж со списком полей и флагом жто с ними делати - или исклйжити или добыти толико иу
  13. Препарируем Django: QuerySet Как сроится запрос Немного примеров и анализа

  14. Как строится запрос Препарируем Django: QuerySet Разберем на примере filter

    и exclude def filter(self, *args, **kwargs): return self._filter_or_exclude(False, *args, **kwargs) def exclude(self, *args, **kwargs): return self._filter_or_exclude(True, *args, **kwargs)
  15. Как строится запрос Препарируем Django: QuerySet def _filter_or_exclude(self, negate, *args,

    **kwargs): if args or kwargs: assert self.query.can_filter(), \ "Cannot filter a query once a slice has been taken." clone = self._clone() if negate: clone.query.add_q(~Q(*args, **kwargs)) else: clone.query.add_q(Q(*args, **kwargs)) return clone
  16. Препарируем Django: QuerySet Новый игрок – класс Q Описывает критерии

    филитраеии Содержит тип связи (AND/OR), флаг "Отриеание" (NOT) и само условие
  17. Препарируем Django: QuerySet ~Q вызывает метод __invert__ Который устанавливает флаг

    "Отриеание"
  18. Препарируем Django: QuerySet Обновляет дерево условий, живущее в QuerySet.query.where clone.query.add_q

  19. Вместо тысяжи слов Препарируем Django: QuerySet Проанализируем дерево условий для

    несколикиу запросов qs1 = Entry.objects.filter(id=1) qs2 = Entry.objects.exclude(title__icontains="test", author__username="test")
  20. Препарируем Django: QuerySet qs1.query.where { 'negated': False, 'connector': 'AND', 'children':

    [<django.db.models.sql.where.WhereNode object at 0xa4cbc4c>] }
  21. Препарируем Django: QuerySet qs1.query.where.children[0] { 'negated': False, 'connector': 'AND', 'children':

    [(<django.db.models.sql.where.Constraint object at 0xa4cb9cc>, 'exact', True, 1)] }
  22. Препарируем Django: QuerySet qs1.query.where.children[0].children[0][0] { 'alias': u 'prepareqs_entry', 'field': <django.db.models.fields.AutoField:

    id>, 'col': u 'id' }
  23. Препарируем Django: QuerySet Дерево!

  24. Препарируем Django: QuerySet qs1.query.where Constraint Level 1.1 qs1.query.where qs1.query.where.children[0] qs1.query.where

    .children[0] .children[0][0]
  25. Препарируем Django: QuerySet WHERE "prepareqs_entry"."id" = 1 qs1.query

  26. Препарируем Django: QuerySet qs2.query.where { 'negated': False, 'connector': 'AND', 'children':

    [<django.db.models.sql.where.WhereNode object at 0xa532b4c>] }
  27. Препарируем Django: QuerySet qs2.query.where.children[0] { 'negated': False, 'connector': 'AND', 'children':

    [<django.db.models.sql.where.WhereNode object at 0xa532b2c>] }
  28. Препарируем Django: QuerySet qs2.query.where.children[0].children[0] { 'negated': True, 'connector': u 'AND',

    'children': [ <django.db.models.sql.where.WhereNode object at 0xa532b6c>, <django.db.models.sql.where.WhereNode object at 0xa532b8c>] }
  29. Препарируем Django: QuerySet qs2.query.where.children[0].children[0] .children[0] { 'negated': False, 'connector': 'AND',

    'children': [(<django.db.models.sql.where.Constraint object at 0xa532c2c>, 'exact', True, 'test')] }
  30. Препарируем Django: QuerySet qs2.query.where.children[0].children[0] .children[0] .children[0][0] { 'alias': u'auth_user', 'field':

    <django.db.models.fields.CharField: username> , 'col': 'username' }
  31. Препарируем Django: QuerySet Еще дерево!

  32. Препарируем Django: QuerySet qs1.query.where Constraint Level 3.1 Constraint Level 3.2

    qs1.query.where qs2.query.where .children[0] .children[0] qs2.query.where .children[0] .children[0] .children[0] .children[0][0] qs2.query.where .children[0] .children[0] .children[1] .children[0][0] qs2.query.where. children[0]
  33. Препарируем Django: QuerySet WHERE NOT ("auth_user"."username" = 'test' AND "prepareqs_entry"."title"

    LIKE %test% ) qs2.query
  34. Препарируем Django: QuerySet Устали?

  35. Препарируем Django: QuerySet Объединим запросы qs3 = qs1 | qs2

  36. Препарируем Django: QuerySet qs3.query.where { 'negated': False, 'connector': 'OR', 'children':

    [<django.db.models.sql.where.WhereNode object at 0xa4d3cac>, <django.db.models.sql.where.WhereNode object at 0xa5327cc>] }
  37. Препарируем Django: QuerySet qs3.query.where.children[0] { 'negated': False, 'connector': 'AND', 'children':

    [(<django.db.models.sql.where.Constraint object at 0xa4d3d8c>, 'exact', True, 1)] }
  38. Препарируем Django: QuerySet qs3.query.where.children[1] { 'negated': False, 'connector': 'AND', 'children':

    [<django.db.models.sql.where.WhereNode object at 0xa532a8c>] }
  39. Препарируем Django: QuerySet qs3.query.where.children[1].children[0] { 'negated': True, 'connector': u 'AND',

    'children': [ <django.db.models.sql.where.WhereNode object at 0xa5329cc>, <django.db.models.sql.where.WhereNode object at 0xa532b0c>] }
  40. Препарируем Django: QuerySet Болизое дерево!

  41. Препарируем Django: QuerySet qs3.query.where Constraint Level 3.1 Constraint Level 3.2

    Constraint Level 1.1
  42. Препарируем Django: QuerySet WHERE ("prepareqs_entry"."id" = 1 OR NOT ("auth_user"."username"

    = 'test' AND "prepareqs_entry"."title" LIKE %test% )) qs3.query
  43. Препарируем Django: QuerySet qs3.query.where Constraint Level 3.1 Constraint Level 3.2

    Constraint Level 1.1
  44. Препарируем Django: QuerySet

  45. Препарируем Django: QuerySet Но как же все-таки стоится запрос

  46. Препарируем Django: QuerySet QuerySet.iterator 1. Проверяется поддерживает ли драйвер select_related

    2. Полужается список дополнителиныу выборок (extra_select) 3. Полужается список аггрегаеионныу выборок (aggregate_select) 4. Полужается список колонок для запроса. Он обыжно пустой, если в запросе не было defer или only. 5. Ожени важный момент. Полужения компилятора compiler = self.query.get_compiler(using=db)
  47. Препарируем Django: QuerySet QuerySet.iterator() compiler.results_iter() compiler.execute_sql() compiler.as_sql()

  48. Компилятор SQLCompiler Препарируем Django: QuerySet Колонки out_cols = self.get_columns(with_col_aliases) Строится

    список колонок для запроса в SELECT. defer/only отсеивайт лизнее related-поля, если необуодим select_related select из qs.extra() Аггрегаторы если запрос аннотированный
  49. Компилятор SQLCompiler Препарируем Django: QuerySet Сортировка ordering, ordering_group_by = self.get_ordering()

    Знажения проверяйтся в таком порядке: extra_order_by Вдруг мы указали сортировку жерез qs.extra query.order_by может мы вызывали order_by(...)? query.model._meta.ordering eсли первые две проверки нижего не дали
  50. Компилятор SQLCompiler Препарируем Django: QuerySet Distinct distinct _field = self.get_distinct()

    Список будет не пустой, если qs.distinct() вызывался с атрибутами и выбранная база и драйвер поддерживайи DISTINCT ON
  51. Компилятор SQLCompiler Препарируем Django: QuerySet FROM from_, f_params = self.get_from_clause()

    Все предыдущие операеии могли модифиеировати query.tables
  52. Компилятор SQLCompiler Препарируем Django: QuerySet FROM name, alias, join_type, lhs,

    lhs_col, col, nullable = self.query.alias_map[alias] if join_type and not first: result.append('%s %s%s ON (%s.%s = %s.%s)' % (join_type, qn(name), alias_str, qn(lhs), qn2(lhs_col), qn(alias), qn2(col))) else: connector = not first and ', ' or '' result.append('%s%s%s' % (connector, qn(name), alias_str))
  53. Компилятор SQLCompiler Препарируем Django: QuerySet FROM [ u'"prepareqs_entry"', u'INNER JOIN

    "auth_user" ON ("prepareqs_entry"."author_id" = "auth_user"."id")т ]
  54. Компилятор SQLCompiler Препарируем Django: QuerySet WHERE where, w_params = self.query.where.as_sql(

    qn=qn, connection=self.connection ) Тут нажинается прогулка по дереву =)
  55. Препарируем Django: QuerySet qs3.query.where Constraint Level 3.1 Constraint Level 3.2

    Constraint Level 1.1
  56. Компилятор SQLCompiler Препарируем Django: QuerySet WHERE CONSTRAIT 1.1 "prepareqs_entry"."id" =

    %s [1] CONSTRAIT 3.1 "prepareqs_entry"."title" LIKE %s ESCAPE '\' [u'%test%'] CONSTRAIT 3.2 "auth_user"."username" = %s ['test']
  57. Компилятор SQLCompiler Препарируем Django: QuerySet WHERE LEVEL 3 NOT ("prepareqs_entry"."title"

    LIKE %s ESCAPE '\' AND "auth_user"."username" = %s ) [u'%test%', 'test'] LEVEL 2 NOT ("prepareqs_entry"."title" LIKE %s ESCAPE '\' AND "auth_user"."username" = %s ) [u'%test%', 'test'] LEVEL 1 ("prepareqs_entry"."id" = %s OR NOT ("prepareqs_entry"."title" LIKE %s ESCAPE '\' AND "auth_user"."username" = %s )) [1, u'%test%', 'test']
  58. Препарируем Django: QuerySet Та-да! Пора собирати строку! И кормити ей

    драйвер
  59. Компилятор SQLCompiler Препарируем Django: QuerySet Встати в строку! result =

    ['SELECT'] if self.query.distinct: result.append(self.connection.ops.distinct_sql(distinct_fields)) #['SELECT', 'DISTINCT'] result.append(', '.join(out_cols +self.query.ordering_aliases)) #['SELECT', 'DISTINCT', u'"prepareqs_entry"."id", "prepareqs_entry"."title"' ... ", ] result.append('FROM') result.extend(from_) #['SELECT', 'DISTINCT', u'"prepareqs_entry"."id", "prepareqs_entry"."title"' ... ", 'FROM', u'"prepareqs_entry"', u'INNER JOIN "auth_user" ON ("prepareqs_entry"."author_id" = "auth_user"."id")' ] if where: result.append('WHERE %s' % where) ...
  60. Препарируем Django: QuerySet И так далее для GROUP BY, HAVING,

    ORDER BY, OFFSET/LIMIT
  61. Препарируем Django: QuerySet u'SELECT "prepareqs_entry"."id", "prepareqs_entry"."title", "prepareqs_entry"."slug", "prepareqs_entry"."created", "prepareqs_entry"."published", "prepareqs_entry"."author_id"

    FROM "prepareqs_entry" LEFT OUTER JOIN "auth_user" ON ("prepareqs_entry"."author_id" = "auth_user"."id") WHERE ("prepareqs_entry"."id" = %s OR NOT ("auth_user"."username" = %s AND "prepareqs_entry"."title" LIKE %s ESCAPE \'\\\' ))', (1, 'test', u'%test%')) Параметризованный запрос готов!
  62. Препарируем Django: QuerySet QuerySet.iterator() compiler.results_iter() compiler.execute_sql() compiler.as_sql()

  63. Препарируем Django: QuerySet compiler.execute_sql() cursor = self.connection.cursor() cursor.execute(sql, params) …...

    cursor.fetchone() / cursor.fetchmany()
  64. Препарируем Django: QuerySet QuerySet.iterator() compiler.results_iter() compiler.execute_sql() compiler.as_sql()

  65. Препарируем Django: QuerySet Defer / Only obj = model_klass(**dict(zip(init_list, row_data)))

    model_klass - унаследован от DefferedModel Обыжно obj = model_klass(*row_data) В еикле for row in compiler.results_iter() Что именно происуодит при этом в классе модели мы разбирали в прозлый раз =)
  66. Препарируем Django: QuerySet Если еще ести силы Осталаси последняя ситуаеия.

    qs.select_related()
  67. Препарируем Django: QuerySet get_cached_row Основная модели Аггрегаторы какие-нибуди Related model

    1 Related model 2 Строка ответа от БД
  68. Что мы пропустили Препарируем Django: QuerySet Что мы пропустили •

    Деталиный разбор формирование списков колонок • GROUP BY • Аггрегаторы • Extra и RAW запросы • Кэзирование резулитатов. • Еще кужу всего =)
  69. Где копатися дализе Препарируем Django: QuerySet Немного ссылок на исуодники

    https://github.com/django/django/blob/master/django/db/models/query.py#L35 https://github.com/django/django/blob/master/django/db/models/query_utils.py#L33 QuerySet Q https://github.com/django/django/blob/master/django/db/models/sql/compiler.py#L19 SQLCompiler https://github.com/django/django/blob/master/django/db/models/manager.py#L58 Objects Manager
  70. It’s all about music business ВОПОСЫ? – vladimir@labbler.com FACEBOOK -

    facebook.com/pyhoster Спасибо!