Converta procedimento armazenado Oracle usando ref_cursor e empacotá variável global para PostgreSQL ou MySQL
-
20-08-2019 - |
Pergunta
Este pacote utiliza duas características únicas da Oracle, ref_cursor e uma variável global pacote. Gostaria de porta a funcionalidade do Oracle para o PostgreSQL ou MySQL.
PACKAGE tox IS
/*=======================*/
g_spool_key spool.key%TYPE := NULL;
TYPE t_spool IS REF CURSOR RETURN spool%ROWTYPE;
/*=======================*/
PROCEDURE begin_spool;
/*=======================*/
PROCEDURE into_spool
(
in_txt IN spool.txt%TYPE
);
/*=======================*/
PROCEDURE reset_spool;
/*=======================*/
FUNCTION end_spool
RETURN t_spool;
/*=======================*/
FUNCTION timestamp
RETURN VARCHAR2;
/*=======================*/
END tox;
PACKAGE BODY tox
IS
/*========================================================================*/
PROCEDURE begin_spool
AS
/*=======================*/
BEGIN
/*=======================*/
SELECT
key.NEXTVAL
INTO
g_spool_key
FROM
DUAL;
/*=======================*/
END begin_spool;
/*========================================================================*/
PROCEDURE into_spool
(
in_txt IN spool.txt%TYPE
)
AS
/*=======================*/
BEGIN
/*=======================*/
INSERT INTO
spool
VALUES
(
g_spool_key,
in_txt,
seq.NEXTVAL
);
/*=======================*/
END into_spool;
/*========================================================================*/
PROCEDURE reset_spool
AS
/*=======================*/
BEGIN
/*=======================*/
DELETE
spool
WHERE
key = g_spool_key;
COMMIT;
begin_spool;
/*=======================*/
END reset_spool;
/*========================================================================*/
FUNCTION end_spool
RETURN t_spool
AS
v_spool t_spool;
/*=======================*/
BEGIN
/*=======================*/
COMMIT;
OPEN v_spool FOR
SELECT
*
FROM
spool
WHERE
key = g_spool_key
ORDER BY
seq;
RETURN v_spool;
/*=======================*/
END end_spool;
/*========================================================================*/
FUNCTION timestamp
RETURN VARCHAR2
AS
/*-----------------------*/
v_result VARCHAR2(14);
/*=======================*/
BEGIN
/*=======================*/
SELECT
TO_CHAR(SYSDATE,'YYYYMMDDHH24MISS')
INTO
v_result
FROM
DUAL;
RETURN v_result;
/*=======================*/
END timestamp;
/*========================================================================*/
END tox;
Você pode produzir o código equivalente? para o PostgreSQL? para o MySQL?
Nota: O código Oracle é thread-safe. Esta é uma característica fundamental.
Solução
PostgreSQL 8.3
O problema no PostgreSQL é a falta de variáveis ??globais (ou pacote), de modo que parte tem que ser resolvido com uma temp-tabela que é criada pela primeira vez. O resto era bastante fácil.
Se você é sério sobre a portabilidade do aplicativo até PostgreSQL ou MySQL, eu recomendo que você não usar variáveis ??globais em tudo desde que eles são uma prática ruim quando codificação (de acordo comigo, pelo menos:))
Mas de qualquer maneira, aqui está o código:
Isto tem de existir antes de executar as funções:
create table spool (key integer, txt varchar(2048), seq integer);
create sequence s_key;
create sequence s_seq;
create schema tox;
create temp table globals (name varchar(10), value varchar(100), primary key(name));
As funções estão sendo colocados no tox esquema para simular um pacote.
create or replace function tox.get_variable(var_name varchar) returns varchar as $$
declare
ret_val varchar(100);
begin
select value into ret_val from globals where name = var_name;
return ret_val;
end
$$ language plpgsql;
create or replace function tox.set_variable(var_name varchar, value anyelement) returns void as $$
begin
delete from globals where name = var_name;
insert into globals values(var_name, value);
end;
$$ language plpgsql;
create or replace function tox.begin_spool() returns integer as $$
begin
perform tox.set_variable('key', nextval('s_key')::varchar);
return tox.get_variable('key');
end;
$$ language plpgsql;
create or replace function tox.reset_spool() returns integer as $$
begin
delete from spool where key = tox.get_variable('key')::integer;
return tox.begin_spool();
end;
$$ language plpgsql;
create or replace function tox.into_spool(in_txt spool.txt%TYPE) returns void as $$
begin
insert into spool values(tox.get_variable('key')::integer, in_txt, nextval('s_seq'));
end;
$$ language plpgsql;
create or replace function tox.end_spool(refcursor) returns refcursor as $$
declare
begin
open $1 for select * from spool where key = tox.get_variable('key')::integer order by seq;
return $1;
end;
$$ language plpgsql;
create or replace function tox.test(txt varchar(100)) returns setof spool as $$
declare
v_spool_key integer;
cnt integer;
begin
v_spool_key = tox.begin_spool();
for cnt in 1..10 loop
perform tox.into_spool(txt || cnt);
end loop;
perform tox.end_spool('spool_cursor');
return query fetch all from spool_cursor;
end;
$$ language plpgsql;
Para testar, basta executar este depois de tudo ter sido criado.
select * from tox.test('Test');
Outras dicas
Para mysql:
- Para ref_cursor você pode apenas usar um seleto regular em um procedimento. Mysql tem um conjunto de resultados implícito que é retornado do procedimento armazenado se você emitir uma instrução SELECT. Veja a minha resposta aqui .
- Para a variável global pacote, você pode colocá-lo em uma mesa, mas parece do seu código que é uma seqüência, assim que pode ser substituído com um campo auto_increment. Isso deve ser bastante simples.
Seria bom se você pode enviar a definição de sua tabela de spool na pergunta. Então eu provavelmente poderia fornecê-lo com código exato para mysql.
Eu tenho dificuldade em entender várias coisas no seu código. Parece que você tem uma tabela com duas seqüências, mas apenas um deles é verdadeiramente uma coluna auto_increment.
Em auto_increment mysql é permitido apenas em uma coluna em uma tabela. você já pensou em fazer outra coluna uma chave estrangeira para uma auto coluna incrementado de outra tabela?
A variável global é complicado, porque o MySQL não tê-los. Eu acho que a única solução é armazená-lo como um escalar em uma tabela, e em seguida, amarre os seus dados a ele com uma chave estrangeira.
Finalmente, retornando um cursor ref é fácil, como indiquei na minha resposta anterior. No link fornecer (a uma resposta diferente), você pode ver um exemplo de código.
Aqui está uma solução testada com o MySQL 5.1.30.
Quanto à sua exigência de thread-segurança, o MySQL Usuário mecanismo variável deve ajudar. Isso permite que você SET
uma variável cujo estado é limitado à sessão actual. Outras sessões também pode criar uma variável com o mesmo nome, e manter um valor diferente nele.
Eu assumo por thread-safety você quer dizer algo como isto - Estado com escopo de sessão. Porque você não pode realmente ter mais Estado thread-safe de grão fino em um banco de dados. Cada segmento da sua aplicação deve ter a sua própria sessão no banco de dados.
Não há pacotes no MySQL, então a variável de usuário é global para a sessão. Outro procedimento armazenado que acontece de usar uma variável com o mesmo nome entrem em conflito.
CREATE TABLE spool (
`key` INT,
txt VARCHAR(2048),
seq INT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE spool_key (
`key` INT AUTO_INCREMENT PRIMARY KEY
);
DELIMITER $$
CREATE PROCEDURE begin_spool ()
BEGIN
DELETE FROM spool_key;
INSERT INTO spool_key (`key`) VALUES (DEFAULT);
SET @sp_key = LAST_INSERT_ID();
END $$
CREATE PROCEDURE into_spool(IN in_txt VARCHAR(2048))
BEGIN
INSERT INTO spool (`key`, txt, seq) VALUES
(@sp_key, in_txt, DEFAULT);
END $$
CREATE PROCEDURE reset_spool()
BEGIN
DELETE spool FROM spool JOIN spool_key USING (`key`);
CALL begin_spool();
END $$
CREATE PROCEDURE end_spool()
BEGIN
SELECT *
FROM spool JOIN spool_key USING (`key`)
ORDER BY seq;
END $$
DELIMITER ;
CALL begin_spool();
CALL into_spool('now is the time');
CALL into_spool('for all good men');
CALL end_spool();
CALL reset_spool();
CALL into_spool('to come to the aid');
CALL into_spool('of their country');
CALL end_spool();
DROP FUNCTION IF EXISTS fmt_timestamp;
CREATE FUNCTION fmt_timestamp() RETURNS CHAR(14)
RETURN DATE_FORMAT(SYSDATE(), '%Y%m%d%H%i%s');
SELECT fmt_timestamp();