ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
mysqli.php
Go to the documentation of this file.
1 <?php
2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox, |
7 // | Stig. S. Bakken, Lukas Smith |
8 // | All rights reserved. |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB |
11 // | API as well as database abstraction for PHP applications. |
12 // | This LICENSE is in the BSD license style. |
13 // | |
14 // | Redistribution and use in source and binary forms, with or without |
15 // | modification, are permitted provided that the following conditions |
16 // | are met: |
17 // | |
18 // | Redistributions of source code must retain the above copyright |
19 // | notice, this list of conditions and the following disclaimer. |
20 // | |
21 // | Redistributions in binary form must reproduce the above copyright |
22 // | notice, this list of conditions and the following disclaimer in the |
23 // | documentation and/or other materials provided with the distribution. |
24 // | |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission. |
29 // | |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
41 // | POSSIBILITY OF SUCH DAMAGE. |
42 // +----------------------------------------------------------------------+
43 // | Author: Lukas Smith <smith@pooteeweet.org> |
44 // +----------------------------------------------------------------------+
45 //
46 // $Id: mysqli.php,v 1.162 2007/05/02 22:00:08 quipo Exp $
47 //
48 
57 {
58  // {{{ properties
59  var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\');
60 
61  var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`');
62 
63  var $sql_comments = array(
64  array('start' => '-- ', 'end' => "\n", 'escape' => false),
65  array('start' => '#', 'end' => "\n", 'escape' => false),
66  array('start' => '/*', 'end' => '*/', 'escape' => false),
67  );
68 
69  var $start_transaction = false;
70 
72  // }}}
73  // {{{ constructor
74 
78  function __construct()
79  {
81 
82  $this->phptype = 'mysqli';
83  $this->dbsyntax = 'mysql';
84 
85  $this->supported['sequences'] = 'emulated';
86  $this->supported['indexes'] = true;
87  $this->supported['affected_rows'] = true;
88  $this->supported['transactions'] = false;
89  $this->supported['savepoints'] = false;
90  $this->supported['summary_functions'] = true;
91  $this->supported['order_by_text'] = true;
92  $this->supported['current_id'] = 'emulated';
93  $this->supported['limit_queries'] = true;
94  $this->supported['LOBs'] = true;
95  $this->supported['replace'] = true;
96  $this->supported['sub_selects'] = 'emulated';
97  $this->supported['auto_increment'] = true;
98  $this->supported['primary_key'] = true;
99  $this->supported['result_introspection'] = true;
100  $this->supported['prepared_statements'] = 'emulated';
101  $this->supported['identifier_quoting'] = true;
102  $this->supported['pattern_escaping'] = true;
103  $this->supported['new_link'] = true;
104 
105  $this->options['default_table_type'] = '';
106  $this->options['multi_query'] = false;
107  }
108 
109  // }}}
110  // {{{ errorInfo()
111 
119  function errorInfo($error = null)
120  {
121  if ($this->connection) {
122  $native_code = @mysqli_errno($this->connection);
123  $native_msg = @mysqli_error($this->connection);
124  } else {
125  $native_code = @mysqli_connect_errno();
126  $native_msg = @mysqli_connect_error();
127  }
128  if (is_null($error)) {
129  static $ecode_map;
130  if (empty($ecode_map)) {
131  $ecode_map = array(
132  1004 => MDB2_ERROR_CANNOT_CREATE,
133  1005 => MDB2_ERROR_CANNOT_CREATE,
134  1006 => MDB2_ERROR_CANNOT_CREATE,
136  1008 => MDB2_ERROR_CANNOT_DROP,
139  1046 => MDB2_ERROR_NODBSELECTED,
140  1048 => MDB2_ERROR_CONSTRAINT,
141  1049 => MDB2_ERROR_NOSUCHDB,
143  1051 => MDB2_ERROR_NOSUCHTABLE,
144  1054 => MDB2_ERROR_NOSUCHFIELD,
147  1064 => MDB2_ERROR_SYNTAX,
148  1091 => MDB2_ERROR_NOT_FOUND,
149  1100 => MDB2_ERROR_NOT_LOCKED,
152  1146 => MDB2_ERROR_NOSUCHTABLE,
153  1216 => MDB2_ERROR_CONSTRAINT,
154  1217 => MDB2_ERROR_CONSTRAINT,
155  1356 => MDB2_ERROR_DIVZERO,
156  1451 => MDB2_ERROR_CONSTRAINT,
157  1452 => MDB2_ERROR_CONSTRAINT,
158  );
159  }
160  if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
161  $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
162  $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
163  $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
164  } else {
165  // Doing this in case mode changes during runtime.
166  $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
167  $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
168  $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
169  }
170  if (isset($ecode_map[$native_code])) {
171  $error = $ecode_map[$native_code];
172  }
173  }
174  return array($error, $native_code, $native_msg);
175  }
176 
177  // }}}
178  // {{{ escape()
179 
191  function escape($text, $escape_wildcards = false)
192  {
193  if ($escape_wildcards) {
194  $text = $this->escapePattern($text);
195  }
196  $connection = $this->getConnection();
197  if (PEAR::isError($connection)) {
198  return $connection;
199  }
200  $text = @mysqli_real_escape_string($connection, $text);
201  return $text;
202  }
203 
204  // }}}
205  // {{{ beginTransaction()
206 
215  function beginTransaction($savepoint = null)
216  {
217  $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
218  $this->_getServerCapabilities();
219  if (!is_null($savepoint)) {
220  if (!$this->supports('savepoints')) {
221  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
222  'savepoints are not supported', __FUNCTION__);
223  }
224  if (!$this->in_transaction) {
225  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
226  'savepoint cannot be released when changes are auto committed', __FUNCTION__);
227  }
228  $query = 'SAVEPOINT '.$savepoint;
229  return $this->_doQuery($query, true);
230  } elseif ($this->in_transaction) {
231  return MDB2_OK; //nothing to do
232  }
233  $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1';
234  $result =& $this->_doQuery($query, true);
235  if (PEAR::isError($result)) {
236  return $result;
237  }
238  $this->in_transaction = true;
239  return MDB2_OK;
240  }
241 
242  // }}}
243  // {{{ commit()
244 
256  function commit($savepoint = null)
257  {
258  $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
259  if (!$this->in_transaction) {
260  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
261  'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
262  }
263  if (!is_null($savepoint)) {
264  if (!$this->supports('savepoints')) {
265  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
266  'savepoints are not supported', __FUNCTION__);
267  }
268  $server_info = $this->getServerVersion();
269  if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
270  return MDB2_OK;
271  }
272  $query = 'RELEASE SAVEPOINT '.$savepoint;
273  return $this->_doQuery($query, true);
274  }
275 
276  if (!$this->supports('transactions')) {
277  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
278  'transactions are not supported', __FUNCTION__);
279  }
280 
281  $result =& $this->_doQuery('COMMIT', true);
282  if (PEAR::isError($result)) {
283  return $result;
284  }
285  if (!$this->start_transaction) {
286  $query = 'SET AUTOCOMMIT = 0';
287  $result =& $this->_doQuery($query, true);
288  if (PEAR::isError($result)) {
289  return $result;
290  }
291  }
292  $this->in_transaction = false;
293  return MDB2_OK;
294  }
295 
296  // }}}
297  // {{{ rollback()
298 
310  function rollback($savepoint = null)
311  {
312  $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
313  if (!$this->in_transaction) {
314  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
315  'rollback cannot be done changes are auto committed', __FUNCTION__);
316  }
317  if (!is_null($savepoint)) {
318  if (!$this->supports('savepoints')) {
319  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
320  'savepoints are not supported', __FUNCTION__);
321  }
322  $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
323  return $this->_doQuery($query, true);
324  }
325 
326  $query = 'ROLLBACK';
327  $result =& $this->_doQuery($query, true);
328  if (PEAR::isError($result)) {
329  return $result;
330  }
331  if (!$this->start_transaction) {
332  $query = 'SET AUTOCOMMIT = 0';
333  $result =& $this->_doQuery($query, true);
334  if (PEAR::isError($result)) {
335  return $result;
336  }
337  }
338  $this->in_transaction = false;
339  return MDB2_OK;
340  }
341 
342  // }}}
343  // {{{ function setTransactionIsolation()
344 
358  function setTransactionIsolation($isolation)
359  {
360  $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
361  if (!$this->supports('transactions')) {
362  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
363  'transactions are not supported', __FUNCTION__);
364  }
365  switch ($isolation) {
366  case 'READ UNCOMMITTED':
367  case 'READ COMMITTED':
368  case 'REPEATABLE READ':
369  case 'SERIALIZABLE':
370  break;
371  default:
372  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
373  'isolation level is not supported: '.$isolation, __FUNCTION__);
374  }
375 
376  $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation";
377  return $this->_doQuery($query, true);
378  }
379 
380  // }}}
381  // {{{ connect()
382 
388  function connect()
389  {
390  if (is_object($this->connection)) {
391  if (count(array_diff($this->connected_dsn, $this->dsn)) == 0) {
392  return MDB2_OK;
393  }
394  $this->connection = 0;
395  }
396 
397  if (!PEAR::loadExtension($this->phptype)) {
398  return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
399  'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
400  }
401 
402  if ($this->options['ssl']) {
403  $init = @mysqli_init();
404  @mysqli_ssl_set(
405  $init,
406  empty($this->dsn['key']) ? null : $this->dsn['key'],
407  empty($this->dsn['cert']) ? null : $this->dsn['cert'],
408  empty($this->dsn['ca']) ? null : $this->dsn['ca'],
409  empty($this->dsn['capath']) ? null : $this->dsn['capath'],
410  empty($this->dsn['cipher']) ? null : $this->dsn['cipher']
411  );
412  if ($connection = @mysqli_real_connect(
413  $init,
414  $this->dsn['hostspec'],
415  $this->dsn['username'],
416  $this->dsn['password'],
417  $this->database_name,
418  $this->dsn['port'],
419  $this->dsn['socket']))
420  {
421  $connection = $init;
422  }
423  } else {
424  // hhvm-patch: begin
425  // HHVM-Fix: "Socket" must be a string!
426  if(!is_string($this->dsn['socket'])) {
427  $this->dsn['socket'] = "";
428  }
429  if(!is_string($this->dsn['port'])) {
430  $this->dsn['port'] = 0;
431  }
432  // HHVM-Fix: use "new mysqli" instead of "@mysqli_connect"
433  $connection = @new mysqli(
434  // hhvm-patch: end
435  $this->dsn['hostspec'],
436  $this->dsn['username'],
437  $this->dsn['password'],
438  $this->database_name,
439  $this->dsn['port'],
440  $this->dsn['socket']
441  );
442  // hhvm-patch: begin
443  if($connection->connect_error)
444  {
445  // Changed data type to boolean on connection errors to adapt mysqli_connect()
446  $connection = false;
447  }
448  // hhvm-patch: end
449  }
450 
451  if (!$connection) {
452  if (($err = @mysqli_connect_error()) != '') {
453  return $this->raiseError(null,
454  null, null, $err, __FUNCTION__);
455  } else {
456  return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
457  'unable to establish a connection', __FUNCTION__);
458  }
459  }
460 
461  if (!empty($this->dsn['charset'])) {
462  $result = $this->setCharset($this->dsn['charset'], $connection);
463  if (PEAR::isError($result)) {
464  return $result;
465  }
466  }
467 
468  $this->connection = $connection;
469  $this->connected_dsn = $this->dsn;
470  $this->connected_database_name = $this->database_name;
471  $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
472 
473  $this->supported['transactions'] = $this->options['use_transactions'];
474  if ($this->options['default_table_type']) {
475  switch (strtoupper($this->options['default_table_type'])) {
476  case 'BLACKHOLE':
477  case 'MEMORY':
478  case 'ARCHIVE':
479  case 'CSV':
480  case 'HEAP':
481  case 'ISAM':
482  case 'MERGE':
483  case 'MRG_ISAM':
484  case 'ISAM':
485  case 'MRG_MYISAM':
486  case 'MYISAM':
487  $this->supported['transactions'] = false;
488  $this->warnings[] = $this->options['default_table_type'] .
489  ' is not a supported default table type';
490  break;
491  }
492  }
493 
494  $this->_getServerCapabilities();
495 
496  return MDB2_OK;
497  }
498 
499  // }}}
500  // {{{ setCharset()
501 
510  function setCharset($charset, $connection = null)
511  {
512  if (is_null($connection)) {
513  $connection = $this->getConnection();
514  if (PEAR::isError($connection)) {
515  return $connection;
516  }
517  }
518  $query = "SET NAMES '".mysqli_real_escape_string($connection, $charset)."'";
519  return $this->_doQuery($query, true, $connection);
520  }
521 
522  // }}}
523  // {{{ disconnect()
524 
534  function disconnect($force = true)
535  {
536  if (is_object($this->connection)) {
537  if ($this->in_transaction) {
538  $dsn = $this->dsn;
540  $persistent = $this->options['persistent'];
541  $this->dsn = $this->connected_dsn;
542  $this->database_name = $this->connected_database_name;
543  $this->options['persistent'] = $this->opened_persistent;
544  $this->rollback();
545  $this->dsn = $dsn;
546  $this->database_name = $database_name;
547  $this->options['persistent'] = $persistent;
548  }
549 
550  if ($force) {
551  @mysqli_close($this->connection);
552  }
553  }
554  return parent::disconnect($force);
555  }
556 
557  // }}}
558  // {{{ _doQuery()
559 
569  function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
570  {
571  $this->last_query = $query;
572  $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
573  if ($result) {
574  if (PEAR::isError($result)) {
575  return $result;
576  }
577  $query = $result;
578  }
579  if ($this->options['disable_query']) {
580  $result = $is_manip ? 0 : null;
581  return $result;
582  }
583 
584  if (is_null($connection)) {
585  $connection = $this->getConnection();
586  if (PEAR::isError($connection)) {
587  return $connection;
588  }
589  }
590  if (is_null($database_name)) {
592  }
593 
594  if ($database_name) {
595  if ($database_name != $this->connected_database_name) {
596  if (!@mysqli_select_db($connection, $database_name)) {
597  $err = $this->raiseError(null, null, null,
598  'Could not select the database: '.$database_name, __FUNCTION__);
599  return $err;
600  }
601  $this->connected_database_name = $database_name;
602  }
603  }
604 
605  if ($this->options['multi_query']) {
606  $result = mysqli_multi_query($connection, $query);
607  } else {
608  $resultmode = $this->options['result_buffering'] ? MYSQLI_USE_RESULT : MYSQLI_USE_RESULT;
609  $result = mysqli_query($connection, $query);
610  }
611 
612  if (!$result) {
613  $err =& $this->raiseError(null, null, null,
614  'Could not execute statement', __FUNCTION__);
615  return $err;
616  }
617 
618  if ($this->options['multi_query']) {
619  if ($this->options['result_buffering']) {
620  if (!($result = @mysqli_store_result($connection))) {
621  $err =& $this->raiseError(null, null, null,
622  'Could not get the first result from a multi query', __FUNCTION__);
623  return $err;
624  }
625  } elseif (!($result = @mysqli_use_result($connection))) {
626  $err =& $this->raiseError(null, null, null,
627  'Could not get the first result from a multi query', __FUNCTION__);
628  return $err;
629  }
630  }
631 
632  $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
633  return $result;
634  }
635 
636  // }}}
637  // {{{ _affectedRows()
638 
648  {
649  if (is_null($connection)) {
650  $connection = $this->getConnection();
651  if (PEAR::isError($connection)) {
652  return $connection;
653  }
654  }
655  return @mysqli_affected_rows($connection);
656  }
657 
658  // }}}
659  // {{{ _modifyQuery()
660 
671  function _modifyQuery($query, $is_manip, $limit, $offset)
672  {
673  if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
674  // "DELETE FROM table" gives 0 affected rows in MySQL.
675  // This little hack lets you know how many rows were deleted.
676  if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
677  $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
678  'DELETE FROM \1 WHERE 1=1', $query);
679  }
680  }
681  if ($limit > 0
682  && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
683  ) {
684  $query = rtrim($query);
685  if (substr($query, -1) == ';') {
686  $query = substr($query, 0, -1);
687  }
688 
689  // LIMIT doesn't always come last in the query
690  // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
691  $after = '';
692  if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
693  $after = $matches[0];
694  $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
695  } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
696  $after = $matches[0];
697  $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
698  } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
699  $after = $matches[0];
700  $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
701  }
702 
703  if ($is_manip) {
704  return $query . " LIMIT $limit";
705  } else {
706  return $query . " LIMIT $offset, $limit";
707  }
708  }
709  return $query;
710  }
711 
712  // }}}
713  // {{{ getServerVersion()
714 
722  function getServerVersion($native = false)
723  {
724  $connection = $this->getConnection();
725  if (PEAR::isError($connection)) {
726  return $connection;
727  }
728  if ($this->connected_server_info) {
729  $server_info = $this->connected_server_info;
730  } else {
731  $server_info = @mysqli_get_server_info($connection);
732  }
733  if (!$server_info) {
734  return $this->raiseError(null, null, null,
735  'Could not get server information', __FUNCTION__);
736  }
737  // cache server_info
738  $this->connected_server_info = $server_info;
739  if (!$native) {
740  $tmp = explode('.', $server_info, 3);
741  if (isset($tmp[2]) && strpos($tmp[2], '-')) {
742  $tmp2 = explode('-', @$tmp[2], 2);
743  } else {
744  $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
745  $tmp2[1] = null;
746  }
747  $server_info = array(
748  'major' => isset($tmp[0]) ? $tmp[0] : null,
749  'minor' => isset($tmp[1]) ? $tmp[1] : null,
750  'patch' => $tmp2[0],
751  'extra' => $tmp2[1],
752  'native' => $server_info,
753  );
754  }
755  return $server_info;
756  }
757 
758  // }}}
759  // {{{ _getServerCapabilities()
760 
768  {
769  static $already_checked = false;
770  if (!$already_checked) {
771  $already_checked = true;
772 
773  //set defaults
774  $this->supported['sub_selects'] = 'emulated';
775  $this->supported['prepared_statements'] = 'emulated';
776  $this->start_transaction = false;
777  $this->varchar_max_length = 255;
778 
779  $server_info = $this->getServerVersion();
780  if (is_array($server_info)) {
781  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) {
782  $this->supported['sub_selects'] = true;
783  $this->supported['prepared_statements'] = true;
784  }
785 
786  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<')
787  || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<')
788  ) {
789  $this->supported['savepoints'] = true;
790  }
791 
792  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) {
793  $this->start_transaction = true;
794  }
795 
796  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
797  $this->varchar_max_length = 65532;
798  }
799  }
800  }
801  }
802 
803  // }}}
804  // {{{ function _skipUserDefinedVariable($query, $position)
805 
818  function _skipUserDefinedVariable($query, $position)
819  {
820  $found = strpos(strrev(substr($query, 0, $position)), '@');
821  if ($found === false) {
822  return $position;
823  }
824  $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
825  $substring = substr($query, $pos, $position - $pos + 2);
826  if (preg_match('/^@\w+:=$/', $substring)) {
827  return $position + 1; //found an user defined variable: skip it
828  }
829  return $position;
830  }
831 
832  // }}}
833  // {{{ prepare()
834 
855  function &prepare($query, $types = null, $result_types = null, $lobs = array())
856  {
857  if ($this->options['emulate_prepared']
858  || $this->supported['prepared_statements'] !== true
859  ) {
860  $obj =& parent::prepare($query, $types, $result_types, $lobs);
861  return $obj;
862  }
863  $is_manip = ($result_types === MDB2_PREPARE_MANIP);
866  $this->offset = $this->limit = 0;
867  $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
868  $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
869  if ($result) {
870  if (PEAR::isError($result)) {
871  return $result;
872  }
873  $query = $result;
874  }
875  $placeholder_type_guess = $placeholder_type = null;
876  $question = '?';
877  $colon = ':';
878  $positions = array();
879  $position = 0;
880  while ($position < strlen($query)) {
881  $q_position = strpos($query, $question, $position);
882  $c_position = strpos($query, $colon, $position);
883  if ($q_position && $c_position) {
884  $p_position = min($q_position, $c_position);
885  } elseif ($q_position) {
886  $p_position = $q_position;
887  } elseif ($c_position) {
888  $p_position = $c_position;
889  } else {
890  break;
891  }
892  if (is_null($placeholder_type)) {
893  $placeholder_type_guess = $query[$p_position];
894  }
895 
896  $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
897  if (PEAR::isError($new_pos)) {
898  return $new_pos;
899  }
900  if ($new_pos != $position) {
901  $position = $new_pos;
902  continue; //evaluate again starting from the new position
903  }
904 
905  if ($query[$position] == $placeholder_type_guess) {
906  if (is_null($placeholder_type)) {
907  $placeholder_type = $query[$p_position];
908  $question = $colon = $placeholder_type;
909  }
910  if ($placeholder_type == ':') {
911  //make sure this is not part of an user defined variable
912  $new_pos = $this->_skipUserDefinedVariable($query, $position);
913  if ($new_pos != $position) {
914  $position = $new_pos;
915  continue; //evaluate again starting from the new position
916  }
917  $parameter = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query);
918  if ($parameter === '') {
919  $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
920  'named parameter with an empty name', __FUNCTION__);
921  return $err;
922  }
923  $positions[$p_position] = $parameter;
924  $query = substr_replace($query, '?', $position, strlen($parameter)+1);
925  } else {
926  $positions[$p_position] = count($positions);
927  }
928  $position = $p_position + 1;
929  } else {
930  $position = $p_position;
931  }
932  }
933  $connection = $this->getConnection();
934  if (PEAR::isError($connection)) {
935  return $connection;
936  }
937 
938  if (!$is_manip) {
939  $statement_name = sprintf($this->options['statement_format'], $this->phptype, md5(time() + rand()));
940  $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
941 
942  $statement =& $this->_doQuery($query, true, $connection);
943  if (PEAR::isError($statement)) {
944  return $statement;
945  }
946  $statement = $statement_name;
947  } else {
948  $statement = @mysqli_prepare($connection, $query);
949  if (!$statement) {
950  $err =& $this->raiseError(null, null, null,
951  'Unable to create prepared statement handle', __FUNCTION__);
952  return $err;
953  }
954  }
955 
956  $class_name = 'MDB2_Statement_'.$this->phptype;
957  $obj =& new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
958  $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
959  return $obj;
960  }
961 
962  // }}}
963  // {{{ replace()
964 
1029  function replace($table, $fields)
1030  {
1031  $count = count($fields);
1032  $query = $values = '';
1033  $keys = $colnum = 0;
1034  for (reset($fields); $colnum < $count; next($fields), $colnum++) {
1035  $name = key($fields);
1036  if ($colnum > 0) {
1037  $query .= ',';
1038  $values.= ',';
1039  }
1040  $query.= $name;
1041  if (isset($fields[$name]['null']) && $fields[$name]['null']) {
1042  $value = 'NULL';
1043  } else {
1044  $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
1045  $value = $this->quote($fields[$name]['value'], $type);
1046  }
1047  $values.= $value;
1048  if (isset($fields[$name]['key']) && $fields[$name]['key']) {
1049  if ($value === 'NULL') {
1050  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1051  'key value '.$name.' may not be NULL', __FUNCTION__);
1052  }
1053  $keys++;
1054  }
1055  }
1056  if ($keys == 0) {
1057  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1058  'not specified which fields are keys', __FUNCTION__);
1059  }
1060 
1061  $connection = $this->getConnection();
1062  if (PEAR::isError($connection)) {
1063  return $connection;
1064  }
1065 
1066  $query = "REPLACE INTO $table ($query) VALUES ($values)";
1067  $result =& $this->_doQuery($query, true, $connection);
1068  if (PEAR::isError($result)) {
1069  return $result;
1070  }
1071  return $this->_affectedRows($connection, $result);
1072  }
1073 
1074  // }}}
1075  // {{{ nextID()
1076 
1088  function nextID($seq_name, $ondemand = true)
1089  {
1090  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1091  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1092  $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
1094  $result =& $this->_doQuery($query, true);
1095  $this->popExpect();
1096  if (PEAR::isError($result)) {
1097  if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
1098  $this->loadModule('Manager', null, true);
1099  $result = $this->manager->createSequence($seq_name);
1100  if (PEAR::isError($result)) {
1101  return $this->raiseError($result, null, null,
1102  'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
1103  } else {
1104  return $this->nextID($seq_name, false);
1105  }
1106  }
1107  return $result;
1108  }
1109  $value = $this->lastInsertID();
1110  if (is_numeric($value)) {
1111  $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
1112  $result =& $this->_doQuery($query, true);
1113  if (PEAR::isError($result)) {
1114  $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
1115  }
1116  }
1117  return $value;
1118  }
1119 
1120  // }}}
1121  // {{{ lastInsertID()
1122 
1132  function lastInsertID($table = null, $field = null)
1133  {
1134  // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
1135  return $this->queryOne('SELECT LAST_INSERT_ID()');
1136  }
1137 
1138  // }}}
1139  // {{{ currID()
1140 
1148  function currID($seq_name)
1149  {
1150  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1151  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1152  $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
1153  return $this->queryOne($query, 'integer');
1154  }
1155 }
1156 
1165 {
1166  // }}}
1167  // {{{ fetchRow()
1168 
1177  function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
1178  {
1179  if (!is_null($rownum)) {
1180  $seek = $this->seek($rownum);
1181  if (PEAR::isError($seek)) {
1182  return $seek;
1183  }
1184  }
1185  if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
1186  $fetchmode = $this->db->fetchmode;
1187  }
1188  if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
1189  $row = @mysqli_fetch_assoc($this->result);
1190  if (is_array($row)
1191  && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
1192  ) {
1193  $row = array_change_key_case($row, $this->db->options['field_case']);
1194  }
1195  } else {
1196  $row = @mysqli_fetch_row($this->result);
1197  }
1198 
1199  if (!$row) {
1200  if ($this->result === false) {
1201  $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1202  'resultset has already been freed', __FUNCTION__);
1203  return $err;
1204  }
1205  $null = null;
1206  return $null;
1207  }
1208  $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
1209  if ($mode) {
1210  $this->db->_fixResultArrayValues($row, $mode);
1211  }
1212  if (!empty($this->types)) {
1213  $row = $this->db->datatype->convertResultRow($this->types, $row, false);
1214  }
1215  if (!empty($this->values)) {
1216  $this->_assignBindColumns($row);
1217  }
1218  if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
1219  $object_class = $this->db->options['fetch_class'];
1220  if ($object_class == 'stdClass') {
1221  $row = (object) $row;
1222  } else {
1223  $row = &new $object_class($row);
1224  }
1225  }
1226  ++$this->rownum;
1227  return $row;
1228  }
1229 
1230  // }}}
1231  // {{{ _getColumnNames()
1232 
1242  function _getColumnNames()
1243  {
1244  $columns = array();
1245  $numcols = $this->numCols();
1246  if (PEAR::isError($numcols)) {
1247  return $numcols;
1248  }
1249  for ($column = 0; $column < $numcols; $column++) {
1250  $column_info = @mysqli_fetch_field_direct($this->result, $column);
1251  $columns[$column_info->name] = $column;
1252  }
1253  if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1254  $columns = array_change_key_case($columns, $this->db->options['field_case']);
1255  }
1256  return $columns;
1257  }
1258 
1259  // }}}
1260  // {{{ numCols()
1261 
1269  function numCols()
1270  {
1271  $cols = @mysqli_num_fields($this->result);
1272  if (is_null($cols)) {
1273  if ($this->result === false) {
1274  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1275  'resultset has already been freed', __FUNCTION__);
1276  } elseif (is_null($this->result)) {
1277  return count($this->types);
1278  }
1279  return $this->db->raiseError(null, null, null,
1280  'Could not get column count', __FUNCTION__);
1281  }
1282  return $cols;
1283  }
1284 
1285  // }}}
1286  // {{{ nextResult()
1287 
1294  function nextResult()
1295  {
1296  $connection = $this->db->getConnection();
1297  if (PEAR::isError($connection)) {
1298  return $connection;
1299  }
1300 
1301  if (!@mysqli_more_results($connection)) {
1302  return false;
1303  }
1304  if (!@mysqli_next_result($connection)) {
1305  return false;
1306  }
1307  if (!($this->result = @mysqli_use_result($connection))) {
1308  return false;
1309  }
1310  return MDB2_OK;
1311  }
1312 
1313  // }}}
1314  // {{{ free()
1315 
1322  function free()
1323  {
1324  if (is_object($this->result) && $this->db->connection) {
1325  $free = @mysqli_free_result($this->result);
1326  if ($free === false) {
1327  return $this->db->raiseError(null, null, null,
1328  'Could not free result', __FUNCTION__);
1329  }
1330  }
1331  $this->result = false;
1332  return MDB2_OK;
1333  }
1334 }
1335 
1344 {
1345  // }}}
1346  // {{{ seek()
1347 
1355  function seek($rownum = 0)
1356  {
1357  if ($this->rownum != ($rownum - 1) && !@mysqli_data_seek($this->result, $rownum)) {
1358  if ($this->result === false) {
1359  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1360  'resultset has already been freed', __FUNCTION__);
1361  } elseif (is_null($this->result)) {
1362  return MDB2_OK;
1363  }
1364  return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1365  'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1366  }
1367  $this->rownum = $rownum - 1;
1368  return MDB2_OK;
1369  }
1370 
1371  // }}}
1372  // {{{ valid()
1373 
1380  function valid()
1381  {
1382  $numrows = $this->numRows();
1383  if (PEAR::isError($numrows)) {
1384  return $numrows;
1385  }
1386  return $this->rownum < ($numrows - 1);
1387  }
1388 
1389  // }}}
1390  // {{{ numRows()
1391 
1398  function numRows()
1399  {
1400  $rows = @mysqli_num_rows($this->result);
1401  if (is_null($rows)) {
1402  if ($this->result === false) {
1403  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1404  'resultset has already been freed', __FUNCTION__);
1405  } elseif (is_null($this->result)) {
1406  return 0;
1407  }
1408  return $this->db->raiseError(null, null, null,
1409  'Could not get row count', __FUNCTION__);
1410  }
1411  return $rows;
1412  }
1413 
1414  // }}}
1415  // {{{ nextResult()
1416 
1424  function nextResult()
1425  {
1426  $connection = $this->db->getConnection();
1427  if (PEAR::isError($connection)) {
1428  return $connection;
1429  }
1430 
1431  if (!@mysqli_more_results($connection)) {
1432  return false;
1433  }
1434  if (!@mysqli_next_result($connection)) {
1435  return false;
1436  }
1437  if (!($this->result = @mysqli_store_result($connection))) {
1438  return false;
1439  }
1440  return MDB2_OK;
1441  }
1442 }
1443 
1452 {
1453  // {{{ _execute()
1454 
1463  function &_execute($result_class = true, $result_wrap_class = false)
1464  {
1465  if (is_null($this->statement)) {
1466  $result =& parent::_execute($result_class, $result_wrap_class);
1467  return $result;
1468  }
1469  $this->db->last_query = $this->query;
1470  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
1471  if ($this->db->getOption('disable_query')) {
1472  $result = $this->is_manip ? 0 : null;
1473  return $result;
1474  }
1475 
1476  $connection = $this->db->getConnection();
1477  if (PEAR::isError($connection)) {
1478  return $connection;
1479  }
1480 
1481  if (!is_object($this->statement)) {
1482  $query = 'EXECUTE '.$this->statement;
1483  }
1484  if (!empty($this->positions)) {
1485  $parameters = array(0 => $this->statement, 1 => '');
1486  $lobs = array();
1487  $i = 0;
1488  foreach ($this->positions as $parameter) {
1489  if (!array_key_exists($parameter, $this->values)) {
1490  return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
1491  'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
1492  }
1493  $value = $this->values[$parameter];
1494  $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1495  if (!is_object($this->statement)) {
1496  if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1497  if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1498  if ($match[1] == 'file://') {
1499  $value = $match[2];
1500  }
1501  $value = @fopen($value, 'r');
1502  $close = true;
1503  }
1504  if (is_resource($value)) {
1505  $data = '';
1506  while (!@feof($value)) {
1507  $data.= @fread($value, $this->db->options['lob_buffer_length']);
1508  }
1509  if ($close) {
1510  @fclose($value);
1511  }
1512  $value = $data;
1513  }
1514  }
1515  $quoted = $this->db->quote($value, $type);
1516  if (PEAR::isError($quoted)) {
1517  return $quoted;
1518  }
1519  $param_query = 'SET @'.$parameter.' = '.$quoted;
1520  $result = $this->db->_doQuery($param_query, true, $connection);
1521  if (PEAR::isError($result)) {
1522  return $result;
1523  }
1524  } else {
1525  if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1526  $parameters[] = null;
1527  $parameters[1].= 'b';
1528  $lobs[$i] = $parameter;
1529  } else {
1530  $parameters[] = $this->db->quote($value, $type, false);
1531  $parameters[1].= $this->db->datatype->mapPrepareDatatype($type);
1532  }
1533  ++$i;
1534  }
1535  }
1536 
1537  if (!is_object($this->statement)) {
1538  $query.= ' USING @'.implode(', @', array_values($this->positions));
1539  } else {
1540 
1541  // pear bug #17024: php 5.3 changed mysqli_stmt_bind_param()
1542  $stmt_params = array();
1543  foreach ($parameters as $k => &$value) {
1544  $stmt_params[$k] = &$value;
1545  }
1546 
1547  $result = @call_user_func_array('mysqli_stmt_bind_param', $stmt_params);
1548  if ($result === false) {
1549  $err =& $this->db->raiseError(null, null, null,
1550  'Unable to bind parameters', __FUNCTION__);
1551  return $err;
1552  }
1553 
1554  foreach ($lobs as $i => $parameter) {
1555  $value = $this->values[$parameter];
1556  $close = false;
1557  if (!is_resource($value)) {
1558  $close = true;
1559  if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1560  if ($match[1] == 'file://') {
1561  $value = $match[2];
1562  }
1563  $value = @fopen($value, 'r');
1564  } else {
1565  $fp = @tmpfile();
1566  @fwrite($fp, $value);
1567  @rewind($fp);
1568  $value = $fp;
1569  }
1570  }
1571  while (!@feof($value)) {
1572  $data = @fread($value, $this->db->options['lob_buffer_length']);
1573  @mysqli_stmt_send_long_data($this->statement, $i, $data);
1574  }
1575  if ($close) {
1576  @fclose($value);
1577  }
1578  }
1579  }
1580  }
1581 
1582  if (!is_object($this->statement)) {
1583  $result = $this->db->_doQuery($query, $this->is_manip, $connection);
1584  if (PEAR::isError($result)) {
1585  return $result;
1586  }
1587 
1588  if ($this->is_manip) {
1589  $affected_rows = $this->db->_affectedRows($connection, $result);
1590  return $affected_rows;
1591  }
1592 
1593  $result =& $this->db->_wrapResult($result, $this->result_types,
1594  $result_class, $result_wrap_class, $this->limit, $this->offset);
1595  } else {
1596  if (!@mysqli_stmt_execute($this->statement)) {
1597  $err =& $this->db->raiseError(null, null, null,
1598  'Unable to execute statement', __FUNCTION__);
1599  return $err;
1600  }
1601 
1602  if ($this->is_manip) {
1603  $affected_rows = @mysqli_stmt_affected_rows($this->statement);
1604  return $affected_rows;
1605  }
1606 
1607  if ($this->db->options['result_buffering']) {
1608  @mysqli_stmt_store_result($this->statement);
1609  }
1610 
1611  $result =& $this->db->_wrapResult($this->statement, $this->result_types,
1612  $result_class, $result_wrap_class, $this->limit, $this->offset);
1613  }
1614 
1615  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
1616  return $result;
1617  }
1618 
1619  // }}}
1620  // {{{ free()
1621 
1628  function free()
1629  {
1630  if (is_null($this->positions)) {
1631  return $this->db->raiseError(MDB2_ERROR, null, null,
1632  'Prepared statement has already been freed', __FUNCTION__);
1633  }
1634  $result = MDB2_OK;
1635 
1636  if (is_object($this->statement)) {
1637  if (!@mysqli_stmt_close($this->statement)) {
1638  $result = $this->db->raiseError(null, null, null,
1639  'Could not free statement', __FUNCTION__);
1640  }
1641  } elseif (!is_null($this->statement)) {
1642  $connection = $this->db->getConnection();
1643  if (PEAR::isError($connection)) {
1644  return $connection;
1645  }
1646 
1647  $query = 'DEALLOCATE PREPARE '.$this->statement;
1648  $result = $this->db->_doQuery($query, true, $connection);
1649  }
1650 
1651  parent::free();
1652  return $result;
1653  }
1654 }
1655 ?>