41.13. Conversão do PL/SQL do Oracle #

41.13.1. Exemplos de conversão
41.13.2. Outras coisas a serem observadas
41.13.3. Apêndice

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:

41.13.1. Exemplos de conversão #

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;

(1)

A sintaxe do RAISE é consideravelmente diferente da instrução do Oracle, embora o caso básico RAISE nome_da_exceção funcione de maneira semelhante.

(2)

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 SQLSTATE escolhidos pelo usuário.


41.13.2. Outras coisas a serem observadas #

Esta seção explica algumas outras coisas a serem observadas ao converter funções do PL/SQL do Oracle para o PostgreSQL.

41.13.2.1. Reversão implícita após exceções #

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.

41.13.2.2. 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.

41.13.2.3. Otimização das funções do PL/pgSQL #

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;

41.13.3. Apêndice #

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;