driver/prepare.cc (231 lines of code) (raw):
// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Copyright (c) 2000, 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 prepare.c
@brief Prepared statement functions.
*/
/***************************************************************************
* The following ODBC APIs are implemented in this file: *
* *
* SQLPrepare (ISO 92) *
* SQLBindParameter (ODBC) *
* SQLDescribeParam (ODBC) *
* SQLParamOptions (ODBC, Deprecated) *
* SQLNumParams (ISO 92) *
* SQLSetScrollOptions (ODBC, Deprecated) *
* *
****************************************************************************/
#include "driver.h"
#ifndef _UNIX_
# include <dos.h>
#endif /* !_UNIX_ */
/**
Prepare a statement for later execution.
@param[in] hStmt Handle of the statement
@param[in] query The statement to prepare (in connection character set)
@param[in] len The length of the statement (or @c SQL_NTS if it is
NUL-terminated)
@param[in] dupe Set to @c TRUE if query is already a duplicate, and
freeing the value is now up to the driver
*/
SQLRETURN SQL_API MySQLPrepare(SQLHSTMT hstmt, SQLCHAR *query, SQLINTEGER len,
bool reset_select_limit, bool force_prepare)
{
STMT *stmt= (STMT *)hstmt;
/*
We free orig_query here, instead of my_SQLPrepare, because
my_SQLPrepare is used by my_pos_update() when a statement requires
additional parameters.
*/
if (GET_QUERY(&stmt->orig_query) != NULL)
{
stmt->orig_query.reset(NULL, NULL, NULL);
}
return my_SQLPrepare(hstmt, query, len, reset_select_limit,
force_prepare);
}
/*
@type : myodbc3 internal
@purpose : prepares an SQL string for execution
*/
SQLRETURN my_SQLPrepare(SQLHSTMT hstmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr,
bool reset_select_limit, bool force_prepare)
{
STMT *stmt= (STMT *) hstmt;
CLEAR_STMT_ERROR(stmt);
stmt->query.reset(NULL, NULL, NULL);
stmt->telemetry.span_start(stmt, "SQL prepare");
auto res = prepare(stmt, (char*)szSqlStr, cbSqlStr, reset_select_limit,
force_prepare);
if (!SQL_SUCCEEDED(res))
{
stmt->telemetry.set_error(stmt, stmt->error);
}
else
{
stmt->telemetry.span_end(stmt);
}
return res;
}
/*
@type : myodbc3 internal
@purpose : binds a buffer to a parameter marker in an SQL statement.
*/
SQLRETURN SQL_API my_SQLBindParameter( SQLHSTMT StatementHandle,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr )
{
STMT *stmt= (STMT *)StatementHandle;
DESCREC *aprec= desc_get_rec(stmt->apd, ParameterNumber - 1, TRUE);
DESCREC *iprec= desc_get_rec(stmt->ipd, ParameterNumber - 1, TRUE);
SQLRETURN rc;
/* TODO if this function fails, the SQL_DESC_COUNT should be unchanged in apd, ipd */
CLEAR_STMT_ERROR(stmt);
if (ParameterNumber < 1)
{
stmt->set_error(MYERR_S1093,NULL,0);
return SQL_ERROR;
}
aprec->par.reset();
/* reset all param fields */
aprec->reset_to_defaults();
iprec->reset_to_defaults();
/* first, set apd fields */
if (ValueType == SQL_C_DEFAULT)
{
ValueType= default_c_type(ParameterType);
/*
Access treats BIGINT as a string on linked tables.
The value is read correctly, but bound as a string.
*/
if (ParameterType == SQL_BIGINT && stmt->dbc->ds->opt_DFLT_BIGINT_BIND_STR)
ValueType= SQL_C_CHAR;
}
if (!SQL_SUCCEEDED(rc = stmt_SQLSetDescField(stmt, stmt->apd,
ParameterNumber,
SQL_DESC_CONCISE_TYPE,
(SQLPOINTER)(SQLLEN)ValueType,
SQL_IS_SMALLINT)))
return rc;
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->apd, ParameterNumber,
SQL_DESC_OCTET_LENGTH,
(SQLPOINTER)BufferLength,
SQL_IS_INTEGER)))
return rc;
/* these three *must* be the last APD params bound */
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->apd, ParameterNumber,
SQL_DESC_DATA_PTR,
ParameterValuePtr, SQL_IS_POINTER)))
return rc;
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->apd, ParameterNumber,
SQL_DESC_OCTET_LENGTH_PTR,
StrLen_or_IndPtr, SQL_IS_POINTER)))
return rc;
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->apd, ParameterNumber,
SQL_DESC_INDICATOR_PTR,
StrLen_or_IndPtr, SQL_IS_POINTER)))
return rc;
/* now the ipd fields */
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->ipd,
ParameterNumber,
SQL_DESC_CONCISE_TYPE,
(SQLPOINTER)(size_t)ParameterType,
SQL_IS_SMALLINT)))
return rc;
if (!SQL_SUCCEEDED(rc= stmt_SQLSetDescField(stmt, stmt->ipd,
ParameterNumber,
SQL_DESC_PARAMETER_TYPE,
(SQLPOINTER)(size_t)InputOutputType,
SQL_IS_SMALLINT)))
return rc;
/* set fields from ColumnSize and DecimalDigits */
switch (ParameterType)
{
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
case SQL_INTERVAL_SECOND:
case SQL_INTERVAL_DAY_TO_SECOND:
case SQL_INTERVAL_HOUR_TO_SECOND:
case SQL_INTERVAL_MINUTE_TO_SECOND:
rc= stmt_SQLSetDescField(stmt, stmt->ipd, ParameterNumber,
SQL_DESC_PRECISION,
(SQLPOINTER)(size_t)DecimalDigits,
SQL_IS_SMALLINT);
break;
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
rc= stmt_SQLSetDescField(stmt, stmt->ipd, ParameterNumber,
SQL_DESC_LENGTH, (SQLPOINTER)ColumnSize,
SQL_IS_ULEN);
break;
case SQL_NUMERIC:
case SQL_DECIMAL:
rc= stmt_SQLSetDescField(stmt, stmt->ipd, ParameterNumber,
SQL_DESC_SCALE,
(SQLPOINTER)(size_t)DecimalDigits,
SQL_IS_SMALLINT);
if (!SQL_SUCCEEDED(rc))
return rc;
/* fall through */
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
rc= stmt_SQLSetDescField(stmt, stmt->ipd, ParameterNumber,
SQL_DESC_PRECISION,
(SQLPOINTER)ColumnSize,
SQL_IS_ULEN);
break;
default:
rc= SQL_SUCCESS;
}
if (!SQL_SUCCEEDED(rc))
return rc;
aprec->par.real_param_done= TRUE;
return SQL_SUCCESS;
}
/**
Deprecated function, for more details see SQLBindParamater.
@param[in] stmt Handle to statement
@param[in] ipar Parameter number
@param[in] fCType Value type
@param[in] fSqlType Parameter type
@param[in] cbColDef Column size
@param[in] ibScale Decimal digits
@param[in] rgbValue Parameter value pointer
@param[in] pcbValue String length or index pointer
@return SQL_SUCCESS or SQL_ERROR (and diag is set)
*/
SQLRETURN SQL_API SQLSetParam(SQLHSTMT hstmt,
SQLUSMALLINT ipar,
SQLSMALLINT fCType,
SQLSMALLINT fSqlType,
SQLULEN cbColDef,
SQLSMALLINT ibScale,
SQLPOINTER rgbValue,
SQLLEN * pcbValue)
{
LOCK_STMT(hstmt);
return my_SQLBindParameter(hstmt, ipar, SQL_PARAM_INPUT_OUTPUT, fCType,
fSqlType, cbColDef, ibScale, rgbValue,
SQL_SETPARAM_VALUE_MAX, pcbValue);
}
/*
@type : ODBC 2.0 API
@purpose : binds a buffer to a parameter marker in an SQL statement.
*/
SQLRETURN SQL_API SQLBindParameter( SQLHSTMT hstmt,
SQLUSMALLINT ipar,
SQLSMALLINT fParamType,
SQLSMALLINT fCType,
SQLSMALLINT fSqlType,
SQLULEN cbColDef,
SQLSMALLINT ibScale,
SQLPOINTER rgbValue,
SQLLEN cbValueMax,
SQLLEN * pcbValue )
{
LOCK_STMT(hstmt);
return my_SQLBindParameter(hstmt, ipar, fParamType, fCType, fSqlType,
cbColDef, ibScale, rgbValue, cbValueMax, pcbValue);
}
/*
@type : ODBC 1.0 API
@purpose : returns the description of a parameter marker associated
with a prepared SQL statement
*/
SQLRETURN SQL_API SQLDescribeParam( SQLHSTMT hstmt,
SQLUSMALLINT ipar __attribute__((unused)),
SQLSMALLINT *pfSqlType,
SQLULEN * pcbColDef,
SQLSMALLINT *pibScale __attribute__((unused)),
SQLSMALLINT *pfNullable )
{
STMT *stmt= (STMT *) hstmt;
/* It is needed only in one case, but we won't make exceptions */
CHECK_HANDLE(hstmt);
if (pfSqlType)
*pfSqlType= SQL_VARCHAR;
if (pcbColDef)
*pcbColDef= (stmt->dbc->ds->opt_BIG_PACKETS ? 24*1024*1024L : 255);
if (pfNullable)
*pfNullable= SQL_NULLABLE_UNKNOWN;
return SQL_SUCCESS;
}
/*
@type : ODBC 1.0 API
@purpose : sets multiple values (arrays) for the set of parameter markers
*/
#ifdef USE_SQLPARAMOPTIONS_SQLULEN_PTR
SQLRETURN SQL_API SQLParamOptions( SQLHSTMT hstmt,
SQLULEN crow,
SQLULEN *pirow )
{
#else
SQLRETURN SQL_API SQLParamOptions( SQLHSTMT hstmt,
SQLUINTEGER crow,
SQLUINTEGER *pirow )
{
#endif
SQLRETURN rc;
STMT *stmt= (STMT *)hstmt;
CHECK_HANDLE(hstmt);
rc= MySQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)crow, 0);
if (!SQL_SUCCEEDED(rc))
return rc;
rc= MySQLSetStmtAttr(stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, pirow, 0);
return rc;
}
/*
@type : ODBC 1.0 API
@purpose : returns the number of parameter markers.
*/
SQLRETURN SQL_API SQLNumParams(SQLHSTMT hstmt, SQLSMALLINT *pcpar)
{
STMT *stmt= (STMT *)hstmt;
CHECK_HANDLE(hstmt);
if (pcpar)
*pcpar= stmt->param_count;
return SQL_SUCCESS;
}
/*
@type : ODBC 1.0 API
@purpose : sets options that control the behavior of cursors.
*/
SQLRETURN SQL_API SQLSetScrollOptions( SQLHSTMT hstmt,
SQLUSMALLINT fConcurrency __attribute__((unused)),
SQLLEN crowKeyset __attribute__((unused)),
SQLUSMALLINT crowRowset )
{
STMT *stmt= (STMT *)hstmt;
CHECK_HANDLE(hstmt);
return stmt_SQLSetDescField(stmt, stmt->ard, 0, SQL_DESC_ARRAY_SIZE,
(SQLPOINTER)(size_t)crowRowset,
SQL_IS_USMALLINT);
}