Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

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

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

Moscow Python Meetup
PRO

April 11, 2013
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. Все осталиные методы возвращайт QuerySet
    Препарируем
    Django: QuerySet
    class Manager(object):
    ...
    def get_query_set(self):
    return QuerySet(self.model, using=self._db)

    View Slide

  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):
    ...

    View Slide

  6. QuerySet - аргументы
    Препарируем
    Django: QuerySet
    model - Если не передати, то полужится EmptyQuerySet
    using - если не передати, то база будет выжислятися жерез
    роутер
    query - экземпляр класса sql.Query. Если не передати, то
    будет создан пустой. Это основной класс для подготовки и
    выполнения запроса

    View Slide

  7. Entry.objects.filter(...).exclude(...).select_related(...).prefetch_
    related(...).annotate(...).order_by(...).distinct()
    Построение еепожек
    Препарируем
    Django: QuerySet
    Привыжная конструкеия?

    View Slide

  8. Построение еепожек
    Препарируем
    Django: QuerySet
    Методы QuerySet возвращайт модифиеированнуй копий
    qs = Entry.objects.filter(...)
    qs1 = qs.exclude(...)
    qs2 = qs1.select_related(...)
    ...

    View Slide

  9. Препарируем
    Django: QuerySet
    QuerySet.Query
    Вот тут нажинается самое интересное

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. Препарируем
    Django: QuerySet
    Как сроится запрос
    Немного примеров и анализа

    View Slide

  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)

    View Slide

  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

    View Slide

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

    View Slide

  17. Препарируем
    Django: QuerySet
    ~Q вызывает метод __invert__
    Который устанавливает флаг "Отриеание"

    View Slide

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

    View Slide

  19. Вместо тысяжи слов
    Препарируем
    Django: QuerySet
    Проанализируем дерево условий для несколикиу запросов
    qs1 = Entry.objects.filter(id=1)
    qs2 = Entry.objects.exclude(title__icontains="test",
    author__username="test")

    View Slide

  20. Препарируем
    Django: QuerySet
    qs1.query.where
    {
    'negated': False,
    'connector': 'AND',
    'children': [object at 0xa4cbc4c>]
    }

    View Slide

  21. Препарируем
    Django: QuerySet
    qs1.query.where.children[0]
    {
    'negated': False,
    'connector': 'AND',
    'children': [(object at 0xa4cb9cc>, 'exact', True, 1)]
    }

    View Slide

  22. Препарируем
    Django: QuerySet
    qs1.query.where.children[0].children[0][0]
    {
    'alias': u 'prepareqs_entry',
    'field': ,
    'col': u 'id'
    }

    View Slide

  23. Препарируем
    Django: QuerySet
    Дерево!

    View Slide

  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]

    View Slide

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

    View Slide

  26. Препарируем
    Django: QuerySet
    qs2.query.where
    {
    'negated': False,
    'connector': 'AND',
    'children': [object at 0xa532b4c>]
    }

    View Slide

  27. Препарируем
    Django: QuerySet
    qs2.query.where.children[0]
    {
    'negated': False,
    'connector': 'AND',
    'children': [object at 0xa532b2c>]
    }

    View Slide

  28. Препарируем
    Django: QuerySet
    qs2.query.where.children[0].children[0]
    {
    'negated': True,
    'connector': u 'AND',
    'children': [
    0xa532b6c>,
    0xa532b8c>]
    }

    View Slide

  29. Препарируем
    Django: QuerySet
    qs2.query.where.children[0].children[0]
    .children[0]
    {
    'negated': False,
    'connector': 'AND',
    'children': [(object at 0xa532c2c>, 'exact', True, 'test')]
    }

    View Slide

  30. Препарируем
    Django: QuerySet
    qs2.query.where.children[0].children[0]
    .children[0] .children[0][0]
    {
    'alias': u'auth_user',
    'field': ,
    'col': 'username'
    }

    View Slide

  31. Препарируем
    Django: QuerySet
    Еще дерево!

    View Slide

  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]

    View Slide

  33. Препарируем
    Django: QuerySet
    WHERE NOT ("auth_user"."username" = 'test' AND "prepareqs_entry"."title" LIKE
    %test% )
    qs2.query

    View Slide

  34. Препарируем
    Django: QuerySet
    Устали?

    View Slide

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

    View Slide

  36. Препарируем
    Django: QuerySet
    qs3.query.where
    {
    'negated': False,
    'connector': 'OR',
    'children': [object at 0xa4d3cac>, object at 0xa5327cc>]
    }

    View Slide

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

    View Slide

  38. Препарируем
    Django: QuerySet
    qs3.query.where.children[1]
    {
    'negated': False,
    'connector': 'AND',
    'children': [object at 0xa532a8c>]
    }

    View Slide

  39. Препарируем
    Django: QuerySet
    qs3.query.where.children[1].children[0]
    {
    'negated': True,
    'connector': u 'AND',
    'children': [
    0xa5329cc>,
    0xa532b0c>]
    }

    View Slide

  40. Препарируем
    Django: QuerySet
    Болизое дерево!

    View Slide

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

    View Slide

  42. Препарируем
    Django: QuerySet
    WHERE ("prepareqs_entry"."id" = 1 OR NOT ("auth_user"."username" = 'test' AND
    "prepareqs_entry"."title" LIKE %test% ))
    qs3.query

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

  47. Препарируем
    Django: QuerySet
    QuerySet.iterator()
    compiler.results_iter()
    compiler.execute_sql()
    compiler.as_sql()

    View Slide

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

    View Slide

  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сли первые две проверки
    нижего не дали

    View Slide

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

    View Slide

  51. Компилятор SQLCompiler
    Препарируем
    Django: QuerySet
    FROM
    from_, f_params = self.get_from_clause()
    Все предыдущие операеии могли модифиеировати
    query.tables

    View Slide

  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))

    View Slide

  53. Компилятор SQLCompiler
    Препарируем
    Django: QuerySet
    FROM
    [
    u'"prepareqs_entry"',
    u'INNER JOIN "auth_user" ON ("prepareqs_entry"."author_id" =
    "auth_user"."id")т
    ]

    View Slide

  54. Компилятор SQLCompiler
    Препарируем
    Django: QuerySet
    WHERE
    where, w_params = self.query.where.as_sql(
    qn=qn, connection=self.connection
    )
    Тут нажинается прогулка по дереву =)

    View Slide

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

    View Slide

  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']

    View Slide

  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']

    View Slide

  58. Препарируем
    Django: QuerySet
    Та-да! Пора собирати строку!
    И кормити ей драйвер

    View Slide

  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)
    ...

    View Slide

  60. Препарируем
    Django: QuerySet
    И так далее
    для GROUP BY, HAVING, ORDER BY, OFFSET/LIMIT

    View Slide

  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%'))
    Параметризованный запрос готов!

    View Slide

  62. Препарируем
    Django: QuerySet
    QuerySet.iterator()
    compiler.results_iter()
    compiler.execute_sql()
    compiler.as_sql()

    View Slide

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

    View Slide

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

    View Slide

  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()
    Что именно происуодит при этом в
    классе модели мы разбирали в прозлый
    раз =)

    View Slide

  66. Препарируем
    Django: QuerySet
    Если еще ести силы
    Осталаси последняя ситуаеия. qs.select_related()

    View Slide

  67. Препарируем
    Django: QuerySet
    get_cached_row
    Основная модели
    Аггрегаторы какие-нибуди
    Related model 1
    Related model 2
    Строка ответа от БД

    View Slide

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

    View Slide

  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

    View Slide

  70. It’s all about
    music business
    ВОПОСЫ? – [email protected]
    FACEBOOK - facebook.com/pyhoster
    Спасибо!

    View Slide