DIAG_ON(unused-macros)
#include "rlm_sql.h"
-
-static int max_dflt(CONF_PAIR **out, void *parent, CONF_SECTION *cs, fr_token_t quote, conf_parser_t const *rule);
+#include "rlm_sql_trunk.h"
typedef struct {
- OCIEnv *env; //!< Number of columns associated with the result set
- OCIError *error; //!< Oracle error handle
- OCISPool *pool; //!< Oracle session pool handle
- char *pool_name; //!< The name of the session pool returned by OCISessionPoolCreate
- ub4 pool_name_len; //!< Length of pool_name in bytes.
-
- uint32_t stmt_cache_size; //!< Statement cache size for each of the sessions in a session pool
- uint32_t spool_timeout; //!< The sessions idle time (in seconds) (0 disable).
- uint32_t spool_min; //!< Specifies the minimum number of sessions in the session pool.
- uint32_t spool_max; //!< Specifies the maximum number of sessions that can be opened in the session pool
- uint32_t spool_inc; //!< Specifies the increment for sessions to be started if the current number of sessions are less than sessMax
+ OCIEnv *env; //!< Environment handle
+ uint32_t stmt_cache_size; //!< Statement cache size
} rlm_sql_oracle_t;
typedef struct {
- OCIStmt *query;
- OCIError *error;
- OCISvcCtx *ctx;
- sb2 *ind;
- char **row;
- int id;
- int col_count; //!< Number of columns associated with the result set
+ OCIStmt *query; //!< Query handle
+ OCIError *error; //!< Error handle
+ OCIServer *srv; //!< Server handle
+ OCISvcCtx *ctx; //!< Service handle
+ OCISession *sess; //!< Session handle
+ sb2 *ind; //!< Indicators regarding contents of the results row.
+ rlm_sql_row_t row; //!< Results row
+ int col_count; //!< Number of columns associated with the result set
+ connection_t *conn; //!< Generic connection structure for this connection.
+ rlm_sql_config_t const *config; //!< SQL instance configuration.
+ fr_sql_query_t *query_ctx; //!< Current request running on the connection.
+ fr_event_timer_t const *read_ev; //!< Timer event for polling reading this connection
+ fr_event_timer_t const *write_ev; //!< Timer event for polling writing this connection
+ uint select_interval; //!< How frequently this connection gets polled for select queries.
+ uint query_interval; //!< How frequently this connection gets polled for other queries.
+ uint poll_count; //!< How many polls have been done for the current query.
} rlm_sql_oracle_conn_t;
-static const conf_parser_t spool_config[] = {
- { FR_CONF_OFFSET("stmt_cache_size", rlm_sql_oracle_t, stmt_cache_size), .dflt = "32" },
- { FR_CONF_OFFSET("timeout", rlm_sql_oracle_t, spool_timeout), .dflt = "0" },
- { FR_CONF_OFFSET("min", rlm_sql_oracle_t, spool_min), .dflt = "1" },
- { FR_CONF_OFFSET("max", rlm_sql_oracle_t, spool_max), .dflt_func = max_dflt },
- { FR_CONF_OFFSET("inc", rlm_sql_oracle_t, spool_inc), .dflt = "1" },
- CONF_PARSER_TERMINATOR
-};
-
static const conf_parser_t driver_config[] = {
- { FR_CONF_POINTER("spool", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) spool_config },
+ { FR_CONF_OFFSET("stmt_cache_size", rlm_sql_oracle_t, stmt_cache_size), .dflt = "32" },
CONF_PARSER_TERMINATOR
};
-static int max_dflt(CONF_PAIR **out, UNUSED void *parent, CONF_SECTION *cs, fr_token_t quote, conf_parser_t const *rule)
-{
- char *strvalue;
-
- strvalue = talloc_asprintf(NULL, "%u", main_config->max_workers);
- *out = cf_pair_alloc(cs, rule->name1, strvalue, T_OP_EQ, T_BARE_WORD, quote);
- talloc_free(strvalue);
-
- return 0;
-}
-
#define MAX_DATASTR_LEN 64
/** Write the last Oracle error out to a buffer
*
* @param out Where to write the error (should be at least 512 bytes).
* @param outlen The length of the error buffer.
- * @param handle sql handle.
- * @param config Instance config.
+ * @param conn Oracle connection.
* @return
- * - 0 on success.
+ * - Oracle error code on success.
* - -1 if there was no error.
*/
-static int sql_snprint_error(char *out, size_t outlen, rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t const *config)
+static int sql_snprint_error(char *out, size_t outlen, rlm_sql_oracle_conn_t *conn)
{
- sb4 errcode = 0;
- rlm_sql_oracle_conn_t *conn = handle->conn;
+ sb4 errcode = 0;
fr_assert(conn);
outlen, OCI_HTYPE_ERROR);
if (!errcode) return -1;
- return 0;
+ return errcode;
}
/** Retrieves any errors associated with the query context
* @return number of errors written to the #sql_log_entry_t array.
*/
static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], NDEBUG_UNUSED size_t outlen,
- fr_sql_query_t *query_ctx, rlm_sql_config_t const *config)
+ fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
{
char errbuff[512];
int ret;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
fr_assert(outlen > 0);
- ret = sql_snprint_error(errbuff, sizeof(errbuff), query_ctx->handle, config);
+ ret = sql_snprint_error(errbuff, sizeof(errbuff), conn);
if (ret < 0) return 0;
out[0].type = L_ERR;
{
rlm_sql_oracle_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_sql_oracle_t);
- if (inst->pool) OCISessionPoolDestroy((dvoid *)inst->pool, (dvoid *)inst->error, OCI_DEFAULT );
- if (inst->error) OCIHandleFree((dvoid *)inst->error, OCI_HTYPE_ERROR);
if (inst->env) OCIHandleFree((dvoid *)inst->env, OCI_HTYPE_ENV);
return 0;
static int mod_instantiate(module_inst_ctx_t const *mctx)
{
- rlm_sql_t const *parent = talloc_get_type_abort(mctx->mi->parent->data, rlm_sql_t);
- rlm_sql_config_t const *config = &parent->config;
rlm_sql_oracle_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_sql_oracle_t);
- char errbuff[512];
- sb4 errcode = 0;
- OraText *sql_password = NULL;
- OraText *sql_login = NULL;
-
- if (!cf_section_find(mctx->mi->conf, "spool", NULL)) {
- ERROR("Couldn't load mctx->configuration of session pool(\"spool\" section in driver mctx->config)");
- return RLM_SQL_ERROR;
- }
/*
* Initialises the oracle environment
*/
if (OCIEnvCreate(&inst->env, OCI_DEFAULT | OCI_THREADED, NULL, NULL, NULL, NULL, 0, NULL)) {
ERROR("Couldn't init Oracle OCI environment (OCIEnvCreate())");
-
- return RLM_SQL_ERROR;
- }
-
- /*
- * Allocates an error handle
- */
- if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&inst->error, OCI_HTYPE_ERROR, 0, NULL)) {
- ERROR("Couldn't init Oracle ERROR handle (OCIHandleAlloc())");
-
- return RLM_SQL_ERROR;
- }
-
- /*
- * Allocates an session pool handle
- */
- if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&inst->pool, OCI_HTYPE_SPOOL, 0, NULL)) {
- ERROR("Couldn't init Oracle session pool (OCIHandleAlloc())");
- return RLM_SQL_ERROR;
- }
-
- /*
- * Create session pool
- */
- DEBUG2("OCISessionPoolCreate min=%d max=%d inc=%d", inst->spool_min, inst->spool_max, inst->spool_inc);
-
- /* We need it to fix const issues between 'const char *' vs 'unsigned char *' */
- memcpy(&sql_login, &config->sql_login, sizeof(sql_login));
- memcpy(&sql_password, &config->sql_password, sizeof(sql_password));
-
- if (OCISessionPoolCreate((dvoid *)inst->env, (dvoid *)inst->error, (dvoid *)inst->pool,
- (OraText**)&inst->pool_name, (ub4*)&inst->pool_name_len,
- (CONST OraText *)config->sql_db, strlen(config->sql_db),
- inst->spool_min, inst->spool_max, inst->spool_inc,
- sql_login, strlen(config->sql_login),
- sql_password, strlen(config->sql_password),
- OCI_SPC_STMTCACHE | OCI_SPC_HOMOGENEOUS)) {
-
- errbuff[0] = '\0';
- OCIErrorGet((dvoid *) inst->error, 1, (OraText *) NULL, &errcode, (OraText *) errbuff,
- sizeof(errbuff), OCI_HTYPE_ERROR);
- if (!errcode) return RLM_SQL_ERROR;
-
- ERROR("Oracle create session pool failed: '%s'", errbuff);
- return RLM_SQL_ERROR;
- }
-
- if (inst->spool_timeout > 0) {
- if (OCIAttrSet(inst->pool, OCI_HTYPE_SPOOL, &inst->spool_timeout, 0,
- OCI_ATTR_SPOOL_TIMEOUT, inst->error) != OCI_SUCCESS) {
- ERROR("Couldn't set Oracle session idle time");
- return RLM_SQL_ERROR;
- }
- }
-
- if (OCIAttrSet(inst->pool, OCI_HTYPE_SPOOL, &inst->stmt_cache_size, 0,
- OCI_ATTR_SPOOL_STMTCACHESIZE, inst->error) != OCI_SUCCESS) {
- ERROR("Couldn't set Oracle default statement cache size");
- return RLM_SQL_ERROR;
+ return -1;
}
return 0;
}
-static sql_rcode_t sql_check_reconnect(rlm_sql_handle_t *handle, rlm_sql_config_t const *config)
+static sql_rcode_t sql_check_reconnect(rlm_sql_oracle_conn_t *conn)
{
char errbuff[512];
- if (sql_snprint_error(errbuff, sizeof(errbuff), handle, config) < 0) return -1;
+ if (sql_snprint_error(errbuff, sizeof(errbuff), conn) < 0) return -1;
if (strstr(errbuff, "ORA-03113") || strstr(errbuff, "ORA-03114")) {
ERROR("OCI_SERVER_NOT_CONNECTED");
return RLM_SQL_ERROR;
}
-static int _sql_socket_destructor(rlm_sql_oracle_conn_t *conn)
+static void _sql_connection_close(UNUSED fr_event_list_t *el, void *h, UNUSED void *uctx)
{
- if (conn->ctx) OCISessionRelease(conn->ctx, conn->error, NULL, 0, OCI_DEFAULT);
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(h, rlm_sql_oracle_conn_t);
+
+ if (conn->sess) {
+ OCISessionEnd(conn->ctx, conn->error, conn->sess, OCI_DEFAULT);
+ OCIHandleFree((dvoid *)conn->sess, OCI_HTYPE_SESSION);
+ }
+ if (conn->ctx) OCIHandleFree((dvoid *)conn->ctx, OCI_HTYPE_SVCCTX);
+ if (conn->srv) {
+ OCIServerDetach(conn->srv, conn->error, OCI_DEFAULT);
+ OCIHandleFree((dvoid *)conn->srv, OCI_HTYPE_SERVER);
+ }
if (conn->error) OCIHandleFree((dvoid *)conn->error, OCI_HTYPE_ERROR);
- return 0;
+
+ talloc_free(h);
}
-static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t const *config,
- UNUSED fr_time_delta_t timeout)
+#define ORACLE_ERROR(_message) \
+ sql_snprint_error(errbuff, sizeof(errbuff), c); \
+ ERROR(_message ": %s", errbuff); \
+ return CONNECTION_STATE_FAILED
+
+CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
+static connection_state_t _sql_connection_init(void **h, connection_t *conn, void *uctx)
{
- char errbuff[512];
+ rlm_sql_t const *sql = talloc_get_type_abort_const(uctx, rlm_sql_t);
+ rlm_sql_oracle_t *inst = talloc_get_type_abort(sql->driver_submodule->data, rlm_sql_oracle_t);
+ rlm_sql_oracle_conn_t *c;
+ char errbuff[512];
+ OraText *sql_password = NULL;
+ OraText *sql_login = NULL;
- rlm_sql_oracle_t *inst = talloc_get_type_abort(handle->inst->driver_submodule->data, rlm_sql_oracle_t);
- rlm_sql_oracle_conn_t *conn;
+ MEM(c = talloc_zero(conn, rlm_sql_oracle_conn_t));
+ *c = (rlm_sql_oracle_conn_t) {
+ .conn = conn,
+ .config = &sql->config,
+ .select_interval = 1000, /* Default starting poll interval - 1ms */
+ .query_interval = 1000,
+ };
- MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_oracle_conn_t));
- talloc_set_destructor(conn, _sql_socket_destructor);
+ /*
+ * Although there are simpler methods to start a connection using a connection
+ * pool, since we need to set an option on the server handle, to enable
+ * non-blocking mode, we have to follow this overly complicated sequence of
+ * handle creation.
+ */
/*
- * Allocates an error handle
+ * Allocate an error handle
*/
- if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&conn->error, OCI_HTYPE_ERROR, 0, NULL)) {
+ if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&c->error, OCI_HTYPE_ERROR, 0, NULL) != OCI_SUCCESS) {
ERROR("Couldn't init Oracle ERROR handle (OCIHandleAlloc())");
+ return CONNECTION_STATE_FAILED;
+ }
- return RLM_SQL_ERROR;
+ /*
+ * Allocate a server handle and attache to a connection from the pool
+ */
+ if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&c->srv, (ub4)OCI_HTYPE_SERVER, 0, NULL) != OCI_SUCCESS) {
+ ERROR("Couldn't allocate Oracle SERVER handle");
+ return CONNECTION_STATE_FAILED;
+ }
+ if (OCIServerAttach(c->srv, c->error, (CONST OraText *)sql->config.sql_db, strlen(sql->config.sql_db), (ub4)OCI_DEFAULT) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to attach");
}
/*
- * Get session from pool
+ * Allocate the service handle (which queries are run on) and associate it with the server
*/
- if (OCISessionGet((dvoid *)inst->env, conn->error, &conn->ctx, NULL,
- (OraText *)inst->pool_name, inst->pool_name_len,
- NULL, 0, NULL, NULL, NULL,
- OCI_SESSGET_SPOOL | OCI_SESSGET_STMTCACHE) != OCI_SUCCESS) {
- ERROR("Oracle get sessin from pool[%s] failed: '%s'",
- inst->pool_name,
- (sql_snprint_error(errbuff, sizeof(errbuff), handle, config) == 0) ? errbuff : "unknown");
+ if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&c->ctx, OCI_HTYPE_SVCCTX, 0, NULL) != OCI_SUCCESS) {
+ ERROR("Couldn't allocate Oracle SERVICE handle");
+ return CONNECTION_STATE_FAILED;
+ }
+ if (OCIAttrSet((dvoid *)c->ctx, OCI_HTYPE_SVCCTX, (dvoid *)c->srv, 0, OCI_ATTR_SERVER, c->error) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to link service and server handles");
+ }
- return RLM_SQL_ERROR;
+
+ if (OCIHandleAlloc((dvoid *)inst->env, (dvoid **)&c->sess, OCI_HTYPE_SESSION, 0, NULL) != OCI_SUCCESS) {
+ ERROR("Couldn't allocate Oracle SESSION handle");
+ return CONNECTION_STATE_FAILED;
}
- return RLM_SQL_OK;
-}
+ /*
+ * We need to fix const issues between 'const char *' vs 'unsigned char *'
+ */
+ memcpy(&sql_login, &c->config->sql_login, sizeof(sql_login));
+ memcpy(&sql_password, &c->config->sql_password, sizeof(sql_password));
+ if (OCIAttrSet((dvoid *)c->sess, OCI_HTYPE_SESSION, sql_login, strlen(c->config->sql_login),
+ OCI_ATTR_USERNAME, c->error) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to set username");
+ }
+ if (OCIAttrSet((dvoid *)c->sess, OCI_HTYPE_SESSION, sql_password, strlen(c->config->sql_password),
+ OCI_ATTR_PASSWORD, c->error) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to set password");
+ }
-static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
-{
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
- int fields, i, status;
- char const **names;
- OCIParam *param;
+ if (OCISessionBegin((dvoid *)c->ctx, c->error, c->sess, OCI_CRED_RDBMS, OCI_STMT_CACHE) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to start session");
+ }
- if (OCIAttrGet((dvoid *)conn->query, OCI_HTYPE_STMT, (dvoid *)&fields, NULL, OCI_ATTR_PARAM_COUNT,
- conn->error)) return RLM_SQL_ERROR;
- if (fields == 0) return RLM_SQL_ERROR;
+ if (OCIAttrSet((dvoid *)c->ctx, OCI_HTYPE_SVCCTX, (dvoid *)c->sess, 0, OCI_ATTR_SESSION, c->error) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to link service and session handles");
+ }
- MEM(names = talloc_array(query_ctx, char const *, fields));
+ if (OCIAttrSet((dvoid *)c->ctx, OCI_HTYPE_SVCCTX, (dvoid *)&inst->stmt_cache_size, 0,
+ OCI_ATTR_STMTCACHESIZE, c->error) != OCI_SUCCESS) {
+ ORACLE_ERROR("Failed to set statement cache size");
+ }
- for (i = 0; i < fields; i++) {
- OraText *pcol_name = NULL;
- ub4 pcol_size = 0;
+ /*
+ * Set the server to be non-blocking if we can.
+ */
+ if (OCIAttrSet((dvoid *)c->srv, OCI_HTYPE_SERVER, (dvoid *)0, 0, OCI_ATTR_NONBLOCKING_MODE, c->error) != OCI_SUCCESS) {
+ sql_snprint_error(errbuff, sizeof(errbuff), c);
+ WARN("Cound not set non-blocking mode: %s", errbuff);
+ }
- status = OCIParamGet(conn->query, OCI_HTYPE_STMT, conn->error, (dvoid **)¶m, i + 1);
- if (status != OCI_SUCCESS) {
- ERROR("OCIParamGet(OCI_HTYPE_STMT) failed in sql_fields()");
- error:
- talloc_free(names);
+ *h = c;
- return RLM_SQL_ERROR;
+ return CONNECTION_STATE_CONNECTED;
+}
+
+SQL_TRUNK_CONNECTION_ALLOC
+
+CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
+static void sql_trunk_request_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
+{
+ rlm_sql_oracle_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_oracle_conn_t);
+ request_t *request;
+ trunk_request_t *treq;
+ fr_sql_query_t *query_ctx;
+ sword ret;
+ char errbuff[512];
+
+ if (trunk_connection_pop_request(&treq, tconn) != 0) return;
+ if (!treq) return;
+
+ query_ctx = talloc_get_type_abort(treq->preq, fr_sql_query_t);
+ request = query_ctx->request;
+
+ switch(query_ctx->status) {
+ case SQL_QUERY_PREPARED:
+ ROPTIONAL(RDEBUG2, DEBUG2, "Executing query: %s", query_ctx->query_str);
+ if (OCIStmtPrepare2(sql_conn->ctx, &sql_conn->query, sql_conn->error,
+ (const OraText *)query_ctx->query_str, strlen(query_ctx->query_str),
+ NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT)) {
+ sql_snprint_error(errbuff, sizeof(errbuff), sql_conn);
+ ERROR("Failed to prepare query: %s", errbuff);
+ trunk_request_signal_fail(treq);
+ return;
}
- status = OCIAttrGet((dvoid **)param, OCI_DTYPE_PARAM, &pcol_name, &pcol_size,
- OCI_ATTR_NAME, conn->error);
- if (status != OCI_SUCCESS) {
- ERROR("OCIParamGet(OCI_ATTR_NAME) failed in sql_fields()");
+ switch (query_ctx->type) {
+ case SQL_QUERY_SELECT:
+ ret = OCIStmtExecute(sql_conn->ctx, sql_conn->query, sql_conn->error, 0, 0, NULL, NULL, OCI_DEFAULT);
+ break;
- goto error;
+ default:
+ ret = OCIStmtExecute(sql_conn->ctx, sql_conn->query, sql_conn->error, 1, 0, NULL, NULL,
+ OCI_COMMIT_ON_SUCCESS);
+ break;
}
+ query_ctx->tconn = tconn;
+
+ switch (ret) {
+ case OCI_STILL_EXECUTING:
+ ROPTIONAL(RDEBUG3, DEBUG3, "Awaiting response");
+ query_ctx->status = SQL_QUERY_SUBMITTED;
+ sql_conn->query_ctx = query_ctx;
+ sql_conn->poll_count = 0;
+ trunk_request_signal_sent(treq);
+ return;
+
+ case OCI_SUCCESS:
+ query_ctx->rcode = RLM_SQL_OK;
+ break;
- names[i] = (char const *)pcol_name;
+ case OCI_NO_DATA:
+ query_ctx->rcode = RLM_SQL_NO_MORE_ROWS;
+ break;
+
+ default:
+ /*
+ * Error code 1 is unique contraint violated
+ */
+ if (sql_snprint_error(errbuff, sizeof(errbuff), sql_conn) == 1) {
+ query_ctx->rcode = RLM_SQL_ALT_QUERY;
+ break;
+ }
+ ERROR("SQL query failed: %s", errbuff);
+ trunk_request_signal_fail(treq);
+ if (sql_check_reconnect(sql_conn) == RLM_SQL_RECONNECT) {
+ connection_signal_reconnect(sql_conn->conn, CONNECTION_FAILED);
+ }
+ return;
+ }
+ query_ctx->status = SQL_QUERY_RETURNED;
+ break;
+
+ default:
+ return;
}
- *out = names;
+ ROPTIONAL(RDEBUG3, DEBUG3, "Got immediate response");
+ trunk_request_signal_reapable(treq);
+ if (request) unlang_interpret_mark_runnable(request);
+}
- return RLM_SQL_OK;
+CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
+static void sql_request_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason,
+ UNUSED void *uctx)
+{
+ fr_sql_query_t *query_ctx = talloc_get_type_abort(preq, fr_sql_query_t);
+ rlm_sql_oracle_conn_t *sql_conn= talloc_get_type_abort(conn->h, rlm_sql_oracle_conn_t);
+
+ if (!query_ctx->treq) return;
+ if (reason != TRUNK_CANCEL_REASON_SIGNAL) return;
+ if (sql_conn->query_ctx == query_ctx) sql_conn->query_ctx = NULL;
}
-static unlang_action_t sql_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
+CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
+static void sql_request_cancel_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn,
+ connection_t *conn, UNUSED void *uctx)
{
- fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
- int status;
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+ trunk_request_t *treq;
+ rlm_sql_oracle_conn_t *sql_conn = talloc_get_type_abort(conn->h, rlm_sql_oracle_conn_t);
+ sword status;
- if (!conn->ctx) {
- ERROR("Socket not connected");
+ if ((trunk_connection_pop_cancellation(&treq, tconn)) != 0) return;
+ if (!treq) return;
- query_ctx->rcode = RLM_SQL_RECONNECT;
- RETURN_MODULE_FAIL;
+ /*
+ * Oracle cancels non-blocking operations with this pair of calls
+ * They operate on the service context handle, and since only one
+ * query will be on a connection at a time, that is what will be cancelled.
+ *
+ * It's not clear from the documentation as to whether this can return
+ * OCI_STILL_EXECUTING - so we allow for that.
+ */
+ status = OCIBreak(sql_conn->ctx, sql_conn->error);
+ switch (status) {
+ case OCI_STILL_EXECUTING:
+ trunk_request_signal_cancel_sent(treq);
+ return;
+
+ case OCI_SUCCESS:
+ break;
+
+ default:
+ {
+ char errbuff[512];
+ sql_snprint_error(errbuff, sizeof(errbuff), sql_conn);
+ ERROR("Failed cancelling query: %s", errbuff);
}
+ }
+ OCIReset(sql_conn->ctx, sql_conn->error) ;
- query_ctx->rcode = RLM_SQL_ERROR;
+ trunk_request_signal_cancel_complete(treq);
+}
- if (OCIStmtPrepare2(conn->ctx, &conn->query, conn->error, (const OraText *)query_ctx->query_str, strlen(query_ctx->query_str),
- NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT)) {
- ERROR("prepare failed in sql_query");
+static void sql_trunk_connection_read_poll(fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+ rlm_sql_oracle_conn_t *c = talloc_get_type_abort(uctx, rlm_sql_oracle_conn_t);
+ fr_sql_query_t *query_ctx = c->query_ctx;
+ trunk_request_t *treq = query_ctx->treq;
+ request_t *request = query_ctx->request;
+ sword ret = OCI_SUCCESS;
+
+ switch (query_ctx->status) {
+ case SQL_QUERY_SUBMITTED:
+ switch (query_ctx->type) {
+ case SQL_QUERY_SELECT:
+ ret = OCIStmtExecute(c->ctx, c->query, c->error, 0, 0, NULL, NULL, OCI_DEFAULT);
+ break;
- RETURN_MODULE_FAIL;
- }
+ default:
+ ret = OCIStmtExecute(c->ctx, c->query, c->error, 1, 0, NULL, NULL, OCI_COMMIT_ON_SUCCESS);
+ break;
+ }
+ c->poll_count++;
+ /* Back off the poll interval, up to half the query timeout */
+ if (c->poll_count > 2) {
+ if (query_ctx->type == SQL_QUERY_SELECT) {
+ if (c->select_interval < fr_time_delta_to_usec(c->config->query_timeout)/2) c->select_interval += 100;
+ } else {
+ if (c->query_interval < fr_time_delta_to_usec(c->config->query_timeout)/2) c->query_interval += 100;
+ }
+ }
- status = OCIStmtExecute(conn->ctx, conn->query, conn->error, 1, 0,
- NULL, NULL, OCI_COMMIT_ON_SUCCESS);
+ switch (ret) {
+ case OCI_STILL_EXECUTING:
+ ROPTIONAL(RDEBUG3, DEBUG3, "Still awaiting response");
+ if (fr_event_timer_in(c, el, &c->read_ev,
+ fr_time_delta_from_usec(query_ctx->type == SQL_QUERY_SELECT ? c->select_interval : c->query_interval),
+ sql_trunk_connection_read_poll, c) < 0) {
+ ERROR("Unable to insert polling event");
+ }
+ return;
+
+ case OCI_SUCCESS:
+ case OCI_NO_DATA:
+ query_ctx->rcode = ret == OCI_NO_DATA ? RLM_SQL_NO_MORE_ROWS : RLM_SQL_OK;
+ /* If we only polled once, reduce the interval*/
+ if (c->poll_count == 1) {
+ if (query_ctx->type == SQL_QUERY_SELECT) {
+ c->select_interval /= 2;
+ } else {
+ c->query_interval /= 2;
+ }
+ }
+ break;
- if (status == OCI_SUCCESS) {
- query_ctx->rcode = RLM_SQL_OK;
- RETURN_MODULE_OK;
- }
- if (status == OCI_ERROR) {
- ERROR("execute query failed in sql_query");
+ default:
+ {
+ char errbuff[512];
+ if (sql_snprint_error(errbuff, sizeof(errbuff), c) == 1) {
+ query_ctx->rcode = RLM_SQL_ALT_QUERY;
+ break;
+ }
+ ROPTIONAL(RERROR, ERROR, "Query failed: %s", errbuff);
+ query_ctx->status = SQL_QUERY_FAILED;
+ trunk_request_signal_fail(treq);
+ if (query_ctx->rcode == RLM_SQL_RECONNECT) connection_signal_reconnect(c->conn, CONNECTION_FAILED);
+ return;
+ }
+ }
+ break;
+
+ case SQL_QUERY_CANCELLED:
+ ret = OCIBreak(c->ctx, c->error);
+ if (ret == OCI_STILL_EXECUTING) {
+ ROPTIONAL(RDEBUG3, DEBUG3, "Still awaiting response");
+ if (fr_event_timer_in(c, el, &c->read_ev, fr_time_delta_from_usec(query_ctx->type == SQL_QUERY_SELECT ? c->select_interval : c->query_interval),
+ sql_trunk_connection_read_poll, c) < 0) {
+ ERROR("Unable to insert polling event");
+ }
+ return;
+ }
+ OCIReset(c->ctx, c->error);
+ trunk_request_signal_cancel_complete(treq);
+ return;
- query_ctx->rcode = sql_check_reconnect(query_ctx->handle, &query_ctx->inst->config);
+ default:
+ return;
}
- RETURN_MODULE_FAIL;
+ if (request) unlang_interpret_mark_runnable(request);
}
-static unlang_action_t sql_select_query(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
+static void sql_trunk_connection_write_poll(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
{
- fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
- int status;
- char **row = NULL;
-
- int i;
- OCIParam *param;
- OCIDefine *define;
-
- ub2 dtype;
- ub2 dsize;
+ trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
- sb2 *ind;
+ trunk_connection_signal_writable(tconn);
+}
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+/*
+ * Oracle doesn't support event driven async, so in this case
+ * we have to resort to polling.
+ *
+ * This "notify" callback sets up the appropriate polling events.
+ */
+CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function */
+static void sql_trunk_connection_notify(UNUSED trunk_connection_t *tconn, connection_t *conn, UNUSED fr_event_list_t *el,
+ trunk_connection_event_t notify_on, UNUSED void *uctx)
+{
+ rlm_sql_oracle_conn_t *c = talloc_get_type_abort(conn->h, rlm_sql_oracle_conn_t);
+ fr_sql_query_t *query_ctx = c->query_ctx;
+ uint poll_interval = (query_ctx && query_ctx->type != SQL_QUERY_SELECT) ? c->query_interval : c->select_interval;
+ switch (notify_on) {
+ case TRUNK_CONN_EVENT_NONE:
+ if (c->read_ev) fr_event_timer_delete(&c->read_ev);
+ if (c->write_ev) fr_event_timer_delete(&c->write_ev);
+ return;
+
+ case TRUNK_CONN_EVENT_BOTH:
+ case TRUNK_CONN_EVENT_READ:
+ if (c->query_ctx) {
+ if (fr_event_timer_in(c, el, &c->read_ev, fr_time_delta_from_usec(poll_interval),
+ sql_trunk_connection_read_poll, c) < 0) {
+ ERROR("Unable to insert polling event");
+ }
+ }
+ if (notify_on == TRUNK_CONN_EVENT_READ) return;
- if (OCIStmtPrepare2(conn->ctx, &conn->query, conn->error, (const OraText *)query_ctx->query_str, strlen(query_ctx->query_str),
- NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT)) {
- ERROR("prepare failed in sql_select_query");
+ FALL_THROUGH;
- goto error;
+ case TRUNK_CONN_EVENT_WRITE:
+ if (fr_event_timer_in(c, el, &c->write_ev, fr_time_delta_from_usec(0),
+ sql_trunk_connection_write_poll, tconn) < 0) {
+ ERROR("Unable to insert polling event");
+ }
+ return;
}
+}
- /*
- * Retrieve a single row
- */
- status = OCIStmtExecute(conn->ctx, conn->query, conn->error, 0, 0, NULL, NULL, OCI_DEFAULT);
- if (status == OCI_NO_DATA) goto finish;
- if (status != OCI_SUCCESS) {
- ERROR("query failed in sql_select_query");
+SQL_QUERY_FAIL
+SQL_QUERY_RESUME
- query_ctx->rcode = sql_check_reconnect(query_ctx->handle, &query_ctx->inst->config);
- RETURN_MODULE_FAIL;
- }
+static unlang_action_t sql_select_query_resume(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
+{
+ fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
+ rlm_sql_row_t row = NULL;
+ sb2 *ind;
+ int i;
+ OCIParam *param;
+ OCIDefine *define;
+ ub2 dtype, dsize;
+
+ if (query_ctx->rcode != RLM_SQL_OK) RETURN_MODULE_FAIL;
/*
* We only need to do this once per result set, because
MEM(ind = talloc_zero_array(row, sb2, conn->col_count + 1));
for (i = 0; i < conn->col_count; i++) {
- status = OCIParamGet(conn->query, OCI_HTYPE_STMT, conn->error, (dvoid **)¶m, i + 1);
- if (status != OCI_SUCCESS) {
+ if (OCIParamGet(conn->query, OCI_HTYPE_STMT, conn->error, (dvoid **)¶m, i + 1) != OCI_SUCCESS) {
ERROR("OCIParamGet() failed in sql_select_query");
-
goto error;
}
- status = OCIAttrGet((dvoid*)param, OCI_DTYPE_PARAM, (dvoid*)&dtype, NULL, OCI_ATTR_DATA_TYPE,
- conn->error);
- if (status != OCI_SUCCESS) {
+ if (OCIAttrGet((dvoid*)param, OCI_DTYPE_PARAM, (dvoid*)&dtype, NULL, OCI_ATTR_DATA_TYPE,
+ conn->error) != OCI_SUCCESS) {
ERROR("OCIAttrGet() failed in sql_select_query");
-
goto error;
}
case SQLT_VCS: /* var char */
case SQLT_CHR: /* char */
case SQLT_STR: /* string */
- status = OCIAttrGet((dvoid *)param, OCI_DTYPE_PARAM, (dvoid *)&dsize, NULL,
- OCI_ATTR_DATA_SIZE, conn->error);
- if (status != OCI_SUCCESS) {
+ if (OCIAttrGet((dvoid *)param, OCI_DTYPE_PARAM, (dvoid *)&dsize, NULL,
+ OCI_ATTR_DATA_SIZE, conn->error) != OCI_SUCCESS) {
ERROR("OCIAttrGet() failed in sql_select_query");
-
goto error;
}
- MEM(row[i] = talloc_zero_array(row, char, dsize + 1));
-
- break;
+ FALL_THROUGH;
case SQLT_DAT:
case SQLT_INT:
case SQLT_UIN:
/*
* Grab the actual row value and write it to the buffer we allocated.
*/
- status = OCIDefineByPos(conn->query, &define, conn->error, i + 1, (ub1 *)row[i], dsize + 1, SQLT_STR,
- (dvoid *)&ind[i], NULL, NULL, OCI_DEFAULT);
-
- if (status != OCI_SUCCESS) {
+ if (OCIDefineByPos(conn->query, &define, conn->error, i + 1, (ub1 *)row[i], dsize + 1, SQLT_STR,
+ (dvoid *)&ind[i], NULL, NULL, OCI_DEFAULT) != OCI_SUCCESS) {
ERROR("OCIDefineByPos() failed in sql_select_query");
goto error;
}
conn->row = row;
conn->ind = ind;
-finish:
query_ctx->rcode = RLM_SQL_OK;
RETURN_MODULE_OK;
RETURN_MODULE_FAIL;
}
+static sql_rcode_t sql_fields(char const **out[], fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
+{
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
+ int fields, i, status;
+ char const **names;
+ OCIParam *param;
+
+ if (OCIAttrGet((dvoid *)conn->query, OCI_HTYPE_STMT, (dvoid *)&fields, NULL, OCI_ATTR_PARAM_COUNT,
+ conn->error)) return RLM_SQL_ERROR;
+ if (fields == 0) return RLM_SQL_ERROR;
+
+ MEM(names = talloc_array(query_ctx, char const *, fields));
+
+ for (i = 0; i < fields; i++) {
+ OraText *pcol_name = NULL;
+ ub4 pcol_size = 0;
+
+ status = OCIParamGet(conn->query, OCI_HTYPE_STMT, conn->error, (dvoid **)¶m, i + 1);
+ if (status != OCI_SUCCESS) {
+ ERROR("OCIParamGet(OCI_HTYPE_STMT) failed in sql_fields()");
+ error:
+ talloc_free(names);
+
+ return RLM_SQL_ERROR;
+ }
+
+ status = OCIAttrGet((dvoid **)param, OCI_DTYPE_PARAM, &pcol_name, &pcol_size,
+ OCI_ATTR_NAME, conn->error);
+ if (status != OCI_SUCCESS) {
+ ERROR("OCIParamGet(OCI_ATTR_NAME) failed in sql_fields()");
+
+ goto error;
+ }
+
+ names[i] = (char const *)pcol_name;
+ }
+
+ *out = names;
+
+ return RLM_SQL_OK;
+}
+
static int sql_num_rows(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
{
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
ub4 rows = 0;
ub4 size = sizeof(ub4);
static unlang_action_t sql_fetch_row(rlm_rcode_t *p_result, UNUSED int *priority, UNUSED request_t *request, void *uctx)
{
fr_sql_query_t *query_ctx = talloc_get_type_abort(uctx, fr_sql_query_t);
- int status;
- rlm_sql_handle_t *handle = query_ctx->handle;
- rlm_sql_oracle_conn_t *conn = handle->conn;
+ int status = OCI_STILL_EXECUTING;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
if (!conn->ctx) {
ERROR("Socket not connected");
query_ctx->row = NULL;
- status = OCIStmtFetch(conn->query, conn->error, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
+ while (status == OCI_STILL_EXECUTING) {
+ status = OCIStmtFetch(conn->query, conn->error, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
+ }
if (status == OCI_SUCCESS) {
query_ctx->row = conn->row;
if (status == OCI_ERROR) {
ERROR("fetch failed in sql_fetch_row");
- query_ctx->rcode = sql_check_reconnect(handle, &query_ctx->inst->config);
+ query_ctx->rcode = sql_check_reconnect(conn);
RETURN_MODULE_FAIL;
}
static sql_rcode_t sql_free_result(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
{
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
+ int status = OCI_STILL_EXECUTING;
/* Cancel the cursor first */
- (void) OCIStmtFetch(conn->query, conn->error, 0, OCI_FETCH_NEXT, OCI_DEFAULT);
+ while (status == OCI_STILL_EXECUTING) {
+ status = OCIStmtFetch(conn->query, conn->error, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
+ }
TALLOC_FREE(conn->row);
conn->ind = NULL; /* ind is a child of row */
conn->col_count = 0;
+ conn->query_ctx = NULL;
- if (OCIStmtRelease (conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) {
+ if (OCIStmtRelease(conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) {
ERROR("OCI release failed in sql_finish_query");
return RLM_SQL_ERROR;
}
static sql_rcode_t sql_finish_query(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
{
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
+
+ conn->query_ctx = NULL;
if (OCIStmtRelease(conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) {
ERROR("OCI release failed in sql_finish_query");
static sql_rcode_t sql_finish_select_query(fr_sql_query_t *query_ctx, UNUSED rlm_sql_config_t const *config)
{
- rlm_sql_oracle_conn_t *conn = query_ctx->handle->conn;
+ rlm_sql_oracle_conn_t *conn = talloc_get_type_abort(query_ctx->tconn->conn->h, rlm_sql_oracle_conn_t);
TALLOC_FREE(conn->row);
conn->ind = NULL; /* ind is a child of row */
conn->col_count = 0;
+ conn->query_ctx = NULL;
if (OCIStmtRelease (conn->query, conn->error, NULL, 0, OCI_DEFAULT) != OCI_SUCCESS ) {
ERROR("OCI release failed in sql_finish_query");
.instantiate = mod_instantiate,
.detach = mod_detach
},
- .sql_socket_init = sql_socket_init,
- .sql_query = sql_query,
- .sql_select_query = sql_select_query,
+ .flags = RLM_SQL_RCODE_FLAGS_ALT_QUERY,
+ .sql_query_resume = sql_query_resume,
+ .sql_select_query_resume = sql_select_query_resume,
.sql_num_rows = sql_num_rows,
.sql_affected_rows = sql_num_rows,
.sql_fetch_row = sql_fetch_row,
.sql_free_result = sql_free_result,
.sql_error = sql_error,
.sql_finish_query = sql_finish_query,
- .sql_finish_select_query = sql_finish_select_query
+ .sql_finish_select_query = sql_finish_select_query,
+ .uses_trunks = true,
+ .trunk_io_funcs = {
+ .connection_alloc = sql_trunk_connection_alloc,
+ .connection_notify = sql_trunk_connection_notify,
+ .request_mux = sql_trunk_request_mux,
+ .request_cancel_mux = sql_request_cancel_mux,
+ .request_cancel = sql_request_cancel,
+ .request_fail = sql_request_fail,
+ }
};