I need some help with Oracle's VPD feature. I have never used it before but did some research online about it, however I'm running into a problem.
Here are the steps that I have taken:
QuanTriDL:
create table NhanVien2
table NhanVien2
QuanTriVPD:
CREATE OR REPLACE CONTEXT ThongTinTaiKhoan USING TTTK_PKG;
CREATE OR REPLACE PACKAGE TTTK_PKG IS
PROCEDURE GetTTTK;
END;
/
CREATE OR REPLACE PACKAGE BODY TTTK_PKG IS
PROCEDURE GetTTTK AS
TaiKhoan varchar(30);
tenPhong varchar(30);
tenChucVu varchar(30);
tenMaNV varchar(10);
BEGIN
TaiKhoan := LOWER(SYS_CONTEXT('USERENV','SESSION_USER'));
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetTaiKhoan',TaiKhoan);
if (TaiKhoan = 'nv001') then
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetChucVu','Giam doc');
else
if (TaiKhoan = 'nv002') then
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetChucVu','Truong phong');
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetPhong','Kinh doanh');
else
if (TaiKhoan = 'nv006') then
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetChucVu','Truong phong');
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetPhong','Ky thuat');
else
DBMS_SESSION.set_context('ThongTinTaiKhoan','GetChucVu','Nhan vien');
end if;
end if;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END GetTTTK;
END;
/
CREATE OR REPLACE TRIGGER RangBuocTTTK AFTER LOGON ON DATABASE
BEGIN QuanTriVPD.TTTK_PKG.GetTTTK;
EXCEPTION WHEN NO_DATA_FOUND
THEN NULL;
END;
/
then:
CREATE OR REPLACE FUNCTION Select_Nhanvien(
schema_p IN VARCHAR2,
table_p IN VARCHAR2)
RETURN VARCHAR2
AS
getChucVu varchar(50);
trave varchar2(1000);
BEGIN
SELECT SYS_CONTEXT('ThongTinTaiKhoan','GetChucVu') into getChucVu FROM DUAL;
trave := '1=2';
if (getChucVu = 'Giam doc') then
trave := NULL;
else
if (getChucVu = 'Truong phong') then
trave :='Phong=(SELECT SYS_CONTEXT(''ThongTinTaiKhoan'',''GetPhong'') FROM DUAL)';
else
trave :='TenTaiKhoan=(SELECT SYS_CONTEXT(''ThongTinTaiKhoan'',''GetTaiKhoan'') FROM DUAL)';
end if;
end if;
RETURN trave;
END;
/
BEGIN
DBMS_RLS.ADD_POLICY (
object_schema => 'QuanTriDL',
object_name => 'NhanVien2',
policy_name => 'VPD_Select_Nhanvien',
function_schema => 'QuanTriVPD',
policy_function => 'Select_Nhanvien',
statement_types => 'SELECT');
END;
/
When connecting as nv001, nv002, nv006 it's OK. But connecting another user:
ORA-28113: policy predicate has error
Why does it cause that error?
(year old question but since I stumbled across it I'll go ahead and answer it for anyone else...)
ORA-28113 just means that when your policy function returned a where clause, the resulting SQL had some error. You can get details by looking at the trace file. Also, try:
select Select_Nhanvien('myschema','mytable') from dual;
And then append the results to a WHERE clause like this:
SELECT * FROM MYTABLE WHERE <results from above>;
And then you should see the root cause. I'm guessing in the case above the 'other user' didn't have either the sys_context variables required to build the where clause, or access to the login trigger.
As a side note, another problem you can run into here is circular reference when your policy function references its own table - ideally I would expect a policy function to bypass itself within the policy function so you can do NOT EXISTS, etc but it doesn't seem to work that way.
Related
I am attempting to pass a java array into my stored procedure and updating records with the values in the array. Currently when I attempt to execute and test the stored procedure I am running into
Error:ORA-06531: Reference to uninitialized collection.
Can anybody push me in the right direction or help me clean up my code. Below is the package spec followed the body.
CREATE OR REPLACE package AOMS.test_array1 as
type t1 is record (
s1 varchar2(1),
i_part_no varchar2(20),
i_itc varchar2(20),
s2 varchar2(1),
l_part_no varchar2(20));
type tab1 is table of t1 ;
tab2 tab1;
Here is the body.
CREATE OR REPLACE PACKAGE BODY AOMS.TEST_ARRAY1 AS
I_ARRAY varchar2(1000);
PROCEDURE test_array2(i_array IN tab2%TYPE) AS
l_s1 VARCHAR2(50);
l_part_no1 VARCHAR2(50);
l_itc varchar2(50);
l_s2 varchar2(50);
l_part_no2 varchar2(50);
BEGIN
FOR x IN i_array.first .. i_array.last
LOOP
l_s1 := i_array(x).s1;
l_part_no1 := i_array(x).i_part_no;
l_itc := i_array(x).i_itc;
l_s2 := i_array(x).s2;
l_part_no2 := i_array(x).l_part_no;
UPDATE replacement_parts
SET frst_src = l_s1,
frst_part_no = l_part_no1,
ITC = l_itc,
last_src = l_s2,
last_part_no = l_part_no2
WHERE
frst_src = 'P'
AND frst_part_no = '96424447 ';
COMMIT;
END LOOP;
END test_array2;
END test_array1;
/
I'm using Toad so when I call the procedure I just right click and execute and enter in my params. Here is the anonymous block code that gets generated when I attempt to execute.
DECLARE
I_ARRAY AOMS.TEST_ARRAY1.tab2%type;
BEGIN
-- I_ARRAY := NULL; Modify the code to initialize this parameter
AOMS.TEST_ARRAY1.TEST_ARRAY2 ( I_ARRAY );
COMMIT;
END;
You have some issues both in the package and in the procedure call.
This should work:
CREATE OR REPLACE PACKAGE test_array1 AS
TYPE t1 IS RECORD
(
s1 VARCHAR2(1),
i_part_no VARCHAR2(20),
i_itc VARCHAR2(20),
s2 VARCHAR2(1),
l_part_no VARCHAR2(20)
);
TYPE tab1 IS TABLE OF t1;
tab2 tab1;
PROCEDURE test_array2(i_array IN tab1);
END test_array1;
CREATE OR REPLACE PACKAGE BODY TEST_ARRAY1 AS
I_ARRAY VARCHAR2(1000);
PROCEDURE test_array2(i_array IN tab1) IS
l_s1 VARCHAR2(50);
l_part_no1 VARCHAR2(50);
l_itc VARCHAR2(50);
l_s2 VARCHAR2(50);
l_part_no2 VARCHAR2(50);
BEGIN
IF i_array.COUNT > 0
THEN
FOR x IN i_array.FIRST .. i_array.LAST
LOOP
l_s1 := i_array(x).s1;
l_part_no1 := i_array(x).i_part_no;
l_itc := i_array(x).i_itc;
l_s2 := i_array(x).s2;
l_part_no2 := i_array(x).l_part_no;
UPDATE replacement_parts
SET frst_src = l_s1,
frst_part_no = l_part_no1,
ITC = l_itc,
last_src = l_s2,
last_part_no = l_part_no2
WHERE frst_src = 'P'
AND frst_part_no = '96424447 ';
COMMIT;
END LOOP;
END IF;
END test_array2;
END test_array1;
/
The call:
DECLARE
I_ARRAY TEST_ARRAY1.tab1;
BEGIN
I_ARRAY := TEST_ARRAY1.tab1();
TEST_ARRAY1.TEST_ARRAY2 ( I_ARRAY );
COMMIT;
END;
The changes I made:
you define a type in your package, then use something like variable%type to declare the procedure, while you can simply use the type.
in the package, while scanning a collection, it's better to check if the collection has values before trying to use collection.first. Trying to access the .first on an empty collection can lead to an issue.
in the caller, you need to initialize the collection the way I showed to avoid the error you are having
As an aside, you should better try to use more explanatory names for variables, types, procedures, packages to avoid confusion between different objects.
Another thing: you have a commit inside a loop; this means that, keeping aside performances, if the first, say, 3 records are updated and then you have an error, you commit 3 updates; is this really what you need? Also, this way the commit in the caller is unuseful.
CREATE PROCEDURE [dbo].[spTest]
#Pozitii varchar(max),
#NrZile int
AS
set #Pozitii = SUBSTRING(#Pozitii,0,LEn(#Pozitii))
CREATE TABLE #Pozitii (part varchar(20) null)
INSERT INTO #Pozitii(part)
SELECT part
FROM dbo.SDF_SplitString(#Pozitii,',')
if exists (SELECT * FROM #Pozitii)
RAISERROR('asdf',16,-1)
else RAISERROR('else',16,-1)
So runing this SP in SQL like this
exec [spTest] '11,12,13,',1
Returns:
(3 row(s) affected)
Msg 50000, Level 16, State 1, Procedure spTest, Line 27
asdf
Now if I run my procedure in delphi ( using an ADO object)
procedure TframePlanificatorPozitieComanda.Button5Click(Sender: TObject);
begin
try
with dm.spTest do
begin
Close;
Parameters.ParambyName('#Pozitii').Value := '11,12,13,';
Parameters.ParambyName('#NrZile').Value := 1;
ExecProc;
end;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
This code is not raising any errors?Any ideas why?
Have you tried adding:
SET NOCOUNT ON;
to your stored procedure? I think the exception is in a second resultset and is getting hidden by the first select result count.
The error is raising in sql-server.. but in delphi is not .. because there the procedure is already executed..
But in Delphi you can check if the procedure is run successfully or not regards what the result of the procedure (some of procedures have no output parameters).
In delphi please check your adostoredprocedure.parameters[0] like:
showmessage(vartostr(self.ADOStoredProc1.Parameters[0].Value))
.
if the result <> 0 that means error.
Using Sql Server 2014, I don't get the behaviour you describe.
I have a stored proc on the server defined as
CREATE PROCEDURE spRaiseError(#AnError int)
AS
BEGIN
declare #Msg Char(20)
if #AnError > 0
begin
Select #Msg = 'MyError ' + convert(Char(8), #AnError)
RaisError(#Msg, 16, -1)
end
else
select 1
END
I have a minimalist D7 Ado project with a TAdoConnection, TAdoQuery, TDataSource and TDBGrid connected up as you'd expect, a TEdit and a TButton.
Using this code
procedure TForm1.Button1Click(Sender: TObject);
var
S : String;
ErrorCount : Integer;
begin
S := 'exec spRaiseError ' + Edit1.Text;
AdoQuery1.SQL.Text := S;
try
AdoQuery1.Open;
except
end;
ErrorCount := AdoConnection1.Errors.Count;
Caption := IntToStr(ErrorCount);
end;
, if the number in Edit1 is > 0 I get only the error number on the form's caption, whereas if it contains 0 I see the value 1 in the DBGrid.
Btw, with "Stop on language exceptions" checked in the D7 debugger settings, without the try/except around AdoQuery1.Open, the debugger sees and catches the exception from the server.
Anyway, the take-home message from this answer is that you can use the TAdoConnection's Errors collection to detect whether there was an error and, if there was, you can get more information from it. See the Delphi OLH amd MS Ado documentation for more info about the Errors collection of TAdoConnection and other Ado-based Delphi classes.
I am trying to format data returned from a cursor to JSON by looping through the records and columns without having to explicitly call on each column name. From what I've researched this vary well may not be a simple task or at least as simple as I'm trying to make it. I'm wondering if anyone else has tried a similar approach and if they had any luck.
declare
type type_cur_tab is table of employees%rowtype
index by PLS_integer;
type type_col_tab is table of varchar2(1000)
index by binary_integer;
tbl_rec type_cur_tab;
tbl_col type_col_tab;
begin
select * BULK COLLECT INTO tbl_rec
from employees;
select column_name BULK COLLECT INTO tbl_col
from all_tab_columns
where UPPER(table_name) = 'EMPLOYEES';
for i IN 1..tbl_rec.COUNT Loop
for j IN 1..tbl_col.count Loop
dbms_output.put_line(tbl_rec(i).tbl_col(j));
end loop;
end loop;
end;
It throws an error saying 'tbl_col' must be declared. I'm sure this is bc it's looking for 'tbl_col' listed inside 'tbl_rec'. Any help is greatly appreciated.
NOTE: I'm aware of the built in JSON conversion but I haven't been able to get it to as fast as I'd like so I'm trying to loop through and add the appropriate formatting along the way.
It is impossible to specify field of tbl_rec(i) in this manner.
Try this:
declare
v_cur sys_refcursor;
col_cnt number;
desc_t dbms_sql.desc_tab;
c number;
vVarchar varchar2(32000);
vNumber number;
vDate date;
v_result clob:='';
rn number:=0;
begin
--Any sql query or pass v_cur as input parameter in function on procedure
open v_cur for
select * from dual;
--------
c:=dbms_sql.to_cursor_number(v_cur);
dbms_sql.describe_columns(c => c, col_cnt => col_cnt, desc_t => desc_t);
for i in 1 .. col_cnt
loop
case desc_t(i).col_type
when dbms_types.TYPECODE_DATE then
dbms_sql.define_column(c, i ,vDate);
when dbms_types.TYPECODE_NUMBER then
dbms_sql.define_column(c, i ,vNumber);
else
dbms_sql.define_column(c, i ,vVarchar,32000);
end case;
end loop;
v_result:='{"rows":{ "row": [';
while (dbms_sql.fetch_rows(c)>0)
loop
if rn > 1 then v_result:=v_result||','; end if;
v_result:=v_result||'{';
for i in 1 .. col_cnt
loop
if (i>1) then v_result:=v_result||','; end if;
case desc_t(i).col_type
--Date
when dbms_types.typecode_date then
dbms_sql.column_value(c,i,vDate);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||to_char(vDate,'dd.mm.yyyy hh24:mi')||'"';
--Number
when dbms_types.typecode_number then
dbms_sql.column_value(c,i,vNumber);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||to_char(vNumber)||'"';
--Varchar - default
else
dbms_sql.column_value(c,i,vVarchar);
v_result:=v_result||' "'||desc_t(i).col_name||'" :"'||vVarchar||'"';
end case;
end loop;
v_result:=v_result||'}';
end loop;
v_result:=v_result||']}}';
dbms_output.put_line (v_result);
end;
Also you can generate XML from ref cursor with DBMS_XMLGEN package and then translate xml into json with xslt transformation.
This is going to be a difficult question to get answered which is why for 3 days that I have worked on this package (my first package ever) I have been hesitant to ask.
Below is the layout for the spec and body of my package. Before you look at that here is what I am trying to accomplish. I AM CLOSE TO FINISHING so there is no need to fear that this question is not worth your time.
You may see a few of my personal notes to self in the code as well.
My code is incomplete and currently isn't compiling but before it ceased to compile I can tell you it did not work either. The DROP and CREATE procedures work. NO NEED TO TOUCH THOSE. My main issues are the LOG_PROC, my EXCEPTIONS, my ARCHIVE_ALL_TABLES... as far as I know
Here is what I am trying to do:
Create a package that could be used to ‘archive’ the newly created tables into archive tables in the format “TEST_TABLE_A_13AUG2012”. This package will use a view I created called VW_TEST_TABLES which has this data:
TEST_TABLE_A
TEST_TABLE_B
TEST_TABLE_C
TEST_TABLE_D
This package will need to drop all previously archived tables before it creates new ones. As such, my package will need to have both DROP_ARCHIVE_TABLES and CREATE_ARCHIVE_TABLES procedures within it. In addition to the DROP and CREATE procedures, my package has a main procedure, called ARCHIVE_ALL_TABLES. This is the procedure that would need to be called (for instance by the scheduler) and do the actual archiving. I need to incorporate proper exception handling in these procedures. (e.g. don’t care if the table does not exist when I go to drop it).
Finally, in order to properly track each archival run, I want to build a logging mechanism. To accomplish this, I built a table in my schema called TEST_PACKAGE_LOG_TBL. This table should has the following columns: ARCHIVE_DATE (DATE), TABLE_NAME (VARCHAR2(30)), STATUS_CODE(VARCHAR2(1)), COMMENTS (VARCHAR2(4000)). For each table I archive, I want to log the date, the table name, either ‘S’ for success or ‘E’ for error and, if I encounter an error in the drop or creation of the table, what the SQLERRM was should be displayed.
Finally, my ARCHIVE_ALL_TABLES procedure should check this log table when it is finishing in order to determine if any tables were not archived properly. I created a function ERRORS_FOUND (return boolean) that accepts one IN parameter (today’s date) and checks the log table for errors. If this function returns true, my ARCHIVE_ALL_TABLES procedure should account for this and ‘notify an administrator’ (For now I am leaving this untouched but eventually it will simply account for this with a comment stating that I would notify an admin and place NULL; in the if then end block.)
To summarize, my package structure must contain (at minimum) the following procedures:
ARCHIVE_ALL_TABLES,
DROP_ARCHIVE_TABLE,
CREATE_ARCHIVE_TABLE,
ERRORS_FOUND (function)
--package specification
CREATE OR REPLACE PACKAGE PKG_TEST_TABLES IS
-- Author :
-- Created : 8/14/2012 8:40:18 AM
-- Purpose : For storing procedures to drop, create, and archive new tables
/* Package specification*/
PROCEDURE ARCHIVE_ALL_TABLES;
PROCEDURE DROP_ARCHIVE_TABLES; --2nd
PROCEDURE CREATE_ARCHIVE_TABLES; --1st and call both from archive tables first assuming it works
PROCEDURE LOG_PROC
(
P_PROCESS_START_TIMESTAMP TIMESTAMP
,P_ARCHIVE_DATE DATE
,P_TABLE_NAME VARCHAR2
,P_STATUS_CODE VARCHAR2
,P_COMMENTS VARCHAR2
);
PROCEDURE W(STR VARCHAR2);
FUNCTION ERRORS_FOUND(P_JOB_RUN_TIMESTAMP TIMESTAMP) RETURN BOOLEAN;
END PKG_TEST_TABLES;
--package body
CREATE OR REPLACE PACKAGE BODY PKG_TEST_TABLES IS
/* Package body*/
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
/* Procedure 'W' is a wrapper for DBMS output. Placed at top of package to make globally available*/
PROCEDURE W(STR VARCHAR2) IS
L_STRING VARCHAR2(4000);
BEGIN
L_STRING := STR;
DBMS_OUTPUT.PUT_LINE(STR);
END;
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE DROP_ARCHIVE_TABLES AS
/* Purpose: For dropping previously archived tables so that new ones can be created */
L_NO_TABLES_TO_DROP EXCEPTION;
BEGIN
/* Will drop previously archived tables not current ones*/
FOR STMT IN (SELECT 'DROP TABLE mySchema.' || TABLE_NAME AS STR
FROM VW_TEST_TABLES
WHERE REGEXP_LIKE(TABLE_NAME, '.+[0...9]'))
LOOP
EXECUTE IMMEDIATE STMT.STR; --so that I don't need ';' at the end of each dynamically created SQL
END LOOP;
W('Done'); --put the W back in here when in package scope
EXCEPTION
WHEN L_NO_TABLES_TO_DROP THEN
NULL;
END;
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE CREATE_ARCHIVE_TABLES AS
/* purpose: setting variable to equal the creation of my 4 tables. Recreating the archive tables */
L_NO_TABLES_TO_CREATE EXCEPTION;
L_TABLES_NOT_SUCCESSFULLY_CREATED EXCEPTION;
BEGIN
FOR STMT IN (SELECT 'CREATE TABLE ' || TABLE_NAME || '_' || TO_CHAR(SYSDATE, 'ddMONyyyy') || ' AS SELECT * FROM ' || TABLE_NAME AS STR
FROM VW_TEST_TABLES)
--LOG_PROC( ,TO_CHAR(SYSDATE, 'ddMONyyyy') , TABLE_NAME ,'E' ,'TABLE ARCHIVED SUCCESSFULLY')
LOOP
--DBMS_OUTPUT.PUT_LINE(STMT.STR); --want to do a dbms output first before using 'execute immediate'. Hit test, and run it
EXECUTE IMMEDIATE STMT.STR; --so that I don't need ';' at the end of each dynamically created SQL
END LOOP;
-- DBMS_OUTPUT.PUT_LINE('Done'); --put the W back in here when in package scope
EXCEPTION
WHEN L_NO_TABLES_TO_CREATE THEN
NULL; --logging can go here
--can call logging procedure here for dml don't need execute immediate, just use insert into
WHEN L_TABLES_NOT_SUCCESSFULLY_CREATED THEN
NULL; --W('ERROR: ' || SQLERRM);
END;
--PROCEDURE IS NOT CREATING TABLES YET
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
PROCEDURE LOG_PROC(P_PROCESS_START_TIMESTAMP TIMESTAMP, P_ARCHIVE_DATE DATE, P_TABLE_NAME VARCHAR2, P_STATUS_CODE VARCHAR2, P_COMMENTS VARCHAR2) AS
PRAGMA AUTONOMOUS_TRANSACTION;
/* variables */
L_PROCESS_START_TIMESTAMP TIMESTAMP; L_ARCHIVE_DATE DATE; L_TABLE_NAME VARCHAR2(4000); L_STATUS_CODE VARCHAR2(1); L_COMMENTS VARCHAR2(4000);
BEGIN
L_PROCESS_START_TIMESTAMP := P_PROCESS_START_TIMESTAMP; L_ARCHIVE_DATE := P_ARCHIVE_DATE; L_TABLE_NAME := P_TABLE_NAME; L_STATUS_CODE := P_STATUS_CODE; L_COMMENTS := P_COMMENTS;
INSERT INTO TEST_PACKAGE_LOG_TBL(PROCESS_START_TIMESTAMP, ARCHIVE_DATE, TABLE_NAME, STATUS_CODE, COMMENTS) VALUES(L_PROCESS_START_TIMESTAMP, L_ARCHIVE_DATE, L_TABLE_NAME, L_STATUS_CODE, L_COMMENTS);
RETURN;
END;
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
FUNCTION ERRORS_FOUND(P_JOB_RUN_TIMESTAMP TIMESTAMP) RETURN BOOLEAN IS
L_JOB_RUN_TIMESTAMP TIMESTAMP; ERROR_COUNT NUMBER; ERROR_BOOL BOOLEAN;
BEGIN
L_JOB_RUN_TIMESTAMP := P_JOB_RUN_TIMESTAMP;
SELECT COUNT(*) INTO ERROR_COUNT FROM TEST_PACKAGE_LOG_TBL WHERE STATUS_CODE = 'E' AND PROCESS_START_TIMESTAMP = L_JOB_RUN_TIMESTAMP; IF ERROR_COUNT > 0 THEN ERROR_BOOL := TRUE; ELSE ERROR_BOOL := FALSE;
END IF;
RETURN ERROR_BOOL;
END;
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
PROCEDURE ARCHIVE_ALL_TABLES AS
/*
Original Author:
Created Date: 13-Aug-2012
Purpose: To drop all tables before recreating and archiving newly created tables
NOTE: in package - do not use create or replace and 'as' would be alternative to 'is'
*/
/*variables*/
L_DROP_ARCHIVE_TABLES VARCHAR2(4000); L_SQL_CREATE_ARCHIVED_TABLES VARCHAR2(4000); L_PREVENT_SQL_INJECTION
EXCEPTION
;
--L_NOTIFY_ADMINISTRATOR VARCHAR(4000); --TO BE DONE AT A LATER TIME
BEGIN
RETURN;
EXCEPTION
WHEN L_PREVENT_SQL_INJECTION THEN NULL;
WHEN OTHERS THEN W('ERROR: ' || SQLERRM);
END;
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
------------------------------------------------------------------------------------------------- ------------------------------------------------------------------
BEGIN
-- Initialization
/*archive all tables is like my 'driver' that calls drop then create while logging to the table. Pragma_auto prevents a rollback which would prevent table logging
FIRST: This package will need to drop all previously archived tables before it creates new ones. call drop func first*/
/* calling ARCHIVE_ALL_TABLES */
BEGIN
-- Call the function
NULL;
END;
RETURN;
END PKG_TEST_TABLES;
Your LOG_PROC is an autonomous transaction, so you need a COMMIT in there.
You define a number of exceptions, but you don't RAISE them anywhere in your code. For example, I'm guessing you need something like this:
PROCEDURE CREATE_ARCHIVE_TABLES AS
L_NO_TABLES_TO_CREATE EXCEPTION;
l_count number := 0;
BEGIN
FOR STMT IN (SELECT ...)
LOOP
l_count := l_count + 1;
EXECUTE IMMEDIATE STMT.STR;
END LOOP;
IF l_count = 0 THEN
RAISE L_NO_TABLES_TO_CREATE;
END IF;
EXCEPTION
WHEN L_NO_TABLES_TO_CREATE THEN
NULL; --logging can go here
END;
Some of my MS SQL stored procedures produce messages using the 'print' command. In my Delphi 2007 application, which connects to MS SQL using TADOConnection, how can I view the output of those 'print' commands?
Key requirements:
1) I can't run the query more than once; it might be updating things.
2) I need to see the 'print' results even if datasets are returned.
That was an interesting one...
The OnInfoMessage event from the ADOConnection works but the Devil is in the details!
Main points:
use CursorLocation = clUseServer instead of the default clUseClient.
use Open and not ExecProc with your ADOStoredProc.
use NextRecordset from the current one to get the following, but be sure to check you have one open.
use SET NOCOUNT = ON in your stored procedure.
SQL side: your stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FG_TEST]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[FG_TEST]
GO
-- =============================================
-- Author: François
-- Description: test multi ADO with info
-- =============================================
CREATE PROCEDURE FG_TEST
AS
BEGIN
-- SET NOCOUNT ON absolutely NEEDED
SET NOCOUNT ON;
PRINT '*** start ***'
SELECT 'one' as Set1Field1
PRINT '*** done once ***'
SELECT 'two' as Set2Field2
PRINT '*** done again ***'
SELECT 'three' as Set3Field3
PRINT '***finish ***'
END
GO
Delphi side:
Create a new VCL Forms Application.
Put a Memo and a Button in your Form.
Copy the following text, change the Catalog and Data Source and Paste it onto your Form
object ADOConnection1: TADOConnection
ConnectionString =
'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security In' +
'fo=False;Initial Catalog=xxxYOURxxxDBxxx;Data Source=xxxYOURxxxSERVERxxx'
CursorLocation = clUseServer
LoginPrompt = False
Provider = 'SQLOLEDB.1'
OnInfoMessage = ADOConnection1InfoMessage
Left = 24
Top = 216
end
object ADOStoredProc1: TADOStoredProc
Connection = ADOConnection1
CursorLocation = clUseServer
ProcedureName = 'FG_TEST;1'
Parameters = <>
Left = 24
Top = 264
end
In the OnInfoMessage of the ADOConnection put
Memo1.Lines.Add(Error.Description);
For the ButtonClick, paste this code
procedure TForm1.Button1Click(Sender: TObject);
const
adStateOpen = $00000001; // or defined in ADOInt
var
I: Integer;
ARecordSet: _Recordset;
begin
Memo1.Lines.Add('==========================');
ADOStoredProc1.Open; // not ExecProc !!!!!
ARecordSet := ADOStoredProc1.Recordset;
while Assigned(ARecordSet) do
begin
// do whatever with current RecordSet
while not ADOStoredProc1.Eof do
begin
Memo1.Lines.Add(ADOStoredProc1.Fields[0].FieldName + ': ' + ADOStoredProc1.Fields[0].Value);
ADOStoredProc1.Next;
end;
// switch to subsequent RecordSet if any
ARecordSet := ADOStoredProc1.NextRecordset(I);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
ADOStoredProc1.Close;
end;
In .net's connection classes there is an event called InfoMessage. In a handler for this event you can retrieve the InfoMessage (print statements) from the event args.
I believe Delphi has a similar event called "OnInfoMessage" that would help you.
I dont think that is possible.
You might use a temp table to dump print statements and return it alongwith results.
Some enhancements to Francois' code (as tested with DXE2) to cater for multiple print statements and the results from a variable number of selects. The changes are subtle.
procedure TForm1.ADOConnection1InfoMessage(Connection: TADOConnection;
const Error: Error; var EventStatus: TEventStatus);
var
i: integer;
begin
// show ALL print statements
for i := 0 to AdoConnection1.Errors.Count - 1 do
begin
// was: cxMemo1.Lines.Add(Error.Description);
cxMemo1.Lines.Add(
ADOConnection1.Errors.Item[i].Description);
end;
end;
procedure TForm1.cxButton1Click(Sender: TObject);
const
adStateOpen = $00000001; // or uses ADOInt
var
records: Integer;
ARecordSet: _RecordSet;
begin
cxMemo1.Lines.Add('==========================');
ADOStoredProc1.Open;
try
ARecordSet := ADOStoredProc1.RecordSet; // initial fetch
while Assigned(ARecordSet) do
begin
// assign the recordset to a DataSets recordset to traverse
AdoDataSet1.Recordset := ARecordSet;
// do whatever with current ARecordSet
while not ADODataSet1.eof do
begin
cxMemo1.Lines.Add(ADODataSet1.Fields[0].FieldName +
': ' + ADODataSet1.Fields[0].Value);
AdoDataSet1.Next;
end;
// fetch next recordset if there is one
ARecordSet := ADOStoredProc1.NextRecordSet(records);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
finally
ADOStoredProc1.Close;
end;
end;