Kausty
Kausty

Reputation: 859

Porting from Oracle to Postgres

Earlier our hosted environments were on Oracle but now we have recently switched to postgres. Porting the tables I believe was successfully since it has been working correct for a long time. I have been actually struggling with porting a procedure but is not working for some reason. I went through the documentation part of the ora2pg but I am not able to crack, which I believe is the last piece of the puzzle.

I've started with this one, which looks like this in Oracle:

create or replace
procedure c_audit(anonymous in boolean, aud_level IN varchar)
AUTHID CURRENT_USER IS 

  script varchar2(32000);
  acc_select varchar(100);
  open_cursor integer;
  returnval integer;
  p_id integer;
  a_id integer;
  p_name varchar2(100);
  a_name varchar2(100);
  v_count integer;
  c_count integer;
  doc_count integer;
  curr_user varchar(100);
begin

for i in (select a.a_name a_name, a.a_id, p.name p_name, p.id p_id, ds.username username
from c_account a
inner join c_pro p on a.a_id = p.a_id
inner join c_dat_ds_xref x on p.id = x.p_id
inner join c_data ds on x.id_datasource = ds.id
inner join c_conntypes ct on x.id_conntype = ct.id_conntype
where ct.typeid = 'CAPTURE'
order by a.a_name, p.name)
LOOP
    curr_user := i.username;
    IF anonymous = true
    THEN
        acc_select := 'select ' || '''' || i.a_id || '''' || ' a_id,' || '''' || i.p_id || '''' || ' p_id';

    ELSE
        acc_select := 'select ' || '''' || i.a_name || '''' || ' a_name,' || '''' || i.p_name || '''' || ' p_name';
    END IF;

    IF upper(aud_level) = 'VERBATIM'
    THEN         
          script:= acc_select || '
        , count(distinct d.document_id) docCount 
        , sum(case when v.document_id is null or v.verbatim_type_value = ''NO_VERBATIM_TEXT'' then 0 else 1 end) VerbCount 
        , sum(case when v.document_id is null then (select to_number(prop_value) verbSize 
                                        from c_properties 
                                        where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                                        and id_pro = 0) else v.credits end) CreditCount 
        from ' || i.username || '.p_document d 
        left outer join ( 
                        select vi.document_id, t.verbatim_type_value 
                        , case when dbms_lob.substr(vi.extracted_original,8) = ''<cbnull>''
                          or t.verbatim_type_value = ''NO_VERBATIM_TEXT''
                                        then coalesce(s2.strucCredit, .25)                            
                                        else ceil(vi.extracted_original_size/coalesce(s.verbSize, 2048)) end credits                          
                        from ' || i.username || '.p_verbatim vi 
                        left outer join ' || i.username || '.pd_verbatim_type t on vi.verbatim_type_id = t.verbatim_type_id
                        , ( 
                                        select to_number(prop_value) verbSize 
                                        from c_properties 
                                        where prop_name = ''METERING.MAXSIZE.VERBATIM'' 
                                        and id_pro = 0 
                        ) s 
                        , ( 
                                        select to_number(prop_value) strucCredit 
                                        from c_properties 
                                        where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                                        and id_pro = 0 
                        ) s2 
        ) v on d.document_id = v.document_id';
    ELSE    
        IF upper(aud_level) = 'DOCUMENT'
        THEN
            script:= acc_select || '
            , count(distinct a.document_id) docCount
            , sum(credits) creditCount
            from (
            select d.document_id, ceil(sum(v.extracted_original_size)/coalesce(s.verbSize,2048)) credits
            from ' || i.username || '.p_document d
            inner join ' || i.username || '.p_verbatim v on d.document_id = v.document_id
            inner join ' || i.username || '.pd_verbatim_type t on v.verbatim_type_id = t.verbatim_type_id
            , ( 
                            select to_number(prop_value) verbSize 
                            from c_properties 
                            where prop_name = ''METERING.MAXSIZE.VERBATIM'' 
                            and id_pro = 0 
            ) s 
            where t.verbatim_type_value <> ''NO_VERBATIM_TEXT''
            and dbms_lob.substr(v.extracted_original,8) <> ''<cbnull>''
            group by d.document_id, s.verbSize
            union
            select d.document_id, coalesce(s2.strucCredit, .25)
            from ' || i.username || '.p_document d
            , ( 
                            select to_number(prop_value) strucCredit 
                            from c_properties 
                            where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                            and id_pro = 0 
            ) s2 
            where d.document_id not in (select distinct v.document_id from ' || i.username || '.p_verbatim v)
            union 
            select distinct d.document_id, coalesce(s2.strucCredit, .25)
            from ' || i.username || '.p_document d
            inner join ' || i.username || '.p_verbatim v on d.document_id = v.document_id
            inner join ' || i.username || '.pd_verbatim_type t on v.verbatim_type_id = t.verbatim_type_id
            , ( 
                            select to_number(prop_value) strucCredit 
                            from c_properties 
                            where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                            and id_pro = 0 
            ) s2 
            where (t.verbatim_type_value = ''NO_VERBATIM_TEXT''
            or dbms_lob.substr(v.extracted_original,8) = ''<cbnull>'')
            ) a';
            ELSE
                dbms_output.put_line('Invalid choice for audit level, no audit generated');
                exit;
            END IF;
    END IF;
begin

open_cursor := dbms_sql.open_cursor;
        DBMS_SQL.PARSE(open_cursor, script,
                   DBMS_SQL.NATIVE);
        IF anonymous = true then
            dbms_sql.define_column(open_cursor,1,a_id);
            dbms_sql.define_column(open_cursor,2,p_id);
        else
            dbms_sql.define_column(open_cursor,1,a_name,100);
            dbms_sql.define_column(open_cursor,2,p_name,100);
        end if;
        dbms_sql.define_column(open_cursor,3,doc_count);
        dbms_sql.define_column(open_cursor,4,v_count);
        IF upper(aud_level) = 'VERBATIM' then
            dbms_sql.define_column(open_cursor,5,c_count);
        end if;
        returnval := DBMS_SQL.EXECUTE(open_cursor);
        loop
            if dbms_sql.fetch_rows(open_cursor) > 0 then
                IF anonymous = true then
                    dbms_sql.column_value(open_cursor,1,a_id);
                    dbms_sql.column_value(open_cursor,2,p_id);
                    dbms_sql.column_value(open_cursor,3,doc_count);
                    dbms_sql.column_value(open_cursor,4,v_count);
                    IF upper(aud_level) = 'VERBATIM' then
                        dbms_sql.column_value(open_cursor,5,c_count);
                        dbms_output.put_line(a_id || ',' || p_id || ',' || doc_count || ',' || v_count || ',' || c_count);
                    else
                        dbms_output.put_line(a_id || ',' || p_id || ',' || doc_count || ',' || v_count);
                    end if;
                else
                    dbms_sql.column_value(open_cursor,1,a_name);
                    dbms_sql.column_value(open_cursor,2,p_name);
                    dbms_sql.column_value(open_cursor,3,doc_count);
                    dbms_sql.column_value(open_cursor,4,v_count);
                    IF upper(aud_level) = 'VERBATIM' then
                        dbms_sql.column_value(open_cursor,5,c_count);
                        dbms_output.put_line(a_name || ',' || p_name || ',' || doc_count || ',' || v_count || ',' || c_count);
                    else
                        dbms_output.put_line(a_name || ',' || p_name || ',' || doc_count || ',' || v_count);
                    end if;
                end if;
            else
                exit;
            end if;
        end loop;

        exception 
            when others then
            --dbms_output.put_line('Error occured. Please check if the current user has Select access to table ' || curr_user || '.p_document ' || curr_user || '.p_verbatim ' || curr_user || '.pd_verbatim_type');
      dbms_output.put_line('Error occured. Please login as ' || curr_user || ' and run the following:');
      dbms_output.put_line('GRANT SELECT ON ' || curr_user || '.P_DOCUMENT to ' || user ||';');
      dbms_output.put_line('GRANT SELECT ON ' || curr_user || '.P_VERBATIM to ' || user ||';');
      dbms_output.put_line('GRANT SELECT ON ' || curr_user || '.pd_verbatim_type to ' || user ||';');
end;
end loop;
end;

Does this procedure appears correct with respect to syntax?

CREATE OR REPLACE FUNCTION c_audit(anonymous boolean, aud_level text) RETURNS VOID AS $body$
DECLARE
  script text;
  acc_select text;
  returnval integer;
  p_id integer;
  a_id integer;
  i record;
  p_name text;
  a_name text;
  v_count integer;
  c_count integer;
  doc_count integer;
  curr_user text;
BEGIN
for i in (SELECT a.a_name a_name, a.a_id, p.name p_name, p.id p_id, ds.username username
from c_account a
inner join c_pro p on a.a_id = p.a_id
inner join c_dat_ds_xref x on p.id = x.p_id
inner join c_data ds on x.id_datasource = ds.id
inner join c_conntypes ct on x.id_conntype = ct.id_conntype
where ct.typeid = 'CAPTURE'
order by a.a_name, p.name)
LOOP
    curr_user := i.username;
    IF anonymous = true
    THEN
        acc_select := 'SELECT ' || '''' || i.a_id || '''' || ' AccountID,' || '''' || i.p_id || '''' || ' ProjectID';
    ELSE
        acc_select := 'SELECT ' || '''' || i.a_name || '''' || ' AccountName,' || '''' || i.p_name || '''' || ' ProjectName';
    END IF;
    IF upper(aud_level) = 'VERBATIM' 
    THEN         
          script:= acc_select || '
        , count(distinct d.document_id) docCount 
        , sum(case when coalesce(CAST(v.document_id AS text), '') = '' or v.verbatim_type_value = ''NO_VERBATIM_TEXT'' then 0 else 1 end) VerbCount 
        , sum(case when coalesce(CAST(v.document_id AS text), '') = '' then (SELECT to_number(prop_value,''9999.99'') verbSize 
                                        from c_properties 
                                        where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                                        and id_project = 0) else v.credits end) CreditCount 
        from ' || i.username || '.p_document d 
        left outer join ( 
                        SELECT vi.document_id, t.verbatim_type_value 
                        , case when substr(vi.extracted_original,8) = ''<cbnull>''
                          or t.verbatim_type_value = ''NO_VERBATIM_TEXT''
                                        then coalesce(s2.strucCredit, .25)                            
                                        else ceil(vi.extracted_original_size/coalesce(s.verbSize, 2048)) end credits                          
                        from ' || i.username || '.p_verbatim vi 
                        left outer join ' || i.username || '.pd_verbatim_type t on vi.verbatim_type_id = t.verbatim_type_id
                        , ( 
                                        select to_number(prop_value,''9999.99'') verbSize 
                                        from c_properties 
                                        where prop_name = ''METERING.MAXSIZE.VERBATIM'' 
                                        and id_project = 0 
                        ) s 
                        , ( 
                                        select to_number(prop_value,''9999.99'') strucCredit 
                                        from c_properties 
                                        where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                                        and id_project = 0 
                        ) s2 
        ) v on d.document_id = v.document_id';
        SELECT format(script) into script;
    ELSE IF upper(aud_level) = 'DOCUMENT'
        THEN
            script:= acc_select || '
            , count(distinct a.document_id) docCount
            , sum(credits) creditCount
            from (
            SELECT d.document_id, ceil(sum(v.extracted_original_size)/coalesce(s.verbSize,2048)) credits
            from ' || i.username || '.p_document d
            inner join ' || i.username || '.p_verbatim v on d.document_id = v.document_id
            inner join ' || i.username || '.pd_verbatim_type t on v.verbatim_type_id = t.verbatim_type_id
            , ( 
                            SELECT to_number(prop_value,''9999.99'') verbSize 
                            from c_properties 
                            where prop_name = ''METERING.MAXSIZE.VERBATIM'' 
                            and id_project = 0 
            ) s 
            where t.verbatim_type_value <> ''NO_VERBATIM_TEXT''
            and substr(v.extracted_original,8) <> ''<cbnull>''
            group by d.document_id, s.verbSize
            union
            select d.document_id, coalesce(s2.strucCredit, .25)
            from ' || i.username || '.p_document d
            , ( 
                            select to_number(prop_value,''9999.99'') strucCredit 
                            from c_properties 
                            where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                            and id_project = 0 
            ) s2 
            where d.document_id not in (select distinct v.document_id from ' || i.username || '.p_verbatim v)
            union 
            select distinct d.document_id, coalesce(s2.strucCredit, .25)
            from ' || i.username || '.p_document d
            inner join ' || i.username || '.p_verbatim v on d.document_id = v.document_id
            inner join ' || i.username || '.pd_verbatim_type t on v.verbatim_type_id = t.verbatim_type_id
            , ( 
                            select to_number(prop_value,''9999.99'') strucCredit 
                            from c_properties 
                            where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
                            and id_project = 0 
            ) s2 
            where (t.verbatim_type_value = ''NO_VERBATIM_TEXT''
            or substr(v.extracted_original,8) = ''<cbnull>'')
            ) a';
            SELECT format(script) into script;
        ELSE
        SELECT format(script) into script;
        exit;
        END IF;
    END IF;
BEGIN
    IF anonymous = true 
    THEN
        IF upper(aud_level) = 'VERBATIM' 
        THEN
            EXECUTE script into a_id, p_id, doc_count, v_count, c_count;
        ELSE
            EXECUTE script into a_id, p_id, doc_count, c_count;
        END IF;
    ELSE
        IF upper(aud_level) = 'VERBATIM'
        THEN
            EXECUTE script into a_name, p_name, doc_count, v_count, c_count;
        ELSE
            EXECUTE script into a_name, p_name, doc_count, c_count;
        END IF;
    END IF;
    GET DIAGNOSTICS returnval := ROW_COUNT;
        LOOP
            IF returnval > 0
            THEN
                IF anonymous = true
                THEN
                    IF upper(aud_level) = 'VERBATIM'
                    THEN
                        SELECT format ('Information %s, %s, %s, %s, %s', a_id, p_id, doc_count, v_count, c_count);
                    ELSE
                        SELECT format ('Information %s, %s, %s, %s', a_id, p_id, doc_count, c_count);
                    END IF;
                ELSE
                    IF upper(aud_level) = 'VERBATIM'
                    THEN
                        SELECT format ('Information %s, %s, %s, %s, %s', a_name, p_name, doc_count, v_count, c_count);
                    ELSE
                        SELECT format ('Information %s, %s, %s, %s', a_name, p_name, doc_count, c_count);
                    END IF;
                END IF;
            ELSE
                EXIT;
            END IF;
        END LOOP;
    EXCEPTION
        WHEN others THEN
          PERFORM format('Error occured. Please login as %s, %s' ,  curr_user ,  ' and run the following:');
          PERFORM format('GRANT SELECT ON %s.P_DOCUMENT to %s', curr_user, user);
          PERFORM format('GRANT SELECT ON %s.P_VERBATIM to %s', curr_user, user);
          PERFORM format('GRANT SELECT ON %s.pd_verbatim_type to %s', curr_user, user);
END;
END LOOP;
END;
$body$
LANGUAGE PLPGSQL
;
ALTER FUNCTION cb_audit(boolean, text) OWNER TO USER;
-- REVOKE ALL ON FUNCTION cb_audit FROM PUBLIC;

The error for failure I get is -

ERROR: too many parameters specified for RAISE Where: PL/pgSQL function "cb_audit" line 145 at RAISE

I found this to be a good link which I used as a reference

I believe that for porting DBMS_OUTPUT.PUT_LINE, RAISE NOTICE should be the right way. I had encountered another error which was for format of the - to_number(prop_value,'9999.99') which appears right as per the syntax mentioned here but then for some reason when I switched to to_number(prop_value,''9999.99''), I did not get the error but not sure why that should be or even if it should work correctly.

The version of Postgres -

PostgreSQL 9.1.10 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3), 64-bit

Edited: I actually attempted to modify this function based on the suggestions from Patrick but due to some reason it is not displaying anything on the screen. I added format() at the end of every script to display the script as but it just executed the code and displays c_audit and null. Though I execute the individual subsql and they do return expected counts and results. Am I missing anything?

Upvotes: 0

Views: 1383

Answers (1)

Patrick
Patrick

Reputation: 32224

You are using two types of string "building": the concatenation || operator and the % placeholder. Both are perfectly legal, but use of the format() function is preferred because the code is cleaner and PG guards against things like SQL-injection behind the scenes.

The error you are getting is that one of your RAISE NOTICE commands has a number of % different from the arguments you supply; forgive me for not looking that up but line 145 is hard to find here on SO. In general, you should rewrite all of them like so:

RAISE NOTICE format('GRANT SELECT ON %I.pd_verbatim_type to %I', curr_user, user);

The %I placeholder takes a SQL identifier as input: it can not be NULL and it will be properly quoted to avoid SQL-injection and keyword collisions. (From your code I take it that curr_user is a schema name and user is a role name, both SQL identifiers.)

Note also that the PERFORM statement in a PL/pgSQL function is a SELECT statement that does not return data, but it is executed to examine a side effect, such as asserting that some data exists. As a consequence, there is no PERFORM privilege, use GRANT SELECT instead.

When quoting in PostgreSQL, SQL-identifiers use double quotes, while string literal values use single quotes. The to_number() function definitely requires single quotes.

A few more points to improve your code:

(1) The two sub-selects in the dynamic query like this one...

'select to_number(prop_value,''9999.99'') strucCredit 
 from c_properties
 where prop_name = ''METERING.STRUCT.ONLY.CHARGE'' 
 and id_project = 0'

... are STABLE: you always get the same result. Instead of leaving them in the dynamic SQL, create two variables and put the results in them before you do anything else:

DECLARE
  ...
  verbSize numeric;
  strucCredit numeric;
BEGIN
  SELECT to_number(prop_value,'9999.99') INTO verbSize 
  FROM c_properties 
  WHERE prop_name = 'METERING.MAXSIZE.VERBATIM' AND id_pro = 0;
  SELECT to_number(prop_value,'9999.99') INTO strucCredit 
  FROM c_properties 
  WHERE prop_name = 'METERING.STRUCT.ONLY.CHARGE' AND id_pro = 0;

  ...

Then use the varibale verbSize and strucCredit in your dynamic SQL. Like this you do these queries just once, instead of a few times for every iteration.

(2) All of the dynamic queries need a GROUP BY 1, 2 clause.

(3) The clause CASE WHEN coalesce(CAST(v.document_id AS text), '') = '' ... should be written like CASE WHEN v.document_id IS NULL ..., assuming that v.document_id can not be an empty string.

(4) You changed your RAISE NOTICE statements to SELECT format(...). That latter form produces no output, use RAISE NOTICE format(...) instead.

(5) Rewrite your dynamic SQL to use the format() function too.

Upvotes: 2

Related Questions