/*------ * 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 #include #include #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) ? "" : 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; }