Внутреннее устройство хранимых процедур
Спецификация реализации хранимых процедур
Как работают разбор и выполнение запросов
Для выполнения запроса вызывается функция sql_parse.cc:mysql_parse(), которая в свою очередь вызывает парсер (yyparse()) с обновлённой структурой Lex в качестве результата. mysql_parse() затем вызывает mysql_execute_command(), который распределяет выполнение по коду команды (в Lex) на соответствующий код для выполнения конкретного запроса.
В выполнении запроса участвуют три структуры, которые представляют интерес для реализации хранимых процедур:
- Lex (упомянутая выше) — это "скомпилированный" запрос, то есть результат работы парсера, который затем интерпретируется для выполнения фактической работы. Он содержит значение перечисления (sql_command), которое представляет тип запроса, и все данные, собранные парсером, необходимые для выполнения (имена таблиц, поля, значения и т. д.).
- THD — это "время выполнения" состояния подключения, содержащее всё необходимое для конкретного клиентского подключения и, среди прочего, структуру Lex, которая выполняется в данный момент.
-
Item_*: Во время разбора все данные переводятся в "элементы", объекты подклассов "Item", такие какItem_int,Item_real,Item_string, и т. д., для основных типов данных, а также различные более специализированные типы Item для выражений, которые должны быть вычислены (объектыItem_func).
Как вписать хранимые процедуры в эту схему
Обзор классов и файлов для хранимых процедур
(Более подробные API в конце этой страницы)
класс sp_head (sp_head.{cc,h})
В нём, среди прочего, хранится массив "инструкций" и метод для выполнения процедуры.
класс sp_pcontext (sp_pcontext.{cc,h}
Это контекст разбора для процедуры. Он используется в основном во время разбора для отслеживания локальных параметров, переменных и меток, но также используется во время вызова для определения режима параметров (IN, OUT или INOUT) и типа при настройке контекста времени выполнения.
класс sp_instr (sp_head.{cc,h})
Это базовый класс для "инструкций", то есть того, что генерирует парсер. Оказывается, нам нужно всего лишь минимум 5 различных подклассов:
- sp_instr_stmt Выполнить оператор. Это "вызов" любого обычного оператора SQL, например, SELECT, INSERT и т. д. Он содержит структуру Lex для данного оператора.
- sp_instr_set Установить значение локальной переменной (или параметра)
- sp_instr_jump Безусловный переход.
- sp_instr_jump_if_not Переход, если условие не истинно. Оказывается, отрицательная проверка удобнее всего при генерации кода для конструкций управления потоком.
- sp_instr_freturn Возвратить значение из FUNCTION и выйти. Для обработчиков условий также необходимы некоторые специальные инструкции, см. этот раздел ниже.
класс sp_rcontext (sp_rcontext.h)
Это контекст времени выполнения в структуре THD. Он содержит массив элементов, параметров и локальных переменных для выполняемой в данный момент хранимой процедуры. Это означает, что поиск значения переменной во время выполнения занимает постоянное время — это простая операция индексирования.
класс Item_splocal (Item.{cc,h})
Это подкласс Item. Его единственная цель — скрыть тот факт, что фактический Item находится в текущем кадре (контексте времени выполнения). Он содержит смещение кадра и делегирует все методы действительному Item в кадре. Именно это генерирует парсер для локальных переменных.
Вспомогательные функции (sp.{cc,h})
Содержит функции для создания, удаления и поиска хранимой процедуры в таблице mysql.proc (или в кэше).
Разбор CREATE PROCEDURE
При разборе CREATE PROCEDURE парсер сначала инициализирует поля sphead и spcont (контекст времени выполнения) в Lex. Код sql_command для результата разбора — SQLCOM_CREATE_PROCEDURE.
Разбор списка параметров и тела относительно прост:
- Параметры: имя, тип и режим (IN/OUT/INOUT) помещаются в
spcont - Объявленные локальные переменные: аналогично параметрам (режим — тогда IN)
- Ссылки на локальные переменные: если идентификатор найден в
spcont, создаётсяItem_splocalс индексом кадра переменной, иначе создаётсяItem_fieldилиItem_ref(как и раньше). - Операторы: Lex в THD заменяется новой структурой Lex, и оператор разбирается как обычно. Создаётся
sp_instr_stmt, содержащий новую Lex, и добавляется к инструкциям вsphead. После этого Lex процедуры восстанавливается в THD. - SET var: Установка локальной переменной генерирует инструкцию
sp_instr_setс смещением переменной в кадре, выражением (элементом Item) и типом. - Управление потоком: Конструкции управления потоком, такие как IF, WHILE и т. д., генерируют условные и безусловные переходы в "очевидном" порядке, но могут потребоваться некоторые пояснения:
- Переходы вперёд: При переходах вперёд точное место назначения неизвестно на момент создания инструкции перехода.
- sphead
therefore contains a list of instruction-label pairs for each forward reference. When the position later is known, the instructions in the list are updated with the correct location.
- sphead
- В конструкциях циклов могут быть необязательные метки. Если в цикле нет метки, генерируется анонимная метка для упрощения разбора.
- Существует два типа CASE. "Простой" случай реализуется с помощью анонимной переменной, привязанной к значению, которое необходимо проверить.
Простой пример
Разбор процедуры:
create procedure a(s char(16))
begin
declare x int;
set x = 3;
while x > 0 do
set x = x-1;
insert into db.tab values (x, s);
end while;
end
сгенерирует следующие структуры:
______
thd: | | _________
| lex -+--->| | ___________________
|______| | spcont -+------------------->| "s",in,char(16):0 |
| sphead -+------ |("x",in,int :1)|
|_________| | |___________________|
____V__________________
| m_name: "a" |
| m_defstr: "create ..."|
| m_instr: ... |
|_______________________|
Обратите внимание, что содержимое spcont изменяется во время разбора, всегда отражая состояние будущей структуры кадра. m_instr — массив инструкций:
Pos. Instruction
0 sp_instr_set(1, '3')
1 sp_instr_jump_if_not(5, 'x>0')
2 sp_instr_set(1, 'x-1')
3 sp_instr_stmt('insert into ...')
4 sp_instr_jump(1)
5 <end>
Здесь '3', 'x>0' и т. д. представляют элементы Item или Lex для соответствующих выражений или операторов.
Разбор CREATE FUNCTION
Создание функции по существу аналогично процедуре с добавлением того, что функция имеет тип возвращаемого значения и оператор RETURN, но не имеет параметров OUT или INOUT.
Основное различие при разборе заключается в том, что мы сохраняем тип результата в sp_head. Однако существуют значительные различия при вызове функции. (См. ниже.)
Хранение, кэширование, удаление
Как видно выше, сохраняется всё определение, включая "CREATE PROCEDURE" (или "FUNCTION"). Определение процедуры хранится в таблице mysql.proc с именем и типом в качестве ключа, тип — это перечисление ("procedure", "function").
Процедура просто хранится в таблице mysql.proc. У функции есть дополнительное требование. Они будут вызываться в выражениях с таким же синтаксисом, как и UDF, поэтому UDF и хранимые функции используют один и тот же пространство имён. Таким образом, мы должны убедиться, что у нас нет UDF и функций с одинаковым именем (даже если они хранятся в разных местах).
Это означает, что мы можем повторно разобрать процедуру любое количество раз. В первый раз полученный Lex используется для хранения процедуры в базе данных (с помощью функции sp.c:sp_create_procedure()).
Самый простой способ — оставить всё как есть и повторно читать процедуру из базы данных каждый раз при её вызове. (И, на самом деле, так будет работать самая ранняя реализация.) Однако это не очень эффективно, и мы можем сделать лучше. Полная реализация должна работать так:
- При создании разобрать и сохранить процедуру. Обратите внимание, что нам всё равно нужно разобрать её, чтобы поймать синтаксические ошибки, но мы не можем проверить, например, существуют ли вызываемые процедуры.
- При первом вызове прочитать из базы данных, разобрать её и кэшировать полученный Lex в памяти. На этот раз мы можем сделать более тщательную проверку ошибок.
- При последующих вызовах использовать кэшированный Lex.
Обратите внимание, что это подразумевает, что структура Lex со своим sphead должна быть реентерабельной, то есть многократно используемой и разделяемой между разными потоками и вызовами. Состояние времени выполнения для процедуры сохраняется в sp_rcontext в THD.
Механизмы хранения, поиска и удаления процедур инкапсулированы в файлах sp.{cc,h}.
Вызов процедуры
Вызов CALL разбирается так же, как и любой оператор. Результирующий Lex имеет sql_command SQLCOM_CALL, имя процедуры, и параметры помещаются в значение_список Lex.
sql_parse.cc:mysql_execute_command() затем использует sp.cc:sp_find() для получения sp_head для процедуры (которая может быть прочитана из базы данных или получена из кэша в памяти) и вызывает метод execute() sp_head. Примечание: важно, чтобы подзапросы, вызываемые процедурой, не выполняли send_ok(). К счастью, в THD->net есть флаг для отключения этого во время вызовов. Если подзапрос завершится ошибкой, он, тем не менее, отправит ошибку клиенту, поэтому механизм CALL должен возвращаться немедленно и без отправки ошибки.
Метод sp_head::execute() работает следующим образом:
- Сохранить указатель на старый контекст времени выполнения в THD (если таковой имеется)
- Создать новый контекст времени выполнения. Информация о необходимом размере содержится в контексте разбора sp_head.
- Поместить каждый параметр (из Lex->value_list вызова) в новый контекст. Если это параметр OUT или INOUT, смещение параметра в кадре вызывающего также устанавливается в новом контексте.
- Для каждой инструкции вызывается её метод execute(). Результатом является указатель на следующую выполняемую инструкцию (или NULL), если произошла ошибка.
- При успешном выполнении установить новые значения параметров OUT и INOUT в кадре вызывающей стороны.
USE database
Перед выполнением инструкции также сохраняется текущая база данных по умолчанию (если такая имеется). Если она была изменена во время выполнения (т. е. был выполнен оператор USE), текущая база данных восстанавливается в исходное состояние.
Это наиболее полезный способ обработки USE в процедурах. В противном случае вызывающая сторона обнаружит себя в другой базе данных после вызова функции, что может быть запутывающим. Восстановление базы данных также предоставляет полную свободу автору процедуры: - Возможно создание «общих» процедур, независимых от фактического имени базы данных. - Возможно создание процедур, работающих с конкретной базой данных, вызывая USE, без необходимости использования полностью квалифицированных имён таблиц повсюду (что не помогает, если вы всё равно хотите вызвать другие, «общие», процедуры).
Оценивание элементов
Существует три случая, когда нам нужно оценить выражение:
- При присваивании переменной
- При вызове процедуры
- При проверке выражения для ветвления (в IF, WHILE и т. д.)
Семантика в хранимых процедурах — «передача по значению», поэтому мы должны оценить все элементы «func» в момент вызова CALL или SET, иначе мы получим своего рода «ленивую» оценку с непредсказуемыми результатами, например, в отношении параметров OUT. Для этого необходима вспомогательная функция sp_head.cc:eval_func_item().
Вызов ФУНКЦИИ
Функции не имеют явного ключевого слова вызова, как процедуры. Вместо этого они появляются в выражениях с обычной синтаксической конструкцией «fun(arg, ...)». Проблема в том, что у нас уже есть Пользовательские функции (UDFs), которые вызываются таким же образом. UDF обнаруживается анализатором лексем (а не анализатором!), в функции find_keyword() и возвращает токен UDF_*_FUNC или UDA_*_SUM с объектом udf_func в качестве yylval.
Таким образом, хранимые функции должны обрабатываться аналогичным образом, и, как следствие, UDF и функции не должны иметь одинаковых имён.
Обнаружение и разбор вызова ФУНКЦИИ
Существование UDF проверяется во время лексического анализа (в sql_lex.cc:find_keyword()). Это имеет недостаток, что они должны существовать до ссылки на них, что было нормально до существования SP, но потом это становится проблемой. Первая реализация SP ФУНКЦИЙ будет работать аналогично, но это должно быть исправлено как можно скорее. (Это потребует переработки способа обработки UDF, поэтому это не делается с самого начала). Пока что ФУНКЦИЯ обнаруживается таким же образом и возвращает токен SP_FUNC. Во время разбора мы проверяем только *существование* функции, мы не анализируем её, так как не можем вызывать анализатор рекурсивно.
При встрече SP_FUNC с параметрами в анализаторе выражений создаётся экземпляр нового класса Item_func_sp. В отличие от UDF, у нас нет разных классов для разных типов возвращаемых значений, так как на данном этапе мы не знаем тип.
Сбор вызываемых ФУНКЦИЙ
Функция отличается от процедуры одним важным аспектом: процедура вызывается как отдельная команда, а функция вызывается «на лету» во время выполнения *другой* команды. Это усложняет задачу по сравнению с CALL: - Мы не можем прочитать и проанализировать ФУНКЦИЮ из таблицы mysql.proc в момент вызова; сервер требует, чтобы все используемые таблицы были открыты и заблокированы в начале выполнения запроса. Очевидным решением было бы просто добавить «mysql.proc» в список таблиц, используемых запросом, но это подразумевает «соединение» с этой таблицей, если запрос является SELECT, поэтому это не работает (и мы не можем легко исключить эту таблицу; так как привилегированный пользователь фактически может захотеть искать в таблице proc). Другим решением, конечно же, было бы разрешить открытие и закрытие таблицы mysql.proc во время выполнения запроса, но это невозможно в настоящее время.
Таким образом, решение заключается в сборе имён ссылающихся ФУНКЦИЙ во время разбора в lex. Затем, прежде чем делать что-либо ещё в mysql_execute_command(), прочитать все функции из базы данных и сохранить их в THD, где функция sp_find_function() может найти их во время выполнения. Примечание. Даже с кэшем в памяти, мы должны убедиться, что функции действительно читаются и кэшируются на этом этапе. Код, который читает и кэширует функции из базы данных, также должен вызываться рекурсивно для каждой прочитанной ФУНКЦИИ, чтобы убедиться, что у нас есть *все* необходимые функции.
Разбор DROP PROCEDURE/FUNCTION
Имя процедуры помещается в Lex->value_list. Код sql_command для результата разбора — SQLCOM_DROP_PROCEDURE/SQLCOM_DROP_FUNCTION.
Удаление выполняется путём простого получения процедуры с помощью функции sp_find() и вызова sp_drop() (оба в sp.{cc,h}).
DROP PROCEDURE/DROP FUNCTION также поддерживает нестандартный «IF EXISTS», аналогично другим инструкциям DROP в MariaDB.
Условие и обработчики
Имена условий — это лексические сущности, которые хранятся в контексте анализатора, как и переменные. Однако условия — это просто «псевдонимы» для строк SQLSTATE или кодов ошибок mysqld (что является нестандартным расширением в MySQL) и используются только во время разбора.
Обработчики бывают трёх типов: CONTINUE, EXIT и UNDO. Последний — это обработчик EXIT с неявной откат, и в настоящее время он не реализован. Обработчик EXIT переходит к концу блока BEGIN-END при завершении. Обработчик CONTINUE возвращается к оператору, который вызвал обработчик.
Обработчики, действующие в любой момент, являются частью состояния выполнения каждого потока, поэтому в sp_rcontext во время выполнения необходимо помещать и извлекать обработчики. Для этого используются специальные инструкции: - sp_instr_hpush_jump Помещает обработчик. Инструкция содержит необходимую информацию, например, о обрабатываемых условиях и местоположении обработчика. Переход переводит нас в местоположение после кода обработчика. - sp_instr_hpop Извлекает обработчики текущей рамки (которую мы только что покинули).
Возможно, странно перескакивать обработчики таким образом, но в этом нет дополнительной стоимости, и по техническим причинам для анализатора проще генерировать инструкции обработчика при их появлении в исходном коде.
При возникновении ошибки вызывается одна из функций обработки ошибок, и сообщение об ошибке обычно сразу же отправляется клиенту. Обработка условия должна выполняться в этих функциях обработки ошибок (их довольно много), чтобы предотвратить их выполнение. Для этого вызывается метод в sp_rcontext потока THD (если он есть). Если обработчик найден, это записывается в контекст, и функция возвращается без отправки сообщения об ошибке. Цикл выполнения (sp_head::execute()) проверяет это после каждой команды и вызывает найденный обработчик. Если во время одной команды возникают несколько ошибок или предупреждений, только первая обрабатывается, остальные игнорируются.
Вызов и возврат из обработчика в случае EXIT тривиальны. Мы просто переходим к нему, и в качестве последней инструкции у него будет sp_instr_jump.
Вызов и возврат из обработчика CONTINUE представляют некоторые особые проблемы. Поскольку нам нужно вернуться к точке после его вызова, мы помещаем адрес возврата в стек в sp_rcontext (это делает цикл выполнения). Обработчик затем завершается специальной инструкцией sp_instr_hreturn, которая возвращается в это место.
Обработчики CONTINUE имеют одну дополнительную проблему: они анализируются на лексическом уровне, где они встречаются, поэтому смещения переменных предполагают, что они фактически вызываются на этом уровне. Однако обработчик может вызываться из подблока, где были объявлены дополнительные локальные переменные, которые затем будут совмещать местоположение любых локальных переменных в самом обработчике. Таким образом, при вызове обработчика CONTINUE нам нужно сохранить любые локальные переменные над смещением фрейма обработчика и восстановить их при возврате. (Это не проблема для обработчиков EXIT, так как они покидают блок в любом случае). Об этом заботится цикл выполнения и инструкция sp_instr_hreturn.
Примеры
Обработчик EXIT:
begin
declare x int default 0;
begin
declare exit handler for 'XXXXX' set x = 1;
(statement1);
(statement2);
end;
(statement3);
end
Pos. Instruction
0 sp_instr_set(0, '0')
1 sp_instr_hpush_jump(4, 1) # location and frame size
2 sp_instr_set(0, '1')
3 sp_instr_jump(6)
4 sp_instr_stmt('statement1')
5 sp_instr_stmt('statement2')
6 sp_instr_hpop(1)
7 sp_instr_stmt('statement3')
Обработчик CONTINUE:
create procedure hndlr1(val int)
begin
declare x int default 0;
declare foo condition for 1146;
declare continue handler for foo set x = 1;
insert into t3 values ("hndlr1", val); # Non-existing table?
if x>0 then
insert into t1 values ("hndlr1", val); # This instead then
end if;
end|
Pos. Instruction
0 sp_instr_set(1, '0')
1 sp_instr_hpush_jump(4, 2)
2 sp_instr_set(1, '1')
3 sp_instr_hreturn(2) # frame size
4 sp_instr_stmt('insert ... t3 ...')
5 sp_instr_jump_if_not(7, 'x>0')
6 sp_instr_stmt('insert ... t1 ...')
7 sp_instr_hpop(2)
Курсоры
Чтобы хранимые процедуры были действительно полезными, вам понадобятся курсоры. MySQL ещё не поддерживает «реальные» курсоры (с API и поддержкой ODBC, позволяющими обновлять, прокручивать произвольно и т. д.), но простой чувствительный, не прокручиваемый, только для чтения курсор можно реализовать в SP, используя класс Protocol_cursor. Этот класс перехватывает создание и отправку наборов результатов и вместо этого хранит их в памяти, как MYSQL_FIELDS и MYSQL_ROWS (как в API клиента).
Для этого нам нужна обычная поддержка привязки имён в sp_pcontext (аналогичная переменным и условиям), чтобы отслеживать объявленные имена курсоров, и соответствующий механизм выполнения в sp_rcontext. Курсоры имеют лексическую область видимости, как всё, имеющее тело или блок BEGIN/END, поэтому они помещаются и извлекаются как обычно (см. условия и переменные выше). Основными операциями с курсором являются OPEN, FETCH и CLOSE, для каждой из которых будет соответствующая инструкция. Кроме того, нам нужны инструкции для помещения нового курсора (это будет включать LEX оператора SELECT курсора) и инструкции извлечения: - sp_instr_cpush Поместить курсор в sp_rcontext. Эта инструкция содержит LEX для оператора select - sp_instr_cpop Извлечь определённое количество курсоров из sp_rcontext. - sp_instr_copen Открыть курсор: это выполнит запрос и получит набор результатов в отдельном memroot. - sp_instr_cfetch Извлечь следующую строку из набора результатов в памяти. Инструкция содержит список переменных (смещения фреймов) для установки. - sp_instr_cclose Освободить набор результатов.
Курсор — это отдельный класс sp_cursor (определённый в sp_rcontex.h), который инкапсулирует базовые операции, используемые вышеперечисленными инструкциями. Этот класс содержит LEX, объект Protocol_cursor и его memroot, а также текущее состояние курсора. Компиляция и выполнение довольно просты. sp_instr_copen — подкласс sp_instr_stmt и использует свой механизм для выполнения подкоманды.
Пример
begin
declare x int;
declare c cursor for select a from t1;
open c;
fetch c into x;
close c;
end
Pos. Instruction
0 sp_instr_cpush('select a from ...')
1 sp_instr_copen(0) # The 0'th cursor
2 sp_instr_cfetch(0) # Contains the variable list
3 sp_instr_cclose(0)
4 sp_instr_cpop(1)
Кэш SP
Существует два способа кэширования SP:
- Один глобальный кэш, общий для всех потоков/соединений
- Один кэш на поток
У обоих методов есть свои преимущества и недостатки:
- Преимущества: экономия памяти, каждая SP считывается из таблицы один раз
- Недостатки: требуется блокировка (= сериализация при доступе), требуются потокобезопасные структуры данных
- Преимущества: скорость, блокировка не требуется (почти), ограниченные требования к потокобезопасности
- Недостатки: используется больше памяти, каждая SP считывается из таблицы один раз на поток
К сожалению, мы не можем использовать альтернативу 1 на данный момент, так как большинство структур данных, которые необходимо кэшировать (lex и items), не являются реентерабельными и потокобезопасными. (Данные изменяются во время выполнения, указатели THD хранятся повсюду и т. д.) Это оставляет нам альтернативу 2 — один кэш на поток; или, фактически, два, так как функции и процедуры хранятся в отдельных кэшах. Это не так ужасно; единственный случай, когда производительность будет значительно хуже, чем в случае глобального кэша, — это приложение, в котором новые потоки подключаются, вызывают процедуру и отключаются снова и снова.
Реализация кэша сама по себе проста и понятна — это хеш-таблица, обернутая в класс и C API (см. API ниже).
Однако есть одна проблема с несколькими кэшами: удаление и изменение процедур. Обычно это должно происходить очень редко в работающей системе; это, как правило, делается во время разработки и тестирования, поэтому не является немыслимым просто игнорировать эту проблему и позволить любым потокам, работающим с кэшированной версией SP, продолжать работать до тех пор, пока они не будут отключены. Но если мы хотим поддерживать согласованность кэшей с точки зрения удаления и изменения, это можно сделать:
- Необходимо глобальное счётчик, инициализированный значением 0 при запуске.
- При каждом удалении (DROP) или изменении (ALTER) увеличьте счётчик на единицу.
- Каждый кэш имеет свою копию счётчика, скопированную при последнем чтении.
- При поиске имени в кэше сначала проверьте, больше ли глобальный счётчик, чем локальная копия. Если это так, очистите кэш и верните «не найдено», обновите локальный счётчик; в противном случае выполните поиск как обычно.
Это сводит затраты к одной короткой блокировке для доступа к целому числу при нормальной работе. Только в случае фактического удаления или изменения кэш очищается. Это может показаться радикальным, но поскольку мы предполагаем, что это редкое событие, это не проблема. Конечно, можно было бы разработать более точное решение, отслеживая каждый SP, но накладные расходы на это не оправдывают усилий.
Краткое описание API классов и функций
Это обзор основных типов. Некоторые типы и другие детали в фактических файлах были опущены для лучшей читабельности.
Контекст парсера: sp_pcontext.h
typedef enum
{
sp_param_in,
sp_param_out,
sp_param_inout
} sp_param_mode_t;
typedef struct
{
LEX_STRING name;
enum enum_field_types type;
sp_param_mode_t mode;
uint offset; // Offset in current frame
my_bool isset;
} sp_pvar_t;
typedef struct sp_cond_type
{
enum { number, state, warning, notfound, exception } type;
char sqlstate[6];
uint mysqlerr;
} sp_cond_type_t;
class sp_pcontext
{
sp_pcontext();
// Return the maximum frame size
uint max_framesize();
// Return the current frame size
uint current_framesize();
// Return the number of parameters
uint params();
// Set the number of parameters to the current frame size
void set_params();
// Set type of the variable at offset 'i' in the frame
void set_type(uint i, enum enum_field_types type);
// Mark the i:th variable to "set" (i.e. having a value) with
// 'val' true.
void set_isset(uint i, my_bool val);
// Push the variable 'name' to the frame.
void push_var(LEX_STRING *name,
enum enum_field_types type, sp_param_mode_t mode);
// Pop 'num' variables from the frame.
void pop_var(uint num = 1);
// Find variable by name
sp_pvar_t *find_pvar(LEX_STRING *name);
// Find variable by index
sp_pvar_t *find_pvar(uint i);
// Push label 'name' of instruction index 'ip' to the label context
sp_label_t *push_label(char *name, uint ip);
// Find label 'name' in the context
sp_label_t *find_label(char *name);
// Return the last pushed label
sp_label_t *last_label();
// Return and remove the last pushed label.
sp_label_t *pop_label();
// Push a condition to the context
void push_cond(LEX_STRING *name, sp_cond_type_t *val);
// Pop a 'num' condition from the context
void pop_cond(uint num);
// Find a condition in the context
sp_cond_type_t *find_cond(LEX_STRING *name);
// Increase the handler count
void add_handler();
// Returns the handler count
uint handlers();
// Push a cursor
void push_cursor(LEX_STRING *name);
// Find a cursor
my_bool find_cursor(LEX_STRING *name, uint *poff);
// Pop 'num' cursors
void pop_cursor(uint num);
// Return the number of cursors
uint cursors();
}
Контекст выполнения (кадр вызова): sp_rcontext.h:
#define SP_HANDLER_NONE 0
#define SP_HANDLER_EXIT 1
#define SP_HANDLER_CONTINUE 2
#define SP_HANDLER_UNDO 3
typedef struct
{
struct sp_cond_type *cond;
uint handler; // Location of handler
int type;
uint foffset; // Frame offset for the handlers declare level
} sp_handler_t;
class sp_rcontext
{
// 'fsize' is the max size of the context, 'hmax' the number of handlers,
// 'cmax' the number of cursors
sp_rcontext(uint fsize, uint hmax, , uint cmax);
// Push value (parameter) 'i' to the frame
void push_item(Item *i);
// Set slot 'idx' to value 'i'
void set_item(uint idx, Item *i);
// Return the item in slot 'idx'
Item *get_item(uint idx);
// Set the "out" index 'oidx' for slot 'idx. If it's an IN slot,
// use 'oidx' -1.
void set_oindex(uint idx, int oidx);
// Return the "out" index for slot 'idx'
int get_oindex(uint idx);
// Set the FUNCTION result
void set_result(Item *i);
// Get the FUNCTION result
Item *get_result();
// Push handler at location 'h' for condition 'cond'. 'f' is the
// current variable frame size.
void push_handler(sp_cond_type_t *cond, uint h, int type, uint f);
// Pop 'count' handlers
void pop_handlers(uint count);
// Find a handler for this error. This sets the state for a found
// handler in the context. If called repeatedly without clearing,
// only the first call's state is kept.
int find_handler(uint sql_errno);
// Returns 1 if a handler has been found, with '*ip' and '*fp' set
// to the handler location and frame size respectively.
int found_handler(uint *ip, uint *fp);
// Clear the found handler state.
void clear_handler();
// Push a return address for a CONTINUE handler
void push_hstack(uint ip);
// Pop the CONTINUE handler return stack
uint pop_hstack();
// Save variables from frame index 'fp' and up.
void save_variables(uint fp);
// Restore saved variables from to frame index 'fp' and up.
void restore_variables(uint fp);
// Push a cursor for the statement (lex)
void push_cursor(LEX *lex);
// Pop 'count' cursors
void pop_cursors(uint count);
// Pop all cursors
void pop_all_cursors();
// Get the 'i'th cursor
sp_cursor *get_cursor(uint i);
}
Процедура: sp_head.h:
#define TYPE_ENUM_FUNCTION 1
#define TYPE_ENUM_PROCEDURE 2
class sp_head
{
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
sp_head();
void init(LEX_STRING *name, LEX *lex, LEX_STRING *comment, char suid);
// Store this procedure in the database. This is a wrapper around
// the function sp_create_procedure().
int create(THD *);
// Invoke a FUNCTION
int
execute_function(THD *thd, Item **args, uint argcount, Item **resp);
// CALL a PROCEDURE
int
execute_procedure(THD *thd, List<Item> *args);
// Add the instruction to this procedure.
void add_instr(sp_instr *);
// Returns the number of instructions.
uint instructions();
// Returns the last instruction
sp_instr *last_instruction();
// Resets lex in 'thd' and keeps a copy of the old one.
void reset_lex(THD *);
// Restores lex in 'thd' from our copy, but keeps some status from the
// one in 'thd', like ptr, tables, fields, etc.
void restore_lex(THD *);
// Put the instruction on the backpatch list, associated with
// the label.
void push_backpatch(sp_instr *, struct sp_label *);
// Update all instruction with this label in the backpatch list to
// the current position.
void backpatch(struct sp_label *);
// Returns the SP name (with optional length in '*lenp').
char *name(uint *lenp = 0);
// Returns the result type for a function
Item_result result();
// Sets various attributes
void sp_set_info(char *creator, uint creatorlen,
longlong created, longlong modified,
bool suid, char *comment, uint commentlen);
}
Инструкции
Базовый класс
class sp_instr
{
// 'ip' is the index of this instruction
sp_instr(uint ip);
// Execute this instrution.
// '*nextp' will be set to the index of the next instruction
// to execute. (For most instruction this will be the
// instruction following this one.)
// Returns 0 on success, non-zero if some error occurred.
virtual int execute(THD *, uint *nextp)
}
<<code>>
===== Statement instruction
<<code>>
class sp_instr_stmt : public sp_instr
{
sp_instr_stmt(uint ip);
int execute(THD *, uint *nextp);
// Set the statement's Lex
void set_lex(LEX *);
// Return the statement's Lex
LEX *get_lex();
}
Инструкция SET
class sp_instr_set : public sp_instr
{
// 'offset' is the variable's frame offset, 'val' the value,
// and 'type' the variable type.
sp_instr_set(uint ip,
uint offset, Item *val, enum enum_field_types type);
int execute(THD *, uint *nextp);
}
Безусловный переход
class sp_instr_jump : public sp_instr
{
// No destination, must be set.
sp_instr_jump(uint ip);
// 'dest' is the destination instruction index.
sp_instr_jump(uint ip, uint dest);
int execute(THD *, uint *nextp);
// Set the destination instruction 'dest'.
void set_destination(uint dest);
}
Условный переход
class sp_instr_jump_if_not : public sp_instr_jump
{
// Jump if 'i' evaluates to false. Destination not set yet.
sp_instr_jump_if_not(uint ip, Item *i);
// Jump to 'dest' if 'i' evaluates to false.
sp_instr_jump_if_not(uint ip, Item *i, uint dest)
int execute(THD *, uint *nextp);
}
Возврат значения функции
class sp_instr_freturn : public sp_instr
{
// Return the value 'val'
sp_instr_freturn(uint ip, Item *val, enum enum_field_types type);
int execute(THD *thd, uint *nextp);
}
Настройка обработчика и переход
class sp_instr_hpush_jump : public sp_instr_jump
{
// Push handler of type 'htype', with current frame size 'fp'
sp_instr_hpush_jump(uint ip, int htype, uint fp);
int execute(THD *thd, uint *nextp);
// Add condition for this handler
void add_condition(struct sp_cond_type *cond);
}
Удаление обработчиков
class sp_instr_hpop : public sp_instr
{
// Pop 'count' handlers
sp_instr_hpop(uint ip, uint count);
int execute(THD *thd, uint *nextp);
}
Возврат из обработчика CONTINUE
class sp_instr_hreturn : public sp_instr
{
// Return from handler, and restore variables to 'fp'.
sp_instr_hreturn(uint ip, uint fp);
int execute(THD *thd, uint *nextp);
}
Добавление указателя CURSOR
class sp_instr_cpush : public sp_instr_stmt
{
// Push a cursor for statement 'lex'
sp_instr_cpush(uint ip, LEX *lex)
int execute(THD *thd, uint *nextp);
}
Удаление указателей CURSOR
class sp_instr_cpop : public sp_instr_stmt
{
// Pop 'count' cursors
sp_instr_cpop(uint ip, uint count)
int execute(THD *thd, uint *nextp);
}
Открытие CURSOR
class sp_instr_copen : public sp_instr_stmt
{
// Open the 'c'th cursor
sp_instr_copen(uint ip, uint c);
int execute(THD *thd, uint *nextp);
}
Закрытие CURSOR
class sp_instr_cclose : public sp_instr
{
// Close the 'c'th cursor
sp_instr_cclose(uint ip, uint c);
int execute(THD *thd, uint *nextp);
}
Получение строки с помощью CURSOR
class sp_instr_cfetch : public sp_instr
{
// Fetch next with the 'c'th cursor
sp_instr_cfetch(uint ip, uint c);
int execute(THD *thd, uint *nextp);
// Add a target variable for the fetch
void add_to_varlist(struct sp_pvar *var);
}
Вспомогательные функции: sp.h
#define SP_OK 0
#define SP_KEY_NOT_FOUND -1
#define SP_OPEN_TABLE_FAILED -2
#define SP_WRITE_ROW_FAILED -3
#define SP_DELETE_ROW_FAILED -4
#define SP_GET_FIELD_FAILED -5
#define SP_PARSE_ERROR -6
// Finds a stored procedure given its name. Returns NULL if not found.
sp_head *sp_find_procedure(THD *, LEX_STRING *name);
// Store the procedure 'name' in the database. 'def' is the complete
// definition string ("create procedure ...").
int sp_create_procedure(THD *,
char *name, uint namelen,
char *def, uint deflen,
char *comment, uint commentlen, bool suid);
// Drop the procedure 'name' from the database.
int sp_drop_procedure(THD *, char *name, uint namelen);
// Finds a stored function given its name. Returns NULL if not found.
sp_head *sp_find_function(THD *, LEX_STRING *name);
// Store the function 'name' in the database. 'def' is the complete
// definition string ("create function ...").
int sp_create_function(THD *,
char *name, uint namelen,
char *def, uint deflen,
char *comment, uint commentlen, bool suid);
// Drop the function 'name' from the database.
int sp_drop_function(THD *, char *name, uint namelen);
Кэш: sp_cache.h
/* Initialize the SP caching once at startup */
void sp_cache_init();
/* Clear the cache *cp and set *cp to NULL */
void sp_cache_clear(sp_cache **cp);
/* Insert an SP to cache. If **cp points to NULL, it's set to a
new cache */
void sp_cache_insert(sp_cache **cp, sp_head *sp);
/* Lookup an SP in cache */
sp_head *sp_cache_lookup(sp_cache **cp, char *name, uint namelen);
/* Remove an SP from cache */
void sp_cache_remove(sp_cache **cp, sp_head *sp);
Схема mysql.proc
Это таблица mysql.proc, используемая в MariaDB 10.4:
CREATE TABLE `proc` (
`db` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`name` char(64) NOT NULL DEFAULT '',
`type` enum('FUNCTION','PROCEDURE','PACKAGE','PACKAGE BODY') NOT NULL,
`specific_name` char(64) NOT NULL DEFAULT '',
`language` enum('SQL') NOT NULL DEFAULT 'SQL',
`sql_data_access` enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA') NOT NULL DEFAULT 'CONTAINS_SQL',
`is_deterministic` enum('YES','NO') NOT NULL DEFAULT 'NO',
`security_type` enum('INVOKER','DEFINER') NOT NULL DEFAULT 'DEFINER',
`param_list` blob NOT NULL,
`returns` longblob NOT NULL,
`body` longblob NOT NULL,
`definer` char(141) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`sql_mode` set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES','IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION','NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB','NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40','ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES','STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES','ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER','HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH','EMPTY_STRING_IS_NULL','SIMULTANEOUS_ASSIGNMENT') NOT NULL DEFAULT '',
`comment` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`character_set_client` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`collation_connection` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`db_collation` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`body_utf8` longblob DEFAULT NULL,
`aggregate` enum('NONE','GROUP') NOT NULL DEFAULT 'NONE',
PRIMARY KEY (`db`,`name`,`type`)
) ENGINE=Aria DEFAULT CHARSET=utf8 PAGE_CHECKSUM=1 TRANSACTIONAL=1 COMMENT='Stored Procedures'
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/stored-procedure-internals/