views:

2994

answers:

4

This package uses two unique features of Oracle, REF_CURSOR and a package global variable. I would like to port the functionality from Oracle to Postgresql or 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;

Can you produce the equivalent code? for Postgresql? for MySQL?

Note: The Oracle code is thread safe. This is a key feature.

A: 

For mysql:

  1. For ref_cursor you can just use a regular select in a procedure. Mysql has an implicit result set that is returned from stored procedure if you issue a select statement. See my answer here.
  2. For the package global variable, you can put it in a table, but it appears from your code that it is a sequence, so it can be replaced with an auto_increment field. That should be pretty simple.

It would help if you can post the definition of your spool table in the question. Then I could probably provide you with exact code for mysql.

Yoni
Yes, please provide the code. The spool table is defined as...create table spool (key number, txt varchar2(2048), seq number)
dacracot
A: 

Here's a solution tested with MySQL 5.1.30.

Regarding your requirement for thread-safety, the MySQL User Variable mechanism should help. This allows you to SET a variable whose state is limited to the current session. Other sessions can also create a variable by the same name, and keep a different value in it.

I assume by thread-safety you mean something like this -- session-scoped state. Because you can't really have more fine-grained thread-safe state in a database. Each thread of your application must have its own session to the database.

There are no packages in MySQL, so the user variable is global to the session. Another stored procedure that happens to use a variable of the same name will conflict.

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();
Bill Karwin
The Oracle solution is thread safe. How is the selection of key from the spool_key table thread safe?
dacracot
A: 

I have a hard time understanding several things in your code. It looks like you have a table with two sequences, but only one of them is truly an auto_increment column.

In mysql auto_increment is allowed only on one column in a table. have you considered making the other column a foreign key to an auto incremented column of another table?

The global variable is tricky, because mysql doesn't have them. I think the only resolution is to store it as a scalar in a table, and then tie your data to it with a foreign key.

Finally, returning a ref cursor is easy, as I pointed out in my previous answer. In the link provide (to a different answer) you can see a code sample.

Yoni
There are no auto_increment columns. There is only one table, spool. There are two sequences, key and seq which are used to populate the columns of the same name in spool. The table solution for the global variable is not thread safe and therefore insufficient.
dacracot
+5  A: 

PostgreSQL 8.3

The problem in PostgreSQL is the lack of global (or package) variables, so that part has to be solved with a temp-table that is created first. The rest of it was quite easy.

If you are serious about porting the application over to PostgreSQL or MySQL, I would recommend you to not use global variables at all since they are bad practice when coding (according to me at least :))

But anyway, here is the code:

This has to exist before running the functions:

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));

The functions are being put in the schema tox to simulate a package.

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;

To test, just run this after everything have been created.

select * from tox.test('Test');
Jimmy Stenke
Global variables are not evil nor immoral, just dangerous, so you had better have a good reason to use them. This code does have a good reason since the global variable, which is global to the session only, allows the package to be thread safe.
dacracot
Is using a temp table (as opposed to a regular table) thread safe?
dacracot
Hmmm... seems the temp table is available only to the local session and is destroyed after the session closes. Smells thread safe to me. At what point must it be created? At the beginning of each session?
dacracot
Yes, it is only available for the duration of the session and has to be created before use. It should be possible to have a check in the set_variable function and create the table if it doesn't exist. But any time before works.
Jimmy Stenke
About the global variables, I guess it depends on which school you learned from :) For me, if there is a way that things can go wrong, avoid using them. But, in the end it is a highly subjective matter.
Jimmy Stenke