driver/my_stmt.cc (506 lines of code) (raw):

// Copyright (c) 2012, 2024, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is designed to work with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, as // designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an additional // permission to link the program and your derivative works with the // separately licensed software that they have either included with // the program or referenced in the documentation. // // Without limiting anything contained in the foregoing, this file, // which is part of Connector/ODBC, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // https://oss.oracle.com/licenses/universal-foss-exception. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA /** @file my_stmt.c @brief Some "methods" for STMT "class" - functions that dispatch a call to either prepared statement version or to regular statement version(i.e. using mysql_stmt_* of mysql_* functions of API, depending of that has been used in that STMT. also contains "mysql_*" versions of "methods". */ #include "driver.h" BOOL ssps_used(STMT *stmt) { return (stmt->ssps != NULL); } /* Errors processing? */ BOOL returned_result(STMT *stmt) { if (ssps_used(stmt)) { /* Basically at this point we are supposed to get result already */ MYSQL_RES *temp_res= NULL; if ((stmt->result != NULL) || (temp_res= mysql_stmt_result_metadata(stmt->ssps)) != NULL) { /* mysql_free_result checks for NULL, so we can always call it */ mysql_free_result(temp_res); return TRUE; } return FALSE; } else { return mysql_field_count(stmt->dbc->mysql) > 0 ; } } my_bool free_current_result(STMT *stmt) { my_bool res= 0; if (stmt->result) { if (ssps_used(stmt)) { free_result_bind(stmt); res= mysql_stmt_free_result(stmt->ssps); } /* We need to always free stmt->result because SSPS keep metadata there */ stmt_result_free(stmt); stmt->result= NULL; } return res; } /* Name may be misleading, the idea is stmt - for directly executed statements, i.e using mysql_* part of api, ssps - prepared on server, using mysql_stmt */ static MYSQL_RES * stmt_get_result(STMT *stmt, BOOL force_use) { /* We can't use USE_RESULT because SQLRowCount will fail in this case! */ if (if_forward_cache(stmt) || force_use) { return mysql_use_result(stmt->dbc->mysql); } else { return mysql_store_result(stmt->dbc->mysql); } } /* For text protocol this get result itself as well. Besides for text protocol we need to use/store each resultset of multiple resultsets */ MYSQL_RES * get_result_metadata(STMT *stmt, BOOL force_use) { /* just a precaution, mysql_free_result checks for NULL anywat */ mysql_free_result(stmt->result); if (ssps_used(stmt)) { stmt->result= mysql_stmt_result_metadata(stmt->ssps); } else { stmt->result= stmt_get_result(stmt, force_use); } return stmt->result; } int bind_result(STMT *stmt) { if (ssps_used(stmt)) { return stmt->ssps_bind_result(); } return 0; } int get_result(STMT *stmt) { if (ssps_used(stmt)) { return ssps_get_result(stmt); } /* Nothing to do here for text protocol */ return 0; } size_t STMT::field_count() { if (ssps) { return mysql_stmt_field_count(ssps); } else { return result && result->field_count > 0 ? result->field_count : mysql_field_count(dbc->mysql); } } my_ulonglong affected_rows(STMT *stmt) { if (ssps_used(stmt)) { return mysql_stmt_affected_rows(stmt->ssps); } else { /* In some cases in c/odbc it cannot be used instead of mysql_num_rows */ return mysql_affected_rows(stmt->dbc->mysql); } } my_ulonglong update_affected_rows(STMT *stmt) { my_ulonglong last_affected; last_affected= affected_rows(stmt); stmt->affected_rows+= last_affected; return last_affected; } my_ulonglong num_rows(STMT *stmt) { my_ulonglong offset= scroller_exists(stmt) && stmt->scroller.next_offset > 0 ? stmt->scroller.next_offset - stmt->scroller.row_count : 0; if (ssps_used(stmt)) { return offset + mysql_stmt_num_rows(stmt->ssps); } else { return offset + mysql_num_rows(stmt->result); } } MYSQL_ROW STMT::fetch_row(bool read_unbuffered) { if (ssps) { if (ssps_bind_result()) { return nullptr; } int err = 0; if (read_unbuffered || m_row_storage.eof()) { /* Reading results from network */ err = mysql_stmt_fetch(ssps); } else { /* Row is already buffered in row storage, use the row storage */ m_row_storage.fill_data(result_bind); } switch (err) { case 1: set_error("HY000", mysql_stmt_error(ssps), mysql_stmt_errno(ssps)); throw error; case MYSQL_NO_DATA: return nullptr; } if (fix_fields) return fix_fields(this, nullptr); // it returns stmt->array return array; } else { return mysql_fetch_row(result); } } unsigned long* fetch_lengths(STMT *stmt) { if (ssps_used(stmt)) { return stmt->result_bind[0].length; } else { return mysql_fetch_lengths(stmt->result); } } MYSQL_ROW_OFFSET row_seek(STMT *stmt, MYSQL_ROW_OFFSET offset) { if (ssps_used(stmt)) { return mysql_stmt_row_seek(stmt->ssps, offset); } else { return mysql_row_seek(stmt->result, offset); } } void data_seek(STMT *stmt, my_ulonglong offset) { if (ssps_used(stmt)) { mysql_stmt_data_seek(stmt->ssps, offset); } else { mysql_data_seek(stmt->result, offset); } } MYSQL_ROW_OFFSET row_tell(STMT *stmt) { if (ssps_used(stmt)) { return mysql_stmt_row_tell(stmt->ssps); } else { return mysql_row_tell(stmt->result); } } int next_result(STMT *stmt) { free_current_result(stmt); if (ssps_used(stmt)) { return mysql_stmt_next_result(stmt->ssps); } else { return mysql_next_result(stmt->dbc->mysql); } } /* --- Data conversion methods --- */ int get_int(STMT *stmt, ulong column_number, char *value, ulong length) { if (ssps_used(stmt)) { return (int)ssps_get_int64<long long>(stmt, column_number, value, length); } else { return (int)strtol(value, NULL, 10); } } unsigned int get_uint(STMT* stmt, ulong column_number, char* value, ulong length) { if (ssps_used(stmt)) { return (int)ssps_get_int64<unsigned long long>(stmt, column_number, value, length); } else { return (unsigned int)strtoul(value, NULL, 10); } } long long get_int64(STMT *stmt, ulong column_number, char *value, ulong length) { if (ssps_used(stmt)) { return ssps_get_int64<long long>(stmt, column_number, value, length); } else { return strtoll(value, NULL, 10); } } unsigned long long get_uint64(STMT* stmt, ulong column_number, char* value, ulong length) { if (ssps_used(stmt)) { return ssps_get_int64<unsigned long long>(stmt, column_number, value, length); } else { return strtoull(value, NULL, 10); } } char * get_string(STMT *stmt, ulong column_number, char *value, ulong *length, char * buffer) { if (ssps_used(stmt)) { return ssps_get_string(stmt, column_number, value, length, buffer); } else { return value; } } double get_double(STMT *stmt, ulong column_number, char *value, ulong length) { if (ssps_used(stmt)) { return ssps_get_double(stmt, column_number, value, length); } else { return myodbc_strtod(value, length); } } BOOL is_null(STMT *stmt, ulong column_number, char *value) { if (ssps_used(stmt)) { return *stmt->result_bind[column_number].is_null; } else { return value == NULL; } } /* Prepares statement depending on connection option either on a client or on a server. Returns SQLRETURN result code since preparing on client or server can produce errors, memory allocation to name one. */ SQLRETURN prepare(STMT *stmt, char * query, SQLINTEGER query_length, bool reset_sql_limit, bool force_prepare) { assert(stmt); /* TODO: I guess we always have to have query length here */ if (query_length <= 0) { query_length = query ? (SQLINTEGER)strlen(query) : 0; } stmt->query.reset(query, query + query_length, stmt->dbc->cxn_charset_info); /* Tokenising string, detecting and storing parameters placeholders, removing {} So far the only possible error is memory allocation. Thus setting it here. If that changes we will need to make "parse" to set error and return rc */ if (parse(&stmt->query)) { return stmt->set_error( MYERR_S1001, NULL, 4001); } ssps_close(stmt); stmt->param_count = (uint)PARAM_COUNT(stmt->query); /* Trusting our parsing we are not using prepared statments unsless there are actually parameter markers in it */ if (!stmt->dbc->ds.opt_NO_SSPS && (PARAM_COUNT(stmt->query) || force_prepare) && !IS_BATCH(&stmt->query) && stmt->query.preparable_on_server(stmt->dbc->mysql->server_version)) { MYLOG_QUERY(stmt, "Using prepared statement"); ssps_init(stmt); /* If the query is in the form of "WHERE CURRENT OF" - we do not need to prepare it at the moment */ if (!stmt->query.get_cursor_name()) { LOCK_DBC(stmt->dbc); if (reset_sql_limit) set_sql_select_limit(stmt->dbc, 0, false); // After the parse and removal of curly brackets from the query // the result string for prepare is inside stmt->query. int prep_res = mysql_stmt_prepare(stmt->ssps, stmt->query.query, (unsigned long)stmt->query.length()); if (prep_res) { MYLOG_QUERY(stmt, mysql_error(stmt->dbc->mysql)); stmt->set_error("HY000"); translate_error((char*)stmt->error.sqlstate.c_str(), MYERR_S1000, mysql_errno(stmt->dbc->mysql)); return SQL_ERROR; } stmt->param_count= mysql_stmt_param_count(stmt->ssps); /* make sure we free the result from the previous time */ if (stmt->result) { mysql_free_result(stmt->result); stmt->result = NULL; } /* Getting result metadata */ stmt->fake_result = false; // reset in case it was set before if ((stmt->result= mysql_stmt_result_metadata(stmt->ssps))) { /*stmt->state= ST_SS_PREPARED;*/ fix_result_types(stmt); /*Should we reset stmt->result?*/ } } } { /* Creating desc records for each parameter */ uint i; for (i= 0; i < stmt->param_count; ++i) { DESCREC *aprec= desc_get_rec(stmt->apd, i, TRUE); DESCREC *iprec= desc_get_rec(stmt->ipd, i, TRUE); } } /* Reset current_param so that SQLParamData starts fresh. */ stmt->current_param= 0; stmt->state= ST_PREPARED; return SQL_SUCCESS; } SQLRETURN send_long_data (STMT *stmt, unsigned int param_num, DESCREC * aprec, const char *chunk, unsigned long length) { #ifdef WE_CAN_SEND_LONG_DATA_PROPERLY if (ssps_used(stmt)) { /* If we haven't already started to do that on client and parameter is binary */ if (aprec->par.value == NULL && aprec->concise_type == SQL_C_BINARY) { SQLRETURN result= ssps_send_long_data(stmt, param_num, chunk, length); /* A bit ugly */ if (result == SQL_SUCCESS_WITH_INFO) { return append2param_value(stmt, aprec, chunk, length); } return result; } else { return append2param_value(stmt, aprec, chunk, length); } } else #endif { aprec->par.add_param_data(chunk, length); return SQL_SUCCESS; } } /*------------------- Scrolled cursor related stuff -------------------*/ /* @param[in] selected - prefetch value in datatsource selected by user @param[in] app_fetchs- how many rows app fetchs at a time, i.e. stmt->ard->array_size @param[in] max_rows - limit for maximal number of rows to fetch */ unsigned int calc_prefetch_number(unsigned int selected, SQLULEN app_fetchs, SQLULEN max_rows) { unsigned int result= selected; if (selected == 0) { return 0; } if (app_fetchs > 1) { if (app_fetchs > selected) { result = (unsigned int)app_fetchs; } if (selected % app_fetchs > 0) { result = (unsigned int)(app_fetchs * (selected/app_fetchs + 1)); } } if (max_rows > 0 && max_rows < result) { return (unsigned int)max_rows; } return result; } BOOL scroller_exists(STMT * stmt) { return stmt->scroller.offset_pos > stmt->scroller.query; } /* Initialization of a scroller */ void scroller_create(STMT * stmt, const char *query, SQLULEN query_len) { /* MAX32_BUFF_SIZE includes place for terminating null, which we do not need and will use for comma */ const size_t len2add = 7/*" LIMIT "*/ + MAX64_BUFF_SIZE/*offset*/ /*- 1*/ + MAX32_BUFF_SIZE; MY_LIMIT_CLAUSE limit = find_position4limit(stmt->dbc->cxn_charset_info, query, query + query_len); stmt->scroller.start_offset= limit.offset; stmt->scroller.total_rows= myodbc_max(stmt->stmt_options.max_rows, 0); if (limit.begin != limit.end) { // This has to be recalculated only if limit is specified stmt->scroller.total_rows= stmt->scroller.total_rows > 0 ? myodbc_min(limit.row_count, stmt->scroller.total_rows) : limit.row_count; } if ((stmt->scroller.row_count > stmt->scroller.total_rows) && (limit.begin != limit.end)) // Only set row cound if LIMIT exists in the original query stmt->scroller.row_count = (unsigned int)stmt->scroller.total_rows; stmt->scroller.next_offset= myodbc_max(limit.offset, 0); stmt->scroller.query_len= query_len + len2add; stmt->scroller.extend_buf((size_t)stmt->scroller.query_len + 1); memset(stmt->scroller.query, ' ', (size_t)stmt->scroller.query_len); memcpy(stmt->scroller.query, query, limit.begin - query); /* Forgive me - now limit.begin points to beginning of limit in scroller's copy of the query */ char *limptr = stmt->scroller.query + (limit.begin - query); limit.begin = limptr; strncpy(limptr, " LIMIT ", 7); /* That is where we will update offset */ stmt->scroller.offset_pos = limptr + 7; /* putting row count in place. normally should not change or only once */ myodbc_snprintf(stmt->scroller.offset_pos + MAX64_BUFF_SIZE - 1, MAX32_BUFF_SIZE + 1, ",%*u", MAX32_BUFF_SIZE-1, stmt->scroller.row_count); /* cpy'ing end of query from original query - not sure if we will allow to have one */ memcpy(stmt->scroller.offset_pos + MAX64_BUFF_SIZE + MAX32_BUFF_SIZE - 1, limit.end, query + query_len - limit.end); *(stmt->scroller.query + stmt->scroller.query_len)= '\0'; } /* Returns next offset/maxrow for current fetch*/ unsigned long long scroller_move(STMT * stmt) { myodbc_snprintf(stmt->scroller.offset_pos, MAX64_BUFF_SIZE, "%*llu", MAX64_BUFF_SIZE - 1, stmt->scroller.next_offset); stmt->scroller.offset_pos[MAX64_BUFF_SIZE - 1]=','; stmt->scroller.next_offset+= stmt->scroller.row_count; return stmt->scroller.next_offset; } SQLRETURN scroller_prefetch(STMT * stmt) { assert(stmt); if (stmt->scroller.total_rows > 0 && stmt->scroller.next_offset >= (stmt->scroller.total_rows + stmt->scroller.start_offset)) { /* (stmt->scroller.next_offset - stmt->scroller.row_count) - current offset, 0 minimum. scroller initialization makes impossible row_count to be > stmt's max_rows */ long long count= stmt->scroller.total_rows - (stmt->scroller.next_offset - stmt->scroller.row_count - stmt->scroller.start_offset); if (count > 0) { myodbc_snprintf(stmt->scroller.offset_pos + MAX64_BUFF_SIZE, MAX32_BUFF_SIZE, "%*u", MAX32_BUFF_SIZE - 1, (unsigned long)count); stmt->scroller.offset_pos[MAX64_BUFF_SIZE + MAX32_BUFF_SIZE - 1] = ' '; } else { return SQL_NO_DATA; } } MYLOG_QUERY(stmt, stmt->scroller.query); LOCK_DBC(stmt->dbc); if (exec_stmt_query(stmt, stmt->scroller.query, (unsigned long)stmt->scroller.query_len, FALSE)) { return SQL_ERROR; } get_result_metadata(stmt, FALSE); return SQL_SUCCESS; } bool scrollable(STMT * stmt, const char * query, const char * query_end) { if (!stmt->query.is_select_statement()) { return FALSE; } /* FOR UPDATE*/ { const char *before_token= query_end; const char *last= mystr_get_prev_token(stmt->dbc->cxn_charset_info, &before_token, query); const char *prev= mystr_get_prev_token(stmt->dbc->cxn_charset_info, &before_token, query); /* we have to tokens - nothing to do*/ if (prev == query) { return FALSE; } before_token= prev - 1; /* FROM can be only token before a last one at most no need to scroll if there is no FROM clause */ if ( myodbc_casecmp(prev,"FROM", 4) && !find_token(stmt->dbc->cxn_charset_info, query, before_token, "FROM")) { return FALSE; } } return TRUE; }