/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_Datasource.qpp Datasource class definition

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_Datasource.h"
#include "qore/intern/QoreHashNodeIntern.h"
#include "qore/intern/QC_Serializable.h"

DLLLOCAL extern QoreClass* QC_ABSTRACTDATASOURCE;
DLLLOCAL extern QoreClass* QC_ABSTRACTSQLSTATEMENT;
DLLLOCAL extern QoreClass* QC_SQLSTATEMENT;

static const char* DSC_ERR = "DATASOURCE-CONSTRUCTOR-ERROR";

static int ds_set_options(ManagedDatasource* ds, const QoreHashNode* opts, ExceptionSink* xsink) {
    ConstHashIterator hi(opts);
    while (hi.next()) {
        // skip "min" and "max" options
        if (!strcmp(hi.getKey(), "min") || !strcmp(hi.getKey(), "max"))
            continue;

        if (ds->setOptionInit(hi.getKey(), hi.get(), xsink))
            return -1;
    }
    return 0;
}

static void ds_constructor_hash(const QoreHashNode* opts, QoreObject* self, Queue* queue, const QoreValue& arg,
        ExceptionSink* xsink) {
    ReferenceHolder<Queue> q(queue, xsink);

    const char* str = check_hash_key(opts, "type", DSC_ERR, xsink);
    if (*xsink) return;

    if (!str) {
        xsink->raiseException(DSC_ERR, "expecting a string value with the 'type' key giving the driver name");
        return;
    }

    DBIDriver* db_driver = DBI.find(str, xsink);
    if (!db_driver) {
        if (!*xsink)
            xsink->raiseException("DATASOURCE-UNSUPPORTED-DATABASE", "DBI driver '%s' cannot be loaded", str);
        return;
    }

    ReferenceHolder<ManagedDatasource> ds(new ManagedDatasource(db_driver), xsink);

    str = check_hash_key(opts, "user", DSC_ERR, xsink);
    if (*xsink) return;
    if (str) ds->setPendingUsername(str);

    str = check_hash_key(opts, "pass", DSC_ERR, xsink);
    if (*xsink) return;
    if (str) ds->setPendingPassword(str);

    str = check_hash_key(opts, "db", DSC_ERR, xsink);
    if (*xsink) return;
    if (str) ds->setPendingDBName(str);

    str = check_hash_key(opts, "charset", DSC_ERR, xsink);
    if (*xsink) return;
    if (str) ds->setPendingDBEncoding(str);

    str = check_hash_key(opts, "host", DSC_ERR, xsink);
    if (*xsink) return;
    if (str) ds->setPendingHostName(str);

    int port = (int)opts->getKeyValue("port").getAsBigInt();
    if (port) {
        if (port < 0) {
            xsink->raiseException(DSC_ERR, "port value must be zero (meaning use the default port) or positive "
                "(value given: %d)", port);
            return;
        }
        ds->setPendingPort(port);
    }

    {
        QoreValue p = opts->getKeyValue("options", xsink);
        if (!p.isNothing()) {
            if (p.getType() != NT_HASH) {
                xsink->raiseException(DSC_ERR, "'options' key is not type 'hash' but is type '%s'", p.getTypeName());
                return;
            }
            const QoreHashNode* opt_hash = p.get<const QoreHashNode>();
            if (ds_set_options(*ds, opt_hash, xsink))
                return;
        }
    }

    if (queue)
        ds->setEventQueue(q.release(), arg.refSelf(), xsink);

    self->setPrivate(CID_DATASOURCE, ds.release());
}

static void create_datasource_from_conf(const QoreString& conf, QoreObject& self, Queue* queue, QoreValue arg,
        ExceptionSink* xsink) {
    ReferenceHolder<Queue> holder(queue, xsink);

    qore_offset_t colon = conf.find(':');
    qore_offset_t atsign = conf.find('@');
    if (colon < 0 || (atsign < colon)) {
        // do not report the string here to avoid leaking password info in case present
        xsink->raiseException("DATASOURCE-PARSE-ERROR", "driver is missing from the datasource string");
        return;
    }

    ReferenceHolder<QoreHashNode> h(parseDatasource(conf.c_str(), xsink), xsink);
    if (!h) {
        assert(*xsink);
        return;
    }

    ds_constructor_hash(*h, &self, holder.release(), arg, xsink);
}

static QoreHashNode* Datasource_serializer(const QoreObject& self, const ManagedDatasource& ds,
        QoreSerializationContext& context, ExceptionSink* xsink) {
    SimpleRefHolder<QoreStringNode> str(const_cast<ManagedDatasource&>(ds).getConfigString(xsink));
    if (*xsink) {
        return nullptr;
    }

    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(autoTypeInfo), xsink);
    rv->setKeyValue("conf", str.release(), xsink);
    return rv.release();
}

static void Datasource_deserializer(QoreObject& self, const QoreHashNode* sdata, QoreDeserializationContext& context,
        ExceptionSink* xsink) {
    if (sdata) {
        QoreValue val = sdata->getKeyValue("conf");
        if (val) {
            if (val.getType() != NT_STRING) {
                xsink->raiseException("DESERIALIZATION-ERROR", "Datasource serialization key 'conf' has type '%s'; "
                    "expecting 'string'", val.getTypeName());
                return;
            }
            create_datasource_from_conf(*val.get<QoreStringNode>(), self, nullptr, QoreValue(), xsink);
            return;
        }
    }

    xsink->raiseException("DESERIALIZATION-ERROR", "missing serialization data for the Datasource class");
}

/** @defgroup database_driver_constants Database Driver Constants
    These are contants that stand for database driver names known to Qore
*/
///@{
//! for the \c "oracle" driver
const DSOracle = "oracle";

//! for the \c "mysql" driver
const DSMySQL = "mysql";

//! for the \c "sybase" driver
const DSSybase = "sybase";

//! for the \c "pgsql" driver
const DSPGSQL = "pgsql";

//! for the \c "freetds" driver
const DSFreeTDS = "freetds";

//! another constant for the \c "freetds" driver
const DSMSSQL = "freetds";

//! for the \c "sqlite3" driver
const DSSQLite3 = "sqlite3";

//! for the \c "db2" driver
const DSDB2 = "db2";

//! for the \c "jdbc" driver
const DSJDBC = "jdbc";
///@}

/** @defgroup dbi_capabilities DBI Capability Constants
    These are contants that define the meaning of bits in the capability mask such as returned by
    Datasource::getCapabilities()
*/
///@{
//! Indicates that the DBI driver supports time zones in times
const DBI_CAP_TIME_ZONE_SUPPORT = DBI_CAP_TIME_ZONE_SUPPORT;

//! Indicates that the DBI driver supports proper character encoding conversions
const DBI_CAP_CHARSET_SUPPORT = DBI_CAP_CHARSET_SUPPORT;

//! Indicates that the DBI driver supports transaction management
const DBI_CAP_TRANSACTION_MANAGEMENT = DBI_CAP_TRANSACTION_MANAGEMENT;

//! Indicates that the DBI driver supports stored procedure execution
const DBI_CAP_STORED_PROCEDURES = DBI_CAP_STORED_PROCEDURES;

//! Indicates that the DBI driver supports LOB columns (BLOBs and CLOBs, for example)
const DBI_CAP_LOB_SUPPORT = DBI_CAP_LOB_SUPPORT;

//! Indicates that the DBI driver supports directly binding qore values into queries using the <tt>%%v</tt> placeholder in the query string
const DBI_CAP_BIND_BY_VALUE = DBI_CAP_BIND_BY_VALUE;

//! Indicates that the DBI driver supports binding placeholder buffers when executing %SQL to retrieve data from queries and procedures, etc
const DBI_CAP_BIND_BY_PLACEHOLDER = DBI_CAP_BIND_BY_PLACEHOLDER;

//! Indicates that the DBI driver supports the Datasource::execRaw() and DatasourcePool::execRaw() methods
const DBI_CAP_HAS_EXECRAW = DBI_CAP_HAS_EXECRAW;

//! Indicates that the DBI driver supports the prepared statement interface (the SQLStatement class)
const DBI_CAP_HAS_STATEMENT = DBI_CAP_HAS_STATEMENT;

//! Indicates that the DBI driver supports a native selectRow() method implementation
const DBI_CAP_HAS_SELECT_ROW = DBI_CAP_HAS_SELECT_ROW;

//! Indicates that the DBI driver supports arbitrary-precision numeric support for binding and retrieving values
const DBI_CAP_HAS_NUMBER_SUPPORT = DBI_CAP_HAS_NUMBER_SUPPORT;

//! Indicates that the DBI driver supports the new driver option API
const DBI_CAP_HAS_OPTION_SUPPORT = DBI_CAP_HAS_OPTION_SUPPORT;

//! Indicates that the DBI driver supports automatically converting date/time values to the server's presumed time zone (also implies that the driver supports the \c "timezone" option) and tagging date/time values with the same; this is independent from the client's current time zone setting
const DBI_CAP_SERVER_TIME_ZONE = DBI_CAP_SERVER_TIME_ZONE;

//! Indicates that the DBI driver supports automatically/transparently reconnecting to the server if the connection is lost while not in a transaction
const DBI_CAP_AUTORECONNECT = DBI_CAP_AUTORECONNECT;

//! Indicates that the DBI driver supports the event API
const DBI_CAP_EVENTS = DBI_CAP_EVENTS;

//! Indicates that the DBI driver supports the describe method
const DBI_CAP_HAS_DESCRIBE = DBI_CAP_HAS_DESCRIBE;

//! Indicates that the DBI driver supports binding arrays by value for bulk DML operations
const DBI_CAP_HAS_ARRAY_BIND = DBI_CAP_HAS_ARRAY_BIND;

//! Indicates that the DBI driver supports @ref resultset_output_binding "result set" output binding with a @ref Qore::SQL::SQLStatement "SQLStatement" output variable
/** @since %Qore 0.8.13
*/
const DBI_CAP_HAS_RESULTSET_OUTPUT = DBI_CAP_HAS_RESULTSET_OUTPUT;
///@}

//! This class provides the %Qore interface to databases
/** @par Overview
    This class provides the main direct interface to DBI drivers (along with the SQLStatement and DatasourcePool classes).\n\n
    The %Datasource class will attempt to load any DBI driver that is not currently loaded in the constructor. For connection pooling support, see the DatasourcePool class.\n\n
    %Datasource objects will implicitly call Datasource::open() if no connection has yet been established and a method is called requiring a connection to the database server. Therefore any method that requires communication with the database server can also throw any exception that the open method can throw.\n\n
    Most %Qore DBI drivers allow "select" queries to be executed through the Datasource::exec() method, and allow %SQL commands (procedure calls, etc) to be executed through the Datasource::select() method, and some DBI drivers do not (depends on the underlying DB API). At any rate, the transaction lock is set when auto-commit is disabled and when the Datasource::exec() or Datasource::beginTransaction() methods are executed as documented above. Therefore executing a transaction relevant command through the Datasource::select() method while auto-commit mode is disabled and a transaction has not yet started will not result in the transaction lock being allocated to the current thread and therefore could cause transaction errors when sharing the %Datasource object between multiple threads.\n\n
    Only databases with an existing %Qore DBI driver can be accessed through the %Datasource class.\n\n
    All %Qore DBI drivers set new connections to use transaction isolation level \c "read committed".\n\n
    The %Datasource class provides high-level, per-connection locking on requests at a level above the DBI drivers to ensure that the communication between clients and servers is properly serialized.\n\n
    %Datasource objects also have a default character encoding; all requests to the server will be made in this encoding, and all responses will be returned in the given encoding.  If no encoding is specifically given to the %Datasource object, the %Datasource object will use the @ref default_encoding.

    @anchor sql_binding
    @par SQL Binding by Value and by Placeholder Overview
    All %Datasource methods accepting %SQL strings to execute understand a special syntax used in the query string to bind %Qore data by value and to specify placeholders for output variables (for example, when executing a stored procedure or database function). Placeholder binding is DBI driver specific, but binding by value is supported with the same syntax in all drivers. Additionally, the \c %%d numeric specifier is supported equally in all %Qore DBI drivers.\n\n
    <b>%Datasource Format Specifiers</b>
    |!Format Specification|!Description
    |\c %%d|If any value other than @ref nothing or @ref null is given, then the value is converted to an integer and this value is substituted in the string at this position; if the value is @ref nothing or @ref null, then a literal \c "null" is substituted instead.
    |\c %%s|The argument is converted to a string and the string is inserted literally without any conversion or escape sequences in the string; this is useful for table or schema prefixes, etc
    |\c %%v|The argument is bound by value according to the DBI driver's implementation.

    @par SQL Binding Details
    To bind %Qore data values directly in a binary format in an %SQL command, use <tt>%%v</tt> in the command string, and include the value as an argument after the string. Binding by value allows the DBI driver to take care of formatting the data properly for use in the query with the database server. When binding by value, strings do not need to be quoted, date/time values do not need special formatting, binary objects (with BLOB columns, for example) can be used directly in queries, etc.\n\n
    Here is an example:
    @code{.py}
int rows = db.exec("insert into table (varchar_col, timestamp_col, blob_col, numeric_col) values (%v, %v, %v, %d)", string, now(), binary, 100);
    @endcode \n
    When using dynamic SQL, to insert a numeric value or a literal \c "null" in a query, use \c %%d in the command string, and include the value as an argument after the string. If the value is @ref nothing or @ref null, a literal \c "null" will be written to the string; otherwise the argument is converted to an arbitrary-precision number or integer value if necessary and written to the string. This is useful for working with \c DECIMAL (\c NUMERIC, \c NUMBER) types in a database-independent way; for example PostgreSQL servers do not do type conversions to \c DECIMAL types when a string, integer, or float is bound by value, therefore to ensure that integral decimal values can be used in a database-independent way (with \c "null" substitution when no value is bound), a valid approach is to use the \c %%d code in the command string instead of \c %%v.  However please note that \c %%v is normally preferred to keep the server-side statement cache a manageable size, in the previous example using the int(), float(), or number() functions to convert string values before binding with \c %%v may be better.\n\n
    For binding placeholders for output variables, write a unique name in the string and prefix it with a colon (ex: \c ":code"). In this case the method will return a hash of the output variables using the placeholder names as keys, but without the colon prefix. By default, a string type will be bound to the position. To bind other variable types to placeholder positions, include the type constant (see @ref sql_constants) as an argument after the command string. For BLOBs, use Binary, for CLOBs, use the string "clob" (constants will be provided in a future release). Not all DBI drivers require placeholder buffer specifications; see the documentation for the DBI driver in question for more information and examples regarding placeholder buffer specifications.

    @anchor resultset_output_binding
    @par Result Set Output Binding
    A result set can be returned in two ways depending on the placeholder buffer specification:
    - @ref Qore::Type::Hash "Hash": (default for drivers that do not require placeholder buffer specifications): returns the entire result set as data
    - @ref Qore::SQL::RESULTSET "RESULTSET": (for drivers that support @ref Qore::SQL::DBI_CAP_HAS_RESULTSET_OUTPUT):
      returns an @ref Qore::SQL::SQLStatement "SQLStatement" representing the result set.  This option allows a large result set to be
      returned in smaller pieces using the @ref Qore::SQL::SQLStatement "SQLStatement" API if desired.

    When using @ref Qore::SQL::RESULTSET "RESULTSET", the @ref Qore::SQL::SQLStatement "SQLStatement" object created
    acquires the @ref transaction_lock "transaction lock" even when used with a method that normally doesn't indicate
    that a transaction will be started such as select() or selectRows(), therefore the transaction lock must be
    released manually in such cases (just as with the normal usage of @ref Qore::SQL::SQLStatement "SQLStatement" objects);
    see the following example for details.

    @par Result Set Output Binding Example
    @code{.py}
    # example of a result set output variable with Oracle
    SQLStatement stmt = ds.select("begin open :rs for select * from my_table; end;", RESULTSET);
    # we have to release the transaction when done
    on_exit ds.rollback();
    # iterate through the rows of the result set
    map process_row($1), stmt;
    @endcode

    @anchor transaction_lock
    @par Datasource Transaction Locks
    %Datasource objects have an internal transaction lock which will be grabbed when the Datasource::exec(), Datasource::vexec() Datasource::execRaw(), or Datasource::beginTransaction() methods are executed and autocommit is not enabled. This enables a single datasource to be safely used for transaction management by several threads simultaneously. Note that an exception in a %Datasource method that would acquire the lock (such as the Datasource::exec() method) when it's not already held, will have the effect that the transaction lock is not acquired.\n\n
    Any thread attempting to do transaction-relevant actions on a %Datasource with auto-commit disabled while a transaction is in progress by another thread will block until the thread currently executing a transaction executes the Datasource::commit() or Datasource::rollback() methods (or the %Datasource is deleted, reset, or closed, in which case the lock is released and an exception is raised as well).\n\n
    There is a timeout associated with the transaction lock; if a thread waits for the transaction lock for more than the timeout period, then an exception will be raised in the waiting thread. The timeout value can be read and changed with the Datasource::getTransactionLockTimeout() and Datasource::setTransactionLockTimeout() methods, respectively. The default transaction lock timeout value is 120 seconds.\n\n
    Note that the @ref Qore::SQL::SQLStatement "SQLStatement" class also grabs the transaction lock when executing if it is created using a %Datasource object in the constructor; for more information see the  @ref Qore::SQL::SQLStatement "SQLStatement" class documentation.

    @par Thread Resource Handling
    The Datasource class manages the transaction lock as a @ref thread_resources "thread resource"; if the
    transaction is not released with a call to @ref Qore::SQL::Datasource::commit() "Datasource::commit()"
    or @ref Qore::SQL::Datasource::rollback() "Datasource::rollback()" when the
    thread exits (or when @ref Qore::throw_thread_resource_exceptions() or
    @ref Qore::throw_thread_resource_exceptions_to_mark() is called), the transaction is rolled back automatically
    and a \c DATASOURCE-TRANSACTION-EXCEPTION exception is raised describing the situation.\n\n
    Being an builtin class, the Datasource class does not
    inherit @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource" explicitly as a part of the exported API,
    and the internal @ref Qore::Thread::AbstractThreadResource::cleanup() "AbstractThreadResource::cleanup()" method
    cannot be overridden or suppressed.

    @par Data Serialization
    The Datasource class supports @ref Serializable "data serialization"; deserialization can fail if the database is not
    supported on or reachable from the target machine.

    When deserializing, any datasource event queue is lost; only the connection information is propagated in the
    deserialized copy.

    @note This class is not available with the @ref PO_NO_DATABASE parse option

    @see <a href="../../modules/SqlUtil/html/index.html">SqlUtil</a> for a high level database-independent API
*/
qclass Datasource [dom=DATABASE; arg=ManagedDatasource* ds; ns=Qore::SQL; vparent=AbstractDatasource, Serializable; serializer=reinterpret_cast<q_serializer_t>(Datasource_serializer); deserializer=Datasource_deserializer];

//! Creates the Datasource object; attempts to load a DBI driver if the driver is not already present in %Qore
/** @param driver The name of the DBI driver for the Datasource. See @ref sql_constants for builtin constants for DBI
    drivers shipped with %Qore, or see the DBI driver documentation to use an add-on driver (this string should be the
    name of the driver to be loaded)
    @param user The user name for the new connection. Also see Datasource::setUserName() for a method that allows this
    parameter to be set after the constructor.
    @param pass The password for the new connection. Also see Datasource::setPassword() for a method that allows this
    parameter to be set after the constructor.
    @param db The database name for the new connection. Also see Datasource::setDBName() for a method that allows this
    parameter to be set after the constructor.
    @param encoding The database-specific name of the @ref character_encoding "character encoding" to use for the new
    connection. Also see Datasource::setDBCharset() for a method that allows this parameter to be set after the
    constructor. If no value is passed for this parameter, then the database character encoding corresponding to the
    @ref default_encoding "default character encoding" will be used instead.
    @param host The host name for the new connection. Also see Datasource::setHostName() for a method that allows this
    parameter to be set after the constructor.
    @param port The port number for the new connection. Also see Datasource::setPort() for a method that allows this
    parameter to be set after the constructor.
    @param options Any options for the database driver for the new connection
    @param queue An optional @ref Qore::Thread::Queue "Queue" object to receive datasource events; note that the
    @ref Qore::Thread::Queue "Queue" passed cannot have any maximum size set or a \c QUEUE-ERROR will be thrown;
    passing @ref nothing will clear any event queue
    @param arg an optional argument to be included in the \c arg key of datasource events

    @par Example:
    @code{.py}
Datasource db(DSPGSQL, "user", "pass", "database", "utf8", "localhost", 5432);
    @endcode

    @throw DATASOURCE-UNSUPPORTED-DATABASE DBI driver cannot be loaded
    @throw DATASOURCE-CONSTRUCTOR-ERROR port value is \<= 0
    @throw DBI-OPTION-ERROR unknown or unsupported option passed to driver
*/
Datasource::constructor(string driver, *string user, *string pass, *string db, *string encoding, *string host, *softint port, *hash<auto> options, *Qore::Thread::Queue[Queue] queue, auto arg) {
    ReferenceHolder<Queue> q(queue, xsink);

    if (!port && driver->find('@') != -1) {
        qore_offset_t colon = driver->find(':');
        qore_offset_t atsign = driver->find('@');
        if (colon < 0 || (atsign < colon)) {
            xsink->raiseException("DATASOURCE-PARSE-ERROR", "driver is missing from the datasource string");
            return;
        }

        ReferenceHolder<QoreHashNode> h(parseDatasource(driver->c_str(), xsink), xsink);
        if (!h) {
            assert(*xsink);
            return;
        }
        ds_constructor_hash(*h, self, q.release(), arg, xsink);
        return;
    }

    DBIDriver *db_driver = DBI.find(driver->c_str(), xsink);
    if (!db_driver) {
        if (!*xsink)
        xsink->raiseException("DATASOURCE-UNSUPPORTED-DATABASE", "DBI driver '%s' cannot be loaded", driver->c_str());
        return;
    }

    ReferenceHolder<ManagedDatasource> ds(new ManagedDatasource(db_driver), xsink);

    if (user && !user->empty())
        ds->setPendingUsername(user->c_str());

    if (pass && !pass->empty())
        ds->setPendingPassword(pass->c_str());

    if (db && !db->empty())
        ds->setPendingDBName(db->c_str());

    if (encoding && !encoding->empty())
        ds->setPendingDBEncoding(encoding->c_str());

    if (host && !host->empty())
        ds->setPendingHostName(host->c_str());

    if (port) {
        if (port < 0) {
            xsink->raiseException(DSC_ERR, "port value must be zero (meaning use the default port) or positive "
                "(value given: %d)", port);
            return;
        }
        ds->setPendingPort((int)port);
    }

    if (options)
        ds_set_options(*ds, options, xsink);

    if (queue)
        ds->setEventQueue(q.release(), arg.refSelf(), xsink);

    self->setPrivate(CID_DATASOURCE, ds.release());
}

//! Creates a Datasource object from a single string giving all parameters that can be parsed by parse_datasource()
/** @param desc a datasource description string in the format that can be parsed by parse_datasource()
    @param queue An optional @ref Qore::Thread::Queue "Queue" object to receive datasource events; note that the
    @ref Qore::Thread::Queue "Queue" passed cannot have any maximum size set or a \c QUEUE-ERROR will be thrown;
    passing @ref nothing will clear any event queue
    @param arg an optional argument to be included in the \c arg key of datasource events

    @par Example:
    @code{.py}
Datasource ds("pgsql:user/pass@db01(utf8)%localhost:5432");
    @endcode

    @throw DATASOURCE-UNSUPPORTED-DATABASE DBI driver cannot be loaded
    @throw DATASOURCE-CONSTRUCTOR-ERRO missing required parameter for connection; port value is \<= 0
    @throw DBI-OPTION-ERROR unknown or unsupported option passed to driver

    @since %Qore 0.8.6
*/
Datasource::constructor(string desc, *Qore::Thread::Queue[Queue] queue, auto arg) {
    ReferenceHolder<Queue> q(queue, xsink);

    TempEncodingHelper tstr(desc, QCS_UTF8, xsink);
    if (*xsink) {
        return;
    }

    create_datasource_from_conf(**tstr, *self, q.release(), arg, xsink);
}

//! Creates a Datasource object from a hash argument giving parameters for the constructor
/** @param opts a hash giving parameters for the new datasource with the following possible keys (the \c "type" key is
    mandatory, also usable with the output of the parse_datasource() function):
    - \c type: (@ref string_or_nothing_type "*string") The name of the database driver to use; this key is mandatory;
      if not present, an exception will be raised. See @ref sql_constants for builtin constants for DBI drivers
      shipped with Qore, or see the DBI driver documentation to use an add-on driver (this string should be the name
      of the driver to be loaded)
    - \c user: (@ref string_or_nothing_type "*string") The user name for the new connection. Also see
      Datasource::setUserName() for a method that allows this parameter to be set after the constructor
    - \c pass: (@ref string_or_nothing_type "*string") The password for the new connection. Also see
      Datasource::setPassword() for a method that allows this parameter to be set after the constructor
    - \c db: (@ref string_or_nothing_type "*string") The database name for the new connection. Also see
      Datasource::setDBName() for a method that allows this parameter to be set after the constructor
    - \c charset: (@ref string_or_nothing_type "*string") The database-specific name of the character encoding to use
      for the new connection. Also see Datasource::setDBCharset() for a method that allows this parameter to be set
      after the constructor. If no value is passed for this parameter, then the database character encoding
      corresponding to the default character encoding for the %Qore process will be used instead
    - \c host: (@ref string_or_nothing_type "*string") The host name for the new connection. Also see
      Datasource::setHostName() for a method that allows this parameter to be set after the constructor
    - \c port: (@ref softint_type "softint") The port number for the new connection. Also see Datasource::setPort()
      for a method that allows this parameter to be set after the constructor. If this key is present and is 0 then an
      exception will be raised.
    - \c options: (@ref hash_or_nothing_type "*hash") Any options for the new connection
    @param queue An optional @ref Qore::Thread::Queue "Queue" object to receive datasource events; note that the
    @ref Qore::Thread::Queue "Queue" passed cannot have any maximum size set or a \c QUEUE-ERROR will be thrown;
    passing @ref nothing will clear any event queue
    @param arg an optional argument to be included in the \c arg key of datasource events

    @par Example:
    @code{.py}
Datasource db(("type": DSPGSQL, "user": "username", "pass": "password", "db": "database", "charset": "utf8", "host": "localhost", "port": 5432);
    @endcode

    @throw DATASOURCE-UNSUPPORTED-DATABASE DBI driver cannot be loaded
    @throw DATASOURCE-CONSTRUCTOR-ERROR missing or invalid \c "driver" key, other key name not assigned to a string;
    port value is \<= 0
    @throw DBI-OPTION-ERROR unknown or unsupported option passed to driver
*/
Datasource::constructor(hash<auto> opts, *Qore::Thread::Queue[Queue] queue, auto arg) {
    ds_constructor_hash(opts, self, queue, arg, xsink);
}

//! Closes the datasource if it's open (if any operations are in progress, will block until the operations complete) and destroys the object
/**
    @par Example:
    @code{.py}
delete db;
    @endcode

    @throw DATASOURCE-TRANSACTION-EXCEPTION The Datasource was destroyed while a transaction was still in progress;
    the transaction will be automatically rolled back
*/
Datasource::destructor() {
    ds->destructor(xsink);
    ds->deref(xsink);
}

//! Creates a new Datasource object with the same driver as the original and copies of all the connection parameters
/**
    @par Example:
    @code{.py}
Datasource new_ds = ds.copy();
    @endcode
*/
Datasource::copy() {
    self->setPrivate(CID_DATASOURCE, ds->copy());
}

//! Opens a connection to the datasouce, using the connection parameters already set; an exception is thrown if any errors occur
/** If the connection is already open, then no action is taken.

    @par Example:
    @code{.py}
db.open();
    @endcode

    @note see the documentation for the DBI driver being used for possible exceptions

    @see Datasource::reset()
*/
nothing Datasource::open() {
    ds->open(xsink);
}

//! Closes the connection to the database; if any actions are in progress on the database, the close call will block until the actions complete. If any errors are encountered, an exception is raised
/**
    @par Example:
    @code{.py}
db.close();
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
nothing Datasource::close() {
    ds->close(xsink);
}

//! Commits the current transaction and releases the @ref transaction_lock "transaction lock"
/**
    @par Example:
    @code{.py}
db.commit();
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
nothing Datasource::commit() {
    ds->commit(xsink);
}

//! Rolls the current transaction back and releases the @ref transaction_lock "transaction lock"
/**
    @par Example:
    @code{.py}
db.rollback();
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
nothing Datasource::rollback() {
    ds->rollback(xsink);
}

//! Turns autocommit on or off for this object
/** @param ac @ref True to turn on autocommit (a commit will be executed after every %SQL call to the server),
    @ref False to turn off autocommit (commits must be manually triggered)
    @par Example:
    @code{.py}
db.setAutoCommit(False);
    @endcode
*/
nothing Datasource::setAutoCommit(bool ac = True) {
    ds->setAutoCommit(ac, xsink);
}

//! Returns the autocommit status for the object
/** @return the autocommit status for the object

    @par Example:
    @code{.py}
bool b = db.getAutoCommit();
    @endcode
*/
bool Datasource::getAutoCommit() [flags=CONSTANT] {
    return ds->getAutoCommit();
}

//! Grabs the transaction lock (if autocommit is disabled) and executes an %SQL command on the server and returns either the integer row count (for example, for updates, inserts, and deletes) or the data retrieved (for example, if a stored procedure is executed that returns values).
/** This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in
    @ref sql_binding "Binding by Value and Placeholder"

    @param sql The %SQL command to execute on the server
    @param ... Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications
    (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return The return value depends on the DBI driver; normally, for commands with placeholders, a hash is returned
    holding the values acquired from executing the %SQL statement. For all other commands, normally an integer row
    count is returned. However, some DBI drivers also allow select statements to be executed through this interface,
    which would also return a hash (column names) of lists (values for each column).

    @par Example:
    @code{.py}
int rows = db.exec("insert into table (varchar_col, timestamp_col, blob_col, numeric_col) values (%v, %v, %v, %d)", string, now(), binary, 100);
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
auto Datasource::exec(string sql, ...) {
    ReferenceHolder<QoreListNode> argl((args->size() > 1 ? args->copyListFrom(1) : 0), xsink);
    return ds->exec(sql, *argl, xsink);
}

//! Grabs the transaction lock (if autocommit is disabled) and executes %SQL code on the DB connection, taking a list for all bind arguments
/** Same as Datasource::exec() except takes an explicit list for bind arguments

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in
    @ref sql_binding "Binding by Value and Placeholder"

    @param sql The %SQL command to execute on the server
    @param vargs Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder
    specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return The return value depends on the DBI driver; normally, for commands with placeholders, a hash is returned
    holding the values acquired from executing the %SQL statement. For all other commands, normally an integer row
    count is returned. However, some DBI drivers also allow select statements to be executed through this interface,
    which would also return a hash (column names) of lists (values for each column).

    @par Example:
    @code{.py}
int rows = db.vexec("insert into example_table value (%v, %v, %v)", arg_list);
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
auto Datasource::vexec(string sql, *softlist<auto> vargs) {
    return ds->exec(sql, vargs, xsink);
}

//! Grabs the transaction lock (if autocommit is disabled) and executes an %SQL command on the server and returns either the row count (for example, for updates and inserts) or the data retrieved (for example, if a stored procedure is executed that returns values)
/** This method does not do any variable binding, so it's useful for example for DDL statements etc

    @par Warning:
    Using this method to execute pure dynamic %SQL many times with different %SQL strings (as opposed to using the
    same string and binding by value instead of dynamic %SQL) can affect application performance by prohibiting the
    efficient usage of the DB server's statement cache. See DB server documentation for variable binding and the %SQL
    statement cache for more information.

    @param sql The %SQL command to execute on the server; this string will not be subjected to any transformations for
    variable binding

    @return The return value depends on the DBI driver; normally, for commands with placeholders, a hash is returned
    holding the values acquired from executing the %SQL statement. For all other commands, normally an integer row
    count is returned. However, some DBI drivers also allow select statements to be executed through this interface,
    which would also return a hash (column names) of lists (values for each column).

    @par Example:
    @code{.py}
db.execRaw("create table my_tab (id number, some_text varchar2(30))");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
auto Datasource::execRaw(string sql) {
    return ds->execRaw(sql, xsink);
}

//! Executes an %SQL select statement on the server and returns the result as a hash (column names) of lists (column values per row)
/** The return format of this method is suitable for use with @ref context "context statements", for easy iteration and processing of query results.
    Alternatively, the HashListIterator class can be used to iterate the return value of this method.

    Additionally, this format is a more efficient format than that returned by the Datasource::selectRows() method, because the column names are not repeated for each row returned. Therefore, for retrieving anything greater than small amounts of data, it is recommended to use this method instead of Datasource::selectRows().

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param ... Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return This method returns a hash (the keys are the column names) of lists (the column data per row) when executed with an %SQL select statement, however some DBI drivers allow any %SQL to be executed through this method, in which case other data types can be returned (such as an integer for a row count or a hash for output parameters when executing a stored procedure).  If no rows are found, a hash of column names assigned to empty lists is returned.

    @par Example:
    @code{.py}
# bind a string and a date/time value by value in a query
hash query = db.select("select * from table where varchar_column = %v and timestamp_column > %v", string, 2007-10-11T15:31:26.289);
if (query.firstValue())
    printf("got results\n");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note
    - See the documentation for the DBI driver being used for additional possible exceptions
    - This method returns all the data available immediately; to process query data piecewise, use the SQLStatement class

    @see
    - Datasource::vselect()
    - <hash>::contextIterator()
    - @ref context
*/
auto Datasource::select(string sql, ...) {
    ReferenceHolder<QoreListNode> argl((args->size() > 1 ? args->copyListFrom(1) : 0), xsink);
    return ds->select(sql, *argl, xsink);
}

//! Executes an %SQL select statement on the server and returns the first row as a hash (the column values)
/** If more than one row is returned, then it is treated as an error and a \c DBI-SELECT-ROW-ERROR is returned (however the DBI driver should raise its own exception here to avoid retrieving more than one row from the server). For a similar method taking a list for all bind arguments, see Datasource::vselectRow().

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param ... Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return This method returns a hash (the keys are the column names) of row data or @ref nothing if no row is found for the query when executed with an %SQL select statement

    @par Example:
    @code{.py}
*hash<auto> h = db.selectRow("select * from example_table where id = 1");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock
    @throw DBI-SELECT-ROW-ERROR more than 1 row retrieved from the server

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
*hash<auto> Datasource::selectRow(string sql, ...) {
    ReferenceHolder<QoreListNode> argl((args->size() > 1 ? args->copyListFrom(1) : 0), xsink);
    return ds->selectRow(sql, *argl, xsink);
}

//! Executes an %SQL select statement on the server and returns the result as a list (rows) of hashes (the column values)
/** The return format of this method is not as memory efficient as that returned by the Datasource::select() method, therefore for larger amounts of data, it is recommended to use Datasource::select().

    The usual return value of this method can be iterated with the ListHashIterator class.

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param ... Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return Normally returns a list (rows) of hash (where the keys are the column names of each row) or @ref nothing if no rows are found for the query, however some DBI drivers allow any %SQL statement to be executed through this method (not only select statements), in this case other data types can be returned

    @par Example:
    @code{.py}
*list<auto> list = db.selectRows("select * from example_table");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note
    - See the documentation for the DBI driver being used for additional possible exceptions
    - This method returns all the data available immediately; to process query data piecewise, use the SQLStatement class

    @see Datasource::select()
*/
auto Datasource::selectRows(string sql, ...) {
    ReferenceHolder<QoreListNode> argl((args->size() > 1 ? args->copyListFrom(1) : 0), xsink);
    return ds->selectRows(sql, *argl, xsink);
}

//! Executes a select statement on the server and returns the results in a hash (column names) of lists (column values per row), taking a list for all bind arguments
/** The return format of this method is suitable for use with @ref context "context statements", for easy iteration and processing of query results.
    Alternatively, the HashListIterator class can be used to iterate the return value of this method.

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param vargs Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return Normally returns a hash (the keys are the column names) of list (each hash key's value is a list giving the row data), however some DBI drivers allow any %SQL statement to be executed through this method (not only select statements), in this case other data types can be returned.  If no rows are found, a hash of column names assigned to empty lists is returned.

    @par Example:
    @code{.py}
hash<auto> query = db.vselect("select * from example_table where id = %v and name = %v", arg_list);
if (query.firstValue())
    printf("got results\n");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note
    - See the documentation for the DBI driver being used for additional possible exceptions
    - This method returns all the data available immediately; to process query data piecewise, use the SQLStatement class

    @see
    - Datasource::select()
    - <hash>::contextIterator()
    - @ref context
*/
auto Datasource::vselect(string sql, *softlist<auto> vargs) {
    return ds->select(sql, vargs, xsink);
}

//! Executes a select statement on the server and returns the first row as a hash (column names and values), taking a list for all bind arguments
/** This method is the same as the Datasource::selectRow() method, except this method takes a single argument after the %SQL command giving the list of bind value parameters

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param vargs Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return This method returns a hash (the keys are the column names) of row data or @ref nothing if no row is found for the query when executed with an %SQL select statement

    @par Example:
    @code{.py}
*hash<auto> h = db.vselectRow("select * from example_table where id = %v and type = %v", arg_list);
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions

    @see Datasource::selectRow()
*/
*hash<auto> Datasource::vselectRow(string sql, *softlist<auto> vargs) {
    return ds->selectRow(sql, vargs, xsink);
}

//! Executes a select statement on the server and returns the results in a list (rows) of hashes (column names and values), taking a list for all bind arguments
/** Same as the Datasource::selectRows() method, except this method takes a single argument after the %SQL command giving the list of bind value parameters.

    The usual return value of this method can be iterated with the ListHashIterator class.

    The return format of this method is not as memory efficient as that returned by the Datasource::select() method, therefore for larger amounts of data, it is recommended to use Datasource::select().

    This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param vargs Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return Normally returns a list (rows) of hash (where the keys are the column names of each row) or @ref nothing if no rows are found for the query, however some DBI drivers allow any %SQL statement to be executed through this method (not only select statements), in this case other data types can be returned

    @par Example:
    @code{.py}
*list<auto> list = db.vselectRows("select * from example_table where id = %v and type = %v", arg_list);
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note
    - See the documentation for the DBI driver being used for additional possible exceptions
    - This method returns all the data available immediately; to process query data piecewise, use the SQLStatement class

    @see Datasource::selectRows()
 */
auto Datasource::vselectRows(string sql, *softlist<auto> vargs) {
    return ds->selectRows(sql, vargs, xsink);
}

//! Executes an %SQL select statement on the server and returns a description of the result set as a hash
/** This method also accepts all bind parameters (<tt>%%d</tt>, <tt>%%v</tt>, <tt>%%s</tt>, etc) as documented in @ref sql_binding "Binding by Value and Placeholder"

    This method does not retain the transaction lock if it was not already acquired before this method is called, so to execute select statements that begin a transaction (such as \c "select for update"), execute Datasource::beginTransaction() first to ensure that the transaction lock is dedicated to the calling thread.

    @param sql The %SQL command to execute on the server
    @param ... Include any values to be bound (using <tt>%%v</tt> in the command string) or placeholder specifications (using <tt>:</tt><em>key_name</em> in the command string) in order after the command string

    @return a hash describing the result set with the following keys:
    - \c name: the name of the column
    - \c type: a string giving the type of the column
    - \c maxsize: an integer giving the column size
    - \c native_type: a string giving the native column type
    - \c internal_id: an integer giving the native column type code

    @par Example:
    @code{.py}
*hash<auto> h = db.describe("select * from example_table where id = 1");
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions

    @since %Qore 0.8.9
 */
*hash<auto> Datasource::describe(string sql, ...) {
    ReferenceHolder<QoreListNode> argl((args->size() > 1 ? args->copyListFrom(1) : 0), xsink);
    return ds->describe(sql, *argl, xsink);
}

//! Manually grabs the transaction lock
/** This method should be called when the Datasource object will be shared between more than 1 thread, and a transaction will be started with a Datasource::select() method or the like.

    This method does not make any communication with the server to start a transaction; it only allocates the transaction lock to the current thread in %Qore.

    It is an error to call this method when autocommit is enabled for the Datasource.

    @par Example:
    @code{.py}
db.beginTransaction();
    @endcode

    @throw AUTOCOMMIT-ERROR Cannot start a transaction when autocommit is enabled
    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock
 */
nothing Datasource::beginTransaction() {
    ds->beginTransaction(xsink);
}

//! Closes and reopens the Datasource
/**
    @par Example:
    @code{.py}
db.reset();
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
 */
nothing Datasource::reset() {
    ds->reset(xsink);
}

//! Returns an integer bitfield of DBI driver capabilities
/** @return an integer bitfield of DBI driver capabilities; see @ref dbi_capabilities for the meaning of each bit

    @par Example:
    @code{.py}
int caps = db.getCapabilities();
if (!(caps & DBI_CAP_TRANSACTION_MANAGEMENT))
    throw "DATASOURCE-ERROR", sprintf("DBI driver %y does not support transaction management", db.getDriverName());
    @endcode
 */
int Datasource::getCapabilities() [flags=CONSTANT] {
    return ds->getCapabilities();
}

//! Returns a list of strings giving the capabilities of the current DBI driver
/** @return a list of strings giving the capabilities of the current DBI driver

    @par Example:
    @code{.py}
printf("driver %y has the following capabilities:\n", db.getDriverName());
foreach string cap in (db.getCapabilityList())
    printf("- %s\n", cap);
    @endcode
*/
list<auto> Datasource::getCapabilityList() [flags=CONSTANT] {
    return ds->getCapabilityList();
}

//! Sets the username parameter for the time a connection to the server is established
/** Invalid usernames will cause an exception to be thrown when the connection is opened

    @param user the username parameter for the time a connection to the server is established

    @par Example:
    @code{.py}
db.setUserName(user);
    @endcode
*/
nothing Datasource::setUserName(string user) {
    ds->setPendingUsername(user->c_str());
}

//! Sets the password parameter for the time a connection to the server is established
/** Invalid passwords will cause an exception to be thrown when the connection is opened

    @param pass the password parameter for the time a connection to the server is established

    @par Example:
    @code{.py}
db.setPassword(pass);
    @endcode
*/
nothing Datasource::setPassword(string pass) {
    ds->setPendingPassword(pass->c_str());
}

//! Sets the database name parameter for the time a connection to the server is established
/** Invalid database names will cause an exception to be thrown when the connection is opened

    @param db the database name parameter for the time a connection to the server is established

    @par Example:
    @code{.py}
db.setDBName(db);
    @endcode
*/
nothing Datasource::setDBName(string db) {
    ds->setPendingDBName(db->c_str());
}

//! Sets the database-specific character encoding name for the next connection to the server
/** This is a method synonym for the Datasource::setDBEncoding() method, kept for backwards-compatibility.

    Invalid character encoding names will cause an exception to be thrown when the connection is opened.

    @param encoding the database-specific character encoding name for the next connection to the server

    @par Example:
    @code{.py}
db.setDBCharset(encoding);
    @endcode
*/
nothing Datasource::setDBCharset(string encoding) {
    ds->setPendingDBEncoding(encoding->c_str());
}

//! Sets the database-specific character encoding name for the next connection to the server
/** Invalid character encoding names will cause an exception to be thrown when the connection is opened.

    @param encoding the database-specific character encoding name for the next connection to the server

    @par Example:
    @code{.py}
db.setDBEncoding(encoding);
    @endcode
*/
nothing Datasource::setDBEncoding(string encoding) {
    ds->setPendingDBEncoding(encoding->c_str());
}

//! Sets the hostname to use for the next connection to the server
/** Invalid hostnames will cause an exception to be thrown when the connection is opened.

    @param host the hostname to use for the next connection to the server

    @par Example:
    @code{.py}
db.setHostName(host);
    @endcode
*/
nothing Datasource::setHostName(string host) {
    ds->setPendingHostName(host->c_str());
}

//! Sets the port number to use for the connection
/** Invalid port numbers will cause an exception to be thrown when the connection is opened

    @param port the port number to use for the connection

    @par Example:
    @code{.py}
db.setPort(port);
    @endcode
*/
nothing Datasource::setPort(softint port = 0) {
    ds->setPendingPort((int)port);
}

//! Returns the username parameter as a string or @ref nothing if none is set
/** @return the username parameter as a string or @ref nothing if none is set

    @par Example:
    @code{.py}
*string user = db.getUserName();
    @endcode
*/
*string Datasource::getUserName() [flags=CONSTANT] {
    return ds->getPendingUsername();
}

//! Returns the password parameter as a string or @ref nothing if none is set
/** @return the password parameter as a string or @ref nothing if none is set

    @par Example:
    @code{.py}
*string pass = db.getPassword();
    @endcode
*/
*string Datasource::getPassword() [flags=CONSTANT] {
    return ds->getPendingPassword();
}

//! Returns the database name parameter as a string or @ref nothing if none is set
/** @return the database name parameter as a string or @ref nothing if none is set

    @par Example:
    @code{.py}
*string db = db.getDBName();
    @endcode
*/
*string Datasource::getDBName() [flags=CONSTANT] {
    return ds->getPendingDBName();
}

//! Retrieves the database-specific charset set encoding for the current connection
/** A method synonym for Datasource::getDBEncoding() kept for backwards-compatibility.

    @return the database-specific charset set encoding for the current connection

    @par Example:
    @code{.py}
*string enc = db.getDBCharset();
    @endcode
*/
*string Datasource::getDBCharset() [flags=CONSTANT] {
    return ds->getPendingDBEncoding();
}

//! Retrieves the database-specific charset set encoding for the current connection
/** @return the database-specific charset set encoding for the current connection

    @par Example:
    @code{.py}
*string enc = db.getDBEncoding();
    @endcode

    @see Datasource::getOSEncoding();
*/
*string Datasource::getDBEncoding() [flags=CONSTANT] {
    return ds->getPendingDBEncoding();
}

//! Returns the %Qore character encoding name for the current connection as a string or \c "(unknown)" if none is set
/** @return the %Qore character encoding name for the current connection as a string or \c "(unknown)" if none is set

    @par Example:
    @code{.py}
string enc = db.getOSCharset();
    @endcode

    @see Datasource::getOSEncoding()
*/
string Datasource::getOSCharset() [flags=CONSTANT] {
    const QoreEncoding *enc = ds->getQoreEncoding();
    return new QoreStringNode(enc ? enc->getCode() : "(unknown)");
}

//! Returns the %Qore character encoding name for the current connection as a string or @ref nothing if none is set
/** @return the %Qore character encoding for the current connection as a string or @ref nothing if none is set

    @par Example:
    @code{.py}
*string enc = db.getOSEncoding();
    @endcode
*/
*string Datasource::getOSEncoding() [flags=CONSTANT] {
    const QoreEncoding *enc = ds->getQoreEncoding();
    return enc ? new QoreStringNode(enc->getCode()) : 0;
}

//! Returns the hostname parameter as a string or @ref nothing if none is set
/** @return the hostname parameter as a string or @ref nothing if none is set

    @par Example:
    @code{.py}
*string host = db.getHostName();
    @endcode
*/
*string Datasource::getHostName() [flags=CONSTANT] {
    return ds->getPendingHostName();
}

//! Gets the port number that will be used for the next connection to the server
/** Invalid port numbers will cause an exception to be thrown when the connection is opened

    @par Example:
    @code{.py}
*int port = db.getPort();
    @endcode
*/
*int Datasource::getPort() [flags=CONSTANT] {
    int port = ds->getPort();
    return port ? QoreValue(port) : QoreValue();
}

//! Sets the transaction lock timeout value in milliseconds; set to 0 for no timeout
/** @param timeout_ms the transaction lock timeout value in milliseconds; set to 0 for no timeout.  Like all %Qore functions and methods taking timeout values, a @ref relative_dates "relative date/time value" may be passed instead of an integer to make the timeout units clear (ex: \c 2500ms for 2.5 seconds).

    @par Example:
    @code{.py}
db.setTransactionLockTimeout(4s);
    @endcode
*/
nothing Datasource::setTransactionLockTimeout(timeout timeout_ms = 0) {
    ds->setTransactionLockTimeout((int)timeout_ms);
}

//! Retrieves the transaction lock timeout value as an integer in milliseconds
/** @return the transaction lock timeout value as an integer in milliseconds

    @par Example:
    @code{.py}
int to_ms = db.getTransactionLockTimeout();
    @endcode
*/
int Datasource::getTransactionLockTimeout() [flags=CONSTANT] {
    return ds->getTransactionLockTimeout();
}

//! Returns the name of the driver used for the object
/** @return the name of the driver used for the object

    @par Example:
    @code{.py}
string driver = db.getDriverName();
    @endcode

    @see getDriverRealName()
*/
string Datasource::getDriverName() [flags=CONSTANT] {
    return new QoreStringNode(ds->getDriver()->getName());
}

//! Returns the driver-specific server version data for the current connection
/** @return the driver-specific server version data for the current connection; see the DBI driver documentation for the return data type and format

    @par Example:
    @code{.py}
auto ver = db.getServerVersion();
    @endcode

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @note see the documentation for the DBI driver being used for additional possible exceptions
*/
auto Datasource::getServerVersion() {
    return ds->getServerVersion(xsink);
}

//! Returns the real DB driver name if supported by the driver, otherwise the %Qore driver name
/** @return the real DB driver name if supported by the driver, otherwise the %Qore driver name

    @par Example:
    @code{.py}
string driver = db.getDriverRealName();
    @endcode

    The return value will differ from getDriverName() in the case of "wrapper" drivers such as \c odbc or \c jdbc,
    where the generic %Qore driver name is insufficient to identify the database server type.

    In other cases, this method will return the same value as getDriverName()

    @since %Qore 1.14
*/
string Datasource::getDriverRealName() [flags=CONSTANT] {
    return ds->ManagedDatasource::getDriverRealName(xsink);
}

//! Retrieves the driver-specific client library version information; this method may not be implemented for all drivers
/** @return the driver-specific client library version information; this method may not be implemented for all drivers; see the DBI driver documentation for the return data type and format

    @par Example:
    @code{.py}
auto ver = db.getClientVersion();
    @endcode

    @note see the documentation for the DBI driver being used for possible exceptions
*/
auto Datasource::getClientVersion() {
    return ds->getClientVersion(xsink);
}

//! Returns @ref True if a transaction is currently in progress, @ref False if not
/** @return @ref True if a transaction is currently in progress, @ref False if not

    @par Example:
    @code{.py}
bool b = db.inTransaction();
    @endcode
*/
bool Datasource::inTransaction() [flags=CONSTANT] {
    return ds->isInTransaction();
}

//! Returns @ref True if the current thread is in a transaction (i.e. holds the transaction lock), @ref False if not
/** @return @ref True if the current thread is in a transaction (i.e. holds the transaction lock), @ref False if not

    @par Example:
    @code{.py}
bool b = db.currentThreadInTransaction();
    @endcode

    @see Datasource::transactionTid()

    @since %Qore 0.8.7
*/
bool Datasource::currentThreadInTransaction() [flags=CONSTANT] {
    return ds->currentThreadInTransaction();
}

//! Returns the TID of the thread holding the transaction lock or -1 if it's not currently held
/** If the Datasource object is used in a multithreaded context and if the transaction lock is not held by the current thread, then the
    transaction lock status could change at any time.

    @return the TID of the thread holding the transaction lock or -1 if it's not currently held

    @par Example:
    @code{.py}
int tid = db.transactionTid();
    @endcode

    @see Datasource::currentThreadInTransaction()

    @since %Qore 0.8.7
*/
int Datasource::transactionTid() [flags=CONSTANT] {
    return ds->transactionTid();
}

//! returns the valid options for the driver associated with the Datasource with descriptions and current values for the current Datasource object
/** @return a hash where the keys are valid option names, and the values are hashes with the following keys:
    - \c "desc": a string description of the option
    - \c "type": a string giving the data type restriction for the option
    - \c "value": the current value of the option

    @note in order to ensure atomicity when dealing with Datasource options, the transaction lock is acquired before executing this method if it was not already owned by the calling thread

    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @since %Qore 0.8.6
*/
hash<auto> Datasource::getOptionHash() [flags=RET_VALUE_ONLY] {
    return ds->getOptionHash(xsink);
}

//! sets an option for the datasource
/** @param opt the option to set
    @param val the value to set

    @note in order to ensure atomicity when dealing with Datasource options, the transaction lock is acquired before executing this method if it was not already owned by the calling thread

    @throw DBI-OPTION-ERROR unknown or unsupported option passed to driver
    @throw DATASOURCE-ERROR the datasource must be open for this call
    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @since %Qore 0.8.6
*/
Datasource::setOption(string opt, auto val) {
    ds->setOption(opt->c_str(), val, xsink);
}

//! Returns the current value for the given option
/** @param opt the option to get

    @note in order to ensure atomicity when dealing with Datasource options, the transaction lock is acquired before executing this method if it was not already owned by the calling thread

    @throw DBI-OPTION-ERROR unknown or unsupported option passed to driver
    @throw TRANSACTION-LOCK-TIMEOUT Timeout trying to acquire the transaction lock

    @since %Qore 0.8.6
*/
auto Datasource::getOption(string opt) [flags=RET_VALUE_ONLY] {
    return ds->getOption(opt->c_str(), xsink);
}

//! Returns a @ref datasource_hash "datasource hash" describing the configuration of the current object
/** @par Example:
    @code{.py}
hash<auto> h = ds.getConfigHash();
    @endcode

    @return a @ref datasource_hash "datasource hash" describing the configuration of the current object

    This method does not require a connection to the server.  The return value may differ for connected and unconnected
    objects, as the DB driver may set default option values when the datasource is connected.

    @since %Qore 0.8.8
*/
hash<auto> Datasource::getConfigHash() [flags=CONSTANT] {
    return ds->getConfigHash(xsink);
}

//! Returns a string giving the configuration of the current object in a format that can be parsed by parse_datasource()
/** @par Example:
    @code{.py}
string str = ds.getConfigString();
    @endcode

    @return a string giving the configuration of the current object in a format that can be parsed by parse_datasource()

    This method does not require a connection to the server.  The return value may differ for connected and unconnected
    objects, as the DB driver may set default option values when the datasource is connected.

    @since %Qore 0.8.8
*/
string Datasource::getConfigString() [flags=CONSTANT] {
    return ds->getConfigString(xsink);
}

//! Sets a queue object for DBI events on the datasource
/**
    @param queue the @ref Qore::Thread::Queue "Queue" object to receive datasource events; note that the @ref Qore::Thread::Queue "Queue" passed cannot have any maximum size set or a \c QUEUE-ERROR will be thrown; passing @ref nothing will clear any event queue
    @param arg an argument to be included in the \c arg key of datasource events

    @throw QUEUE-ERROR the Queue passed has a maximum size set

    @since %Qore 0.8.9
*/
nothing Datasource::setEventQueue(Qore::Thread::Queue[Queue] queue, auto arg) {
    ReferenceHolder<Queue> q(queue, xsink);

    if (queue->getMax() != -1)
        return xsink->raiseException("QUEUE-ERROR", "the Queue object passed as a maximum size of %d entr%s, which could cause internal I/O to freeze in the Datasource object if used; use a Queue object with no maximum size for datasource event monitoring", queue->getMax(), queue->getMax() == 1 ? "y" : "ies");

    ds->setEventQueue(q.release(), arg.refSelf(), xsink);
}

//! Clears the queue object for DBI events on the datasource
/**
    @since %Qore 0.8.9
*/
nothing Datasource::clearEventQueue() {
    ds->setEventQueue(0, 0, xsink);
}

//! Returns an @ref Qore::SQL::AbstractSQLStatement "AbstractSQLStatement" object based on the current database connection object
/** @par Example:
    @code{.py}
AbstractSQLStatement stmt = ds.getSQLStatement();
    @endcode

    @throw SQLSTATEMENT-ERROR the DBI driver for the given object does not support the prepared statement API

    @since Qore 0.9.0
*/
AbstractSQLStatement Datasource::getSQLStatement() {
    if (!ds->getDriver()->hasStatementAPI()) {
        xsink->raiseException("SQLSTATEMENT-ERROR", "DBI driver '%s' does not support the prepared statement API",
            ds->getDriver()->getName());
        return QoreValue();
    }

    return new QoreObject(QC_SQLSTATEMENT, getProgram(), new QoreSQLStatement(ds));
}
