50.6. Исполнитель
Исполнитель принимает план, созданный планировщиком/оптимизатором, и обрабатывает его рекурсивно, чтобы получить требуемый набор строк. Обработка выполняется по конвейеру, с получением данных по требованию. При вызове любого узла плана он должен выдать очередную строку, либо сообщить, что выдача строк завершена.
В качестве более конкретного примера, давайте предположим, что верхним узлом плана оказался узел MergeJoin
. Для того чтобы выполнить какое-либо соединение, необходимо выбрать две строки (одну из каждого вложенного плана). Поэтому исполнитель рекурсивно вызывает себя для обработки вложенных планов (он начинает с плана левого дерева
). Новый верхний узел (верхний узел левого вложенного плана) может быть, например, узлом Sort
, и тогда для получения входной строки снова требуется рекурсия. Дочерним узлом Sort
может быть узел SeqScan
, представляющий собственно чтение таблицы. В результате выполнения этого узла исполнитель выбирает одну строку из таблицы и возвращает её вызывающему узлу. Узел Sort
, в свою очередь, будет продолжать вызывать дочерний узел, пока не получит все строки для сортировки. Когда строки закончатся (дочерний узел сообщит об этом, возвратив NULL вместо строки), узел Sort
выполнит сортировку, и наконец сможет выдать свою первую строку, а именно строку первую по порядку сортировки. Остальные строки будут сохраняться в нём, чтобы он мог выдавать их по порядку при последующих вызовах.
Узел MergeJoin
подобным образом затребует первую строку и у вложенного плана справа. Затем он сравнивает две строки и определяет, можно ли их соединить; если да, он возвращает соединённую строки вызывающему узлу. При следующем вызове, или немедленно, если он не может соединить текущую пару поступивших строк, он переходит к следующей строке в одном отношении или в другом (в зависимости от результата сравнения) и снова проверяет соответствие. В конце концов, данные в одном или другом вложенном плане заканчиваются и узел MergeJoin
возвращает NULL, показывая тем самым, что другие строки соединения получить нельзя.
Сложные запросы могут содержать много уровней вложенности узлов плана, но общий подход тот же: каждый узел вычисляет и возвращает следующую полученную строку при очередном вызове. Каждый узел также должен производить отбор и расчёты, которые были назначены ему планировщиком.
Механизм исполнителя применяется для обработки всех четырёх основных типов SQL-запросов: SELECT
, INSERT
, UPDATE
и DELETE
. С SELECT
код исполнителя верхнего уровня должен только выдать клиенту все строки, полученные от дерева плана запроса. Запросы INSERT ... SELECT
, UPDATE
и DELETE
по сути выполняются как SELECT
под специальным узлом ModifyTable
на верхнем уровне плана.
Команда INSERT ... SELECT
подаёт строки в узел ModifyTable
для добавления в отношение. С UPDATE
планировщик делает так, чтобы каждая вычисленная строка включала значения всех изменённых столбцов плюс TID (Tuple ID, идентификатор кортежа) исходной целевой строки; эти данные подаются в узел ModifyTable
, который использует эту информацию, чтобы создать новую изменённую строку и пометить старую строку как удалённую. С DELETE
план фактически возвращает только один столбец, TID, а узел ModifyTable
использует значения TID, чтобы найти каждую целевую строку и пометить её как удалённую.
Простая команда INSERT ... VALUES
создаёт тривиальное дерево плана, содержащее единственный узел Result
, который вычисляет ровно одну строку результата и подаёт её в вышестоящий узел ModifyTable
для добавления в отношение.