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