Documentação do PostgreSQL 8.0.0 | ||||
---|---|---|---|---|
Anterior | Início | Capítulo 35. PL/pgSQL - Linguagem procedural SQL | Fim | Próxima |
Esta seção explica as diferenças entre a linguagem PL/pgSQL do PostgreSQL e a linguagem PL/SQL do Oracle, para ajudar aos desenvolvedores na conversão dos aplicativos do Oracle para o PostgreSQL.
A linguagem PL/pgSQL é semelhante à linguagem PL/SQL em muitos aspectos. É uma linguagem estruturada em blocos, imperativa, e todas as variáveis devem ser declaradas. As atribuições, laços e condicionais são semelhantes. As principais diferenças que se deve ter em mente na conversão da linguagem PL/SQL para a linguagem PL/pgSQL, são:
No PostgreSQL não existe valor padrão para parâmetros.
No PostgreSQL os nomes das funções podem ser sobrecarregados. Geralmente isto é utilizado para superar o problema da falta de parâmetros padrão.
Os cursores não são necessários na linguagem PL/pgSQL, basta por a consulta na instrução FOR (Consulte o Exemplo 35-8.)
No PostgreSQL é necessário utilizar a delimitação por cifrão ($), ou criar seqüências de escape para os apóstrofos presentes no corpo da função. Consulte a Seção 35.2.1.
Em vez de pacotes, são utilizados esquemas para organizar as funções em grupos.
Não existem pacotes, não existem variáveis no nível de pacote também. Isto aborrece um pouco. Em seu lugar, o estado por sessão pode ser mantido em tabelas temporárias.
Não podem ser utilizados nomes de parâmetros idênticos aos das colunas referenciadas na função. O Oracle permite que isto seja feito se o nome do parâmetro for qualificado na forma nome_da_função.nome_do_parâmetro. [1]
O Exemplo 35-7 mostra como converter uma função simples de PL/SQL para PL/pgSQL.
Exemplo 35-7. Conversão de uma função simples de PL/SQL para PL/pgSQL
Abaixo está uma função escrita na linguagem PL/SQL do Oracle:
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_nome IN varchar, v_versao IN varchar) RETURN varchar IS BEGIN IF v_versao IS NULL THEN RETURN v_nome; END IF; RETURN v_nome || '/' || v_versao; END; / show errors;
Vamos examinar esta função para ver as diferenças com relação à linguagem PL/pgSQL:
O Oracle permite que sejam passados parâmetros IN, OUT e INOUT para as funções. Por exemplo, INOUT significa que o parâmetro recebe um valor e retorna outro. O PostgreSQL somente possui parâmetros IN e, portanto, não há especificação do tipo do parâmetro.
A palavra chave RETURN no protótipo da função (não no corpo da função), no PostgreSQL se torna RETURNS. Além disso, IS se torna AS, e é necessário adicionar a cláusula LANGUAGE, porque a linguagem PL/pgSQL não é a única possível.
No PostgreSQL o corpo da função é considerado como sendo um literal cadeia de caracteres, portanto é necessário utilizar delimitação por apóstrofos ou cifrão em torno do corpo da função. Isto substitui a barra (/) terminadora da abordagem do Oracle.
Não existe o comando /show errors no PostgreSQL, e não há necessidade uma vez que os erros são relatados automaticamente.
Abaixo está mostrado como esta função deve se parecer após ser convertida para o PostgreSQL: [2]
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_nome varchar, v_versao varchar) RETURNS varchar AS $$ BEGIN IF v_versao IS NULL THEN RETURN v_nome; END IF; RETURN v_nome || '/' || v_versao; END; $$ LANGUAGE plpgsql;
O Exemplo 35-8 mostra como converter uma função que cria outra função, e como tratar o problema dos apóstrofos.
Exemplo 35-8. Conversão de uma função que cria outra função de PL/SQL para PL/pgSQL
O procedimento abaixo obtém linhas a partir de uma instrução SELECT, e constrói uma função grande com os resultados em instruções IF, por motivo de eficiência. Em particular, deve ser observada a diferença entre o cursor e o laço FOR.
Esta é a versã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 VARCHAR, v_domain IN VARCHAR, v_url IN VARCHAR) RETURN VARCHAR 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;
Abaixo está mostrado como esta função ficaria no PostgreSQL:
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$ DECLARE referrer_key RECORD; -- declare a generic record to be used in a FOR func_body text; func_cmd text; BEGIN func_body := 'BEGIN' ; -- Notice how we scan through the results of a query in a FOR loop -- using the FOR <record> construct. FOR referrer_key IN SELECT * FROM cs_referrer_keys ORDER BY try_order 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; RETURN; END; $func$ LANGUAGE plpgsql;
Deve ser observado como o corpo da função é construído em separado e passado através da função quote_literal para duplicar qualquer apóstrofo porventura existente. Esta técnica é necessária, porque não pode ser utilizada a delimitação por cifrão com segurança para definir a nova função: não há certeza de quais cadeias de caracteres serão interpoladas a partir do campo referrer_key.key_string (Aqui é assumido que se pode confiar que referrer_key.kind será sempre host, domain, ou url, mas referrer_key.key_string pode ser qualquer coisa e, em particular, pode conter o caractere cifrão). Na verdade esta função é uma melhoria com relação à original do Oracle, porque não irá gerar código com erro quando referrer_key.key_string ou referrer_key.referrer_type contiverem apóstrofos.
O Exemplo 35-9 mostra como converter uma função com parâmetros OUT e manipulação de cadeia de caracteres. O PostgreSQL não possui a função instr, mas isto pode ser superado utilizando uma combinação de outras funções. Na Seção 35.11.3 existe uma implementação em PL/pgSQL da função instr que pode ser utilizada para facilitar a conversão.
Exemplo 35-9. Conversão de um procedimento com manipulação de cadeia de caracteres e parâmetros OUT de PL/SQL para PL/pgSQL
O procedimento mostrado abaixo, escrito na linguagem PL/SQL do Oracle, é utilizado para analisar uma URL e retornar vários elementos (hospedeiro, caminho e comando). No PostgreSQL, as funções podem retornar apenas um valor. Uma forma de superar este problema é tornar o valor retornado do tipo composto (tipo linha).
Esta é a versão Oracle:
CREATE OR REPLACE PROCEDURE cs_parse_url( v_url IN VARCHAR, v_host OUT VARCHAR, -- Este é passado de volta, v_path OUT VARCHAR, -- este também, v_query OUT VARCHAR) -- e 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;
Abaixo está mostrada uma conversão possível para PL/pgSQL:
CREATE TYPE cs_parse_url_result AS ( v_host VARCHAR, v_path VARCHAR, v_query VARCHAR ); CREATE OR REPLACE FUNCTION cs_parse_url(v_url VARCHAR) RETURNS cs_parse_url_result AS $$ DECLARE res cs_parse_url_result; a_pos1 INTEGER; a_pos2 INTEGER; BEGIN res.v_host := NULL; res.v_path := NULL; res.v_query := NULL; a_pos1 := instr(v_url, '//'); IF a_pos1 = 0 THEN RETURN res; END IF; a_pos2 := instr(v_url, '/', a_pos1 + 2); IF a_pos2 = 0 THEN res.v_host := substr(v_url, a_pos1 + 2); res.v_path := '/'; RETURN res; END IF; res.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 res.v_path := substr(v_url, a_pos2); RETURN res; END IF; res.v_path := substr(v_url, a_pos2, a_pos1 - a_pos2); res.v_query := substr(v_url, a_pos1 + 1); RETURN res; END; $$ LANGUAGE plpgsql;
Esta função poderia ser utilizada da seguinte maneira:
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 35-10 mostra como converter um procedimento que utiliza diversas funcionalidades específicas do Oracle.
Exemplo 35-10. Conversão de um procedimento de PL/SQL para PL/pgSQL
A versão Oracle:
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS a_running_job_count INTEGER; PRAGMA AUTONOMOUS_TRANSACTION;(1) BEGIN LOCK TABLE cs_jobs IN EXCLUSIVE MODE;(2) SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL; IF a_running_job_count > 0 THEN COMMIT; -- liberar bloqueio(3) raise_application_error(-20000, 'Não foi possível criar uma nova tarefa: já há uma em execução.'); 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, sysdate); EXCEPTION WHEN dup_val_on_index THEN NULL; -- não se preocupar se já existe END; COMMIT; END; / show errors
Procedimentos como este podem ser facilmente convertidos em funções PostgreSQL que retornam void. Este procedimento, em particular, é interessante porque pode ensinar algumas coisas:
Abaixo está mostrada uma maneira de como esta função poderia ser convertida para PL/pgSQL:
CREATE OR REPLACE FUNCTION cs_create_job(v_job_id integer) RETURNS void 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 RAISE EXCEPTION 'Não foi possível criar uma nova tarefa: já há uma em execução.';(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 preocupar se já existe END; RETURN; END; $$ LANGUAGE plpgsql;
Esta seção explica algumas poucas outras coisas a serem observadas ao converter funções da linguagem PL/SQL do Oracle para o PostgreSQL.
Na linguagem PL/pgSQL, quando uma exceção é capturada pela cláusula EXCEPTION todas as alterações no banco de dados desde o começo do bloco (BEGIN) são desfeitas automaticamente, ou seja, este comportamento é equivalente ao que seria obtido no Oracle utilizando:
BEGIN SAVEPOINT s1; ... código ... EXCEPTION WHEN ... THEN ROLLBACK TO s1; ... código ... WHEN ... THEN ROLLBACK TO s1; ... código ... END;
Caso se esteja convertendo um procedimento do Oracle que utilize SAVEPOINT e ROLLBACK TO neste estilo, a tarefa é fácil: basta omitir o SAVEPOINT e o ROLLBACK TO. Se o procedimento utilizar SAVEPOINT e ROLLBACK TO de uma maneira diferente, então será necessário pensar sobre o assunto.
A versão PL/pgSQL do EXECUTE trabalha de forma semelhante à versão PL/SQL, mas é necessário lembrar de utilizar as funções quote_literal(text) e quote_string(text) conforme descrito na Seção 35.6.5. As construções do tipo EXECUTE 'SELECT * FROM $1'; não funcionam, a menos que estas funções sejam utilizadas.
O PostgreSQL disponibiliza dois modificadores na criação da função para otimizar a execução: "volatilidade" (se a função sempre retorna o mesmo resultado quando são passados os mesmos argumentos); se é "estrita" (se a função retorna nulo se algum dos argumentos for nulo). Para obter mais detalhes deve ser consultada a página de referência do comando CREATE FUNCTION.
Para fazer uso dos atributos de otimização, o comando CREATE FUNCTION deve ficar parecido com:
CREATE FUNCTION foo(...) RETURNS integer AS $$ ... $$ LANGUAGE plpgsql STRICT IMMUTABLE;
Esta seção contém o código de um conjunto de funções compatíveis com a função instr do Oracle que podem ser utilizadas para diminuir o esforço de conversão.
-- -- Funções instr equivalentes a do Oracle. -- Sintaxe: instr(string1, string2, [n], [m]) -- onde [] indica que os parâmetros são opcionais. -- -- Procura em string1, a partir no n-ésimo caractere, a m-ésima -- ocorrência de string2. Se n for negativo, procura para trás. -- Se m não for fornecido, é assumido como sendo igual a 1 -- (procura a partir do primeiro caractere). -- CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$ DECLARE pos integer; BEGIN pos:= instr($1, $2, 1); RETURN pos; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search 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 IN temp_str); IF pos = 0 THEN RETURN 0; ELSE RETURN pos + beg_index - 1; END IF; ELSE ss_length := char_length(string_to_search); length := char_length(string); beg := length + beg_index - ss_length + 2; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); pos := position(string_to_search IN temp_str); IF pos > 0 THEN RETURN beg; END IF; beg := beg - 1; END LOOP; RETURN 0; END IF; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search 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 beg_index > 0 THEN beg := beg_index; temp_str := substring(string FROM beg_index); FOR i IN 1..occur_index LOOP pos := position(string_to_search IN temp_str); IF i = 1 THEN beg := beg + pos - 1; ELSE beg := beg + pos; END IF; temp_str := substring(string FROM beg + 1); END LOOP; IF pos = 0 THEN RETURN 0; ELSE RETURN beg; END IF; ELSE ss_length := char_length(string_to_search); length := char_length(string); beg := length + beg_index - ss_length + 2; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); pos := position(string_to_search IN temp_str); IF pos > 0 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; END IF; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE;
[1] |
Tirado da lista de discussão. (N. do T.) |
[2] |
Oracle — o Oracle utiliza as convenções: verbo_substantivo para procedimentos, funções e gatilhos (por exemplo, admitir_empregado, obter_salario e auditar_empregado, respectivamente); a convenção v_substantivo para as variáveis (por exemplo, v_nome); a convenção cursor_substantivo para os cursores (por exemplo, cursor_empregado, entre outras. Fonte: Develop Applications Using Database Procedures 7.2, PO1 Student Guide, Oracle. (N. do T.) |