../../../srclib/apr-util/include/apr_dbd.h ========================================== /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Overview of what this is and does: * http://www.apache.org/~niq/dbd.html */ #ifndef APR_DBD_H #define APR_DBD_H #include "apu.h" #include "apr_pools.h" #ifdef __cplusplus extern "C" { #endif /** * @file apr_dbd.h * @brief APR-UTIL DBD library */ /** * @defgroup APR_Util_DBD DBD routines * @ingroup APR_Util * @{ */ /** * Mapping of C to SQL types, used for prepared statements. * @remarks * For apr_dbd_p[v]query/select functions, in and out parameters are always * const char * (i.e. regular nul terminated strings). LOB types are passed * with four (4) arguments: payload, length, table and column, all as const * char *, where table and column are reserved for future use by Oracle. * @remarks * For apr_dbd_p[v]bquery/select functions, in and out parameters are * described next to each enumeration constant and are generally native binary * types or some APR data type. LOB types are passed with four (4) arguments: * payload (char*), length (apr_size_t*), table (char*) and column (char*). * Table and column are reserved for future use by Oracle. */ typedef enum { APR_DBD_TYPE_NONE, APR_DBD_TYPE_TINY, /**< \%hhd : in, out: char* */ APR_DBD_TYPE_UTINY, /**< \%hhu : in, out: unsigned char* */ APR_DBD_TYPE_SHORT, /**< \%hd : in, out: short* */ APR_DBD_TYPE_USHORT, /**< \%hu : in, out: unsigned short* */ APR_DBD_TYPE_INT, /**< \%d : in, out: int* */ APR_DBD_TYPE_UINT, /**< \%u : in, out: unsigned int* */ APR_DBD_TYPE_LONG, /**< \%ld : in, out: long* */ APR_DBD_TYPE_ULONG, /**< \%lu : in, out: unsigned long* */ APR_DBD_TYPE_LONGLONG, /**< \%lld : in, out: apr_int64_t* */ APR_DBD_TYPE_ULONGLONG, /**< \%llu : in, out: apr_uint64_t* */ APR_DBD_TYPE_FLOAT, /**< \%f : in, out: float* */ APR_DBD_TYPE_DOUBLE, /**< \%lf : in, out: double* */ APR_DBD_TYPE_STRING, /**< \%s : in: char*, out: char** */ APR_DBD_TYPE_TEXT, /**< \%pDt : in: char*, out: char** */ APR_DBD_TYPE_TIME, /**< \%pDi : in: char*, out: char** */ APR_DBD_TYPE_DATE, /**< \%pDd : in: char*, out: char** */ APR_DBD_TYPE_DATETIME, /**< \%pDa : in: char*, out: char** */ APR_DBD_TYPE_TIMESTAMP, /**< \%pDs : in: char*, out: char** */ APR_DBD_TYPE_ZTIMESTAMP, /**< \%pDz : in: char*, out: char** */ APR_DBD_TYPE_BLOB, /**< \%pDb : in: char* apr_size_t* char* char*, out: apr_bucket_brigade* */ APR_DBD_TYPE_CLOB, /**< \%pDc : in: char* apr_size_t* char* char*, out: apr_bucket_brigade* */ APR_DBD_TYPE_NULL /**< \%pDn : in: void*, out: void** */ } apr_dbd_type_e; /* These are opaque structs. Instantiation is up to each backend */ typedef struct apr_dbd_driver_t apr_dbd_driver_t; typedef struct apr_dbd_t apr_dbd_t; typedef struct apr_dbd_transaction_t apr_dbd_transaction_t; typedef struct apr_dbd_results_t apr_dbd_results_t; typedef struct apr_dbd_row_t apr_dbd_row_t; typedef struct apr_dbd_prepared_t apr_dbd_prepared_t; /** apr_dbd_init: perform once-only initialisation. Call once only. * * @param pool - pool to register any shutdown cleanups, etc */ APU_DECLARE(apr_status_t) apr_dbd_init(apr_pool_t *pool); /** apr_dbd_get_driver: get the driver struct for a name * * @param pool - (process) pool to register cleanup * @param name - driver name * @param driver - pointer to driver struct. * @return APR_SUCCESS for success * @return APR_ENOTIMPL for no driver (when DSO not enabled) * @return APR_EDSOOPEN if DSO driver file can't be opened * @return APR_ESYMNOTFOUND if the driver file doesn't contain a driver */ APU_DECLARE(apr_status_t) apr_dbd_get_driver(apr_pool_t *pool, const char *name, const apr_dbd_driver_t **driver); /** apr_dbd_open_ex: open a connection to a backend * * @param pool - working pool * @param params - arguments to driver (implementation-dependent) * @param handle - pointer to handle to return * @param driver - driver struct. * @param error - descriptive error. * @return APR_SUCCESS for success * @return APR_EGENERAL if driver exists but connection failed * @remarks PostgreSQL: the params is passed directly to the PQconnectdb() * function (check PostgreSQL documentation for more details on the syntax). * @remarks SQLite2: the params is split on a colon, with the first part used * as the filename and second part converted to an integer and used as file * mode. * @remarks SQLite3: the params is passed directly to the sqlite3_open() * function as a filename to be opened (check SQLite3 documentation for more * details). * @remarks Oracle: the params can have "user", "pass", "dbname" and "server" * keys, each followed by an equal sign and a value. Such key/value pairs can * be delimited by space, CR, LF, tab, semicolon, vertical bar or comma. * @remarks MySQL: the params can have "host", "port", "user", "pass", * "dbname", "sock", "flags" "fldsz", "group" and "reconnect" keys, each * followed by an equal sign and a value. Such key/value pairs can be * delimited by space, CR, LF, tab, semicolon, vertical bar or comma. For * now, "flags" can only recognise CLIENT_FOUND_ROWS (check MySQL manual for * details). The value associated with "fldsz" determines maximum amount of * memory (in bytes) for each of the fields in the result set of prepared * statements. By default, this value is 1 MB. The value associated with * "group" determines which group from configuration file to use (see * MYSQL_READ_DEFAULT_GROUP option of mysql_options() in MySQL manual). * Reconnect is set to 1 by default (i.e. true). * @remarks FreeTDS: the params can have "username", "password", "appname", * "dbname", "host", "charset", "lang" and "server" keys, each followed by an * equal sign and a value. */ APU_DECLARE(apr_status_t) apr_dbd_open_ex(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *params, apr_dbd_t **handle, const char **error); /** apr_dbd_open: open a connection to a backend * * @param pool - working pool * @param params - arguments to driver (implementation-dependent) * @param handle - pointer to handle to return * @param driver - driver struct. * @return APR_SUCCESS for success * @return APR_EGENERAL if driver exists but connection failed * @see apr_dbd_open_ex */ APU_DECLARE(apr_status_t) apr_dbd_open(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *params, apr_dbd_t **handle); /** apr_dbd_close: close a connection to a backend * * @param handle - handle to close * @param driver - driver struct. * @return APR_SUCCESS for success or error status */ APU_DECLARE(apr_status_t) apr_dbd_close(const apr_dbd_driver_t *driver, apr_dbd_t *handle); /* apr-function-shaped versions of things */ /** apr_dbd_name: get the name of the driver * * @param driver - the driver * @return - name */ APU_DECLARE(const char*) apr_dbd_name(const apr_dbd_driver_t *driver); /** apr_dbd_native_handle: get native database handle of the underlying db * * @param driver - the driver * @param handle - apr_dbd handle * @return - native handle */ APU_DECLARE(void*) apr_dbd_native_handle(const apr_dbd_driver_t *driver, apr_dbd_t *handle); /** check_conn: check status of a database connection * * @param driver - the driver * @param pool - working pool * @param handle - the connection to check * @return APR_SUCCESS or error */ APU_DECLARE(int) apr_dbd_check_conn(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle); /** apr_dbd_set_dbname: select database name. May be a no-op if not supported. * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param name - the database to select * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_set_dbname(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, const char *name); /** apr_dbd_transaction_start: start a transaction. May be a no-op. * * @param driver - the driver * @param pool - a pool to use for error messages (if any). * @param handle - the db connection * @param trans - ptr to a transaction. May be null on entry * @return 0 for success or error code * @remarks Note that transaction modes, set by calling * apr_dbd_transaction_mode_set(), will affect all query/select calls within * a transaction. By default, any error in query/select during a transaction * will cause the transaction to inherit the error code and any further * query/select calls will fail immediately. Put transaction in "ignore * errors" mode to avoid that. Use "rollback" mode to do explicit rollback. */ APU_DECLARE(int) apr_dbd_transaction_start(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_transaction_t **trans); /** apr_dbd_transaction_end: end a transaction * (commit on success, rollback on error). * May be a no-op. * * @param driver - the driver * @param handle - the db connection * @param trans - the transaction. * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_transaction_end(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_transaction_t *trans); #define APR_DBD_TRANSACTION_COMMIT 0x00 /**< commit the transaction */ #define APR_DBD_TRANSACTION_ROLLBACK 0x01 /**< rollback the transaction */ #define APR_DBD_TRANSACTION_IGNORE_ERRORS 0x02 /**< ignore transaction errors */ /** apr_dbd_transaction_mode_get: get the mode of transaction * * @param driver - the driver * @param trans - the transaction * @return mode of transaction */ APU_DECLARE(int) apr_dbd_transaction_mode_get(const apr_dbd_driver_t *driver, apr_dbd_transaction_t *trans); /** apr_dbd_transaction_mode_set: set the mode of transaction * * @param driver - the driver * @param trans - the transaction * @param mode - new mode of the transaction * @return the mode of transaction in force after the call */ APU_DECLARE(int) apr_dbd_transaction_mode_set(const apr_dbd_driver_t *driver, apr_dbd_transaction_t *trans, int mode); /** apr_dbd_query: execute an SQL query that doesn't return a result set * * @param driver - the driver * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the SQL statement to execute * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_query(const apr_dbd_driver_t *driver, apr_dbd_t *handle, int *nrows, const char *statement); /** apr_dbd_select: execute an SQL query that returns a result set * * @param driver - the driver * @param pool - pool to allocate the result set * @param handle - the connection * @param res - pointer to result set pointer. May point to NULL on entry * @param statement - the SQL statement to execute * @param random - 1 to support random access to results (seek any row); * 0 to support only looping through results in order * (async access - faster) * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_select(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, const char *statement, int random); /** apr_dbd_num_cols: get the number of columns in a results set * * @param driver - the driver * @param res - result set. * @return number of columns */ APU_DECLARE(int) apr_dbd_num_cols(const apr_dbd_driver_t *driver, apr_dbd_results_t *res); /** apr_dbd_num_tuples: get the number of rows in a results set * of a synchronous select * * @param driver - the driver * @param res - result set. * @return number of rows, or -1 if the results are asynchronous */ APU_DECLARE(int) apr_dbd_num_tuples(const apr_dbd_driver_t *driver, apr_dbd_results_t *res); /** apr_dbd_get_row: get a row from a result set * * @param driver - the driver * @param pool - pool to allocate the row * @param res - result set pointer * @param row - pointer to row pointer. May point to NULL on entry * @param rownum - row number (counting from 1), or -1 for "next row". * Ignored if random access is not supported. * @return 0 for success, -1 for rownum out of range or data finished */ APU_DECLARE(int) apr_dbd_get_row(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_results_t *res, apr_dbd_row_t **row, int rownum); /** apr_dbd_get_entry: get an entry from a row * * @param driver - the driver * @param row - row pointer * @param col - entry number * @return value from the row, or NULL if col is out of bounds. */ APU_DECLARE(const char*) apr_dbd_get_entry(const apr_dbd_driver_t *driver, apr_dbd_row_t *row, int col); /** apr_dbd_get_name: get an entry name from a result set * * @param driver - the driver * @param res - result set pointer * @param col - entry number * @return name of the entry, or NULL if col is out of bounds. */ APU_DECLARE(const char*) apr_dbd_get_name(const apr_dbd_driver_t *driver, apr_dbd_results_t *res, int col); /** apr_dbd_error: get current error message (if any) * * @param driver - the driver * @param handle - the connection * @param errnum - error code from operation that returned an error * @return the database current error message, or message for errnum * (implementation-dependent whether errnum is ignored) */ APU_DECLARE(const char*) apr_dbd_error(const apr_dbd_driver_t *driver, apr_dbd_t *handle, int errnum); /** apr_dbd_escape: escape a string so it is safe for use in query/select * * @param driver - the driver * @param pool - pool to alloc the result from * @param string - the string to escape * @param handle - the connection * @return the escaped, safe string */ APU_DECLARE(const char*) apr_dbd_escape(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *string, apr_dbd_t *handle); /** apr_dbd_prepare: prepare a statement * * @param driver - the driver * @param pool - pool to alloc the result from * @param handle - the connection * @param query - the SQL query * @param label - A label for the prepared statement. * use NULL for temporary prepared statements * (eg within a Request in httpd) * @param statement - statement to prepare. May point to null on entry. * @return 0 for success or error code * @remarks To specify parameters of the prepared query, use \%s, \%d etc. * (see below for full list) in place of database specific parameter syntax * (e.g. for PostgreSQL, this would be $1, $2, for SQLite3 this would be ? * etc.). For instance: "SELECT name FROM customers WHERE name=%s" would be * a query that this function understands. * @remarks Here is the full list of format specifiers that this function * understands and what they map to in SQL: \%hhd (TINY INT), \%hhu (UNSIGNED * TINY INT), \%hd (SHORT), \%hu (UNSIGNED SHORT), \%d (INT), \%u (UNSIGNED * INT), \%ld (LONG), \%lu (UNSIGNED LONG), \%lld (LONG LONG), \%llu * (UNSIGNED LONG LONG), \%f (FLOAT, REAL), \%lf (DOUBLE PRECISION), \%s * (VARCHAR), \%pDt (TEXT), \%pDi (TIME), \%pDd (DATE), \%pDa (DATETIME), * \%pDs (TIMESTAMP), \%pDz (TIMESTAMP WITH TIME ZONE), \%pDb (BLOB), \%pDc * (CLOB) and \%pDn (NULL). Not all databases have support for all these * types, so the underlying driver will attempt the "best match" where * possible. A \% followed by any letter not in the above list will be * interpreted as VARCHAR (i.e. \%s). */ APU_DECLARE(int) apr_dbd_prepare(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, const char *query, const char *label, apr_dbd_prepared_t **statement); /** apr_dbd_pquery: query using a prepared statement + args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param nargs - ignored (for backward compatibility only) * @param args - args to prepared statement * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_pquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, int nargs, const char **args); /** apr_dbd_pselect: select using a prepared statement + args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param nargs - ignored (for backward compatibility only) * @param args - args to prepared statement * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_pselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, int nargs, const char **args); /** apr_dbd_pvquery: query using a prepared statement + args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param ... - varargs list * @return 0 for success or error code */ APU_DECLARE_NONSTD(int) apr_dbd_pvquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, ...); /** apr_dbd_pvselect: select using a prepared statement + args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param ... - varargs list * @return 0 for success or error code */ APU_DECLARE_NONSTD(int) apr_dbd_pvselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, ...); /** apr_dbd_pbquery: query using a prepared statement + binary args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param args - binary args to prepared statement * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_pbquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, const void **args); /** apr_dbd_pbselect: select using a prepared statement + binary args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param args - binary args to prepared statement * @return 0 for success or error code */ APU_DECLARE(int) apr_dbd_pbselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const void **args); /** apr_dbd_pvbquery: query using a prepared statement + binary args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param ... - varargs list of binary args * @return 0 for success or error code */ APU_DECLARE_NONSTD(int) apr_dbd_pvbquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, ...); /** apr_dbd_pvbselect: select using a prepared statement + binary args * * @param driver - the driver * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param ... - varargs list of binary args * @return 0 for success or error code */ APU_DECLARE_NONSTD(int) apr_dbd_pvbselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, ...); /** apr_dbd_datum_get: get a binary entry from a row * * @param driver - the driver * @param row - row pointer * @param col - entry number * @param type - type of data to get * @param data - pointer to data, allocated by the caller * @return APR_SUCCESS on success, APR_ENOENT if data is NULL or APR_EGENERAL */ APU_DECLARE(apr_status_t) apr_dbd_datum_get(const apr_dbd_driver_t *driver, apr_dbd_row_t *row, int col, apr_dbd_type_e type, void *data); /** @} */ #ifdef __cplusplus } #endif #endif ../../../srclib/apr-util/include/private/apr_dbd_internal.h =========================================================== /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Overview of what this is and does: * http://www.apache.org/~niq/dbd.html */ #ifndef APR_DBD_INTERNAL_H #define APR_DBD_INTERNAL_H #include #include "apr_dbd.h" #ifdef __cplusplus extern "C" { #endif #define TXN_IGNORE_ERRORS(t) \ ((t) && ((t)->mode & APR_DBD_TRANSACTION_IGNORE_ERRORS)) #define TXN_NOTICE_ERRORS(t) \ ((t) && !((t)->mode & APR_DBD_TRANSACTION_IGNORE_ERRORS)) #define TXN_DO_COMMIT(t) (!((t)->mode & APR_DBD_TRANSACTION_ROLLBACK)) #define TXN_DO_ROLLBACK(t) ((t)->mode & APR_DBD_TRANSACTION_ROLLBACK) #define TXN_MODE_BITS \ (APR_DBD_TRANSACTION_ROLLBACK|APR_DBD_TRANSACTION_IGNORE_ERRORS) struct apr_dbd_driver_t { /** name */ const char *name; /** init: allow driver to perform once-only initialisation. * Called once only. May be NULL */ void (*init)(apr_pool_t *pool); /** native_handle: return the native database handle of the underlying db * * @param handle - apr_dbd handle * @return - native handle */ void *(*native_handle)(apr_dbd_t *handle); /** open: obtain a database connection from the server rec. * Must be explicitly closed when you're finished with it. * WARNING: only use this when you need a connection with * a lifetime other than a request * * @param pool - a pool to use for error messages (if any). * @param params - connection parameters. * @param error - descriptive error. * @return database handle, or NULL on error. */ apr_dbd_t *(*open)(apr_pool_t *pool, const char *params, const char **error); /** check_conn: check status of a database connection * * @param pool - a pool to use for error messages (if any). * @param handle - the connection to check * @return APR_SUCCESS or error */ apr_status_t (*check_conn)(apr_pool_t *pool, apr_dbd_t *handle); /** close: close/release a connection obtained from open() * * @param handle - the connection to release * @return APR_SUCCESS or error */ apr_status_t (*close)(apr_dbd_t *handle); /** set_dbname: select database name. May be a no-op if not supported. * * @param pool - working pool * @param handle - the connection * @param name - the database to select * @return 0 for success or error code */ int (*set_dbname)(apr_pool_t* pool, apr_dbd_t *handle, const char *name); /** transaction: start a transaction. May be a no-op. * * @param pool - a pool to use for error messages (if any). * @param handle - the connection * @param trans - ptr to a transaction. May be null on entry * @return 0 for success or error code */ int (*start_transaction)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_transaction_t **trans); /** end_transaction: end a transaction * (commit on success, rollback on error). * May be a no-op. * * @param trans - the transaction. * @return 0 for success or error code */ int (*end_transaction)(apr_dbd_transaction_t *trans); /** query: execute an SQL query that doesn't return a result set * * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the SQL statement to execute * @return 0 for success or error code */ int (*query)(apr_dbd_t *handle, int *nrows, const char *statement); /** select: execute an SQL query that returns a result set * * @param pool - pool to allocate the result set * @param handle - the connection * @param res - pointer to result set pointer. May point to NULL on entry * @param statement - the SQL statement to execute * @param random - 1 to support random access to results (seek any row); * 0 to support only looping through results in order * (async access - faster) * @return 0 for success or error code */ int (*select)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, const char *statement, int random); /** num_cols: get the number of columns in a results set * * @param res - result set. * @return number of columns */ int (*num_cols)(apr_dbd_results_t *res); /** num_tuples: get the number of rows in a results set * of a synchronous select * * @param res - result set. * @return number of rows, or -1 if the results are asynchronous */ int (*num_tuples)(apr_dbd_results_t *res); /** get_row: get a row from a result set * * @param pool - pool to allocate the row * @param res - result set pointer * @param row - pointer to row pointer. May point to NULL on entry * @param rownum - row number, or -1 for "next row". Ignored if random * access is not supported. * @return 0 for success, -1 for rownum out of range or data finished */ int (*get_row)(apr_pool_t *pool, apr_dbd_results_t *res, apr_dbd_row_t **row, int rownum); /** get_entry: get an entry from a row * * @param row - row pointer * @param col - entry number * @param val - entry to fill * @return 0 for success, -1 for no data, +1 for general error */ const char* (*get_entry)(const apr_dbd_row_t *row, int col); /** error: get current error message (if any) * * @param handle - the connection * @param errnum - error code from operation that returned an error * @return the database current error message, or message for errnum * (implementation-dependent whether errnum is ignored) */ const char *(*error)(apr_dbd_t *handle, int errnum); /** escape: escape a string so it is safe for use in query/select * * @param pool - pool to alloc the result from * @param string - the string to escape * @param handle - the connection * @return the escaped, safe string */ const char *(*escape)(apr_pool_t *pool, const char *string, apr_dbd_t *handle); /** prepare: prepare a statement * * @param pool - pool to alloc the result from * @param handle - the connection * @param query - the SQL query * @param label - A label for the prepared statement. * use NULL for temporary prepared statements * (eg within a Request in httpd) * @param nargs - number of parameters in the query * @param nvals - number of values passed in p[b]query/select * @param types - pointer to an array with types of parameters * @param statement - statement to prepare. May point to null on entry. * @return 0 for success or error code */ int (*prepare)(apr_pool_t *pool, apr_dbd_t *handle, const char *query, const char *label, int nargs, int nvals, apr_dbd_type_e *types, apr_dbd_prepared_t **statement); /** pvquery: query using a prepared statement + args * * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param args - args to prepared statement * @return 0 for success or error code */ int (*pvquery)(apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, va_list args); /** pvselect: select using a prepared statement + args * * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param args - args to prepared statement * @return 0 for success or error code */ int (*pvselect)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, va_list args); /** pquery: query using a prepared statement + args * * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param args - args to prepared statement * @return 0 for success or error code */ int (*pquery)(apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, const char **args); /** pselect: select using a prepared statement + args * * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param args - args to prepared statement * @return 0 for success or error code */ int (*pselect)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const char **args); /** get_name: get a column title from a result set * * @param res - result set pointer * @param col - entry number * @return param name, or NULL if col is out of bounds. */ const char* (*get_name)(const apr_dbd_results_t *res, int col); /** transaction_mode_get: get the mode of transaction * * @param trans - the transaction. * @return mode of transaction */ int (*transaction_mode_get)(apr_dbd_transaction_t *trans); /** transaction_mode_set: get the mode of transaction * * @param trans - the transaction. * @param mode - new mode of the transaction * @return the mode of transaction in force after the call */ int (*transaction_mode_set)(apr_dbd_transaction_t *trans, int mode); /** format of prepared statement parameters */ const char *pformat; /** pvbquery: query using a prepared statement + binary args * * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param args - binary args to prepared statement * @return 0 for success or error code */ int (*pvbquery)(apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, va_list args); /** pvbselect: select using a prepared statement + binary args * * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param args - binary args to prepared statement * @return 0 for success or error code */ int (*pvbselect)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, va_list args); /** pbquery: query using a prepared statement + binary args * * @param pool - working pool * @param handle - the connection * @param nrows - number of rows affected. * @param statement - the prepared statement to execute * @param args - binary args to prepared statement * @return 0 for success or error code */ int (*pbquery)(apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement,const void **args); /** pbselect: select using a prepared statement + binary args * * @param pool - working pool * @param handle - the connection * @param res - pointer to query results. May point to NULL on entry * @param statement - the prepared statement to execute * @param random - Whether to support random-access to results * @param args - binary args to prepared statement * @return 0 for success or error code */ int (*pbselect)(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const void **args); /** datum_get: get a binary entry from a row * * @param row - row pointer * @param col - entry number * @param type - type of data to get * @param data - pointer to data, allocated by the caller * @return APR_SUCCESS, an error code on error or if col is out of bounds */ apr_status_t (*datum_get)(const apr_dbd_row_t *row, int col, apr_dbd_type_e type, void *data); }; /* Export mutex lock/unlock for drivers that need it * deprecated; create a per-dbd mutex within the (*init) function * to avoid blocking other providers running on other threads */ APU_DECLARE(apr_status_t) apr_dbd_mutex_lock(void); APU_DECLARE(apr_status_t) apr_dbd_mutex_unlock(void); #ifdef __cplusplus } #endif #endif apr_dbd.c ========= /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "apu_config.h" #include "apu.h" #include "apr_pools.h" #include "apr_dso.h" #include "apr_strings.h" #include "apr_hash.h" #include "apr_thread_mutex.h" #include "apr_lib.h" #include "apr_atomic.h" #include "apu_internal.h" #include "apr_dbd_internal.h" #include "apr_dbd.h" #include "apu_version.h" static apr_hash_t *drivers = NULL; static apr_uint32_t initialised = 0, in_init = 1; #define CLEANUP_CAST (apr_status_t (*)(void*)) #if APR_HAS_THREADS /* deprecated, but required for existing providers. Existing and new * providers should be refactored to use a provider-specific mutex so * that different providers do not block one another. * In APR 1.3 this is no longer used for dso module loading, and * apu_dso_mutex_[un]lock is used instead. * In APR 2.0 this should become entirely local to libaprutil-2.so and * no longer be exported. */ static apr_thread_mutex_t* mutex = NULL; APU_DECLARE(apr_status_t) apr_dbd_mutex_lock() { return apr_thread_mutex_lock(mutex); } APU_DECLARE(apr_status_t) apr_dbd_mutex_unlock() { return apr_thread_mutex_unlock(mutex); } #else APU_DECLARE(apr_status_t) apr_dbd_mutex_lock() { return APR_SUCCESS; } APU_DECLARE(apr_status_t) apr_dbd_mutex_unlock() { return APR_SUCCESS; } #endif #if !APU_DSO_BUILD #define DRIVER_LOAD(name,driver,pool) \ { \ extern const apr_dbd_driver_t driver; \ apr_hash_set(drivers,name,APR_HASH_KEY_STRING,&driver); \ if (driver.init) { \ driver.init(pool); \ } \ } #endif static apr_status_t apr_dbd_term(void *ptr) { /* set drivers to NULL so init can work again */ drivers = NULL; /* Everything else we need is handled by cleanups registered * when we created mutexes and loaded DSOs */ return APR_SUCCESS; } APU_DECLARE(apr_status_t) apr_dbd_init(apr_pool_t *pool) { apr_status_t ret = APR_SUCCESS; apr_pool_t *parent; if (apr_atomic_inc32(&initialised)) { apr_atomic_set32(&initialised, 1); /* prevent wrap-around */ while (apr_atomic_read32(&in_init)) /* wait until we get fully inited */ ; return APR_SUCCESS; } /* Top level pool scope, need process-scope lifetime */ for (parent = pool; parent; parent = apr_pool_parent_get(pool)) pool = parent; #if APU_DSO_BUILD /* deprecate in 2.0 - permit implicit initialization */ apu_dso_init(pool); #endif drivers = apr_hash_make(pool); #if APR_HAS_THREADS ret = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool); /* This already registers a pool cleanup */ #endif #if !APU_DSO_BUILD /* Load statically-linked drivers: */ #if APU_HAVE_MYSQL DRIVER_LOAD("mysql", apr_dbd_mysql_driver, pool); #endif #if APU_HAVE_PGSQL DRIVER_LOAD("pgsql", apr_dbd_pgsql_driver, pool); #endif #if APU_HAVE_SQLITE3 DRIVER_LOAD("sqlite3", apr_dbd_sqlite3_driver, pool); #endif #if APU_HAVE_SQLITE2 DRIVER_LOAD("sqlite2", apr_dbd_sqlite2_driver, pool); #endif #if APU_HAVE_ORACLE DRIVER_LOAD("oracle", apr_dbd_oracle_driver, pool); #endif #if APU_HAVE_FREETDS DRIVER_LOAD("freetds", apr_dbd_freetds_driver, pool); #endif #if APU_HAVE_ODBC DRIVER_LOAD("odbc", apr_dbd_odbc_driver, pool); #endif #if APU_HAVE_SOME_OTHER_BACKEND DRIVER_LOAD("firebird", apr_dbd_other_driver, pool); #endif #endif /* APU_DSO_BUILD */ apr_pool_cleanup_register(pool, NULL, apr_dbd_term, apr_pool_cleanup_null); apr_atomic_dec32(&in_init); return ret; } APU_DECLARE(apr_status_t) apr_dbd_get_driver(apr_pool_t *pool, const char *name, const apr_dbd_driver_t **driver) { #if APU_DSO_BUILD char modname[32]; char symname[34]; apr_dso_handle_sym_t symbol; #endif apr_status_t rv; #if APU_DSO_BUILD rv = apu_dso_mutex_lock(); if (rv) { return rv; } #endif *driver = apr_hash_get(drivers, name, APR_HASH_KEY_STRING); if (*driver) { #if APU_DSO_BUILD apu_dso_mutex_unlock(); #endif return APR_SUCCESS; } #if APU_DSO_BUILD /* The driver DSO must have exactly the same lifetime as the * drivers hash table; ignore the passed-in pool */ pool = apr_hash_pool_get(drivers); #if defined(NETWARE) apr_snprintf(modname, sizeof(modname), "dbd%s.nlm", name); #elif defined(WIN32) apr_snprintf(modname, sizeof(modname), "apr_dbd_%s-" APU_STRINGIFY(APU_MAJOR_VERSION) ".dll", name); #else apr_snprintf(modname, sizeof(modname), "apr_dbd_%s-" APU_STRINGIFY(APU_MAJOR_VERSION) ".so", name); #endif apr_snprintf(symname, sizeof(symname), "apr_dbd_%s_driver", name); rv = apu_dso_load(&symbol, modname, symname, pool); if (rv == APR_SUCCESS || rv == APR_EINIT) { /* previously loaded?!? */ *driver = symbol; name = apr_pstrdup(pool, name); apr_hash_set(drivers, name, APR_HASH_KEY_STRING, *driver); rv = APR_SUCCESS; if ((*driver)->init) { (*driver)->init(pool); } goto unlock; } name = apr_pstrdup(pool, name); apr_hash_set(drivers, name, APR_HASH_KEY_STRING, *driver); unlock: apu_dso_mutex_unlock(); #else /* not builtin and !APR_HAS_DSO => not implemented */ rv = APR_ENOTIMPL; #endif return rv; } APU_DECLARE(apr_status_t) apr_dbd_open_ex(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *params, apr_dbd_t **handle, const char **error) { apr_status_t rv; *handle = (driver->open)(pool, params, error); if (*handle == NULL) { return APR_EGENERAL; } rv = apr_dbd_check_conn(driver, pool, *handle); if ((rv != APR_SUCCESS) && (rv != APR_ENOTIMPL)) { /* XXX: rv is APR error code, but apr_dbd_error() takes int! */ if (error) { *error = apr_dbd_error(driver, *handle, rv); } apr_dbd_close(driver, *handle); return APR_EGENERAL; } return APR_SUCCESS; } APU_DECLARE(apr_status_t) apr_dbd_open(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *params, apr_dbd_t **handle) { return apr_dbd_open_ex(driver,pool,params,handle,NULL); } APU_DECLARE(int) apr_dbd_transaction_start(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_transaction_t **trans) { int ret = driver->start_transaction(pool, handle, trans); if (*trans) { apr_pool_cleanup_register(pool, *trans, CLEANUP_CAST driver->end_transaction, apr_pool_cleanup_null); } return ret; } APU_DECLARE(int) apr_dbd_transaction_end(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_transaction_t *trans) { apr_pool_cleanup_kill(pool, trans, CLEANUP_CAST driver->end_transaction); return driver->end_transaction(trans); } APU_DECLARE(int) apr_dbd_transaction_mode_get(const apr_dbd_driver_t *driver, apr_dbd_transaction_t *trans) { return driver->transaction_mode_get(trans); } APU_DECLARE(int) apr_dbd_transaction_mode_set(const apr_dbd_driver_t *driver, apr_dbd_transaction_t *trans, int mode) { return driver->transaction_mode_set(trans, mode); } APU_DECLARE(apr_status_t) apr_dbd_close(const apr_dbd_driver_t *driver, apr_dbd_t *handle) { return driver->close(handle); } APU_DECLARE(const char*) apr_dbd_name(const apr_dbd_driver_t *driver) { return driver->name; } APU_DECLARE(void*) apr_dbd_native_handle(const apr_dbd_driver_t *driver, apr_dbd_t *handle) { return driver->native_handle(handle); } APU_DECLARE(int) apr_dbd_check_conn(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle) { return driver->check_conn(pool, handle); } APU_DECLARE(int) apr_dbd_set_dbname(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, const char *name) { return driver->set_dbname(pool,handle,name); } APU_DECLARE(int) apr_dbd_query(const apr_dbd_driver_t *driver, apr_dbd_t *handle, int *nrows, const char *statement) { return driver->query(handle,nrows,statement); } APU_DECLARE(int) apr_dbd_select(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, const char *statement, int random) { return driver->select(pool,handle,res,statement,random); } APU_DECLARE(int) apr_dbd_num_cols(const apr_dbd_driver_t *driver, apr_dbd_results_t *res) { return driver->num_cols(res); } APU_DECLARE(int) apr_dbd_num_tuples(const apr_dbd_driver_t *driver, apr_dbd_results_t *res) { return driver->num_tuples(res); } APU_DECLARE(int) apr_dbd_get_row(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_results_t *res, apr_dbd_row_t **row, int rownum) { return driver->get_row(pool,res,row,rownum); } APU_DECLARE(const char*) apr_dbd_get_entry(const apr_dbd_driver_t *driver, apr_dbd_row_t *row, int col) { return driver->get_entry(row,col); } APU_DECLARE(const char*) apr_dbd_get_name(const apr_dbd_driver_t *driver, apr_dbd_results_t *res, int col) { return driver->get_name(res,col); } APU_DECLARE(const char*) apr_dbd_error(const apr_dbd_driver_t *driver, apr_dbd_t *handle, int errnum) { return driver->error(handle,errnum); } APU_DECLARE(const char*) apr_dbd_escape(const apr_dbd_driver_t *driver, apr_pool_t *pool, const char *string, apr_dbd_t *handle) { return driver->escape(pool,string,handle); } APU_DECLARE(int) apr_dbd_prepare(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, const char *query, const char *label, apr_dbd_prepared_t **statement) { size_t qlen; int i, nargs = 0, nvals = 0; char *p, *pq; const char *q; apr_dbd_type_e *t; if (!driver->pformat) { return APR_ENOTIMPL; } /* find the number of parameters in the query */ for (q = query; *q; q++) { if (q[0] == '%') { if (apr_isalpha(q[1])) { nargs++; } else if (q[1] == '%') { q++; } } } nvals = nargs; qlen = strlen(query) + nargs * (strlen(driver->pformat) + sizeof(nargs) * 3 + 2) + 1; pq = apr_palloc(pool, qlen); t = apr_pcalloc(pool, sizeof(*t) * nargs); for (p = pq, q = query, i = 0; *q; q++) { if (q[0] == '%') { if (apr_isalpha(q[1])) { switch (q[1]) { case 'd': t[i] = APR_DBD_TYPE_INT; break; case 'u': t[i] = APR_DBD_TYPE_UINT; break; case 'f': t[i] = APR_DBD_TYPE_FLOAT; break; case 'h': switch (q[2]) { case 'h': switch (q[3]){ case 'd': t[i] = APR_DBD_TYPE_TINY; q += 2; break; case 'u': t[i] = APR_DBD_TYPE_UTINY; q += 2; break; } break; case 'd': t[i] = APR_DBD_TYPE_SHORT; q++; break; case 'u': t[i] = APR_DBD_TYPE_USHORT; q++; break; } break; case 'l': switch (q[2]) { case 'l': switch (q[3]){ case 'd': t[i] = APR_DBD_TYPE_LONGLONG; q += 2; break; case 'u': t[i] = APR_DBD_TYPE_ULONGLONG; q += 2; break; } break; case 'd': t[i] = APR_DBD_TYPE_LONG; q++; break; case 'u': t[i] = APR_DBD_TYPE_ULONG; q++; break; case 'f': t[i] = APR_DBD_TYPE_DOUBLE; q++; break; } break; case 'p': if (q[2] == 'D') { switch (q[3]) { case 't': t[i] = APR_DBD_TYPE_TEXT; q += 2; break; case 'i': t[i] = APR_DBD_TYPE_TIME; q += 2; break; case 'd': t[i] = APR_DBD_TYPE_DATE; q += 2; break; case 'a': t[i] = APR_DBD_TYPE_DATETIME; q += 2; break; case 's': t[i] = APR_DBD_TYPE_TIMESTAMP; q += 2; break; case 'z': t[i] = APR_DBD_TYPE_ZTIMESTAMP; q += 2; break; case 'b': t[i] = APR_DBD_TYPE_BLOB; q += 2; break; case 'c': t[i] = APR_DBD_TYPE_CLOB; q += 2; break; case 'n': t[i] = APR_DBD_TYPE_NULL; q += 2; break; } } break; } q++; switch (t[i]) { case APR_DBD_TYPE_NONE: /* by default, we expect strings */ t[i] = APR_DBD_TYPE_STRING; break; case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: /* three (3) more values passed in */ nvals += 3; break; default: break; } /* insert database specific parameter reference */ p += apr_snprintf(p, qlen - (p - pq), driver->pformat, ++i); } else if (q[1] == '%') { /* reduce %% to % */ *p++ = *q++; } else { *p++ = *q; } } else { *p++ = *q; } } *p = '\0'; return driver->prepare(pool,handle,pq,label,nargs,nvals,t,statement); } APU_DECLARE(int) apr_dbd_pquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, int nargs, const char **args) { return driver->pquery(pool,handle,nrows,statement,args); } APU_DECLARE(int) apr_dbd_pselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, int nargs, const char **args) { return driver->pselect(pool,handle,res,statement,random,args); } APU_DECLARE_NONSTD(int) apr_dbd_pvquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, ...) { int ret; va_list args; va_start(args, statement); ret = driver->pvquery(pool,handle,nrows,statement,args); va_end(args); return ret; } APU_DECLARE_NONSTD(int) apr_dbd_pvselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, ...) { int ret; va_list args; va_start(args, random); ret = driver->pvselect(pool,handle,res,statement,random,args); va_end(args); return ret; } APU_DECLARE(int) apr_dbd_pbquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, const void **args) { return driver->pbquery(pool,handle,nrows,statement,args); } APU_DECLARE(int) apr_dbd_pbselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const void **args) { return driver->pbselect(pool,handle,res,statement,random,args); } APU_DECLARE_NONSTD(int) apr_dbd_pvbquery(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, int *nrows, apr_dbd_prepared_t *statement, ...) { int ret; va_list args; va_start(args, statement); ret = driver->pvbquery(pool,handle,nrows,statement,args); va_end(args); return ret; } APU_DECLARE_NONSTD(int) apr_dbd_pvbselect(const apr_dbd_driver_t *driver, apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, ...) { int ret; va_list args; va_start(args, random); ret = driver->pvbselect(pool,handle,res,statement,random,args); va_end(args); return ret; } APU_DECLARE(apr_status_t) apr_dbd_datum_get(const apr_dbd_driver_t *driver, apr_dbd_row_t *row, int col, apr_dbd_type_e type, void *data) { return driver->datum_get(row,col,type,data); } apr_dbd_oracle.c ================ /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Developed initially by Nick Kew and Chris Darroch. * Contributed to the APR project by kind permission of * Pearson Education Core Technology Group (CTG), * formerly Central Media Group (CMG). */ /* apr_dbd_oracle - a painful attempt * * Based first on the documentation at * http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96584/toc.htm * * Those docs have a lot of internal inconsistencies, contradictions, etc * So I've snarfed the demo programs (from Oracle 8, not included in * the current downloadable oracle), and used code from them. * * Why do cdemo81.c and cdemo82.c do the same thing in very different ways? * e.g. cdemo82 releases all its handle on shutdown; cdemo81 doesn't * * All the ORA* functions return a "sword". Some of them are documented; * others aren't. So I've adopted a policy of using switch statements * everywhere, even when we're not doing anything with the return values. * * This makes no attempt at performance tuning, such as setting * prefetch cache size. We need some actual performance data * to make that meaningful. Input from someone with experience * as a sysop using oracle would be a good start. */ /* shut compiler up */ #ifdef DEBUG #define int_errorcode int errorcode #else #define int_errorcode #endif #include "apu.h" #if APU_HAVE_ORACLE #include #include #include #include #include "apr_strings.h" #include "apr_lib.h" #include "apr_time.h" #include "apr_hash.h" #include "apr_buckets.h" #define TRANS_TIMEOUT 30 #define MAX_ARG_LEN 256 /* in line with other apr_dbd drivers. We alloc this * lots of times, so a large value gets hungry. * Should really make it configurable */ #define DEFAULT_LONG_SIZE 4096 #define DBD_ORACLE_MAX_COLUMNS 256 #define NUMERIC_FIELD_SIZE 32 #define CHECK_CONN_QUERY "SELECT 1 FROM dual" #define ERR_BUF_SIZE 200 #ifdef DEBUG #include #endif #include "apr_dbd_internal.h" /* declarations */ static const char *dbd_oracle_error(apr_dbd_t *sql, int n); static int dbd_oracle_prepare(apr_pool_t *pool, apr_dbd_t *sql, const char *query, const char *label, int nargs, int nvals, apr_dbd_type_e *types, apr_dbd_prepared_t **statement); static int outputParams(apr_dbd_t*, apr_dbd_prepared_t*); static int dbd_oracle_pselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **results, apr_dbd_prepared_t *statement, int seek, const char **values); static int dbd_oracle_pquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, const char **values); static int dbd_oracle_start_transaction(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_transaction_t **trans); static int dbd_oracle_end_transaction(apr_dbd_transaction_t *trans); struct apr_dbd_transaction_t { int mode; enum { TRANS_NONE, TRANS_ERROR, TRANS_1, TRANS_2 } status; apr_dbd_t *handle; OCITrans *trans; OCISnapshot *snapshot1; OCISnapshot *snapshot2; }; struct apr_dbd_results_t { apr_pool_t *pool; apr_dbd_t* handle; unsigned int rownum; int seek; int nrows; apr_dbd_prepared_t *statement; }; struct apr_dbd_t { sword status; OCIError *err; OCIServer *svr; OCISvcCtx *svc; OCISession *auth; apr_dbd_transaction_t* trans; apr_pool_t *pool; char buf[ERR_BUF_SIZE]; /* for error messages */ apr_size_t long_size; apr_dbd_prepared_t *check_conn_stmt; }; struct apr_dbd_row_t { int n; apr_dbd_results_t *res; apr_pool_t *pool; }; typedef struct { apr_dbd_type_e type; sb2 ind; sb4 len; OCIBind *bind; union { void *raw; char *sval; int ival; unsigned int uval; double fval; OCILobLocator *lobval; } value; } bind_arg; typedef struct { int type; sb2 ind; ub2 len; /* length of actual output */ OCIDefine *defn; apr_size_t sz; /* length of buf for output */ union { void *raw; char *sval; OCILobLocator *lobval; } buf; const char *name; } define_arg; struct apr_dbd_prepared_t { OCIStmt *stmt; int nargs; int nvals; bind_arg *args; int nout; define_arg *out; apr_dbd_t *handle; apr_pool_t *pool; int type; }; /* AFAICT from the docs, the OCIEnv thingey can be used async * across threads, so lets have a global one. * * We'll need shorter-lived envs to deal with requests and connections * * Hmmm, that doesn't work: we don't have a usermem framework. * OK, forget about using APR pools here, until we figure out * the right way to do it (if such a thing exists). */ static OCIEnv *dbd_oracle_env = NULL; /* Oracle specific bucket for BLOB/CLOB types */ typedef struct apr_bucket_lob apr_bucket_lob; /** * A bucket referring to a Oracle BLOB/CLOB */ struct apr_bucket_lob { /** Number of buckets using this memory */ apr_bucket_refcount refcount; /** The row this bucket refers to */ const apr_dbd_row_t *row; /** The column this bucket refers to */ int col; /** The pool into which any needed structures should * be created while reading from this bucket */ apr_pool_t *readpool; }; static void lob_bucket_destroy(void *data); static apr_status_t lob_bucket_read(apr_bucket *e, const char **str, apr_size_t *len, apr_read_type_e block); static apr_bucket *apr_bucket_lob_make(apr_bucket *b, const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p); static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p, apr_bucket_alloc_t *list); static const apr_bucket_type_t apr_bucket_type_lob = { "LOB", 5, APR_BUCKET_DATA, lob_bucket_destroy, lob_bucket_read, apr_bucket_setaside_notimpl, apr_bucket_shared_split, apr_bucket_shared_copy }; static void lob_bucket_destroy(void *data) { apr_bucket_lob *f = data; if (apr_bucket_shared_destroy(f)) { /* no need to destroy database objects here; it will get * done automatically when the pool gets cleaned up */ apr_bucket_free(f); } } static apr_status_t lob_bucket_read(apr_bucket *e, const char **str, apr_size_t *len, apr_read_type_e block) { apr_bucket_lob *a = e->data; const apr_dbd_row_t *row = a->row; apr_dbd_results_t *res = row->res; int col = a->col; apr_bucket *b = NULL; apr_size_t blength = e->length; /* bytes remaining in file past offset */ apr_off_t boffset = e->start; define_arg *val = &res->statement->out[col]; apr_dbd_t *sql = res->handle; /* Only with 10g, unfortunately oraub8 length = APR_BUCKET_BUFF_SIZE; */ ub4 length = APR_BUCKET_BUFF_SIZE; char *buf = NULL; *str = NULL; /* in case we die prematurely */ /* fetch from offset if not at the beginning */ buf = apr_palloc(row->pool, APR_BUCKET_BUFF_SIZE); sql->status = OCILobRead(sql->svc, sql->err, val->buf.lobval, &length, 1 + (size_t)boffset, (dvoid*) buf, APR_BUCKET_BUFF_SIZE, NULL, NULL, 0, SQLCS_IMPLICIT); /* Only with 10g, unfortunately sql->status = OCILobRead2(sql->svc, sql->err, val->buf.lobval, &length, NULL, 1 + boffset, (dvoid*) buf, APR_BUCKET_BUFF_SIZE, OCI_ONE_PIECE, NULL, NULL, 0, SQLCS_IMPLICIT); */ if (sql->status != OCI_SUCCESS) { return APR_EGENERAL; } blength -= length; *len = length; *str = buf; /* * Change the current bucket to refer to what we read, * even if we read nothing because we hit EOF. */ apr_bucket_pool_make(e, *str, *len, res->pool); /* If we have more to read from the field, then create another bucket */ if (blength > 0) { /* for efficiency, we can just build a new apr_bucket struct * to wrap around the existing LOB bucket */ b = apr_bucket_alloc(sizeof(*b), e->list); b->start = boffset + *len; b->length = blength; b->data = a; b->type = &apr_bucket_type_lob; b->free = apr_bucket_free; b->list = e->list; APR_BUCKET_INSERT_AFTER(e, b); } else { lob_bucket_destroy(a); } return APR_SUCCESS; } static apr_bucket *apr_bucket_lob_make(apr_bucket *b, const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p) { apr_bucket_lob *f; f = apr_bucket_alloc(sizeof(*f), b->list); f->row = row; f->col = col; f->readpool = p; b = apr_bucket_shared_make(b, f, offset, len); b->type = &apr_bucket_type_lob; return b; } static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p, apr_bucket_alloc_t *list) { apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); APR_BUCKET_INIT(b); b->free = apr_bucket_free; b->list = list; return apr_bucket_lob_make(b, row, col, offset, len, p); } static apr_status_t dbd_free_lobdesc(void *lob) { switch (OCIDescriptorFree(lob, OCI_DTYPE_LOB)) { case OCI_SUCCESS: return APR_SUCCESS; default: return APR_EGENERAL; } } static apr_status_t dbd_free_snapshot(void *snap) { switch (OCIDescriptorFree(snap, OCI_DTYPE_SNAP)) { case OCI_SUCCESS: return APR_SUCCESS; default: return APR_EGENERAL; } } static void dbd_oracle_init(apr_pool_t *pool) { if (dbd_oracle_env == NULL) { /* Sadly, OCI_SHARED seems to be impossible to use, due to * various Oracle bugs. See, for example, Oracle MetaLink bug 2972890 * and PHP bug http://bugs.php.net/bug.php?id=23733 */ #ifdef OCI_NEW_LENGTH_SEMANTICS OCIEnvCreate(&dbd_oracle_env, OCI_THREADED|OCI_NEW_LENGTH_SEMANTICS, NULL, NULL, NULL, NULL, 0, NULL); #else OCIEnvCreate(&dbd_oracle_env, OCI_THREADED, NULL, NULL, NULL, NULL, 0, NULL); #endif } } static apr_dbd_t *dbd_oracle_open(apr_pool_t *pool, const char *params, const char **error) { apr_dbd_t *ret = apr_pcalloc(pool, sizeof(apr_dbd_t)); int errorcode; char *BLANK = ""; struct { const char *field; char *value; } fields[] = { {"user", BLANK}, {"pass", BLANK}, {"dbname", BLANK}, {"server", BLANK}, {NULL, NULL} }; int i; const char *ptr; const char *key; size_t klen; const char *value; size_t vlen; static const char *const delims = " \r\n\t;|,"; ret->pool = pool; ret->long_size = DEFAULT_LONG_SIZE; /* snitch parsing from the MySQL driver */ for (ptr = strchr(params, '='); ptr; ptr = strchr(ptr, '=')) { /* don't dereference memory that may not belong to us */ if (ptr == params) { ++ptr; continue; } for (key = ptr-1; apr_isspace(*key); --key); klen = 0; while (apr_isalpha(*key)) { if (key == params) { /* Don't parse off the front of the params */ --key; ++klen; break; } --key; ++klen; } ++key; for (value = ptr+1; apr_isspace(*value); ++value); vlen = strcspn(value, delims); for (i=0; fields[i].field != NULL; ++i) { if (!strncasecmp(fields[i].field, key, klen)) { fields[i].value = apr_pstrndup(pool, value, vlen); break; } } ptr = value+vlen; } ret->status = OCIHandleAlloc(dbd_oracle_env, (dvoid**)&ret->err, OCI_HTYPE_ERROR, 0, NULL); switch (ret->status) { default: #ifdef DEBUG printf("ret->status is %d\n", ret->status); break; #else return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIHandleAlloc(dbd_oracle_env, (dvoid**)&ret->svr, OCI_HTYPE_SERVER, 0, NULL); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (alloc svr): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIHandleAlloc(dbd_oracle_env, (dvoid**)&ret->svc, OCI_HTYPE_SVCCTX, 0, NULL); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (alloc svc): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } /* All the examples use the #else */ #if CAN_DO_LOGIN ret->status = OCILogon(dbd_oracle_env, ret->err, &ret->svc, fields[0].value, strlen(fields[0].value), fields[1].value, strlen(fields[1].value), fields[2].value, strlen(fields[2].value)); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR: %s\n", ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } #else ret->status = OCIServerAttach(ret->svr, ret->err, (text*) fields[3].value, strlen(fields[3].value), OCI_DEFAULT); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (server attach): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIAttrSet(ret->svc, OCI_HTYPE_SVCCTX, ret->svr, 0, OCI_ATTR_SERVER, ret->err); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (attr set): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIHandleAlloc(dbd_oracle_env, (dvoid**)&ret->auth, OCI_HTYPE_SESSION, 0, NULL); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (alloc auth): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIAttrSet(ret->auth, OCI_HTYPE_SESSION, fields[0].value, strlen(fields[0].value), OCI_ATTR_USERNAME, ret->err); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (attr username): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIAttrSet(ret->auth, OCI_HTYPE_SESSION, fields[1].value, strlen(fields[1].value), OCI_ATTR_PASSWORD, ret->err); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (attr password): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCISessionBegin(ret->svc, ret->err, ret->auth, OCI_CRED_RDBMS, OCI_DEFAULT); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (session begin): %s\n", ret->status, ret->buf); break; #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif case OCI_SUCCESS: break; } ret->status = OCIAttrSet(ret->svc, OCI_HTYPE_SVCCTX, ret->auth, 0, OCI_ATTR_SESSION, ret->err); switch (ret->status) { default: #ifdef DEBUG OCIErrorGet(ret->err, 1, NULL, &errorcode, ret->buf, sizeof(ret->buf), OCI_HTYPE_ERROR); printf("OPEN ERROR %d (attr session): %s\n", ret->status, ret->buf); #else if (error) { *error = apr_pcalloc(pool, ERR_BUF_SIZE); OCIErrorGet(ret->err, 1, NULL, &errorcode, (unsigned char*)(*error), ERR_BUF_SIZE, OCI_HTYPE_ERROR); } return NULL; #endif break; case OCI_SUCCESS: break; } #endif if(dbd_oracle_prepare(pool, ret, CHECK_CONN_QUERY, NULL, 0, 0, NULL, &ret->check_conn_stmt) != 0) { return NULL; } return ret; } #ifdef EXPORT_NATIVE_FUNCS static apr_size_t dbd_oracle_long_size_set(apr_dbd_t *sql, apr_size_t long_size) { apr_size_t old_size = sql->long_size; sql->long_size = long_size; return old_size; } #endif static const char *dbd_oracle_get_name(const apr_dbd_results_t *res, int n) { define_arg *val = &res->statement->out[n]; if ((n < 0) || (n >= res->statement->nout)) { return NULL; } return val->name; } static int dbd_oracle_get_row(apr_pool_t *pool, apr_dbd_results_t *res, apr_dbd_row_t **rowp, int rownum) { apr_dbd_row_t *row = *rowp; apr_dbd_t *sql = res->handle; int_errorcode; if (row == NULL) { row = apr_palloc(pool, sizeof(apr_dbd_row_t)); *rowp = row; row->res = res; /* Oracle starts counting at 1 according to the docs */ row->n = res->seek ? rownum : 1; row->pool = pool; } else { if (res->seek) { row->n = rownum; } else { ++row->n; } } if (res->seek) { sql->status = OCIStmtFetch2(res->statement->stmt, res->handle->err, 1, OCI_FETCH_ABSOLUTE, row->n, OCI_DEFAULT); } else { sql->status = OCIStmtFetch2(res->statement->stmt, res->handle->err, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); } switch (sql->status) { case OCI_SUCCESS: (*rowp)->res = res; return 0; case OCI_NO_DATA: return -1; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Execute error %d: %s\n", sql->status, sql->buf); #endif /* fallthrough */ default: return 1; } return 0; } static const char *dbd_oracle_error(apr_dbd_t *sql, int n) { /* This is ugly. Needs us to pass in a buffer of unknown size. * Either we put it on the handle, or we have to keep allocing/copying */ sb4 errorcode; switch (sql->status) { case OCI_SUCCESS: return "OCI_SUCCESS"; case OCI_SUCCESS_WITH_INFO: return "OCI_SUCCESS_WITH_INFO"; case OCI_NEED_DATA: return "OCI_NEED_DATA"; case OCI_NO_DATA: return "OCI_NO_DATA"; case OCI_INVALID_HANDLE: return "OCI_INVALID_HANDLE"; case OCI_STILL_EXECUTING: return "OCI_STILL_EXECUTING"; case OCI_CONTINUE: return "OCI_CONTINUE"; } switch (OCIErrorGet(sql->err, 1, NULL, &errorcode, (text*) sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR)) { case OCI_SUCCESS: return sql->buf; default: return "internal error: OCIErrorGet failed"; } } static apr_status_t freeStatement(void *statement) { int rv = APR_SUCCESS; OCIStmt *stmt = ((apr_dbd_prepared_t*)statement)->stmt; #ifdef PREPARE2 OCIError *err; if (OCIHandleAlloc(dbd_oracle_env, (dvoid**)&err, OCI_HTYPE_ERROR, 0, NULL) != OCI_SUCCESS) { return APR_EGENERAL; } if (OCIStmtRelease(stmt, err, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS) { rv = APR_EGENERAL; } if (OCIHandleFree(err, OCI_HTYPE_ERROR) != OCI_SUCCESS) { rv = APR_EGENERAL; } #else if (OCIHandleFree(stmt, OCI_HTYPE_STMT) != OCI_SUCCESS) { rv = APR_EGENERAL; } #endif return rv; } static int dbd_oracle_select(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **results, const char *query, int seek) { int ret = 0; apr_dbd_prepared_t *statement = NULL; ret = dbd_oracle_prepare(pool, sql, query, NULL, 0, 0, NULL, &statement); if (ret != 0) { return ret; } ret = dbd_oracle_pselect(pool, sql, results, statement, seek, NULL); if (ret != 0) { return ret; } return ret; } static int dbd_oracle_query(apr_dbd_t *sql, int *nrows, const char *query) { int ret = 0; apr_pool_t *pool; apr_dbd_prepared_t *statement = NULL; if (sql->trans && sql->trans->status == TRANS_ERROR) { return 1; } /* make our own pool so that APR allocations don't linger and so that * both Stmt and LOB handles are cleaned up (LOB handles may be * allocated when preparing APR_DBD_TYPE_CLOB/BLOBs) */ apr_pool_create(&pool, sql->pool); ret = dbd_oracle_prepare(pool, sql, query, NULL, 0, 0, NULL, &statement); if (ret == 0) { ret = dbd_oracle_pquery(pool, sql, nrows, statement, NULL); if (ret == 0) { sql->status = OCIAttrGet(statement->stmt, OCI_HTYPE_STMT, nrows, 0, OCI_ATTR_ROW_COUNT, sql->err); } } apr_pool_destroy(pool); return ret; } static const char *dbd_oracle_escape(apr_pool_t *pool, const char *arg, apr_dbd_t *sql) { return arg; /* OCI has no concept of string escape */ } static int dbd_oracle_prepare(apr_pool_t *pool, apr_dbd_t *sql, const char *query, const char *label, int nargs, int nvals, apr_dbd_type_e *types, apr_dbd_prepared_t **statement) { int ret = 0; int i; apr_dbd_prepared_t *stmt ; if (*statement == NULL) { *statement = apr_pcalloc(pool, sizeof(apr_dbd_prepared_t)); } stmt = *statement; stmt->handle = sql; stmt->pool = pool; stmt->nargs = nargs; stmt->nvals = nvals; /* populate our own args, if any */ if (nargs > 0) { stmt->args = apr_pcalloc(pool, nargs*sizeof(bind_arg)); for (i = 0; i < nargs; i++) { stmt->args[i].type = types[i]; } } sql->status = OCIHandleAlloc(dbd_oracle_env, (dvoid**) &stmt->stmt, OCI_HTYPE_STMT, 0, NULL); if (sql->status != OCI_SUCCESS) { return 1; } sql->status = OCIStmtPrepare(stmt->stmt, sql->err, (text*) query, strlen(query), OCI_NTV_SYNTAX, OCI_DEFAULT); if (sql->status != OCI_SUCCESS) { OCIHandleFree(stmt->stmt, OCI_HTYPE_STMT); return 1; } apr_pool_cleanup_register(pool, stmt, freeStatement, apr_pool_cleanup_null); /* Perl gets statement type here */ sql->status = OCIAttrGet(stmt->stmt, OCI_HTYPE_STMT, &stmt->type, 0, OCI_ATTR_STMT_TYPE, sql->err); if (sql->status != OCI_SUCCESS) { return 1; } /* Perl sets PREFETCH_MEMORY here, but the docs say there's a working default */ #if 0 sql->status = OCIAttrSet(stmt->stmt, OCI_HTYPE_STMT, &prefetch_size, sizeof(prefetch_size), OCI_ATTR_PREFETCH_MEMORY, sql->err); if (sql->status != OCI_SUCCESS) { return 1; } #endif if (stmt->type == OCI_STMT_SELECT) { ret = outputParams(sql, stmt); } return ret; } static void dbd_oracle_bind(apr_dbd_prepared_t *statement, const char **values) { OCIStmt *stmt = statement->stmt; apr_dbd_t *sql = statement->handle; int i, j; sb2 null_ind = -1; for (i = 0, j = 0; i < statement->nargs; i++, j++) { if (values[j] == NULL) { sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, NULL, 0, SQLT_STR, &null_ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); } else { switch (statement->args[i].type) { case APR_DBD_TYPE_BLOB: { char *data = (char *)values[j]; int size = atoi((char*)values[++j]); /* skip table and column for now */ j += 2; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, data, size, SQLT_LBI, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); } break; case APR_DBD_TYPE_CLOB: { char *data = (char *)values[j]; int size = atoi((char*)values[++j]); /* skip table and column for now */ j += 2; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, data, size, SQLT_LNG, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); } break; default: sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, (dvoid*) values[j], strlen(values[j]) + 1, SQLT_STR, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; } } if (sql->status != OCI_SUCCESS) { return; } } return; } static int outputParams(apr_dbd_t *sql, apr_dbd_prepared_t *stmt) { OCIParam *parms; int i; ub2 paramtype[DBD_ORACLE_MAX_COLUMNS]; ub2 paramsize[DBD_ORACLE_MAX_COLUMNS]; char *paramname[DBD_ORACLE_MAX_COLUMNS]; ub4 paramnamelen[DBD_ORACLE_MAX_COLUMNS]; int_errorcode; /* Perl uses 0 where we used 1 */ sql->status = OCIStmtExecute(sql->svc, stmt->stmt, sql->err, 0, 0, NULL, NULL, OCI_DESCRIBE_ONLY); switch (sql->status) { case OCI_SUCCESS: case OCI_SUCCESS_WITH_INFO: break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Describing prepared statement: %s\n", sql->buf); #endif default: return 1; } while (sql->status == OCI_SUCCESS) { sql->status = OCIParamGet(stmt->stmt, OCI_HTYPE_STMT, sql->err, (dvoid**)&parms, stmt->nout+1); switch (sql->status) { case OCI_SUCCESS: sql->status = OCIAttrGet(parms, OCI_DTYPE_PARAM, ¶mtype[stmt->nout], 0, OCI_ATTR_DATA_TYPE, sql->err); sql->status = OCIAttrGet(parms, OCI_DTYPE_PARAM, ¶msize[stmt->nout], 0, OCI_ATTR_DATA_SIZE, sql->err); sql->status = OCIAttrGet(parms, OCI_DTYPE_PARAM, ¶mname[stmt->nout], ¶mnamelen[stmt->nout], OCI_ATTR_NAME, sql->err); ++stmt->nout; } } switch (sql->status) { case OCI_SUCCESS: break; case OCI_ERROR: break; /* this is what we expect at end-of-loop */ default: return 1; } /* OK, the above works. We have the params; now OCIDefine them */ stmt->out = apr_palloc(stmt->pool, stmt->nout*sizeof(define_arg)); for (i=0; inout; ++i) { stmt->out[i].type = paramtype[i]; stmt->out[i].len = stmt->out[i].sz = paramsize[i]; stmt->out[i].name = apr_pstrmemdup(stmt->pool, paramname[i], paramnamelen[i]); switch (stmt->out[i].type) { default: switch (stmt->out[i].type) { case SQLT_NUM: /* 2: numeric, Perl worst case=130+38+3 */ stmt->out[i].sz = 171; break; case SQLT_CHR: /* 1: char */ case SQLT_AFC: /* 96: ANSI fixed char */ stmt->out[i].sz *= 4; /* ugh, wasteful UCS-4 handling */ break; case SQLT_DAT: /* 12: date, depends on NLS date format */ stmt->out[i].sz = 75; break; case SQLT_BIN: /* 23: raw binary, perhaps UTF-16? */ stmt->out[i].sz *= 2; break; case SQLT_RID: /* 11: rowid */ case SQLT_RDD: /* 104: rowid descriptor */ stmt->out[i].sz = 20; break; case SQLT_TIMESTAMP: /* 187: timestamp */ case SQLT_TIMESTAMP_TZ: /* 188: timestamp with time zone */ case SQLT_INTERVAL_YM: /* 189: interval year-to-month */ case SQLT_INTERVAL_DS: /* 190: interval day-to-second */ case SQLT_TIMESTAMP_LTZ: /* 232: timestamp with local time zone */ stmt->out[i].sz = 75; break; default: #ifdef DEBUG printf("Unsupported data type: %d\n", stmt->out[i].type); #endif break; } ++stmt->out[i].sz; stmt->out[i].buf.raw = apr_palloc(stmt->pool, stmt->out[i].sz); sql->status = OCIDefineByPos(stmt->stmt, &stmt->out[i].defn, sql->err, i+1, stmt->out[i].buf.sval, stmt->out[i].sz, SQLT_STR, &stmt->out[i].ind, &stmt->out[i].len, 0, OCI_DEFAULT); break; case SQLT_LNG: /* 8: long */ stmt->out[i].sz = sql->long_size * 4 + 4; /* ugh, UCS-4 handling */ stmt->out[i].buf.raw = apr_palloc(stmt->pool, stmt->out[i].sz); sql->status = OCIDefineByPos(stmt->stmt, &stmt->out[i].defn, sql->err, i+1, stmt->out[i].buf.raw, stmt->out[i].sz, SQLT_LVC, &stmt->out[i].ind, NULL, 0, OCI_DEFAULT); break; case SQLT_LBI: /* 24: long binary, perhaps UTF-16? */ stmt->out[i].sz = sql->long_size * 2 + 4; /* room for int prefix */ stmt->out[i].buf.raw = apr_palloc(stmt->pool, stmt->out[i].sz); sql->status = OCIDefineByPos(stmt->stmt, &stmt->out[i].defn, sql->err, i+1, stmt->out[i].buf.raw, stmt->out[i].sz, SQLT_LVB, &stmt->out[i].ind, NULL, 0, OCI_DEFAULT); break; case SQLT_BLOB: /* 113 */ case SQLT_CLOB: /* 112 */ /*http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96584/oci05bnd.htm#434937*/ sql->status = OCIDescriptorAlloc(dbd_oracle_env, (dvoid**)&stmt->out[i].buf.lobval, OCI_DTYPE_LOB, 0, NULL); apr_pool_cleanup_register(stmt->pool, stmt->out[i].buf.lobval, dbd_free_lobdesc, apr_pool_cleanup_null); sql->status = OCIDefineByPos(stmt->stmt, &stmt->out[i].defn, sql->err, i+1, (dvoid*) &stmt->out[i].buf.lobval, -1, stmt->out[i].type, &stmt->out[i].ind, &stmt->out[i].len, 0, OCI_DEFAULT); break; } switch (sql->status) { case OCI_SUCCESS: break; default: return 1; } } return 0; } static int dbd_oracle_pquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, const char **values) { OCISnapshot *oldsnapshot = NULL; OCISnapshot *newsnapshot = NULL; apr_dbd_transaction_t* trans = sql->trans; int exec_mode; int_errorcode; if (trans) { switch (trans->status) { case TRANS_ERROR: return -1; case TRANS_NONE: trans = NULL; break; case TRANS_1: oldsnapshot = trans->snapshot1; newsnapshot = trans->snapshot2; trans->status = TRANS_2; break; case TRANS_2: oldsnapshot = trans->snapshot2; newsnapshot = trans->snapshot1; trans->status = TRANS_1; break; } exec_mode = OCI_DEFAULT; } else { exec_mode = OCI_COMMIT_ON_SUCCESS; } dbd_oracle_bind(statement, values); sql->status = OCIStmtExecute(sql->svc, statement->stmt, sql->err, 1, 0, oldsnapshot, newsnapshot, exec_mode); switch (sql->status) { case OCI_SUCCESS: break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Execute error %d: %s\n", sql->status, sql->buf); #endif /* fallthrough */ default: if (TXN_NOTICE_ERRORS(trans)) { trans->status = TRANS_ERROR; } return 1; } sql->status = OCIAttrGet(statement->stmt, OCI_HTYPE_STMT, nrows, 0, OCI_ATTR_ROW_COUNT, sql->err); return 0; } static int dbd_oracle_pvquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, va_list args) { const char **values; int i; if (sql->trans && sql->trans->status == TRANS_ERROR) { return -1; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const char*); } return dbd_oracle_pquery(pool, sql, nrows, statement, values); } static int dbd_oracle_pselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **results, apr_dbd_prepared_t *statement, int seek, const char **values) { int exec_mode = seek ? OCI_STMT_SCROLLABLE_READONLY : OCI_DEFAULT; OCISnapshot *oldsnapshot = NULL; OCISnapshot *newsnapshot = NULL; apr_dbd_transaction_t* trans = sql->trans; int_errorcode; if (trans) { switch (trans->status) { case TRANS_ERROR: return 1; case TRANS_NONE: trans = NULL; break; case TRANS_1: oldsnapshot = trans->snapshot1; newsnapshot = trans->snapshot2; trans->status = TRANS_2; break; case TRANS_2: oldsnapshot = trans->snapshot2; newsnapshot = trans->snapshot1; trans->status = TRANS_1; break; } } dbd_oracle_bind(statement, values); sql->status = OCIStmtExecute(sql->svc, statement->stmt, sql->err, 0, 0, oldsnapshot, newsnapshot, exec_mode); switch (sql->status) { case OCI_SUCCESS: break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Executing prepared statement: %s\n", sql->buf); #endif /* fallthrough */ default: if (TXN_NOTICE_ERRORS(trans)) { trans->status = TRANS_ERROR; } return 1; } if (!*results) { *results = apr_palloc(pool, sizeof(apr_dbd_results_t)); } (*results)->handle = sql; (*results)->statement = statement; (*results)->seek = seek; (*results)->rownum = seek ? 0 : -1; (*results)->pool = pool; return 0; } static int dbd_oracle_pvselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **results, apr_dbd_prepared_t *statement, int seek, va_list args) { const char **values; int i; if (sql->trans && sql->trans->status == TRANS_ERROR) { return -1; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const char*); } return dbd_oracle_pselect(pool, sql, results, statement, seek, values); } static void dbd_oracle_bbind(apr_dbd_prepared_t * statement, const void **values) { OCIStmt *stmt = statement->stmt; apr_dbd_t *sql = statement->handle; int i, j; sb2 null_ind = -1; apr_dbd_type_e type; for (i = 0, j = 0; i < statement->nargs; i++, j++) { type = (values[j] == NULL ? APR_DBD_TYPE_NULL : statement->args[i].type); switch (type) { case APR_DBD_TYPE_TINY: statement->args[i].value.ival = *(char*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.ival, sizeof(statement->args[i].value.ival), SQLT_INT, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_UTINY: statement->args[i].value.uval = *(unsigned char*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.uval, sizeof(statement->args[i].value.uval), SQLT_UIN, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_SHORT: statement->args[i].value.ival = *(short*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.ival, sizeof(statement->args[i].value.ival), SQLT_INT, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_USHORT: statement->args[i].value.uval = *(unsigned short*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.uval, sizeof(statement->args[i].value.uval), SQLT_UIN, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_INT: statement->args[i].value.ival = *(int*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.ival, sizeof(statement->args[i].value.ival), SQLT_INT, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_UINT: statement->args[i].value.uval = *(unsigned int*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.uval, sizeof(statement->args[i].value.uval), SQLT_UIN, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_LONG: statement->args[i].value.sval = apr_psprintf(statement->pool, "%ld", *(long*)values[j]); sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, statement->args[i].value.sval, strlen(statement->args[i].value.sval)+1, SQLT_STR, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_ULONG: statement->args[i].value.sval = apr_psprintf(statement->pool, "%lu", *(unsigned long*)values[j]); sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, statement->args[i].value.sval, strlen(statement->args[i].value.sval)+1, SQLT_STR, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_LONGLONG: statement->args[i].value.sval = apr_psprintf(statement->pool, "%" APR_INT64_T_FMT, *(apr_int64_t*)values[j]); sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, statement->args[i].value.sval, strlen(statement->args[i].value.sval)+1, SQLT_STR, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_ULONGLONG: statement->args[i].value.sval = apr_psprintf(statement->pool, "%" APR_UINT64_T_FMT, *(apr_uint64_t*)values[j]); sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, statement->args[i].value.sval, strlen(statement->args[i].value.sval)+1, SQLT_UIN, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_FLOAT: statement->args[i].value.fval = *(float*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.fval, sizeof(statement->args[i].value.fval), SQLT_FLT, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_DOUBLE: statement->args[i].value.fval = *(double*)values[j]; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, &statement->args[i].value.fval, sizeof(statement->args[i].value.fval), SQLT_FLT, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_STRING: case APR_DBD_TYPE_TEXT: case APR_DBD_TYPE_TIME: case APR_DBD_TYPE_DATE: case APR_DBD_TYPE_DATETIME: case APR_DBD_TYPE_TIMESTAMP: case APR_DBD_TYPE_ZTIMESTAMP: sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, (dvoid*) values[j], strlen(values[j]) + 1, SQLT_STR, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; case APR_DBD_TYPE_BLOB: { char *data = (char *)values[j]; apr_size_t size = *(apr_size_t*)values[++j]; /* skip table and column for now */ j += 2; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, data, size, SQLT_LBI, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); } break; case APR_DBD_TYPE_CLOB: { char *data = (char *)values[j]; apr_size_t size = *(apr_size_t*)values[++j]; /* skip table and column for now */ j += 2; sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, data, size, SQLT_LNG, &statement->args[i].ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); } break; case APR_DBD_TYPE_NULL: default: sql->status = OCIBindByPos(stmt, &statement->args[i].bind, sql->err, i + 1, NULL, 0, SQLT_STR, &null_ind, NULL, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT); break; } if (sql->status != OCI_SUCCESS) { return; } } return; } static int dbd_oracle_pbquery(apr_pool_t * pool, apr_dbd_t * sql, int *nrows, apr_dbd_prepared_t * statement, const void **values) { OCISnapshot *oldsnapshot = NULL; OCISnapshot *newsnapshot = NULL; apr_dbd_transaction_t* trans = sql->trans; int exec_mode; int_errorcode; if (trans) { switch (trans->status) { case TRANS_ERROR: return -1; case TRANS_NONE: trans = NULL; break; case TRANS_1: oldsnapshot = trans->snapshot1; newsnapshot = trans->snapshot2; trans->status = TRANS_2; break; case TRANS_2: oldsnapshot = trans->snapshot2; newsnapshot = trans->snapshot1; trans->status = TRANS_1; break; } exec_mode = OCI_DEFAULT; } else { exec_mode = OCI_COMMIT_ON_SUCCESS; } dbd_oracle_bbind(statement, values); sql->status = OCIStmtExecute(sql->svc, statement->stmt, sql->err, 1, 0, oldsnapshot, newsnapshot, exec_mode); switch (sql->status) { case OCI_SUCCESS: break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Execute error %d: %s\n", sql->status, sql->buf); #endif /* fallthrough */ default: if (TXN_NOTICE_ERRORS(trans)) { trans->status = TRANS_ERROR; } return 1; } sql->status = OCIAttrGet(statement->stmt, OCI_HTYPE_STMT, nrows, 0, OCI_ATTR_ROW_COUNT, sql->err); return 0; } static int dbd_oracle_pvbquery(apr_pool_t * pool, apr_dbd_t * sql, int *nrows, apr_dbd_prepared_t * statement, va_list args) { const void **values; int i; if (sql->trans && sql->trans->status == TRANS_ERROR) { return -1; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const void*); } return dbd_oracle_pbquery(pool, sql, nrows, statement, values); } static int dbd_oracle_pbselect(apr_pool_t * pool, apr_dbd_t * sql, apr_dbd_results_t ** results, apr_dbd_prepared_t * statement, int seek, const void **values) { int exec_mode = seek ? OCI_STMT_SCROLLABLE_READONLY : OCI_DEFAULT; OCISnapshot *oldsnapshot = NULL; OCISnapshot *newsnapshot = NULL; apr_dbd_transaction_t* trans = sql->trans; int_errorcode; if (trans) { switch (trans->status) { case TRANS_ERROR: return 1; case TRANS_NONE: trans = NULL; break; case TRANS_1: oldsnapshot = trans->snapshot1; newsnapshot = trans->snapshot2; trans->status = TRANS_2; break; case TRANS_2: oldsnapshot = trans->snapshot2; newsnapshot = trans->snapshot1; trans->status = TRANS_1; break; } } dbd_oracle_bbind(statement, values); sql->status = OCIStmtExecute(sql->svc, statement->stmt, sql->err, 0, 0, oldsnapshot, newsnapshot, exec_mode); switch (sql->status) { case OCI_SUCCESS: break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Executing prepared statement: %s\n", sql->buf); #endif /* fallthrough */ default: if (TXN_NOTICE_ERRORS(trans)) { trans->status = TRANS_ERROR; } return 1; } if (!*results) { *results = apr_palloc(pool, sizeof(apr_dbd_results_t)); } (*results)->handle = sql; (*results)->statement = statement; (*results)->seek = seek; (*results)->rownum = seek ? 0 : -1; (*results)->pool = pool; return 0; } static int dbd_oracle_pvbselect(apr_pool_t * pool, apr_dbd_t * sql, apr_dbd_results_t ** results, apr_dbd_prepared_t * statement, int seek, va_list args) { const void **values; int i; if (sql->trans && sql->trans->status == TRANS_ERROR) { return -1; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const void*); } return dbd_oracle_pbselect(pool, sql, results, statement, seek, values); } static int dbd_oracle_start_transaction(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_transaction_t **trans) { int ret = 0; int_errorcode; if (*trans) { dbd_oracle_end_transaction(*trans); } else { *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t)); OCIHandleAlloc(dbd_oracle_env, (dvoid**)&(*trans)->trans, OCI_HTYPE_TRANS, 0, 0); OCIAttrSet(sql->svc, OCI_HTYPE_SVCCTX, (*trans)->trans, 0, OCI_ATTR_TRANS, sql->err); } sql->status = OCITransStart(sql->svc, sql->err, TRANS_TIMEOUT, OCI_TRANS_NEW); switch (sql->status) { case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Transaction: %s\n", sql->buf); #endif ret = 1; break; case OCI_SUCCESS: (*trans)->handle = sql; (*trans)->status = TRANS_1; sql->trans = *trans; switch (OCIDescriptorAlloc(dbd_oracle_env, (dvoid**)&(*trans)->snapshot1, OCI_DTYPE_SNAP, 0, NULL)) { case OCI_SUCCESS: apr_pool_cleanup_register(pool, (*trans)->snapshot1, dbd_free_snapshot, apr_pool_cleanup_null); break; case OCI_INVALID_HANDLE: ret = 1; break; } switch (OCIDescriptorAlloc(dbd_oracle_env, (dvoid**)&(*trans)->snapshot2, OCI_DTYPE_SNAP, 0, NULL)) { case OCI_SUCCESS: apr_pool_cleanup_register(pool, (*trans)->snapshot2, dbd_free_snapshot, apr_pool_cleanup_null); break; case OCI_INVALID_HANDLE: ret = 1; break; } break; default: ret = 1; break; } return ret; } static int dbd_oracle_end_transaction(apr_dbd_transaction_t *trans) { int ret = 1; /* no transaction is an error cond */ sword status; apr_dbd_t *handle = trans->handle; if (trans) { switch (trans->status) { case TRANS_NONE: /* No trans is an error here */ status = OCI_ERROR; break; case TRANS_ERROR: status = OCITransRollback(handle->svc, handle->err, OCI_DEFAULT); break; default: /* rollback on explicit rollback request */ if (TXN_DO_ROLLBACK(trans)) { status = OCITransRollback(handle->svc, handle->err, OCI_DEFAULT); } else { status = OCITransCommit(handle->svc, handle->err, OCI_DEFAULT); } break; } handle->trans = NULL; switch (status) { case OCI_SUCCESS: ret = 0; break; default: ret = 3; break; } } return ret; } static int dbd_oracle_transaction_mode_get(apr_dbd_transaction_t *trans) { if (!trans) return APR_DBD_TRANSACTION_COMMIT; return trans->mode; } static int dbd_oracle_transaction_mode_set(apr_dbd_transaction_t *trans, int mode) { if (!trans) return APR_DBD_TRANSACTION_COMMIT; return trans->mode = (mode & TXN_MODE_BITS); } /* This doesn't work for BLOB because of NULLs, but it can fake it * if the BLOB is really a string */ static const char *dbd_oracle_get_entry(const apr_dbd_row_t *row, int n) { ub4 len = 0; ub1 csform = 0; ub2 csid = 0; apr_size_t buflen = 0; char *buf = NULL; define_arg *val = &row->res->statement->out[n]; apr_dbd_t *sql = row->res->handle; int_errorcode; if ((n < 0) || (n >= row->res->statement->nout) || (val->ind == -1)) { return NULL; } switch (val->type) { case SQLT_BLOB: case SQLT_CLOB: sql->status = OCILobGetLength(sql->svc, sql->err, val->buf.lobval, &len); switch (sql->status) { case OCI_SUCCESS: case OCI_SUCCESS_WITH_INFO: if (len == 0) { buf = ""; } break; case OCI_ERROR: #ifdef DEBUG OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Finding LOB length: %s\n", sql->buf); break; #endif default: break; } if (len == 0) { break; } if (val->type == APR_DBD_TYPE_CLOB) { #if 1 /* Is this necessary, or can it be defaulted? */ sql->status = OCILobCharSetForm(dbd_oracle_env, sql->err, val->buf.lobval, &csform); if (sql->status == OCI_SUCCESS) { sql->status = OCILobCharSetId(dbd_oracle_env, sql->err, val->buf.lobval, &csid); } switch (sql->status) { case OCI_SUCCESS: case OCI_SUCCESS_WITH_INFO: buflen = (len+1) * 4; /* ugh, wasteful UCS-4 handling */ /* zeroise all - where the string ends depends on charset */ buf = apr_pcalloc(row->pool, buflen); break; #ifdef DEBUG case OCI_ERROR: OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Reading LOB character set: %s\n", sql->buf); break; /*** XXX?? ***/ #endif default: break; /*** XXX?? ***/ } #else /* ignore charset */ buflen = (len+1) * 4; /* ugh, wasteful UCS-4 handling */ /* zeroise all - where the string ends depends on charset */ buf = apr_pcalloc(row->pool, buflen); #endif } else { /* BUG: this'll only work if the BLOB looks like a string */ buflen = len; buf = apr_palloc(row->pool, buflen+1); buf[buflen] = 0; } if (!buf) { break; } sql->status = OCILobRead(sql->svc, sql->err, val->buf.lobval, &len, 1, (dvoid*) buf, buflen, NULL, NULL, csid, csform); switch (sql->status) { case OCI_SUCCESS: case OCI_SUCCESS_WITH_INFO: break; #ifdef DEBUG case OCI_ERROR: OCIErrorGet(sql->err, 1, NULL, &errorcode, sql->buf, sizeof(sql->buf), OCI_HTYPE_ERROR); printf("Reading LOB: %s\n", sql->buf); buf = NULL; /*** XXX?? ***/ break; #endif default: buf = NULL; /*** XXX?? ***/ break; } break; case SQLT_LNG: case SQLT_LBI: /* raw is struct { ub4 len; char *buf; } */ len = *(ub4*) val->buf.raw; buf = apr_pstrndup(row->pool, val->buf.sval + sizeof(ub4), len); break; default: buf = apr_pstrndup(row->pool, val->buf.sval, val->len); break; } return (const char*) buf; } /* XXX Should this use Oracle proper API instead of calling get_entry()? */ static apr_status_t dbd_oracle_datum_get(const apr_dbd_row_t *row, int n, apr_dbd_type_e type, void *data) { define_arg *val = &row->res->statement->out[n]; const char *entry; if ((n < 0) || (n >= row->res->statement->nout)) { return APR_EGENERAL; } if(val->ind == -1) { return APR_ENOENT; } switch (type) { case APR_DBD_TYPE_TINY: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(char*)data = atoi(entry); break; case APR_DBD_TYPE_UTINY: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(unsigned char*)data = atoi(entry); break; case APR_DBD_TYPE_SHORT: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(short*)data = atoi(entry); break; case APR_DBD_TYPE_USHORT: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(unsigned short*)data = atoi(entry); break; case APR_DBD_TYPE_INT: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(int*)data = atoi(entry); break; case APR_DBD_TYPE_UINT: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(unsigned int*)data = atoi(entry); break; case APR_DBD_TYPE_LONG: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(long*)data = atol(entry); break; case APR_DBD_TYPE_ULONG: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(unsigned long*)data = atol(entry); break; case APR_DBD_TYPE_LONGLONG: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(apr_int64_t*)data = apr_atoi64(entry); break; case APR_DBD_TYPE_ULONGLONG: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(apr_uint64_t*)data = apr_atoi64(entry); break; case APR_DBD_TYPE_FLOAT: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(float*)data = (float)atof(entry); break; case APR_DBD_TYPE_DOUBLE: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(double*)data = atof(entry); break; case APR_DBD_TYPE_STRING: case APR_DBD_TYPE_TEXT: case APR_DBD_TYPE_TIME: case APR_DBD_TYPE_DATE: case APR_DBD_TYPE_DATETIME: case APR_DBD_TYPE_TIMESTAMP: case APR_DBD_TYPE_ZTIMESTAMP: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } *(char**)data = (char*)entry; break; case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: { apr_bucket *e; apr_bucket_brigade *b = (apr_bucket_brigade*)data; apr_dbd_t *sql = row->res->handle; ub4 len = 0; switch (val->type) { case SQLT_BLOB: case SQLT_CLOB: sql->status = OCILobGetLength(sql->svc, sql->err, val->buf.lobval, &len); switch(sql->status) { case OCI_SUCCESS: case OCI_SUCCESS_WITH_INFO: if (len == 0) { e = apr_bucket_eos_create(b->bucket_alloc); } else { e = apr_bucket_lob_create(row, n, 0, len, row->pool, b->bucket_alloc); } break; default: return APR_ENOENT; } break; default: entry = dbd_oracle_get_entry(row, n); if (entry == NULL) { return APR_ENOENT; } e = apr_bucket_pool_create(entry, strlen(entry), row->pool, b->bucket_alloc); break; } APR_BRIGADE_INSERT_TAIL(b, e); } break; case APR_DBD_TYPE_NULL: *(void**)data = NULL; break; default: return APR_EGENERAL; } return APR_SUCCESS; } static apr_status_t dbd_oracle_close(apr_dbd_t *handle) { /* FIXME: none of the oracle docs/examples say anything about * closing/releasing handles. Which seems unlikely ... */ /* OK, let's grab from cdemo again. * cdemo81 does nothing; cdemo82 does OCIHandleFree on the handles */ switch (OCISessionEnd(handle->svc, handle->err, handle->auth, (ub4)OCI_DEFAULT)) { default: break; } switch (OCIServerDetach(handle->svr, handle->err, (ub4) OCI_DEFAULT )) { default: break; } /* does OCISessionEnd imply this? */ switch (OCIHandleFree((dvoid *) handle->auth, (ub4) OCI_HTYPE_SESSION)) { default: break; } switch (OCIHandleFree((dvoid *) handle->svr, (ub4) OCI_HTYPE_SERVER)) { default: break; } switch (OCIHandleFree((dvoid *) handle->svc, (ub4) OCI_HTYPE_SVCCTX)) { default: break; } switch (OCIHandleFree((dvoid *) handle->err, (ub4) OCI_HTYPE_ERROR)) { default: break; } return APR_SUCCESS; } static apr_status_t dbd_oracle_check_conn(apr_pool_t *pool, apr_dbd_t *sql) { apr_dbd_results_t *res = NULL; apr_dbd_row_t *row = NULL; if(dbd_oracle_pselect(pool, sql, &res, sql->check_conn_stmt, 0, NULL) != 0) { return APR_EGENERAL; } if(dbd_oracle_get_row(pool, res, &row, -1) != 0) { return APR_EGENERAL; } if(dbd_oracle_get_row(pool, res, &row, -1) != -1) { return APR_EGENERAL; } return APR_SUCCESS; } static int dbd_oracle_select_db(apr_pool_t *pool, apr_dbd_t *handle, const char *name) { /* FIXME: need to find this in the docs */ return APR_ENOTIMPL; } static void *dbd_oracle_native(apr_dbd_t *handle) { /* FIXME: can we do anything better? Oracle doesn't seem to have * a concept of a handle in the sense we use it. */ return dbd_oracle_env; } static int dbd_oracle_num_cols(apr_dbd_results_t* res) { return res->statement->nout; } static int dbd_oracle_num_tuples(apr_dbd_results_t* res) { if (!res->seek) { return -1; } if (res->nrows >= 0) { return res->nrows; } res->handle->status = OCIAttrGet(res->statement->stmt, OCI_HTYPE_STMT, &res->nrows, 0, OCI_ATTR_ROW_COUNT, res->handle->err); return res->nrows; } APU_MODULE_DECLARE_DATA const apr_dbd_driver_t apr_dbd_oracle_driver = { "oracle", dbd_oracle_init, dbd_oracle_native, dbd_oracle_open, dbd_oracle_check_conn, dbd_oracle_close, dbd_oracle_select_db, dbd_oracle_start_transaction, dbd_oracle_end_transaction, dbd_oracle_query, dbd_oracle_select, dbd_oracle_num_cols, dbd_oracle_num_tuples, dbd_oracle_get_row, dbd_oracle_get_entry, dbd_oracle_error, dbd_oracle_escape, dbd_oracle_prepare, dbd_oracle_pvquery, dbd_oracle_pvselect, dbd_oracle_pquery, dbd_oracle_pselect, dbd_oracle_get_name, dbd_oracle_transaction_mode_get, dbd_oracle_transaction_mode_set, ":apr%d", dbd_oracle_pvbquery, dbd_oracle_pvbselect, dbd_oracle_pbquery, dbd_oracle_pbselect, dbd_oracle_datum_get }; #endif