Arquivos
hhvm/hphp/runtime/ext/pdo_mysql.cpp
T
smith c646a30002 Litstr must die, episode III.
Many callsites of Array.set(litstr, ...)
2013-04-23 12:57:40 -07:00

1275 linhas
35 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 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_01.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. |
+----------------------------------------------------------------------+
*/
#include <runtime/ext/pdo_mysql.h>
#include <runtime/ext/ext_stream.h>
#include <mysql/mysql.h>
#ifdef PHP_MYSQL_UNIX_SOCK_ADDR
#ifdef MYSQL_UNIX_ADDR
#undef MYSQL_UNIX_ADDR
#endif
#define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
#endif
namespace HPHP {
IMPLEMENT_DEFAULT_EXTENSION(pdo_mysql);
///////////////////////////////////////////////////////////////////////////////
class PDOMySqlError {
public:
PDOMySqlError() : file(NULL), line(0), errcode(0), errmsg(NULL) {
}
const char *file;
int line;
unsigned int errcode;
char *errmsg;
};
class PDOMySqlStatement;
class PDOMySqlConnection : public PDOConnection {
public:
PDOMySqlConnection();
virtual ~PDOMySqlConnection();
virtual bool create(CArrRef options);
int handleError(const char *file, int line, PDOMySqlStatement *stmt = NULL);
virtual bool support(SupportedMethod method);
virtual bool closer();
virtual bool preparer(CStrRef sql, sp_PDOStatement *stmt, CVarRef options);
virtual int64_t doer(CStrRef sql);
virtual bool quoter(CStrRef input, String &quoted, PDOParamType paramtype);
virtual bool begin();
virtual bool commit();
virtual bool rollback();
virtual bool setAttribute(int64_t attr, CVarRef value);
virtual String lastId(const char *name);
virtual bool fetchErr(PDOStatement *stmt, Array &info);
virtual int getAttribute(int64_t attr, Variant &value);
virtual bool checkLiveness();
virtual void persistentShutdown();
bool buffered() const { return m_buffered;}
unsigned long max_buffer_size() const { return m_max_buffer_size;}
bool fetch_table_names() const { return m_fetch_table_names;}
private:
MYSQL *m_server;
unsigned m_attached:1;
unsigned m_buffered:1;
unsigned m_emulate_prepare:1;
unsigned m_fetch_table_names:1;
unsigned m__reserved:31;
unsigned long m_max_buffer_size;
PDOMySqlError m_einfo;
};
class PDOMySqlStatement : public PDOStatement {
public:
PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server);
virtual ~PDOMySqlStatement();
bool create(CStrRef sql, CArrRef options);
virtual bool support(SupportedMethod method);
virtual bool executer();
virtual bool fetcher(PDOFetchOrientation ori, long offset);
virtual bool describer(int colno);
virtual bool getColumn(int colno, Variant &value);
virtual bool paramHook(PDOBoundParam *param, PDOParamEvent event_type);
virtual bool getColumnMeta(int64_t colno, Array &return_value);
virtual bool nextRowset();
virtual bool cursorCloser();
MYSQL_STMT *stmt() { return m_stmt;}
private:
PDOMySqlConnection *m_conn;
MYSQL *m_server;
MYSQL_RES *m_result;
const MYSQL_FIELD *m_fields;
MYSQL_ROW m_current_data;
long *m_current_lengths;
PDOMySqlError m_einfo;
MYSQL_STMT *m_stmt;
int m_num_params;
MYSQL_BIND *m_params;
my_bool *m_in_null;
unsigned long *m_in_length;
MYSQL_BIND *m_bound_result;
my_bool *m_out_null;
unsigned long *m_out_length;
unsigned int m_params_given;
unsigned m_max_length:1;
void setRowCount();
bool executePrepared();
int handleError(const char *file, int line);
};
///////////////////////////////////////////////////////////////////////////////
/* For the convenience of drivers, this function will parse a data source
* string, of the form "name=value; name2=value2" and populate variables
* according to the data you pass in and array of pdo_data_src_parser structures */
struct pdo_data_src_parser {
const char *optname;
char *optval;
int freeme;
};
static int php_pdo_parse_data_source(const char *data_source,
int data_source_len,
struct pdo_data_src_parser *parsed,
int nparams) {
int i, j;
int valstart = -1;
int semi = -1;
int optstart = 0;
int nlen;
int n_matches = 0;
i = 0;
while (i < data_source_len) {
/* looking for NAME= */
if (data_source[i] == '\0') {
break;
}
if (data_source[i] != '=') {
++i;
continue;
}
valstart = ++i;
/* now we're looking for VALUE; or just VALUE<NUL> */
semi = -1;
while (i < data_source_len) {
if (data_source[i] == '\0') {
semi = i++;
break;
}
if (data_source[i] == ';') {
semi = i++;
break;
}
++i;
}
if (semi == -1) {
semi = i;
}
/* find the entry in the array */
nlen = valstart - optstart - 1;
for (j = 0; j < nparams; j++) {
if (0 == strncmp(data_source + optstart, parsed[j].optname, nlen) &&
parsed[j].optname[nlen] == '\0') {
/* got a match */
if (parsed[j].freeme) {
free(parsed[j].optval);
}
parsed[j].optval = strndup(data_source + valstart, semi - valstart);
parsed[j].freeme = 1;
++n_matches;
break;
}
}
while (i < data_source_len && isspace(data_source[i])) {
i++;
}
optstart = i;
}
return n_matches;
}
static long pdo_attr_lval(CArrRef options, int opt, long defaultValue) {
if (options.exists(opt)) {
return options[opt].toInt64();
}
return defaultValue;
}
static String pdo_attr_strval(CArrRef options, int opt, const char *def) {
if (options.exists(opt)) {
return options[opt].toString();
}
if (def) {
return def;
}
return String();
}
///////////////////////////////////////////////////////////////////////////////
PDOMySqlConnection::PDOMySqlConnection()
: m_server(NULL), m_attached(0), m_buffered(0), m_emulate_prepare(0),
m_fetch_table_names(0), m__reserved(0), m_max_buffer_size(0) {
}
PDOMySqlConnection::~PDOMySqlConnection() {
if (m_server) {
mysql_close(m_server);
}
if (m_einfo.errmsg) {
free(m_einfo.errmsg);
}
}
bool PDOMySqlConnection::create(CArrRef options) {
int i, ret = 0;
char *host = NULL, *unix_socket = NULL;
unsigned int port = 3306;
char *dbname;
struct pdo_data_src_parser vars[] = {
{ "charset", NULL, 0 },
{ "dbname", "", 0 },
{ "host", "localhost", 0 },
{ "port", "3306", 0 },
{ "unix_socket", MYSQL_UNIX_ADDR, 0 },
};
int connect_opts = 0
#ifdef CLIENT_MULTI_RESULTS
|CLIENT_MULTI_RESULTS
#endif
#ifdef CLIENT_MULTI_STATEMENTS
|CLIENT_MULTI_STATEMENTS
#endif
;
php_pdo_parse_data_source(data_source.data(), data_source.size(), vars, 5);
/* handle for the server */
if (!(m_server = mysql_init(NULL))) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
m_max_buffer_size = 1024*1024;
m_buffered = m_emulate_prepare = 1;
/* handle MySQL options */
if (!options.empty()) {
long connect_timeout = pdo_attr_lval(options, PDO_ATTR_TIMEOUT, 30);
long local_infile = pdo_attr_lval(options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
String init_cmd, default_file, default_group;
long compress = 0;
m_buffered = pdo_attr_lval(options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
m_emulate_prepare = pdo_attr_lval(options, PDO_MYSQL_ATTR_DIRECT_QUERY,
m_emulate_prepare);
m_emulate_prepare = pdo_attr_lval(options, PDO_ATTR_EMULATE_PREPARES,
m_emulate_prepare);
m_max_buffer_size = pdo_attr_lval(options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
m_max_buffer_size);
if (pdo_attr_lval(options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
connect_opts |= CLIENT_FOUND_ROWS;
}
if (pdo_attr_lval(options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
connect_opts |= CLIENT_IGNORE_SPACE;
}
if (mysql_options(m_server, MYSQL_OPT_CONNECT_TIMEOUT,
(const char *)&connect_timeout)) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
if (mysql_options(m_server, MYSQL_OPT_LOCAL_INFILE,
(const char *)&local_infile)) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
#ifdef MYSQL_OPT_RECONNECT
/* since 5.0.3, the default for this option is 0 if not specified.
* we want the old behaviour */
{
long reconnect = 1;
mysql_options(m_server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
}
#endif
init_cmd = pdo_attr_strval(options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
if (!init_cmd.empty()) {
if (mysql_options(m_server, MYSQL_INIT_COMMAND, init_cmd.data())) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
}
default_file = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
NULL);
if (!default_file.empty()) {
if (mysql_options(m_server, MYSQL_READ_DEFAULT_FILE,
default_file.data())) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
}
default_group = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
NULL);
if (!default_group.empty()) {
if (mysql_options(m_server, MYSQL_READ_DEFAULT_GROUP,
default_group.data())) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
}
compress = pdo_attr_lval(options, PDO_MYSQL_ATTR_COMPRESS, 0);
if (compress) {
if (mysql_options(m_server, MYSQL_OPT_COMPRESS, 0)) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
}
}
dbname = vars[1].optval;
host = vars[2].optval;
if (vars[3].optval) {
port = atoi(vars[3].optval);
}
if (vars[2].optval && !strcmp("localhost", vars[2].optval)) {
unix_socket = vars[4].optval;
}
/* TODO: - Check zval cache + ZTS */
if (mysql_real_connect(m_server, host, username.c_str(), password.c_str(),
dbname, port, unix_socket, connect_opts) == NULL) {
handleError(__FILE__, __LINE__);
goto cleanup;
}
if (!auto_commit) {
mysql_autocommit(m_server, auto_commit);
}
m_attached = 1;
alloc_own_columns = 1;
max_escaped_char_length = 2;
ret = 1;
cleanup:
for (i = 0; i < (int)(sizeof(vars)/sizeof(vars[0])); i++) {
if (vars[i].freeme) {
free(vars[i].optval);
}
}
return ret;
}
bool PDOMySqlConnection::support(SupportedMethod method) {
return true;
}
bool PDOMySqlConnection::closer() {
if (m_server) {
mysql_close(m_server);
m_server = NULL;
}
if (m_einfo.errmsg) {
free(m_einfo.errmsg);
m_einfo.errmsg = NULL;
}
return false;
}
int PDOMySqlConnection::handleError(const char *file, int line,
PDOMySqlStatement *stmt) {
PDOErrorType *pdo_err;
PDOMySqlError *einfo = &m_einfo;
if (stmt) {
pdo_err = &stmt->error_code;
} else {
pdo_err = &error_code;
}
if (stmt && stmt->stmt()) {
einfo->errcode = mysql_stmt_errno(stmt->stmt());
} else {
einfo->errcode = mysql_errno(m_server);
}
einfo->file = file;
einfo->line = line;
if (einfo->errmsg) {
free(einfo->errmsg);
einfo->errmsg = NULL;
}
if (einfo->errcode) {
if (einfo->errcode == 2014) {
einfo->errmsg =
strdup("Cannot execute queries while other unbuffered queries are "
"active. Consider using PDOStatement::fetchAll(). "
"Alternatively, if your code is only ever going to run against "
"mysql, you may enable query buffering by setting the "
"PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.");
} else if (einfo->errcode == 2057) {
einfo->errmsg =
strdup("A stored procedure returning result sets of different size "
"was called. This is not supported by libmysql");
} else {
einfo->errmsg = strdup(mysql_error(m_server));
}
} else { /* no error */
strcpy(*pdo_err, PDO_ERR_NONE);
return false;
}
if (stmt && stmt->stmt()) {
strcpy(*pdo_err, mysql_stmt_sqlstate(stmt->stmt()));
} else {
strcpy(*pdo_err, mysql_sqlstate(m_server));
}
if (stmt && stmt->stmt()) {
pdo_raise_impl_error(stmt->dbh, NULL, pdo_err[0], einfo->errmsg);
} else {
throw_pdo_exception((int)einfo->errcode, uninit_null(), "SQLSTATE[%s] [%d] %s",
pdo_err[0], einfo->errcode, einfo->errmsg);
}
return einfo->errcode;
}
bool PDOMySqlConnection::preparer(CStrRef sql, sp_PDOStatement *stmt,
CVarRef options) {
PDOMySqlStatement *s = new PDOMySqlStatement(this, m_server);
*stmt = s;
if (m_emulate_prepare) {
return true;
}
int server_version = mysql_get_server_version(m_server);
if (server_version < 40100) {
return true;
}
if (s->create(sql, options)) {
alloc_own_columns = 1;
return true;
}
stmt->reset();
strcpy(error_code, s->error_code);
return false;
}
int64_t PDOMySqlConnection::doer(CStrRef sql) {
if (mysql_real_query(m_server, sql.data(), sql.size())) {
handleError(__FILE__, __LINE__);
return -1;
}
my_ulonglong c = mysql_affected_rows(m_server);
if (c == (my_ulonglong) -1) {
handleError(__FILE__, __LINE__);
return m_einfo.errcode ? -1 : 0;
}
/* MULTI_QUERY support - eat up all unfetched result sets */
while (mysql_more_results(m_server)) {
if (mysql_next_result(m_server)) {
return true;
}
MYSQL_RES *result = mysql_store_result(m_server);
if (result) {
mysql_free_result(result);
}
}
return c;
}
bool PDOMySqlConnection::quoter(CStrRef input, String &quoted,
PDOParamType paramtype) {
String s(2 * input.size() + 3, ReserveString);
char *buf = s.mutableSlice().ptr;
int len = mysql_real_escape_string(m_server, buf + 1,
input.data(), input.size());
len++;
buf[0] = buf[len] = '\'';
len++;
quoted = s.setSize(len);
return true;
}
bool PDOMySqlConnection::begin() {
return doer("START TRANSACTION") >= 0;
}
bool PDOMySqlConnection::commit() {
return mysql_commit(m_server) >= 0;
}
bool PDOMySqlConnection::rollback() {
return mysql_rollback(m_server) >= 0;
}
bool PDOMySqlConnection::setAttribute(int64_t attr, CVarRef value) {
switch (attr) {
case PDO_ATTR_AUTOCOMMIT:
/* ignore if the new value equals the old one */
if (auto_commit ^ value.toBoolean()) {
auto_commit = value.toBoolean();
mysql_autocommit(m_server, auto_commit);
}
return true;
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
m_buffered = value.toBoolean();
return true;
case PDO_MYSQL_ATTR_DIRECT_QUERY:
case PDO_ATTR_EMULATE_PREPARES:
m_emulate_prepare = value.toBoolean();
return true;
case PDO_ATTR_FETCH_TABLE_NAMES:
m_fetch_table_names = value.toBoolean();
return true;
case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
if (value.toInt64() < 0) {
/* TODO: Johannes, can we throw a warning here? */
m_max_buffer_size = 1024*1024;
} else {
m_max_buffer_size = value.toInt64();
}
return true;
default:
return false;
}
}
String PDOMySqlConnection::lastId(const char *name) {
return (int64_t)mysql_insert_id(m_server);
}
bool PDOMySqlConnection::fetchErr(PDOStatement *stmt, Array &info) {
if (m_einfo.errcode) {
info.append((int64_t)m_einfo.errcode);
info.append(String(m_einfo.errmsg, CopyString));
}
return true;
}
int PDOMySqlConnection::getAttribute(int64_t attr, Variant &value) {
switch (attr) {
case PDO_ATTR_CLIENT_VERSION:
value = String((char *)mysql_get_client_info(), CopyString);
break;
case PDO_ATTR_SERVER_VERSION:
value = String((char *)mysql_get_server_info(m_server), CopyString);
break;
case PDO_ATTR_CONNECTION_STATUS:
value = String((char *)mysql_get_host_info(m_server), CopyString);
break;
case PDO_ATTR_SERVER_INFO: {
char *tmp = (char *)mysql_stat(m_server);
if (tmp) {
value = String(tmp, CopyString);
} else {
handleError(__FILE__, __LINE__);
return -1;
}
break;
}
case PDO_ATTR_AUTOCOMMIT:
value = (int64_t)auto_commit;
break;
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
value = (int64_t)m_buffered;
break;
case PDO_MYSQL_ATTR_DIRECT_QUERY:
value = (int64_t)m_emulate_prepare;
break;
case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
value = (int64_t)m_max_buffer_size;
break;
default:
return 0;
}
return 1;
}
bool PDOMySqlConnection::checkLiveness() {
return !mysql_ping(m_server);
}
void PDOMySqlConnection::persistentShutdown() {
// do nothing
}
///////////////////////////////////////////////////////////////////////////////
void PDOMySqlStatement::setRowCount() {
my_ulonglong count = mysql_stmt_affected_rows(m_stmt);
if (count != (my_ulonglong)-1) {
row_count = count;
}
}
bool PDOMySqlStatement::executePrepared() {
/* (re)bind the parameters */
if (mysql_stmt_bind_param(m_stmt, m_params) || mysql_stmt_execute(m_stmt)) {
if (m_params) {
free(m_params);
m_params = 0;
}
handleError(__FILE__, __LINE__);
if (mysql_stmt_errno(m_stmt) == 2057) {
/* CR_NEW_STMT_METADATA makes the statement unusable */
m_stmt = NULL;
}
return false;
}
if (!m_result) {
int i;
/* figure out the result set format, if any */
m_result = mysql_stmt_result_metadata(m_stmt);
if (m_result) {
int calc_max_length = m_conn->buffered() && m_max_length == 1;
m_fields = mysql_fetch_fields(m_result);
if (m_bound_result) {
int i;
for (i = 0; i < column_count; i++) {
free(m_bound_result[i].buffer);
}
free(m_bound_result);
free(m_out_null);
free(m_out_length);
}
column_count = (int)mysql_num_fields(m_result);
m_bound_result = (MYSQL_BIND*)calloc(column_count, sizeof(MYSQL_BIND));
m_out_null = (my_bool*)calloc(column_count, sizeof(my_bool));
m_out_length = (unsigned long *)calloc(column_count,
sizeof(unsigned long));
/* summon memory to hold the row */
for (i = 0; i < column_count; i++) {
if (calc_max_length && m_fields[i].type == FIELD_TYPE_BLOB) {
my_bool on = 1;
mysql_stmt_attr_set(m_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
calc_max_length = 0;
}
switch (m_fields[i].type) {
case FIELD_TYPE_INT24:
m_bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
break;
case FIELD_TYPE_LONG:
m_bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
break;
case FIELD_TYPE_LONGLONG:
m_bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
break;
case FIELD_TYPE_TINY:
m_bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
break;
case FIELD_TYPE_SHORT:
m_bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
break;
default:
m_bound_result[i].buffer_length =
m_fields[i].max_length? m_fields[i].max_length:
m_fields[i].length;
/* work-around for longtext and alike */
if (m_bound_result[i].buffer_length > m_conn->max_buffer_size()) {
m_bound_result[i].buffer_length = m_conn->max_buffer_size();
}
}
/* 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 (m_fields[i].max_length == 0 &&
m_bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
m_bound_result[i].buffer_length = 128;
}
m_out_length[i] = 0;
m_bound_result[i].buffer = malloc(m_bound_result[i].buffer_length);
m_bound_result[i].is_null = &m_out_null[i];
m_bound_result[i].length = &m_out_length[i];
m_bound_result[i].buffer_type = MYSQL_TYPE_STRING;
}
if (mysql_stmt_bind_result(m_stmt, m_bound_result)) {
handleError(__FILE__, __LINE__);
return false;
}
/* if buffered, pre-fetch all the data */
if (m_conn->buffered()) {
mysql_stmt_store_result(m_stmt);
}
}
}
setRowCount();
return true;
}
static const 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)
#ifdef FIELD_TYPE_NEWDECIMAL
PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
#endif
#ifdef FIELD_TYPE_GEOMETRY
PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
#ifdef MYSQL_HAS_YEAR
PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(SET)
PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
#ifdef FIELD_TYPE_NEWDATE
PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
#endif
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;
}
#undef PDO_MYSQL_NATIVE_TYPE_NAME
}
///////////////////////////////////////////////////////////////////////////////
PDOMySqlStatement::PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server)
: m_conn(conn), m_server(server), m_result(NULL), m_fields(NULL),
m_current_data(NULL), m_current_lengths(NULL), m_stmt(NULL),
m_num_params(0), m_params(NULL), m_in_null(NULL), m_in_length(NULL),
m_bound_result(NULL), m_out_null(NULL), m_out_length(NULL),
m_params_given(0), m_max_length(0) {
}
PDOMySqlStatement::~PDOMySqlStatement() {
if (m_result) {
/* free the resource */
mysql_free_result(m_result);
m_result = NULL;
}
if (m_einfo.errmsg) {
free(m_einfo.errmsg);
m_einfo.errmsg = NULL;
}
if (m_stmt) {
mysql_stmt_close(m_stmt);
m_stmt = NULL;
}
if (m_params) {
free(m_params);
}
if (m_in_null) {
free(m_in_null);
}
if (m_in_length) {
free(m_in_length);
}
if (m_bound_result) {
int i;
for (i = 0; i < column_count; i++) {
free(m_bound_result[i].buffer);
}
free(m_bound_result);
free(m_out_null);
free(m_out_length);
}
if (m_server) {
while (mysql_more_results(m_server)) {
if (mysql_next_result(m_server) != 0) {
break;
}
MYSQL_RES *res = mysql_store_result(m_server);
if (res) {
mysql_free_result(res);
}
}
}
}
bool PDOMySqlStatement::create(CStrRef sql, CArrRef options) {
supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
String nsql;
int ret = pdo_parse_params(this, sql, nsql);
if (ret == 1) {
/* query was rewritten */
} else if (ret == -1) {
/* failed to parse */
return false;
} else {
nsql = sql;
}
if (!(m_stmt = mysql_stmt_init(m_server))) {
handleError(__FILE__, __LINE__);
return false;
}
if (mysql_stmt_prepare(m_stmt, nsql.data(), nsql.size())) {
/* TODO: might need to pull statement specific info here? */
/* if the query isn't supported by the protocol, fallback to emulation */
if (mysql_errno(m_server) == 1295) {
supports_placeholders = PDO_PLACEHOLDER_NONE;
return true;
}
handleError(__FILE__, __LINE__);
return false;
}
m_num_params = mysql_stmt_param_count(m_stmt);
if (m_num_params) {
m_params_given = 0;
m_params = (MYSQL_BIND*)calloc(m_num_params, sizeof(MYSQL_BIND));
m_in_null = (my_bool*)calloc(m_num_params, sizeof(my_bool));
m_in_length = (unsigned long*)calloc(m_num_params, sizeof(unsigned long));
}
m_max_length = pdo_attr_lval(options, PDO_ATTR_MAX_COLUMN_LEN, 0);
return true;
}
bool PDOMySqlStatement::support(SupportedMethod method) {
switch (method) {
case MethodSetAttribute:
case MethodGetAttribute:
return false;
default:
break;
}
return true;
}
int PDOMySqlStatement::handleError(const char *file, int line) {
PDOMySqlConnection *conn = dynamic_cast<PDOMySqlConnection*>(dbh.get());
assert(conn);
return conn->handleError(file, line, this);
}
bool PDOMySqlStatement::executer() {
if (m_stmt) {
return executePrepared();
}
/* ensure that we free any previous unfetched results */
if (m_result) {
mysql_free_result(m_result);
m_result = NULL;
}
if (mysql_real_query(m_server, active_query_string.data(),
active_query_string.size()) != 0) {
handleError(__FILE__, __LINE__);
return false;
}
my_ulonglong row_count = mysql_affected_rows(m_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 (!m_conn->buffered()) {
m_result = mysql_use_result(m_server);
} else {
m_result = mysql_store_result(m_server);
}
if (NULL == m_result) {
handleError(__FILE__, __LINE__);
return false;
}
row_count = mysql_num_rows(m_result);
column_count = (int) mysql_num_fields(m_result);
m_fields = mysql_fetch_fields(m_result);
} else {
/* this was a DML or DDL query (INSERT, UPDATE, DELETE, ... */
row_count = row_count;
}
return true;
}
bool PDOMySqlStatement::fetcher(PDOFetchOrientation ori, long offset) {
int ret;
if (m_stmt) {
ret = mysql_stmt_fetch(m_stmt);
if (ret == MYSQL_DATA_TRUNCATED) {
ret = 0;
}
if (ret) {
if (ret != MYSQL_NO_DATA) {
handleError(__FILE__, __LINE__);
}
return false;
}
return true;
}
if (!m_result) {
strcpy(error_code, "HY000");
return false;
}
if ((m_current_data = mysql_fetch_row(m_result)) == NULL) {
if (mysql_errno(m_server)) {
handleError(__FILE__, __LINE__);
}
return false;
}
m_current_lengths = (long int *)mysql_fetch_lengths(m_result);
return true;
}
bool PDOMySqlStatement::describer(int colno) {
if (!m_result) {
return false;
}
if (colno < 0 || colno >= column_count) {
/* error invalid column */
return false;
}
if (columns.empty()) {
for (int i = 0; i < column_count; i++) {
columns.set(i, Object(new PDOColumn()));
}
}
// fetch all on demand, this seems easiest if we've been here before bail out
PDOColumn *col = columns[0].toObject().getTyped<PDOColumn>();
if (!col->name.empty()) {
return true;
}
for (int i = 0; i < column_count; i++) {
col = columns[i].toObject().getTyped<PDOColumn>();
if (m_conn->fetch_table_names()) {
col->name = String(m_fields[i].table) + "." +
String(m_fields[i].name);
} else {
col->name = String(m_fields[i].name, CopyString);
}
col->precision = m_fields[i].decimals;
col->maxlen = m_fields[i].length;
col->param_type = PDO_PARAM_STR;
}
return true;
}
bool PDOMySqlStatement::getColumn(int colno, Variant &value) {
if (!m_result) {
return false;
}
if (!m_stmt) {
if (m_current_data == NULL || !m_result) {
return false;
}
}
if (colno < 0 || colno >= column_count) {
/* error invalid column */
return false;
}
char *ptr; int len;
if (m_stmt) {
if (m_out_null[colno]) {
value = String();
return true;
}
ptr = (char*)m_bound_result[colno].buffer;
if (m_out_length[colno] > m_bound_result[colno].buffer_length) {
/* mysql lied about the column width */
strcpy(error_code, "01004"); /* truncated */
m_out_length[colno] = m_bound_result[colno].buffer_length;
len = m_out_length[colno];
value = String(ptr, len, CopyString);
return false;
}
len = m_out_length[colno];
value = String(ptr, len, CopyString);
return true;
}
ptr = m_current_data[colno];
len = m_current_lengths[colno];
value = String(ptr, len, CopyString);
return true;
}
bool PDOMySqlStatement::paramHook(PDOBoundParam *param,
PDOParamEvent event_type) {
MYSQL_BIND *b;
if (m_stmt && param->is_param) {
switch (event_type) {
case PDO_PARAM_EVT_ALLOC:
/* sanity check parameter number range */
if (param->paramno < 0 || param->paramno >= m_num_params) {
strcpy(error_code, "HY093");
return false;
}
m_params_given++;
b = &m_params[param->paramno];
param->driver_data = b;
b->is_null = &m_in_null[param->paramno];
b->length = &m_in_length[param->paramno];
/* recall how many parameters have been provided */
return true;
case PDO_PARAM_EVT_EXEC_PRE:
if ((int)m_params_given < m_num_params) {
/* too few parameter bound */
strcpy(error_code, "HY093");
return false;
}
b = (MYSQL_BIND*)param->driver_data;
*b->is_null = 0;
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
param->parameter.isNull()) {
*b->is_null = 1;
b->buffer_type = MYSQL_TYPE_STRING;
b->buffer = NULL;
b->buffer_length = 0;
*b->length = 0;
return true;
}
switch (PDO_PARAM_TYPE(param->param_type)) {
case PDO_PARAM_STMT:
return false;
case PDO_PARAM_LOB:
if (param->parameter.isResource()) {
Variant buf = f_stream_get_contents(param->parameter);
if (!same(buf, false)) {
param->parameter = buf;
} else {
pdo_raise_impl_error(dbh, this, "HY105",
"Expected a stream resource");
return false;
}
}
/* fall through */
default:
;
}
if (param->parameter.isString()) {
String sparam = param->parameter.toString();
b->buffer_type = MYSQL_TYPE_STRING;
b->buffer = (void*)sparam.data();
b->buffer_length = sparam.size();
*b->length = sparam.size();
return true;
}
if (param->parameter.isInteger()) {
param->parameter = param->parameter.toInt64();
b->buffer_type = MYSQL_TYPE_LONG;
b->buffer = param->parameter.getInt64Data();
return true;
}
if (param->parameter.isDouble()) {
b->buffer_type = MYSQL_TYPE_DOUBLE;
b->buffer = param->parameter.getDoubleData();
return true;
}
return false;
case PDO_PARAM_EVT_FREE:
case PDO_PARAM_EVT_EXEC_POST:
case PDO_PARAM_EVT_FETCH_PRE:
case PDO_PARAM_EVT_FETCH_POST:
case PDO_PARAM_EVT_NORMALIZE:
/* do nothing */
break;
}
}
return true;
}
static const StaticString s_mysql_def("mysql:def");
static const StaticString s_not_null("not_null");
static const StaticString s_primary_key("primary_key");
static const StaticString s_multiple_key("multiple_key");
static const StaticString s_unique_key("unique_key");
static const StaticString s_blob("blob");
static const StaticString s_native_type("native_type");
static const StaticString s_flags("flags");
static const StaticString s_table("table");
bool PDOMySqlStatement::getColumnMeta(int64_t colno, Array &return_value) {
if (!m_result) {
return false;
}
if (colno < 0 || colno >= column_count) {
/* error invalid column */
return false;
}
Array ret = Array::Create();
Array flags = Array::Create();
const MYSQL_FIELD *F = m_fields + colno;
if (F->def) {
ret.set(s_mysql_def, String(F->def, CopyString));
}
if (IS_NOT_NULL(F->flags)) {
flags.append(s_not_null);
}
if (IS_PRI_KEY(F->flags)) {
flags.append(s_primary_key);
}
if (F->flags & MULTIPLE_KEY_FLAG) {
flags.append(s_multiple_key);
}
if (F->flags & UNIQUE_KEY_FLAG) {
flags.append(s_unique_key);
}
if (IS_BLOB(F->flags)) {
flags.append(s_blob);
}
const char *str = type_to_name_native(F->type);
if (str) {
ret.set(s_native_type, str);
}
ret.set(s_flags, flags);
ret.set(s_table, String(F->table, CopyString));
return true;
}
bool PDOMySqlStatement::nextRowset() {
/* ensure that we free any previous unfetched results */
if (m_stmt) {
column_count = (int)mysql_num_fields(m_result);
mysql_stmt_free_result(m_stmt);
}
if (m_result) {
mysql_free_result(m_result);
m_result = NULL;
}
int ret = mysql_next_result(m_server);
if (ret > 0) {
handleError(__FILE__, __LINE__);
return false;
}
if (ret < 0) {
/* No more results */
return false;
}
my_ulonglong row_count;
if (!m_conn->buffered()) {
m_result = mysql_use_result(m_server);
row_count = 0;
} else {
m_result = mysql_store_result(m_server);
if ((my_ulonglong)-1 == (row_count = mysql_affected_rows(m_server))) {
handleError(__FILE__, __LINE__);
return false;
}
}
if (!m_result) {
return false;
}
row_count = row_count;
column_count = (int)mysql_num_fields(m_result);
m_fields = mysql_fetch_fields(m_result);
return true;
}
bool PDOMySqlStatement::cursorCloser() {
if (m_result) {
mysql_free_result(m_result);
m_result = NULL;
}
if (m_stmt) {
return !mysql_stmt_free_result(m_stmt);
}
while (mysql_more_results(m_server)) {
if (mysql_next_result(m_server) != 0) {
break;
}
MYSQL_RES *res = mysql_store_result(m_server);
if (res) {
mysql_free_result(res);
}
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
PDOMySql::PDOMySql() : PDODriver("mysql") {
}
PDOConnection *PDOMySql::createConnectionObject() {
return new PDOMySqlConnection();
}
///////////////////////////////////////////////////////////////////////////////
}