PageRenderTime 29ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/ext/pdo_mysql.cpp

https://github.com/tstarling/hiphop-php
C++ | 1289 lines | 1061 code | 158 blank | 70 comment | 217 complexity | 68b2cd1fab8e960e150c0a7a523482f7 MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2014 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. +----------------------------------------------------------------------+
  16. */
  17. #include "hphp/runtime/ext/pdo_mysql.h"
  18. #include "hphp/runtime/ext/stream/ext_stream.h"
  19. #include "hphp/runtime/vm/jit/translator-inline.h"
  20. #include "mysql.h"
  21. #ifdef PHP_MYSQL_UNIX_SOCK_ADDR
  22. #ifdef MYSQL_UNIX_ADDR
  23. #undef MYSQL_UNIX_ADDR
  24. #endif
  25. #define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
  26. #endif
  27. namespace HPHP {
  28. IMPLEMENT_DEFAULT_EXTENSION_VERSION(pdo_mysql, 1.0.2);
  29. ///////////////////////////////////////////////////////////////////////////////
  30. class PDOMySqlError {
  31. public:
  32. PDOMySqlError() : file(NULL), line(0), errcode(0), errmsg(NULL) {
  33. }
  34. const char *file;
  35. int line;
  36. unsigned int errcode;
  37. char *errmsg;
  38. };
  39. class PDOMySqlStatement;
  40. class PDOMySqlConnection : public PDOConnection {
  41. public:
  42. PDOMySqlConnection();
  43. virtual ~PDOMySqlConnection();
  44. virtual bool create(const Array& options);
  45. int handleError(const char *file, int line, PDOMySqlStatement *stmt = NULL);
  46. virtual bool support(SupportedMethod method);
  47. virtual bool closer();
  48. virtual bool preparer(const String& sql, sp_PDOStatement *stmt, const Variant& options);
  49. virtual int64_t doer(const String& sql);
  50. virtual bool quoter(const String& input, String &quoted, PDOParamType paramtype);
  51. virtual bool begin();
  52. virtual bool commit();
  53. virtual bool rollback();
  54. virtual bool setAttribute(int64_t attr, const Variant& value);
  55. virtual String lastId(const char *name);
  56. virtual bool fetchErr(PDOStatement *stmt, Array &info);
  57. virtual int getAttribute(int64_t attr, Variant &value);
  58. virtual bool checkLiveness();
  59. virtual void persistentShutdown();
  60. bool buffered() const { return m_buffered;}
  61. unsigned long max_buffer_size() const { return m_max_buffer_size;}
  62. bool fetch_table_names() const { return m_fetch_table_names;}
  63. private:
  64. MYSQL *m_server;
  65. unsigned m_attached:1;
  66. unsigned m_buffered:1;
  67. unsigned m_emulate_prepare:1;
  68. unsigned m_fetch_table_names:1;
  69. unsigned long m_max_buffer_size;
  70. PDOMySqlError m_einfo;
  71. };
  72. class PDOMySqlStatement : public PDOStatement {
  73. public:
  74. DECLARE_RESOURCE_ALLOCATION(PDOMySqlStatement);
  75. PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server);
  76. virtual ~PDOMySqlStatement();
  77. bool create(const String& sql, const Array& options);
  78. virtual bool support(SupportedMethod method);
  79. virtual bool executer();
  80. virtual bool fetcher(PDOFetchOrientation ori, long offset);
  81. virtual bool describer(int colno);
  82. virtual bool getColumn(int colno, Variant &value);
  83. virtual bool paramHook(PDOBoundParam *param, PDOParamEvent event_type);
  84. virtual bool getColumnMeta(int64_t colno, Array &return_value);
  85. virtual bool nextRowset();
  86. virtual bool cursorCloser();
  87. MYSQL_STMT *stmt() { return m_stmt;}
  88. private:
  89. PDOMySqlConnection *m_conn;
  90. MYSQL *m_server;
  91. MYSQL_RES *m_result;
  92. const MYSQL_FIELD *m_fields;
  93. MYSQL_ROW m_current_data;
  94. long *m_current_lengths;
  95. PDOMySqlError m_einfo;
  96. MYSQL_STMT *m_stmt;
  97. int m_num_params;
  98. MYSQL_BIND *m_params;
  99. my_bool *m_in_null;
  100. unsigned long *m_in_length;
  101. MYSQL_BIND *m_bound_result;
  102. my_bool *m_out_null;
  103. unsigned long *m_out_length;
  104. unsigned int m_params_given;
  105. unsigned m_max_length:1;
  106. void setRowCount();
  107. bool executePrepared();
  108. int handleError(const char *file, int line);
  109. };
  110. ///////////////////////////////////////////////////////////////////////////////
  111. /* For the convenience of drivers, this function will parse a data source
  112. * string, of the form "name=value; name2=value2" and populate variables
  113. * according to the data you pass in and array of pdo_data_src_parser structures */
  114. struct pdo_data_src_parser {
  115. const char *optname;
  116. char *optval;
  117. int freeme;
  118. };
  119. static int php_pdo_parse_data_source(const char *data_source,
  120. int data_source_len,
  121. struct pdo_data_src_parser *parsed,
  122. int nparams) {
  123. int i, j;
  124. int valstart = -1;
  125. int semi = -1;
  126. int optstart = 0;
  127. int nlen;
  128. int n_matches = 0;
  129. i = 0;
  130. while (i < data_source_len) {
  131. /* looking for NAME= */
  132. if (data_source[i] == '\0') {
  133. break;
  134. }
  135. if (data_source[i] != '=') {
  136. ++i;
  137. continue;
  138. }
  139. valstart = ++i;
  140. /* now we're looking for VALUE; or just VALUE<NUL> */
  141. semi = -1;
  142. while (i < data_source_len) {
  143. if (data_source[i] == '\0') {
  144. semi = i++;
  145. break;
  146. }
  147. if (data_source[i] == ';') {
  148. semi = i++;
  149. break;
  150. }
  151. ++i;
  152. }
  153. if (semi == -1) {
  154. semi = i;
  155. }
  156. /* find the entry in the array */
  157. nlen = valstart - optstart - 1;
  158. for (j = 0; j < nparams; j++) {
  159. if (0 == strncmp(data_source + optstart, parsed[j].optname, nlen) &&
  160. parsed[j].optname[nlen] == '\0') {
  161. /* got a match */
  162. if (parsed[j].freeme) {
  163. free(parsed[j].optval);
  164. }
  165. parsed[j].optval = strndup(data_source + valstart, semi - valstart);
  166. parsed[j].freeme = 1;
  167. ++n_matches;
  168. break;
  169. }
  170. }
  171. while (i < data_source_len && isspace(data_source[i])) {
  172. i++;
  173. }
  174. optstart = i;
  175. }
  176. return n_matches;
  177. }
  178. static long pdo_attr_lval(const Array& options, int opt, long defaultValue) {
  179. if (options.exists(opt)) {
  180. return options[opt].toInt64();
  181. }
  182. return defaultValue;
  183. }
  184. static String pdo_attr_strval(const Array& options, int opt, const char *def) {
  185. if (options.exists(opt)) {
  186. return options[opt].toString();
  187. }
  188. if (def) {
  189. return def;
  190. }
  191. return String();
  192. }
  193. ///////////////////////////////////////////////////////////////////////////////
  194. PDOMySqlConnection::PDOMySqlConnection()
  195. : m_server(NULL), m_attached(0), m_buffered(0), m_emulate_prepare(0),
  196. m_fetch_table_names(0), m_max_buffer_size(0) {
  197. }
  198. PDOMySqlConnection::~PDOMySqlConnection() {
  199. if (m_server) {
  200. mysql_close(m_server);
  201. }
  202. if (m_einfo.errmsg) {
  203. free(m_einfo.errmsg);
  204. }
  205. }
  206. bool PDOMySqlConnection::create(const Array& options) {
  207. int i, ret = 0;
  208. char *host = NULL, *unix_socket = NULL;
  209. unsigned int port = 3306;
  210. char *dbname;
  211. char *charset = nullptr;
  212. struct pdo_data_src_parser vars[] = {
  213. { "charset", nullptr, 0 },
  214. { "dbname", "", 0 },
  215. { "host", "localhost", 0 },
  216. { "port", "3306", 0 },
  217. { "unix_socket", MYSQL_UNIX_ADDR, 0 },
  218. };
  219. int connect_opts = 0
  220. #ifdef CLIENT_MULTI_RESULTS
  221. |CLIENT_MULTI_RESULTS
  222. #endif
  223. #ifdef CLIENT_MULTI_STATEMENTS
  224. |CLIENT_MULTI_STATEMENTS
  225. #endif
  226. ;
  227. php_pdo_parse_data_source(data_source.data(), data_source.size(), vars, 5);
  228. /* handle for the server */
  229. if (!(m_server = mysql_init(NULL))) {
  230. handleError(__FILE__, __LINE__);
  231. goto cleanup;
  232. }
  233. m_max_buffer_size = 1024*1024;
  234. m_buffered = m_emulate_prepare = 1;
  235. charset = vars[0].optval;
  236. /* handle MySQL options */
  237. if (!options.empty()) {
  238. long connect_timeout = pdo_attr_lval(options, PDO_ATTR_TIMEOUT, 30);
  239. long local_infile = pdo_attr_lval(options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
  240. String init_cmd, default_file, default_group;
  241. long compress = 0;
  242. m_buffered = pdo_attr_lval(options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
  243. m_emulate_prepare = pdo_attr_lval(options, PDO_MYSQL_ATTR_DIRECT_QUERY,
  244. m_emulate_prepare);
  245. m_emulate_prepare = pdo_attr_lval(options, PDO_ATTR_EMULATE_PREPARES,
  246. m_emulate_prepare);
  247. m_max_buffer_size = pdo_attr_lval(options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
  248. m_max_buffer_size);
  249. if (pdo_attr_lval(options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
  250. connect_opts |= CLIENT_FOUND_ROWS;
  251. }
  252. if (pdo_attr_lval(options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
  253. connect_opts |= CLIENT_IGNORE_SPACE;
  254. }
  255. if (mysql_options(m_server, MYSQL_OPT_CONNECT_TIMEOUT,
  256. (const char *)&connect_timeout)) {
  257. handleError(__FILE__, __LINE__);
  258. goto cleanup;
  259. }
  260. if (mysql_options(m_server, MYSQL_OPT_LOCAL_INFILE,
  261. (const char *)&local_infile)) {
  262. handleError(__FILE__, __LINE__);
  263. goto cleanup;
  264. }
  265. #ifdef MYSQL_OPT_RECONNECT
  266. /* since 5.0.3, the default for this option is 0 if not specified.
  267. * we want the old behaviour */
  268. {
  269. long reconnect = 1;
  270. mysql_options(m_server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
  271. }
  272. #endif
  273. init_cmd = pdo_attr_strval(options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
  274. if (!init_cmd.empty()) {
  275. if (mysql_options(m_server, MYSQL_INIT_COMMAND, init_cmd.data())) {
  276. handleError(__FILE__, __LINE__);
  277. goto cleanup;
  278. }
  279. }
  280. default_file = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
  281. NULL);
  282. if (!default_file.empty()) {
  283. if (mysql_options(m_server, MYSQL_READ_DEFAULT_FILE,
  284. default_file.data())) {
  285. handleError(__FILE__, __LINE__);
  286. goto cleanup;
  287. }
  288. }
  289. default_group = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
  290. NULL);
  291. if (!default_group.empty()) {
  292. if (mysql_options(m_server, MYSQL_READ_DEFAULT_GROUP,
  293. default_group.data())) {
  294. handleError(__FILE__, __LINE__);
  295. goto cleanup;
  296. }
  297. }
  298. compress = pdo_attr_lval(options, PDO_MYSQL_ATTR_COMPRESS, 0);
  299. if (compress) {
  300. if (mysql_options(m_server, MYSQL_OPT_COMPRESS, 0)) {
  301. handleError(__FILE__, __LINE__);
  302. goto cleanup;
  303. }
  304. }
  305. }
  306. if (charset) {
  307. if (mysql_options(m_server, MYSQL_SET_CHARSET_NAME, charset)) {
  308. handleError(__FILE__, __LINE__);
  309. goto cleanup;
  310. }
  311. }
  312. dbname = vars[1].optval;
  313. host = vars[2].optval;
  314. if (vars[3].optval) {
  315. port = atoi(vars[3].optval);
  316. }
  317. if (vars[2].optval && !strcmp("localhost", vars[2].optval)) {
  318. unix_socket = vars[4].optval;
  319. }
  320. /* TODO: - Check zval cache + ZTS */
  321. if (mysql_real_connect(m_server, host, username.c_str(), password.c_str(),
  322. dbname, port, unix_socket, connect_opts) == NULL) {
  323. handleError(__FILE__, __LINE__);
  324. goto cleanup;
  325. }
  326. if (!auto_commit) {
  327. mysql_autocommit(m_server, auto_commit);
  328. }
  329. m_attached = 1;
  330. alloc_own_columns = 1;
  331. max_escaped_char_length = 2;
  332. ret = 1;
  333. cleanup:
  334. for (i = 0; i < (int)(sizeof(vars)/sizeof(vars[0])); i++) {
  335. if (vars[i].freeme) {
  336. free(vars[i].optval);
  337. }
  338. }
  339. return ret;
  340. }
  341. bool PDOMySqlConnection::support(SupportedMethod method) {
  342. return true;
  343. }
  344. bool PDOMySqlConnection::closer() {
  345. if (m_server) {
  346. mysql_close(m_server);
  347. m_server = NULL;
  348. }
  349. if (m_einfo.errmsg) {
  350. free(m_einfo.errmsg);
  351. m_einfo.errmsg = NULL;
  352. }
  353. return false;
  354. }
  355. int PDOMySqlConnection::handleError(const char *file, int line,
  356. PDOMySqlStatement *stmt) {
  357. PDOErrorType *pdo_err;
  358. PDOMySqlError *einfo = &m_einfo;
  359. if (stmt) {
  360. pdo_err = &stmt->error_code;
  361. } else {
  362. pdo_err = &error_code;
  363. }
  364. if (stmt && stmt->stmt()) {
  365. einfo->errcode = mysql_stmt_errno(stmt->stmt());
  366. } else {
  367. einfo->errcode = mysql_errno(m_server);
  368. }
  369. einfo->file = file;
  370. einfo->line = line;
  371. if (einfo->errmsg) {
  372. free(einfo->errmsg);
  373. einfo->errmsg = NULL;
  374. }
  375. if (einfo->errcode) {
  376. if (einfo->errcode == 2014) {
  377. einfo->errmsg =
  378. strdup("Cannot execute queries while other unbuffered queries are "
  379. "active. Consider using PDOStatement::fetchAll(). "
  380. "Alternatively, if your code is only ever going to run against "
  381. "mysql, you may enable query buffering by setting the "
  382. "PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.");
  383. } else if (einfo->errcode == 2057) {
  384. einfo->errmsg =
  385. strdup("A stored procedure returning result sets of different size "
  386. "was called. This is not supported by libmysql");
  387. } else {
  388. einfo->errmsg = strdup(mysql_error(m_server));
  389. }
  390. } else { /* no error */
  391. strcpy(*pdo_err, PDO_ERR_NONE);
  392. return false;
  393. }
  394. if (stmt && stmt->stmt()) {
  395. strcpy(*pdo_err, mysql_stmt_sqlstate(stmt->stmt()));
  396. } else {
  397. strcpy(*pdo_err, mysql_sqlstate(m_server));
  398. }
  399. if (stmt && stmt->stmt()) {
  400. pdo_raise_impl_error(stmt->dbh, NULL, pdo_err[0], einfo->errmsg);
  401. } else {
  402. throw_pdo_exception((int)einfo->errcode, uninit_null(), "SQLSTATE[%s] [%d] %s",
  403. pdo_err[0], einfo->errcode, einfo->errmsg);
  404. }
  405. return einfo->errcode;
  406. }
  407. bool PDOMySqlConnection::preparer(const String& sql, sp_PDOStatement *stmt,
  408. const Variant& options) {
  409. PDOMySqlStatement *s = NEWOBJ(PDOMySqlStatement)(this, m_server);
  410. *stmt = s;
  411. if (m_emulate_prepare) {
  412. return true;
  413. }
  414. int server_version = mysql_get_server_version(m_server);
  415. if (server_version < 40100) {
  416. return true;
  417. }
  418. if (s->create(sql, options.toArray())) {
  419. alloc_own_columns = 1;
  420. return true;
  421. }
  422. stmt->reset();
  423. strcpy(error_code, s->error_code);
  424. return false;
  425. }
  426. int64_t PDOMySqlConnection::doer(const String& sql) {
  427. if (mysql_real_query(m_server, sql.data(), sql.size())) {
  428. handleError(__FILE__, __LINE__);
  429. return -1;
  430. }
  431. my_ulonglong c = mysql_affected_rows(m_server);
  432. if (c == (my_ulonglong) -1) {
  433. handleError(__FILE__, __LINE__);
  434. return m_einfo.errcode ? -1 : 0;
  435. }
  436. /* MULTI_QUERY support - eat up all unfetched result sets */
  437. while (mysql_more_results(m_server)) {
  438. if (mysql_next_result(m_server)) {
  439. return true;
  440. }
  441. MYSQL_RES *result = mysql_store_result(m_server);
  442. if (result) {
  443. mysql_free_result(result);
  444. }
  445. }
  446. return c;
  447. }
  448. bool PDOMySqlConnection::quoter(const String& input, String &quoted,
  449. PDOParamType paramtype) {
  450. String s(2 * input.size() + 3, ReserveString);
  451. char *buf = s.bufferSlice().ptr;
  452. int len = mysql_real_escape_string(m_server, buf + 1,
  453. input.data(), input.size());
  454. len++;
  455. buf[0] = buf[len] = '\'';
  456. len++;
  457. quoted = s.setSize(len);
  458. return true;
  459. }
  460. bool PDOMySqlConnection::begin() {
  461. return doer("START TRANSACTION") >= 0;
  462. }
  463. bool PDOMySqlConnection::commit() {
  464. return mysql_commit(m_server) >= 0;
  465. }
  466. bool PDOMySqlConnection::rollback() {
  467. return mysql_rollback(m_server) >= 0;
  468. }
  469. bool PDOMySqlConnection::setAttribute(int64_t attr, const Variant& value) {
  470. switch (attr) {
  471. case PDO_ATTR_AUTOCOMMIT:
  472. /* ignore if the new value equals the old one */
  473. if (auto_commit ^ value.toBoolean()) {
  474. auto_commit = value.toBoolean();
  475. mysql_autocommit(m_server, auto_commit);
  476. }
  477. return true;
  478. case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
  479. m_buffered = value.toBoolean();
  480. return true;
  481. case PDO_MYSQL_ATTR_DIRECT_QUERY:
  482. case PDO_ATTR_EMULATE_PREPARES:
  483. m_emulate_prepare = value.toBoolean();
  484. return true;
  485. case PDO_ATTR_FETCH_TABLE_NAMES:
  486. m_fetch_table_names = value.toBoolean();
  487. return true;
  488. case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
  489. if (value.toInt64() < 0) {
  490. /* TODO: Johannes, can we throw a warning here? */
  491. m_max_buffer_size = 1024*1024;
  492. } else {
  493. m_max_buffer_size = value.toInt64();
  494. }
  495. return true;
  496. default:
  497. return false;
  498. }
  499. }
  500. String PDOMySqlConnection::lastId(const char *name) {
  501. return (int64_t)mysql_insert_id(m_server);
  502. }
  503. bool PDOMySqlConnection::fetchErr(PDOStatement *stmt, Array &info) {
  504. if (m_einfo.errcode) {
  505. info.append((int64_t)m_einfo.errcode);
  506. info.append(String(m_einfo.errmsg, CopyString));
  507. }
  508. return true;
  509. }
  510. int PDOMySqlConnection::getAttribute(int64_t attr, Variant &value) {
  511. switch (attr) {
  512. case PDO_ATTR_CLIENT_VERSION:
  513. value = String((char *)mysql_get_client_info(), CopyString);
  514. break;
  515. case PDO_ATTR_SERVER_VERSION:
  516. value = String((char *)mysql_get_server_info(m_server), CopyString);
  517. break;
  518. case PDO_ATTR_CONNECTION_STATUS:
  519. value = String((char *)mysql_get_host_info(m_server), CopyString);
  520. break;
  521. case PDO_ATTR_SERVER_INFO: {
  522. char *tmp = (char *)mysql_stat(m_server);
  523. if (tmp) {
  524. value = String(tmp, CopyString);
  525. } else {
  526. handleError(__FILE__, __LINE__);
  527. return -1;
  528. }
  529. break;
  530. }
  531. case PDO_ATTR_AUTOCOMMIT:
  532. value = (int64_t)auto_commit;
  533. break;
  534. case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
  535. value = (int64_t)m_buffered;
  536. break;
  537. case PDO_MYSQL_ATTR_DIRECT_QUERY:
  538. value = (int64_t)m_emulate_prepare;
  539. break;
  540. case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
  541. value = (int64_t)m_max_buffer_size;
  542. break;
  543. default:
  544. return 0;
  545. }
  546. return 1;
  547. }
  548. bool PDOMySqlConnection::checkLiveness() {
  549. return !mysql_ping(m_server);
  550. }
  551. void PDOMySqlConnection::persistentShutdown() {
  552. // do nothing
  553. }
  554. ///////////////////////////////////////////////////////////////////////////////
  555. void PDOMySqlStatement::setRowCount() {
  556. my_ulonglong count = mysql_stmt_affected_rows(m_stmt);
  557. if (count != (my_ulonglong)-1) {
  558. row_count = count;
  559. }
  560. }
  561. bool PDOMySqlStatement::executePrepared() {
  562. /* (re)bind the parameters */
  563. if (mysql_stmt_bind_param(m_stmt, m_params) || mysql_stmt_execute(m_stmt)) {
  564. if (m_params) {
  565. free(m_params);
  566. m_params = 0;
  567. }
  568. handleError(__FILE__, __LINE__);
  569. if (mysql_stmt_errno(m_stmt) == 2057) {
  570. /* CR_NEW_STMT_METADATA makes the statement unusable */
  571. m_stmt = NULL;
  572. }
  573. return false;
  574. }
  575. if (!m_result) {
  576. int i;
  577. /* figure out the result set format, if any */
  578. m_result = mysql_stmt_result_metadata(m_stmt);
  579. if (m_result) {
  580. int calc_max_length = m_conn->buffered() && m_max_length == 1;
  581. m_fields = mysql_fetch_fields(m_result);
  582. if (m_bound_result) {
  583. int i;
  584. for (i = 0; i < column_count; i++) {
  585. free(m_bound_result[i].buffer);
  586. }
  587. free(m_bound_result);
  588. free(m_out_null);
  589. free(m_out_length);
  590. }
  591. column_count = (int)mysql_num_fields(m_result);
  592. m_bound_result = (MYSQL_BIND*)calloc(column_count, sizeof(MYSQL_BIND));
  593. m_out_null = (my_bool*)calloc(column_count, sizeof(my_bool));
  594. m_out_length = (unsigned long *)calloc(column_count,
  595. sizeof(unsigned long));
  596. /* summon memory to hold the row */
  597. for (i = 0; i < column_count; i++) {
  598. if (calc_max_length && m_fields[i].type == FIELD_TYPE_BLOB) {
  599. my_bool on = 1;
  600. mysql_stmt_attr_set(m_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
  601. calc_max_length = 0;
  602. }
  603. switch (m_fields[i].type) {
  604. case FIELD_TYPE_INT24:
  605. m_bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
  606. break;
  607. case FIELD_TYPE_LONG:
  608. m_bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
  609. break;
  610. case FIELD_TYPE_LONGLONG:
  611. m_bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
  612. break;
  613. case FIELD_TYPE_TINY:
  614. m_bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
  615. break;
  616. case FIELD_TYPE_SHORT:
  617. m_bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
  618. break;
  619. default:
  620. m_bound_result[i].buffer_length =
  621. m_fields[i].max_length? m_fields[i].max_length:
  622. m_fields[i].length;
  623. /* work-around for longtext and alike */
  624. if (m_bound_result[i].buffer_length > m_conn->max_buffer_size()) {
  625. m_bound_result[i].buffer_length = m_conn->max_buffer_size();
  626. }
  627. }
  628. /* there are cases where the length reported by mysql is too short.
  629. * eg: when describing a table that contains an enum column. Since
  630. * we have no way of knowing the true length either, we'll bump up
  631. * our buffer size to a reasonable size, just in case */
  632. if (m_fields[i].max_length == 0 &&
  633. m_bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
  634. m_bound_result[i].buffer_length = 128;
  635. }
  636. m_out_length[i] = 0;
  637. m_bound_result[i].buffer = malloc(m_bound_result[i].buffer_length);
  638. m_bound_result[i].is_null = &m_out_null[i];
  639. m_bound_result[i].length = &m_out_length[i];
  640. m_bound_result[i].buffer_type = MYSQL_TYPE_STRING;
  641. }
  642. if (mysql_stmt_bind_result(m_stmt, m_bound_result)) {
  643. handleError(__FILE__, __LINE__);
  644. return false;
  645. }
  646. /* if buffered, pre-fetch all the data */
  647. if (m_conn->buffered()) {
  648. mysql_stmt_store_result(m_stmt);
  649. }
  650. }
  651. }
  652. setRowCount();
  653. return true;
  654. }
  655. static const char *type_to_name_native(int type) {
  656. #define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
  657. switch (type) {
  658. PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
  659. PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
  660. #ifdef MYSQL_HAS_TINY
  661. PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
  662. #endif
  663. PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
  664. PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
  665. PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
  666. PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
  667. PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
  668. PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
  669. PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
  670. #ifdef FIELD_TYPE_NEWDECIMAL
  671. PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
  672. #endif
  673. #ifdef FIELD_TYPE_GEOMETRY
  674. PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
  675. #endif
  676. PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
  677. #ifdef MYSQL_HAS_YEAR
  678. PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
  679. #endif
  680. PDO_MYSQL_NATIVE_TYPE_NAME(SET)
  681. PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
  682. PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
  683. #ifdef FIELD_TYPE_NEWDATE
  684. PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
  685. #endif
  686. PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
  687. PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
  688. PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
  689. PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
  690. PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
  691. PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
  692. PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
  693. default:
  694. return NULL;
  695. }
  696. #undef PDO_MYSQL_NATIVE_TYPE_NAME
  697. }
  698. ///////////////////////////////////////////////////////////////////////////////
  699. PDOMySqlStatement::PDOMySqlStatement(PDOMySqlConnection *conn, MYSQL *server)
  700. : m_conn(conn), m_server(server), m_result(NULL), m_fields(NULL),
  701. m_current_data(NULL), m_current_lengths(NULL), m_stmt(NULL),
  702. m_num_params(0), m_params(NULL), m_in_null(NULL), m_in_length(NULL),
  703. m_bound_result(NULL), m_out_null(NULL), m_out_length(NULL),
  704. m_params_given(0), m_max_length(0) {
  705. this->dbh = conn;
  706. }
  707. PDOMySqlStatement::~PDOMySqlStatement() {
  708. sweep();
  709. }
  710. void PDOMySqlStatement::sweep() {
  711. if (m_result) {
  712. /* free the resource */
  713. mysql_free_result(m_result);
  714. m_result = NULL;
  715. }
  716. if (m_einfo.errmsg) {
  717. free(m_einfo.errmsg);
  718. m_einfo.errmsg = NULL;
  719. }
  720. if (m_stmt) {
  721. mysql_stmt_close(m_stmt);
  722. m_stmt = NULL;
  723. }
  724. if (m_params) {
  725. free(m_params);
  726. }
  727. if (m_in_null) {
  728. free(m_in_null);
  729. }
  730. if (m_in_length) {
  731. free(m_in_length);
  732. }
  733. if (m_bound_result) {
  734. int i;
  735. for (i = 0; i < column_count; i++) {
  736. free(m_bound_result[i].buffer);
  737. }
  738. free(m_bound_result);
  739. free(m_out_null);
  740. free(m_out_length);
  741. }
  742. if (m_server) {
  743. while (mysql_more_results(m_server)) {
  744. if (mysql_next_result(m_server) != 0) {
  745. break;
  746. }
  747. MYSQL_RES *res = mysql_store_result(m_server);
  748. if (res) {
  749. mysql_free_result(res);
  750. }
  751. }
  752. }
  753. }
  754. bool PDOMySqlStatement::create(const String& sql, const Array& options) {
  755. supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
  756. String nsql;
  757. int ret = pdo_parse_params(this, sql, nsql);
  758. if (ret == 1) {
  759. /* query was rewritten */
  760. } else if (ret == -1) {
  761. /* failed to parse */
  762. return false;
  763. } else {
  764. nsql = sql;
  765. }
  766. if (!(m_stmt = mysql_stmt_init(m_server))) {
  767. handleError(__FILE__, __LINE__);
  768. return false;
  769. }
  770. if (mysql_stmt_prepare(m_stmt, nsql.data(), nsql.size())) {
  771. /* TODO: might need to pull statement specific info here? */
  772. /* if the query isn't supported by the protocol, fallback to emulation */
  773. if (mysql_errno(m_server) == 1295) {
  774. supports_placeholders = PDO_PLACEHOLDER_NONE;
  775. return true;
  776. }
  777. handleError(__FILE__, __LINE__);
  778. return false;
  779. }
  780. m_num_params = mysql_stmt_param_count(m_stmt);
  781. if (m_num_params) {
  782. m_params_given = 0;
  783. m_params = (MYSQL_BIND*)calloc(m_num_params, sizeof(MYSQL_BIND));
  784. m_in_null = (my_bool*)calloc(m_num_params, sizeof(my_bool));
  785. m_in_length = (unsigned long*)calloc(m_num_params, sizeof(unsigned long));
  786. }
  787. m_max_length = pdo_attr_lval(options, PDO_ATTR_MAX_COLUMN_LEN, 0);
  788. return true;
  789. }
  790. bool PDOMySqlStatement::support(SupportedMethod method) {
  791. switch (method) {
  792. case MethodSetAttribute:
  793. case MethodGetAttribute:
  794. return false;
  795. default:
  796. break;
  797. }
  798. return true;
  799. }
  800. int PDOMySqlStatement::handleError(const char *file, int line) {
  801. assert(m_conn);
  802. return m_conn->handleError(file, line, this);
  803. }
  804. bool PDOMySqlStatement::executer() {
  805. if (m_stmt) {
  806. return executePrepared();
  807. }
  808. /* ensure that we free any previous unfetched results */
  809. if (m_result) {
  810. mysql_free_result(m_result);
  811. m_result = NULL;
  812. }
  813. if (mysql_real_query(m_server, active_query_string.data(),
  814. active_query_string.size()) != 0) {
  815. handleError(__FILE__, __LINE__);
  816. return false;
  817. }
  818. my_ulonglong affected_count = mysql_affected_rows(m_server);
  819. if (affected_count == (my_ulonglong)-1) {
  820. /* we either have a query that returned a result set or an error occured
  821. lets see if we have access to a result set */
  822. if (!m_conn->buffered()) {
  823. m_result = mysql_use_result(m_server);
  824. } else {
  825. m_result = mysql_store_result(m_server);
  826. }
  827. if (NULL == m_result) {
  828. handleError(__FILE__, __LINE__);
  829. return false;
  830. }
  831. row_count = mysql_num_rows(m_result);
  832. column_count = (int) mysql_num_fields(m_result);
  833. m_fields = mysql_fetch_fields(m_result);
  834. }
  835. else {
  836. row_count = affected_count;
  837. }
  838. return true;
  839. }
  840. bool PDOMySqlStatement::fetcher(PDOFetchOrientation ori, long offset) {
  841. int ret;
  842. if (m_stmt) {
  843. ret = mysql_stmt_fetch(m_stmt);
  844. if (ret == MYSQL_DATA_TRUNCATED) {
  845. ret = 0;
  846. }
  847. if (ret) {
  848. if (ret != MYSQL_NO_DATA) {
  849. handleError(__FILE__, __LINE__);
  850. }
  851. return false;
  852. }
  853. return true;
  854. }
  855. if (!m_result) {
  856. strcpy(error_code, "HY000");
  857. return false;
  858. }
  859. if ((m_current_data = mysql_fetch_row(m_result)) == NULL) {
  860. if (mysql_errno(m_server)) {
  861. handleError(__FILE__, __LINE__);
  862. }
  863. return false;
  864. }
  865. m_current_lengths = (long int *)mysql_fetch_lengths(m_result);
  866. return true;
  867. }
  868. bool PDOMySqlStatement::describer(int colno) {
  869. if (!m_result) {
  870. return false;
  871. }
  872. if (colno < 0 || colno >= column_count) {
  873. /* error invalid column */
  874. return false;
  875. }
  876. if (columns.empty()) {
  877. for (int i = 0; i < column_count; i++) {
  878. columns.set(i, Resource(NEWOBJ(PDOColumn)));
  879. }
  880. }
  881. // fetch all on demand, this seems easiest if we've been here before bail out
  882. PDOColumn *col = columns[0].toResource().getTyped<PDOColumn>();
  883. if (!col->name.empty()) {
  884. return true;
  885. }
  886. for (int i = 0; i < column_count; i++) {
  887. col = columns[i].toResource().getTyped<PDOColumn>();
  888. if (m_conn->fetch_table_names()) {
  889. col->name = String(m_fields[i].table) + "." +
  890. String(m_fields[i].name);
  891. } else {
  892. col->name = String(m_fields[i].name, CopyString);
  893. }
  894. col->precision = m_fields[i].decimals;
  895. col->maxlen = m_fields[i].length;
  896. col->param_type = PDO_PARAM_STR;
  897. }
  898. return true;
  899. }
  900. bool PDOMySqlStatement::getColumn(int colno, Variant &value) {
  901. if (!m_result) {
  902. return false;
  903. }
  904. if (!m_stmt) {
  905. if (m_current_data == NULL || !m_result) {
  906. return false;
  907. }
  908. }
  909. if (colno < 0 || colno >= column_count) {
  910. /* error invalid column */
  911. return false;
  912. }
  913. char *ptr; int len;
  914. if (m_stmt) {
  915. if (m_out_null[colno]) {
  916. value = String();
  917. return true;
  918. }
  919. ptr = (char*)m_bound_result[colno].buffer;
  920. if (m_out_length[colno] > m_bound_result[colno].buffer_length) {
  921. /* mysql lied about the column width */
  922. strcpy(error_code, "01004"); /* truncated */
  923. m_out_length[colno] = m_bound_result[colno].buffer_length;
  924. len = m_out_length[colno];
  925. value = String(ptr, len, CopyString);
  926. return false;
  927. }
  928. len = m_out_length[colno];
  929. value = String(ptr, len, CopyString);
  930. return true;
  931. }
  932. ptr = m_current_data[colno];
  933. len = m_current_lengths[colno];
  934. value = String(ptr, len, CopyString);
  935. return true;
  936. }
  937. bool PDOMySqlStatement::paramHook(PDOBoundParam *param,
  938. PDOParamEvent event_type) {
  939. MYSQL_BIND *b;
  940. if (m_stmt && param->is_param) {
  941. switch (event_type) {
  942. case PDO_PARAM_EVT_ALLOC:
  943. /* sanity check parameter number range */
  944. if (param->paramno < 0 || param->paramno >= m_num_params) {
  945. strcpy(error_code, "HY093");
  946. return false;
  947. }
  948. m_params_given++;
  949. b = &m_params[param->paramno];
  950. param->driver_data = b;
  951. b->is_null = &m_in_null[param->paramno];
  952. b->length = &m_in_length[param->paramno];
  953. /* recall how many parameters have been provided */
  954. return true;
  955. case PDO_PARAM_EVT_EXEC_PRE:
  956. if ((int)m_params_given < m_num_params) {
  957. /* too few parameter bound */
  958. strcpy(error_code, "HY093");
  959. return false;
  960. }
  961. b = (MYSQL_BIND*)param->driver_data;
  962. *b->is_null = 0;
  963. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
  964. param->parameter.isNull()) {
  965. *b->is_null = 1;
  966. b->buffer_type = MYSQL_TYPE_STRING;
  967. b->buffer = NULL;
  968. b->buffer_length = 0;
  969. *b->length = 0;
  970. return true;
  971. }
  972. switch (PDO_PARAM_TYPE(param->param_type)) {
  973. case PDO_PARAM_STMT:
  974. return false;
  975. case PDO_PARAM_LOB:
  976. if (param->parameter.isResource()) {
  977. Variant buf = f_stream_get_contents(param->parameter.toResource());
  978. if (!same(buf, false)) {
  979. param->parameter = buf;
  980. } else {
  981. pdo_raise_impl_error(m_conn, this, "HY105",
  982. "Expected a stream resource");
  983. return false;
  984. }
  985. }
  986. /* fall through */
  987. default:
  988. ;
  989. }
  990. if (param->parameter.isString()) {
  991. String sparam = param->parameter.toString();
  992. b->buffer_type = MYSQL_TYPE_STRING;
  993. b->buffer = (void*)sparam.data();
  994. b->buffer_length = sparam.size();
  995. *b->length = sparam.size();
  996. return true;
  997. }
  998. if (param->parameter.isInteger()) {
  999. param->parameter = param->parameter.toInt64();
  1000. b->buffer_type = MYSQL_TYPE_LONG;
  1001. b->buffer = param->parameter.getInt64Data();
  1002. return true;
  1003. }
  1004. if (param->parameter.isDouble()) {
  1005. b->buffer_type = MYSQL_TYPE_DOUBLE;
  1006. b->buffer = param->parameter.getDoubleData();
  1007. return true;
  1008. }
  1009. return false;
  1010. case PDO_PARAM_EVT_FREE:
  1011. case PDO_PARAM_EVT_EXEC_POST:
  1012. case PDO_PARAM_EVT_FETCH_PRE:
  1013. case PDO_PARAM_EVT_FETCH_POST:
  1014. case PDO_PARAM_EVT_NORMALIZE:
  1015. /* do nothing */
  1016. break;
  1017. }
  1018. }
  1019. return true;
  1020. }
  1021. const StaticString
  1022. s_mysql_def("mysql:def"),
  1023. s_not_null("not_null"),
  1024. s_primary_key("primary_key"),
  1025. s_multiple_key("multiple_key"),
  1026. s_unique_key("unique_key"),
  1027. s_blob("blob"),
  1028. s_native_type("native_type"),
  1029. s_flags("flags"),
  1030. s_table("table");
  1031. bool PDOMySqlStatement::getColumnMeta(int64_t colno, Array &ret) {
  1032. if (!m_result) {
  1033. return false;
  1034. }
  1035. if (colno < 0 || colno >= column_count) {
  1036. /* error invalid column */
  1037. return false;
  1038. }
  1039. Array flags = Array::Create();
  1040. const MYSQL_FIELD *F = m_fields + colno;
  1041. if (F->def) {
  1042. ret.set(s_mysql_def, String(F->def, CopyString));
  1043. }
  1044. if (IS_NOT_NULL(F->flags)) {
  1045. flags.append(s_not_null);
  1046. }
  1047. if (IS_PRI_KEY(F->flags)) {
  1048. flags.append(s_primary_key);
  1049. }
  1050. if (F->flags & MULTIPLE_KEY_FLAG) {
  1051. flags.append(s_multiple_key);
  1052. }
  1053. if (F->flags & UNIQUE_KEY_FLAG) {
  1054. flags.append(s_unique_key);
  1055. }
  1056. if (IS_BLOB(F->flags)) {
  1057. flags.append(s_blob);
  1058. }
  1059. const char *str = type_to_name_native(F->type);
  1060. if (str) {
  1061. ret.set(s_native_type, str);
  1062. }
  1063. ret.set(s_flags, flags);
  1064. ret.set(s_table, String(F->table, CopyString));
  1065. return true;
  1066. }
  1067. bool PDOMySqlStatement::nextRowset() {
  1068. /* ensure that we free any previous unfetched results */
  1069. if (m_stmt) {
  1070. column_count = (int)mysql_num_fields(m_result);
  1071. mysql_stmt_free_result(m_stmt);
  1072. }
  1073. if (m_result) {
  1074. mysql_free_result(m_result);
  1075. m_result = NULL;
  1076. }
  1077. int ret = mysql_next_result(m_server);
  1078. if (ret > 0) {
  1079. handleError(__FILE__, __LINE__);
  1080. return false;
  1081. }
  1082. if (ret < 0) {
  1083. /* No more results */
  1084. return false;
  1085. }
  1086. my_ulonglong row_count;
  1087. if (!m_conn->buffered()) {
  1088. m_result = mysql_use_result(m_server);
  1089. row_count = 0;
  1090. } else {
  1091. m_result = mysql_store_result(m_server);
  1092. if ((my_ulonglong)-1 == (row_count = mysql_affected_rows(m_server))) {
  1093. handleError(__FILE__, __LINE__);
  1094. return false;
  1095. }
  1096. }
  1097. if (!m_result) {
  1098. return false;
  1099. }
  1100. column_count = (int)mysql_num_fields(m_result);
  1101. m_fields = mysql_fetch_fields(m_result);
  1102. return true;
  1103. }
  1104. bool PDOMySqlStatement::cursorCloser() {
  1105. if (m_result) {
  1106. mysql_free_result(m_result);
  1107. m_result = NULL;
  1108. }
  1109. if (m_stmt) {
  1110. return !mysql_stmt_free_result(m_stmt);
  1111. }
  1112. while (mysql_more_results(m_server)) {
  1113. if (mysql_next_result(m_server) != 0) {
  1114. break;
  1115. }
  1116. MYSQL_RES *res = mysql_store_result(m_server);
  1117. if (res) {
  1118. mysql_free_result(res);
  1119. }
  1120. }
  1121. return true;
  1122. }
  1123. ///////////////////////////////////////////////////////////////////////////////
  1124. PDOMySql::PDOMySql() : PDODriver("mysql") {
  1125. }
  1126. PDOConnection *PDOMySql::createConnectionObject() {
  1127. // Doesn't use NEWOBJ because PDOConnection is malloced
  1128. return new PDOMySqlConnection();
  1129. }
  1130. ///////////////////////////////////////////////////////////////////////////////
  1131. }