3297 lines
84 KiB
C
3297 lines
84 KiB
C
/*------
|
|
* Module: statement.c
|
|
*
|
|
* Description: This module contains functions related to creating
|
|
* and manipulating a statement.
|
|
*
|
|
* Classes: StatementClass (Functions prefix: "SC_")
|
|
*
|
|
* API functions: SQLAllocStmt, SQLFreeStmt
|
|
*
|
|
* Comments: See "readme.txt" for copyright and license information.
|
|
*-------
|
|
*/
|
|
|
|
#ifdef WIN_MULTITHREAD_SUPPORT
|
|
#ifndef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0400
|
|
#endif /* _WIN32_WINNT */
|
|
#endif /* WIN_MULTITHREAD_SUPPORT */
|
|
|
|
#include "statement.h"
|
|
#include "misc.h" // strncpy_null
|
|
|
|
#include "bind.h"
|
|
#include "connection.h"
|
|
#include "multibyte.h"
|
|
#include "qresult.h"
|
|
#include "convert.h"
|
|
#include "environ.h"
|
|
#include "loadlib.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "pgapifunc.h"
|
|
|
|
#define IS_BLANK(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n')
|
|
|
|
/* Map sql commands to statement types */
|
|
static const struct
|
|
{
|
|
int type;
|
|
char *s;
|
|
} Statement_Type[] =
|
|
|
|
{
|
|
{
|
|
STMT_TYPE_SELECT, "SELECT"
|
|
}
|
|
,{
|
|
STMT_TYPE_INSERT, "INSERT"
|
|
}
|
|
,{
|
|
STMT_TYPE_UPDATE, "UPDATE"
|
|
}
|
|
,{
|
|
STMT_TYPE_DELETE, "DELETE"
|
|
}
|
|
,{
|
|
STMT_TYPE_PROCCALL, "{"
|
|
}
|
|
,{
|
|
STMT_TYPE_SET, "SET"
|
|
}
|
|
,{
|
|
STMT_TYPE_RESET, "RESET"
|
|
}
|
|
,{
|
|
STMT_TYPE_CREATE, "CREATE"
|
|
}
|
|
,{
|
|
STMT_TYPE_DECLARE, "DECLARE"
|
|
}
|
|
,{
|
|
STMT_TYPE_FETCH, "FETCH"
|
|
}
|
|
,{
|
|
STMT_TYPE_MOVE, "MOVE"
|
|
}
|
|
,{
|
|
STMT_TYPE_CLOSE, "CLOSE"
|
|
}
|
|
,{
|
|
STMT_TYPE_PREPARE, "PREPARE"
|
|
}
|
|
,{
|
|
STMT_TYPE_EXECUTE, "EXECUTE"
|
|
}
|
|
,{
|
|
STMT_TYPE_DEALLOCATE, "DEALLOCATE"
|
|
}
|
|
,{
|
|
STMT_TYPE_DROP, "DROP"
|
|
}
|
|
,{
|
|
STMT_TYPE_START, "BEGIN"
|
|
}
|
|
,{
|
|
STMT_TYPE_START, "START"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "SAVEPOINT"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "RELEASE"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "COMMIT"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "END"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "ROLLBACK"
|
|
}
|
|
,{
|
|
STMT_TYPE_TRANSACTION, "ABORT"
|
|
}
|
|
,{
|
|
STMT_TYPE_LOCK, "LOCK"
|
|
}
|
|
,{
|
|
STMT_TYPE_ALTER, "ALTER"
|
|
}
|
|
,{
|
|
STMT_TYPE_GRANT, "GRANT"
|
|
}
|
|
,{
|
|
STMT_TYPE_REVOKE, "REVOKE"
|
|
}
|
|
,{
|
|
STMT_TYPE_COPY, "COPY"
|
|
}
|
|
,{
|
|
STMT_TYPE_ANALYZE, "ANALYZE"
|
|
}
|
|
,{
|
|
STMT_TYPE_NOTIFY, "NOTIFY"
|
|
}
|
|
,{
|
|
STMT_TYPE_EXPLAIN, "EXPLAIN"
|
|
}
|
|
|
|
/*
|
|
* Special-commands that cannot be run in a transaction block. This isn't
|
|
* as granular as it could be. VACUUM can never be run in a transaction
|
|
* block, but some variants of REINDEX and CLUSTER can be. CHECKPOINT
|
|
* doesn't throw an error if you do, but it cannot be rolled back so
|
|
* there's no point in beginning a new transaction for it.
|
|
*/
|
|
,{
|
|
STMT_TYPE_SPECIAL, "VACUUM"
|
|
}
|
|
,{
|
|
STMT_TYPE_SPECIAL, "REINDEX"
|
|
}
|
|
,{
|
|
STMT_TYPE_SPECIAL, "CLUSTER"
|
|
}
|
|
,{
|
|
STMT_TYPE_SPECIAL, "CHECKPOINT"
|
|
}
|
|
|
|
,{
|
|
STMT_TYPE_WITH, "WITH"
|
|
}
|
|
,{
|
|
0, NULL
|
|
}
|
|
};
|
|
|
|
static QResultClass *libpq_bind_and_exec(StatementClass *stmt);
|
|
static void SC_set_errorinfo(StatementClass *self, QResultClass *res, int errkind);
|
|
static void SC_set_error_if_not_set(StatementClass *self, int errornumber, const char *errmsg, const char *func);
|
|
|
|
|
|
RETCODE SQL_API
|
|
PGAPI_AllocStmt(HDBC hdbc,
|
|
HSTMT * phstmt, UDWORD flag)
|
|
{
|
|
CSTR func = "PGAPI_AllocStmt";
|
|
ConnectionClass *conn = (ConnectionClass *) hdbc;
|
|
StatementClass *stmt;
|
|
ARDFields *ardopts;
|
|
|
|
MYLOG(0, "entering...\n");
|
|
|
|
if (!conn)
|
|
{
|
|
CC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
stmt = SC_Constructor(conn);
|
|
|
|
MYLOG(0, "**** : hdbc = %p, stmt = %p\n", hdbc, stmt);
|
|
|
|
if (!stmt)
|
|
{
|
|
CC_set_error(conn, CONN_STMT_ALLOC_ERROR, "No more memory to allocate a further SQL-statement", func);
|
|
*phstmt = SQL_NULL_HSTMT;
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (!CC_add_statement(conn, stmt))
|
|
{
|
|
CC_set_error(conn, CONN_STMT_ALLOC_ERROR, "Maximum number of statements exceeded.", func);
|
|
SC_Destructor(stmt);
|
|
*phstmt = SQL_NULL_HSTMT;
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
*phstmt = (HSTMT) stmt;
|
|
|
|
stmt->iflag = flag;
|
|
/* Copy default statement options based from Connection options */
|
|
if (0 != (PODBC_INHERIT_CONNECT_OPTIONS & flag))
|
|
{
|
|
stmt->options = stmt->options_orig = conn->stmtOptions;
|
|
stmt->ardi.ardf = conn->ardOptions;
|
|
}
|
|
else
|
|
{
|
|
InitializeStatementOptions(&stmt->options_orig);
|
|
stmt->options = stmt->options_orig;
|
|
InitializeARDFields(&stmt->ardi.ardf);
|
|
}
|
|
ardopts = SC_get_ARDF(stmt);
|
|
ARD_AllocBookmark(ardopts);
|
|
|
|
/* Save the handle for later */
|
|
stmt->phstmt = phstmt;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
|
|
RETCODE SQL_API
|
|
PGAPI_FreeStmt(HSTMT hstmt,
|
|
SQLUSMALLINT fOption)
|
|
{
|
|
CSTR func = "PGAPI_FreeStmt";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
|
|
MYLOG(0, "entering...hstmt=%p, fOption=%hi\n", hstmt, fOption);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
SC_clear_error(stmt);
|
|
|
|
if (fOption == SQL_DROP)
|
|
{
|
|
ConnectionClass *conn = stmt->hdbc;
|
|
|
|
/* Remove the statement from the connection's statement list */
|
|
if (conn)
|
|
{
|
|
QResultClass *res;
|
|
|
|
if (STMT_EXECUTING == stmt->status)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Statement is currently executing a transaction.", func);
|
|
return SQL_ERROR; /* stmt may be executing a transaction */
|
|
}
|
|
if (conn->unnamed_prepared_stmt == stmt)
|
|
conn->unnamed_prepared_stmt = NULL;
|
|
|
|
/*
|
|
* Free any cursors and discard any result info.
|
|
* Don't detach the statement from the connection
|
|
* before freeing the associated cursors. Otherwise
|
|
* CC_cursor_count() would get wrong results.
|
|
*/
|
|
res = SC_get_Result(stmt);
|
|
QR_Destructor(res);
|
|
SC_init_Result(stmt);
|
|
if (!CC_remove_statement(conn, stmt))
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Statement is currently executing a transaction.", func);
|
|
return SQL_ERROR; /* stmt may be executing a
|
|
* transaction */
|
|
}
|
|
}
|
|
|
|
if (stmt->execute_delegate)
|
|
{
|
|
PGAPI_FreeStmt(stmt->execute_delegate, SQL_DROP);
|
|
stmt->execute_delegate = NULL;
|
|
}
|
|
if (stmt->execute_parent)
|
|
stmt->execute_parent->execute_delegate = NULL;
|
|
/* Destroy the statement and free any results, cursors, etc. */
|
|
SC_Destructor(stmt);
|
|
}
|
|
else if (fOption == SQL_UNBIND)
|
|
SC_unbind_cols(stmt);
|
|
else if (fOption == SQL_CLOSE)
|
|
{
|
|
/*
|
|
* this should discard all the results, but leave the statement
|
|
* itself in place (it can be executed again)
|
|
*/
|
|
stmt->transition_status = STMT_TRANSITION_ALLOCATED;
|
|
if (stmt->execute_delegate)
|
|
{
|
|
PGAPI_FreeStmt(stmt->execute_delegate, SQL_DROP);
|
|
stmt->execute_delegate = NULL;
|
|
}
|
|
if (!SC_recycle_statement(stmt))
|
|
{
|
|
return SQL_ERROR;
|
|
}
|
|
SC_set_Curres(stmt, NULL);
|
|
}
|
|
else if (fOption == SQL_RESET_PARAMS)
|
|
SC_free_params(stmt, STMT_FREE_PARAMS_ALL);
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_OPTION_OUT_OF_RANGE_ERROR, "Invalid option passed to PGAPI_FreeStmt.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* StatementClass implementation
|
|
*/
|
|
void
|
|
InitializeStatementOptions(StatementOptions *opt)
|
|
{
|
|
memset(opt, 0, sizeof(StatementOptions));
|
|
opt->scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
opt->cursor_type = SQL_CURSOR_FORWARD_ONLY;
|
|
opt->retrieve_data = SQL_RD_ON;
|
|
opt->use_bookmarks = SQL_UB_OFF;
|
|
opt->metadata_id = SQL_FALSE;
|
|
}
|
|
|
|
static void SC_clear_parse_status(StatementClass *self, ConnectionClass *conn)
|
|
{
|
|
self->parse_status = STMT_PARSE_NONE;
|
|
}
|
|
|
|
static void SC_init_discard_output_params(StatementClass *self)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(self);
|
|
|
|
if (!conn) return;
|
|
self->discard_output_params = 0;
|
|
if (!conn->connInfo.use_server_side_prepare)
|
|
self->discard_output_params = 1;
|
|
}
|
|
|
|
static void SC_init_parse_method(StatementClass *self)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(self);
|
|
|
|
self->parse_method = 0;
|
|
if (!conn) return;
|
|
if (0 == (PODBC_EXTERNAL_STATEMENT & self->iflag)) return;
|
|
if (self->catalog_result) return;
|
|
if (conn->connInfo.drivers.parse)
|
|
SC_set_parse_forced(self);
|
|
}
|
|
|
|
StatementClass *
|
|
SC_Constructor(ConnectionClass *conn)
|
|
{
|
|
StatementClass *rv;
|
|
|
|
rv = (StatementClass *) malloc(sizeof(StatementClass));
|
|
if (rv)
|
|
{
|
|
rv->hdbc = conn;
|
|
rv->phstmt = NULL;
|
|
rv->result = NULL;
|
|
rv->curres = NULL;
|
|
rv->catalog_result = FALSE;
|
|
rv->prepare = NON_PREPARE_STATEMENT;
|
|
rv->prepared = NOT_YET_PREPARED;
|
|
rv->status = STMT_ALLOCATED;
|
|
rv->external = FALSE;
|
|
rv->iflag = 0;
|
|
rv->plan_name = NULL;
|
|
rv->transition_status = STMT_TRANSITION_UNALLOCATED;
|
|
rv->multi_statement = -1; /* unknown */
|
|
rv->num_params = -1; /* unknown */
|
|
rv->processed_statements = NULL;
|
|
|
|
rv->__error_message = NULL;
|
|
rv->__error_number = 0;
|
|
rv->pgerror = NULL;
|
|
|
|
rv->statement = NULL;
|
|
rv->stmt_with_params = NULL;
|
|
rv->load_statement = NULL;
|
|
rv->statement_type = STMT_TYPE_UNKNOWN;
|
|
|
|
rv->currTuple = -1;
|
|
rv->rowset_start = 0;
|
|
SC_set_rowset_start(rv, -1, FALSE);
|
|
rv->current_col = -1;
|
|
rv->bind_row = 0;
|
|
rv->from_pos = rv->load_from_pos = rv->where_pos = -1;
|
|
rv->last_fetch_count = rv->last_fetch_count_include_ommitted = 0;
|
|
rv->save_rowset_size = -1;
|
|
|
|
rv->data_at_exec = -1;
|
|
rv->current_exec_param = -1;
|
|
rv->exec_start_row = -1;
|
|
rv->exec_end_row = -1;
|
|
rv->exec_current_row = -1;
|
|
rv->put_data = FALSE;
|
|
rv->ref_CC_error = FALSE;
|
|
rv->join_info = 0;
|
|
rv->curr_param_result = 0;
|
|
SC_init_parse_method(rv);
|
|
|
|
rv->lobj_fd = -1;
|
|
INIT_NAME(rv->cursor_name);
|
|
|
|
/* Parse Stuff */
|
|
rv->ti = NULL;
|
|
rv->ntab = 0;
|
|
rv->num_key_fields = -1; /* unknown */
|
|
SC_clear_parse_status(rv, conn);
|
|
rv->proc_return = -1;
|
|
SC_init_discard_output_params(rv);
|
|
rv->cancel_info = 0;
|
|
|
|
/* Clear Statement Options -- defaults will be set in AllocStmt */
|
|
memset(&rv->options, 0, sizeof(StatementOptions));
|
|
InitializeEmbeddedDescriptor((DescriptorClass *)&(rv->ardi),
|
|
rv, SQL_ATTR_APP_ROW_DESC);
|
|
InitializeEmbeddedDescriptor((DescriptorClass *)&(rv->apdi),
|
|
rv, SQL_ATTR_APP_PARAM_DESC);
|
|
InitializeEmbeddedDescriptor((DescriptorClass *)&(rv->irdi),
|
|
rv, SQL_ATTR_IMP_ROW_DESC);
|
|
InitializeEmbeddedDescriptor((DescriptorClass *)&(rv->ipdi),
|
|
rv, SQL_ATTR_IMP_PARAM_DESC);
|
|
|
|
rv->miscinfo = 0;
|
|
rv->execinfo = 0;
|
|
rv->rb_or_tc = 0;
|
|
SC_reset_updatable(rv);
|
|
rv->diag_row_count = 0;
|
|
rv->stmt_time = 0;
|
|
rv->execute_delegate = NULL;
|
|
rv->execute_parent = NULL;
|
|
rv->allocated_callbacks = 0;
|
|
rv->num_callbacks = 0;
|
|
rv->callbacks = NULL;
|
|
GetDataInfoInitialize(SC_get_GDTI(rv));
|
|
PutDataInfoInitialize(SC_get_PDTI(rv));
|
|
rv->lock_CC_for_rb = FALSE;
|
|
INIT_STMT_CS(rv);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
char
|
|
SC_Destructor(StatementClass *self)
|
|
{
|
|
CSTR func = "SC_Destructor";
|
|
QResultClass *res = SC_get_Result(self);
|
|
|
|
MYLOG(0, "entering self=%p, self->result=%p, self->hdbc=%p\n", self, res, self->hdbc);
|
|
SC_clear_error(self);
|
|
if (STMT_EXECUTING == self->status)
|
|
{
|
|
SC_set_error(self, STMT_SEQUENCE_ERROR, "Statement is currently executing a transaction.", func);
|
|
return FALSE;
|
|
}
|
|
|
|
if (res)
|
|
{
|
|
if (!self->hdbc)
|
|
res->conn = NULL; /* prevent any dbase activity */
|
|
|
|
QR_Destructor(res);
|
|
}
|
|
|
|
SC_initialize_stmts(self, TRUE);
|
|
|
|
/* Free the parsed table information */
|
|
SC_initialize_cols_info(self, FALSE, TRUE);
|
|
|
|
NULL_THE_NAME(self->cursor_name);
|
|
/* Free the parsed field information */
|
|
DC_Destructor((DescriptorClass *) SC_get_ARDi(self));
|
|
DC_Destructor((DescriptorClass *) SC_get_APDi(self));
|
|
DC_Destructor((DescriptorClass *) SC_get_IRDi(self));
|
|
DC_Destructor((DescriptorClass *) SC_get_IPDi(self));
|
|
GDATA_unbind_cols(SC_get_GDTI(self), TRUE);
|
|
PDATA_free_params(SC_get_PDTI(self), STMT_FREE_PARAMS_ALL);
|
|
|
|
if (self->__error_message)
|
|
free(self->__error_message);
|
|
if (self->pgerror)
|
|
ER_Destructor(self->pgerror);
|
|
cancelNeedDataState(self);
|
|
if (self->callbacks)
|
|
free(self->callbacks);
|
|
|
|
DELETE_STMT_CS(self);
|
|
free(self);
|
|
|
|
MYLOG(0, "leaving\n");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
SC_init_Result(StatementClass *self)
|
|
{
|
|
self->result = self->curres = NULL;
|
|
self->curr_param_result = 0;
|
|
MYLOG(0, "leaving(%p)\n", self);
|
|
}
|
|
|
|
void
|
|
SC_set_Result(StatementClass *self, QResultClass *res)
|
|
{
|
|
if (res != self->result)
|
|
{
|
|
MYLOG(0, "(%p, %p)\n", self, res);
|
|
QR_Destructor(self->result);
|
|
self->result = self->curres = res;
|
|
if (NULL != res)
|
|
self->curr_param_result = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Free parameters and free the memory from the
|
|
* data-at-execution parameters that was allocated in SQLPutData.
|
|
*/
|
|
void
|
|
SC_free_params(StatementClass *self, char option)
|
|
{
|
|
if (option != STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY)
|
|
{
|
|
APD_free_params(SC_get_APDF(self), option);
|
|
IPD_free_params(SC_get_IPDF(self), option);
|
|
}
|
|
PDATA_free_params(SC_get_PDTI(self), option);
|
|
self->data_at_exec = -1;
|
|
self->current_exec_param = -1;
|
|
self->put_data = FALSE;
|
|
if (option == STMT_FREE_PARAMS_ALL)
|
|
{
|
|
self->exec_start_row = -1;
|
|
self->exec_end_row = -1;
|
|
self->exec_current_row = -1;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
statement_type(const char *statement)
|
|
{
|
|
int i;
|
|
|
|
/* ignore leading whitespace in query string */
|
|
while (*statement && (isspace((UCHAR) *statement) || *statement == '('))
|
|
statement++;
|
|
|
|
for (i = 0; Statement_Type[i].s; i++)
|
|
if (!strnicmp(statement, Statement_Type[i].s, strlen(Statement_Type[i].s)))
|
|
return Statement_Type[i].type;
|
|
|
|
return STMT_TYPE_OTHER;
|
|
}
|
|
|
|
void
|
|
SC_set_planname(StatementClass *stmt, const char *plan_name)
|
|
{
|
|
if (stmt->plan_name)
|
|
free(stmt->plan_name);
|
|
if (plan_name && plan_name[0])
|
|
stmt->plan_name = strdup(plan_name);
|
|
else
|
|
stmt->plan_name = NULL;
|
|
}
|
|
|
|
void
|
|
SC_set_rowset_start(StatementClass *stmt, SQLLEN start, BOOL valid_base)
|
|
{
|
|
QResultClass *res = SC_get_Curres(stmt);
|
|
SQLLEN incr = start - stmt->rowset_start;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "%p->SC_set_rowstart " FORMAT_LEN "->" FORMAT_LEN "(%s) ", stmt, stmt->rowset_start, start, valid_base ? "valid" : "unknown");
|
|
if (res != NULL)
|
|
{
|
|
BOOL valid = QR_has_valid_base(res);
|
|
MYPRINTF(DETAIL_LOG_LEVEL, ":(%p)QR is %s", res, QR_has_valid_base(res) ? "valid" : "unknown");
|
|
|
|
if (valid)
|
|
{
|
|
if (valid_base)
|
|
QR_inc_rowstart_in_cache(res, incr);
|
|
else
|
|
QR_set_no_valid_base(res);
|
|
}
|
|
else if (valid_base)
|
|
{
|
|
QR_set_has_valid_base(res);
|
|
if (start < 0)
|
|
QR_set_rowstart_in_cache(res, -1);
|
|
else
|
|
QR_set_rowstart_in_cache(res, start);
|
|
}
|
|
if (!QR_get_cursor(res))
|
|
res->key_base = start;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, ":(%p)QR result=" FORMAT_LEN "(%s)", res, QR_get_rowstart_in_cache(res), QR_has_valid_base(res) ? "valid" : "unknown");
|
|
}
|
|
stmt->rowset_start = start;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, ":stmt result=" FORMAT_LEN "\n", stmt->rowset_start);
|
|
}
|
|
void
|
|
SC_inc_rowset_start(StatementClass *stmt, SQLLEN inc)
|
|
{
|
|
SQLLEN start = stmt->rowset_start + inc;
|
|
|
|
SC_set_rowset_start(stmt, start, TRUE);
|
|
}
|
|
int
|
|
SC_set_current_col(StatementClass *stmt, int col)
|
|
{
|
|
if (col == stmt->current_col)
|
|
return col;
|
|
if (col >= 0)
|
|
reset_a_getdata_info(SC_get_GDTI(stmt), col + 1);
|
|
stmt->current_col = col;
|
|
|
|
return stmt->current_col;
|
|
}
|
|
|
|
void
|
|
SC_set_prepared(StatementClass *stmt, int prepared)
|
|
{
|
|
if (prepared == stmt->prepared)
|
|
;
|
|
else if (NOT_YET_PREPARED == prepared && PREPARED_PERMANENTLY == stmt->prepared)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
|
|
if (conn)
|
|
{
|
|
ENTER_CONN_CS(conn);
|
|
if (CONN_CONNECTED == conn->status)
|
|
{
|
|
if (CC_is_in_error_trans(conn))
|
|
{
|
|
CC_mark_a_object_to_discard(conn, 's', stmt->plan_name);
|
|
}
|
|
else
|
|
{
|
|
QResultClass *res;
|
|
char dealloc_stmt[128];
|
|
|
|
SPRINTF_FIXED(dealloc_stmt, "DEALLOCATE \"%s\"", stmt->plan_name);
|
|
res = CC_send_query(conn, dealloc_stmt, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
|
|
QR_Destructor(res);
|
|
}
|
|
}
|
|
LEAVE_CONN_CS(conn);
|
|
}
|
|
}
|
|
if (NOT_YET_PREPARED == prepared)
|
|
SC_set_planname(stmt, NULL);
|
|
stmt->prepared = prepared;
|
|
}
|
|
|
|
/*
|
|
* Initialize stmt_with_params and load_statement member pointer
|
|
* deallocating corresponding prepared plan. Also initialize
|
|
* statement member pointer if specified.
|
|
*/
|
|
RETCODE
|
|
SC_initialize_stmts(StatementClass *self, BOOL initializeOriginal)
|
|
{
|
|
ProcessedStmt *pstmt;
|
|
ProcessedStmt *next_pstmt;
|
|
|
|
if (self->lock_CC_for_rb)
|
|
{
|
|
LEAVE_CONN_CS(SC_get_conn(self));
|
|
self->lock_CC_for_rb = FALSE;
|
|
}
|
|
if (initializeOriginal)
|
|
{
|
|
if (self->statement)
|
|
{
|
|
free(self->statement);
|
|
self->statement = NULL;
|
|
}
|
|
|
|
pstmt = self->processed_statements;
|
|
while (pstmt)
|
|
{
|
|
if (pstmt->query)
|
|
free(pstmt->query);
|
|
next_pstmt = pstmt->next;
|
|
free(pstmt);
|
|
pstmt = next_pstmt;
|
|
}
|
|
self->processed_statements = NULL;
|
|
|
|
self->prepare = NON_PREPARE_STATEMENT;
|
|
SC_set_prepared(self, NOT_YET_PREPARED);
|
|
self->statement_type = STMT_TYPE_UNKNOWN; /* unknown */
|
|
self->multi_statement = -1; /* unknown */
|
|
self->num_params = -1; /* unknown */
|
|
self->proc_return = -1; /* unknown */
|
|
self->join_info = 0;
|
|
SC_init_parse_method(self);
|
|
SC_init_discard_output_params(self);
|
|
}
|
|
if (self->stmt_with_params)
|
|
{
|
|
free(self->stmt_with_params);
|
|
self->stmt_with_params = NULL;
|
|
}
|
|
if (self->load_statement)
|
|
{
|
|
free(self->load_statement);
|
|
self->load_statement = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL SC_opencheck(StatementClass *self, const char *func)
|
|
{
|
|
QResultClass *res;
|
|
|
|
if (!self)
|
|
return FALSE;
|
|
if (self->status == STMT_EXECUTING)
|
|
{
|
|
SC_set_error(self, STMT_SEQUENCE_ERROR, "Statement is currently executing a transaction.", func);
|
|
return TRUE;
|
|
}
|
|
/*
|
|
* We can dispose the result of Describe-only any time.
|
|
*/
|
|
if (self->prepare && self->status == STMT_DESCRIBED)
|
|
{
|
|
MYLOG(0, "self->prepare && self->status == STMT_DESCRIBED\n");
|
|
return FALSE;
|
|
}
|
|
if (res = SC_get_Curres(self), NULL != res)
|
|
{
|
|
if (QR_command_maybe_successful(res) && res->backend_tuples)
|
|
{
|
|
SC_set_error(self, STMT_SEQUENCE_ERROR, "The cursor is open.", func);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
RETCODE
|
|
SC_initialize_and_recycle(StatementClass *self)
|
|
{
|
|
SC_initialize_stmts(self, TRUE);
|
|
if (!SC_recycle_statement(self))
|
|
return SQL_ERROR;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
void
|
|
SC_reset_result_for_rerun(StatementClass *self)
|
|
{
|
|
QResultClass *res;
|
|
ColumnInfoClass *flds;
|
|
|
|
if (!self) return;
|
|
if (res = SC_get_Result(self), NULL == res)
|
|
return;
|
|
flds = QR_get_fields(res);
|
|
if (NULL == flds ||
|
|
0 == CI_get_num_fields(flds))
|
|
SC_set_Result(self, NULL);
|
|
else
|
|
{
|
|
QR_reset_for_re_execute(res);
|
|
self->curr_param_result = 1;
|
|
SC_set_Curres(self, NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called from SQLPrepare if STMT_PREMATURE, or
|
|
* from SQLExecute if STMT_FINISHED, or
|
|
* from SQLFreeStmt(SQL_CLOSE)
|
|
*/
|
|
char
|
|
SC_recycle_statement(StatementClass *self)
|
|
{
|
|
CSTR func = "SC_recycle_statement";
|
|
ConnectionClass *conn;
|
|
|
|
MYLOG(0, "entering self=%p\n", self);
|
|
|
|
SC_clear_error(self);
|
|
/* This would not happen */
|
|
if (self->status == STMT_EXECUTING)
|
|
{
|
|
SC_set_error(self, STMT_SEQUENCE_ERROR, "Statement is currently executing a transaction.", func);
|
|
return FALSE;
|
|
}
|
|
|
|
if (SC_get_conn(self)->unnamed_prepared_stmt == self)
|
|
SC_get_conn(self)->unnamed_prepared_stmt = NULL;
|
|
|
|
conn = SC_get_conn(self);
|
|
switch (self->status)
|
|
{
|
|
case STMT_ALLOCATED:
|
|
/* this statement does not need to be recycled */
|
|
return TRUE;
|
|
|
|
case STMT_READY:
|
|
break;
|
|
|
|
case STMT_DESCRIBED:
|
|
break;
|
|
|
|
case STMT_FINISHED:
|
|
break;
|
|
|
|
default:
|
|
SC_set_error(self, STMT_INTERNAL_ERROR, "An internal error occured while recycling statements", func);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (self->prepared)
|
|
{
|
|
case NOT_YET_PREPARED:
|
|
case PREPARED_TEMPORARILY:
|
|
/* Free the parsed table/field information */
|
|
SC_initialize_cols_info(self, TRUE, TRUE);
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "SC_clear_parse_status\n");
|
|
SC_clear_parse_status(self, conn);
|
|
break;
|
|
}
|
|
|
|
/* Free any cursors */
|
|
if (SC_get_Result(self))
|
|
SC_set_Result(self, NULL);
|
|
self->miscinfo = 0;
|
|
self->execinfo = 0;
|
|
/* self->rbonerr = 0; Never clear the bits here */
|
|
|
|
/*
|
|
* Reset only parameters that have anything to do with results
|
|
*/
|
|
self->status = STMT_READY;
|
|
self->catalog_result = FALSE; /* not very important */
|
|
|
|
self->currTuple = -1;
|
|
SC_set_rowset_start(self, -1, FALSE);
|
|
SC_set_current_col(self, -1);
|
|
self->bind_row = 0;
|
|
MYLOG(DETAIL_LOG_LEVEL, "statement=%p ommitted=0\n", self);
|
|
self->last_fetch_count = self->last_fetch_count_include_ommitted = 0;
|
|
|
|
self->__error_message = NULL;
|
|
self->__error_number = 0;
|
|
|
|
self->lobj_fd = -1;
|
|
|
|
SC_free_params(self, STMT_FREE_PARAMS_DATA_AT_EXEC_ONLY);
|
|
SC_initialize_stmts(self, FALSE);
|
|
cancelNeedDataState(self);
|
|
self->cancel_info = 0;
|
|
/*
|
|
* reset the current attr setting to the original one.
|
|
*/
|
|
self->options.scroll_concurrency = self->options_orig.scroll_concurrency;
|
|
self->options.cursor_type = self->options_orig.cursor_type;
|
|
self->options.keyset_size = self->options_orig.keyset_size;
|
|
self->options.maxLength = self->options_orig.maxLength;
|
|
self->options.maxRows = self->options_orig.maxRows;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Scan the query wholly or partially (if the next_cmd param specified).
|
|
* Also count the number of parameters respectviely.
|
|
*/
|
|
void
|
|
SC_scanQueryAndCountParams(const char *query, const ConnectionClass *conn,
|
|
ssize_t *next_cmd, SQLSMALLINT * pcpar,
|
|
po_ind_t *multi_st, po_ind_t *proc_return)
|
|
{
|
|
const char *tstr, *tag = NULL;
|
|
size_t taglen = 0;
|
|
char tchar, bchar, escape_in_literal = '\0';
|
|
char in_literal = FALSE, in_ident_keyword = FALSE,
|
|
in_dquote_identifier = FALSE,
|
|
in_dollar_quote = FALSE, in_escape = FALSE,
|
|
in_line_comment = FALSE, del_found = FALSE;
|
|
int comment_level = 0;
|
|
po_ind_t multi = FALSE;
|
|
SQLSMALLINT num_p;
|
|
encoded_str encstr;
|
|
|
|
MYLOG(0, "entering...\n");
|
|
num_p = 0;
|
|
if (proc_return)
|
|
*proc_return = 0;
|
|
if (next_cmd)
|
|
*next_cmd = -1;
|
|
tstr = query;
|
|
make_encoded_str(&encstr, conn, tstr);
|
|
for (bchar = '\0', tchar = encoded_nextchar(&encstr); tchar; tchar = encoded_nextchar(&encstr))
|
|
{
|
|
if (MBCS_NON_ASCII(encstr)) /* multibyte char */
|
|
{
|
|
if ((UCHAR) tchar >= 0x80)
|
|
bchar = tchar;
|
|
if (in_dquote_identifier ||
|
|
in_literal ||
|
|
in_dollar_quote ||
|
|
in_escape ||
|
|
in_line_comment ||
|
|
comment_level > 0)
|
|
;
|
|
else
|
|
in_ident_keyword = TRUE;
|
|
|
|
continue;
|
|
}
|
|
if (!multi && del_found)
|
|
{
|
|
if (IS_NOT_SPACE(tchar))
|
|
{
|
|
multi = TRUE;
|
|
if (next_cmd)
|
|
break;
|
|
}
|
|
}
|
|
if (in_ident_keyword)
|
|
{
|
|
if (isalnum(tchar) ||
|
|
DOLLAR_QUOTE == tchar ||
|
|
'_' == tchar)
|
|
{
|
|
bchar = tchar;
|
|
continue;
|
|
}
|
|
in_ident_keyword = FALSE;
|
|
}
|
|
|
|
if (in_dollar_quote)
|
|
{
|
|
if (tchar == DOLLAR_QUOTE)
|
|
{
|
|
if (strncmp((const char *) ENCODE_PTR(encstr), tag, taglen) == 0)
|
|
{
|
|
in_dollar_quote = FALSE;
|
|
tag = NULL;
|
|
encoded_position_shift(&encstr, taglen - 1);
|
|
}
|
|
}
|
|
}
|
|
else if (in_literal)
|
|
{
|
|
if (in_escape)
|
|
in_escape = FALSE;
|
|
else if (tchar == escape_in_literal)
|
|
in_escape = TRUE;
|
|
else if (tchar == LITERAL_QUOTE)
|
|
in_literal = FALSE;
|
|
}
|
|
else if (in_dquote_identifier)
|
|
{
|
|
if (tchar == IDENTIFIER_QUOTE)
|
|
in_dquote_identifier = FALSE;
|
|
}
|
|
else if (in_line_comment)
|
|
{
|
|
if (PG_LINEFEED == tchar)
|
|
in_line_comment = FALSE;
|
|
}
|
|
else if (comment_level > 0)
|
|
{
|
|
if ('/' == tchar && '*' == ENCODE_PTR(encstr)[1])
|
|
{
|
|
tchar = encoded_nextchar(&encstr);
|
|
comment_level++;
|
|
}
|
|
else if ('*' == tchar && '/' == ENCODE_PTR(encstr)[1])
|
|
{
|
|
tchar = encoded_nextchar(&encstr);
|
|
comment_level--;
|
|
}
|
|
}
|
|
else if (isalnum(tchar))
|
|
in_ident_keyword = TRUE;
|
|
else
|
|
{
|
|
if (tchar == '?')
|
|
{
|
|
if (0 == num_p && bchar == '{')
|
|
{
|
|
if (proc_return)
|
|
*proc_return = 1;
|
|
}
|
|
num_p++;
|
|
}
|
|
else if (tchar == ';')
|
|
{
|
|
del_found = TRUE;
|
|
if (next_cmd)
|
|
*next_cmd = encstr.pos;
|
|
}
|
|
else if (tchar == DOLLAR_QUOTE)
|
|
{
|
|
const char *ptr = (const char *) ENCODE_PTR(encstr);
|
|
taglen = findTag(ptr, encstr.ccsc);
|
|
if (taglen > 0)
|
|
{
|
|
in_dollar_quote = TRUE;
|
|
tag = ptr;
|
|
encoded_position_shift(&encstr, taglen - 1);
|
|
}
|
|
}
|
|
else if (tchar == LITERAL_QUOTE)
|
|
{
|
|
in_literal = TRUE;
|
|
escape_in_literal = CC_get_escape(conn);
|
|
if (!escape_in_literal)
|
|
{
|
|
if (LITERAL_EXT == ENCODE_PTR(encstr)[-1])
|
|
escape_in_literal = ESCAPE_IN_LITERAL;
|
|
}
|
|
}
|
|
else if (tchar == IDENTIFIER_QUOTE)
|
|
in_dquote_identifier = TRUE;
|
|
else if ('-' == tchar)
|
|
{
|
|
if ('-' == ENCODE_PTR(encstr)[1])
|
|
{
|
|
tchar = encoded_nextchar(&encstr);
|
|
in_line_comment = TRUE;
|
|
}
|
|
}
|
|
else if ('/' == tchar)
|
|
{
|
|
if ('*' == ENCODE_PTR(encstr)[1])
|
|
{
|
|
tchar = encoded_nextchar(&encstr);
|
|
comment_level++;
|
|
}
|
|
}
|
|
if (IS_NOT_SPACE(tchar))
|
|
bchar = tchar;
|
|
}
|
|
}
|
|
if (pcpar)
|
|
*pcpar = num_p;
|
|
if (multi_st)
|
|
*multi_st = multi;
|
|
|
|
MYLOG(0, "leaving...num_p=%d multi=%d\n", num_p, multi);
|
|
}
|
|
|
|
/*
|
|
* Describe the result set a statement will produce (for
|
|
* SQLPrepare/SQLDescribeCol)
|
|
*
|
|
* returns # of fields if successful, -1 on error.
|
|
*/
|
|
Int4
|
|
SC_describe(StatementClass *self)
|
|
{
|
|
Int4 num_fields = -1;
|
|
QResultClass *res;
|
|
MYLOG(0, "entering status = %d\n", self->status);
|
|
|
|
res = SC_get_Curres(self);
|
|
if (NULL != res)
|
|
{
|
|
num_fields = QR_NumResultCols(res);
|
|
if (num_fields > 0 ||
|
|
NULL != QR_get_command(res))
|
|
return num_fields;
|
|
}
|
|
if (self->status == STMT_READY)
|
|
{
|
|
MYLOG(0, " preprocess: status = READY\n");
|
|
|
|
self->miscinfo = 0;
|
|
self->execinfo = 0;
|
|
|
|
decideHowToPrepare(self, FALSE);
|
|
switch (SC_get_prepare_method(self))
|
|
{
|
|
case NAMED_PARSE_REQUEST:
|
|
case PARSE_TO_EXEC_ONCE:
|
|
if (SQL_SUCCESS != prepareParameters(self, FALSE))
|
|
return num_fields;
|
|
break;
|
|
case PARSE_REQ_FOR_INFO:
|
|
if (SQL_SUCCESS != prepareParameters(self, FALSE))
|
|
return num_fields;
|
|
self->status = STMT_DESCRIBED;
|
|
break;
|
|
default:
|
|
if (SQL_SUCCESS != prepareParameters(self, TRUE))
|
|
return num_fields;
|
|
self->status = STMT_DESCRIBED;
|
|
break;
|
|
}
|
|
if (res = SC_get_Curres(self), NULL != res)
|
|
{
|
|
num_fields = QR_NumResultCols(res);
|
|
return num_fields;
|
|
}
|
|
}
|
|
return num_fields;
|
|
}
|
|
|
|
|
|
/* This is only called from SQLFreeStmt(SQL_UNBIND) */
|
|
char
|
|
SC_unbind_cols(StatementClass *self)
|
|
{
|
|
ARDFields *opts = SC_get_ARDF(self);
|
|
GetDataInfo *gdata = SC_get_GDTI(self);
|
|
BindInfoClass *bookmark;
|
|
|
|
ARD_unbind_cols(opts, FALSE);
|
|
GDATA_unbind_cols(gdata, FALSE);
|
|
if (bookmark = opts->bookmark, bookmark != NULL)
|
|
{
|
|
bookmark->buffer = NULL;
|
|
bookmark->used = NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
void
|
|
SC_clear_error(StatementClass *self)
|
|
{
|
|
QResultClass *res;
|
|
|
|
self->__error_number = 0;
|
|
if (self->__error_message)
|
|
{
|
|
free(self->__error_message);
|
|
self->__error_message = NULL;
|
|
}
|
|
if (self->pgerror)
|
|
{
|
|
ER_Destructor(self->pgerror);
|
|
self->pgerror = NULL;
|
|
}
|
|
self->diag_row_count = 0;
|
|
if (res = SC_get_Curres(self), res)
|
|
{
|
|
QR_set_message(res, NULL);
|
|
QR_set_notice(res, NULL);
|
|
res->sqlstate[0] = '\0';
|
|
}
|
|
self->stmt_time = 0;
|
|
memset(&self->localtime, 0, sizeof(self->localtime));
|
|
self->localtime.tm_sec = -1;
|
|
SC_unref_CC_error(self);
|
|
}
|
|
|
|
|
|
/*
|
|
* This function creates an error info which is the concatenation
|
|
* of the result, statement, connection, and socket messages.
|
|
*/
|
|
|
|
/* Map sql commands to statement types */
|
|
static const struct
|
|
{
|
|
int number;
|
|
const char ver3str[6];
|
|
const char ver2str[6];
|
|
} Statement_sqlstate[] =
|
|
|
|
{
|
|
{ STMT_ERROR_IN_ROW, "01S01", "01S01" },
|
|
{ STMT_OPTION_VALUE_CHANGED, "01S02", "01S02" },
|
|
{ STMT_ROW_VERSION_CHANGED, "01001", "01001" }, /* data changed */
|
|
{ STMT_POS_BEFORE_RECORDSET, "01S06", "01S06" },
|
|
{ STMT_TRUNCATED, "01004", "01004" }, /* data truncated */
|
|
{ STMT_INFO_ONLY, "00000", "00000" }, /* just an information that is returned, no error */
|
|
|
|
{ STMT_OK, "00000", "00000" }, /* OK */
|
|
{ STMT_EXEC_ERROR, "HY000", "S1000" }, /* also a general error */
|
|
{ STMT_STATUS_ERROR, "HY010", "S1010" },
|
|
{ STMT_SEQUENCE_ERROR, "HY010", "S1010" }, /* Function sequence error */
|
|
{ STMT_NO_MEMORY_ERROR, "HY001", "S1001" }, /* memory allocation failure */
|
|
{ STMT_COLNUM_ERROR, "07009", "S1002" }, /* invalid column number */
|
|
{ STMT_NO_STMTSTRING, "HY001", "S1001" }, /* having no stmtstring is also a malloc problem */
|
|
{ STMT_ERROR_TAKEN_FROM_BACKEND, "HY000", "S1000" }, /* general error */
|
|
{ STMT_INTERNAL_ERROR, "HY000", "S1000" }, /* general error */
|
|
{ STMT_STILL_EXECUTING, "HY010", "S1010" },
|
|
{ STMT_NOT_IMPLEMENTED_ERROR, "HYC00", "S1C00" }, /* == 'driver not
|
|
* capable' */
|
|
{ STMT_BAD_PARAMETER_NUMBER_ERROR, "07009", "S1093" },
|
|
{ STMT_OPTION_OUT_OF_RANGE_ERROR, "HY092", "S1092" },
|
|
{ STMT_INVALID_COLUMN_NUMBER_ERROR, "07009", "S1002" },
|
|
{ STMT_RESTRICTED_DATA_TYPE_ERROR, "07006", "07006" },
|
|
{ STMT_INVALID_CURSOR_STATE_ERROR, "07005", "24000" },
|
|
{ STMT_CREATE_TABLE_ERROR, "42S01", "S0001" }, /* table already exists */
|
|
{ STMT_NO_CURSOR_NAME, "S1015", "S1015" },
|
|
{ STMT_INVALID_CURSOR_NAME, "34000", "34000" },
|
|
{ STMT_INVALID_ARGUMENT_NO, "HY024", "S1009" }, /* invalid argument value */
|
|
{ STMT_ROW_OUT_OF_RANGE, "HY107", "S1107" },
|
|
{ STMT_OPERATION_CANCELLED, "HY008", "S1008" },
|
|
{ STMT_INVALID_CURSOR_POSITION, "HY109", "S1109" },
|
|
{ STMT_VALUE_OUT_OF_RANGE, "HY019", "22003" },
|
|
{ STMT_OPERATION_INVALID, "HY011", "S1011" },
|
|
{ STMT_PROGRAM_TYPE_OUT_OF_RANGE, "?????", "?????" },
|
|
{ STMT_BAD_ERROR, "08S01", "08S01" }, /* communication link failure */
|
|
{ STMT_INVALID_OPTION_IDENTIFIER, "HY092", "HY092" },
|
|
{ STMT_RETURN_NULL_WITHOUT_INDICATOR, "22002", "22002" },
|
|
{ STMT_INVALID_DESCRIPTOR_IDENTIFIER, "HY091", "HY091" },
|
|
{ STMT_OPTION_NOT_FOR_THE_DRIVER, "HYC00", "HYC00" },
|
|
{ STMT_FETCH_OUT_OF_RANGE, "HY106", "S1106" },
|
|
{ STMT_COUNT_FIELD_INCORRECT, "07002", "07002" },
|
|
{ STMT_INVALID_NULL_ARG, "HY009", "S1009" },
|
|
{ STMT_NO_RESPONSE, "08S01", "08S01" },
|
|
{ STMT_COMMUNICATION_ERROR, "08S01", "08S01" }
|
|
};
|
|
|
|
static PG_ErrorInfo *
|
|
SC_create_errorinfo(const StatementClass *self, PG_ErrorInfo *pgerror_fail_safe)
|
|
{
|
|
QResultClass *res = SC_get_Curres(self);
|
|
ConnectionClass *conn = SC_get_conn(self);
|
|
Int4 errornum;
|
|
size_t pos;
|
|
BOOL resmsg = FALSE, detailmsg = FALSE, msgend = FALSE;
|
|
BOOL looponce, loopend;
|
|
char msg[4096], *wmsg;
|
|
char *ermsg = NULL, *sqlstate = NULL;
|
|
PG_ErrorInfo *pgerror;
|
|
|
|
if (self->pgerror)
|
|
return self->pgerror;
|
|
errornum = self->__error_number;
|
|
if (errornum == 0)
|
|
return NULL;
|
|
|
|
looponce = (SC_get_Result(self) != res);
|
|
msg[0] = '\0';
|
|
for (loopend = FALSE; (NULL != res) && !loopend; res = res->next)
|
|
{
|
|
if (looponce)
|
|
loopend = TRUE;
|
|
if ('\0' != res->sqlstate[0])
|
|
{
|
|
if (NULL != sqlstate && strnicmp(res->sqlstate, "00", 2) == 0)
|
|
continue;
|
|
sqlstate = res->sqlstate;
|
|
if ('0' != sqlstate[0] ||
|
|
'1' < sqlstate[1])
|
|
loopend = TRUE;
|
|
}
|
|
if (NULL != res->message)
|
|
{
|
|
STRCPY_FIXED(msg, res->message);
|
|
detailmsg = resmsg = TRUE;
|
|
}
|
|
else if (NULL != res->messageref)
|
|
{
|
|
STRCPY_FIXED(msg, res->messageref);
|
|
detailmsg = resmsg = TRUE;
|
|
}
|
|
if (msg[0])
|
|
ermsg = msg;
|
|
else if (QR_get_notice(res))
|
|
{
|
|
char *notice = QR_get_notice(res);
|
|
size_t len = strlen(notice);
|
|
if (len < sizeof(msg))
|
|
{
|
|
memcpy(msg, notice, len);
|
|
msg[len] = '\0';
|
|
ermsg = msg;
|
|
}
|
|
else
|
|
{
|
|
ermsg = notice;
|
|
msgend = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (!msgend && (wmsg = SC_get_errormsg(self)) && wmsg[0])
|
|
{
|
|
pos = strlen(msg);
|
|
|
|
snprintf(&msg[pos], sizeof(msg) - pos, "%s%s",
|
|
detailmsg ? ";\n" : "",
|
|
wmsg);
|
|
ermsg = msg;
|
|
detailmsg = TRUE;
|
|
}
|
|
if (!self->ref_CC_error)
|
|
msgend = TRUE;
|
|
|
|
if (conn && !msgend)
|
|
{
|
|
if (!resmsg && (wmsg = CC_get_errormsg(conn)) && wmsg[0] != '\0')
|
|
{
|
|
pos = strlen(msg);
|
|
snprintf(&msg[pos], sizeof(msg) - pos,
|
|
";\n%s", CC_get_errormsg(conn));
|
|
}
|
|
|
|
ermsg = msg;
|
|
}
|
|
pgerror = ER_Constructor(self->__error_number, ermsg);
|
|
if (!pgerror)
|
|
{
|
|
if (pgerror_fail_safe)
|
|
{
|
|
memset(pgerror_fail_safe, 0, sizeof(*pgerror_fail_safe));
|
|
pgerror = pgerror_fail_safe;
|
|
pgerror->status = self->__error_number;
|
|
pgerror->errorsize = sizeof(pgerror->__error_message);
|
|
STRCPY_FIXED(pgerror->__error_message, ermsg);
|
|
pgerror->recsize = -1;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
if (sqlstate)
|
|
STRCPY_FIXED(pgerror->sqlstate, sqlstate);
|
|
else if (conn)
|
|
{
|
|
if (!msgend && conn->sqlstate[0])
|
|
STRCPY_FIXED(pgerror->sqlstate, conn->sqlstate);
|
|
else
|
|
{
|
|
EnvironmentClass *env = (EnvironmentClass *) CC_get_env(conn);
|
|
|
|
errornum -= LOWEST_STMT_ERROR;
|
|
if (errornum < 0 ||
|
|
errornum >= sizeof(Statement_sqlstate) / sizeof(Statement_sqlstate[0]))
|
|
{
|
|
errornum = 1 - LOWEST_STMT_ERROR;
|
|
}
|
|
STRCPY_FIXED(pgerror->sqlstate, EN_is_odbc3(env) ?
|
|
Statement_sqlstate[errornum].ver3str :
|
|
Statement_sqlstate[errornum].ver2str);
|
|
}
|
|
}
|
|
|
|
return pgerror;
|
|
}
|
|
|
|
|
|
StatementClass *SC_get_ancestor(StatementClass *stmt)
|
|
{
|
|
StatementClass *child = stmt, *parent;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering stmt=%p\n", stmt);
|
|
for (child = stmt, parent = child->execute_parent; parent; child = parent, parent = child->execute_parent)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "parent=%p\n", parent);
|
|
}
|
|
return child;
|
|
}
|
|
void SC_reset_delegate(RETCODE retcode, StatementClass *stmt)
|
|
{
|
|
StatementClass *delegate = stmt->execute_delegate;
|
|
|
|
if (!delegate)
|
|
return;
|
|
PGAPI_FreeStmt(delegate, SQL_DROP);
|
|
}
|
|
|
|
void
|
|
SC_set_error(StatementClass *self, int number, const char *message, const char *func)
|
|
{
|
|
if (self->__error_message)
|
|
free(self->__error_message);
|
|
self->__error_number = number;
|
|
self->__error_message = message ? strdup(message) : NULL;
|
|
if (func && number != STMT_OK && number != STMT_INFO_ONLY)
|
|
SC_log_error(func, "", self);
|
|
}
|
|
|
|
|
|
void
|
|
SC_set_errormsg(StatementClass *self, const char *message)
|
|
{
|
|
if (self->__error_message)
|
|
free(self->__error_message);
|
|
self->__error_message = message ? strdup(message) : NULL;
|
|
}
|
|
|
|
|
|
void
|
|
SC_replace_error_with_res(StatementClass *self, int number, const char *message, const QResultClass *from_res, BOOL check)
|
|
{
|
|
QResultClass *self_res;
|
|
BOOL repstate;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering %p->%p check=%i\n", from_res ,self, check);
|
|
if (check)
|
|
{
|
|
if (0 == number) return;
|
|
if (0 > number && /* SQL_SUCCESS_WITH_INFO */
|
|
0 < self->__error_number)
|
|
return;
|
|
}
|
|
if (!from_res)
|
|
return;
|
|
self->__error_number = number;
|
|
if (!check || message)
|
|
{
|
|
if (self->__error_message)
|
|
free(self->__error_message);
|
|
self->__error_message = message ? strdup(message) : NULL;
|
|
}
|
|
if (self->pgerror)
|
|
{
|
|
ER_Destructor(self->pgerror);
|
|
self->pgerror = NULL;
|
|
}
|
|
self_res = SC_get_Curres(self);
|
|
if (!self_res) return;
|
|
if (self_res == from_res) return;
|
|
QR_add_message(self_res, QR_get_message(from_res));
|
|
QR_add_notice(self_res, QR_get_notice(from_res));
|
|
repstate = FALSE;
|
|
if (!check)
|
|
repstate = TRUE;
|
|
else if (from_res->sqlstate[0])
|
|
{
|
|
if (!self_res->sqlstate[0] || strncmp(self_res->sqlstate, "00", 2) == 0)
|
|
repstate = TRUE;
|
|
else if (strncmp(from_res->sqlstate, "01", 2) >= 0)
|
|
repstate = TRUE;
|
|
}
|
|
if (repstate)
|
|
STRCPY_FIXED(self_res->sqlstate, from_res->sqlstate);
|
|
}
|
|
|
|
void
|
|
SC_error_copy(StatementClass *self, const StatementClass *from, BOOL check)
|
|
{
|
|
QResultClass *self_res, *from_res;
|
|
BOOL repstate;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering %p->%p check=%i\n", from ,self, check);
|
|
if (!from) return; /* for safety */
|
|
if (self == from) return; /* for safety */
|
|
if (check)
|
|
{
|
|
if (0 == from->__error_number) /* SQL_SUCCESS */
|
|
return;
|
|
if (0 > from->__error_number && /* SQL_SUCCESS_WITH_INFO */
|
|
0 < self->__error_number)
|
|
return;
|
|
}
|
|
self->__error_number = from->__error_number;
|
|
if (!check || from->__error_message)
|
|
{
|
|
if (self->__error_message)
|
|
free(self->__error_message);
|
|
self->__error_message = from->__error_message ? strdup(from->__error_message) : NULL;
|
|
}
|
|
if (self->pgerror)
|
|
{
|
|
ER_Destructor(self->pgerror);
|
|
self->pgerror = NULL;
|
|
}
|
|
self_res = SC_get_Curres(self);
|
|
from_res = SC_get_Curres(from);
|
|
if (!self_res || !from_res)
|
|
return;
|
|
QR_add_message(self_res, QR_get_message(from_res));
|
|
QR_add_notice(self_res, QR_get_notice(from_res));
|
|
repstate = FALSE;
|
|
if (!check)
|
|
repstate = TRUE;
|
|
else if (from_res->sqlstate[0])
|
|
{
|
|
if (!self_res->sqlstate[0] || strncmp(self_res->sqlstate, "00", 2) == 0)
|
|
repstate = TRUE;
|
|
else if (strncmp(from_res->sqlstate, "01", 2) >= 0)
|
|
repstate = TRUE;
|
|
}
|
|
if (repstate)
|
|
STRCPY_FIXED(self_res->sqlstate, from_res->sqlstate);
|
|
}
|
|
|
|
|
|
void
|
|
SC_full_error_copy(StatementClass *self, const StatementClass *from, BOOL allres)
|
|
{
|
|
PG_ErrorInfo *pgerror;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering %p->%p\n", from ,self);
|
|
if (!from) return; /* for safety */
|
|
if (self == from) return; /* for safety */
|
|
if (self->__error_message)
|
|
{
|
|
free(self->__error_message);
|
|
self->__error_message = NULL;
|
|
}
|
|
if (from->__error_message)
|
|
self->__error_message = strdup(from->__error_message);
|
|
self->__error_number = from->__error_number;
|
|
if (from->pgerror)
|
|
{
|
|
if (self->pgerror)
|
|
ER_Destructor(self->pgerror);
|
|
self->pgerror = ER_Dup(from->pgerror);
|
|
return;
|
|
}
|
|
else if (!allres)
|
|
return;
|
|
pgerror = SC_create_errorinfo(from, NULL);
|
|
if (!pgerror || !pgerror->__error_message[0])
|
|
{
|
|
ER_Destructor(pgerror);
|
|
return;
|
|
}
|
|
if (self->pgerror)
|
|
ER_Destructor(self->pgerror);
|
|
self->pgerror = pgerror;
|
|
}
|
|
|
|
/* Returns the next SQL error information. */
|
|
RETCODE SQL_API
|
|
PGAPI_StmtError(SQLHSTMT hstmt,
|
|
SQLSMALLINT RecNumber,
|
|
SQLCHAR * szSqlState,
|
|
SQLINTEGER * pfNativeError,
|
|
SQLCHAR * szErrorMsg,
|
|
SQLSMALLINT cbErrorMsgMax,
|
|
SQLSMALLINT * pcbErrorMsg,
|
|
UWORD flag)
|
|
{
|
|
/* CC: return an error of a hdesc */
|
|
PG_ErrorInfo *pgerror, error;
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
int errnum = SC_get_errornumber(stmt);
|
|
|
|
if (pgerror = SC_create_errorinfo(stmt, &error), NULL == pgerror)
|
|
return SQL_NO_DATA_FOUND;
|
|
if (pgerror != &error)
|
|
stmt->pgerror = pgerror;
|
|
if (STMT_NO_MEMORY_ERROR == errnum &&
|
|
!pgerror->__error_message[0])
|
|
STRCPY_FIXED(pgerror->__error_message, "Memory Allocation Error??");
|
|
return ER_ReturnError(pgerror, RecNumber, szSqlState,
|
|
pfNativeError, szErrorMsg, cbErrorMsgMax,
|
|
pcbErrorMsg, flag);
|
|
}
|
|
|
|
time_t
|
|
SC_get_time(StatementClass *stmt)
|
|
{
|
|
if (!stmt)
|
|
return time(NULL);
|
|
if (0 == stmt->stmt_time)
|
|
stmt->stmt_time = time(NULL);
|
|
return stmt->stmt_time;
|
|
}
|
|
|
|
struct tm *
|
|
SC_get_localtime(StatementClass *stmt)
|
|
{
|
|
#ifndef HAVE_LOCALTIME_R
|
|
struct tm * tim;
|
|
#endif /* HAVE_LOCALTIME_R */
|
|
|
|
if (stmt->localtime.tm_sec < 0)
|
|
{
|
|
SC_get_time(stmt);
|
|
#ifdef HAVE_LOCALTIME_R
|
|
localtime_r(&stmt->stmt_time, &(stmt->localtime));
|
|
#else
|
|
tim = localtime(&stmt->stmt_time);
|
|
stmt->localtime = *tim;
|
|
#endif /* HAVE_LOCALTIME_R */
|
|
}
|
|
|
|
return &(stmt->localtime);
|
|
}
|
|
|
|
RETCODE
|
|
SC_fetch(StatementClass *self)
|
|
{
|
|
CSTR func = "SC_fetch";
|
|
QResultClass *res = SC_get_Curres(self);
|
|
ARDFields *opts;
|
|
GetDataInfo *gdata;
|
|
int retval;
|
|
RETCODE result;
|
|
|
|
Int2 num_cols,
|
|
lf;
|
|
OID type;
|
|
int atttypmod;
|
|
char *value;
|
|
ColumnInfoClass *coli;
|
|
BindInfoClass *bookmark;
|
|
BOOL useCursor;
|
|
KeySet *keyset = NULL;
|
|
|
|
/* TupleField *tupleField; */
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering statement=%p res=%p ommitted=0\n", self, res);
|
|
self->last_fetch_count = self->last_fetch_count_include_ommitted = 0;
|
|
if (!res)
|
|
return SQL_ERROR;
|
|
coli = QR_get_fields(res); /* the column info */
|
|
|
|
MYLOG(0, "fetch_cursor=%d, %p->total_read=" FORMAT_LEN "\n", SC_is_fetchcursor(self), res, res->num_total_read);
|
|
|
|
useCursor = (SC_is_fetchcursor(self) && (NULL != QR_get_cursor(res)));
|
|
if (!useCursor)
|
|
{
|
|
if (self->currTuple >= (Int4) QR_get_num_total_tuples(res) - 1 ||
|
|
(self->options.maxRows > 0 && self->currTuple == self->options.maxRows - 1))
|
|
{
|
|
/*
|
|
* if at the end of the tuples, return "no data found" and set
|
|
* the cursor past the end of the result set
|
|
*/
|
|
self->currTuple = QR_get_num_total_tuples(res);
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
|
|
MYLOG(0, "**** : non-cursor_result\n");
|
|
(self->currTuple)++;
|
|
}
|
|
else
|
|
{
|
|
/* read from the cache or the physical next tuple */
|
|
retval = QR_next_tuple(res, self);
|
|
if (retval < 0)
|
|
{
|
|
MYLOG(0, "**** : end_tuples\n");
|
|
if (QR_get_cursor(res) &&
|
|
SQL_CURSOR_FORWARD_ONLY == self->options.cursor_type &&
|
|
QR_once_reached_eof(res))
|
|
QR_close(res);
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
else if (retval > 0)
|
|
(self->currTuple)++; /* all is well */
|
|
else
|
|
{
|
|
SC_set_errorinfo(self, res, 1);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
if (QR_haskeyset(res))
|
|
{
|
|
SQLLEN kres_ridx;
|
|
|
|
kres_ridx = GIdx2KResIdx(self->currTuple, self, res);
|
|
if (kres_ridx >= 0 && kres_ridx < res->num_cached_keys)
|
|
{
|
|
UWORD pstatus = res->keyset[kres_ridx].status;
|
|
MYLOG(DETAIL_LOG_LEVEL, "SC_ pstatus[" FORMAT_LEN "]=%hx fetch_count=" FORMAT_LEN "\n", kres_ridx, pstatus, self->last_fetch_count);
|
|
if (0 != (pstatus & (CURS_SELF_DELETING | CURS_SELF_DELETED)))
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
if (SQL_ROW_DELETED != (pstatus & KEYSET_INFO_PUBLIC) &&
|
|
0 != (pstatus & CURS_OTHER_DELETED))
|
|
{
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
}
|
|
if (0 != (CURS_NEEDS_REREAD & pstatus))
|
|
{
|
|
UWORD qcount;
|
|
|
|
result = SC_pos_reload(self, self->currTuple, &qcount, 0);
|
|
if (SQL_ERROR == result)
|
|
return result;
|
|
pstatus &= ~CURS_NEEDS_REREAD;
|
|
}
|
|
keyset = res->keyset + kres_ridx;
|
|
}
|
|
}
|
|
|
|
num_cols = QR_NumPublicResultCols(res);
|
|
|
|
result = SQL_SUCCESS;
|
|
self->last_fetch_count++;
|
|
MYLOG(DETAIL_LOG_LEVEL, "stmt=%p ommitted++\n", self);
|
|
self->last_fetch_count_include_ommitted++;
|
|
|
|
opts = SC_get_ARDF(self);
|
|
/*
|
|
* If the bookmark column was bound then return a bookmark. Since this
|
|
* is used with SQLExtendedFetch, and the rowset size may be greater
|
|
* than 1, and an application can use row or column wise binding, use
|
|
* the code in copy_and_convert_field() to handle that.
|
|
*/
|
|
if ((bookmark = opts->bookmark) && bookmark->buffer)
|
|
{
|
|
SC_set_current_col(self, -1);
|
|
SC_Create_bookmark(self, bookmark, self->bind_row, self->currTuple, keyset);
|
|
}
|
|
|
|
if (self->options.retrieve_data == SQL_RD_OFF) /* data isn't required */
|
|
return SQL_SUCCESS;
|
|
/* The following adjustment would be needed after SQLMoreResults() */
|
|
if (opts->allocated < num_cols)
|
|
extend_column_bindings(opts, num_cols);
|
|
gdata = SC_get_GDTI(self);
|
|
if (gdata->allocated != opts->allocated)
|
|
extend_getdata_info(gdata, opts->allocated, TRUE);
|
|
for (lf = 0; lf < num_cols; lf++)
|
|
{
|
|
MYLOG(0, "fetch: cols=%d, lf=%d, opts = %p, opts->bindings = %p, buffer[] = %p\n", num_cols, lf, opts, opts->bindings, opts->bindings[lf].buffer);
|
|
|
|
/* reset for SQLGetData */
|
|
GETDATA_RESET(gdata->gdata[lf]);
|
|
|
|
if (NULL == opts->bindings)
|
|
continue;
|
|
if (opts->bindings[lf].buffer != NULL)
|
|
{
|
|
/* this column has a binding */
|
|
|
|
type = CI_get_oid(coli, lf); /* speed things up */
|
|
atttypmod = CI_get_atttypmod(coli, lf); /* speed things up */
|
|
|
|
MYLOG(0, "type = %d, atttypmod = %d\n", type, atttypmod);
|
|
|
|
if (useCursor)
|
|
value = QR_get_value_backend(res, lf);
|
|
else
|
|
{
|
|
SQLLEN curt = GIdx2CacheIdx(self->currTuple, self, res);
|
|
MYLOG(DETAIL_LOG_LEVEL, "%p->base=" FORMAT_LEN " curr=" FORMAT_LEN " st=" FORMAT_LEN " valid=%d\n", res, QR_get_rowstart_in_cache(res), self->currTuple, SC_get_rowset_start(self), QR_has_valid_base(res));
|
|
MYLOG(DETAIL_LOG_LEVEL, "curt=" FORMAT_LEN "\n", curt);
|
|
value = QR_get_value_backend_row(res, curt, lf);
|
|
}
|
|
|
|
MYLOG(0, "value = '%s'\n", (value == NULL) ? "<NULL>" : value);
|
|
|
|
retval = copy_and_convert_field_bindinfo(self, type, atttypmod, value, lf);
|
|
|
|
MYLOG(0, "copy_and_convert: retval = %d\n", retval);
|
|
|
|
switch (retval)
|
|
{
|
|
case COPY_OK:
|
|
break; /* OK, do next bound column */
|
|
|
|
case COPY_UNSUPPORTED_TYPE:
|
|
SC_set_error(self, STMT_RESTRICTED_DATA_TYPE_ERROR, "Received an unsupported type from Postgres.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_UNSUPPORTED_CONVERSION:
|
|
SC_set_error(self, STMT_RESTRICTED_DATA_TYPE_ERROR, "Couldn't handle the necessary data type conversion.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_RESULT_TRUNCATED:
|
|
SC_set_error(self, STMT_TRUNCATED, "Fetched item was truncated.", func);
|
|
MYLOG(DETAIL_LOG_LEVEL, "The %dth item was truncated\n", lf + 1);
|
|
MYLOG(DETAIL_LOG_LEVEL, "The buffer size = " FORMAT_LEN, opts->bindings[lf].buflen);
|
|
MYLOG(DETAIL_LOG_LEVEL, " and the value is '%s'\n", value);
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
break;
|
|
|
|
case COPY_INVALID_STRING_CONVERSION: /* invalid string */
|
|
SC_set_error(self, STMT_STRING_CONVERSION_ERROR, "invalid string conversion occured.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
/* error msg already filled in */
|
|
case COPY_GENERAL_ERROR:
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
/* This would not be meaningful in SQLFetch. */
|
|
case COPY_NO_DATA_FOUND:
|
|
break;
|
|
|
|
default:
|
|
SC_set_error(self, STMT_INTERNAL_ERROR, "Unrecognized return value from copy_and_convert_field.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#include "dlg_specific.h"
|
|
RETCODE
|
|
SC_execute(StatementClass *self)
|
|
{
|
|
CSTR func = "SC_execute";
|
|
ConnectionClass *conn;
|
|
IPDFields *ipdopts;
|
|
char was_ok, was_nonfatal;
|
|
QResultClass *res = NULL;
|
|
Int2 oldstatus,
|
|
numcols;
|
|
QueryInfo qi;
|
|
ConnInfo *ci;
|
|
unsigned int qflag = 0;
|
|
BOOL is_in_trans, issue_begin, has_out_para;
|
|
BOOL use_extended_protocol;
|
|
int func_cs_count = 0, i;
|
|
BOOL useCursor, isSelectType;
|
|
|
|
conn = SC_get_conn(self);
|
|
ci = &(conn->connInfo);
|
|
|
|
/* Begin a transaction if one is not already in progress */
|
|
|
|
/*
|
|
* Basically we don't have to begin a transaction in autocommit mode
|
|
* because Postgres backend runs in autocomit mode. We issue "BEGIN"
|
|
* in the following cases. 1) we use declare/fetch and the statement
|
|
* is SELECT (because declare/fetch must be called in a transaction).
|
|
* 2) we are in autocommit off state and the statement isn't of type
|
|
* OTHER.
|
|
*/
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
ENTER_INNER_CONN_CS(conn, func_cs_count);
|
|
oldstatus = conn->status;
|
|
if (CONN_EXECUTING == conn->status)
|
|
{
|
|
SC_set_error(self, STMT_SEQUENCE_ERROR, "Connection is already in use.", func);
|
|
MYLOG(0, "problem with connection\n");
|
|
goto cleanup;
|
|
}
|
|
is_in_trans = CC_is_in_trans(conn);
|
|
if ((useCursor = SC_is_fetchcursor(self)))
|
|
{
|
|
QResultClass *curres = SC_get_Curres(self);
|
|
|
|
if (NULL != curres &&
|
|
curres->dataFilled)
|
|
useCursor = (NULL != QR_get_cursor(curres));
|
|
}
|
|
/* issue BEGIN ? */
|
|
issue_begin = TRUE;
|
|
if (!self->external)
|
|
issue_begin = FALSE;
|
|
else if (is_in_trans)
|
|
{
|
|
issue_begin = FALSE;
|
|
if (STMT_TYPE_START == self->statement_type &&
|
|
CC_does_autocommit(conn))
|
|
{
|
|
CC_commit(conn);
|
|
is_in_trans = CC_is_in_trans(conn);
|
|
}
|
|
}
|
|
else if (CC_does_autocommit(conn) &&
|
|
(!useCursor
|
|
/* || SC_is_with_hold(self) thiw would lose the performance */
|
|
))
|
|
issue_begin = FALSE;
|
|
else if (self->statement_type == STMT_TYPE_SPECIAL)
|
|
{
|
|
/*
|
|
* Some utility commands like VACUUM cannot be run in a transaction
|
|
* block, so don't begin one even if auto-commit mode is disabled.
|
|
*
|
|
* An application should never issue an explicit BEGIN when
|
|
* auto-commit mode is disabled (probably not even when it's enabled,
|
|
* actually). We used to also suppress the implicit BEGIN when the
|
|
* statement was of STMT_TYPE_START type, ie. if the application
|
|
* issued an explicit BEGIN, but that actually seems like a bad idea.
|
|
* First of all, if you issue a BEGIN twice the backend will give a
|
|
* warning which can be helpful to spot mistakes in the application
|
|
* (because it shouldn't be doing that).
|
|
*/
|
|
issue_begin = FALSE;
|
|
}
|
|
if (issue_begin)
|
|
{
|
|
MYLOG(0, " about to begin a transaction on statement = %p\n", self);
|
|
qflag |= GO_INTO_TRANSACTION;
|
|
}
|
|
|
|
/*
|
|
* If the session query timeout setting differs from the statement one,
|
|
* change it.
|
|
*/
|
|
if (conn->stmt_timeout_in_effect != self->options.stmt_timeout)
|
|
{
|
|
char query[64];
|
|
|
|
SPRINTF_FIXED(query, "SET statement_timeout = %d",
|
|
(int) self->options.stmt_timeout * 1000);
|
|
res = CC_send_query(conn, query, NULL, 0, NULL);
|
|
if (QR_command_maybe_successful(res))
|
|
conn->stmt_timeout_in_effect = self->options.stmt_timeout;
|
|
QR_Destructor(res);
|
|
}
|
|
|
|
if (!SC_SetExecuting(self, TRUE))
|
|
{
|
|
SC_set_error(self, STMT_OPERATION_CANCELLED, "Cancel Reuest Accepted", func);
|
|
goto cleanup;
|
|
}
|
|
conn->status = CONN_EXECUTING;
|
|
|
|
/* If it's a SELECT statement, use a cursor. */
|
|
|
|
/*
|
|
* Note that the declare cursor has already been prepended to the
|
|
* statement
|
|
*/
|
|
/* in copy_statement... */
|
|
if (self->stmt_with_params)
|
|
use_extended_protocol = FALSE;
|
|
else
|
|
{
|
|
use_extended_protocol = TRUE;
|
|
}
|
|
isSelectType = (SC_may_use_cursor(self) || self->statement_type == STMT_TYPE_PROCCALL);
|
|
if (use_extended_protocol)
|
|
{
|
|
if (issue_begin)
|
|
CC_begin(conn);
|
|
|
|
res = libpq_bind_and_exec(self);
|
|
if (!res)
|
|
{
|
|
if (SC_get_errornumber(self) <= 0)
|
|
{
|
|
SC_set_error(self, STMT_NO_RESPONSE, "Could not receive the response, communication down ??", func);
|
|
}
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else if (isSelectType)
|
|
{
|
|
char fetch[128];
|
|
const char *appendq = NULL;
|
|
QueryInfo *qryi = NULL;
|
|
|
|
qflag |= (SQL_CONCUR_READ_ONLY != self->options.scroll_concurrency ? CREATE_KEYSET : 0);
|
|
MYLOG(0, " Sending SELECT statement on stmt=%p, cursor_name='%s' qflag=%d,%d\n", self, SC_cursor_name(self), qflag, self->options.scroll_concurrency);
|
|
|
|
/* send the declare/select */
|
|
if (useCursor)
|
|
{
|
|
qi.result_in = NULL;
|
|
qi.cursor = SC_cursor_name(self);
|
|
qi.fetch_size = qi.row_size = ci->drivers.fetch_max;
|
|
SPRINTF_FIXED(fetch,
|
|
"fetch " FORMAT_LEN " in \"%s\"",
|
|
qi.fetch_size, SC_cursor_name(self));
|
|
qryi = &qi;
|
|
appendq = fetch;
|
|
qflag &= (~READ_ONLY_QUERY); /* must be a SAVEPOINT after DECLARE */
|
|
}
|
|
res = SC_get_Result(self);
|
|
if (self->curr_param_result && res)
|
|
SC_set_Result(self, res->next);
|
|
res = CC_send_query_append(conn, self->stmt_with_params, qryi, qflag, SC_get_ancestor(self), appendq);
|
|
if (useCursor && QR_command_maybe_successful(res))
|
|
{
|
|
/*
|
|
* If we sent a DECLARE CURSOR + FETCH, throw away the result of
|
|
* the DECLARE CURSOR statement, and only return the result of the
|
|
* FETCH to the caller. However, if we received any NOTICEs as
|
|
* part of the DECLARE CURSOR, carry those over.
|
|
*/
|
|
if (appendq)
|
|
{
|
|
QResultClass *qres, *nres;
|
|
|
|
for (qres = res; qres;)
|
|
{
|
|
if (qres->command && strnicmp(qres->command, "fetch", 5) == 0)
|
|
{
|
|
break;
|
|
}
|
|
nres = qres->next;
|
|
if (nres && QR_get_notice(qres) != NULL)
|
|
{
|
|
if (QR_command_successful(nres) &&
|
|
QR_command_nonfatal(qres))
|
|
{
|
|
QR_set_rstatus(nres, PORES_NONFATAL_ERROR);
|
|
}
|
|
QR_add_notice(nres, QR_get_notice(qres));
|
|
}
|
|
qres->next = NULL;
|
|
QR_Destructor(qres);
|
|
qres = nres;
|
|
|
|
/*
|
|
* If we received fewer rows than requested, there are no
|
|
* more rows to fetch.
|
|
*/
|
|
if (qres->num_cached_rows < qi.row_size)
|
|
QR_set_reached_eof(qres);
|
|
}
|
|
res = qres;
|
|
}
|
|
if (res && SC_is_with_hold(self))
|
|
QR_set_withhold(res);
|
|
}
|
|
MYLOG(0, " done sending the query:\n");
|
|
}
|
|
else
|
|
{
|
|
/* not a SELECT statement so don't use a cursor */
|
|
MYLOG(0, " it's NOT a select statement: stmt=%p\n", self);
|
|
res = CC_send_query(conn, self->stmt_with_params, NULL, qflag, SC_get_ancestor(self));
|
|
}
|
|
|
|
if (!isSelectType)
|
|
{
|
|
/*
|
|
* We shouldn't send COMMIT. Postgres backend does the autocommit
|
|
* if neccessary. (Zoltan, 04/26/2000)
|
|
*/
|
|
|
|
/*
|
|
* Above seems wrong. Even in case of autocommit, started
|
|
* transactions must be committed. (Hiroshi, 02/11/2001)
|
|
*/
|
|
if (CC_is_in_trans(conn))
|
|
{
|
|
if (!is_in_trans)
|
|
CC_set_in_manual_trans(conn);
|
|
if (self->external && CC_does_autocommit(conn) && !CC_is_in_manual_trans(conn))
|
|
CC_commit(conn);
|
|
}
|
|
}
|
|
|
|
if (CONN_DOWN != conn->status)
|
|
conn->status = oldstatus;
|
|
self->status = STMT_FINISHED;
|
|
LEAVE_INNER_CONN_CS(func_cs_count, conn);
|
|
|
|
/* Check the status of the result */
|
|
if (res)
|
|
{
|
|
was_ok = QR_command_successful(res);
|
|
was_nonfatal = QR_command_nonfatal(res);
|
|
|
|
if (0 < SC_get_errornumber(self))
|
|
;
|
|
else if (was_ok)
|
|
SC_set_errornumber(self, STMT_OK);
|
|
else if (was_nonfatal)
|
|
SC_set_errornumber(self, STMT_INFO_ONLY);
|
|
else
|
|
SC_set_errorinfo(self, res, 0);
|
|
/* set cursor before the first tuple in the list */
|
|
self->currTuple = -1;
|
|
SC_set_current_col(self, -1);
|
|
SC_set_rowset_start(self, -1, FALSE);
|
|
|
|
/* issue "ABORT" when query aborted */
|
|
if (QR_get_aborted(res))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
QResultClass *tres;
|
|
|
|
/* see if the query did return any result columns */
|
|
for (tres = res, numcols = 0; !numcols && tres; tres = tres->next)
|
|
{
|
|
numcols = QR_NumResultCols(tres);
|
|
}
|
|
/* now allocate the array to hold the binding info */
|
|
if (numcols > 0)
|
|
{
|
|
ARDFields *opts = SC_get_ARDF(self);
|
|
extend_column_bindings(opts, numcols);
|
|
if (opts->bindings == NULL)
|
|
{
|
|
QR_Destructor(res);
|
|
SC_set_error(self, STMT_NO_MEMORY_ERROR,"Could not get enough free memory to store the binding information", func);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "!!%p->miscinfo=%x res=%p\n", self, self->miscinfo, res);
|
|
/*
|
|
* special handling of result for keyset driven cursors.
|
|
* Use the columns info of the 1st query and
|
|
* user the keyset info of the 2nd query.
|
|
*/
|
|
if (SQL_CURSOR_KEYSET_DRIVEN == self->options.cursor_type &&
|
|
SQL_CONCUR_READ_ONLY != self->options.scroll_concurrency &&
|
|
!useCursor)
|
|
{
|
|
if (tres = res->next, tres)
|
|
{
|
|
QR_set_fields(tres, QR_get_fields(res));
|
|
QR_set_fields(res, NULL);
|
|
tres->num_fields = res->num_fields;
|
|
res->next = NULL;
|
|
QR_Destructor(res);
|
|
SC_init_Result(self);
|
|
SC_set_Result(self, tres);
|
|
res = tres;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Bad Error -- The error message will be in the Connection */
|
|
if (!conn->pqconn)
|
|
SC_set_error(self, STMT_BAD_ERROR, CC_get_errormsg(conn), func);
|
|
else if (self->statement_type == STMT_TYPE_CREATE)
|
|
{
|
|
SC_set_error(self, STMT_CREATE_TABLE_ERROR, "Error creating the table", func);
|
|
|
|
/*
|
|
* This would allow the table to already exists, thus
|
|
* appending rows to it. BUT, if the table didn't have the
|
|
* same attributes, it would fail. return
|
|
* SQL_SUCCESS_WITH_INFO;
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(self, STMT_EXEC_ERROR, CC_get_errormsg(conn), func);
|
|
}
|
|
|
|
}
|
|
if (!SC_get_Result(self))
|
|
SC_set_Result(self, res);
|
|
else
|
|
{
|
|
QResultClass *last;
|
|
|
|
for (last = SC_get_Result(self); NULL != last->next; last = last->next)
|
|
{
|
|
if (last == res)
|
|
break;
|
|
}
|
|
if (last != res)
|
|
last->next = res;
|
|
self->curr_param_result = 1;
|
|
}
|
|
if (NULL == SC_get_Curres(self))
|
|
SC_set_Curres(self, SC_get_Result(self));
|
|
|
|
ipdopts = SC_get_IPDF(self);
|
|
has_out_para = FALSE;
|
|
if (self->statement_type == STMT_TYPE_PROCCALL &&
|
|
(SC_get_errornumber(self) == STMT_OK ||
|
|
SC_get_errornumber(self) == STMT_INFO_ONLY))
|
|
{
|
|
Int2 io, out;
|
|
has_out_para = (CountParameters(self, NULL, &io, &out) > 0);
|
|
/*
|
|
* I'm not sure if the following REFCUR_SUPPORT stuff is valuable
|
|
* or not.
|
|
*/
|
|
#ifdef REFCUR_SUPPORT
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "!!! numfield=%d field_type=%u\n", QR_NumResultCols(res), QR_get_field_type(res, 0));
|
|
if (!has_out_para &&
|
|
0 < QR_NumResultCols(res) &&
|
|
PG_TYPE_REFCURSOR == QR_get_field_type(res, 0))
|
|
{
|
|
char fetch[128];
|
|
int stmt_type = self->statement_type;
|
|
|
|
STR_TO_NAME(self->cursor_name, QR_get_value_backend_text(res, 0, 0));
|
|
QR_Destructor(res);
|
|
SC_init_Result(self);
|
|
SC_set_fetchcursor(self);
|
|
qi.result_in = NULL;
|
|
qi.cursor = SC_cursor_name(self);
|
|
qi.cache_size = qi.row_size = ci->drivers.fetch_max;
|
|
SPRINTF_FIXED(fetch, "fetch " FORMAT_LEN " in \"%s\"", qi.fetch_size, SC_cursor_name(self));
|
|
res = CC_send_query(conn, fetch, &qi, qflag | READ_ONLY_QUERY, SC_get_ancestor(self));
|
|
if (NULL != res)
|
|
SC_set_Result(self, res);
|
|
}
|
|
#endif /* REFCUR_SUPPORT */
|
|
}
|
|
if (has_out_para)
|
|
{ /* get the return value of the procedure call */
|
|
RETCODE ret;
|
|
HSTMT hstmt = (HSTMT) self;
|
|
|
|
self->bind_row = 0;
|
|
ret = SC_fetch(hstmt);
|
|
MYLOG(DETAIL_LOG_LEVEL, "!!SC_fetch return =%d\n", ret);
|
|
if (SQL_SUCCEEDED(ret))
|
|
{
|
|
APDFields *apdopts = SC_get_APDF(self);
|
|
SQLULEN offset = apdopts->param_offset_ptr ? *apdopts->param_offset_ptr : 0;
|
|
ARDFields *ardopts = SC_get_ARDF(self);
|
|
const ParameterInfoClass *apara;
|
|
const ParameterImplClass *ipara;
|
|
int save_bind_size = ardopts->bind_size, gidx, num_p;
|
|
|
|
ardopts->bind_size = apdopts->param_bind_type;
|
|
num_p = self->num_params;
|
|
if (ipdopts->allocated < num_p)
|
|
num_p = ipdopts->allocated;
|
|
for (i = 0, gidx = 0; i < num_p; i++)
|
|
{
|
|
ipara = ipdopts->parameters + i;
|
|
if (ipara->paramType == SQL_PARAM_OUTPUT ||
|
|
ipara->paramType == SQL_PARAM_INPUT_OUTPUT)
|
|
{
|
|
apara = apdopts->parameters + i;
|
|
ret = PGAPI_GetData(hstmt, gidx + 1, apara->CType, apara->buffer + offset, apara->buflen, apara->used ? LENADDR_SHIFT(apara->used, offset) : NULL);
|
|
if (!SQL_SUCCEEDED(ret))
|
|
{
|
|
SC_set_error(self, STMT_EXEC_ERROR, "GetData to Procedure return failed.", func);
|
|
break;
|
|
}
|
|
gidx++;
|
|
}
|
|
}
|
|
ardopts->bind_size = save_bind_size; /* restore */
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(self, STMT_EXEC_ERROR, "SC_fetch to get a Procedure return failed.", func);
|
|
}
|
|
}
|
|
cleanup:
|
|
#undef return
|
|
SC_SetExecuting(self, FALSE);
|
|
CLEANUP_FUNC_CONN_CS(func_cs_count, conn);
|
|
if (CONN_DOWN != conn->status)
|
|
conn->status = oldstatus;
|
|
/* self->status = STMT_FINISHED; */
|
|
if (SC_get_errornumber(self) == STMT_OK)
|
|
return SQL_SUCCESS;
|
|
else if (SC_get_errornumber(self) < STMT_OK)
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
else
|
|
{
|
|
if (!SC_get_errormsg(self) || !SC_get_errormsg(self)[0])
|
|
{
|
|
if (STMT_NO_MEMORY_ERROR != SC_get_errornumber(self))
|
|
SC_set_errormsg(self, "Error while executing the query");
|
|
SC_log_error(func, NULL, self);
|
|
}
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
#define CALLBACK_ALLOC_ONCE 4
|
|
int enqueueNeedDataCallback(StatementClass *stmt, NeedDataCallfunc func, void *data)
|
|
{
|
|
if (stmt->num_callbacks >= stmt->allocated_callbacks)
|
|
{
|
|
SC_REALLOC_return_with_error(stmt->callbacks, NeedDataCallback,
|
|
sizeof(NeedDataCallback) * (stmt->allocated_callbacks +
|
|
CALLBACK_ALLOC_ONCE), stmt, "NeedDataCallback enqueue error", 0);
|
|
stmt->allocated_callbacks += CALLBACK_ALLOC_ONCE;
|
|
}
|
|
stmt->callbacks[stmt->num_callbacks].func = func;
|
|
stmt->callbacks[stmt->num_callbacks].data = data;
|
|
stmt->num_callbacks++;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "stmt=%p, func=%p, count=%d\n", stmt, func, stmt->num_callbacks);
|
|
return stmt->num_callbacks;
|
|
}
|
|
|
|
RETCODE dequeueNeedDataCallback(RETCODE retcode, StatementClass *stmt)
|
|
{
|
|
RETCODE ret;
|
|
NeedDataCallfunc func;
|
|
void *data;
|
|
int i, cnt;
|
|
|
|
MYLOG(0, "entering ret=%d count=%d\n", retcode, stmt->num_callbacks);
|
|
if (SQL_NEED_DATA == retcode)
|
|
return retcode;
|
|
if (stmt->num_callbacks <= 0)
|
|
return retcode;
|
|
func = stmt->callbacks[0].func;
|
|
data = stmt->callbacks[0].data;
|
|
for (i = 1; i < stmt->num_callbacks; i++)
|
|
stmt->callbacks[i - 1] = stmt->callbacks[i];
|
|
cnt = --stmt->num_callbacks;
|
|
ret = (*func)(retcode, data);
|
|
free(data);
|
|
if (SQL_NEED_DATA != ret && cnt > 0)
|
|
ret = dequeueNeedDataCallback(ret, stmt);
|
|
return ret;
|
|
}
|
|
|
|
void cancelNeedDataState(StatementClass *stmt)
|
|
{
|
|
int cnt = stmt->num_callbacks, i;
|
|
|
|
stmt->num_callbacks = 0;
|
|
for (i = 0; i < cnt; i++)
|
|
{
|
|
if (stmt->callbacks[i].data)
|
|
free(stmt->callbacks[i].data);
|
|
stmt->callbacks[i].data = NULL;
|
|
}
|
|
SC_reset_delegate(SQL_ERROR, stmt);
|
|
}
|
|
|
|
void
|
|
SC_log_error(const char *func, const char *desc, const StatementClass *self)
|
|
{
|
|
const char *head;
|
|
#define NULLCHECK(a) (a ? a : "(NULL)")
|
|
if (self)
|
|
{
|
|
QResultClass *res = SC_get_Result(self);
|
|
const ARDFields *opts = SC_get_ARDF(self);
|
|
const APDFields *apdopts = SC_get_APDF(self);
|
|
SQLLEN rowsetSize;
|
|
const int level = 9;
|
|
|
|
rowsetSize = (STMT_TRANSITION_EXTENDED_FETCH == self->transition_status ? opts->size_of_rowset_odbc2 : opts->size_of_rowset);
|
|
if (SC_get_errornumber(self) <= 0)
|
|
head = "STATEMENT WARNING";
|
|
else
|
|
{
|
|
head = "STATEMENT ERROR";
|
|
QLOG(level, "%s: func=%s, desc='%s', errnum=%d, errmsg='%s'\n",head, func, desc, self->__error_number, NULLCHECK(self->__error_message));
|
|
}
|
|
MYLOG(0, "%s: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", head, func, desc, self->__error_number, NULLCHECK(self->__error_message));
|
|
if (SC_get_errornumber(self) > 0)
|
|
{
|
|
QLOG(level, " ------------------------------------------------------------\n");
|
|
QLOG(level, " hdbc=%p, stmt=%p, result=%p\n", self->hdbc, self, res);
|
|
QLOG(level, " prepare=%d, external=%d\n", self->prepare, self->external);
|
|
QLOG(level, " bindings=%p, bindings_allocated=%d\n", opts->bindings, opts->allocated);
|
|
QLOG(level, " parameters=%p, parameters_allocated=%d\n", apdopts->parameters, apdopts->allocated);
|
|
QLOG(level, " statement_type=%d, statement='%s'\n", self->statement_type, NULLCHECK(self->statement));
|
|
QLOG(level, " stmt_with_params='%s'\n", NULLCHECK(self->stmt_with_params));
|
|
QLOG(level, " data_at_exec=%d, current_exec_param=%d, put_data=%d\n", self->data_at_exec, self->current_exec_param, self->put_data);
|
|
QLOG(level, " currTuple=" FORMAT_LEN ", current_col=%d, lobj_fd=%d\n", self->currTuple, self->current_col, self->lobj_fd);
|
|
QLOG(level, " maxRows=" FORMAT_LEN ", rowset_size=" FORMAT_LEN ", keyset_size=" FORMAT_LEN ", cursor_type=%u, scroll_concurrency=%d\n", self->options.maxRows, rowsetSize, self->options.keyset_size, self->options.cursor_type, self->options.scroll_concurrency);
|
|
QLOG(level, " cursor_name='%s'\n", SC_cursor_name(self));
|
|
|
|
QLOG(level, " ----------------QResult Info -------------------------------\n");
|
|
|
|
if (res)
|
|
{
|
|
QLOG(level, " fields=%p, backend_tuples=%p, tupleField=%p, conn=%p\n", QR_get_fields(res), res->backend_tuples, res->tupleField, res->conn);
|
|
QLOG(level, " fetch_count=" FORMAT_LEN ", num_total_rows=" FORMAT_ULEN ", num_fields=%d, cursor='%s'\n", res->fetch_number, QR_get_num_total_tuples(res), res->num_fields, NULLCHECK(QR_get_cursor(res)));
|
|
QLOG(level, " message='%s', command='%s', notice='%s'\n", NULLCHECK(QR_get_message(res)), NULLCHECK(res->command), NULLCHECK(res->notice));
|
|
QLOG(level, " status=%d\n", QR_get_rstatus(res));
|
|
}
|
|
|
|
/* Log the connection error if there is one */
|
|
CC_log_error(func, desc, self->hdbc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MYLOG(0, "INVALID STATEMENT HANDLE ERROR: func=%s, desc='%s'\n", func, desc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Extended Query
|
|
*/
|
|
|
|
static BOOL
|
|
RequestStart(StatementClass *stmt, ConnectionClass *conn, const char *func)
|
|
{
|
|
BOOL ret = TRUE;
|
|
unsigned int svpopt = 0;
|
|
|
|
#ifdef _HANDLE_ENLIST_IN_DTC_
|
|
if (conn->asdum)
|
|
CALL_IsolateDtcConn(conn, TRUE);
|
|
#endif /* _HANDLE_ENLIST_IN_DTC_ */
|
|
if (NULL == conn->pqconn)
|
|
{
|
|
SC_set_error(stmt, STMT_COMMUNICATION_ERROR, "The connection has been lost", __FUNCTION__);
|
|
return SQL_ERROR;
|
|
}
|
|
if (CC_started_rbpoint(conn))
|
|
return TRUE;
|
|
if (SC_is_readonly(stmt))
|
|
svpopt |= SVPOPT_RDONLY;
|
|
if (SQL_ERROR == SetStatementSvp(stmt, svpopt))
|
|
{
|
|
char emsg[128];
|
|
|
|
SPRINTF_FIXED(emsg, "internal savepoint error in %s", func);
|
|
SC_set_error_if_not_set(stmt, STMT_INTERNAL_ERROR, emsg, func);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* In auto-commit mode, begin a new transaction implicitly if no
|
|
* transaction is in progress yet. However, some special statements like
|
|
* VACUUM and CLUSTER cannot be run in a transaction block.
|
|
*/
|
|
if (!CC_is_in_trans(conn) && CC_loves_visible_trans(conn) &&
|
|
stmt->statement_type != STMT_TYPE_SPECIAL)
|
|
{
|
|
ret = CC_begin(conn);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void log_params(int nParams, const Oid *paramTypes, const UCHAR * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat)
|
|
{
|
|
int i, j;
|
|
BOOL isBinary;
|
|
|
|
for (i = 0; i < nParams; i++)
|
|
{
|
|
isBinary = paramFormats ? paramFormats[i] : FALSE;
|
|
if (!paramValues[i])
|
|
QLOG(TUPLE_LOG_LEVEL, "\t%c (null) OID=%u\n", isBinary ? 'b' : 't', paramTypes ? paramTypes[i] : 0);
|
|
else if (isBinary)
|
|
{
|
|
QLOG(TUPLE_LOG_LEVEL, "\tb '");
|
|
for (j = 0; j < paramLengths[i]; j++)
|
|
QPRINTF(TUPLE_LOG_LEVEL, "%02x", paramValues[i][j]);
|
|
QPRINTF(TUPLE_LOG_LEVEL, " OID=%u\n", paramTypes ? paramTypes[i] : 0);
|
|
}
|
|
else
|
|
QLOG(TUPLE_LOG_LEVEL, "\tt '%s' OID=%u\n", paramValues[i], paramTypes ? paramTypes[i] : 0);
|
|
}
|
|
}
|
|
|
|
static QResultClass *
|
|
libpq_bind_and_exec(StatementClass *stmt)
|
|
{
|
|
CSTR func = "libpq_bind_and_exec";
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
int nParams;
|
|
Oid *paramTypes = NULL;
|
|
char **paramValues = NULL;
|
|
int *paramLengths = NULL;
|
|
int *paramFormats = NULL;
|
|
int resultFormat;
|
|
PGresult *pgres = NULL;
|
|
int pgresstatus;
|
|
QResultClass *newres = NULL;
|
|
QResultClass *res = NULL;
|
|
char *cmdtag;
|
|
char *rowcount;
|
|
|
|
/* It will only be used under Batch Protocol. */
|
|
int nBatchCount = 1;
|
|
|
|
if (!RequestStart(stmt, conn, func))
|
|
return NULL;
|
|
|
|
#ifdef NOT_USED
|
|
if (CC_is_in_trans(conn) && !CC_started_rbpoint(conn))
|
|
{
|
|
if (SQL_ERROR == SetStatementSvp(stmt, 0))
|
|
{
|
|
SC_set_error_if_not_set(stmt, STMT_INTERNAL_ERROR, "internal savepoint error in build_libpq_bind_params", func);
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif /* NOT_USED */
|
|
|
|
/* 1. Bind */
|
|
MYLOG(0, "bind stmt=%p\n", stmt);
|
|
if (SC_CanUseBatchProto(stmt))
|
|
{
|
|
if (!build_libpq_bind_params_batch(stmt,
|
|
&nParams,
|
|
&nBatchCount,
|
|
¶mTypes,
|
|
¶mValues,
|
|
¶mLengths, ¶mFormats,
|
|
&resultFormat))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!build_libpq_bind_params(stmt,
|
|
&nParams,
|
|
¶mTypes,
|
|
¶mValues,
|
|
¶mLengths, ¶mFormats,
|
|
&resultFormat))
|
|
{
|
|
if (SC_get_errornumber(stmt) <= 0)
|
|
SC_set_errornumber(stmt, STMT_NO_MEMORY_ERROR);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* 2. Execute */
|
|
MYLOG(0, "execute stmt=%p\n", stmt);
|
|
if (!SC_is_fetchcursor(stmt))
|
|
{
|
|
if (stmt->prepared == NOT_YET_PREPARED ||
|
|
(stmt->prepared == PREPARED_TEMPORARILY && conn->unnamed_prepared_stmt != stmt))
|
|
{
|
|
SC_set_error(stmt, STMT_EXEC_ERROR, "about to execute a non-prepared statement", func);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* 2.5 Prepare and Describe if needed */
|
|
if (stmt->prepared == PREPARING_TEMPORARILY ||
|
|
(stmt->prepared == PREPARED_TEMPORARILY && conn->unnamed_prepared_stmt != stmt))
|
|
{
|
|
ProcessedStmt *pstmt;
|
|
|
|
if (!stmt->processed_statements)
|
|
{
|
|
if (prepareParametersNoDesc(stmt, FALSE, EXEC_PARAM_CAST) == SQL_ERROR)
|
|
goto cleanup;
|
|
}
|
|
|
|
pstmt = stmt->processed_statements;
|
|
QLOG(0, "PQexecParams: %p '%s' nParams=%d\n", conn->pqconn, pstmt->query, nParams);
|
|
log_params(nParams, paramTypes, (const UCHAR * const *) paramValues, paramLengths, paramFormats, resultFormat);
|
|
if (SC_CanUseBatchProto(stmt))
|
|
{
|
|
pgres = PQexecParamsBatch(conn->pqconn,
|
|
pstmt->query,
|
|
nParams,
|
|
nBatchCount,
|
|
paramTypes,
|
|
(const char **) paramValues,
|
|
paramLengths,
|
|
paramFormats,
|
|
resultFormat);
|
|
}
|
|
else
|
|
{
|
|
pgres = PQexecParams(conn->pqconn,
|
|
pstmt->query,
|
|
nParams,
|
|
paramTypes,
|
|
(const char **) paramValues,
|
|
paramLengths,
|
|
paramFormats,
|
|
resultFormat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *plan_name;
|
|
|
|
if (stmt->prepared == PREPARING_PERMANENTLY)
|
|
{
|
|
if (prepareParameters(stmt, FALSE) == SQL_ERROR)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* prepareParameters() set plan name, so don't fetch this earlier */
|
|
plan_name = stmt->plan_name ? stmt->plan_name : NULL_STRING;
|
|
|
|
/* already prepared */
|
|
QLOG(0, "PQexecPrepared: %p plan=%s nParams=%d\n", conn->pqconn, plan_name, nParams);
|
|
log_params(nParams, paramTypes, (const UCHAR * const *) paramValues, paramLengths, paramFormats, resultFormat);
|
|
if (SC_CanUseBatchProto(stmt))
|
|
{
|
|
/* already prepared */
|
|
pgres = PQexecPreparedBatch(conn->pqconn,
|
|
plan_name, /* portal name == plan name */
|
|
nParams,
|
|
nBatchCount,
|
|
(const char **) paramValues, paramLengths, paramFormats,
|
|
resultFormat);
|
|
}
|
|
else
|
|
{
|
|
pgres = PQexecPrepared(conn->pqconn,
|
|
plan_name, /* portal name == plan name */
|
|
nParams,
|
|
(const char **) paramValues, paramLengths, paramFormats,
|
|
resultFormat);
|
|
}
|
|
}
|
|
if (stmt->curr_param_result)
|
|
{
|
|
for (res = SC_get_Result(stmt); NULL != res && NULL != res->next; res = res->next) ;
|
|
}
|
|
else
|
|
res = NULL;
|
|
|
|
if (!res)
|
|
{
|
|
newres = res = QR_Constructor();
|
|
if (!res)
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory while allocating result set", func);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* 3. Receive results */
|
|
MYLOG(DETAIL_LOG_LEVEL, "get_Result=%p %p %d\n", res, SC_get_Result(stmt), stmt->curr_param_result);
|
|
pgresstatus = PQresultStatus(pgres);
|
|
switch (pgresstatus)
|
|
{
|
|
case PGRES_COMMAND_OK:
|
|
/* portal query command, no tuples returned */
|
|
/* read in the return message from the backend */
|
|
cmdtag = PQcmdStatus(pgres);
|
|
QLOG(0, "\tok: - 'C' - %s\n", cmdtag);
|
|
QR_set_command(res, cmdtag);
|
|
if (QR_command_successful(res))
|
|
QR_set_rstatus(res, PORES_COMMAND_OK);
|
|
|
|
/* get rowcount */
|
|
rowcount = PQcmdTuples(pgres);
|
|
if (rowcount && rowcount[0])
|
|
res->recent_processed_row_count = atoi(rowcount);
|
|
else
|
|
res->recent_processed_row_count = -1;
|
|
|
|
/* Update the transaction status of connection. */
|
|
{
|
|
PGTransactionStatusType transStatus = PQtransactionStatus(conn->pqconn);
|
|
switch(transStatus)
|
|
{
|
|
case PQTRANS_INTRANS:
|
|
case PQTRANS_INERROR:
|
|
{
|
|
const char *trimedQuery = stmt->statement;
|
|
CC_set_in_trans(conn);
|
|
|
|
/* Trim blank prefix. */
|
|
while(*trimedQuery != '\0' && IS_BLANK(*trimedQuery))
|
|
trimedQuery ++;
|
|
|
|
if (strnicmp(trimedQuery, "begin", strlen("begin")) == 0)
|
|
{
|
|
CC_set_in_manual_trans(conn);
|
|
}
|
|
else if (strnicmp(trimedQuery, "start", strlen("start")) == 0 &&
|
|
IS_BLANK(*(trimedQuery + strlen("start"))))
|
|
{
|
|
trimedQuery += strlen("start");
|
|
while(*trimedQuery != '\0' && IS_BLANK(*trimedQuery))
|
|
trimedQuery ++;
|
|
if (strnicmp(trimedQuery, "transaction", strlen("transaction")) == 0)
|
|
CC_set_in_manual_trans(conn);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
CC_set_no_trans(conn);
|
|
CC_set_no_manual_trans(conn);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case PGRES_EMPTY_QUERY:
|
|
/* We return the empty query */
|
|
QR_set_rstatus(res, PORES_EMPTY_QUERY);
|
|
break;
|
|
case PGRES_NONFATAL_ERROR:
|
|
handle_pgres_error(conn, pgres, "libpq_bind_and_exec", res, FALSE);
|
|
break;
|
|
|
|
case PGRES_BAD_RESPONSE:
|
|
case PGRES_FATAL_ERROR:
|
|
handle_pgres_error(conn, pgres, "libpq_bind_and_exec", res, TRUE);
|
|
break;
|
|
case PGRES_TUPLES_OK:
|
|
if (!QR_from_PGresult(res, stmt, conn, NULL, &pgres))
|
|
goto cleanup;
|
|
if (res->rstatus == PORES_TUPLES_OK && res->notice)
|
|
QR_set_rstatus(res, PORES_NONFATAL_ERROR);
|
|
break;
|
|
case PGRES_COPY_OUT:
|
|
case PGRES_COPY_IN:
|
|
case PGRES_COPY_BOTH:
|
|
default:
|
|
/* skip the unexpected response if possible */
|
|
QR_set_rstatus(res, PORES_BAD_RESPONSE);
|
|
CC_set_error(conn, CONNECTION_BACKEND_CRAZY, "Unexpected protocol character from backend (send_query)", func);
|
|
CC_on_abort(conn, CONN_DEAD);
|
|
|
|
QLOG(0, "PQexecXxxx error: - (%d) - %s\n", pgresstatus, CC_get_errormsg(conn));
|
|
break;
|
|
}
|
|
|
|
if (res != newres && NULL != newres)
|
|
QR_Destructor(newres);
|
|
if (SC_CanUseBatchProto(stmt))
|
|
stmt->exec_end_row =
|
|
stmt->exec_current_row = SC_get_APDF(stmt)->paramset_size;
|
|
cleanup:
|
|
if (pgres)
|
|
PQclear(pgres);
|
|
if (paramValues)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nParams * nBatchCount; i++)
|
|
{
|
|
if (paramValues[i] != NULL)
|
|
free(paramValues[i]);
|
|
}
|
|
free(paramValues);
|
|
}
|
|
if (paramTypes)
|
|
free(paramTypes);
|
|
if (paramLengths)
|
|
free(paramLengths);
|
|
if (paramFormats)
|
|
free(paramFormats);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Parse a query using libpq.
|
|
*
|
|
* 'res' is only passed here for error reporting purposes. If an error is
|
|
* encountered, it is set in 'res', and the function returns FALSE.
|
|
*/
|
|
static BOOL
|
|
ParseWithLibpq(StatementClass *stmt, const char *plan_name,
|
|
const char *query,
|
|
Int2 num_params, const char *comment, QResultClass *res)
|
|
{
|
|
CSTR func = "ParseWithLibpq";
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
Int4 sta_pidx = -1, end_pidx = -1;
|
|
const char *cstatus;
|
|
Oid *paramTypes = NULL;
|
|
BOOL retval = FALSE;
|
|
PGresult *pgres = NULL;
|
|
|
|
MYLOG(0, "entering plan_name=%s query=%s\n", plan_name, query);
|
|
if (!RequestStart(stmt, conn, func))
|
|
return FALSE;
|
|
|
|
if (stmt->discard_output_params)
|
|
num_params = 0;
|
|
else if (num_params != 0)
|
|
{
|
|
#ifdef NOT_USED
|
|
sta_pidx += stmt->proc_return;
|
|
#endif /* NOT_USED */
|
|
int pidx;
|
|
|
|
sta_pidx = stmt->current_exec_param;
|
|
if (num_params < 0)
|
|
end_pidx = stmt->num_params - 1;
|
|
else
|
|
end_pidx = sta_pidx + num_params - 1;
|
|
#ifdef NOT_USED
|
|
num_params = end_pidx - sta_pidx + 1;
|
|
#endif /* NOT_USED */
|
|
for (num_params = 0, pidx = sta_pidx - 1;;)
|
|
{
|
|
SC_param_next(stmt, &pidx, NULL, NULL);
|
|
if (pidx > end_pidx)
|
|
break;
|
|
else if (pidx < end_pidx)
|
|
{
|
|
if (0 == num_params)
|
|
sta_pidx = pidx;
|
|
num_params++;
|
|
}
|
|
else
|
|
{
|
|
num_params++;
|
|
break;
|
|
}
|
|
}
|
|
MYLOG(0, "sta_pidx=%d end_pidx=%d num_p=%d\n", sta_pidx, end_pidx, num_params);
|
|
}
|
|
|
|
/*
|
|
* We let the server deduce the right datatype for the parameters, except
|
|
* for out parameters, which are sent as VOID.
|
|
*/
|
|
if (num_params > 0)
|
|
{
|
|
int i;
|
|
int j;
|
|
IPDFields *ipdopts = SC_get_IPDF(stmt);
|
|
|
|
paramTypes = malloc(sizeof(Oid) * num_params);
|
|
if (paramTypes == NULL)
|
|
{
|
|
SC_set_errornumber(stmt, STMT_NO_MEMORY_ERROR);
|
|
goto cleanup;
|
|
}
|
|
|
|
MYLOG(0, "ipdopts->allocated: %d\n", ipdopts->allocated);
|
|
j = 0;
|
|
for (i = sta_pidx; i <= end_pidx; i++)
|
|
{
|
|
if (i < ipdopts->allocated)
|
|
{
|
|
if (SQL_PARAM_OUTPUT == ipdopts->parameters[i].paramType)
|
|
paramTypes[j++] = PG_TYPE_VOID;
|
|
else
|
|
paramTypes[j++] = sqltype_to_bind_pgtype(conn,
|
|
ipdopts->parameters[i].SQLType);
|
|
}
|
|
else
|
|
{
|
|
/* Unknown type of parameter. Let the server decide */
|
|
paramTypes[j++] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (plan_name == NULL || plan_name[0] == '\0')
|
|
conn->unnamed_prepared_stmt = NULL;
|
|
|
|
/* Prepare */
|
|
QLOG(0, "PQprepare: %p '%s' plan=%s nParams=%d\n", conn->pqconn, query, plan_name, num_params);
|
|
pgres = PQprepare(conn->pqconn, plan_name, query, num_params, paramTypes);
|
|
if (PQresultStatus(pgres) != PGRES_COMMAND_OK)
|
|
{
|
|
handle_pgres_error(conn, pgres, "ParseWithlibpq", res, TRUE);
|
|
goto cleanup;
|
|
}
|
|
cstatus = PQcmdStatus(pgres);
|
|
QLOG(0, "\tok: - 'C' - %s\n", cstatus);
|
|
if (stmt->plan_name)
|
|
SC_set_prepared(stmt, PREPARED_PERMANENTLY);
|
|
else
|
|
SC_set_prepared(stmt, PREPARED_TEMPORARILY);
|
|
|
|
if (plan_name == NULL || plan_name[0] == '\0')
|
|
conn->unnamed_prepared_stmt = stmt;
|
|
|
|
retval = TRUE;
|
|
|
|
cleanup:
|
|
if (paramTypes)
|
|
free(paramTypes);
|
|
|
|
if (pgres)
|
|
PQclear(pgres);
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse and describe a query using libpq.
|
|
*
|
|
* Returns an empty result set that has the column information, or error code
|
|
* and message, filled in. If 'res' is not NULL, it is the result set
|
|
* returned, otherwise a new one is allocated.
|
|
*
|
|
* NB: The caller must set stmt->current_exec_param before calling this
|
|
* function!
|
|
*/
|
|
QResultClass *
|
|
ParseAndDescribeWithLibpq(StatementClass *stmt, const char *plan_name,
|
|
const char *query_param,
|
|
Int2 num_params, const char *comment,
|
|
QResultClass *res)
|
|
{
|
|
CSTR func = "ParseAndDescribeWithLibpq";
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
PGresult *pgres = NULL;
|
|
int num_p;
|
|
Int2 num_discard_params;
|
|
IPDFields *ipdopts;
|
|
int pidx;
|
|
int i;
|
|
Oid oid;
|
|
SQLSMALLINT paramType;
|
|
|
|
MYLOG(0, "entering plan_name=%s query=%s\n", plan_name, query_param);
|
|
if (!RequestStart(stmt, conn, func))
|
|
return NULL;
|
|
|
|
if (!res)
|
|
res = QR_Constructor();
|
|
if (!res)
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Couldn't allocate memory for query", func);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* We need to do Prepare + Describe as two different round-trips to the
|
|
* server, while before we switched to use libpq, we used to send a Parse
|
|
* and Describe message followed by a single Sync.
|
|
*/
|
|
if (!ParseWithLibpq(stmt, plan_name, query_param, num_params, comment, res))
|
|
goto cleanup;
|
|
|
|
/* Describe */
|
|
QLOG(0, "\tPQdescribePrepared: %p plan_name=%s\n", conn->pqconn, plan_name);
|
|
|
|
pgres = PQdescribePrepared(conn->pqconn, plan_name);
|
|
switch (PQresultStatus(pgres))
|
|
{
|
|
case PGRES_COMMAND_OK:
|
|
QLOG(0, "\tok: - 'C' - %s\n", PQcmdStatus(pgres));
|
|
/* expected */
|
|
break;
|
|
case PGRES_NONFATAL_ERROR:
|
|
handle_pgres_error(conn, pgres, "ParseAndDescribeWithLibpq", res, FALSE);
|
|
goto cleanup;
|
|
case PGRES_FATAL_ERROR:
|
|
handle_pgres_error(conn, pgres, "ParseAndDescribeWithLibpq", res, TRUE);
|
|
goto cleanup;
|
|
default:
|
|
/* skip the unexpected response if possible */
|
|
CC_set_error(conn, CONNECTION_BACKEND_CRAZY, "Unexpected result from PQdescribePrepared", func);
|
|
CC_on_abort(conn, CONN_DEAD);
|
|
|
|
MYLOG(0, "PQdescribePrepared: error - %s\n", CC_get_errormsg(conn));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Extract parameter information from the result set */
|
|
num_p = PQnparams(pgres);
|
|
MYLOG(DETAIL_LOG_LEVEL, "num_params=%d info=%d\n", stmt->num_params, num_p);
|
|
if (get_qlog() > 0 || get_mylog() > 0)
|
|
{
|
|
int i;
|
|
|
|
QLOG(0, "\tnParams=%d", num_p);
|
|
for (i = 0; i < num_p; i++)
|
|
QPRINTF(0, " %u", PQparamtype(pgres, i));
|
|
QPRINTF(0, "\n");
|
|
}
|
|
num_discard_params = 0;
|
|
if (stmt->discard_output_params)
|
|
CountParameters(stmt, NULL, NULL, &num_discard_params);
|
|
if (num_discard_params < stmt->proc_return)
|
|
num_discard_params = stmt->proc_return;
|
|
if (num_p + num_discard_params != (int) stmt->num_params)
|
|
{
|
|
MYLOG(0, "ParamInfo unmatch num_params(=%d) != info(=%d)+discard(=%d)\n", stmt->num_params, num_p, num_discard_params);
|
|
/* stmt->num_params = (Int2) num_p + num_discard_params; it's possible in case of multi command queries */
|
|
}
|
|
ipdopts = SC_get_IPDF(stmt);
|
|
extend_iparameter_bindings(ipdopts, stmt->num_params);
|
|
pidx = stmt->current_exec_param;
|
|
if (pidx >= 0)
|
|
pidx--;
|
|
for (i = 0; i < num_p; i++)
|
|
{
|
|
SC_param_next(stmt, &pidx, NULL, NULL);
|
|
if (pidx >= stmt->num_params)
|
|
{
|
|
MYLOG(0, "%dth parameter's position(%d) is out of bound[%d]\n", i, pidx, stmt->num_params);
|
|
break;
|
|
}
|
|
oid = PQparamtype(pgres, i);
|
|
paramType = ipdopts->parameters[pidx].paramType;
|
|
if (SQL_PARAM_OUTPUT != paramType ||
|
|
PG_TYPE_VOID != oid)
|
|
PIC_set_pgtype(ipdopts->parameters[pidx], oid);
|
|
}
|
|
|
|
/* Extract Portal information */
|
|
QR_set_conn(res, conn);
|
|
|
|
if (CI_read_fields_from_pgres(QR_get_fields(res), pgres))
|
|
{
|
|
Int2 dummy1, dummy2;
|
|
int cidx;
|
|
int num_io_params;
|
|
|
|
QR_set_rstatus(res, PORES_FIELDS_OK);
|
|
res->num_fields = CI_get_num_fields(QR_get_fields(res));
|
|
if (QR_haskeyset(res))
|
|
res->num_fields -= res->num_key_fields;
|
|
num_io_params = CountParameters(stmt, NULL, &dummy1, &dummy2);
|
|
if (stmt->proc_return > 0 ||
|
|
num_io_params > 0)
|
|
{
|
|
ipdopts = SC_get_IPDF(stmt);
|
|
extend_iparameter_bindings(ipdopts, stmt->num_params);
|
|
for (i = 0, cidx = 0; i < stmt->num_params; i++)
|
|
{
|
|
if (i < stmt->proc_return)
|
|
ipdopts->parameters[i].paramType = SQL_PARAM_OUTPUT;
|
|
paramType =ipdopts->parameters[i].paramType;
|
|
if (SQL_PARAM_OUTPUT == paramType ||
|
|
SQL_PARAM_INPUT_OUTPUT == paramType)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "!![%d].PGType %u->%u\n", i, PIC_get_pgtype(ipdopts->parameters[i]), CI_get_oid(QR_get_fields(res), cidx));
|
|
PIC_set_pgtype(ipdopts->parameters[i], CI_get_oid(QR_get_fields(res), cidx));
|
|
cidx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (NULL == QR_get_fields(res)->coli_array)
|
|
{
|
|
QR_set_rstatus(res, PORES_NO_MEMORY_ERROR);
|
|
QR_set_messageref(res, "Out of memory while reading field information");
|
|
}
|
|
else
|
|
{
|
|
QR_set_rstatus(res, PORES_BAD_RESPONSE);
|
|
QR_set_message(res, "Error reading field information");
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (pgres)
|
|
PQclear(pgres);
|
|
|
|
return res;
|
|
}
|
|
|
|
enum {
|
|
CancelRequestSet = 1L
|
|
,CancelRequestAccepted = (1L << 1)
|
|
,CancelCompleted = (1L << 2)
|
|
};
|
|
/* commonly used for short term lock */
|
|
#if defined(WIN_MULTITHREAD_SUPPORT)
|
|
extern CRITICAL_SECTION common_cs;
|
|
#elif defined(POSIX_MULTITHREAD_SUPPORT)
|
|
extern pthread_mutex_t common_cs;
|
|
#endif /* WIN_MULTITHREAD_SUPPORT */
|
|
BOOL SC_IsExecuting(const StatementClass *self)
|
|
{
|
|
BOOL ret;
|
|
ENTER_COMMON_CS; /* short time blocking */
|
|
ret = (STMT_EXECUTING == self->status);
|
|
LEAVE_COMMON_CS;
|
|
return ret;
|
|
}
|
|
BOOL SC_SetExecuting(StatementClass *self, BOOL on)
|
|
{
|
|
BOOL exeSet = FALSE;
|
|
ENTER_COMMON_CS; /* short time blocking */
|
|
if (on)
|
|
{
|
|
if (0 == (self->cancel_info & CancelRequestSet))
|
|
{
|
|
self->status = STMT_EXECUTING;
|
|
exeSet = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->cancel_info = 0;
|
|
self->status = STMT_FINISHED;
|
|
exeSet = TRUE;
|
|
}
|
|
LEAVE_COMMON_CS;
|
|
return exeSet;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
BOOL SC_SetCancelRequest(StatementClass *self)
|
|
{
|
|
BOOL enteredCS = FALSE;
|
|
|
|
ENTER_COMMON_CS;
|
|
if (0 != (self->cancel_info & CancelCompleted))
|
|
;
|
|
else if (STMT_EXECUTING == self->status)
|
|
{
|
|
self->cancel_info |= CancelRequestSet;
|
|
}
|
|
else
|
|
{
|
|
/* try to acquire */
|
|
if (TRY_ENTER_STMT_CS(self))
|
|
enteredCS = TRUE;
|
|
else
|
|
self->cancel_info |= CancelRequestSet;
|
|
}
|
|
LEAVE_COMMON_CS;
|
|
return enteredCS;
|
|
}
|
|
#endif /* NOT_USED */
|
|
|
|
BOOL SC_AcceptedCancelRequest(const StatementClass *self)
|
|
{
|
|
BOOL shouldCancel = FALSE;
|
|
ENTER_COMMON_CS;
|
|
if (0 != (self->cancel_info & (CancelRequestSet | CancelRequestAccepted | CancelCompleted)))
|
|
shouldCancel = TRUE;
|
|
LEAVE_COMMON_CS;
|
|
return shouldCancel;
|
|
}
|
|
|
|
static void
|
|
SC_set_error_if_not_set(StatementClass *self, int errornumber, const char *errmsg, const char *func)
|
|
{
|
|
int errnum = SC_get_errornumber(self);
|
|
|
|
if (errnum <= 0)
|
|
{
|
|
const char *emsg = SC_get_errormsg(self);
|
|
|
|
if (emsg && 0 == errnum)
|
|
SC_set_errornumber(self, errornumber);
|
|
else
|
|
SC_set_error(self, errornumber, errmsg, func);
|
|
}
|
|
}
|
|
|
|
static void
|
|
SC_set_errorinfo(StatementClass *self, QResultClass *res, int errkind)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(self);
|
|
|
|
if (CC_not_connected(conn))
|
|
{
|
|
SC_set_error_if_not_set(self, STMT_COMMUNICATION_ERROR, "The connection has been lost", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
switch (QR_get_rstatus(res))
|
|
{
|
|
case PORES_NO_MEMORY_ERROR:
|
|
SC_set_error_if_not_set(self, STMT_NO_MEMORY_ERROR, "memory allocation error???", __FUNCTION__);
|
|
break;
|
|
case PORES_BAD_RESPONSE:
|
|
SC_set_error_if_not_set(self, STMT_COMMUNICATION_ERROR, "communication error occured", __FUNCTION__);
|
|
break;
|
|
case PORES_INTERNAL_ERROR:
|
|
SC_set_error_if_not_set(self, STMT_INTERNAL_ERROR, "Internal error fetching next row", __FUNCTION__);
|
|
break;
|
|
default:
|
|
switch (errkind)
|
|
{
|
|
case 1:
|
|
SC_set_error_if_not_set(self, STMT_EXEC_ERROR, "Error while fetching the next result", __FUNCTION__);
|
|
break;
|
|
default:
|
|
SC_set_error_if_not_set(self, STMT_EXEC_ERROR, "Error while executing the query", __FUNCTION__);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Before 9.6, the driver offered very simple bookmark support -- it is just
|
|
* the current row number.
|
|
* Now the driver offers more verbose bookmarks which contain KeySet informations
|
|
* (CTID (+ OID)). Though they consume 12bytes(row number + CTID) or 16bytes
|
|
* (row number + CTID + OID), they are useful in declare/fetch mode.
|
|
*/
|
|
|
|
PG_BM SC_Resolve_bookmark(const ARDFields *opts, Int4 idx)
|
|
{
|
|
BindInfoClass *bookmark;
|
|
SQLLEN *used;
|
|
SQLULEN offset;
|
|
SQLUINTEGER bind_size;
|
|
size_t cpylen = sizeof(Int4);
|
|
PG_BM pg_bm;
|
|
|
|
bookmark = opts->bookmark;
|
|
offset = opts->row_offset_ptr ? *(opts->row_offset_ptr) : 0;
|
|
bind_size = opts->bind_size;
|
|
memset(&pg_bm, 0, sizeof(pg_bm));
|
|
if (used = bookmark->used, used != NULL)
|
|
{
|
|
used = LENADDR_SHIFT(used, offset);
|
|
if (bind_size > 0)
|
|
used = LENADDR_SHIFT(used, idx * bind_size);
|
|
else
|
|
used = LENADDR_SHIFT(used, idx * sizeof(SQLLEN));
|
|
if (*used >= sizeof(pg_bm))
|
|
cpylen = sizeof(pg_bm);
|
|
else if (*used >= 12)
|
|
cpylen = 12;
|
|
MYLOG(0, "used=" FORMAT_LEN " cpylen=" FORMAT_SIZE_T "\n", *used, cpylen);
|
|
}
|
|
memcpy(&pg_bm, CALC_BOOKMARK_ADDR(bookmark, offset, bind_size, idx), cpylen);
|
|
MYLOG(0, "index=%d block=%d off=%d\n", pg_bm.index, pg_bm.keys.blocknum, pg_bm.keys.offset);
|
|
pg_bm.index = SC_resolve_int4_bookmark(pg_bm.index);
|
|
|
|
return pg_bm;
|
|
}
|
|
|
|
int SC_Create_bookmark(StatementClass *self, BindInfoClass *bookmark, Int4 bind_row, Int4 currTuple, const KeySet *keyset)
|
|
{
|
|
ARDFields *opts = SC_get_ARDF(self);
|
|
SQLUINTEGER bind_size = opts->bind_size;
|
|
SQLULEN offset = opts->row_offset_ptr ? *opts->row_offset_ptr : 0;
|
|
size_t cvtlen = sizeof(Int4);
|
|
PG_BM pg_bm;
|
|
|
|
MYLOG(0, "entering type=%d buflen=" FORMAT_LEN " buf=%p\n", bookmark->returntype, bookmark->buflen, bookmark->buffer);
|
|
memset(&pg_bm, 0, sizeof(pg_bm));
|
|
if (SQL_C_BOOKMARK == bookmark->returntype)
|
|
;
|
|
else if (bookmark->buflen >= sizeof(pg_bm))
|
|
cvtlen = sizeof(pg_bm);
|
|
else if (bookmark->buflen >= 12)
|
|
cvtlen = 12;
|
|
pg_bm.index = SC_make_int4_bookmark(currTuple);
|
|
if (keyset)
|
|
pg_bm.keys = *keyset;
|
|
memcpy(CALC_BOOKMARK_ADDR(bookmark, offset, bind_size, bind_row), &pg_bm, cvtlen);
|
|
if (bookmark->used)
|
|
{
|
|
SQLLEN *used = LENADDR_SHIFT(bookmark->used, offset);
|
|
|
|
if (bind_size > 0)
|
|
used = LENADDR_SHIFT(used, bind_row * bind_size);
|
|
else
|
|
used = LENADDR_SHIFT(used, bind_row * sizeof(SQLLEN));
|
|
*used = cvtlen;
|
|
}
|
|
MYLOG(0, "leaving cvtlen=" FORMAT_SIZE_T " ix(bl,of)=%d(%d,%d)\n", cvtlen, pg_bm.index, pg_bm.keys.blocknum, pg_bm.keys.offset);
|
|
|
|
return COPY_OK;
|
|
}
|
|
|
|
|
|
/* For batch execution:
|
|
* - binded parameter sets count should be greater than 1.
|
|
* - binded parameter sets should not be all ignored.
|
|
* - INSERT/UPDATE/DELETE commands enabled.
|
|
*/
|
|
BOOL SC_CanUseBatchProto(const StatementClass *self)
|
|
{
|
|
Int2 st = STMT_TYPE_UNKNOWN;
|
|
APDFields *apdopts = NULL;
|
|
int usefulParamsCount = 0;
|
|
int i = 0;
|
|
|
|
/* Trigger for protocol is off. */
|
|
if (NULL == self->hdbc ||
|
|
/* Only exten protocol can trigger batch operations on. */
|
|
0 == self->hdbc->connInfo.use_server_side_prepare ||
|
|
0 == self->hdbc->connInfo.use_batch_protocol ||
|
|
1 != self->hdbc->connInfo.backend_support_batch_proto)
|
|
return FALSE;
|
|
|
|
apdopts = SC_get_APDF(self);
|
|
if (apdopts->paramset_size <= 1)
|
|
return FALSE;
|
|
|
|
usefulParamsCount = apdopts->paramset_size;
|
|
|
|
for (i = 0; i < apdopts->paramset_size; i++)
|
|
{
|
|
if (apdopts->param_operation_ptr &&
|
|
apdopts->param_operation_ptr[i] == SQL_PARAM_IGNORE)
|
|
usefulParamsCount --;
|
|
}
|
|
|
|
/* Only one(or zero?) parameter set not ignored by user? */
|
|
if (usefulParamsCount <= 1)
|
|
return FALSE;
|
|
|
|
if (self->statement &&
|
|
st == STMT_TYPE_UNKNOWN)
|
|
st = statement_type(self->statement);
|
|
|
|
if (st == STMT_TYPE_INSERT ||
|
|
st == STMT_TYPE_UPDATE ||
|
|
st == STMT_TYPE_DELETE)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|