c646a30002
Many callsites of Array.set(litstr, ...)
1275 linhas
35 KiB
C++
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 "ed, 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 "ed,
|
|
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();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|