В данном разделе описываются различия между Digital Q.DataBase PL/pgSQL языком и языком Oracle PL/SQL языком, в помощь разработчикам, переносящим приложения из Oracle® на Digital Q.DataBase.
PL/pgSQL во многом похож на PL/SQL. Он является блочно-структурированным императивным языком, в котором необходимо объявлять все переменные. Операции присваивания, циклы и условные конструкции аналогичны. Основные различия, которые следует учитывать при переносе из PL/SQL для того чтобы PL/pgSQL следующие:
Если имя, используемое в команде SQL, может быть как именем столбца
таблицы, задействованной в команде, так и ссылкой на переменную функции,
PL/SQL оно интерпретируется как имя столбца.
По умолчанию PL/pgSQL будет выдана ошибка
с сообщением о неоднозначности имени. Можно указать параметр
plpgsql.variable_conflict = use_column
для изменения этого поведения в соответствии с PL/SQL,
как описано в Раздел 5.6.11.1.
Прежде всего рекомендуется избегать подобных неоднозначностей,
но если требуется перенести большой объем кода, зависящий от
такого поведения, установка значения variable_conflict может оказаться
оптимальным решением.
В Digital Q.DataBase тело функции должно быть представлено в виде строкового литерала. Следовательно, необходимо использовать экранирование долларами или дублировать одинарные кавычки в теле функции (см. Раздел 5.6.12.1.)
Названия типов данных часто требуют перевода. Например, в Oracle строковые
значения обычно объявляются с типом данных varchar2, которые
не является стандартным типом SQL. В Digital Q.DataBase,
используйте тип данных varchar или text вместо него. Аналогичным образом замените
тип данных число на numeric, либо используйте иной числовой
тип данных, если имеется более подходящий.
Для группировки функций вместо пакетов следует использовать схемы.
Поскольку пакеты отсутствуют, переменные уровня пакета также не предусмотрены. Это создает определенные неудобства. Состояние сеанса можно сохранять вместо этого во временных таблицах.
Integer FOR циклы с REVERSE работают
иначе: PL/SQL выполняет обратный отсчёт от второго
числа к первому, тогда как PL/pgSQL выполняет обратный отсчёт
от первого числа ко второму, что требует перестановки границ цикла
при переносе. Данная несовместимость нежелательна,
но её изменение маловероятно. (См. Раздел 5.6.6.5.5.)
FOR циклы по результатам запросов (за исключением курсоров) также работают
иначе: целевые переменные должны быть предварительно объявлены,
в то время как PL/SQL всегда объявляет их неявно.
Преимущество этого подхода заключается в том, что значения переменных остаются доступными
после выхода из цикла.
Существуют различные синтаксические различия при использовании курсорных переменных.
Пример 5.6.9 показывает, как перенести простую функцию из PL/SQL на PL/pgSQL.
Пример 5.6.9. Перенос простой функции из PL/SQL на PL/pgSQL
Ниже приведен пример Oracle PL/SQL функции:
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar2,
v_version varchar2)
RETURN varchar2 IS
BEGIN
IF v_version IS NULL THEN
RETURN v_name;
END IF;
RETURN v_name || '/' || v_version;
END;
/
show errors;
Рассмотрим эту функцию и проанализируем ее отличия от PL/pgSQL:
Имя типа varchar2 необходимо изменить на varchar
или text. В примерах данного раздела будет
использоваться varchar, но text часто является более предпочтительным, если
не требуются конкретные ограничения длины строки.
RETURN ключевое слово в прототипе
функции (не в теле функции) заменяется на
RETURNS в
Digital Q.DataBase.
Кроме того, IS заменяется на AS, и требуется
добавить LANGUAGE предложение, так как PL/pgSQL
не является единственным возможным языком функций.
В Digital Q.DataBase, тело функции рассматривается
как строковый литерал, поэтому необходимо использовать кавычки или знаки доллара
для его обрамления. Это заменяет завершающий символ /
в подходе Oracle.
show errors команда не существует в
Digital Q.DataBase, и в ней нет необходимости, так как сообщения об ошибках
выводятся автоматически.
Так будет выглядеть эта функция после переноса в Digital Q.DataBase:
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
v_version varchar)
RETURNS varchar AS $$
BEGIN
IF v_version IS NULL THEN
RETURN v_name;
END IF;
RETURN v_name || '/' || v_version;
END;
$$ LANGUAGE plpgsql;
Пример 5.6.10 демонстрирует способ переноса функции, создающей другую функцию, и способы решения возникающих при этом проблем с использованием кавычек.
Пример 5.6.10. Портирование функции, создающей другую функцию, из PL/SQL на PL/pgSQL
Следующая процедура выбирает строки из
SELECT оператора и формирует большую функцию
с результатами в IF операторах в целях повышения эффективности.
Версия для Oracle:
CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
CURSOR referrer_keys IS
SELECT * FROM cs_referrer_keys
ORDER BY try_order;
func_cmd VARCHAR(4000);
BEGIN
func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR2,
v_domain IN VARCHAR2, v_url IN VARCHAR2) RETURN VARCHAR2 IS BEGIN';
FOR referrer_key IN referrer_keys LOOP
func_cmd := func_cmd ||
' IF v_' || referrer_key.kind
|| ' LIKE ''' || referrer_key.key_string
|| ''' THEN RETURN ''' || referrer_key.referrer_type
|| '''; END IF;';
END LOOP;
func_cmd := func_cmd || ' RETURN NULL; END;';
EXECUTE IMMEDIATE func_cmd;
END;
/
show errors;
Ниже показано, как эта функция будет выглядеть в Digital Q.DataBase:
CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc() AS $func$
DECLARE
referrer_keys CURSOR IS
SELECT * FROM cs_referrer_keys
ORDER BY try_order;
func_body text;
func_cmd text;
BEGIN
func_body := 'BEGIN';
FOR referrer_key IN referrer_keys LOOP
func_body := func_body ||
' IF v_' || referrer_key.kind
|| ' LIKE ' || quote_literal(referrer_key.key_string)
|| ' THEN RETURN ' || quote_literal(referrer_key.referrer_type)
|| '; END IF;' ;
END LOOP;
func_body := func_body || ' RETURN NULL; END;';
func_cmd :=
'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host varchar,
v_domain varchar,
v_url varchar)
RETURNS varchar AS '
|| quote_literal(func_body)
|| ' LANGUAGE plpgsql;' ;
EXECUTE func_cmd;
END;
$func$ LANGUAGE plpgsql;
Следует обратить внимание на то, что тело функции формируется отдельно и передается через quote_literal для дублирования в нем всех символов кавычек. Данный прием необходим, поскольку нельзя гарантированно использовать экранирование долларами при определении новой функции: нет уверенности в том, какие именно строки будут интерполированы из referrer_key.key_string поля. (В данном случае предполагается, что referrer_key.kind можно считать всегда host, domain, или
url, но referrer_key.key_string может быть любым; в частности, оно может содержать знаки доллара.) Данная функция фактически является усовершенствованием оригинала Oracle, поскольку она не генерирует некорректный код в случаях, когда referrer_key.key_string или
referrer_key.referrer_type содержит кавычки.
Пример 5.6.11 демонстрирует способ переноса функции с OUT параметрами и операциями со строками.
Digital Q.DataBase не имеет встроенной
instr функция, однако её можно создать путем комбинирования других функций. В Раздел 5.6.13.3 имеется
PL/pgSQL реализация
instr которые можно использовать для упрощения процесса переноса.
Пример 5.6.11. Перенос процедуры с использованием манипуляций со строками и
OUT параметров из PL/SQL в
PL/pgSQL
Следующая Oracle процедура PL/SQL используется для синтаксического анализа URL-адреса и возврата нескольких элементов (хоста, пути и запроса).
Версия для Oracle:
CREATE OR REPLACE PROCEDURE cs_parse_url(
v_url IN VARCHAR2,
v_host OUT VARCHAR2, -- Данное значение будет возвращено
v_path OUT VARCHAR2, -- Этот параметр также будет возвращен
v_query OUT VARCHAR2) -- И этот параметр также будет возвращен
IS
a_pos1 INTEGER;
a_pos2 INTEGER;
BEGIN
v_host := NULL;
v_path := NULL;
v_query := NULL;
a_pos1 := instr(v_url, '//');
IF a_pos1 = 0 THEN
RETURN;
END IF;
a_pos2 := instr(v_url, '/', a_pos1 + 2);
IF a_pos2 = 0 THEN
v_host := substr(v_url, a_pos1 + 2);
v_path := '/';
RETURN;
END IF;
v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
a_pos1 := instr(v_url, '?', a_pos2 + 1);
IF a_pos1 = 0 THEN
v_path := substr(v_url, a_pos2);
RETURN;
END IF;
v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
v_query := substr(v_url, a_pos1 + 1);
END;
/
show errors;
Ниже представлен возможный вариант реализации на языке PL/pgSQL:
CREATE OR REPLACE FUNCTION cs_parse_url(
v_url IN VARCHAR,
v_host OUT VARCHAR, -- Данный параметр будет возвращен
v_path OUT VARCHAR, -- Этот параметр также будет возвращен
v_query OUT VARCHAR) -- И этот параметр
AS $$
DECLARE
a_pos1 INTEGER;
a_pos2 INTEGER;
BEGIN
v_host := NULL;
v_path := NULL;
v_query := NULL;
a_pos1 := instr(v_url, '//');
IF a_pos1 = 0 THEN
RETURN;
END IF;
a_pos2 := instr(v_url, '/', a_pos1 + 2);
IF a_pos2 = 0 THEN
v_host := substr(v_url, a_pos1 + 2);
v_path := '/';
RETURN;
END IF;
v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
a_pos1 := instr(v_url, '?', a_pos2 + 1);
IF a_pos1 = 0 THEN
v_path := substr(v_url, a_pos2);
RETURN;
END IF;
v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
v_query := substr(v_url, a_pos1 + 1);
END;
$$ LANGUAGE plpgsql;
Эту функцию можно использовать следующим образом:
SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');
Пример 5.6.12 демонстрирует перенос процедуры, в которой используются многочисленные специфичные для Oracle функциональные возможности.
Пример 5.6.12. Перенос процедуры из PL/SQL на PL/pgSQL
Версия Oracle:
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS
a_running_job_count INTEGER;
BEGIN
LOCK TABLE cs_jobs IN EXCLUSIVE MODE;
SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;
IF a_running_job_count > 0 THEN
COMMIT; -- снятие блокировки
raise_application_error(-20000,
'Не удалось создать новое задание: в данный момент уже выполняется другое задание.');
END IF;
DELETE FROM cs_active_job;
INSERT INTO cs_active_job(job_id) VALUES (v_job_id);
BEGIN
INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
EXCEPTION
WHEN dup_val_on_index THEN NULL; -- игнорировать, если запись уже существует
END;
COMMIT;
END;
/
show errors
Ниже показан пример переноса данной процедуры в PL/pgSQL:
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id integer) AS $$
DECLARE
a_running_job_count integer;
BEGIN
LOCK TABLE cs_jobs IN EXCLUSIVE MODE;
SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;
IF a_running_job_count > 0 THEN
COMMIT; -- снятие блокировки
RAISE EXCEPTION 'Unable to create a new job: a job is currently running'; -- (1)
END IF;
DELETE FROM cs_active_job;
INSERT INTO cs_active_job(job_id) VALUES (v_job_id);
BEGIN
INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
EXCEPTION
WHEN unique_violation THEN -- (2)
-- игнорировать, если запись уже существует
END;
COMMIT;
END;
$$ LANGUAGE plpgsql;
Синтаксис | |
Имена исключений, поддерживаемые в PL/pgSQL выполняются; отличаются от имен в Oracle. Набор встроенных имен исключений гораздо шире (см. Приложение 8.1). В настоящее время отсутствует возможность объявлять пользовательские имена исключений, хотя вместо этого можно генерировать произвольные значения SQLSTATE. |
В данном разделе описываются некоторые другие аспекты, которые следует учитывать при переносе функций Oracle в PL/SQL Digital Q.DataBase.
В PL/pgSQL, когда исключение перехватывается в блоке
EXCEPTION все изменения в базе данных, произведенные с начала этого блока
BEGIN автоматически откатываются. То есть поведение эквивалентно результату, который был бы получен в Oracle при использовании:
BEGIN
SAVEPOINT s1;
... код здесь ...
EXCEPTION
WHEN ... THEN
ROLLBACK TO s1;
... code here ...
WHEN ... THEN
ROLLBACK TO s1;
... code here ...
END;
При переносе процедуры Oracle, в которой используется
SAVEPOINT и ROLLBACK TO в данном стиле, задача упрощается: достаточно опустить SAVEPOINT и
ROLLBACK TO. При наличии процедуры, использующей
SAVEPOINT и ROLLBACK TO иным образом,
то потребуется тщательный анализ.
EXECUTE #
Предложение PL/pgSQL версия
EXECUTE работает аналогично
PL/SQL версии, однако следует учитывать необходимость использования
quote_literal и
quote_ident как описано в Раздел 5.6.5.4. Конструкции
типа EXECUTE 'SELECT * FROM $1'; не будет работать
надежно без использования этих функций.
Digital Q.DataBase предоставляет два модификатора создания функций для оптимизации выполнения: «категория изменчивости» (возвращает ли функция всегда один и тот же результат при одинаковых аргументах) и «строгость» (возвращает ли функция значение null, если какой-либо из аргументов равен null). Для получения подробной информации следует обратиться к CREATE FUNCTION справочной странице.
При использовании этих атрибутов оптимизации
CREATE FUNCTION оператор может выглядеть следующим образом:
CREATE FUNCTION foo(...) RETURNS integer AS $$ ... $$ LANGUAGE plpgsql STRICT IMMUTABLE;
В данном разделе приведен код набора Oracle-совместимых
instr функций, которые можно использовать для упрощения
переноса кода.
--
-- функции instr, имитирующие аналоги в Oracle
-- Синтаксис: instr(string1, string2 [, n [, m]])
-- где [] обозначает необязательные параметры.
--
-- Поиск в строке string1, начиная с n-го символа, m-го вхождения
-- строки string2. Если параметр n имеет отрицательное значение, поиск выполняется в обратном направлении, начиная с abs(n)-го символа от конца строки string1. -- Если параметр n не указан, принимается значение 1 (поиск начинается с первого символа). -- Если параметр m не указан, принимается значение 1 (поиск первого вхождения). -- Возвращается начальный индекс строки string2 в строке string1 либо 0, если строка string2 не найдена. -- CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$ BEGIN RETURN instr($1, $2, 1); END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
beg_index integer)
RETURNS integer AS $$
DECLARE
pos integer NOT NULL DEFAULT 0;
temp_str varchar;
beg integer;
length integer;
ss_length integer;
BEGIN
IF beg_index > 0 THEN
temp_str := substring(string FROM beg_index);
pos := position(string_to_search_for IN temp_str);
IF pos = 0 THEN
RETURN 0;
ELSE
RETURN pos + beg_index - 1;
END IF;
ELSIF beg_index < 0 THEN
ss_length := char_length(string_to_search_for);
length := char_length(string);
beg := length + 1 + beg_index;
WHILE beg > 0 LOOP
temp_str := substring(string FROM beg FOR ss_length);
IF string_to_search_for = temp_str THEN
RETURN beg;
END IF;
beg := beg - 1;
END LOOP;
RETURN 0;
ELSE
RETURN 0; END IF; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
beg_index integer, occur_index integer)
RETURNS integer AS $$
DECLARE
pos integer NOT NULL DEFAULT 0;
occur_number integer NOT NULL DEFAULT 0;
temp_str varchar;
beg integer;
i integer;
length integer;
ss_length integer;
BEGIN
IF occur_index <= 0 THEN
RAISE 'аргумент ''%'' вне диапазона', occur_index
USING ERRCODE = '22003';
END IF;
IF beg_index > 0 THEN
beg := beg_index - 1;
FOR i IN 1..occur_index LOOP
temp_str := substring(string FROM beg + 1);
pos := position(string_to_search_for IN temp_str);
IF pos = 0 THEN
RETURN 0;
END IF;
beg := beg + pos;
END LOOP;
RETURN beg;
ELSIF beg_index < 0 THEN
ss_length := char_length(string_to_search_for);
length := char_length(string);
beg := length + 1 + beg_index;
WHILE beg > 0 LOOP
temp_str := substring(string FROM beg FOR ss_length);
IF string_to_search_for = temp_str THEN
occur_number := occur_number + 1;
IF occur_number = occur_index THEN
RETURN beg;
END IF;
END IF;
beg := beg - 1;
END LOOP;
RETURN 0;
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;