mirror of
https://github.com/php/php-src.git
synced 2025-08-16 14:08:47 +02:00

Also tripped over the return of PECL #5200; looks like mysql doesn't return an accurate length for the columns. The PDO driver will sanity check the real length against the buffer size it allocated (based on the info provided by mysql), so that we won't overrun the buffer. In addition, if a varchar field is reported as having a length of less than 128, we'll allocate 128 just in case. If the data is truncated, report it via the appropriate sqlstate code. There must be a better way to do this stuff.
595 lines
15 KiB
C
Executable file
595 lines
15 KiB
C
Executable file
/*
|
|
+----------------------------------------------------------------------+
|
|
| PHP Version 5 |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 1997-2005 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.0 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_0.txt. |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Author: George Schlossnagle <george@omniti.com> |
|
|
| Wez Furlong <wez@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
/* $Id$ */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "php.h"
|
|
#include "php_ini.h"
|
|
#include "ext/standard/info.h"
|
|
#include "pdo/php_pdo.h"
|
|
#include "pdo/php_pdo_driver.h"
|
|
#include "php_pdo_mysql.h"
|
|
#include "php_pdo_mysql_int.h"
|
|
|
|
|
|
static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
|
|
if (S->result) {
|
|
/* free the resource */
|
|
mysql_free_result(S->result);
|
|
S->result = NULL;
|
|
}
|
|
if (S->einfo.errmsg) {
|
|
efree(S->einfo.errmsg);
|
|
S->einfo.errmsg = NULL;
|
|
}
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
if (S->stmt) {
|
|
mysql_stmt_close(S->stmt);
|
|
S->stmt = NULL;
|
|
}
|
|
if (S->params) {
|
|
efree(S->params);
|
|
efree(S->in_null);
|
|
efree(S->in_length);
|
|
}
|
|
if (S->bound_result)
|
|
{
|
|
int i;
|
|
for (i = 0; i < stmt->column_count; i++) {
|
|
efree(S->bound_result[i].buffer);
|
|
}
|
|
|
|
efree(S->bound_result);
|
|
efree(S->out_null);
|
|
efree(S->out_length);
|
|
}
|
|
#endif
|
|
efree(S);
|
|
return 1;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
pdo_mysql_db_handle *H = S->H;
|
|
my_ulonglong row_count;
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
int i;
|
|
|
|
if (S->stmt) {
|
|
/* (re)bind the parameters */
|
|
if (mysql_stmt_bind_param(S->stmt, S->params)) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
if (mysql_stmt_execute(S->stmt)) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
if (!S->result) {
|
|
/* figure out the result set format, if any */
|
|
S->result = mysql_stmt_result_metadata(S->stmt);
|
|
if (S->result) {
|
|
int calc_max_length = H->buffered && S->max_length == 1;
|
|
|
|
S->fields = mysql_fetch_fields(S->result);
|
|
|
|
if (S->bound_result) {
|
|
int i;
|
|
for (i = 0; i < stmt->column_count; i++) {
|
|
efree(S->bound_result[i].buffer);
|
|
}
|
|
efree(S->bound_result);
|
|
efree(S->out_null);
|
|
efree(S->out_length);
|
|
}
|
|
|
|
stmt->column_count = (int)mysql_num_fields(S->result);
|
|
S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
|
|
S->out_null = ecalloc(stmt->column_count, sizeof(my_bool));
|
|
S->out_length = ecalloc(stmt->column_count, sizeof(unsigned long));
|
|
|
|
/* summon memory to hold the row */
|
|
for (i = 0; i < stmt->column_count; i++) {
|
|
if (calc_max_length && S->fields[i].type == FIELD_TYPE_BLOB) {
|
|
my_bool on = 1;
|
|
mysql_stmt_attr_set(S->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
|
|
calc_max_length = 0;
|
|
}
|
|
switch (S->fields[i].type) {
|
|
case FIELD_TYPE_INT24:
|
|
S->bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH;
|
|
break;
|
|
case FIELD_TYPE_LONG:
|
|
S->bound_result[i].buffer_length = MAX_INT_WIDTH;
|
|
break;
|
|
case FIELD_TYPE_LONGLONG:
|
|
S->bound_result[i].buffer_length = MAX_BIGINT_WIDTH;
|
|
break;
|
|
case FIELD_TYPE_TINY:
|
|
S->bound_result[i].buffer_length = MAX_TINYINT_WIDTH;
|
|
break;
|
|
case FIELD_TYPE_SHORT:
|
|
S->bound_result[i].buffer_length = MAX_SMALLINT_WIDTH;
|
|
break;
|
|
default:
|
|
S->bound_result[i].buffer_length =
|
|
S->fields[i].max_length? S->fields[i].max_length:
|
|
S->fields[i].length;
|
|
}
|
|
#if 0
|
|
printf("%d: max_length=%d length=%d buffer_length=%d type=%d\n",
|
|
i,
|
|
S->fields[i].max_length,
|
|
S->fields[i].length,
|
|
S->bound_result[i].buffer_length,
|
|
S->fields[i].type
|
|
);
|
|
#endif
|
|
|
|
/* there are cases where the length reported by mysql is too short.
|
|
* eg: when describing a table that contains an enum column. Since
|
|
* we have no way of knowing the true length either, we'll bump up
|
|
* our buffer size to a reasonable size, just in case */
|
|
if (S->fields[i].max_length == 0 && S->bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
|
|
S->bound_result[i].buffer_length = 128;
|
|
}
|
|
|
|
S->out_length[i] = 0;
|
|
|
|
S->bound_result[i].buffer = emalloc(S->bound_result[i].buffer_length);
|
|
S->bound_result[i].is_null = &S->out_null[i];
|
|
S->bound_result[i].length = &S->out_length[i];
|
|
S->bound_result[i].buffer_type = MYSQL_TYPE_STRING;
|
|
}
|
|
|
|
if (mysql_stmt_bind_result(S->stmt, S->bound_result)) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
/* if buffered, pre-fetch all the data */
|
|
if (H->buffered) {
|
|
mysql_stmt_store_result(S->stmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
row_count = mysql_stmt_affected_rows(S->stmt);
|
|
if (row_count != (my_ulonglong)-1) {
|
|
stmt->row_count = row_count;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
/* ensure that we free any previous unfetched results */
|
|
if (S->result) {
|
|
mysql_free_result(S->result);
|
|
S->result = NULL;
|
|
}
|
|
|
|
if (mysql_real_query(H->server, stmt->active_query_string, stmt->active_query_stringlen) != 0) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
row_count = mysql_affected_rows(H->server);
|
|
if (row_count == (my_ulonglong)-1) {
|
|
/* we either have a query that returned a result set or an error occured
|
|
lets see if we have access to a result set */
|
|
if (!H->buffered) {
|
|
S->result = mysql_use_result(H->server);
|
|
} else {
|
|
S->result = mysql_store_result(H->server);
|
|
}
|
|
if (NULL == S->result) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
stmt->row_count = 0;
|
|
|
|
if (!stmt->executed) {
|
|
stmt->column_count = (int) mysql_num_fields(S->result);
|
|
S->fields = mysql_fetch_fields(S->result);
|
|
}
|
|
} else {
|
|
/* this was a DML or DDL query (INSERT, UPDATE, DELETE, ... */
|
|
stmt->row_count = row_count;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
|
|
{
|
|
#if HAVE_MYSQL_NEXT_RESULT
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
pdo_mysql_db_handle *H = S->H;
|
|
my_ulonglong row_count;
|
|
int ret;
|
|
|
|
/* ensure that we free any previous unfetched results */
|
|
if (S->result) {
|
|
mysql_free_result(S->result);
|
|
S->result = NULL;
|
|
}
|
|
|
|
ret = mysql_next_result(H->server);
|
|
|
|
if (ret > 0) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
} else if (ret < 0) {
|
|
/* No more results */
|
|
return 0;
|
|
} else {
|
|
if ((my_ulonglong)-1 == (row_count = mysql_affected_rows(H->server))) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
return 0;
|
|
}
|
|
|
|
if (!H->buffered) {
|
|
S->result = mysql_use_result(H->server);
|
|
} else {
|
|
S->result = mysql_store_result(H->server);
|
|
}
|
|
|
|
if (NULL == S->result) {
|
|
return 0;
|
|
}
|
|
|
|
stmt->row_count = row_count;
|
|
stmt->column_count = (int) mysql_num_fields(S->result);
|
|
S->fields = mysql_fetch_fields(S->result);
|
|
return 1;
|
|
}
|
|
#else
|
|
strcpy(stmt->error_code, "HYC00");
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
|
|
enum pdo_param_event event_type TSRMLS_DC)
|
|
{
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
MYSQL_BIND *b;
|
|
|
|
if (S->stmt && param->is_param) {
|
|
switch (event_type) {
|
|
case PDO_PARAM_EVT_ALLOC:
|
|
/* sanity check parameter number range */
|
|
if (param->paramno < 0 || param->paramno >= S->num_params) {
|
|
strcpy(stmt->error_code, "HY093");
|
|
return 0;
|
|
}
|
|
b = &S->params[param->paramno];
|
|
param->driver_data = b;
|
|
b->is_null = &S->in_null[param->paramno];
|
|
b->length = &S->in_length[param->paramno];
|
|
return 1;
|
|
|
|
case PDO_PARAM_EVT_EXEC_PRE:
|
|
b = (MYSQL_BIND*)param->driver_data;
|
|
|
|
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
|
|
Z_TYPE_P(param->parameter) == IS_NULL) {
|
|
*b->is_null = 1;
|
|
b->buffer_type = MYSQL_TYPE_STRING;
|
|
b->buffer = NULL;
|
|
b->buffer_length = 0;
|
|
*b->length = 0;
|
|
return 1;
|
|
}
|
|
|
|
switch (PDO_PARAM_TYPE(param->param_type)) {
|
|
case PDO_PARAM_STMT:
|
|
return 0;
|
|
case PDO_PARAM_LOB:
|
|
if (Z_TYPE_P(param->parameter) == IS_RESOURCE) {
|
|
php_stream *stm;
|
|
php_stream_from_zval_no_verify(stm, ¶m->parameter);
|
|
if (stm) {
|
|
SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter);
|
|
Z_TYPE_P(param->parameter) = IS_STRING;
|
|
Z_STRLEN_P(param->parameter) = php_stream_copy_to_mem(stm,
|
|
&Z_STRVAL_P(param->parameter), PHP_STREAM_COPY_ALL, 0);
|
|
} else {
|
|
pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource");
|
|
return 0;
|
|
}
|
|
}
|
|
/* fall through */
|
|
|
|
default:
|
|
;
|
|
}
|
|
|
|
switch (Z_TYPE_P(param->parameter)) {
|
|
case IS_STRING:
|
|
b->buffer_type = MYSQL_TYPE_STRING;
|
|
b->buffer = Z_STRVAL_P(param->parameter);
|
|
b->buffer_length = Z_STRLEN_P(param->parameter);
|
|
*b->length = Z_STRLEN_P(param->parameter);
|
|
return 1;
|
|
|
|
case IS_LONG:
|
|
b->buffer_type = MYSQL_TYPE_LONG;
|
|
b->buffer = &Z_LVAL_P(param->parameter);
|
|
return 1;
|
|
|
|
case IS_DOUBLE:
|
|
b->buffer_type = MYSQL_TYPE_DOUBLE;
|
|
b->buffer = &Z_DVAL_P(param->parameter);
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt,
|
|
enum pdo_fetch_orientation ori, long offset TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
int ret;
|
|
|
|
if (S->stmt) {
|
|
ret = mysql_stmt_fetch(S->stmt);
|
|
|
|
#ifdef MYSQL_DATA_TRUNCATED
|
|
if (ret == MYSQL_DATA_TRUNCATED) {
|
|
ret = 0;
|
|
}
|
|
#endif
|
|
|
|
if (ret) {
|
|
if (ret != MYSQL_NO_DATA) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if (!S->result) {
|
|
return 0;
|
|
}
|
|
if ((S->current_data = mysql_fetch_row(S->result)) == NULL) {
|
|
if (mysql_errno(S->H->server)) {
|
|
pdo_mysql_error_stmt(stmt);
|
|
}
|
|
return 0;
|
|
}
|
|
S->current_lengths = mysql_fetch_lengths(S->result);
|
|
return 1;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
struct pdo_column_data *cols = stmt->columns;
|
|
unsigned int i;
|
|
|
|
if (!S->result) {
|
|
return 0;
|
|
}
|
|
|
|
if (colno >= stmt->column_count) {
|
|
/* error invalid column */
|
|
return 0;
|
|
}
|
|
|
|
/* fetch all on demand, this seems easiest
|
|
** if we've been here before bail out
|
|
*/
|
|
if (cols[0].name) {
|
|
return 1;
|
|
}
|
|
for (i=0; i < stmt->column_count; i++) {
|
|
int namelen;
|
|
namelen = strlen(S->fields[i].name);
|
|
cols[i].precision = S->fields[i].decimals;
|
|
cols[i].maxlen = S->fields[i].length;
|
|
cols[i].namelen = namelen;
|
|
cols[i].name = estrndup(S->fields[i].name, namelen);
|
|
cols[i].param_type = PDO_PARAM_STR;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
if (!S->stmt) {
|
|
#endif
|
|
if (S->current_data == NULL || !S->result) {
|
|
return 0;
|
|
}
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
}
|
|
#endif
|
|
if (colno >= stmt->column_count) {
|
|
/* error invalid column */
|
|
return 0;
|
|
}
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
if (S->stmt) {
|
|
if (S->out_null[colno]) {
|
|
*ptr = NULL;
|
|
*len = 0;
|
|
return 1;
|
|
}
|
|
*ptr = S->bound_result[colno].buffer;
|
|
if (S->out_length[colno] > S->bound_result[colno].buffer_length) {
|
|
/* mysql lied about the column width */
|
|
strcpy(stmt->error_code, "01004"); /* truncated */
|
|
S->out_length[colno] = S->bound_result[colno].buffer_length;
|
|
*len = S->out_length[colno];
|
|
return 0;
|
|
}
|
|
*len = S->out_length[colno];
|
|
return 1;
|
|
}
|
|
#endif
|
|
*ptr = S->current_data[colno];
|
|
*len = S->current_lengths[colno];
|
|
return 1;
|
|
}
|
|
|
|
static char *type_to_name_native(int type)
|
|
{
|
|
#define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
|
|
|
|
switch (type) {
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
|
|
#ifdef MYSQL_HAS_TINY
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
|
|
#endif
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
|
|
#ifdef MYSQL_HAS_YEAR
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
|
|
#endif
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
|
|
PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
MYSQL_FIELD *F;
|
|
zval *flags;
|
|
char *str;
|
|
|
|
if (!S->result) {
|
|
return FAILURE;
|
|
}
|
|
if (colno >= stmt->column_count) {
|
|
/* error invalid column */
|
|
return FAILURE;
|
|
}
|
|
|
|
array_init(return_value);
|
|
MAKE_STD_ZVAL(flags);
|
|
array_init(flags);
|
|
|
|
F = S->fields + colno;
|
|
|
|
if (F->def) {
|
|
add_assoc_string(return_value, "mysql:def", F->def, 1);
|
|
}
|
|
if (IS_NOT_NULL(F->flags)) {
|
|
add_next_index_string(flags, "not_null", 1);
|
|
}
|
|
if (IS_PRI_KEY(F->flags)) {
|
|
add_next_index_string(flags, "primary_key", 1);
|
|
}
|
|
if (F->flags & MULTIPLE_KEY_FLAG) {
|
|
add_next_index_string(flags, "multiple_key", 1);
|
|
}
|
|
if (F->flags & UNIQUE_KEY_FLAG) {
|
|
add_next_index_string(flags, "unique_key", 1);
|
|
}
|
|
if (IS_BLOB(F->flags)) {
|
|
add_next_index_string(flags, "blob", 1);
|
|
}
|
|
str = type_to_name_native(F->type);
|
|
if (str) {
|
|
add_assoc_string(return_value, "native_type", str, 1);
|
|
}
|
|
|
|
add_assoc_zval(return_value, "flags", flags);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
|
|
{
|
|
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
|
|
#if HAVE_MYSQL_STMT_PREPARE
|
|
if (S->stmt) {
|
|
return mysql_stmt_free_result(S->stmt);
|
|
}
|
|
#endif
|
|
if (S->result) {
|
|
mysql_free_result(S->result);
|
|
S->result = NULL;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
struct pdo_stmt_methods mysql_stmt_methods = {
|
|
pdo_mysql_stmt_dtor,
|
|
pdo_mysql_stmt_execute,
|
|
pdo_mysql_stmt_fetch,
|
|
pdo_mysql_stmt_describe,
|
|
pdo_mysql_stmt_get_col,
|
|
pdo_mysql_stmt_param_hook,
|
|
NULL, /* set_attr */
|
|
NULL, /* get_attr */
|
|
pdo_mysql_stmt_col_meta,
|
|
pdo_mysql_stmt_next_rowset,
|
|
pdo_mysql_stmt_cursor_closer
|
|
};
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
* vim600: noet sw=4 ts=4 fdm=marker
|
|
* vim<600: noet sw=4 ts=4
|
|
*/
|