/** @mainpage Qore oracle Module

    @tableofcontents

    Contents of this documentation:
    - @ref oracleintro
    - @ref ora_options
    - @ref bindings_and_types
    - @ref binding_by_value
    - @ref binding_by_placeholder
    - @ref default_oracle_to_qore_mapping
    - @ref named_types
    - @ref aq
    - @ref ora_known_issues
    - @ref ora_releasenotes

    @section oracleintro Introduction to the oracle Module

    The oracle module provides an Oracle driver to Qore's DBI system, allowing Qore
    programs to take access Oracle databases through the Qore's \c Datasource,
    \c DatasourcePool, and \c SQLStatement classes.

    This module is released under the <a href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL 2.1</a>
    and is tagged as such in the module's header (meaning it can be loaded
    unconditionally regardless of how the Qore library was initialized).

    Also included with the binary oracle module:
    - <a href="../../OracleExtensions/html/index.html">OracleExtensions</a> user module providng transparent SQL
      tracing support using Oracle's \c DBMS_APPLICATION_INFO package.</a>

    Example of creating an Oracle Datasource (note that \a db_encoding, \a host,
    and \a port are optional - using the hostname and port allows connections
    to be established without TNS, without these parameters TNS is used):

    @code
    Datasource ds(SQL::DSOracle, user, pass, db, db_encoding, host, port);
    @endcode

    or

    @code
    Datasource ds("oracle:user/password@db%host:1522");
    @endcode

    This driver supports the following DBI capabilities:
    - \c DBI_CAP_TRANSACTION_MANAGEMENT
    - \c DBI_CAP_STORED_PROCEDURES
    - \c DBI_CAP_CHARSET_SUPPORT
    - \c DBI_CAP_LOB_SUPPORT
    - \c DBI_CAP_BIND_BY_VALUE
    - \c DBI_CAP_BIND_BY_PLACEHOLDER
    - \c DBI_CAP_HAS_EXECRAW
    - \c DBI_CAP_HAS_STATEMENT
    - \c DBI_CAP_HAS_SELECT_ROW
    - \c DBI_CAP_HAS_NUMBER_SUPPORT
    - \c DBI_CAP_HAS_OPTION_SUPPORT
    - \c DBI_CAP_SERVER_TIME_ZONE
    - \c DBI_CAP_AUTORECONNECT
    - \c DBI_CAP_HAS_ARRAY_BIND
    - \c DBI_CAP_HAS_RESULTSET_OUTPUT

    The \c Datasource::getServerVersion() and \c Datasource::getClientVersion()
    methods are implemented for this driver. \c Datasource::getServerVersion()
    returns a string giving server information similar to the following:

    @code
    Oracle Database 10g Release 10.2.0.1.0 - 64bit Production
    @endcode

    The \c Datasource::getClientVersion() returns a hash giving version
    information in the following keys: \c major, \c minor,
    \c update, \c patch, \c port_update.

    Note: There seems to be a bug in Oracle 9i and earlier in the streaming
    \c OCILobRead() function, where the LOB buffer must be at least twice
    as big as the LOB data to be read. This bug does not affect versions
    of the Qore oracle module linked with Oracle 10g libraries or later.

    @note Object (Oracle named type) and collection support is supplied
    courtesy of \c ocilib (http://orclib.sourceforge.net/), note that
    \c ocilib was highly modified to be usable in this module, mostly
    due to the fact that we use a separate environment data structure for
    each connection to ensure maximum thread scalability - the Oracle docs
    say that all operations on an environment handle or any handle derived
    from an environment handle (i.e. statement handles, etc) must be either
    wrapped in a mutex (when initialized with \c OCI_NO_MUTEX) or will
    be wrapped in a mutex by oracle (with \c OCI_THREADED and
    without \c OCI_NO_MUTEX).

    @section ora_options Driver Options

    When compiled again Qore 0.8.6+ the oracle driver support the following DBI options on each connection:
    - \c "optimal-numbers": return <tt>NUMBER</tt> types as an integer if possible, if not as an arbitrary-precision
      number
    - \c "string-numbers": return <tt>NUMBER</tt> types as strings (for backwards-compatibility)
    - \c "numeric-numbers": return <tt>NUMBER</tt> types as arbitrary-precision number values
    - \c "timezone": accepts a string argument that can be either a region name (ex: \c "Europe/Prague") or a UTC
      offset (ex: \c "+01:00") to set the server's time zone rules; this is useful if connecting to a database server
      in a different time zone.  If this option is not set then the server's time zone is assumed to be the same as
      the client's time zone; see @ref timezone.

    Options can be set in the \c Datasource or \c DatasourcePool constructors as in the following examples:
    @code
    Dataource ds("oracle:user/pass@db{numeric-numbers,timezone=Europe/Vienna}");
    DataourcePool dsp("oracle:user/pass@db%host.internal:1521{optimal-numbers}");
    @endcode

    Options are stored separately for each connection.

    @subsection number_options Number Options

    The number options (\c "optimal-numbers", \c "string-numbers", and \c "numeric-numbers") are all mutually-
    exclusive; setting one automatically disables the others.   These options also ignore their arguments; the last
    one set takes effect (also note that setting it with any value sets the option, even @ref False).

    The default if no other option is explicitly set is \c "optimal-numbers".  Note that this represents a change from
    previous versions where Oracle <tt>NUMBER</tt> values were returned as strings in order to avoid the loss of
    information.  To set the old behavior, set the \c "string-numbers" option when creating the \c Datasource or
    \c DatasourcePool object.

    @section bindings_and_types Binding and Types

    When retrieving Oracle data, Oracle types are converted to Qore types as
    per @ref default_oracle_to_qore_mapping.

    Binding by value is supported for any statement executed through this
    driver; Oracle types are converted to Qore types as per @ref binding_by_value.

    Binding by placeholder is required to retrieve values from a procedure
    or function call. The oracle driver assumes that any placeholder values
    are string values unless a placeholder buffer specification is passed
    in the argument position corresponding to the placeholder specification
    in the string. For placeholder buffer specification values,
    see @ref binding_by_placeholder.

    Oracle \c TIMESTAMP data supports time resolution to the microsecond,
    however Qore's date/time value only supports a millisecond resolution.
    Any Oracle \c TIMESTAMP values are rounded to millisecond resolution
    when converted to Qore data types.  See also @ref timezone.

    PL/SQL code and stored procedure and function execution is supported;
    the following is an example of a stored procedure call using bind by
    value and bind by placeholder (the \c %v characters represent the
    positions for binding the arguments following the SQL string by
    value, and the placeholder names are prefixed by a colon):

    @code
hash<auto> result = ds.exec("begin h3g_psft_order_import.insert_h3g_psft_customers(%v, %v, :status_code,
    :error_code, :error_description); end;", "Customer Name", "Customer-ID", Type::Int, Type::Int);
    printf("%N\n", result);
    @endcode

    This will bind the \c "Customer Name" and \c "Customer-ID" strings by
    value (as per @ref binding_by_value these will be bound with Oracle type
    \c SQLT_STRING), and the output variables will be bound by placeholder
    (the first two will be bound as per @ref binding_by_placeholder with
    buffers of Oracle type \c SQLT_INT, and the last placeholder buffer
    will get the default buffer type of \c SQLT_STRING), resulting in a hash
    giving the values of the output variables:

    @code
    hash: (3 members)
        status_code : 0
        error_code : 0
        error_description : <NULL>;
    @endcode

    @subsection timezone Time Zone Support

    The driver now sets the server's time zone rules when the connection is established; this is taken from the
    current time zone settings of the calling Program object and can also be overridden/changed by setting the
    \c "timezone" driver option (see @ref ora_options).

    All date/time values bound by value are converted to the server's time zone before binding to ensure that
    date/time values are stored correctly in the server.

    When selecting date/time values, the values returned are tagged with the server's time zone.

    Note that the above rules are applied when the current Program's time zone settings are different than the
    connection's time zone settings at the time when the write operation (bind) read operation (select) is performed.
    This is meant to provide consistent support to connecting to a database server in a different time zone.

    @section select_with_dup_col Selects With Duplicated Columns

    When columns are duplicated in select statements, the duplicated columns are renamed by the driver with an
    underscore and a numerix suffix (<i>column</i><tt>_</tt><i>#</i>) as follows:
    @code
hash<auto> h = ds.select("select a.id, b.id, c.id from a, b, c where a.id = b.id = c.id");
    @endcode

    Would return a result as follows (assuming 1 row in each table with \a id = 1):
    @verbatim
{id: 1, id_1: 1, id_2: 1}
    @endverbatim

    @subsection binding_by_value Binding by Value

    |!Argument|!OCI Type|!Description|
    |\c Type::Binary|\c SQLT_BIN|For use with \c BLOB columns, for example.|
    |\c Type::String|\c SQLT_STR|For use with string data, \c VARCHAR, \c CHAR, \c CLOB columns, etc|
    |\c Type::Int|\c SQLT_INT or \c SQLT_STR|if the int > 32-bits = \c SQLT_STR, <= 32-bit int = \c SQLT_INT|
    |\c Type::Boolean|\c SQLT_INT|True is bound as 1, False as 0|
    |\c Type::Float|\c SQLT_BDOUBLE|For use with \c FLOAT, \c BINARY_FLOAT, \c BINARY_DOUBLE columns, etc|
    |\c Type::Date|\c SQLT_TIMESTAMP|For use with \c DATE, \c TIMESTAMP, etc columns|
    |Wrapped \c Type::Hash and \c Type::List|\c SQLT_NTY|For use with @ref named_types|

    @subsection binding_by_placeholder Binding by Placeholder

    |!Argument|!OCI Type|!Description|
    |\c Type::Binary|\c SQLT_BIN|For retrieving \c RAW data up to 65531 bytes in size.|
    |\c SQL::BLOB|\c SQLT_BLOB|For retrieving \c BLOB data. The \c LOB handle is used to read the entire \c BLOB content into a binary object.|
    |\c SQL::CLOB|\c SQLT_CLOB|For retrieving \c CLOB data. The \c LOB handle is used to read the entire \c CLOB content into a Qore string.|
    |\c SQL::RESULTSET|\c SQLT_RSET|For retrieving a result set as a @ref Qore::SQL::SQLStatement "SQLStatement" object|
    |\c SQL::VARCHAR|\c SQLT_STR|For retrieving character data (\c VARCHAR2, etc). To specify a buffer size larger than 512 bytes, simply use the size in bytes as the argument. See @ref string_sizes|
    |\c Type::Int|\c SQLT_INT|For retrieving integer numeric data up to 32 bits (for larger numbers or for non-integer numbers use \c SQL::VARCHAR or \c Type::Float.|
    |\c Type::Float|\c SQLT_BDOUBLE|For retrieving data in 64-bit floating point format.|
    |\c Type::Date|\c SQLT_TIMESTAMP|For retrieving dates and times.|
    |\c Type::Hash|\c SQLT_RSET|For retrieving result sets from a stored procedure that returns a cursor.|
    |Wrapped \c Type::Hash and \c Type::List|\c SQLT_NTY|For use with @ref named_types|

    @subsection default_oracle_to_qore_mapping Default Oracle to Qore Mappings

    |!Oracle Column Type|!Qore Type|!Notes|
    |\c REAL, \c FLOAT, \c DOUBLE \c PRECISION, \c BINARY_FLOAT, \c BINARY_DOUBLE|\c Type::Float|direct conversion|
    |\c DATE|\c Type::Date|direct conversion|
    |\c TIMESTAMP|\c Type::Date (absolute)|resolution to the microsecond|
    |\c TIMESTAMP \c WITH \c TIME \c ZONE|\c Type::Date (absolute)|includes time zone information and has a resolution to the microsecond|
    |\c TIMESTAMP \c WITH \c LOCAL \c TIME \c ZONE|\c Type::Date (absolute)|includes time zone information and has a resolution to the microsecond|
    |\c INTERVAL \c YEAR \c TO \c MONTH|\c Type::Date (relative)|direct conversion to a relative date|
    |\c INTERVAL \c DAY \c TO \c SECOND|\c Type::Date (relative)|direct conversion to a relative date|
    |\c SMALLINT, \c INTEGER|\c Type::Int|direct conversion|
    |\c NUMBER, \c NUMERIC, \c DECIMAL|\c Type::String|conversion to a string to avoid loss of precision|
    |\c CLOB|\c Type::String|the LOB handle is used to read the entire CLOB content into a string|
    |\c RAW, \c LONG RAW|\c Type::Binary|direct conversion|
    |\c BLOB|\c Type::Binary|the LOB handle is used to read the entire BLOB content into a binary object|
    |\c CURSOR (result set)|\c Type::Hash|the result set is returned as a hash of lists|

    @subsection oracle_array_bind Oracle Array Binds

    The Oracle driver supports the \c DBI_CAP_HAS_ARRAY_BIND capability, so it can find arrays of bind values to SQL for
    highly efficient SQL communication with the server; data for an arbitrary number of rows can be sent to the server in
    one command.

    Array binding support differs depending on the data type; additionally it's possible to mix arrays of values and single
    values when binding; single values will be repeated in each row bound.

    Note that when arrays are bound; each array must have the same data type in each element or the element can contain
    @ref nothing "NOTHING" or @ref null "NULL", both of which are bound as @ref null "NULL".

    <b>Array Binding Support</b>
    |!Qore Type|!IN|!OUT|!Single
    |@ref string "string"|\c Y|\c Y|\c Y
    |@ref int "int"|\c Y|\c Y|\c Y
    |@ref number "number"|\c Y|\c Y|\c Y
    |@ref boolean "bool"|\c Y|\c Y|\c Y
    |@ref float "float"|\c Y|\c N|\c Y
    |@ref date "date"|\c Y|\c N|\c Y
    |@ref binary "binary"|\c Y|\c N|\c Y

    Other non-null types are not supported.

    @par Example:
    @code
int rows = ds.exec("merge into merge_target t using (select %v as id, %v as val, %v as code from dual) source
    on (t.id = source.id)
    when not matched then
        insert (id, val, code) values (source.id, source.val, source.code)
    when matched then
        update set val = source.val, code = source.code", (1, 2, 3), ("val1", "val2", "val3"), "const_code");
    @endcode

    @see @ref named_types "named types" and collections as an alternative approach to high-volume SQL operations in
    single commands

    @subsection string_sizes CHAR and VARCHAR2 to Qore String

    @subsubsection string_sizes_tabs Direct Access to Tables/Views
    Data returned from \c DatasourcePool::select() (and similar methods) use real string sizes:
        - full string for CHAR(n) including trailing spaces
        - full string for VARCHAR2(n). See example below for trailing spaces handling vs. column size.

    Let's assume follwing data:

    @code
    create table str_test (
        str_char char(10),
        str_var  varchar2(10),
        str_vars varchar2(10)
    );
    -- the spaces around string in the 3rd value are important!
    insert into str_test values ('foo', 'foo', ' foo ');
    commit;
    @endcode

    @code
    printf("%N\n", ds.select("select * from str_test"));

    hash: (3 members)
      str_char : list: (1 element)
        [0]="foo       "
      str_var : list: (1 element)
        [0]="foo"
      str_vars : list: (1 element)
        [0]=" foo "
    @endcode

    @subsubsection string_sizes_plsql PL/SQL Wrappers

    Data returned from PL/SQL code use different approach.
    Strings returned from this call are right-trimmed because OCI does not
    provide exact value size in this case by PL/SQL nature. The trimming is
    mandatory to avoid values with full allocated buffer (like strings with
    512 spaces at the end).

    @code
    -- sample PL/SQL procedure
    create or replace procedure p_str_test(
        o_str_char out str_test.str_char%type,
        o_str_var  out str_test.str_var%type,
        o_str_vars out str_test.str_vars%type
    )
    is
    begin
        select   str_char,   str_var,   str_vars
            into o_str_char, o_str_var, o_str_vars
            from str_test where rownum < 2;
    end;
    /
    @endcode

    @code
    printf("%N\n", ds.exec("begin p_str_test(:str_char, :str_var, :str_vars); end;"));

    hash: (3 members)
      str_char : "foo"
      str_var : "foo"
      str_vars : " foo"
    @endcode

    @section named_types Named Types (Objects)

    Qore Oracle driver supports Oracle objects and collections (NTY in the following text).

    Special initialization of the driver is mandatory to use NTY in Qore scripts:

    @code
    %requires oracle
    @endcode

    This statement imports additional functions for NTY binding and fetching
    and therefore is required in Qore code that wants to use these
    functions - the automatic loading of DBI drivers on reference happens
    at run-time, therefore any references to the functions provided by
    this module can only be resolved at parse time if the module is
    explicitly required as above.

    |!Function|!Description|
    |Qore::Oracle::bindOracleObject()|Binds a Qore \c value as a Object type \c typename. Hash keys are object \
        attributes
    |Qore::Oracle::placeholderOracleObject()|Allows fetching Object type \c typename. The returned hash is a plain \
        %Qore hash with keys set as the object's attributes
    |Qore::Oracle::bindOracleCollection()|Binds \c value as a Collection \c typename
    |Qore::Oracle::placeholderOracleCollection()|Allows fetching a Collection \c typename. The returned list is a \
        plain %Qore list of values with the collection's type

    Type names (strings) are case insensitive.

    Key names in \c value hashes are case sensitive and should follow
    Oracle uppercase naming convention.

    Keys which are not found in the keys-attribute mappings are silently
    ignored. If there is a missing key for any attribute, an exception is thrown.

    @note Strings/integers are converted to date in collections and objects when required - collection of datetimes,
    object datetime attributes, etc. So eg. 1970-01-01 is the value of "wrong" strings.

    Functions can be nested so there can be for example list (collection)
    of objects and vice versa:

    Sample named types defined in the DB:

    @code
    CREATE OR REPLACE TYPE test_object
        AUTHID current_user AS OBJECT
    (
        attr_num number,
        attr_varchar varchar2(20)
    );
    CREATE OR REPLACE TYPE test_collection IS TABLE OF test_object;
    @endcode

    Example of binding NTY:

    @code
    hash obj1 = ( "ATTR_NUM" : 1, "ATTR_VARCHAR" : "lorem ipsum" );
    hash obj2 = ( "ATTR_NUM" : 2, "ATTR_VARCHAR" : "dolor sir amet" );
    list l = ( bindOracleObject('test_object', obj1),
               bindOracleObject('test_object', obj2) );
    ds.exec("begin test_pkg.foo(%v); end;", bindOracleCollection('test_collection', l));
    @endcode

    Example of fetching NTY:

    @code
    *hash res = ds.exec("begin test_pkg.get_obj(:retval); end;",
                        placeholderOracleObject("TEST_OBJECT"));
    printf("%N\n", res);

    # will print out
    hash: (
        "ATTR_NUM" : 5,
        "ATTR_VARCHAR" : "foobar!"
    )
    @endcode

    @warning Oracle Named Types (objects and collections) are custom data types
        stored directly in the Oracle system catalogue. They are real SQL
        types - created with \c CREATE \c [ \c OR \c REPLACE \c ] \c TYPE
        command. It's a common misundrestanding that PL/SQL types created
        for example in package specification can be used as NTY too, however this is an
	incorrect assumption.  PL/SQL types cannot be used directly with the Oracle \c OCI
        library, however you can use custom wrappers or any other workarounds, of course.

    @section aq Oracle Advanced Queueing (AQ)

    Oracle Advanced Queueing (AQ) is the Oracle database's queue management
    feature. AQ provides a message queuing infrastructure as integral part
    of the Oracle server engine. It provides an API for enqueing messages
    to database queues. These messages can later be dequeued for asynchronous
    processing. Oracle AQ also provides functionality to preserve, track,
    document, correlate, and query messages in queues.

    Features:

    - synchronous message posting
    - synchronouns message fetching
    - asynchronous event-driven listening in the DB queue

    Special initialization of the driver is mandatory to use AQ in Qore scripts:

    @code
    %requires oracle
    @endcode

    Qore Oracle module contains these classes to handle AQ:

    |!Class|!Description
    |Qore::Oracle::AQQueue|Main queue handler
    |Qore::Oracle::AQMessage|Enhanced features for message

    @section ora_known_issues Known Issues

    Unfortunately there are some known bugs in the Oracle Module which cannot be fixed
    in the Qore driver right now. These are bugs in the underlying Oracle C Interface
    (OCI) library mostly.

    @subsection named_types_known_issues Named Types Known Issues

    - Returning a collection of objects and Oracle client 10.x does not work correctly sometimes.
      There is only one instance of object (a first one) used for all items in the
      collection in some cases. Upgrade to Oracle client 11.x solves it.

    - Binding a collection of objects can lose VARCHAR2 attribute values replacing
      them with NULL. This behavior has been observed on Solaris SPARC architecture
      using Oracle CLient 11.1.x. Upgrading to 11.2.x solves the issue.

    - Oracle Metalink Bug #3604465 - bind named type by placeholder can cause
      \c ORA-21525 (attribute number or (collection element at index) %s violated
      its constraints) if the object contains numbers with size constraint. An example:

    @code
    TYPE a_object AS OBJECT (
        my_num1 number(3), -- it will cause ORA-21525
        my_num2 number     -- it will work
    )
    @endcode

    - A crash when is the \c placeholderOracleObject() used in the place
      where is the \c placeholderOracleCollection() expected by Oracle.

    @code
    hash res = ds.exec("begin test_pkg.get_collection(:retval); end;",
                        placeholderOracleObject("test_object"));

    # there is an exception expected: OCI-21710 but following crash appears:
    OCI-21500: internal error code, arguments: [kgepop: no error frame to pop to], [], [], [], [], [], [], []
    OCI-21710: argument is expecting a valid memory address of an object
    @endcode

    @subsection aq_known_issues Advaned Queueing Known Issues

    - Some versions of 11.2 client software is unable to receive change notifications/OCI Callbacks or
      AQ notifications against some versions of 11.2 database.

      Typically no errors result, and the observed behavior is that the notification simply does not occur.

      A row will be observed in \c DBA_CHANGE_NOTIFICATION_REGS, but then be removed,
      without notification occurring at the client.

      Note that the behavior of this problem is consistent, and no notifications
      at all will be received. This bug does not apply for intermittent behaviors.

      In the case of 11.2.0.1 client pointing to 11.2.0.2 database, the following error may occur:
      @code
kpcerecv: incorrect iovec count=6210
kpcest_err_handler: Max errors exceeded..Closing connection
      @endcode

      Behavior may be noticed as a result of upgrading either client or database software
      to the affected versions, or after applying a CPU that contains the fix for unpublished Bug 10065474.

      This behavior is a result of Oracle Bug 10065474, and is specific to 11.2 database.

      The fix is primarily a client side fix, however there are some database side changes
      that may be required once the client side fix is applied, so the fix for Oracle Bug 10065474
      can actually CAUSE this behavior, if the change is not applied at both client and server.

      <b>Solution:</b> Apply patches or upgrade versions as applicable to obtain a working combination.

      <b>Workaround:</b> A workaround can be used in most cases to bypass the authentication phase, which is where the
      problem behavior occurs.  To do that, set the following event in the database:
      @code
      alter system set events '10867 trace name context forever, level 1';
      @endcode

<table>
   <tr><td rowspan="2">Server version</td><td colspan="4">Client version</td></tr>
   <tr><th>11.2.0.1.0</th><th>11.2.0.2.0</th><th>11.2.0.2.0+fix</th><th>11.2.0.3.0</th></tr>
   <tr><th>11.2.0.1.0</th><td>works</td><td>fails</td><td>works</td><td>fails</td></tr>
   <tr><th>11.2.0.2.0</th><td>fails</td><td>works</td><td>fails</td><td>fails</td></tr>
   <tr><th>11.2.0.2.0+fix</th><td>works</td><td>fails</td><td>works</td><td>works</td></tr>
   <tr><th>11.2.0.3.0</th><td>&nbsp;</td><td>fails</td><td>works</td><td>works</td></tr>
</table>

    @subsection darwin_issues Issues on Darwin/macOS

    Due to the way that Apple has changed dynamic library path handling in recent releases of
    Darwin/macOS, the Oracle module's rpath must be updated with the path to the Oracle
    dynamic libraries.

    Therefore if you have moved/upgraded your Oracle libraries or are using a binary module
    build on another system, execute the following command to update the module's
    internal rpath:

    @verbatim install_name_tool -add_rpath ${ORACLE_LIB_DIR} ${MODULE_NAME}@endverbatim

    for example:
    @verbatim
    sudo install_name_tool -add_rpath ${ORACLE_INSTANT_CLIENT} /usr/local/lib/qore-modules/oracle*.qmod
    @endverbatim

    Note that this should not be necessary when building and installing on the same system,
    as the rpath used when linking is set automatically when the module is installed.

    @section ora_releasenotes Release Notes

    @subsection oracle3_3_2 oracle Driver Version 3.3.2
    - fixed building with \c qpp from %Qore 1.12.4+

    @subsection oracle3_3_1 oracle Driver Version 3.3.1
    - fixed inconsistent bind exceptions to all use \c BIND-ERROR
      (<a href="https://github.com/qorelanguage/qore/issues/4390">issue 4390</a>)
    - fixed a bug where the driver would accept array binds for select statements and only bind the first array
      value, silently returning erroneous results
      (<a href="https://github.com/qorelanguage/qore/issues/2154">issue 2154</a>)
    - added cmake build solution for this module

    @subsection oracle33 oracle Driver Version 3.3
    - fixed two off-by-one errors in ocilib code
      (<a href="https://github.com/qorelanguage/qore/issues/4192">issue 4192</a>)
    - fixed disconnection events to be handled with all SQL actions instead of just exec calls
      (<a href="https://github.com/qorelanguage/qore/issues/3944">issue 3944</a>)
    - fixed a crash with open statements with NTY placeholders with a connection loss
      (<a href="https://github.com/qorelanguage/qore/issues/3446">issue 3446</a>)
    - updated to build with up to v19 of Oracle libs
      (<a href="https://github.com/qorelanguage/qore/issues/3424">issue 3424</a>)
    - added support for the <tt>STMT EXEC DESCRIBE</tt> DBI method for efficient describing of SQL statements without
      executing the statement itself and therefore avoiding a large performance penalty for this operation
      (<a href="https://github.com/qorelanguage/qore/issues/2773">issue 2773</a>)
    - added support for the \c RESULTSET placeholder specification and the \c DBI_CAP_HAS_RESULTSET_OUTPUT driver capability
      that allow result set output variables to be returned as @ref Qore::SQL::SQLStatement "SQLStatement" objects
      (<a href="https://github.com/qorelanguage/qore/issues/1300">issue 1300</a>)
    - fixed a bug where NTY truncated CLOBs to a quarter of their lengths
      (<a href="https://github.com/qorelanguage/qore/issues/1802">issue 1802</a>)
    - fixed a bug where \c ORA-03113 and \c ORA-03114 were not treated as lost connections
      (<a href="https://github.com/qorelanguage/qore/issues/1963">issue 1963</a>)
    - fixed a bug where \c DBI-SELECT-ROW-ERROR exceptions were being raised as \c ORACLE-SELECT-ROW-ERROR exceptions
      (<a href="https://github.com/qorelanguage/qore/issues/2542">issue 2542</a>)
    - implemented a workaround for a bug in Oracle pre 12c related to \c ORA-01041 errors
      (<a href="https://github.com/qorelanguage/qore/issues/802">issue 802</a>)
    - fixed issues related to rpath handling on Darwin/macOS
      (<a href="https://github.com/qorelanguage/qore/issues/475">issue 475</a>)
    - fixed an issue selecting large \c LONG column values
      (<a href="https://github.com/qorelanguage/qore/issues/2609">issue 2609</a>)

    @subsection oracle321 oracle Driver Version 3.2.1
    - updated SQLStatement::fetchColumns() to return an empty hash when no data is available in all cases
      (<a href="https://github.com/qorelanguage/qore/issues/1241">issue 1241</a>)
    - implemented support for the lost connection API added in Qore 0.8.12.10
      (<a href="https://github.com/qorelanguage/qore/issues/1250">issue 1250</a>)
    - fixed a memory leak in connection handling
      (<a href="https://github.com/qorelanguage/qore/issues/1272">issue 1272</a>)
    - fixed a memory leak in error handling
      (<a href="https://github.com/qorelanguage/qore/issues/1583">issue 1583</a>)
    - fixed a bug in connection handling where statement and bind handles acquired on the old connection context were
      freed on the new connection context after the old connection context had been freed itself
      (<a href="https://github.com/qorelanguage/qore/issues/1717">issue 1717</a>)
    - fixed a bug where NTY truncated CLOBs to a quarter of their lengths
      (<a href="https://github.com/qorelanguage/qore/issues/1802">issue 1802</a>)
    - worked around a bug in Oracle handling \c DATE values in selects with dates bound by value when the date is in
      the same time zone as the server's time zone but has a different DST offset
      (<a href="https://github.com/qorelanguage/qore/issues/2448">issue 2448</a>)

    @subsection oracle32 oracle Driver Version 3.2
    - added more Qore exception handling in ocilib object/NTY APIs
    - ensure all strings written to the DB are converted to the target character encoding before writing
    - copy all values when binding by value in case they are bound to an IN OUT variable, in which case memory
      corruption would occur previously
    - support for IN OUT variables by giving a hash argument with the "value" key as the input value for the output
      variable
    - placeholders and quotes in SQL comments are ignored
    - user modules moved to top-level qore module directory from version-specific module directory since they are
      valid for multiple versions of qore
    - fixed a major bug where a lost connection during an SQL operation using named types could cause a crash due to
      the database-specific private context becoming invalid and then used after deletion
    - fixed a major bug where a crash generated internally in the OCI library would occur when NTYs were bound in a
      prepared statement and the statement was executed in a new logon session after transparent reconnection
    - fixed bugs binding values to number attributes of NTYs and collections
    - fixed a crashing bug handling the case when there are multiple objects of different incompatible types added to
      a collection; now an exception is raised
    - implemented binding number types by placeholder (including in/out vars)
    - implemented binding arrays of scalar values for input and output
    - implemented support for returning an empty list or hash after a short read with SQLStatement::fetchRows() or
      SQLStatement::fetchColumns(); if one of these methods is called a second time after returning an empty data
      structure, then an exception is raised
    - implemented support for bulk DML with selects by default and explicitly with SQLStatement::fetchRows() and
      SQLStatement::fetchColumns(); default prefetch row count: 1000
    - fixed a bug in driver option processing that could lead to a crash
    - fixed a bug where duplicated columns would result in corrupt lists when selected in column mode
    - fixed a bug where very large numbers were being retrieved incorrectly and would result in an \c ORA-01406 error
    - fixed a bug where number values were being returned with decimal points truncated in locales where the decimal
      position is not marked with a dot
    - fixed a bug where collections with no columns (but based on a table for example) could not be used
      (<a href="https://github.com/qorelanguage/qore/issues/455">issue 455</a>)
    - fixed a bug where cached NTY information was reused with stale pointers when connections were reestablished
      (<a href="https://github.com/qorelanguage/qore/issues/537">issue 537</a>)
    - fixed a bug where SQLStatement::describe() was failing even though result set information was available
    - fixed a bug rewriting column names when selecting with row formats
      (https://github.com/qorelanguage/qore/issues/828)
    - fixed a bug where binary values were bound directly which caused \c ORA-01461 errors when more than one binary
      value > 4000 bytes was bound (<a href="https://github.com/qorelanguage/qore/issues/875">issue 875</a>)
    - fixed some potentially ambiguous method declarations in \c OracleDatasourceBase in
      <a href="../../OracleExtensions/html/index.html">OracleExtensions</a> for forward-compatibility with future
      versions of %Qore

    @subsection oracle31 oracle Driver Version 3.1

    <b>New Features and Bug Fixes</b>
    - fixed retrieving NUMBER types from objects/NTYs
    - added more Qore exception handling in ocilib object/NTY APIs
    - enforce DBI options on NTY retrieval with numeric values

    @subsection oracle30 oracle Driver Version 3.0

    <b>New Features and Bug Fixes</b>
    - driver supports Oracle Advanced Queuing
    - driver supports selecting data from LONG columns (deprecated but used in system catalog tables for example)
    - driver now claims support for DBI_CAP_AUTORECONNECT, however the underlying functionality has been present for a
      long time

    @subsection oracle221 oracle Driver Version 2.2.1

    <b>Bug Fixes</b>
    - fixed a bug where non-integer values were returned as integers
    - fixed a bug in returning negative integer values that were being returned as arbitrary-precision numeric values
      instead of integers
    - updated CLOB writing to use the streaming APIs instead of trying to write in one large block since there are
      bugs in some oracle client libraries related to trying to write large blocks, and using the streaming apis
      resolves the problem

    @subsection oracle22 oracle Driver Version 2.2

    <b>New Features and Bug Fixes</b>
    - fixed bugs in named type support - when an unknown named type is accessed for the first time, ocilib keeps an
      invalid entry for the named type in the type list; the second time the type is accessed, a crash results (bug
      reported and already fixed in upstream ocilib)
    - fixed memory bugs in handling named type bind parameters created by hand where the value type is incorrect
    - now the driver throws an exception if SQLStatement::fetchRow() is called before SQLStatement::next() (before it
      would return garbage data)
    - supports the arbitrary-precision numeric type in qore 0.8.6+
    - supports DBI options with qore 0.8.6+; options supported:
      - \c "optimal-numbers": return <tt>NUMBER</tt> values as an integer if possible, if not as an arbitrary-
        precision number
      - \c "string-numbers": return <tt>NUMBER</tt> values as strings (for backwards-compatibility)
      - \c "numeric-numbers": return <tt>NUMBER</tt> values as arbitrary-precision number values
      - \c "timezone": accepts a string argument that can be either a region name (ex: \c "Europe/Prague") or a UTC
        offset (ex: \c "+01:00") to set the server's time zone rules; this is useful if connecting to a database
        server in a different time zone.  If this option is not set then the server's time zone is assumed to be the
        same as the client's time zone; see @ref timezone.
    - the default for the number options (see @ref number_options) is now "optimal-numbers" - meaning that
      <tt>NUMBER</tt> values are retrieved as <tt>int</tt> if possible, otherwise as a <tt>number</tt> type is
      returned.\n
      For backwards-compatibility, set the \c "string-numbers" option to return <tt>NUMBER</tt> values as strings
*/
