Clean up VectorEffects::init
[hiphop-php.git] / hphp / runtime / ext / pdo_mysql.cpp
blob43205b018b9159a001dbceb7af799ffd3f54c6e7
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/pdo_mysql.h"
19 #include "hphp/runtime/ext/ext_stream.h"
20 #include "mysql.h"
22 #ifdef PHP_MYSQL_UNIX_SOCK_ADDR
23 #ifdef MYSQL_UNIX_ADDR
24 #undef MYSQL_UNIX_ADDR
25 #endif
26 #define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
27 #endif
29 namespace HPHP {
30 IMPLEMENT_DEFAULT_EXTENSION(pdo_mysql);
32 ///////////////////////////////////////////////////////////////////////////////
34 class PDOMySqlError {
35 public:
36 PDOMySqlError() : file(NULL), line(0), errcode(0), errmsg(NULL) {
39 const char *file;
40 int line;
41 unsigned int errcode;
42 char *errmsg;
45 class PDOMySqlStatement;
46 class PDOMySqlConnection : public PDOConnection {
47 public:
48 PDOMySqlConnection();
49 virtual ~PDOMySqlConnection();
50 virtual bool create(CArrRef options);
52 int handleError(const char *file, int line, PDOMySqlStatement *stmt = NULL);
54 virtual bool support(SupportedMethod method);
55 virtual bool closer();
56 virtual bool preparer(CStrRef sql, sp_PDOStatement *stmt, CVarRef options);
57 virtual int64_t doer(CStrRef sql);
58 virtual bool quoter(CStrRef input, String &quoted, PDOParamType paramtype);
59 virtual bool begin();
60 virtual bool commit();
61 virtual bool rollback();
62 virtual bool setAttribute(int64_t attr, CVarRef value);
63 virtual String lastId(const char *name);
64 virtual bool fetchErr(PDOStatement *stmt, Array &info);
65 virtual int getAttribute(int64_t attr, Variant &value);
66 virtual bool checkLiveness();
67 virtual void persistentShutdown();
69 bool buffered() const { return m_buffered;}
70 unsigned long max_buffer_size() const { return m_max_buffer_size;}
71 bool fetch_table_names() const { return m_fetch_table_names;}
73 private:
74 MYSQL *m_server;
75 unsigned m_attached:1;
76 unsigned m_buffered:1;
77 unsigned m_emulate_prepare:1;
78 unsigned m_fetch_table_names:1;
79 unsigned long m_max_buffer_size;
80 PDOMySqlError m_einfo;
83 class PDOMySqlStatement : public PDOStatement {
84 public:
85 PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server);
86 virtual ~PDOMySqlStatement();
88 bool create(CStrRef sql, CArrRef options);
90 virtual bool support(SupportedMethod method);
91 virtual bool executer();
92 virtual bool fetcher(PDOFetchOrientation ori, long offset);
93 virtual bool describer(int colno);
94 virtual bool getColumn(int colno, Variant &value);
95 virtual bool paramHook(PDOBoundParam *param, PDOParamEvent event_type);
96 virtual bool getColumnMeta(int64_t colno, Array &return_value);
97 virtual bool nextRowset();
98 virtual bool cursorCloser();
100 MYSQL_STMT *stmt() { return m_stmt;}
102 private:
103 PDOMySqlConnection *m_conn;
104 MYSQL *m_server;
105 MYSQL_RES *m_result;
106 const MYSQL_FIELD *m_fields;
107 MYSQL_ROW m_current_data;
108 long *m_current_lengths;
109 PDOMySqlError m_einfo;
110 MYSQL_STMT *m_stmt;
111 int m_num_params;
112 MYSQL_BIND *m_params;
113 my_bool *m_in_null;
114 unsigned long *m_in_length;
115 MYSQL_BIND *m_bound_result;
116 my_bool *m_out_null;
117 unsigned long *m_out_length;
118 unsigned int m_params_given;
119 unsigned m_max_length:1;
121 void setRowCount();
122 bool executePrepared();
123 int handleError(const char *file, int line);
126 ///////////////////////////////////////////////////////////////////////////////
128 /* For the convenience of drivers, this function will parse a data source
129 * string, of the form "name=value; name2=value2" and populate variables
130 * according to the data you pass in and array of pdo_data_src_parser structures */
131 struct pdo_data_src_parser {
132 const char *optname;
133 char *optval;
134 int freeme;
137 static int php_pdo_parse_data_source(const char *data_source,
138 int data_source_len,
139 struct pdo_data_src_parser *parsed,
140 int nparams) {
141 int i, j;
142 int valstart = -1;
143 int semi = -1;
144 int optstart = 0;
145 int nlen;
146 int n_matches = 0;
148 i = 0;
149 while (i < data_source_len) {
150 /* looking for NAME= */
152 if (data_source[i] == '\0') {
153 break;
156 if (data_source[i] != '=') {
157 ++i;
158 continue;
161 valstart = ++i;
163 /* now we're looking for VALUE; or just VALUE<NUL> */
164 semi = -1;
165 while (i < data_source_len) {
166 if (data_source[i] == '\0') {
167 semi = i++;
168 break;
170 if (data_source[i] == ';') {
171 semi = i++;
172 break;
174 ++i;
177 if (semi == -1) {
178 semi = i;
181 /* find the entry in the array */
182 nlen = valstart - optstart - 1;
183 for (j = 0; j < nparams; j++) {
184 if (0 == strncmp(data_source + optstart, parsed[j].optname, nlen) &&
185 parsed[j].optname[nlen] == '\0') {
186 /* got a match */
187 if (parsed[j].freeme) {
188 free(parsed[j].optval);
190 parsed[j].optval = strndup(data_source + valstart, semi - valstart);
191 parsed[j].freeme = 1;
192 ++n_matches;
193 break;
197 while (i < data_source_len && isspace(data_source[i])) {
198 i++;
201 optstart = i;
204 return n_matches;
207 static long pdo_attr_lval(CArrRef options, int opt, long defaultValue) {
208 if (options.exists(opt)) {
209 return options[opt].toInt64();
211 return defaultValue;
214 static String pdo_attr_strval(CArrRef options, int opt, const char *def) {
215 if (options.exists(opt)) {
216 return options[opt].toString();
218 if (def) {
219 return def;
221 return String();
224 ///////////////////////////////////////////////////////////////////////////////
226 PDOMySqlConnection::PDOMySqlConnection()
227 : m_server(NULL), m_attached(0), m_buffered(0), m_emulate_prepare(0),
228 m_fetch_table_names(0), m_max_buffer_size(0) {
231 PDOMySqlConnection::~PDOMySqlConnection() {
232 if (m_server) {
233 mysql_close(m_server);
235 if (m_einfo.errmsg) {
236 free(m_einfo.errmsg);
240 bool PDOMySqlConnection::create(CArrRef options) {
241 int i, ret = 0;
242 char *host = NULL, *unix_socket = NULL;
243 unsigned int port = 3306;
244 char *dbname;
245 struct pdo_data_src_parser vars[] = {
246 { "charset", NULL, 0 },
247 { "dbname", "", 0 },
248 { "host", "localhost", 0 },
249 { "port", "3306", 0 },
250 { "unix_socket", MYSQL_UNIX_ADDR, 0 },
252 int connect_opts = 0
253 #ifdef CLIENT_MULTI_RESULTS
254 |CLIENT_MULTI_RESULTS
255 #endif
256 #ifdef CLIENT_MULTI_STATEMENTS
257 |CLIENT_MULTI_STATEMENTS
258 #endif
261 php_pdo_parse_data_source(data_source.data(), data_source.size(), vars, 5);
263 /* handle for the server */
264 if (!(m_server = mysql_init(NULL))) {
265 handleError(__FILE__, __LINE__);
266 goto cleanup;
269 m_max_buffer_size = 1024*1024;
270 m_buffered = m_emulate_prepare = 1;
272 /* handle MySQL options */
273 if (!options.empty()) {
274 long connect_timeout = pdo_attr_lval(options, PDO_ATTR_TIMEOUT, 30);
275 long local_infile = pdo_attr_lval(options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
276 String init_cmd, default_file, default_group;
277 long compress = 0;
278 m_buffered = pdo_attr_lval(options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
280 m_emulate_prepare = pdo_attr_lval(options, PDO_MYSQL_ATTR_DIRECT_QUERY,
281 m_emulate_prepare);
282 m_emulate_prepare = pdo_attr_lval(options, PDO_ATTR_EMULATE_PREPARES,
283 m_emulate_prepare);
285 m_max_buffer_size = pdo_attr_lval(options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
286 m_max_buffer_size);
288 if (pdo_attr_lval(options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
289 connect_opts |= CLIENT_FOUND_ROWS;
291 if (pdo_attr_lval(options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
292 connect_opts |= CLIENT_IGNORE_SPACE;
295 if (mysql_options(m_server, MYSQL_OPT_CONNECT_TIMEOUT,
296 (const char *)&connect_timeout)) {
297 handleError(__FILE__, __LINE__);
298 goto cleanup;
301 if (mysql_options(m_server, MYSQL_OPT_LOCAL_INFILE,
302 (const char *)&local_infile)) {
303 handleError(__FILE__, __LINE__);
304 goto cleanup;
306 #ifdef MYSQL_OPT_RECONNECT
307 /* since 5.0.3, the default for this option is 0 if not specified.
308 * we want the old behaviour */
310 long reconnect = 1;
311 mysql_options(m_server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
313 #endif
314 init_cmd = pdo_attr_strval(options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
315 if (!init_cmd.empty()) {
316 if (mysql_options(m_server, MYSQL_INIT_COMMAND, init_cmd.data())) {
317 handleError(__FILE__, __LINE__);
318 goto cleanup;
322 default_file = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
323 NULL);
324 if (!default_file.empty()) {
325 if (mysql_options(m_server, MYSQL_READ_DEFAULT_FILE,
326 default_file.data())) {
327 handleError(__FILE__, __LINE__);
328 goto cleanup;
332 default_group = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
333 NULL);
334 if (!default_group.empty()) {
335 if (mysql_options(m_server, MYSQL_READ_DEFAULT_GROUP,
336 default_group.data())) {
337 handleError(__FILE__, __LINE__);
338 goto cleanup;
342 compress = pdo_attr_lval(options, PDO_MYSQL_ATTR_COMPRESS, 0);
343 if (compress) {
344 if (mysql_options(m_server, MYSQL_OPT_COMPRESS, 0)) {
345 handleError(__FILE__, __LINE__);
346 goto cleanup;
351 dbname = vars[1].optval;
352 host = vars[2].optval;
353 if (vars[3].optval) {
354 port = atoi(vars[3].optval);
356 if (vars[2].optval && !strcmp("localhost", vars[2].optval)) {
357 unix_socket = vars[4].optval;
360 /* TODO: - Check zval cache + ZTS */
361 if (mysql_real_connect(m_server, host, username.c_str(), password.c_str(),
362 dbname, port, unix_socket, connect_opts) == NULL) {
363 handleError(__FILE__, __LINE__);
364 goto cleanup;
367 if (!auto_commit) {
368 mysql_autocommit(m_server, auto_commit);
371 m_attached = 1;
373 alloc_own_columns = 1;
374 max_escaped_char_length = 2;
376 ret = 1;
378 cleanup:
379 for (i = 0; i < (int)(sizeof(vars)/sizeof(vars[0])); i++) {
380 if (vars[i].freeme) {
381 free(vars[i].optval);
385 return ret;
388 bool PDOMySqlConnection::support(SupportedMethod method) {
389 return true;
392 bool PDOMySqlConnection::closer() {
393 if (m_server) {
394 mysql_close(m_server);
395 m_server = NULL;
397 if (m_einfo.errmsg) {
398 free(m_einfo.errmsg);
399 m_einfo.errmsg = NULL;
401 return false;
404 int PDOMySqlConnection::handleError(const char *file, int line,
405 PDOMySqlStatement *stmt) {
406 PDOErrorType *pdo_err;
407 PDOMySqlError *einfo = &m_einfo;
409 if (stmt) {
410 pdo_err = &stmt->error_code;
411 } else {
412 pdo_err = &error_code;
415 if (stmt && stmt->stmt()) {
416 einfo->errcode = mysql_stmt_errno(stmt->stmt());
417 } else {
418 einfo->errcode = mysql_errno(m_server);
421 einfo->file = file;
422 einfo->line = line;
424 if (einfo->errmsg) {
425 free(einfo->errmsg);
426 einfo->errmsg = NULL;
429 if (einfo->errcode) {
430 if (einfo->errcode == 2014) {
431 einfo->errmsg =
432 strdup("Cannot execute queries while other unbuffered queries are "
433 "active. Consider using PDOStatement::fetchAll(). "
434 "Alternatively, if your code is only ever going to run against "
435 "mysql, you may enable query buffering by setting the "
436 "PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.");
437 } else if (einfo->errcode == 2057) {
438 einfo->errmsg =
439 strdup("A stored procedure returning result sets of different size "
440 "was called. This is not supported by libmysql");
441 } else {
442 einfo->errmsg = strdup(mysql_error(m_server));
444 } else { /* no error */
445 strcpy(*pdo_err, PDO_ERR_NONE);
446 return false;
449 if (stmt && stmt->stmt()) {
450 strcpy(*pdo_err, mysql_stmt_sqlstate(stmt->stmt()));
451 } else {
452 strcpy(*pdo_err, mysql_sqlstate(m_server));
455 if (stmt && stmt->stmt()) {
456 pdo_raise_impl_error(stmt->dbh, NULL, pdo_err[0], einfo->errmsg);
457 } else {
458 throw_pdo_exception((int)einfo->errcode, uninit_null(), "SQLSTATE[%s] [%d] %s",
459 pdo_err[0], einfo->errcode, einfo->errmsg);
461 return einfo->errcode;
464 bool PDOMySqlConnection::preparer(CStrRef sql, sp_PDOStatement *stmt,
465 CVarRef options) {
466 PDOMySqlStatement *s = new PDOMySqlStatement(this, m_server);
467 *stmt = s;
469 if (m_emulate_prepare) {
470 return true;
472 int server_version = mysql_get_server_version(m_server);
473 if (server_version < 40100) {
474 return true;
477 if (s->create(sql, options.toArray())) {
478 alloc_own_columns = 1;
479 return true;
482 stmt->reset();
483 strcpy(error_code, s->error_code);
484 return false;
487 int64_t PDOMySqlConnection::doer(CStrRef sql) {
488 if (mysql_real_query(m_server, sql.data(), sql.size())) {
489 handleError(__FILE__, __LINE__);
490 return -1;
493 my_ulonglong c = mysql_affected_rows(m_server);
494 if (c == (my_ulonglong) -1) {
495 handleError(__FILE__, __LINE__);
496 return m_einfo.errcode ? -1 : 0;
499 /* MULTI_QUERY support - eat up all unfetched result sets */
500 while (mysql_more_results(m_server)) {
501 if (mysql_next_result(m_server)) {
502 return true;
504 MYSQL_RES *result = mysql_store_result(m_server);
505 if (result) {
506 mysql_free_result(result);
509 return c;
512 bool PDOMySqlConnection::quoter(CStrRef input, String &quoted,
513 PDOParamType paramtype) {
514 String s(2 * input.size() + 3, ReserveString);
515 char *buf = s.mutableSlice().ptr;
516 int len = mysql_real_escape_string(m_server, buf + 1,
517 input.data(), input.size());
518 len++;
519 buf[0] = buf[len] = '\'';
520 len++;
521 quoted = s.setSize(len);
522 return true;
525 bool PDOMySqlConnection::begin() {
526 return doer("START TRANSACTION") >= 0;
529 bool PDOMySqlConnection::commit() {
530 return mysql_commit(m_server) >= 0;
533 bool PDOMySqlConnection::rollback() {
534 return mysql_rollback(m_server) >= 0;
537 bool PDOMySqlConnection::setAttribute(int64_t attr, CVarRef value) {
538 switch (attr) {
539 case PDO_ATTR_AUTOCOMMIT:
540 /* ignore if the new value equals the old one */
541 if (auto_commit ^ value.toBoolean()) {
542 auto_commit = value.toBoolean();
543 mysql_autocommit(m_server, auto_commit);
545 return true;
547 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
548 m_buffered = value.toBoolean();
549 return true;
550 case PDO_MYSQL_ATTR_DIRECT_QUERY:
551 case PDO_ATTR_EMULATE_PREPARES:
552 m_emulate_prepare = value.toBoolean();
553 return true;
554 case PDO_ATTR_FETCH_TABLE_NAMES:
555 m_fetch_table_names = value.toBoolean();
556 return true;
557 case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
558 if (value.toInt64() < 0) {
559 /* TODO: Johannes, can we throw a warning here? */
560 m_max_buffer_size = 1024*1024;
561 } else {
562 m_max_buffer_size = value.toInt64();
564 return true;
565 default:
566 return false;
570 String PDOMySqlConnection::lastId(const char *name) {
571 return (int64_t)mysql_insert_id(m_server);
574 bool PDOMySqlConnection::fetchErr(PDOStatement *stmt, Array &info) {
575 if (m_einfo.errcode) {
576 info.append((int64_t)m_einfo.errcode);
577 info.append(String(m_einfo.errmsg, CopyString));
579 return true;
582 int PDOMySqlConnection::getAttribute(int64_t attr, Variant &value) {
583 switch (attr) {
584 case PDO_ATTR_CLIENT_VERSION:
585 value = String((char *)mysql_get_client_info(), CopyString);
586 break;
587 case PDO_ATTR_SERVER_VERSION:
588 value = String((char *)mysql_get_server_info(m_server), CopyString);
589 break;
590 case PDO_ATTR_CONNECTION_STATUS:
591 value = String((char *)mysql_get_host_info(m_server), CopyString);
592 break;
593 case PDO_ATTR_SERVER_INFO: {
594 char *tmp = (char *)mysql_stat(m_server);
595 if (tmp) {
596 value = String(tmp, CopyString);
597 } else {
598 handleError(__FILE__, __LINE__);
599 return -1;
601 break;
603 case PDO_ATTR_AUTOCOMMIT:
604 value = (int64_t)auto_commit;
605 break;
606 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
607 value = (int64_t)m_buffered;
608 break;
609 case PDO_MYSQL_ATTR_DIRECT_QUERY:
610 value = (int64_t)m_emulate_prepare;
611 break;
612 case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
613 value = (int64_t)m_max_buffer_size;
614 break;
615 default:
616 return 0;
618 return 1;
621 bool PDOMySqlConnection::checkLiveness() {
622 return !mysql_ping(m_server);
625 void PDOMySqlConnection::persistentShutdown() {
626 // do nothing
629 ///////////////////////////////////////////////////////////////////////////////
631 void PDOMySqlStatement::setRowCount() {
632 my_ulonglong count = mysql_stmt_affected_rows(m_stmt);
633 if (count != (my_ulonglong)-1) {
634 row_count = count;
638 bool PDOMySqlStatement::executePrepared() {
639 /* (re)bind the parameters */
640 if (mysql_stmt_bind_param(m_stmt, m_params) || mysql_stmt_execute(m_stmt)) {
641 if (m_params) {
642 free(m_params);
643 m_params = 0;
645 handleError(__FILE__, __LINE__);
646 if (mysql_stmt_errno(m_stmt) == 2057) {
647 /* CR_NEW_STMT_METADATA makes the statement unusable */
648 m_stmt = NULL;
650 return false;
653 if (!m_result) {
654 int i;
656 /* figure out the result set format, if any */
657 m_result = mysql_stmt_result_metadata(m_stmt);
658 if (m_result) {
659 int calc_max_length = m_conn->buffered() && m_max_length == 1;
660 m_fields = mysql_fetch_fields(m_result);
661 if (m_bound_result) {
662 int i;
663 for (i = 0; i < column_count; i++) {
664 free(m_bound_result[i].buffer);
666 free(m_bound_result);
667 free(m_out_null);
668 free(m_out_length);
671 column_count = (int)mysql_num_fields(m_result);
672 m_bound_result = (MYSQL_BIND*)calloc(column_count, sizeof(MYSQL_BIND));
673 m_out_null = (my_bool*)calloc(column_count, sizeof(my_bool));
674 m_out_length = (unsigned long *)calloc(column_count,
675 sizeof(unsigned long));
677 /* summon memory to hold the row */
678 for (i = 0; i < column_count; i++) {
679 if (calc_max_length && m_fields[i].type == FIELD_TYPE_BLOB) {
680 my_bool on = 1;
681 mysql_stmt_attr_set(m_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
682 calc_max_length = 0;
684 switch (m_fields[i].type) {
685 case FIELD_TYPE_INT24:
686 m_bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
687 break;
688 case FIELD_TYPE_LONG:
689 m_bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
690 break;
691 case FIELD_TYPE_LONGLONG:
692 m_bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
693 break;
694 case FIELD_TYPE_TINY:
695 m_bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
696 break;
697 case FIELD_TYPE_SHORT:
698 m_bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
699 break;
700 default:
701 m_bound_result[i].buffer_length =
702 m_fields[i].max_length? m_fields[i].max_length:
703 m_fields[i].length;
704 /* work-around for longtext and alike */
705 if (m_bound_result[i].buffer_length > m_conn->max_buffer_size()) {
706 m_bound_result[i].buffer_length = m_conn->max_buffer_size();
710 /* there are cases where the length reported by mysql is too short.
711 * eg: when describing a table that contains an enum column. Since
712 * we have no way of knowing the true length either, we'll bump up
713 * our buffer size to a reasonable size, just in case */
714 if (m_fields[i].max_length == 0 &&
715 m_bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
716 m_bound_result[i].buffer_length = 128;
719 m_out_length[i] = 0;
721 m_bound_result[i].buffer = malloc(m_bound_result[i].buffer_length);
722 m_bound_result[i].is_null = &m_out_null[i];
723 m_bound_result[i].length = &m_out_length[i];
724 m_bound_result[i].buffer_type = MYSQL_TYPE_STRING;
727 if (mysql_stmt_bind_result(m_stmt, m_bound_result)) {
728 handleError(__FILE__, __LINE__);
729 return false;
732 /* if buffered, pre-fetch all the data */
733 if (m_conn->buffered()) {
734 mysql_stmt_store_result(m_stmt);
739 setRowCount();
740 return true;
743 static const char *type_to_name_native(int type) {
744 #define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
746 switch (type) {
747 PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
748 PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
749 #ifdef MYSQL_HAS_TINY
750 PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
751 #endif
752 PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
753 PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
754 PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
755 PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
756 PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
757 PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
758 PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
759 #ifdef FIELD_TYPE_NEWDECIMAL
760 PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
761 #endif
762 #ifdef FIELD_TYPE_GEOMETRY
763 PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
764 #endif
765 PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
766 #ifdef MYSQL_HAS_YEAR
767 PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
768 #endif
769 PDO_MYSQL_NATIVE_TYPE_NAME(SET)
770 PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
771 PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
772 #ifdef FIELD_TYPE_NEWDATE
773 PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
774 #endif
775 PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
776 PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
777 PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
778 PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
779 PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
780 PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
781 PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
782 default:
783 return NULL;
785 #undef PDO_MYSQL_NATIVE_TYPE_NAME
788 ///////////////////////////////////////////////////////////////////////////////
790 PDOMySqlStatement::PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server)
791 : m_conn(conn), m_server(server), m_result(NULL), m_fields(NULL),
792 m_current_data(NULL), m_current_lengths(NULL), m_stmt(NULL),
793 m_num_params(0), m_params(NULL), m_in_null(NULL), m_in_length(NULL),
794 m_bound_result(NULL), m_out_null(NULL), m_out_length(NULL),
795 m_params_given(0), m_max_length(0) {
798 PDOMySqlStatement::~PDOMySqlStatement() {
799 if (m_result) {
800 /* free the resource */
801 mysql_free_result(m_result);
802 m_result = NULL;
804 if (m_einfo.errmsg) {
805 free(m_einfo.errmsg);
806 m_einfo.errmsg = NULL;
808 if (m_stmt) {
809 mysql_stmt_close(m_stmt);
810 m_stmt = NULL;
813 if (m_params) {
814 free(m_params);
816 if (m_in_null) {
817 free(m_in_null);
819 if (m_in_length) {
820 free(m_in_length);
823 if (m_bound_result) {
824 int i;
825 for (i = 0; i < column_count; i++) {
826 free(m_bound_result[i].buffer);
829 free(m_bound_result);
830 free(m_out_null);
831 free(m_out_length);
834 if (m_server) {
835 while (mysql_more_results(m_server)) {
836 if (mysql_next_result(m_server) != 0) {
837 break;
839 MYSQL_RES *res = mysql_store_result(m_server);
840 if (res) {
841 mysql_free_result(res);
847 bool PDOMySqlStatement::create(CStrRef sql, CArrRef options) {
848 supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
850 String nsql;
851 int ret = pdo_parse_params(this, sql, nsql);
852 if (ret == 1) {
853 /* query was rewritten */
854 } else if (ret == -1) {
855 /* failed to parse */
856 return false;
857 } else {
858 nsql = sql;
861 if (!(m_stmt = mysql_stmt_init(m_server))) {
862 handleError(__FILE__, __LINE__);
863 return false;
866 if (mysql_stmt_prepare(m_stmt, nsql.data(), nsql.size())) {
867 /* TODO: might need to pull statement specific info here? */
868 /* if the query isn't supported by the protocol, fallback to emulation */
869 if (mysql_errno(m_server) == 1295) {
870 supports_placeholders = PDO_PLACEHOLDER_NONE;
871 return true;
873 handleError(__FILE__, __LINE__);
874 return false;
877 m_num_params = mysql_stmt_param_count(m_stmt);
878 if (m_num_params) {
879 m_params_given = 0;
880 m_params = (MYSQL_BIND*)calloc(m_num_params, sizeof(MYSQL_BIND));
881 m_in_null = (my_bool*)calloc(m_num_params, sizeof(my_bool));
882 m_in_length = (unsigned long*)calloc(m_num_params, sizeof(unsigned long));
885 m_max_length = pdo_attr_lval(options, PDO_ATTR_MAX_COLUMN_LEN, 0);
886 return true;
889 bool PDOMySqlStatement::support(SupportedMethod method) {
890 switch (method) {
891 case MethodSetAttribute:
892 case MethodGetAttribute:
893 return false;
894 default:
895 break;
897 return true;
900 int PDOMySqlStatement::handleError(const char *file, int line) {
901 PDOMySqlConnection *conn = dynamic_cast<PDOMySqlConnection*>(dbh.get());
902 assert(conn);
903 return conn->handleError(file, line, this);
906 bool PDOMySqlStatement::executer() {
907 if (m_stmt) {
908 return executePrepared();
911 /* ensure that we free any previous unfetched results */
912 if (m_result) {
913 mysql_free_result(m_result);
914 m_result = NULL;
917 if (mysql_real_query(m_server, active_query_string.data(),
918 active_query_string.size()) != 0) {
919 handleError(__FILE__, __LINE__);
920 return false;
923 my_ulonglong row_count = mysql_affected_rows(m_server);
924 if (row_count == (my_ulonglong)-1) {
925 /* we either have a query that returned a result set or an error occured
926 lets see if we have access to a result set */
927 if (!m_conn->buffered()) {
928 m_result = mysql_use_result(m_server);
929 } else {
930 m_result = mysql_store_result(m_server);
932 if (NULL == m_result) {
933 handleError(__FILE__, __LINE__);
934 return false;
937 row_count = mysql_num_rows(m_result);
938 column_count = (int) mysql_num_fields(m_result);
939 m_fields = mysql_fetch_fields(m_result);
943 return true;
946 bool PDOMySqlStatement::fetcher(PDOFetchOrientation ori, long offset) {
947 int ret;
948 if (m_stmt) {
949 ret = mysql_stmt_fetch(m_stmt);
950 if (ret == MYSQL_DATA_TRUNCATED) {
951 ret = 0;
953 if (ret) {
954 if (ret != MYSQL_NO_DATA) {
955 handleError(__FILE__, __LINE__);
957 return false;
959 return true;
962 if (!m_result) {
963 strcpy(error_code, "HY000");
964 return false;
967 if ((m_current_data = mysql_fetch_row(m_result)) == NULL) {
968 if (mysql_errno(m_server)) {
969 handleError(__FILE__, __LINE__);
971 return false;
974 m_current_lengths = (long int *)mysql_fetch_lengths(m_result);
975 return true;
978 bool PDOMySqlStatement::describer(int colno) {
979 if (!m_result) {
980 return false;
983 if (colno < 0 || colno >= column_count) {
984 /* error invalid column */
985 return false;
988 if (columns.empty()) {
989 for (int i = 0; i < column_count; i++) {
990 columns.set(i, Resource(new PDOColumn()));
994 // fetch all on demand, this seems easiest if we've been here before bail out
995 PDOColumn *col = columns[0].toResource().getTyped<PDOColumn>();
996 if (!col->name.empty()) {
997 return true;
999 for (int i = 0; i < column_count; i++) {
1000 col = columns[i].toResource().getTyped<PDOColumn>();
1002 if (m_conn->fetch_table_names()) {
1003 col->name = String(m_fields[i].table) + "." +
1004 String(m_fields[i].name);
1005 } else {
1006 col->name = String(m_fields[i].name, CopyString);
1009 col->precision = m_fields[i].decimals;
1010 col->maxlen = m_fields[i].length;
1011 col->param_type = PDO_PARAM_STR;
1013 return true;
1016 bool PDOMySqlStatement::getColumn(int colno, Variant &value) {
1017 if (!m_result) {
1018 return false;
1021 if (!m_stmt) {
1022 if (m_current_data == NULL || !m_result) {
1023 return false;
1026 if (colno < 0 || colno >= column_count) {
1027 /* error invalid column */
1028 return false;
1030 char *ptr; int len;
1031 if (m_stmt) {
1032 if (m_out_null[colno]) {
1033 value = String();
1034 return true;
1036 ptr = (char*)m_bound_result[colno].buffer;
1037 if (m_out_length[colno] > m_bound_result[colno].buffer_length) {
1038 /* mysql lied about the column width */
1039 strcpy(error_code, "01004"); /* truncated */
1040 m_out_length[colno] = m_bound_result[colno].buffer_length;
1041 len = m_out_length[colno];
1042 value = String(ptr, len, CopyString);
1043 return false;
1045 len = m_out_length[colno];
1046 value = String(ptr, len, CopyString);
1047 return true;
1049 ptr = m_current_data[colno];
1050 len = m_current_lengths[colno];
1051 value = String(ptr, len, CopyString);
1052 return true;
1055 bool PDOMySqlStatement::paramHook(PDOBoundParam *param,
1056 PDOParamEvent event_type) {
1057 MYSQL_BIND *b;
1058 if (m_stmt && param->is_param) {
1059 switch (event_type) {
1060 case PDO_PARAM_EVT_ALLOC:
1061 /* sanity check parameter number range */
1062 if (param->paramno < 0 || param->paramno >= m_num_params) {
1063 strcpy(error_code, "HY093");
1064 return false;
1066 m_params_given++;
1068 b = &m_params[param->paramno];
1069 param->driver_data = b;
1070 b->is_null = &m_in_null[param->paramno];
1071 b->length = &m_in_length[param->paramno];
1072 /* recall how many parameters have been provided */
1073 return true;
1075 case PDO_PARAM_EVT_EXEC_PRE:
1076 if ((int)m_params_given < m_num_params) {
1077 /* too few parameter bound */
1078 strcpy(error_code, "HY093");
1079 return false;
1082 b = (MYSQL_BIND*)param->driver_data;
1083 *b->is_null = 0;
1084 if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
1085 param->parameter.isNull()) {
1086 *b->is_null = 1;
1087 b->buffer_type = MYSQL_TYPE_STRING;
1088 b->buffer = NULL;
1089 b->buffer_length = 0;
1090 *b->length = 0;
1091 return true;
1094 switch (PDO_PARAM_TYPE(param->param_type)) {
1095 case PDO_PARAM_STMT:
1096 return false;
1097 case PDO_PARAM_LOB:
1098 if (param->parameter.isResource()) {
1099 Variant buf = f_stream_get_contents(param->parameter.toResource());
1100 if (!same(buf, false)) {
1101 param->parameter = buf;
1102 } else {
1103 pdo_raise_impl_error(dbh, this, "HY105",
1104 "Expected a stream resource");
1105 return false;
1108 /* fall through */
1110 default:
1114 if (param->parameter.isString()) {
1115 String sparam = param->parameter.toString();
1116 b->buffer_type = MYSQL_TYPE_STRING;
1117 b->buffer = (void*)sparam.data();
1118 b->buffer_length = sparam.size();
1119 *b->length = sparam.size();
1120 return true;
1122 if (param->parameter.isInteger()) {
1123 param->parameter = param->parameter.toInt64();
1124 b->buffer_type = MYSQL_TYPE_LONG;
1125 b->buffer = param->parameter.getInt64Data();
1126 return true;
1128 if (param->parameter.isDouble()) {
1129 b->buffer_type = MYSQL_TYPE_DOUBLE;
1130 b->buffer = param->parameter.getDoubleData();
1131 return true;
1133 return false;
1134 case PDO_PARAM_EVT_FREE:
1135 case PDO_PARAM_EVT_EXEC_POST:
1136 case PDO_PARAM_EVT_FETCH_PRE:
1137 case PDO_PARAM_EVT_FETCH_POST:
1138 case PDO_PARAM_EVT_NORMALIZE:
1139 /* do nothing */
1140 break;
1143 return true;
1146 static const StaticString s_mysql_def("mysql:def");
1147 static const StaticString s_not_null("not_null");
1148 static const StaticString s_primary_key("primary_key");
1149 static const StaticString s_multiple_key("multiple_key");
1150 static const StaticString s_unique_key("unique_key");
1151 static const StaticString s_blob("blob");
1152 static const StaticString s_native_type("native_type");
1153 static const StaticString s_flags("flags");
1154 static const StaticString s_table("table");
1156 bool PDOMySqlStatement::getColumnMeta(int64_t colno, Array &ret) {
1157 if (!m_result) {
1158 return false;
1160 if (colno < 0 || colno >= column_count) {
1161 /* error invalid column */
1162 return false;
1165 Array flags = Array::Create();
1167 const MYSQL_FIELD *F = m_fields + colno;
1168 if (F->def) {
1169 ret.set(s_mysql_def, String(F->def, CopyString));
1171 if (IS_NOT_NULL(F->flags)) {
1172 flags.append(s_not_null);
1174 if (IS_PRI_KEY(F->flags)) {
1175 flags.append(s_primary_key);
1177 if (F->flags & MULTIPLE_KEY_FLAG) {
1178 flags.append(s_multiple_key);
1180 if (F->flags & UNIQUE_KEY_FLAG) {
1181 flags.append(s_unique_key);
1183 if (IS_BLOB(F->flags)) {
1184 flags.append(s_blob);
1186 const char *str = type_to_name_native(F->type);
1187 if (str) {
1188 ret.set(s_native_type, str);
1190 ret.set(s_flags, flags);
1191 ret.set(s_table, String(F->table, CopyString));
1192 return true;
1195 bool PDOMySqlStatement::nextRowset() {
1196 /* ensure that we free any previous unfetched results */
1197 if (m_stmt) {
1198 column_count = (int)mysql_num_fields(m_result);
1199 mysql_stmt_free_result(m_stmt);
1201 if (m_result) {
1202 mysql_free_result(m_result);
1203 m_result = NULL;
1206 int ret = mysql_next_result(m_server);
1207 if (ret > 0) {
1208 handleError(__FILE__, __LINE__);
1209 return false;
1211 if (ret < 0) {
1212 /* No more results */
1213 return false;
1216 my_ulonglong row_count;
1217 if (!m_conn->buffered()) {
1218 m_result = mysql_use_result(m_server);
1219 row_count = 0;
1220 } else {
1221 m_result = mysql_store_result(m_server);
1222 if ((my_ulonglong)-1 == (row_count = mysql_affected_rows(m_server))) {
1223 handleError(__FILE__, __LINE__);
1224 return false;
1228 if (!m_result) {
1229 return false;
1232 column_count = (int)mysql_num_fields(m_result);
1233 m_fields = mysql_fetch_fields(m_result);
1234 return true;
1237 bool PDOMySqlStatement::cursorCloser() {
1238 if (m_result) {
1239 mysql_free_result(m_result);
1240 m_result = NULL;
1242 if (m_stmt) {
1243 return !mysql_stmt_free_result(m_stmt);
1246 while (mysql_more_results(m_server)) {
1247 if (mysql_next_result(m_server) != 0) {
1248 break;
1250 MYSQL_RES *res = mysql_store_result(m_server);
1251 if (res) {
1252 mysql_free_result(res);
1255 return true;
1258 ///////////////////////////////////////////////////////////////////////////////
1260 PDOMySql::PDOMySql() : PDODriver("mysql") {
1263 PDOConnection *PDOMySql::createConnectionObject() {
1264 return new PDOMySqlConnection();
1267 ///////////////////////////////////////////////////////////////////////////////