9i Database Triggers
9i Database Triggers
INTRODUCTION
Oracle8i introduced an important expansion of database triggers that has been enhanced with each new version. This important expansion extended the use of database triggers beyond tables to allow more flexibility and functionality to be integrated into your database environment and applications. From Oracle 8.1 to Oracle 9.2, this expansion has continued and the power has increased. Most DBAs and developers are unaware or have not taken advantage of these new database triggers, and therefore, the intent of this paper is to provide insight into these new triggers to ensure that they are understood and used to their fullest by both DBAs and developers. This paper concentrates on identifying the new database triggers and associated components of these new triggers. This paper does not focus on database triggers at the table level. The following topics are covered in this paper: Overview of Database Triggers Creation of Database Triggers Security of Database Triggers Enabling and Disabling Database Triggers Database System Events DDL/Client Events Event Attributes Logging Connection Time (LOGON/LOGOFF) Pinning Objects Upon Startup (STARTUP) Audit Trail of Objects (CREATE/ALTER/DROP) Disable the Ability to Drop Objects (DROP) Disable Logins Dynamically (LOGON) Source Version History (CREATE) Log SQL Information Upon Shutdown (SHUTDOWN)
Data Dictionary Views for Database Triggers Re-Creating OBJECT Creation (DBMS_METADATA Package) All examples were executed under Oracle 9.2.0.1.0.
OF
OVERVIEW
DATABASE TRIGGERS
In todays day and age, Oracle applications are being deployed in a variety of ways with a variety of front-end tools being used to build these applications. The constant continues to be the Oracle database flexibility and functionality that can be realized at the database level. Since
Page 1
the introduction of the internet, many companies applications are up and running 24X7X52 and are available from anywhere by anyone. Application logic controls access to these applications, which ultimately controls access to the data. It is always important to have mechanisms in place to ensure that access is only allowed through the application and that activity in the database is monitored. Prior to Oracle8i, Oracle provided the ability to build logic at the table level through database triggers. These triggers were great for added validation and control at the table level for DML (INSERT, UPDATE, DELETE) activity on tables. In Oracle8i, Oracle expanded this last line of defense from table triggers to other event triggers by introducing eight new database triggers. These eight database triggers introduced the extension of this powerful capability to database startup/shutdown, server-side errors, user logons/logoffs, and object creations/alterations/drops. The implicit execution process employed by previous database triggers is the same for these new triggers. Once the database trigger is created and enabled, it is executed based on database actions. No explicit calls are necessary to execute these database triggers. The catproc.sql script that is executed upon a database creation calls two main SQL scripts that create the infrastructure to allow for database trigger execution. These scripts are highlighted below and are located in the ORACLE_HOME/rdbms/admin directory: DBMSSTDX.SQL extends the standard package by creating the DBMS_STANDARD package that contains a variety of functions, including the event attribute functions. DBMSTRIG.SQL creates functions on top of the DBMS_STANDARD functions to allow for easy reference to the event attributes by granting EXECUTE on these functions and creating a PUBLIC synonym for these functions.
A segment of the DBMSTRIG.SQL file is shown below for the ORA_DICT_OBJ_NAME event attribute.
Rem returns the object name on which the DDL statement is being done create or replace function dictionary_obj_name return varchar2 is begin return dbms_standard.dictionary_obj_name; end; / grant execute on dictionary_obj_name to public / create or replace public synonym ora_dict_obj_name for dictionary_obj_name /
CREATION
OF
DATABASE TRIGGERS
When a database trigger is created, the trigger is checked for syntax, the dependency tree and privileges are checked, and then the trigger is compiled into pcode and stored in the database. Therefore, triggers are similar to stored packages and procedures in the sense of creation, storage, and execution of pcode. The main differences are that database triggers source code is stored in a different data dictionary table and database triggers execute implicitly based on actions, whereas, packages and procedures are explicitly called.
Page 2
If errors occur during the creation or compilation of a database trigger, then the trigger is still created and enabled. If a database event executes that causes the database trigger to execute, the database event will fail. Therefore, if an error occurs during creation or compilation, the trigger needs to be either dropped, fixed and re-created, or disabled to ensure that processing does not stop. To view errors, the SHOW ERRORS command can be executed or the errors can be retrieved from the USER_ERRORS data dictionary view. It is good practice to use the following guidelines when creating database triggers: Keep it simple do not create complicated database triggers, follow standards, and only create a database trigger when truly needed Keep the length of the database trigger code relatively small, if it starts to get lengthy, create a stored procedure and call the procedure from the trigger; likewise if the segment of code in the database trigger is used in other locations of code, make it modular and create a stored procedure and call the procedure Only create a database trigger when needed to avoid unnecessary overhead, since dependent on the level of the database trigger, overhead will be introduced
The maximum size of a database trigger is 32K. This limitation can be increased by turning the body of the trigger into a stored package or procedure.
SECURITY
OF
DATABASE TRIGGERS
In order to create a database trigger, the schema must have one of 3 Oracle system privileges: CREATE TRIGGER: this privilege allows for a schema to create a database trigger on a table they own. CREATE ANY TRIGGER: this privilege allows a schema to create a database trigger on a table owned by another schema. ADMINISTER DATABASE TRIGGER: this privilege allows a schema to create a database wide database trigger. Once a trigger is created, it is executed implicitly. Internally, Oracle fires the trigger in the existing user transaction. However, triggers execute in the same manner as the default of stored packages and procedures, namely, with the creator of the trigger privilege in the trigger. Therefore, if the USER variable is referenced in a trigger, the creator of the trigger is returned, not the user that caused the trigger to fire. If any stored packages or procedures are called from within a trigger, the schema creating the trigger must have EXECUTE privilege on that object. Triggers are the same as stored packages and procedures and therefore, have dependencies that can cause a trigger to become invalidated. Any time a referenced stored package or procedure is modified, the trigger becomes invalidated. If a trigger ever becomes invalidated, then Oracle will attempt to internally re-compile the trigger the next time it is referenced. As a standard, a trigger that becomes invalidated, should be recompiled manually to ensure that the trigger will compile successfully. To compile a trigger manually, the ALTER TRIGGER command is used. This is shown below:
ALTER TRIGGER logon_trigger COMPILE;
To recompile a trigger, you must either own the trigger or have the ALTER ANY TRIGGER system privilege. If a package or procedure referenced in a trigger is dropped, then the trigger becomes
Page 3
invalid. When the trigger is recompiled either manually or automatically by Oracle, it will fail since it will not be able to successfully reference all of its components. In this case, the trigger is marked with VALID WITH ERRORS and the event will fail when executed.
ENABLING
AND
Disabled database triggers are companions to invalid objects. In some respects, a disabled trigger is far more dangerous than an invalid object because it doesnt fail; it just doesnt execute! This can have severe consequences for applications (and, consequently, for business processes) that depend on business logic stored within procedural code in database triggers. For this reason, you MUST run the following script regularly to ensure there are not any disabled triggers that you are not aware of:
SELECT trigger_name, trigger_type, base_object_type, triggering_event FROM user_triggers WHERE status <> 'ENABLED' AND db_object_type IN ('DATABASE ', 'SCHEMA') ORDER BY trigger_name; TRIGGER_NAME TRIGGER_TYPE BASE_OBJECT_TYPE TRIGGERING_EVEN ------------------------ ---------------- ---------------- --------------DB_STARTUP_TRIGGER AFTER EVENT DATABASE STARTUP
Once the triggers are identified, they can be enabled manually or a dynamic SQL or PL/SQL script can be created to build the SQL statements to ENABLE the triggers. To enable database triggers, the following three commands could be executed.
ALTER TRIGGER db_startup_trigger ENABLE; ALTER TRIGGER before_insert_customer ENABLE; ALTER TABLE s_customer ENABLE ALL TRIGGERS; -- enabling a database trigger -- enabling a table trigger -- enabling all triggers on a table
The preceding commands allow you to enable one trigger at a time or all the triggers on a table. To enable all triggers under a schema, the following script can be used to build an ENABLE script dynamically:
SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT 'ALTER TRIGGER ' || trigger_name || ' ENABLE;' FROM user_triggers ORDER BY table_name; ALTER TRIGGER DB_STARTUP_TRIGGER ENABLE; ALTER TRIGGER BEFORE_INSERT_CUSTOMER ENABLE; ALTER TRIGGER BEFORE_UPDATE_CUSTOMER ENABLE;
This script can be modified to change the word ENABLE to DISABLE and re-executed to dynamically build a DISABLE script. There may be times that you want to disable triggers for a data load or special processing. In this case, the enable commands shown earlier in this section could be modified as shown below:
ALTER TRIGGER DB_STARTUP_TRIGGER DISABLE; ALTER TRIGGER before_insert_customer DISABLE; ALTER TABLE s_customer DISABLE ALL TRIGGERS;
Page 4
Prior to version 7.3 of Oracle (version 2.3 of PL/SQL), triggers were not stored in compiled format. This means every time the trigger was executed, the trigger was compiled and loaded into memory. This caused additional overhead when using database triggers. Therefore, many people kept certain functions outside of database triggers. This consideration has gone away, since the overhead is now significantly reduced with storing the compiled format. In Oracle9i, there is an undocumented parameter _system_trig_enabled that you can set to FALSE to disable event triggers. In Oracle8i, the parameter is not hidden, and is system_trig_enabled. This parameter can be used during an upgrade, downgrade and/or when patching applications.
BEFORE/AFTE R Execution
BEFORE
Description
Executed when a user logs off, at the start of the logoff process
Attribute Event
LOGON
AFTER
STARTUP
AFTER
ora_sysevent ora_login_user ora_instance_num ora_database_name Executed when a user logs ora_sysevent into the database, after a ora_login_user successful login of the user ora_instance_num ora_database_name ora_client_ip_address Executed when the database is ora_sysevent opened; starts a separate ora_login_user transaction and commits after ora_instance_num this trigger is complete ora_database_name Executed when the instance is shutdown; prior to the shutdown of the instance process; not always executed on abnormal shutdown; starts a separate transaction and commits after this trigger is complete ora_sysevent ora_login_user ora_instance_num ora_database_name
SHUTDOWN
BEFORE
Page 5
SERVERERROR
AFTER
SUSPEND
AFTER
Executes when an Oracle error occurs (can check for a specific error number to only execute for (errno=eno)); does not execute for certain errors (1034, 1403, 1422, 1423, 4030); starts a separate transaction and commits after this trigger is complete Executed whenever a server error causes a transaction to be suspended (example: outof-space error)
The startup and shutdown triggers can only be created at the database level. The other four database system events can be created at the database or schema levels. The STARTUP trigger returns a success, even if the trigger fails. The SERVERERROR trigger does not execute when the following Oracle errors are returned: ORA-01403: data not found ORA-01422: exact fetch returns more than requested number of rows ORA-01423: error encountered while checking for extra rows in exact fetch ORA-01034: ORACLE not available ORA-04030: out of process memory
For these triggers, Oracle opens an autonomous transaction scope, fires the trigger, and commits any separate transaction.
DDL/CLIENT EVENTS
There are 14 DDL/client event triggers and these can be created at the database level and will execute for all schemas, or these can be created at the schema level and will execute only for the schema it is created for. When a trigger is created at the schema level, the trigger is created in the schema specified and executes only for that schema. This provides a great deal of flexibility depending on your environment and what you want to monitor or respond to. The 14 DDL/client event triggers are outlined below, along with a description and the event attributes that are set for each event. Database Trigger BEFORE/AFTE R Execution Description Attribute Events
Page 6
ALTER
BEFORE/AFTER
DROP
BEFORE/AFTER
ANALYZE
BEFORE/AFTER
ASSOCIATE STATISTICS
BEFORE/AFTER
AUDIT/NOAUDIT
BEFORE/AFTER
COMMENT
BEFORE/AFTER
CREATE
BEFORE/AFTER
ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type Executed when object ora_dict_obj_name altered ora_dict_obj_owner ora_des_encrypted_password (for ALTER USER events) ora_is_alter_column, ora_is_drop_column (for ALTER TABLE events) ora_sysevent ora_login_user ora_instance_num Executed when object is ora_database_name dropped ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_sysevent ora_login_user ora_instance_num Executed when the analyze ora_database_name command is executed ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_sysevent ora_login_user ora_instance_num Executed when the associate ora_database_name statistics command is ora_dict_obj_name executed ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list ora_sysevent Executed when the audit or ora_login_user noaudit command is executed ora_instance_num ora_database_name ora_sysevent ora_login_user Executed when the comment ora_instance_num command is executed ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_sysevent ora_login_user ora_instance_num ora_database_name Executed when an object is ora_dict_obj_type created ora_dict_obj_name ora_dict_obj_owner ora_is_creating_nested_table (for CREATE TABLE events)
Page 7
DDL
BEFORE/AFTER
Executed when SQL DDL commands are executed (not executed when and ALTER/CREATE DATABASE, CREATE CONTROLFILE, or DDL issued through the PL/SQL procedure interface)
DISASSOCIATE STATISTICS
BEFORE/AFTER
GRANT
BEFORE/AFTER
RENAME
BEFORE/AFTER
REVOKE
BEFORE/AFTER
TRUNCATE
BEFORE/AFTER
ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_grantee ora_with_grant_option ora_privileges ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_revokee ora_privileges ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner
The new triggers are ideal for DBAs to build mechanisms based on certain events. When the database is started, the objects that need to be pinned can now be moved from the startup SQL script to the STARTUP trigger. When the database is shut down, statistics scripts can be executed to log information into monitoring tables with the SHUTDOWN trigger. Error trapping can be enhanced with the SERVERERROR trigger. Capturing user connect time can be handled
Page 8
through the LOGON and LOGOFF triggers. An object audit trail can be created through the CREATE, ALTER, and DROP triggers.
EVENT ATTRIBUTES
With the introduction of the 20 new database triggers, came the creation of attribute events or variables that are set when a certain database trigger event is executed. The previous section highlighted each of the database triggers, along with the event attributes that are set and can be referenced for each trigger. Below is a list of each of the attribute events, the data type and a short description. Attribute Event
ora_client_ip_address ora_database_name ora_des_encrypted_password ora_dict_obj_name ora_dict_obj_name_list (name_list OUT ora_name_list_t) ora_dict_obj_owner
Data Type
VARCHAR2 VARCHAR2(50) VARCHAR2 VARCHAR(30) BINARY_INTEGER VARCHAR(30)
Description Provides the IP address of the client machine when using TCP/IP Provides the database name Provides the DES encrypted password of the user being created or altered Provides the object name of the object being manipulated Provides a list of object names being manipulated Provides the owner of the object being manipulated Provides the owners of the objects being manipulated Provides the type of object being manipulated Provides the number of grantees Provides the instance number. Provides a return value of TRUE if the specified column is altered Provides a return value of TRUE if the current event is creating a nested table Provides a return value of TRUE if the specified column is dropped Provides a return value of TRUE is the error specified is on the error stack Provides the login schema Provides the position in a CREATE TABLE command where the partition clause can be inserted when using the INSTEAD OF trigger Provides the list of privileges being granted
ora_dict_obj_owner_list(owner BINARY_INTEGER _list OUT ora_name_list_t) ora_dict_obj_type ora_grantee( user_list OUT ora_name_list_t) ora_instance_num ora_is_alter_column( column_name IN VARCHAR2) ora_is_creating_nested_table ora_is_drop_column( column_name IN VARCHAR2) ora_is_servererror ora_login_user ora_partition_pos ora_privilege_list( privilege_list OUT VARCHAR(20) BINARY_INTEGER NUMBER BOOLEAN BOOLEAN BOOLEAN BOOLEAN VARCHAR2(30) BINARY_INTEGER BINARY_INTEGER
Page 9
or revoked Provides a list of the revokees of the revoke command Provides the error on the error stack for the position specified in the stack (1 meaning the top of the stack) Provides the total number of errors on the error stack Provides the error on the error stack for the position specified in the stack (1 meaning the top of the stack) Provides the number of strings that have been substituted into the error message on the error stack for the position specified in the stack (1 meaning the top of the stack) Provides the matching substitution value in the error message for the parameter number specified in conjunction with the position specified on the stack ( 1 meaning the top of the stack) Provides the SQL statement of the statement that caused the trigger to execute (if the statement is lengthy, it will separate it into multiple PL/SQL table elements); the value returned specifies the number of elements Provides the system or client event that caused the trigger to execute Provides a return value of TRUE if the privileges are granted with the grant option Provides a return value of true if the error is related to an out-of-space error and provides the object information of the object with the error
NUMBER
BINARY_INTEGER
VARCHAR2
BINARY_INTEGER
VARCHAR2
BINARY_INTEGER
ora_sysevent ora_with_grant_option Space_error_info( error_number OUT NUMBER, error_type OUT VARCHAR2, object_owner OUT VARCHAR2, table_space_name OUT VARCHAR2, object_name OUT VARCHAR2, sub_object_name OUT VARCHAR2)
VARCHAR2(20) BOOLEAN
BOOLEAN
These attribute events allow extreme flexibility and functionality in each of the database triggers and should be used as necessary.
AND
DEVELOPER)
The power and flexibility is endless with the 20 new database triggers. This section provides a variety of examples to illustrate this power. A list of the examples is provided below. Logging Connection Time (LOGON/LOGOFF)
Page 10
Pinning Objects Upon Startup (STARTUP) Audit Trail of Objects (CREATE/ALTER/DROP) Disable the Ability to Drop Objects (DROP) Disable Logins Dynamically (LOGON) Source Version History (CREATE) Log SQL Information Upon Shutdown (SHUTDOWN)
CREATE OR REPLACE TRIGGER logon_log_trigger AFTER LOGON ON DATABASE BEGIN INSERT INTO session_logon_statistics (sid, user_logged, start_time) SELECT DISTINCT sid, ora_login_user, SYSDATE FROM v$mystat; END; / CREATE OR REPLACE TRIGGER logoff_log_trigger BEFORE LOGOFF ON DATABASE BEGIN UPDATE session_logon_statistics SET end_time = SYSDATE WHERE sid = (select distinct sid from v$mystat) AND end_time IS NULL; END; /
The SID is selected from the V$MYSTAT view. SELECT privilege must be granted on the V_$MYSTAT view to the creator of these triggers. The following script retrieves the information from the SESSION_LOGON_STATISTICS table.
COLUMN COLUMN COLUMN SELECT user_logged FORMAT a15 start_time FORMAT a20 end_time FORMAT a20 sid, user_logged, TO_CHAR(start_time, 'MM/DD/YYYY HH24:MI:SS') start_time, TO_CHAR(end_time, 'MM/DD/YYYY HH24:MI:SS') end_time FROM session_logon_statistics order by sid, user_logged, start_time; SID USER_LOGGED START_TIME END_TIME ---------- --------------- -------------------- -------------------12 TRIGGER_TEST 01/22/2003 19:11:53 01/22/2003 19:17:22
Page 11
12 13 13 13 13 14
01/22/2003 19:18:03
The following is a basic method of inserting objects to pin into the table. This can be modified to be more robust and often a Forms front-end interface is created to allow for this function.
INSERT INTO objects_to_pin (owner, object, type) VALUES (UPPER('&owner'), UPPER('&object'), UPPER('&type'));
The type needs to follow the same conventions as defined in the SHARED_POOL package. This is outlined in the creation script of the DBMS_SHARED_POOL script (dbmspool.sql) and is included for reference below:
Value ----P Q R T JS JC Kind of Object to keep ---------------------package/procedure/function sequence trigger type java source java class
Page 12
JR JD C
The following procedure takes one parameter to signify if the objects in the table should be pinned or unpinned. The default is to pin the objects. A U as input will unpin the objects in the table.
CREATE OR REPLACE PROCEDURE pin_objects (p_pin_flag_txt IN VARCHAR2 := 'P') IS -- The p_pin_flag_txt is either 'P' for pin -- or 'U' for unpin. CURSOR cur_pin_objects IS SELECT owner || '.' owner, object, type FROM objects_to_pin ORDER BY owner, object; BEGIN FOR cur_pin_objects_rec IN cur_pin_objects LOOP IF p_pin_flag_txt = 'U' THEN DBMS_SHARED_POOL.UNKEEP(cur_pin_objects_rec.owner || cur_pin_objects_rec.object, cur_pin_objects_rec.type); ELSE DBMS_SHARED_POOL.KEEP(cur_pin_objects_rec.owner || cur_pin_objects_rec.object, cur_pin_objects_rec.type); END IF; END LOOP; END pin_objects; /
The pin_objects procedure should be called from the database startup script to make certain the PL/SQL objects are pinned immediately, which will ensure the Shared Pool space is contiguously allocated in memory. In Oracle 8.1, the pin_objects procedure can be moved from the database startup script to the new STARTUP database trigger. The STANDARD package and SOURCE_HISTORY were insert into the OBJECTS_TO_PIN table using the INSERT script above. The following output displays the contents of this table.
COLUMN object FORMAT a30 SELECT * FROM objects_to_pin; OWNER --------------SYS TRIGGER_TEST OBJECT -----------------------------STANDARD SOURCE_HISTORY TYPE -----------P R
The following query displays the stored PL/SQL program units information regarding pinning. The script below has been modified to focus on the two objects that we are attempting to pin. The WHERE clause would usually only contain the following condition.
WHERE kept = 'YES'
COLUMN type FORMAT a12 SET PAGESIZE 58 SELECT owner, name, type, kept FROM v$db_object_cache WHERE name in ('STANDARD', 'SOURCE_HISTORY'); OWNER --------------SYS SYS TRIGGER_TEST TRIGGER_TEST NAME ------------------------STANDARD STANDARD SOURCE_HISTORY SOURCE_HISTORY TYPE -----------PACKAGE PACKAGE BODY TABLE TRIGGER KEP --NO NO NO NO
The previous query can be executed to list the sharable memory (sharable_mem column) required for the object and the number of times the object was loaded and executed (loads and executions columns) to determine which objects should be pinned. In order to take this example full circle, a database startup script is created that calls the PIN_OBJECTS procedure. This will ensure that objects will be pinned upon startup no matter where the database is being started from (whether manually or through a script).
CREATE OR REPLACE TRIGGER db_startup_trigger AFTER STARTUP ON DATABASE BEGIN pin_objects; END; /
The database is then shutdown and started up and the query against the V$DB_OBJECT_CACHE view is executed again as shown below.
SQL> shutdown Database closed. Database dismounted. ORACLE instance shut down. SQL> startup ORACLE instance started. Total System Global Area Fixed Size Variable Size Database Buffers Redo Buffers Database mounted. Database opened. OWNER --------------SYS SYS TRIGGER_TEST TRIGGER_TEST 135338868 453492 109051904 25165824 667648 bytes bytes bytes bytes bytes
Pinning frequently executed Oracle objects and application objects is recommended, but remember adequate space must still exist in the Shared Pool for the objects that are not pinned.
Page 14
AUDIT TRAIL
OF
OBJECTS (CREATE/ALTER/DROP)
The next example provides the ability to build your own flexible object audit trail. The example below creates database triggers to capture and log every CREATE, ALTER and DROP operation on the database. First a table is created to store the audit information.
CREATE TABLE audit_object_mods (mod_date DATE, type_of_mod VARCHAR2(20), mod_user VARCHAR2(30), instance_num NUMBER, database_name VARCHAR2(50), object_owner VARCHAR2(30), object_type VARCHAR2(20), object_name VARCHAR2(30));
Next, the CREATE, ALTER, and DROP database triggers are created. These are created for all schemas. If the audit was only focused on one particular schema, then the ON DATABASE line would be changed to ON TRIGGER_TEST.SCHEMA where TRIGGER _TEST is the name of the schema desired to audit.
CREATE OR REPLACE TRIGGER create_object_trigger AFTER CREATE ON DATABASE BEGIN INSERT INTO audit_object_mods (mod_date, type_of_mod, mod_user, instance_num, database_name, object_owner, object_type, object_name) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; / CREATE OR REPLACE TRIGGER alter_object_trigger AFTER ALTER ON DATABASE BEGIN INSERT INTO audit_object_mods (mod_date, type_of_mod, mod_user, instance_num, database_name, object_owner, object_type, object_name) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; / CREATE OR REPLACE TRIGGER drop_object_trigger AFTER DROP ON DATABASE BEGIN INSERT INTO audit_object_mods (mod_date, type_of_mod, mod_user, instance_num, database_name,
Page 15
object_owner, object_type, object_name) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; /
At this point, the audit is set up and as these operations take place in the database, they will be logged to the AUDIT_OBJECT_MODS table. To view the audit, the following script can be executed.
COLUMN type_of_mod FORMAT a10 COLUMN mod_user FORMAT a12 COLUMN database_name FORMAT a10 COLUMN object_owner FORMAT a12 COLUMN object_type FORMAT a10 COLUMN object_name FORMAT a25 SET LINESIZE 130 SELECT mod_date, type_of_mod, mod_user, instance_num, database_name, object_owner, object_type, object_name FROM audit_object_mods ORDER BY mod_date, type_of_mod, object_owner, object_type, object_name;
MOD_DATE --------27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 TYPE_OF_MO ---------CREATE CREATE CREATE CREATE DROP CREATE ALTER ALTER MOD_USER I_NUM DATABASE_N OBJECT_OWNER OBJECT_TYP ------------ ----- ---------- ------------ ---------TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST PROCEDURE TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TABLE TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER TRIGGER_TEST 1 TUSC.WORLD TRIGGER_TEST TRIGGER OBJECT_NAME ------------------------ALTER_OBJECT_TRIGGER DROP_OBJECT_TRIGGER PIN_OBJECTS DB_STARTUP_TRIGGER DATABASE_STARTUP_TRIGGER TEST14 DB_STARTUP_TRIGGER DB_STARTUP_TRIGGER
The individual CREATE, ALTER, and DROP database triggers created previously can be replaced with one trigger creation with the use of the OR condition as shown below.
CREATE OR REPLACE TRIGGER c_a_d_object_trigger AFTER CREATE OR ALTER OR DROP ON DATABASE BEGIN INSERT INTO audit_object_mods (mod_date, type_of_mod, mod_user, instance_num, database_name, object_owner, object_type, object_name) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; /
DISABLE
THE
ABILITY
TO
The following example illustrates how a trigger can be created for specific schema to disallow the ability to DROP objects. The trigger is for the PLSQL_USER.
Page 16
CREATE OR REPLACE TRIGGER stop_drop_trigger BEFORE DROP ON plsql_user.SCHEMA BEGIN RAISE_APPLICATION_ERROR ( num => -20000, msg => 'Cannot DROP Objects'); END; /
When the PLSQL_USER attempts to DROP any object an error will result as shown below.
SHOW USER USER is "PLSQL_USER" DROP TABLE temp; drop table temp * ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-20000: Cannot DROP Objects ORA-06512: at line 2
A record is inserted into this table with the value of YES. The following logon trigger is created to reference this table upon logon and look for the value of the record to determine if logins are allowed.
CREATE OR REPLACE TRIGGER logon_allow_trigger AFTER LOGON ON DATABASE DECLARE CURSOR logon_allowed IS SELECT logons_allowed FROM logons_allowed; lv_allowed logons_allowed.logons_allowed%TYPE; BEGIN OPEN logon_allowed; FETCH logon_allowed INTO lv_allowed; IF lv_allowed != 'YES' THEN CLOSE logon_allowed; RAISE_APPLICATION_ERROR ( num => -20000, msg => 'Logins Not Allowed.'); ELSE CLOSE logon_allowed; END IF;
Page 17
END; /
The trigger looks for a value of YES and if the value is NOT YES, then the login will not succeed and the login will fail with an error. To illustrate, the table is updated and the logons_allowed value is set to NO. A user attempts to login and they get the following error:
SQL*Plus: Release 9.2.0.1.0 - Production on Wed Jan 22 21:45:27 2003 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
ERROR: ORA-00604: error occurred at recursive SQL level 1 ORA-20000: Logins Not Allowed. ORA-06512: at line 12
Any time an error is returned either at runtime as forced in the above trigger or if the above trigger was INVALID due to an error upon creation, the SYS and SYSTEM users are exempt from errors in the logon triggers and will be allowed to logon. Likewise, any schema with ADMINISTER DATABASE TRIGGER system privilege follows the same logic. Therefore, when the creator of the trigger above (TRIGGER_TEST who has the above privilege), as well as the SYS and SYSTEM users attempt to logon, they succeed. If the trigger was created with an error and is INVALID, the 3 users and any schema with the privilege above would still succeed upon logon. However, if the trigger was created with an error and is INVALID and anyone else attempts to logon, they will receive the following message.
SQL*Plus: Release 9.2.0.1.0 - Production on Tue Jan 28 15:06:21 2003 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
Enter user-name:
source_history DATE NOT VARCHAR2(30) NOT VARCHAR2(30) NOT VARCHAR2(20), NUMBER NOT VARCHAR2(4000));
A CREATE trigger is then created that will insert into the SOURCE_HISTORY table after each new object creation as shown below.
CREATE OR REPLACE trigger source_history AFTER CREATE ON DATABASE BEGIN INSERT INTO source_history SELECT SYSDATE, owner, name, type, line, text FROM dba_source WHERE owner = ORA_DICT_OBJ_OWNER AND name = ORA_DICT_OBJ_NAME AND type = ORA_DICT_OBJ_TYPE; END source_history; /
The same scripts created for the DBA_SOURCE view can now be used on this view by only changing the view name to SOURCE_HISTORY. A sample script is shown below with the query limited to one object.
COLUMN owner FORMAT a12 COLUMN name FORMAT a11 COLUMN line FORMAT 9999 COLUMN text FORMAT a60 WORD_WRAPPED SELECT change_date, owner, name, type, line, text FROM source_history WHERE name = 'PIN_OBJECTS' order by change_date, owner, name, type, line;
CHANGE_DA --------27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 27-JAN-03 OWNER -----------TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST TRIGGER_TEST NAME ----------PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS PIN_OBJECTS TYPE LINE TEXT ---------- ----- -----------------------------------------------------------PROCEDURE 1 PROCEDURE pin_objects PROCEDURE 2 (p_pin_flag_txt IN VARCHAR2 := 'P') IS PROCEDURE 3 -- The p_pin_flag_txt is either 'P' for pin PROCEDURE 4 -- or 'U' for unpin. PROCEDURE 5 CURSOR cur_pin_objects IS PROCEDURE 6 SELECT owner || '.' owner, PROCEDURE 7 object PROCEDURE 8 FROM objects_to_pin PROCEDURE 9 ORDER BY owner, object; PROCEDURE 10 BEGIN PROCEDURE 11 FOR cur_pin_objects_rec IN cur_pin_objects LOOP PROCEDURE 12 IF p_pin_flag_txt = 'U' THEN PROCEDURE 13 DBMS_SHARED_POOL.UNKEEP(cur_pin_objects_rec.owner || PROCEDURE 14 cur_pin_objects_rec.object, 'P'); PROCEDURE 15 ELSE PROCEDURE 16 DBMS_SHARED_POOL.KEEP(cur_pin_objects_rec.owner || PROCEDURE 17 cur_pin_objects_rec.object, 'P'); PROCEDURE 18 END IF; PROCEDURE 19 END LOOP; PROCEDURE 20 END pin_objects;
A procedure is created to log the desired information from the view into the log table. There is a low threshold in the example below for the number of READS. This can be modified for your system to only log the SQL for statements over a large number of disk reads.
CREATE OR REPLACE PROCEDURE SQLAREA_LOG AS CURSOR c1 IS SELECT disk_reads reads, executions exe, ROUND(disk_reads/DECODE(executions, 0, 1, executions)) ratio, sql_text text FROM v$sqlarea WHERE disk_reads > 100 ORDER BY disk_reads/DECODE(executions, 0, 1, executions) DESC; lv_current_date DATE := SYSDATE; BEGIN FOR c1_rec in c1 LOOP INSERT INTO sqlarea_history (log_date, disk_reads, executions, execution_ratio, sql_text) VALUES (lv_current_date, c1_rec.reads, c1_rec.exe, c1_rec.ratio, c1_rec.text); END LOOP; END; /
Lastly, the database shutdown trigger is created to call the SQLAREA_LOG procedure.
CREATE OR REPLACE TRIGGER db_shutdown_trigger BEFORE SHUTDOWN ON DATABASE BEGIN sqlarea_log; END; /
The SQL can then be queried at anytime in the future with the following statement.
COLUMN sql_text FORMAT a25 COLUMN log_date FORMAT a19 SET PAGESIZE 58 SELECT TO_CHAR(log_date, 'MM/DD/YYYY:HH24:MI:SS') log_date, disk_reads, executions exe, execution_ratio ratio, sql_text FROM sqlarea_history;
Page 20
LOG_DATE DISK_READS EXE RATIO SQL_TEXT ------------------- ---------- ---------- ---------- ------------------------01/22/2003:22:55:09 379 1 379 select o.owner#,o.obj#,de code(o.linkname,null, dec ode(u.name,null,'SYS',u.n ame),o.remoteowner), o.na me,o.linkname,o.namespace ,o.subname from user$ u, obj$ o where u.user#(+)=o .owner# and o.type#=:1 an d not exists (select p_ob j# from dependency$ where p_obj# = o.obj#) for upd ate 01/22/2003:22:55:09 213 1 213 select distinct i.obj# fr om sys.idl_ub1$ i where i .obj#>=:1 and i.obj# not in (select d.p_obj# from sys.dependency$ d) 01/22/2003:22:55:09 115 1 115 select distinct d.p_obj#, d.p_timestamp from sys.de pendency$ d, obj$ o where d.p_obj#>=:1 and d.d_obj #=o.obj# and o.status!=5
FOR
DATABASE TRIGGERS
There are several data dictionary views that reveal information about triggers. The main two views center on the source code view that contains the source code for package, procedures and functions, but also stores the compilation status of triggers. This is important to determine if a trigger has been compiled successfully. The source code for triggers and the important information regarding being ENABLED or DISABLED is also stored in the trigger views. There are 3 views for each that center on the scope of the information. These are listed below.
user_triggers all_triggers dba_triggers user_source all_source dba_source
Some examples follow to exemplify the usefulness of these views. The first example script provides information on the compilation status of triggers. A status of VALID indicates the trigger is compiled and is ready for execution. A status of INVALID means that the trigger needs to be compiled prior to execution. This will be automatically attempted by Oracle the next time the trigger is fired or manually with the ALTERCOMPILE command prior to the next firing of the trigger. I always recommend recompiling manually to ensure the compilation will be valid and if
Page 21
not, then changes can be made until it does become VALID. You always want to know prior to production if there will be compilation issues that you need to address.
COLUMN SELECT FROM WHERE object_name FORMAT a24 object_name, object_type, status user_objects object_type = 'TRIGGER'; OBJECT_TYP ---------TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER TRIGGER STATUS ------VALID VALID VALID VALID VALID VALID VALID VALID VALID VALID VALID VALID
OBJECT_NAME -----------------------ALTER_OBJECT_TRIGGER CREATE_OBJECT_TRIGGER DB_SHUTDOWN_TRIGGER DB_STARTUP_TRIGGER DROP_OBJECT_TRIGGER LOGOFF_LOG_TRIGGER LOGON_ALLOW_TRIGGER LOGON_LOG_TRIGGER SOURCE_HISTORY STOP_DROP_TRIGGER TEST TEST_LOGON
As evidenced in the output, all triggers are VALID at the current time. If an object that is referenced by one of these triggers is dropped or altered in any manner, this will cause the trigger to become INVALID. The second example provides information about the triggers in the system. This is useful to understand the database and schema triggers that are defined in a database.
COLUMN COLUMN SELECT FROM WHERE trigger_name FORMAT a24 triggering_event FORMAT a15 trigger_name, trigger_type, base_object_type, triggering_event, status user_triggers db_object_type IN ('DATABASE ', 'SCHEMA'); TRIGGER_TYPE ---------------AFTER EVENT AFTER EVENT BEFORE EVENT AFTER EVENT AFTER EVENT BEFORE EVENT AFTER EVENT AFTER EVENT AFTER EVENT AFTER EVENT BEFORE EVENT AFTER EVENT BASE_OBJECT_TYPE ---------------DATABASE DATABASE DATABASE DATABASE DATABASE DATABASE DATABASE DATABASE DATABASE DATABASE SCHEMA SCHEMA TRIGGERING_EVEN --------------ALTER CREATE SHUTDOWN STARTUP DROP LOGOFF LOGON LOGON CREATE LOGON DROP LOGON STATUS -------ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED ENABLED
TRIGGER_NAME -----------------------ALTER_OBJECT_TRIGGER CREATE_OBJECT_TRIGGER DB_SHUTDOWN_TRIGGER DB_STARTUP_TRIGGER DROP_OBJECT_TRIGGER LOGOFF_LOG_TRIGGER LOGON_ALLOW_TRIGGER LOGON_LOG_TRIGGER SOURCE_HISTORY TEST_LOGON STOP_DROP_TRIGGER TEST
The output identifies the triggers with the type of triggers, events that cause each to execute, the timing of the execution (when) and the execution status. Refer to the previous section on ENABLING and DISABLING triggers for more information on this view.
Page 22
The last example provides the source code of each of the triggers (limited to the DB_STARTUP_TRIGGER trigger).
COLUMN COLUMN COLUMN SELECT FROM WHERE trigger_name FORMAT a24 triggering_event FORMAT a15 trigger_body FORMAT a25 word_wrapped trigger_name, trigger_type, base_object_type, triggering_event, trigger_body user_triggers trigger_name = 'DB_STARTUP_TRIGGER';
TRIGGER_NAME TRIGGER_TYPE BASE_OBJECT_TYPE TRIGGERING_EVEN TRIGGER_BODY ------------------ ------------ ---------------- --------------------------------------DB_STARTUP_TRIGGER AFTER EVENT DATABASE STARTUP BEGIN pin_objects; END;
The output provides attributes on the triggers, as well as, the entire contents of the trigger that was created.
The creation statements of the procedures and triggers created in this paper can be retrieved via the DBMS_METADATA.GET_DDL function in the following code segment. The output is displayed to the screen, but could be directed to a flat file via the UTL_FILE package.
SET LONG 1000 SELECT name, type, DBMS_METADATA.GET_DDL(type, name) FROM user_source WHERE line = 1;
NAME TYPE DBMS_METADATA.GET_DDL(TYPE,NAME) ------------------------------ ------------ -------------------------------------------------------DB_SHUTDOWN_TRIGGER TRIGGER CREATE OR REPLACE TRIGGER "TRIGGER_TEST"."DB_SHUTDOWN_TRIGGER" BEFORE SHUTDOWN ON DATABASE BEGIN sqlarea_log; END; ALTER TRIGGER "TRIGGER_TEST"."DB_SHUTDOWN_TRIGGER" ENABLE
Page 23
DB_STARTUP_TRIGGER
TRIGGER
CREATE OR REPLACE TRIGGER "TRIGGER_TEST"."DB_STARTUP_TRIGGER" AFTER STARTUP ON DATABASE BEGIN pin_objects; END; ALTER TRIGGER "TRIGGER_TEST"."DB_STARTUP_TRIGGER" ENABLE CREATE OR REPLACE TRIGGER "TRIGGER_TEST"."LOGOFF_LOG_TRIGGER" BEFORE LOGOFF ON DATABASE BEGIN UPDATE session_logon_statistics SET end_time = SYSDATE WHERE sid = (select distinct sid from v$mystat) AND end_time IS NULL; END; ALTER TRIGGER "TRIGGER_TEST"."LOGOFF_LOG_TRIGGER" ENABLE CREATE OR REPLACE TRIGGER "TRIGGER_TEST"."LOGON_LOG_TRIGGER" AFTER LOGON ON DATABASE BEGIN INSERT INTO session_logon_statistics (sid, user_logged, start_time) SELECT DISTINCT sid, USER, SYSDATE FROM v$mystat; END; ALTER TRIGGER "TRIGGER_TEST"."LOGON_LOG_TRIGGER" ENABLE CREATE OR REPLACE PROCEDURE "TRIGGER_TEST"."PIN_OBJECTS" (p_pin_flag_txt IN VARCHAR2 := 'P') IS -- The p_pin_flag_txt is either 'P' for pin -- or 'U' for unpin. CURSOR cur_pin_objects IS SELECT owner || '.' owner, object, type FROM objects_to_pin ORDER BY owner, object; BEGIN FOR cur_pin_objects_rec IN cur_pin_objects LOOP IF p_pin_flag_txt = 'U' THEN DBMS_SHARED_POOL.UNKEEP(cur_pin_objects_rec.owner || cur_pin_objects_rec.object, cur_pin_objects_rec.type); ELSE DBMS_SHARED_POOL.KEEP(cur_pin_objects_rec.owner || cur_pin_objects_rec.object, cur_pin_objects_rec.type); END IF; END LOOP; END pin_objects; CREATE OR REPLACE TRIGGER "TRIGGER_TEST"."SOURCE_HISTORY" AFTER CREATE ON DATABASE BEGIN INSERT INTO source_history SELECT sysdate, owner, name, type, line, text FROM dba_source WHERE owner = ora_dict_obj_owner AND name = ora_dict_obj_name AND type = ora_dict_obj_type; END; ALTER TRIGGER "TRIGGER_TEST"."SOURCE_HISTORY" ENABLE
LOGOFF_LOG_TRIGGER
TRIGGER
LOGON_LOG_TRIGGER
TRIGGER
PIN_OBJECTS
PROCEDURE
SOURCE_HISTORY
TRIGGER
As can be seen in the output, the triggers are created and enabled. Only a subset of the output is shown above, and only a basic example of the use of the DBMS_METADATA package was shown above. This package is extremely powerful.
Page 24
SUMMARY
This paper outlined the new database triggers that were introduced in Oracle8i and enhanced each version to increase the power and flexibility of this database feature. The new triggers were outlined with examples to highlight the EXTREME flexibility and functionality available.
ABOUT
THE
AUTHOR
Joe Trezzo ([email protected]) is a cofounder, President and Chief Operating Officer of TUSC (www.tusc.com). He has been working with Oracle since version 4 and is the author of the Oracle Press book titled Oracle PL/SQL Tips and Techniques. He is a regular presenter at many Oracle user groups and is in the Entrepreneur Hall of Fame.
REFERENCES
Oracle PL/SQL Tips and Techniques (Oracle Press), Joseph C. Trezzo Oracle9i Instant PL/SQL Scripts (Oracle Press), Kevin Loney Oracle9i DBA Handbook (Oracle Press), Kevin Loney Oracle9i The Complete Reference (Oracle Press), Kevin Loney Oracle9i New Features (Oracle Press), Robert Freeman PL/SQL User's Guide and Reference(Release 9.0.1 & 9.2.0), Oracle Corporation Supplied PL/SQL Packages and Types Reference (Release 9.0.1 & 9.2.0), Oracle Corporation Oracle9i Database Administrators Guide (Release 9.0.1 & 9.2.0), Oracle Corporation Oracle9i SQL Reference (Release 9.0.1 & 9.2.0), Oracle Corporation Oracle9i Database Concepts (Release 9.0.1 & 9.2.0), Oracle Corporation Oracle9i Application Developers Guide - Fundamentals (Release 9.0.1 & 9.2.0), Oracle Corporation Oracle9i Database New Features (Release 9.0.1 & 9.2.0), Oracle Corporation $ORACLE_HOME/rdbms/doc/README_rdbms.htm www.tusc.com www.osborne.com
Neither TUSC nor the author guarantees this document to be error-free. Please provide comments/questions to [email protected].
Page 25