Assistance needed for a first timer in package creation - database

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;

Related

Oracle: Insert only unique records into custom record in compound trigger

I am having a compound trigger in the following format. I need to ensure that my custom declaration ints_rows takes only "unique rows". Meaning if the ints_rows already has a record similar to what is being inserted, it should ignore it. More like a SET data structure. How do I do it in oracle? (I am pretty new to oracle, hence I am not great at syntax) I think I have to either change the BULK COLLECT statement or the declaration of int_records but I might be wrong. Any help/hints are much appreciated.
This is my compound trigger code.
CREATE OR REPLACE
TRIGGER MY_COMPOUND_TRIGGER
FOR UPDATE OF some_random_column ON some_random_table
COMPOUND TRIGGER
TYPE int_records IS RECORD (
column_one another_table.column_one%TYPE,
column_two another_table.column_two%TYPE
);
TYPE row_list IS TABLE OF int_records INDEX BY simple_integer;
ints_rows row_list;
BEFORE STATEMENT IS
BEGIN
ints_rows.delete;
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
SELECT column_one, column_two BULK COLLECT INTO ints_rows
FROM some_table_x WHERE some_col_id=:OLD.some_col_id;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN 1 .. ints_rows.COUNT LOOP
-- DO SOMETHING
END LOOP;
auctions_rows.delete;
END AFTER STATEMENT;
END;
This statement should not be getting duplicates at all.
SELECT column_one, column_two BULK COLLECT INTO ints_rows
FROM some_table_x WHERE some_col_id=:OLD.some_col_id;
In the AFTER EACH ROW part you overwrite entire ints_rows. In principle it could be this one:
CREATE TABLE SOME_RANDOM_TABLE (some_random_column NUMBER, some_col_id NUMBER);
CREATE TABLE ANOTHER_TABLE (column_one NUMBER, column_two NUMBER);
CREATE TABLE SOME_TABLE_X (some_col_id NUMBER, column_one NUMBER, column_two NUMBER);
CREATE OR REPLACE TYPE int_records AS OBJECT (
column_one NUMBER,
column_two NUMBER,
MAP MEMBER FUNCTION getId RETURN VARCHAR2
);
CREATE OR REPLACE TYPE BODY int_records AS
MAP MEMBER FUNCTION getId RETURN VARCHAR2 IS
BEGIN
RETURN column_one ||','|| column_two;
END getId;
END;
/
CREATE OR REPLACE TYPE row_list IS TABLE OF int_records;
CREATE OR REPLACE TRIGGER MY_COMPOUND_TRIGGER
FOR UPDATE OF some_random_column ON SOME_RANDOM_TABLE
COMPOUND TRIGGER
ints_rows row_list;
BEFORE STATEMENT IS
BEGIN
ints_rows := row_list();
END BEFORE STATEMENT;
AFTER EACH ROW IS
int_row row_list;
BEGIN
SELECT int_records(column_one, column_two)
BULK COLLECT INTO int_row
FROM SOME_TABLE_X x
WHERE x.some_col_id = :OLD.some_col_id;
ints_rows := ints_rows MULTISET UNION DISTINCT int_row;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN 1 .. ints_rows.COUNT LOOP
NULL;
END LOOP;
END AFTER STATEMENT;
END;
Please test and let us know if it does not work. You may make the records distinct manually with a loop or you need to implement a MAP MEMBER FUNCTION for the RECORD.

How can print out the current time with DBMS_OUTPUT.PUT_LINE?

I use the following Code to execute an export data pump.
set serveroutput on;
DECLARE
ind NUMBER; -- Loop index
h1 NUMBER; -- Data Pump job handle
percent_done NUMBER; -- Percentage of job complete
job_state VARCHAR2(30); -- To keep track of job state
le ku$_LogEntry; -- For WIP and error messages
js ku$_JobStatus; -- The job status from get_status
jd ku$_JobDesc; -- The job description from get_status
sts ku$_Status; -- The status object returned by get_status
>>>>>>>>>>>>>>v_systimestamp TIMESTAMP := SYSTIMESTAMP;<<<<<<<<<<<<<<
BEGIN
h1 := DBMS_DATAPUMP.OPEN('EXPORT','SCHEMA',NULL,'EXAMPLE3','LATEST');
DBMS_DATAPUMP.ADD_FILE(h1, 'dumpfile.dmp', 'EXPORT_DIRECTORY', NULL, DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE, 1);
DBMS_DATAPUMP.METADATA_FILTER(h1,'SCHEMA_EXPR','IN (''SchemaName'')');
DBMS_DATAPUMP.START_JOB(h1);
percent_done := 0;
job_state := 'UNDEFINED';
while (job_state != 'COMPLETED') and (job_state != 'STOPPED') loop
DBMS_OUTPUT.PUT_LINE(v_systimestamp);
dbms_datapump.get_status(h1,
dbms_datapump.ku$_status_job_error +
dbms_datapump.ku$_status_job_status +
dbms_datapump.ku$_status_wip,-1,job_state,sts);
js := sts.job_status;
-- If the percentage done changed, display the new value.
if js.percent_done != percent_done
then
dbms_output.put_line('*** Job percent done = ' ||
to_char(js.percent_done));
percent_done := js.percent_done;
end if;
-- If any work-in-progress (WIP) or error messages were received for the job,
-- display them.
if (bitand(sts.mask,dbms_datapump.ku$_status_wip) != 0)
then
le := sts.wip;
else
if (bitand(sts.mask,dbms_datapump.ku$_status_job_error) != 0)
then
le := sts.error;
else
le := null;
end if;
end if;
if le is not null
then
ind := le.FIRST;
while ind is not null loop
>>>>>>>>>>>>>>DBMS_OUTPUT.PUT_LINE(v_systimestamp);<<<<<<<<<<<<<<
dbms_output.put_line(le(ind).LogText);
ind := le.NEXT(ind);
end loop;
end if;
end loop;
-- Indicate that the job finished and detach from it.
dbms_output.put_line('Job has completed');
dbms_output.put_line('Final job state = ' || job_state);
dbms_datapump.detach(h1);
END;
The problem is that the export takes too long. It takes 25 minutes with this SQL Code. The Size of the schema is 1.8 GB.
And I would like to find out how much time individual steps take. That's why I want to insert a timestamp in after each process step. Then I can see how long individual steps need.
I have marked the code for the timestamp with (>>>> <<<<) in the code.
The timestamp is not updating the time. I need the CURRENT time after each process. Can you help me?
In your code, you are setting the value of v_systimestamp at the beginning of your script - this will not change throughout the script run. You can reset the value before you log like:
v_systimestamp TIMESTAMP := SYSTIMESTAMP
DBMS_OUTPUT.PUT_LINE(v_systimestamp)
or doing something like the following before and after each section you want to monitor (no variable required):
DBMS_OUTPUT.PUT_LINE('Time Started: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('Time Ended: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS'));
Here example 'CURRENT_TIMESTAMP'
curDateTime TIMESTAMP := CURRENT_TIMESTAMP ;
DBMS_OUTPUT.PUT_LINE('CUR TIME '|| curDateTime);
If a procedure you run takes a long time, DBMS_OUTPUT.PUT_LINE won't help much. Well, it will display something, but - as you use a loop, depending on number of iterations, DBMS_OUTPUT might run out of buffer size or exceed number of visible lines in SQL*Plus (if you use it) and you'll lose part of the output.
Besides, you won't see a single letter of the DBMS_OUTPUT.PUT_LINE until the procedure finishes.
Therefore, I'd suggest you to use another approach - a simple logging which requires a table, a sequence and an (autonomous transaction) procedure. Here's the script:
CREATE TABLE a1_log
(
id NUMBER,
datum DATE,
descr VARCHAR2 (500)
);
CREATE SEQUENCE seqlognap START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE PROCEDURE a1_p_log (par_descr IN VARCHAR2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO a1_log
SELECT seqlognap.NEXTVAL, SYSDATE, par_descr FROM DUAL;
COMMIT;
END a1_p_log;
Then put A1_P_LOG calls into your own procedure; something like
begin
a1_p_log('selecting from a large table');
select ... into ... from ...;
a1_p_log('entering the loop');
while ... loop
a1_p_log('doing something');
end loop;
a1_p_log('the end');
end;
Here's where the autonomous transaction matters - it'll commit inserts into the log table (without affecting the main transaction), so you can (in another session, of course) trace the execution, simply by repeatedly issuing
SELECT *
FROM a1_log
ORDER BY id;
It'll show (imaginary example):
1 22.05.2018 18:13:00 selecting from a large table
2 22.05.2018 18:20:07 entering the loop
3 22.05.2018 18:20:07 doing something
4 22.05.2018 18:20:08 doing something
5 22.05.2018 18:20:09 doing something
6 22.05.2018 18:20:10 the end
and you'll see that step 1 takes 7 minutes to execute, so - that's what you need to investigate. It means that you don't have to wait the main procedure to finish - cancel it and start working on the bottleneck. Once you fix it, run everything again.

PostgreSQL full-text search with arrays

I would like to implement full-text search within my application but I'm running into some roadblocks associated with my Array-type columns. How would one implement a psql trigger so that the when my "object" table is updated, each element (which are strings) of its array column is added to the tsvector column of my "search" table?
In Postgres 9.6 array_to_tsvector was added.
If you are dealing with same table you can write it something like this.
CREATE FUNCTION tsv_trigger() RETURNS trigger AS $$
begin
IF (TG_OP = 'INSERT') OR old.array_column <> new.array_column THEN
new.tsv := array_to_tsvector( new.array_column);
END IF;
return new;
end
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON my_table FOR EACH ROW EXECUTE PROCEDURE tsv_trigger();
If you are dealing with two tables than you need to write update
CREATE FUNCTION cross_tables_tsv_trigger() RETURNS trigger AS $$
begin
IF (TG_OP = 'INSERT') OR old.array_column <> new.array_column THEN
UPDATE search_table st
SET tsv = array_to_tsvector( new.array_column )
WHERE st.id = new.searchable_record_id
END IF;
# you can't return NULL because you'll break the chain
return new;
end
$$ LANGUAGE plpgsql;
Pay attention that it will differ from default to_tsvector( array_to_string() ) combination.
It goes without position numbers, and lowercase normalization so you can get a unexpected results.

Modify the metadata of a database

This is simple, i have a database that contains many tables and what i want to do is to add a default value to all the fields that represent a boolean (a char(1 byte)). So is there a way to (using a function) write some logic that uses the meta-data of the database and its tables to add that default value without iterating manually on each field in each table ?
Hope this is clear guys :)
Use the block given below for doing your task
DECLARE
LV_SQL VARCHAR2(4000);
CURSOR C_GET_COLUMNS IS
SELECT TABLE_NAME,COLUMN_NAME,NULLABLE,DATA_LENGTH,DATA_TYPE
FROM USER_TAB_COLUMNS
WHERE DATA_TYPE = 'CHAR'
AND DATA_LENGTH = 1;
BEGIN
FOR I IN C_GET_COLUMNS LOOP
LV_SQL := 'ALTER TABLE '||I.TABLE_NAME||' MODIFY '||I.COLUMN_NAME||' '||I.DATA_TYPE||'('||I.DATA_LENGTH||') DEFAULT '||CHR(39)||'Y'||CHR(39);
EXECUTE IMMEDIATE LV_SQL;
LV_SQL := 'UPDATE '||I.TABLE_NAME||' SET '||I.COLUMN_NAME||' = '||CHR(39)||'Y'||CHR(39)||' WHERE '||I.COLUMN_NAME||' IS NULL';
DBMS_OUTPUT.PUT_LINE(LV_SQL);
EXECUTE IMMEDIATE LV_SQL;
END LOOP;
END;

ORA-28113: policy predicate has error

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.

Resources