Esta seção explica as diferenças entre a linguagem PL/pgSQL do PostgreSQL e a linguagem PL/SQL do SGBD Oracle, para ajudar os desenvolvedores a converterem aplicações do Oracle® para o PostgreSQL.
O PL/pgSQL é semelhante ao PL/SQL em muitos aspectos. É uma linguagem imperativa estruturada em blocos, e todas as variáveis devem ser declaradas. Atribuições, laços e condicionais são semelhantes. As principais diferenças que se deve ter em mente ao migrar do PL/SQL para o PL/pgSQL são:
Se o nome usado em um comando SQL puder ser
o nome de uma coluna de uma tabela usada no comando, ou uma
referência a uma variável da função, o
PL/SQL o tratará como o nome da coluna.
Por padrão, o PL/pgSQL relata um erro
reclamando que o nome é ambíguo.
Pode ser especificado
plpgsql.variable_conflict = use_column
para mudar este comportamento no PL/pgSQL
para corresponder ao PL/SQL,
como explicado em Substituição de variável.
Geralmente é melhor evitar estas ambiguidades antes de tudo,
mas se houver necessidade de converter uma grande quantidade
de código dependente desse comportamento, definir
variable_conflict pode ser a melhor solução.
No PostgreSQL o corpo da função deve ser escrito como um literal cadeia de caracteres. Portanto, é necessário usar delimitação por cifrão ou apóstrofos no corpo da função. (Veja Tratamento de apóstrofos.)
Os nomes dos tipos de dados geralmente precisam de tradução.
Por exemplo, no Oracle, os valores
cadeia de caracteres são normalmente declarados como do
tipo varchar2, que é um tipo fora do padrão
SQL.
No PostgreSQL, deve ser usado o tipo
varchar ou text.
Da mesma forma, deve ser substituído o tipo de dados
number por numeric, ou ser usado
outro tipo de dados numérico, caso haja algum mais apropriado.
Em vez de pacotes (packages), devem ser usados esquemas para organizar as funções em grupos.
Como não há pacotes no PostgreSQL, também não há variáveis no nível de pacote. Isto é algo irritante. O estado pode ser mantido por sessão em tabelas temporárias.
Laços FOR inteiros com
REVERSE funcionam de forma diferente:
O PL/SQL faz a contagem regressiva do
segundo número para o primeiro, enquanto o
PL/pgSQL faz a contagem regressiva do
primeiro número para o segundo, exigindo que os limites do laço
sejam trocados durante a conversão.
Esta incompatibilidade é lamentável, mas é improvável que seja
mudada. (Veja FOR (variante inteira).)
Laços FOR sobre consultas (que não sejam cursores)
também funcionam de forma diferente:
as variáveis de destino devem ter sido declaradas, enquanto o
PL/SQL sempre as declara implicitamente.
Uma vantagem disso é que os valores das variáveis permanecem
acessíveis após o fim do laço.
Existem várias diferenças de notação para o uso de variáveis de cursor.
O Exemplo 41.11 mostra como converter uma função simples de PL/SQL para o PL/pgSQL.
Exemplo 41.11. Conversão de uma função simples do PL/SQL para o PL/pgSQL
A seguir está uma função PL/SQL do Oracle:
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;
Vamos examinar esta função e ver as diferenças em comparação com o PL/pgSQL:
O nome do tipo varchar2 deve ser mudado para
varchar ou text.
Nos exemplos dessa seção, vamos usar varchar, mas
text é geralmente uma escolha melhor, quando não
é necessário especificar o limite de comprimento
da cadeia de caracteres.
A palavra-chave RETURN no protótipo da função
(não no corpo da função) se torna RETURNS
no PostgreSQL.
Também, IS se torna AS,
sendo necessário adicionar a cláusula LANGUAGE,
porque o PL/pgSQL não é a única
linguagem de função possível.
No PostgreSQL, o corpo da função é
considerado um literal cadeia de caracteres, então é necessário
usar apóstrofos ou delimitação por cifrão redor dele.
Isto substitui a terminação / na abordagem
do Oracle.
O comando show errors não existe no
PostgreSQL, e não é necessário,
por os erros serem relatados automaticamente.
É assim que esta função ficaria quando convertida para o PostgreSQL:
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;
O Exemplo 41.12 mostra como converter uma função que cria outra função, e como lidar com os problemas de apóstrofos resultantes.
Exemplo 41.12. Conversão de uma função que cria outra função do PL/SQL para o PL/pgSQL
O procedimento a seguir obtém linhas de uma instrução
SELECT, e cria uma função grande com os
resultados em instruções IF, para fins de
eficiência.
Esta é a versão para o 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;
Aqui está como esta função ficaria no PostgreSQL:
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;
Note como o corpo da função é construído em separado, e
passado por quote_literal para duplicar
quaisquer apóstrofos nele.
Esta técnica é necessária, porque não podemos usar com segurança
a delimitação por cifrão para definir a nova função:
não sabemos com certeza quais cadeia de caracteres serão interpoladas
do campo referrer_key.key_string.
(Estamos assumindo aqui que referrer_key.kind
pode ser confiável para ser sempre host,
domain, ou url, mas
referrer_key.key_string pode ser qualquer
coisa, em particular pode conter cifrões.)
Esta função é, na verdade, uma melhoria em relação à função original
do Oracle, porque não vai gerar código
falhado quando referrer_key.key_string
ou referrer_key.referrer_type
contiverem apóstrofos.
O Exemplo 41.13 mostra como converter uma
função com parâmetros OUT, e manipulação de
cadeias de caracteres.
O PostgreSQL não possui a função
instr nativa, mas uma pode ser criada usando
a combinação de outras funções.
Em Apêndice existe uma implementação
escrita em PL/pgSQL da função
instr, que pode ser usada para facilitar a conversão.
Exemplo 41.13. Conversão de um procedimento com manipulação de cadeia de caracteres e parâmetros OUT do PL/SQL para o PL/pgSQL
O procedimento PL/SQL Oracle a seguir é usado para analisar um URL, e retornar vários elementos (hospedeiro, caminho e consulta).
Esta é a versão do Oracle:
CREATE OR REPLACE PROCEDURE cs_parse_url(
v_url IN VARCHAR2,
v_host OUT VARCHAR2, -- Este será passado de volta
v_path OUT VARCHAR2, -- Este também
v_query OUT VARCHAR2) -- E também este
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;
Aqui está uma possível conversão para o PL/pgSQL:
CREATE OR REPLACE FUNCTION cs_parse_url(
v_url IN VARCHAR,
v_host OUT VARCHAR, -- Este será passado de volta
v_path OUT VARCHAR, -- Este também
v_query OUT VARCHAR) -- E também este
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;
Esta função pode ser usada dessa forma:
SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');
v_host | v_path | v_query
------------+------------+---------
foobar.com | /query.cgi | baz
(1 linha)
O Exemplo 41.14 mostra como converter um procedimento que usa vários recursos específicos do Oracle.
Exemplo 41.14. Conversão de procedimento do PL/SQL para o PL/pgSQL
A versão do 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; -- liberar o bloqueio
raise_application_error(-20000,
'Não foi possível criar uma nova tarefa: uma tarefa está em execução no momento.');
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; --não se preocupe se já existir.
END;
COMMIT;
END;
/
show errors
Aqui está uma possível conversão para o 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; -- liberar o bloqueio
RAISE EXCEPTION 'Não foi possível criar uma nova tarefa: uma tarefa está em execução no momento.'; -- (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)
-- não se preocupe se já existir.
END;
COMMIT;
END;
$$ LANGUAGE plpgsql;
A sintaxe do | |
Os nomes de exceção com suporte pelo
PL/pgSQL são diferentes dos do
Oracle.
O conjunto de nomes de exceção nativos é muito maior
(veja Códigos de erro do PostgreSQL).
Não há atualmente maneira de declarar nomes de exceção definidos
pelo usuário, embora se possa lançar valores de
|
Esta seção explica algumas outras coisas a serem observadas ao converter funções do PL/SQL do Oracle para o PostgreSQL.
No PL/pgSQL, quando uma exceção é
capturada por uma cláusula EXCEPTION, todas as
alterações no banco de dados desde o BEGIN
do bloco são desfeitas automaticamente.
Ou seja, o comportamento equivale ao que se obteria no
Oracle com:
BEGIN
SAVEPOINT s1;
... código aqui ...
EXCEPTION
WHEN ... THEN
ROLLBACK TO s1;
... código aqui ...
WHEN ... THEN
ROLLBACK TO s1;
... código aqui ...
END;
Se estiver convertendo um procedimento Oracle
que usa SAVEPOINT e ROLLBACK TO
no estilo acima, a tarefa será fácil:
basta apenas omitir o SAVEPOINT e os
ROLLBACK TO.
Se existir um procedimento que usa SAVEPOINT e
ROLLBACK TO de uma maneira diferente, será
necessário pensar no assunto.
EXECUTE #
A versão do EXECUTE do
PL/pgSQL funciona de forma semelhante
à versão do PL/SQL, mas deve ser
lembrado usar as funções quote_literal e
quote_ident conforme descrito em
Execução de comandos dinâmicos.
Construções do tipo EXECUTE 'SELECT * FROM $1';
não vão funcionar de forma confiável, a menos que se use estas funções.
O PostgreSQL oferece dois modificadores de criação de função para otimizar a execução: “volatilidade” (se a função retorna sempre o mesmo resultado quando recebe os mesmos argumentos) e “rigor” (se a função retorna nulo se algum argumento for nulo). Consulte a página de referência de CREATE FUNCTION para obter detalhes.
Ao usar estes atributos de otimização, a instrução
CREATE FUNCTION pode se parecer assim:
CREATE FUNCTION foo(...) RETURNS integer AS $$ ... $$ LANGUAGE plpgsql STRICT IMMUTABLE;
Esta seção contém o código para um conjunto de funções
instr compatíveis com o
Oracle, que pode ser usado para
simplificar os esforços de conversão.
--
-- funções instr que imitam a correspondente do Oracle
-- Sintaxe: instr(string1, string2 [, n [, m]])
-- onde [] denota parâmetros opcionais.
--
-- Procure em string1, começando no n-ésimo caractere,
-- pela m-ésima ocorrência de string2.
-- Se n for negativo, procure para trás, começando no abs(n)-ésimo
-- caractere do final de string1.
-- Se n não for passado, assuma 1 (a procura começa no primeiro caractere).
-- Se m não for passado, assuma 1 (encontre a primeira ocorrência).
-- Retorna o índice inicial de string2 em string1,
-- ou 0 se string2 não for encontrada.
--
CREATE OR REPLACE FUNCTION instr(varchar, varchar) RETURNS integer AS $$
BEGIN
RETURN instr($1, $2, 1);
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;
CREATE OR REPLACE 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 OR REPLACE 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 'argument ''%'' is out of range', 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;