Skip to main content

Change in ALL_ARGUMENTS as of 18c: no more composite "explosion"

The Oracle catalog contains hundreds of views (generally referred to as "data dictionary views") that provide information about the objects stored in the database (tables, views, PL/SQL program units, etc.). Several of these views are extremely helpful to PL/SQL developers in analyzing and managing their code.

Here are a few examples:
  • ALL_OBJECTS - information about all database objects which can be accessed by the current user.
  • ALL_ARGUMENTS - information about every argument of every packaged subprogram and schema-level program unit for which the current user has EXECUTE authority.
  • ALL_IDENTIFIERS - information about identifiers in program units, gathered by the Oracle Database 11g PL/Scope feature.
In this blog post, I explore a change in behavior for ALL_ARGUMENTS (and its USER* and DBA* variants) as of Oracle Database 18c.

ALL_ARGUMENTS lists the arguments of the procedures and functions that are accessible to the current user. USER_ARGUMENTS, as you might know or guess, offers information about those arguments defined in procedures and functions in the currently connected schema.

This view gives developers access to all sorts of interesting information, allowing them to create queries to perform checks on their code.

You can, for example, determine if any arguments of an "undesireable" type, such as CHAR or LONG. You can also find out if a subprogram is a procedure or a function.

You can also determine if a subprogram is a function or a procedure (though this is now also possible through PL/Scope and ALL_IDENTIFIERS). Here's an OracleLiveSQL script showing this usage.

As you likely know, an argument could be a scalar (number, date, string. etc.) or a composite (consisting of multiple "pieces", such as a record or object type). Up until Oracle Database 12c Release 2, the way that ALL_ARGUMENTS recursively expanded composites. In other words, that view contained one row for the argument itself and then one row for each attribute of an object type or field of a record. You can see this below.

I create a table, then a function that accepts a record as an in parameter.
CREATE TABLE plch_employees
(
   employee_id     INTEGER
 , last_name       VARCHAR2 (100)
 , first_name      VARCHAR2 (100)
 , date_of_birth   DATE
 , hire_date       DATE
 , salary          NUMBER
)
/

CREATE OR REPLACE FUNCTION plch_get_info (
   employee_in    IN plch_employees%ROWTYPE
 , info_type_in   IN PLS_INTEGER
 , for_date_in    IN DATE)
   RETURN BOOLEAN
   AUTHID DEFINER
IS
BEGIN
   RETURN NULL;
END;
/

/* Verify choice correctness */

SELECT COUNT (*) arg_count
  FROM user_arguments 
 WHERE object_name = 'PLCH_GET_INFO' 
/

ARG_COUNT
---------
10
Run this came code in 18c and above, and the count of rows is just 4. The following script shows a bit more detail.
SQL> 
select OBJECT_NAME,ARGUMENT_NAME,POSITION
  2    FROM user_arguments
  3   WHERE object_name = 'PLCH_GET_INFO'
  4  /
 
OBJECT_NAME          ARGUMENT_NAME          POSITION
-------------------- -------------------- ----------
PLCH_GET_INFO                                      0
PLCH_GET_INFO        EMPLOYEE_IN                   1
PLCH_GET_INFO        INFO_TYPE_IN                  2
PLCH_GET_INFO        FOR_DATE_IN                   3
 
4 rows selected.
 
12.2
 
SQL> select OBJECT_NAME,ARGUMENT_NAME,POSITION
  2    FROM user_arguments
  3   WHERE object_name = 'PLCH_GET_INFO'
  4  /
 
OBJECT_NAME          ARGUMENT_NAME          POSITION
-------------------- -------------------- ----------
PLCH_GET_INFO                                      0
PLCH_GET_INFO        EMPLOYEE_IN                   1
PLCH_GET_INFO        EMPLOYEE_ID                   1
PLCH_GET_INFO        LAST_NAME                     2
PLCH_GET_INFO        FIRST_NAME                    3
PLCH_GET_INFO        DATE_OF_BIRTH                 4
PLCH_GET_INFO        HIRE_DATE                     5
PLCH_GET_INFO        SALARY                        6
PLCH_GET_INFO        INFO_TYPE_IN                  2
PLCH_GET_INFO        FOR_DATE_IN                   3

If you relied on that expanded data in ALL_ARGUMENTS in any of your QA scripts, you will now need to join with one of the following views: ALL_PLSQL_TYPES, ALL_PLSQL_TYPE_ATTRS, and ALL_PLSQL_COLL_TYPES.

Here are two sources of more information on this change, as provided by Jurgen, who also offers an extensive comment about his team's experience with this change below.

Changes to /ALL/USER/DBA User View and PL/SQL External Libraries

About Bug 5910872

Comments

  1. Hi Steven,

    Do you happen to know any detail about why that behavior has been changed?
    I am aware about the following bug that is related to the performance of DataPump Import operations and, generally, the amount of space occupied in the SYSTEM tablespace:
    Bug 5910872 : ARGUMENT$ DATA UNNECESSARILY DUPLICATED
    It seems to be quite a nice improvement, however, I have not seen any tests showing the performance effects of that change.
    I guess JDBC should also be affected in addition to any other operations utilizing %_ARGUMENTS views extensively (don't recall anything right now apart from JDBC metadata queries).

    Regards,
    Mikhail.

    ReplyDelete
    Replies
    1. Mikhail, I believe you are right on target. A key motivation for the change was to reduce the amount of space need to store argument information.

      Delete
  2. Hi Steven,

    we stumbled upon that pesky issue no more than month ago since we skipped 18c for good.

    Here's some input from Oracle
    ----

    https://docs.oracle.com/en/database/oracle/oracle-database/18/rnrdm/pl-sql-bugs.html#GUID-A0EB14E7-D759-46F4-B438-A5F6DB1C3761

    https://docs.oracle.com/en/database/oracle/oracle-database/19/upgrd/behavior-changes-deprecated-desupport-oracle-database.html#GUID-543498D6-3799-4217-9BE3-4BB8630FC32D

    The changes where made because of compilation performance/memory limitation issues on table ARGUMENT$.
    Just search for bug id 5910872:

    https://support.oracle.com/epmos/faces/SearchDocDisplay?_adf.ctrl-state=u4vhb0pq8_4&_afrLoop=525552462352302

    https://support.oracle.com/epmos/faces/SearchDocDisplay?_adf.ctrl-state=u4vhb0pq8_4&_afrLoop=525552462352302

    ----

    Our data access interface "Oracle OCI <-> C++" is not able to resolve nested datatypes (data type level > 0) anymore because "OCIDescribeAny()" is based on ARGUMENT$ which only represents level 0 data types now.
    We internally only support the fetch of table type data (collections) holding single scalar datatypes but also this is now not working out of the box with OCIDescribeAny() anymore.

    Also all of our dynamic SQL generating PLSQL procedures which rely on the complete xxx_ARGUMENTS views don't work anymore as intended.
    Furthermore even DBMS_DESCRIBE does not work like it used to because it's based on OCIDescribeAny().

    It's noteable that there exists an Oracle EVENT which reverts the new ARGUMENT$ behaviour. Full recompilation is necessary after setting the event.
    => events='10946, level 65536'

    But setting an event to force backward compatibility could hit you very hard in future Oracle releases lacking that special event.
    Additionally no one knows what other side effect this event will piggyback - just like every patch/bundle/set does.

    Because of this reasons I wrote a pipelined function which emulates a fully resolved xxx_ARGUMENTS fetch. I created a local ALL_ARGUMENTS view so that no further PLSQL code changes are necessary. The query performance on my own ALL_ARGUMENTS is unfortunately under par. We now consider to cache the fully resolved result. Maybe a materialized view will help here.

    Our data access interface using OCI is now also rewritten and resolves now at least to data type level 1.


    Beware that there even exists an Oracle 12.1 "bugfix" which already introduces this new behavoir!

    BR,
    Jürgen

    ReplyDelete
    Replies
    1. Jürgen, thank you so much for providing all this detailed information. I am sorry this change had such a dramatic impact for you.

      Two questions:

      1. Have you notified Oracle Support of any of these issues? I will also pass this along to the PL/SQL team.

      2. Any possibility that you could and would share your pipelined table function implementation on, say, Oracle LiveSQL (livesql.oracle.com) for others to use?

      Many thanks, Steven

      Delete
    2. Hello Steven,

      so far we haven't opened a SR on MOS regarding that issue since Oracle stated in their documentary that this is indeed a intended behaviour change for 18c.
      Should I open one despite that fact?
      It really makes me wonder that there only exists just a handful of complaints because of that hefty change on Google or MOS search.
      Seems no one really uses this views or haven't really noticed the changes so far.

      I could share my code but I've to remove the code pieces which rely on our base PLSQL code.

      BR,
      Jürgen

      Delete
    3. You are right that it is a known and documented change, so definitely will not be considered a bug. And they will not be changing it back.

      I expect that most people who use all_arguments do so "lightly" and either didn't pay attention to the recursive expansion or didn't rely on it.

      Delete
    4. hi Jürgen/Steven, We are facing this issue and our front end relies on dba_arguments. Could you please share the code. -Danny

      Delete

Post a Comment

Popular posts from this blog

Running out of PGA memory with MULTISET ops? Watch out for DISTINCT!

A PL/SQL team inside Oracle made excellent use of nested tables and MULTISET operators in SQL, blending data in tables with procedurally-generated datasets (nested tables).  All was going well when they hit the dreaded: ORA-04030: out of process memory when trying to allocate 2032 bytes  They asked for my help.  The error occurred on this SELECT: SELECT  *    FROM header_tab trx    WHERE (generated_ntab1 SUBMULTISET OF trx.column_ntab)       AND ((trx.column_ntab MULTISET             EXCEPT DISTINCT generated_ntab2) IS EMPTY) The problem is clearly related to the use of those nested tables. Now, there was clearly sufficient PGA for the nested tables themselves. So the problem was in executing the MULTISET-related functionality. We talked for a bit about dropping the use of nested tables and instead doing everything in SQL, to avoid the PGA error. That would, however require lots of wo...

How to Pick the Limit for BULK COLLECT

This question rolled into my In Box today: In the case of using the LIMIT clause of BULK COLLECT, how do we decide what value to use for the limit? First I give the quick answer, then I provide support for that answer Quick Answer Start with 100. That's the default (and only) setting for cursor FOR loop optimizations. It offers a sweet spot of improved performance over row-by-row and not-too-much PGA memory consumption. Test to see if that's fast enough (likely will be for many cases). If not, try higher values until you reach the performance level you need - and you are not consuming too much PGA memory.  Don't hard-code the limit value: make it a parameter to your subprogram or a constant in a package specification. Don't put anything in the collection you don't need. [from Giulio Dottorini] Remember: each session that runs this code will use that amount of memory. Background When you use BULK COLLECT, you retrieve more than row with each fetch, ...

PL/SQL 101: Three ways to get error message/stack in PL/SQL

The PL/SQL Challenge quiz for 10 September - 16 September 2016 explored the different ways you can obtain the error message / stack in PL/SQL. Note: an error stack is a sequence of multiple error messages that can occur when an exception is propagated and re-raised through several layers of nested blocks. The three ways are: SQLERRM - The original, traditional and (oddly enough) not currently recommended function to get the current error message. Not recommended because the next two options avoid a problem which you are unlikely  to run into: the error stack will be truncated at 512 bytes, and you might lose some error information. DBMS_UTILITY.FORMAT_ERROR_STACK - Returns the error message / stack, and will not truncate your string like SQLERRM will. UTL_CALL_STACK API - Added in Oracle Database 12c, the UTL_CALL_STACK package offers a comprehensive API into the execution call stack, the error stack and the error backtrace.  Note: check out this LiveSQL script if...