Data Consistency within the same Transaction - oracle

Any one can help on the below issue.
Thanks in advance.
I am updating a Table ACC_STATUS for Id 100
Current Value of Status Column is 0.
UPDATE ACC_STATUS
SET STATUS = 1
WHERE ID = 100;
After Update I am calling a Procedure, pro_do_other_things, which is in another Schema.
There is a SELECT From Table ACC_STATUS in this Procedure - pro_do_other_things. The result is not showing the updated value always. In some cases it shows the New Updated Value and in some other cases Old Value.
Please not that both Update Statement and Call to Procedure are happening in the Same Transaction as below.
BEGIN
UPDATE ACC_STATUS
SET STATUS = 1
WHERE ID = 100;
OTHER_SCHEMA.pro_do_other_things;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END;
Could anyone please explain why it is happening so?

By default the procedures are defined with definer rights (same as AUTHID DEFINER, see https://docs.oracle.com/database/121/DBSEG/dr_ir.htm).
It means they are executed with the privileges of the schema they are defined in.
If there is a table without schema referenced in such stored procedure, the table is resolved as table from the definer's schema.
In your case the OTHER_SCHEMA.pro_do_other_things makes select from OTHER_SCHEMA.acc_status, not from acc_status in your schema.
When you define the procedure as AUTHID CURRENT_USER, then a referenced table without schema will be resolved as a table from the invoker's schema.
In this case the OTHER_SCHEMA.pro_do_other_things would make select from acc_status in your schema.
Note: When you always want to reference the same table regardless the invoker, you have to use it explicitly with schema or you have to create and use a synonym.

Related

Oracle - using synonym as variable

i need to use synonym as variable in a block. I have 2 different schemas with same tables on them and job that switches between schemas making one active. Now I want to write a block checking which schema is active with ALL_SYNONYMS and using result as part of a query.
Here is example:
DECLARE
OWNER VARCHAR2(15);
BEGIN
SELECT TABLE_OWNER
INTO OWNER
FROM ALL_SYNONYMS
WHERE TABLE_NAME = 'MY_TABLE1';
SELECT *
FROM OWNER.MY_TABLE2 ;
END;
But I’m getting ORA-06550 table or view does not exist, and when i run query itself where i put value from ALL_SYNONYMS it returns result.
Any idea how to fix this?
Thanks
You are attempting using symptoms incorrectly. Synonyms are used so you do not need to know which is active. According to the documentation:
Synonyms provide both data independence and location transparency.
Synonyms permit applications to function without modification
regardless of which user owns the table or view and regardless of
which database holds the table or view.
You just use the synonym instead of the object itself.
create table evens( id integer generated always as identity
, val integer
) ;
create table odds( id integer generated always as identity
, val integer
) ;
insert all
when mod(val,2) = 0 then into evens(val)
when mod(val,2) = 1 then into odds(val)
select level val
from dual connect by level <= 10;
-- create the synonym then use it in Select;
create or replace synonym current_even_odd for evens;
select * from current_even_odd;
-- now change the synonym, then run the EXACT same query.
create or replace synonym current_even_odd for odds;
select * from current_even_odd;
In this case it is not quite without modification, you need to change the synonym, But it seems you are trying that already.
Note: You cannot create a synonym for a schema but must point it to a specific object.
I attempted a db<>fiddle for the above, but it appears it is having problems at the moment.
I agree with Belayer that the synonym should provide a layer of abstraction on your tables and your procedure shouldn't need to know what the schema is. But the "table or view does not exist" error is likely an issue related to privileges and definer's rights versus invoker's rights.
To directly reference an object in a procedure, the procedure's schema must have a direct grant to the table. However, an ad hoc query only needs a role with privileges on the object. This is why the SQL will work in your IDE but not in the procedure. Ensure the code that modifies objects and switches synonyms is granting privileges to both roles and directly to schemas.
If direct grants are not possible, you will need to modify the procedure to use AUTHID CURRENT_USER and change the SQL statements to use dynamic SQL - which can be a huge pain. For example:
create or replace procedure test_procedure authid current_user is
v_count number;
begin
execute immediate
q'[
select count(*)
from some_table
]'
into v_count;
end test_procedure;
/
If you really do need to manually switch between schemas, then you may want to consider using something like execute immediate 'alter session set current_schema=schema1'; in the procedure and using dynamic SQL for all of the querying.

How do you reference APEX Application Items in Trigger

I am creating a BEFORE DELETE trigger to insert records into an archive table that are deleted through an APEX form on another table. I want to update two columns "UPDATED_BY" and "UPDATED"_DATE" based on the SYSDATE and :APP_USER (APEX Application Item). I'm getting an error related to the :APP_USER (PLS-00049: bad bind variable 'APP_USER'). Is there a specific syntax I have to use to reference APEX app items when creating the trigger?
CREATE OR REPLACE TRIGGER "ARCHIVE_DELETED_RECORDS"
BEFORE
delete on "RECORD_TABLE"
for each row
begin
INSERT INTO DELETED_RECORD_TABLE (
STAGING_ID,
SOURCE_FILE_ID,
DELETION_DATE,
DELETED_BY,
DELETED_FROM)
SELECT
STAGING_ID,
SOURCE_FILE_ID,
SYSDATE,
:APP_USER,
'Record Table 1'
FROM RECORD_TABLE
end;
/
ALTER TRIGGER "ARCHIVE_DELETED_RECORDS" ENABLE;
You can use the V() function to refer directly to APEX bind variables like :APP_USER in a database trigger, or really in any PL/SQL outside of APEX. APEX also generally uses package state to store global variables which need to be accessed externally. See here for some discussion.
In your case, instead of :APP_USER, you could use either V('APP_USER') or APEX_APPLICATION.G_USER. Documentation is here.
Note that if records are deleted from your table outside of APEX (for example, by a DBA running delete from RECORD_TABLE), the DELETED_BY field will be null. So you might want something like:
COALESCE(V('APP_USER'), SYS_CONTEXT ('USERENV', 'OS_USER'), USER)
To prefer the APEX user if that's available, or the OS user if that's available, or finally the current schema user if nothing else is available.

existing state of package has been invalidated

I am facing this error.
I have two Schema Schema A and Schema B
Schema B contains a table my_table in which values are being Inserted.
There is also a triggger my_trigger written for my_table in schemaB for each row
CREATE OR REPLACE TRIGGER schemaB.my_trigger
ON schemaA.my_table
FOR EACH ROW
BEGIN
IF INSERTING THEN
schemaA.my_package.my_procedure (:NEW.field_A,NEW.field_B, :NEW.field_C);
END IF;
EXCEPTION
WHEN OTHERS THEN
Insert into my_log(DBMS_UTILITY.format_error_stack,sysdate);
END my_trigger;
/ AFTER INSERT
This trigger written on my_table of schemaB is calling a procedure which is present in Schema A.
However when the trigger is being fired I am getting the below error in my logs
ERROR: ORA-04061: existing state of package "schemaA.my_package" has been invalidated
ORA-04065: not executed, altered or dropped package "schemaA.my_package"
ORA-06508: PL/SQL: could not find program unit being called: "schemaA.my_package"
ORA-06512: at "schemaB.my_trigger", line 17 10/1/2015 6:38:07 PM
Also the procedure in schemaA is declared as PRAGMA_AUTONOMOUS_TRANSACTION
Is this some grants issue as i checked all the grants has been given, I have checked dependencies of the both trigger and procedure
and all seems to valid. Can you kindly help?
I have tried using Pragma serially_reusable in the calling package but still giving me same error
Many thanks
Possible issues you can have is:
The package/procedure you are calling is invalid
check this query whether you have an entry of your package or objects used in your package in this all_objects view
select * from all_objects where status = 'INVALID' and owner = 'SCHEMA_NAME';
Check your package is having global variables? if yes then check if those variable is not being changed by any other session
run below script to compile all the objects in your schema
begin
dbms_utility.compile_schema('SCHEMA_NAME',false);
end;
Last option if none of the above works then remove all the procedures/function from your package, add new function and try to run your function from the trigger. check if this works then your package is in special lock. after adding a new function it's state will be valid again and then you can add all your actual funcs/procs and remove the newly added one.

Can I create a trigger in which it creates a new user?

if it is possible... how can do that? i'm new in databases , please help me!
i'm using oracle and I want when create a new user when I insert a new row in a table "users"
As JGreenwell has mentioned, yes, you can do it, but is is not a good idea. The main problem is not the password in clear, is the transactional side effects.
Let's see the solution to password in clear:
It is easy to solve the problem of the password (do not store password in clear, encrypt it or hash it). If you have installed DMBS_CRYPTO and have privileges on it then you can create a procedure (I assume you have a table USERS with to columns: USERNAME and USERPASS):
CREATE OR REPLACE PROCEDURE MYCREATEUSER( NAME VARCHAR, PASS VARCHAR )
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'CREATE USER ' || NAME || ' IDENTIFIED BY "' || PASS || '"';
END;
and a trigger:
CREATE OR REPLACE TRIGGER TUSERS BEFORE INSERT ON USERS FOR EACH ROW
BEGIN
MYCREATEUSER(:NEW.USERNAME, :NEW.USERPASS);
:NEW.USERPASS := DBMS_CRYPTO.HASH(:NEW.USERPASS, DBMS_CRYPTO.HASH_SH1);
END;
Your insert statement uses the username and the password, the trigger creates the user, but before inserting the row it replaces the password with its SHA1 hash value. You must remember this when you query the table.
One example:
INSERT INTO USERS VALUES('Mary', 'Mary123');
To check:
SELECT COUNT(*)
FROM USERS
WHERE USERNAME = 'Mary' AND
USERPASS = DBMS_CRYPTO.HASH('Mary123', DBMS_CRYPTO.HASH_SH1);
If count = 1 then the user and password exists, but if count = 0 the user/password does not exists.
This example only works if: you have installed DBMS_CRYPTO and has enough privileges, and you have CREATE TABLE privileges (remember that when a procedure executes statements it only has explicit granted privileges, privileges that came from ROLES are disabled - so, it is not enough to have the RESOURCE privilege, you need the CREATE TABLE explicit privilege).
But as I have mentioned it is not a good idea, because you can be affected by transactional side effects. Let's see the main problem:
To maintain ACID principles (Atomicity, Consistency, Isolation and Durability) Oracle needs to ensure that one statement is viewed as an Atomic operation. If I execute INSERT INTO USERS SELECT * FROM OTHER_USER_TABLES this statement must be viewed as an atomic unit. The atomic unit starts when you send the statement and ends when oracle notifies you the error/ok code.
But trigger is executed inside the statement (inside its temporal scope). The trigger controls the insert statement (if it fails all the statement fails, if it says ok, the statement is ok). Note that the statement ends after the triggers finalization. This means that it is not possible to commit or rollback the current transaction before the statement ends (because in this case the Atomicity is not ensured). So, a trigger cannot COMMIT/ROLLBACK the current transaction (it cannot commit and it cannot call a procedure to commit it, nobody can do it before statement ends).
CREATE TABLE is an autocommit statement (if forces a commit) so it is not possible to CREATE a table inside the trigger scope.
But we say "you can" and we have a working example. How?
The answer is the PRAGMA AUTONOMOUS_TRANSACTION.
This pragma forces oracle to execute the affected PL/SQL block in a new different transaction. So, in our example, oracle executes MYCREATEUSER procedure in a new transaction.
Oracle does not have a nested transaction model, so the inner AUTONOMOUS_TRANSACTION does not depend on the outer trigger's transaction. They are both flat transactions. This means that if the procedures ends (the user is created) and the insert finally fails (due to any reason) the row is not in the table but the new user exists.
Let's see an example:
INSERT INTO USERS VALUES ('Anne', 'An232131');
INSERT INTO USERS VALUES ('Mike', "ABC123');
ROLLBACK;
This examples inserts two rows (it creates two users) and rollback the transaction. So, finally the inserts are canceled and the rows (Anne an Mike) are not in users table. But Anne and Mike Oracle's users exists (the rollback) does not affect the user creation because they have been created by a different transaction that finally commited (the autonomous transaction).
This problem is not easy to solve without a nested transactional model (in a nested transactional model a inner transaction only finally commits when its outer transaction does).

Calling a Procedure inside a Function PL/SQL

I am trying to figure out how to call the following procedure from a function using Oracle 11g 11.2.0.2.0. Does anyone have some ideas? :
-- CREATES or REPLACES insert_into_table_a Procedure.
CREATE OR REPLACE PROCEDURE insert_into_table_a
-- Declares variable to be inserted into tables.
(test_insert_a VARCHAR2)
IS
-- Begins Procedure.
BEGIN
-- Creates a savepoint.
SAVEPOINT all_or_none;
-- Inserts test_insert_a into the CONTACT_ID column of the CONTACT table.
INSERT INTO CONTACT (CONTACT_ID)
VALUES (test_insert_a);
-- Inserts test_insert_a into the ADDRESS_ID column of the ADDRESS table.
INSERT INTO ADDRESS (ADDRESS_ID)
VALUES (test_insert_a);
-- Inserts test_insert_a int the TELEPHONE_ID column of the TELEPHONE table.
INSERT INTO TELEPHONE (TELEPHONE_ID)
VALUES (test_insert_a);
--Commits inserts.
COMMIT;
-- Creates exception, incase any errors occur, all changes are rolled back.
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO all_or_none;
-- Ends procedure.
END;
/
-- Shows any errors created.
SHOW ERRORS
You can call the procedure from a function just as you'd call any other PL/SQL block
CREATE OR REPLACE FUNCTION my_function
RETURN integer
IS
l_parameter VARCHAR2(100) := 'foo';
BEGIN
insert_into_table_a( l_parameter );
RETURN 1
END;
That being said, it doesn't make sense to call this procedure from a function. Procedures should manipulate data. Functions should return values. If you call a procedure that manipulates data from a function, you can no longer use that function in a SQL statement, which is one of the primary reasons to create a function in the first place. You also make managing security more complicated-- if you use functions and procedures properly, DBAs can give read-only users execute privileges on the functions without worrying that they'll be giving them the ability to manipulate data.
The procedure itself seems highly suspicious as well. A column named address_id in the address table should be the primary key and the name implies that it is a number. The same applies for the contact_id column in the contact table and the telephone_id column in the telephone table. The fact that you are inserting a string rather than a number and the fact that you are inserting the same value in the three tables implies that neither of these implications are actually true. That's going to be very confusing for whoever has to work with your system in the future.

Resources