regex for oracle "create procedure" definition - sql

I need regex to capture full "create procedure" statement.
Here is one of examples, which I used for testing my regex:
CREATE OR REPLACE PROCEDURE sp_for_comp (P_VARNAME IN VARCHAR2, P_VALUE IN OUT NUMBER)
as
v_if_exists NUMBER(10,0);
BEGIN
SELECT COUNT(*) INTO v_if_exists FROM PKG_VAR WHERE VARIABLENAME = P_VARNAME;
IF v_if_exists > 0
THEN
begin
SELECT VALUE INTO P_VALUE FROM PKG_VAR WHERE VARIABLENAME = P_VARNAME;
EXCEPTION
WHEN OTHERS THEN
NULL;
end;
ELSE
begin
INSERT INTO PKG_VAR VALUES(P_VARNAME, P_VALUE);
EXCEPTION
WHEN OTHERS THEN
NULL;
end;
END IF;
END;
/
Current regex:
/CREATE\s+(OR\s+REPLACE\s+)?PROCEDURE\s+(\w+)\s*\(((?!.*\bEND\b\s*(\w+\s*)?\;\s*\/).*\s*)+.+/
As for my issue: I use QRegularExpression class and program failed when I run it on large files. Also, when I run it on small file - all works correctly.
After a lot of tests on online debuggers, like regexr.com, I could not find problem in regex.
How I should change it and where are may be problems?

Try something very simple like:
CREATE(\s+OR\s+REPLACE)\s+PROCEDURE.*?END;\s*/
It just looks for the CREATE OR REPLACE PROCEDURE at the start and then the end will be END; followed by / (indicating the end of the PL/SQL block in the SQL scope) on the next line with the minimal amount between.
(Note: You will probably want to use the ni regular expression match parameters to allow . to match the newline character and to do case-insensitive matches.)
Example:
CREATE TABLE script (value ) AS
SELECT 'CREATE OR REPLACE PROCEDURE sp_for_comp (P_VARNAME IN VARCHAR2, P_VALUE IN OUT NUMBER)' || CHR(10)
|| ' as' || CHR(10)
|| ' v_if_exists NUMBER(10,0);' || CHR(10)
|| 'BEGIN' || CHR(10)
|| ' SELECT COUNT(*) INTO v_if_exists FROM PKG_VAR WHERE VARIABLENAME = P_VARNAME;' || CHR(10)
|| ' IF v_if_exists > 0' || CHR(10)
|| ' THEN' || CHR(10)
|| ' begin' || CHR(10)
|| ' SELECT VALUE INTO P_VALUE FROM PKG_VAR WHERE VARIABLENAME = P_VARNAME;' || CHR(10)
|| ' EXCEPTION' || CHR(10)
|| ' WHEN OTHERS THEN' || CHR(10)
|| ' NULL;' || CHR(10)
|| ' end;' || CHR(10)
|| 'ELSE' || CHR(10)
|| ' begin' || CHR(10)
|| ' INSERT INTO PKG_VAR VALUES(P_VARNAME, P_VALUE);' || CHR(10)
|| ' EXCEPTION' || CHR(10)
|| ' WHEN OTHERS THEN' || CHR(10)
|| ' NULL;' || CHR(10)
|| ' end;' || CHR(10)
|| ' END IF;' || CHR(10)
|| 'END;' || CHR(10)
|| '/'
FROM DUAL;
SELECT COUNT(*)
FROM script
WHERE REGEXP_LIKE( value, '^CREATE(\s+OR\s+REPLACE)\s+PROCEDURE.*?END;\s*/$', 'n' );
Outputs:
COUNT(*)
--------
1

Related

Convert dbms_output to html and selectively suppress other output

I am using the code below which outputs as intended:
DECLARE
v_ins_param VARCHAR2(10);
CURSOR c_ins_param IS
SELECT status
FROM v$instance;
BEGIN
OPEN c_ins_param;
LOOP
FETCH c_ins_param INTO v_ins_param;
EXIT WHEN c_ins_param%NOTFOUND;
--
--
--
IF v_ins_param = 'OPEN' THEN
DBMS_OUTPUT.PUT_LINE('
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || '
Great! Your database is up.
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || ' ');
ELSIF v_ins_param = 'MOUNTED' THEN
DBMS_OUTPUT.PUT_LINE('
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || '
Database is only mounted.
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || '
');
ELSE
DBMS_OUTPUT.PUT_LINE('
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || '
Database is neither mounted or open.
' || CHR(35) || '' || CHR(35) ||'' || CHR(35) || '
');
END IF;
END LOOP;
CLOSE c_ins_param;
END;
/
This is the "kind" of output it'll give you:
###
Great! Your database is up.
###
Ultimately this will be a series of anonymous block "typed" scripts like this one which I want to output to a single file.
I am now trying to get the output to just give the text information only, in a html output. I saw this on another page and adapted it for my code to see how it would / could work:
spool c:mag.html
DECLARE
CURSOR c1 IS
SELECT * FROM dept;
BEGIN
DBMS_OUTPUT.PUT_LINE('< pre >');
DBMS_OUTPUT.PUT_LINE('< h1 >Report on Databases</h1>');
FOR mag IN c1
LOOP
EXIT WHEN c1%notfound;
DBMS_OUTPUT.PUT_LINE('< b >Department Name[/b] =' || mag.dname);
END LOOP;
END;
/
However the way of doing this, using:
mag.dname
is not applicable in my 'non for loop' example.
I feel like there must be a more appropriate way of achieving the output. I have tried:
set markup html on
And it does not just returned impact the output but ALL of the code.
Has anyone tried this before and know perhaps how I might get the html output for the returned data only?
Here's an example - I store the following in a script called (say) scr.sql
set termout off
set feedback off
set serverout on
begin
for i in (
select
case
when status = 'OPEN' then '<p>Database is open</p>'
when status = 'MOUNTED' then '<p>Database is mounted</p>'
else '<p>Database is not happy</p>'
end status_output
from v$instance
)
loop
dbms_output.put_line(i.status_output);
end loop;
end;
.
spool /tmp/status.html
/
spool off
and then run this from SQLPlus as
SQL> #scr.sql
and the only thing my resultant status.html is
<p>Database is open</p>

PL/SQL Oracle dbms_output make evenly output

I tried searching for an answer and found none.
I would love to make my output evenly even if the length of the article is different for each article.
this is what I have when the length are different
This is the code for the people intersted to look at
set serverout ON format WORD_WRAPPED;
CREATE OR REPLACE PROCEDURE imprimeFacture IS
CURSOR c_facture IS
SELECT
customer.contact as c_contact,
customer.address as c_address,
customer.city as c_city,
customer.state as C_state,
customer.zip as c_zip,
customer.phone as c_phone,
salesman.nom as s_nom,
salesman.address as s_address,
salesman.city as s_city,
salesman.state as s_state,
salesman.zip as s_zip,
salesman.phone as s_phone,
invoices.ino as i_ino,
invoices.idate as i_date,
detail.qty as d_qty,
parts.descript as p_description,
detail.price as d_prix,
detail.ltotal as d_prixTotal,
(
SELECT
round(SUM(detail.ltotal), 2)
FROM
detail
WHERE
detail.ino = 1054
) AS totalfacture,
(
SELECT
round(SUM(detail.ltotal * 0.15), 2)
FROM
detail
WHERE
detail.ino = 1054
) AS taxe,
(
SELECT
round(SUM(detail.ltotal * 0.15 + detail.ltotal), 2)
FROM
detail
WHERE
detail.ino = 1054
) AS totalavectaxe
FROM
invoices
JOIN detail ON detail.ino = invoices.ino
JOIN parts ON parts.pno = detail.pno
JOIN customer ON customer.cno = invoices.cno
JOIN salesman ON salesman.salesman = invoices.salesman
WHERE
invoices.ino = 1054;
rec_facture c_facture%rowtype;
fictifAvantBoucle NUMBER := 0;
BEGIN
open c_facture;
loop
FETCH c_facture into rec_facture;
exit when c_facture%notfound;
if fictifAvantBoucle = 0 then
dbms_output.put_line('Facture numéro: ' || rec_facture.i_ino || chr(10) || 'Date de facturation (Y/M/D): ' || rec_facture.i_date --section Facture
|| chr(10) || chr(10) || 'Vendu par ' || chr(10) || rec_facture.s_nom || chr(10) || 'Téléphone: ' || rec_facture.s_phone || chr(10) --section vendeur
|| 'Address: ' || rec_facture.s_address || chr(10)
|| 'Ville: ' || rec_facture.s_city || chr(10) || 'État: ' || rec_facture.s_state || chr(10) || 'ZIP: ' || rec_facture.s_zip
|| chr(10) || chr(10) || 'Facturé à ' || chr(10) || rec_facture.c_contact || chr(10) || 'Téléphone: ' || rec_facture.c_phone || chr(10) --section client
|| 'Address: ' || rec_facture.c_address
|| chr(10) || 'Ville: ' || rec_facture.c_city || chr(10) || 'État: ' || rec_facture.c_state || chr(10) || 'ZIP: ' || rec_facture.c_zip || chr(10)
|| chr(10) || '---------------------------------------------------------------------------------------------------------------------------' --section facture Article
|| chr(10) || 'Qty' || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || 'Article' || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9)
|| 'Prix unitaire' || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || 'Montant total' || chr(10)
|| '---------------------------------------------------------------------------------------------------------------------------');
fictifAvantBoucle := fictifAvantBoucle + 1;
end if;
dbms_output.put_line(rec_facture.d_qty || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || rec_facture.p_description || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9)
|| rec_facture.d_prix || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || chr(9) || rec_facture.d_prixTotal);
end loop;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('no data found');
END;
BEGIN
imprimeFacture();
END;
Change your output line to something like
DBMS_OUTPUT.PUT_LINE(LPAD(rec_facture.d_qty, 3)) || -- d_qty : 3 characters
RPAD(' ', 21) || -- blanks
RPAD(rec_facture.p_description, 29) || -- p_description : 29 chars
LPAD(rec_facture.d_prix, 13) || -- d_prix : 13 chars
RPAD(' ', 23) || -- blanks
LPAD(rec_facture.d_prixTotal, 13)); -- d_prixTotal : 13 chars

Sanitize user input with the USING keyword in PL/pgSQL

This is how I create my search_term:
IF char_length(search_term) > 0 THEN
order_by := 'ts_rank_cd(textsearchable_index_col, to_tsquery(''' || search_term || ':*''))+GREATEST(0,(-1*EXTRACT(epoch FROM age(last_edited)/86400))+60)/60 DESC';
search_term := 'to_tsquery(''' || search_term || ':*'') ## textsearchable_index_col';
ELSE
search_term := 'true';
END IF;
I am having some trouble with a PLPGSQL function:
RETURN QUERY EXECUTE '
SELECT
*
FROM
articles
WHERE
$1 AND
' || publication_date_query || ' AND
primary_category LIKE ''' || category_filter || ''' AND
' || tags_query || ' AND
' || districts_query || ' AND
' || capability_query || ' AND
' || push_notification_query || ' AND
' || distance_query || ' AND
' || revision_by || ' AND
' || publication_priority_query || ' AND
' || status_query || ' AND
is_template = ' || only_templates || ' AND
status <> ''DELETED''
ORDER BY ' || order_by || ' LIMIT 500'
USING search_term;
END; $$;
returns ERROR:
argument of AND must be type boolean, not type text at character 64
As opposed to:
RETURN QUERY EXECUTE '
SELECT
*
FROM
articles
WHERE
' || search_term || ' AND
' || publication_date_query || ' AND
primary_category LIKE ''' || category_filter || ''' AND
' || tags_query || ' AND
' || districts_query || ' AND
' || capability_query || ' AND
' || push_notification_query || ' AND
' || distance_query || ' AND
' || revision_by || ' AND
' || publication_priority_query || ' AND
' || status_query || ' AND
is_template = ' || only_templates || ' AND
status <> ''DELETED''
ORDER BY ' || order_by || ' LIMIT 500';
END; $$;
... which works. Am I missing something?
My goal is to sanitize my user input.
If some of your input parameters can be NULL or empty and should be ignored in this case, you best build your whole statement dynamically depending on user input - and omit respective WHERE / ORDER BY clauses completely.
The key is to handle NULL and empty string correctly, safely (and elegantly) in the process. For starters, search_term <> '' is a smarter test than char_length(search_term) > 0. See:
Best way to check for "empty or null value"
And you need a firm understanding of PL/pgSQL, or you may be in over your head. Example code for your case:
CREATE OR REPLACE FUNCTION my_func(
_search_term text = NULL -- default value NULL to allow short call
, _publication_date_query date = NULL
-- , more parameters
)
RETURNS SETOF articles AS
$func$
DECLARE
sql text;
sql_order text; -- defaults to NULL
BEGIN
sql := concat_ws(' AND '
,'SELECT * FROM articles WHERE status <> ''DELETED''' -- first WHERE clause is immutable
, CASE WHEN _search_term <> '' THEN '$1 ## textsearchable_index_col' END -- ELSE NULL is implicit
, CASE WHEN _publication_date_query <> '' THEN 'publication_date > $2' END -- or similar ...
-- , more more parameters
);
IF search_term <> '' THEN -- note use of $1!
sql_order := 'ORDER BY ts_rank_cd(textsearchable_index_col, $1) + GREATEST(0,(-1*EXTRACT(epoch FROM age(last_edited)/86400))+60)/60 DESC';
END IF;
RETURN QUERY EXECUTE concat_ws(' ', sql, sql_order, 'LIMIT 500')
USING to_tsquery(_search_term || ':*') -- $1 -- prepare ts_query once here!
, _publication_date_query -- $2 -- order of params must match!
-- , more parameters
;
END
$func$ LANGUAGE plpgsql;
I added default values for function parameters, so you can omit params that don't apply in the call. Like:
SELECT * FROM my_func(_publication_date_query => '2016-01-01');
More:
Functions with variable number of input parameters
The forgotten assignment operator "=" and the commonplace ":="
Note the strategic use of concat_ws(). See:
How to concatenate columns in a Postgres SELECT?
Here is a related answer with lots of explanation:
Test for null in function with varying parameters

Prepared statements in PostgreSQL

Trying to figure out how to make prepared statements work in plpgsql in order to sanitize my code.
PREPARE statements(text, text, text, text, text, text, text, text, text, text, text, text) AS
'SELECT
*
FROM
articles
WHERE
' || $1 || ' AND
' || $2 || ' AND
primary_category LIKE ''' || $3 || ''' AND
' || $4 || ' AND
' || $5 || ' AND
' || $6 || ' AND
' || $7 || ' AND
' || $8 || ' AND
' || $9 || ' AND
' || $10 || ' AND
' || $11 || ' AND
is_template = ' || §12 || ' AND
status <> ''DELETED''
ORDER BY ' || $13 || ' LIMIT 500';
RETURN QUERY EXECUTE statements(search_term, publication_date_query, category_filter, tags_query, districts_query, capability_query, push_notification_query, distance_query, revision_by, publication_priority_query, status_query, only_templates, order_by);
The above code returns
ERROR: syntax error at or near "'SELECT
*
FROM
articles
WHERE
'"
LINE 67: 'SELECT
I declade my variables like so:
DECLARE
tags_query text := 'true';
BEGIN
IF char_length(search_term) > 0 THEN
order_by := 'ts_rank_cd(textsearchable_index_col, to_tsquery(''' || search_term || ':*''))+GREATEST(0,(-1*EXTRACT(epoch FROM age(last_edited)/86400))+60)/60 DESC';
search_term := 'to_tsquery(''' || search_term || ':*'') ## textsearchable_index_col';
ELSE
search_term := 'true';
END IF;
...
I am new at this, please don't freak out immediately, if it is something silly, i did not notice.
Edit: PostgreSQL Version 9.6
Edit: I am aware of the documentation.
I see more issues.
PLpgSQL doesn't support explicitly prepared commands - so SQL EXECUTE command is different than PLpgSQL EXECUTE command. Parameter of PLpgSQL EXECUTE command is SQL string - not name of prepared command. There are not clean way, how to execute SQL explicitly prepared command from PLpgSQL. So, combination PREPARE cmd(); EXECUTE cmd() in PLpgSQL has not any sense.
Parameter of prepared statement should by clean value - it cannot be used inside apostrophes. ` ' $n ' is another nonsense. Just $n is safe. ' $n ' means string " $n " what is probably different, than you are expecting.

PL/SQL: numeric or value error%s in loop

I have a function with a loop inside a loop and I'm getting 'numeric or value error'.
sendXML CLOB;
FOR p IN (
SELECT ID, NAME, GUID FROM products
WHERE ID = IN_PROJECT_ID
)
LOOP
if p.ID is not null and p.NAME is not null then
sendXML := sendXML || '<product type="product" id="' || p.ID|| '" name="' || p.NAME || '">';
FOR t IN (
SELECT
identifier ATTR_IDENTIFIER,
label ATTR_LABEL,
CASE type
WHEN UPPER('STRING') THEN TRIM(string_value)
WHEN UPPER('NUMBER') THEN TRIM(TO_CHAR(number_value))
ELSE '' END ATTR_VALUE
FROM products_data
WHERE
product_id = p.ID AND
identifier is not null
ORDER BY identifier
)
LOOP
sendXML := sendXML || '<attribute identifier="' || t.ATTR_IDENTIFIER || '" label="'|| t.ATTR_LABEL || '">' || t.ATTR_VALUE || '</attribute>';
END LOOP;
END IF;
END LOOP;
The error
ORA-06502: PL/SQL:numeric or value error ORA-06512: at "ASM.XXXX", line 85 06502
06502. 00000 - "PL/SQL: numeric or value error%s"
throws for the line:
sendXML := sendXML || '<product type="product" id="' || p.ID|| '" name="' || p.NAME || '">';
But I found out that if I delete the last
sendXML := sendXML || '<attribute identifier="' || t.ATTR_IDENTIFIER || '" label="'|| t.ATTR_LABEL || '">' || t.ATTR_VALUE || '</attribute>';
I'm not getting any error.
Wheres the problem?
SOLUTION:
p.ID is integer and must be char... TO_CHAR(p.ID) solved my problem!
sendXML := sendXML || '<product type="product" id="' || TO_CHAR(p.ID) ||
My guess is that the string becomes larger than the maximum for varchar2 in PL/SQL.
Try the following to append text to a clob:
dbms_lob.append(sendXML, to_clob('<product type="product" id="' || p.ID|| '" name="' || p.NAME || '">'));
and the second one:
dbms_lob.append(sendXML, to_clob('<attribute identifier="' || t.ATTR_IDENTIFIER || '" label="'|| t.ATTR_LABEL || '">' || t.ATTR_VALUE || '</attribute>'));
Use quote operator like
sendXML := sendXML || q'[<attribute identifier=']' || t.ATTR_IDENTIFIER || q'[' label=']'|| t.ATTR_LABEL || q'['>]' || t.ATTR_VALUE || q'[</attribute>]';
To escape quotes. See if error persists