ILIAS  release_4-3 Revision
 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  $connection = @mysqli_connect(
425  $this->dsn['hostspec'],
426  $this->dsn['username'],
427  $this->dsn['password'],
428  $this->database_name,
429  $this->dsn['port'],
430  $this->dsn['socket']
431  );
432  }
433 
434  if (!$connection) {
435  if (($err = @mysqli_connect_error()) != '') {
436  return $this->raiseError(null,
437  null, null, $err, __FUNCTION__);
438  } else {
439  return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
440  'unable to establish a connection', __FUNCTION__);
441  }
442  }
443 
444  if (!empty($this->dsn['charset'])) {
445  $result = $this->setCharset($this->dsn['charset'], $connection);
446  if (PEAR::isError($result)) {
447  return $result;
448  }
449  }
450 
451  $this->connection = $connection;
452  $this->connected_dsn = $this->dsn;
453  $this->connected_database_name = $this->database_name;
454  $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
455 
456  $this->supported['transactions'] = $this->options['use_transactions'];
457  if ($this->options['default_table_type']) {
458  switch (strtoupper($this->options['default_table_type'])) {
459  case 'BLACKHOLE':
460  case 'MEMORY':
461  case 'ARCHIVE':
462  case 'CSV':
463  case 'HEAP':
464  case 'ISAM':
465  case 'MERGE':
466  case 'MRG_ISAM':
467  case 'ISAM':
468  case 'MRG_MYISAM':
469  case 'MYISAM':
470  $this->supported['transactions'] = false;
471  $this->warnings[] = $this->options['default_table_type'] .
472  ' is not a supported default table type';
473  break;
474  }
475  }
476 
477  $this->_getServerCapabilities();
478 
479  return MDB2_OK;
480  }
481 
482  // }}}
483  // {{{ setCharset()
484 
493  function setCharset($charset, $connection = null)
494  {
495  if (is_null($connection)) {
496  $connection = $this->getConnection();
497  if (PEAR::isError($connection)) {
498  return $connection;
499  }
500  }
501  $query = "SET NAMES '".mysqli_real_escape_string($connection, $charset)."'";
502  return $this->_doQuery($query, true, $connection);
503  }
504 
505  // }}}
506  // {{{ disconnect()
507 
517  function disconnect($force = true)
518  {
519  if (is_object($this->connection)) {
520  if ($this->in_transaction) {
521  $dsn = $this->dsn;
523  $persistent = $this->options['persistent'];
524  $this->dsn = $this->connected_dsn;
525  $this->database_name = $this->connected_database_name;
526  $this->options['persistent'] = $this->opened_persistent;
527  $this->rollback();
528  $this->dsn = $dsn;
529  $this->database_name = $database_name;
530  $this->options['persistent'] = $persistent;
531  }
532 
533  if ($force) {
534  @mysqli_close($this->connection);
535  }
536  }
537  return parent::disconnect($force);
538  }
539 
540  // }}}
541  // {{{ _doQuery()
542 
552  function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
553  {
554  $this->last_query = $query;
555  $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
556  if ($result) {
557  if (PEAR::isError($result)) {
558  return $result;
559  }
560  $query = $result;
561  }
562  if ($this->options['disable_query']) {
563  $result = $is_manip ? 0 : null;
564  return $result;
565  }
566 
567  if (is_null($connection)) {
568  $connection = $this->getConnection();
569  if (PEAR::isError($connection)) {
570  return $connection;
571  }
572  }
573  if (is_null($database_name)) {
575  }
576 
577  if ($database_name) {
578  if ($database_name != $this->connected_database_name) {
579  if (!@mysqli_select_db($connection, $database_name)) {
580  $err = $this->raiseError(null, null, null,
581  'Could not select the database: '.$database_name, __FUNCTION__);
582  return $err;
583  }
584  $this->connected_database_name = $database_name;
585  }
586  }
587 
588  if ($this->options['multi_query']) {
589  $result = mysqli_multi_query($connection, $query);
590  } else {
591  $resultmode = $this->options['result_buffering'] ? MYSQLI_USE_RESULT : MYSQLI_USE_RESULT;
592  $result = mysqli_query($connection, $query);
593  }
594 
595  if (!$result) {
596  $err =& $this->raiseError(null, null, null,
597  'Could not execute statement', __FUNCTION__);
598  return $err;
599  }
600 
601  if ($this->options['multi_query']) {
602  if ($this->options['result_buffering']) {
603  if (!($result = @mysqli_store_result($connection))) {
604  $err =& $this->raiseError(null, null, null,
605  'Could not get the first result from a multi query', __FUNCTION__);
606  return $err;
607  }
608  } elseif (!($result = @mysqli_use_result($connection))) {
609  $err =& $this->raiseError(null, null, null,
610  'Could not get the first result from a multi query', __FUNCTION__);
611  return $err;
612  }
613  }
614 
615  $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
616  return $result;
617  }
618 
619  // }}}
620  // {{{ _affectedRows()
621 
631  {
632  if (is_null($connection)) {
633  $connection = $this->getConnection();
634  if (PEAR::isError($connection)) {
635  return $connection;
636  }
637  }
638  return @mysqli_affected_rows($connection);
639  }
640 
641  // }}}
642  // {{{ _modifyQuery()
643 
654  function _modifyQuery($query, $is_manip, $limit, $offset)
655  {
656  if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
657  // "DELETE FROM table" gives 0 affected rows in MySQL.
658  // This little hack lets you know how many rows were deleted.
659  if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
660  $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
661  'DELETE FROM \1 WHERE 1=1', $query);
662  }
663  }
664  if ($limit > 0
665  && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
666  ) {
667  $query = rtrim($query);
668  if (substr($query, -1) == ';') {
669  $query = substr($query, 0, -1);
670  }
671 
672  // LIMIT doesn't always come last in the query
673  // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
674  $after = '';
675  if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
676  $after = $matches[0];
677  $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
678  } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
679  $after = $matches[0];
680  $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
681  } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
682  $after = $matches[0];
683  $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
684  }
685 
686  if ($is_manip) {
687  return $query . " LIMIT $limit";
688  } else {
689  return $query . " LIMIT $offset, $limit";
690  }
691  }
692  return $query;
693  }
694 
695  // }}}
696  // {{{ getServerVersion()
697 
705  function getServerVersion($native = false)
706  {
707  $connection = $this->getConnection();
708  if (PEAR::isError($connection)) {
709  return $connection;
710  }
711  if ($this->connected_server_info) {
712  $server_info = $this->connected_server_info;
713  } else {
714  $server_info = @mysqli_get_server_info($connection);
715  }
716  if (!$server_info) {
717  return $this->raiseError(null, null, null,
718  'Could not get server information', __FUNCTION__);
719  }
720  // cache server_info
721  $this->connected_server_info = $server_info;
722  if (!$native) {
723  $tmp = explode('.', $server_info, 3);
724  if (isset($tmp[2]) && strpos($tmp[2], '-')) {
725  $tmp2 = explode('-', @$tmp[2], 2);
726  } else {
727  $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
728  $tmp2[1] = null;
729  }
730  $server_info = array(
731  'major' => isset($tmp[0]) ? $tmp[0] : null,
732  'minor' => isset($tmp[1]) ? $tmp[1] : null,
733  'patch' => $tmp2[0],
734  'extra' => $tmp2[1],
735  'native' => $server_info,
736  );
737  }
738  return $server_info;
739  }
740 
741  // }}}
742  // {{{ _getServerCapabilities()
743 
751  {
752  static $already_checked = false;
753  if (!$already_checked) {
754  $already_checked = true;
755 
756  //set defaults
757  $this->supported['sub_selects'] = 'emulated';
758  $this->supported['prepared_statements'] = 'emulated';
759  $this->start_transaction = false;
760  $this->varchar_max_length = 255;
761 
762  $server_info = $this->getServerVersion();
763  if (is_array($server_info)) {
764  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) {
765  $this->supported['sub_selects'] = true;
766  $this->supported['prepared_statements'] = true;
767  }
768 
769  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<')
770  || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<')
771  ) {
772  $this->supported['savepoints'] = true;
773  }
774 
775  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) {
776  $this->start_transaction = true;
777  }
778 
779  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
780  $this->varchar_max_length = 65532;
781  }
782  }
783  }
784  }
785 
786  // }}}
787  // {{{ function _skipUserDefinedVariable($query, $position)
788 
801  function _skipUserDefinedVariable($query, $position)
802  {
803  $found = strpos(strrev(substr($query, 0, $position)), '@');
804  if ($found === false) {
805  return $position;
806  }
807  $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
808  $substring = substr($query, $pos, $position - $pos + 2);
809  if (preg_match('/^@\w+:=$/', $substring)) {
810  return $position + 1; //found an user defined variable: skip it
811  }
812  return $position;
813  }
814 
815  // }}}
816  // {{{ prepare()
817 
838  function &prepare($query, $types = null, $result_types = null, $lobs = array())
839  {
840  if ($this->options['emulate_prepared']
841  || $this->supported['prepared_statements'] !== true
842  ) {
843  $obj =& parent::prepare($query, $types, $result_types, $lobs);
844  return $obj;
845  }
846  $is_manip = ($result_types === MDB2_PREPARE_MANIP);
849  $this->offset = $this->limit = 0;
850  $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
851  $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
852  if ($result) {
853  if (PEAR::isError($result)) {
854  return $result;
855  }
856  $query = $result;
857  }
858  $placeholder_type_guess = $placeholder_type = null;
859  $question = '?';
860  $colon = ':';
861  $positions = array();
862  $position = 0;
863  while ($position < strlen($query)) {
864  $q_position = strpos($query, $question, $position);
865  $c_position = strpos($query, $colon, $position);
866  if ($q_position && $c_position) {
867  $p_position = min($q_position, $c_position);
868  } elseif ($q_position) {
869  $p_position = $q_position;
870  } elseif ($c_position) {
871  $p_position = $c_position;
872  } else {
873  break;
874  }
875  if (is_null($placeholder_type)) {
876  $placeholder_type_guess = $query[$p_position];
877  }
878 
879  $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
880  if (PEAR::isError($new_pos)) {
881  return $new_pos;
882  }
883  if ($new_pos != $position) {
884  $position = $new_pos;
885  continue; //evaluate again starting from the new position
886  }
887 
888  if ($query[$position] == $placeholder_type_guess) {
889  if (is_null($placeholder_type)) {
890  $placeholder_type = $query[$p_position];
891  $question = $colon = $placeholder_type;
892  }
893  if ($placeholder_type == ':') {
894  //make sure this is not part of an user defined variable
895  $new_pos = $this->_skipUserDefinedVariable($query, $position);
896  if ($new_pos != $position) {
897  $position = $new_pos;
898  continue; //evaluate again starting from the new position
899  }
900  $parameter = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query);
901  if ($parameter === '') {
902  $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
903  'named parameter with an empty name', __FUNCTION__);
904  return $err;
905  }
906  $positions[$p_position] = $parameter;
907  $query = substr_replace($query, '?', $position, strlen($parameter)+1);
908  } else {
909  $positions[$p_position] = count($positions);
910  }
911  $position = $p_position + 1;
912  } else {
913  $position = $p_position;
914  }
915  }
916  $connection = $this->getConnection();
917  if (PEAR::isError($connection)) {
918  return $connection;
919  }
920 
921  if (!$is_manip) {
922  $statement_name = sprintf($this->options['statement_format'], $this->phptype, md5(time() + rand()));
923  $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
924 
925  $statement =& $this->_doQuery($query, true, $connection);
926  if (PEAR::isError($statement)) {
927  return $statement;
928  }
929  $statement = $statement_name;
930  } else {
931  $statement = @mysqli_prepare($connection, $query);
932  if (!$statement) {
933  $err =& $this->raiseError(null, null, null,
934  'Unable to create prepared statement handle', __FUNCTION__);
935  return $err;
936  }
937  }
938 
939  $class_name = 'MDB2_Statement_'.$this->phptype;
940  $obj =& new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
941  $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
942  return $obj;
943  }
944 
945  // }}}
946  // {{{ replace()
947 
1012  function replace($table, $fields)
1013  {
1014  $count = count($fields);
1015  $query = $values = '';
1016  $keys = $colnum = 0;
1017  for (reset($fields); $colnum < $count; next($fields), $colnum++) {
1018  $name = key($fields);
1019  if ($colnum > 0) {
1020  $query .= ',';
1021  $values.= ',';
1022  }
1023  $query.= $name;
1024  if (isset($fields[$name]['null']) && $fields[$name]['null']) {
1025  $value = 'NULL';
1026  } else {
1027  $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
1028  $value = $this->quote($fields[$name]['value'], $type);
1029  }
1030  $values.= $value;
1031  if (isset($fields[$name]['key']) && $fields[$name]['key']) {
1032  if ($value === 'NULL') {
1033  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1034  'key value '.$name.' may not be NULL', __FUNCTION__);
1035  }
1036  $keys++;
1037  }
1038  }
1039  if ($keys == 0) {
1040  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1041  'not specified which fields are keys', __FUNCTION__);
1042  }
1043 
1044  $connection = $this->getConnection();
1045  if (PEAR::isError($connection)) {
1046  return $connection;
1047  }
1048 
1049  $query = "REPLACE INTO $table ($query) VALUES ($values)";
1050  $result =& $this->_doQuery($query, true, $connection);
1051  if (PEAR::isError($result)) {
1052  return $result;
1053  }
1054  return $this->_affectedRows($connection, $result);
1055  }
1056 
1057  // }}}
1058  // {{{ nextID()
1059 
1071  function nextID($seq_name, $ondemand = true)
1072  {
1073  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1074  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1075  $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
1077  $result =& $this->_doQuery($query, true);
1078  $this->popExpect();
1079  if (PEAR::isError($result)) {
1080  if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
1081  $this->loadModule('Manager', null, true);
1082  $result = $this->manager->createSequence($seq_name);
1083  if (PEAR::isError($result)) {
1084  return $this->raiseError($result, null, null,
1085  'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
1086  } else {
1087  return $this->nextID($seq_name, false);
1088  }
1089  }
1090  return $result;
1091  }
1092  $value = $this->lastInsertID();
1093  if (is_numeric($value)) {
1094  $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
1095  $result =& $this->_doQuery($query, true);
1096  if (PEAR::isError($result)) {
1097  $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
1098  }
1099  }
1100  return $value;
1101  }
1102 
1103  // }}}
1104  // {{{ lastInsertID()
1105 
1115  function lastInsertID($table = null, $field = null)
1116  {
1117  // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
1118  return $this->queryOne('SELECT LAST_INSERT_ID()');
1119  }
1120 
1121  // }}}
1122  // {{{ currID()
1123 
1131  function currID($seq_name)
1132  {
1133  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1134  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1135  $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
1136  return $this->queryOne($query, 'integer');
1137  }
1138 }
1139 
1148 {
1149  // }}}
1150  // {{{ fetchRow()
1151 
1160  function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
1161  {
1162  if (!is_null($rownum)) {
1163  $seek = $this->seek($rownum);
1164  if (PEAR::isError($seek)) {
1165  return $seek;
1166  }
1167  }
1168  if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
1169  $fetchmode = $this->db->fetchmode;
1170  }
1171  if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
1172  $row = @mysqli_fetch_assoc($this->result);
1173  if (is_array($row)
1174  && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
1175  ) {
1176  $row = array_change_key_case($row, $this->db->options['field_case']);
1177  }
1178  } else {
1179  $row = @mysqli_fetch_row($this->result);
1180  }
1181 
1182  if (!$row) {
1183  if ($this->result === false) {
1184  $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1185  'resultset has already been freed', __FUNCTION__);
1186  return $err;
1187  }
1188  $null = null;
1189  return $null;
1190  }
1191  $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
1192  if ($mode) {
1193  $this->db->_fixResultArrayValues($row, $mode);
1194  }
1195  if (!empty($this->types)) {
1196  $row = $this->db->datatype->convertResultRow($this->types, $row, false);
1197  }
1198  if (!empty($this->values)) {
1199  $this->_assignBindColumns($row);
1200  }
1201  if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
1202  $object_class = $this->db->options['fetch_class'];
1203  if ($object_class == 'stdClass') {
1204  $row = (object) $row;
1205  } else {
1206  $row = &new $object_class($row);
1207  }
1208  }
1209  ++$this->rownum;
1210  return $row;
1211  }
1212 
1213  // }}}
1214  // {{{ _getColumnNames()
1215 
1225  function _getColumnNames()
1226  {
1227  $columns = array();
1228  $numcols = $this->numCols();
1229  if (PEAR::isError($numcols)) {
1230  return $numcols;
1231  }
1232  for ($column = 0; $column < $numcols; $column++) {
1233  $column_info = @mysqli_fetch_field_direct($this->result, $column);
1234  $columns[$column_info->name] = $column;
1235  }
1236  if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1237  $columns = array_change_key_case($columns, $this->db->options['field_case']);
1238  }
1239  return $columns;
1240  }
1241 
1242  // }}}
1243  // {{{ numCols()
1244 
1252  function numCols()
1253  {
1254  $cols = @mysqli_num_fields($this->result);
1255  if (is_null($cols)) {
1256  if ($this->result === false) {
1257  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1258  'resultset has already been freed', __FUNCTION__);
1259  } elseif (is_null($this->result)) {
1260  return count($this->types);
1261  }
1262  return $this->db->raiseError(null, null, null,
1263  'Could not get column count', __FUNCTION__);
1264  }
1265  return $cols;
1266  }
1267 
1268  // }}}
1269  // {{{ nextResult()
1270 
1277  function nextResult()
1278  {
1279  $connection = $this->db->getConnection();
1280  if (PEAR::isError($connection)) {
1281  return $connection;
1282  }
1283 
1284  if (!@mysqli_more_results($connection)) {
1285  return false;
1286  }
1287  if (!@mysqli_next_result($connection)) {
1288  return false;
1289  }
1290  if (!($this->result = @mysqli_use_result($connection))) {
1291  return false;
1292  }
1293  return MDB2_OK;
1294  }
1295 
1296  // }}}
1297  // {{{ free()
1298 
1305  function free()
1306  {
1307  if (is_object($this->result) && $this->db->connection) {
1308  $free = @mysqli_free_result($this->result);
1309  if ($free === false) {
1310  return $this->db->raiseError(null, null, null,
1311  'Could not free result', __FUNCTION__);
1312  }
1313  }
1314  $this->result = false;
1315  return MDB2_OK;
1316  }
1317 }
1318 
1327 {
1328  // }}}
1329  // {{{ seek()
1330 
1338  function seek($rownum = 0)
1339  {
1340  if ($this->rownum != ($rownum - 1) && !@mysqli_data_seek($this->result, $rownum)) {
1341  if ($this->result === false) {
1342  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1343  'resultset has already been freed', __FUNCTION__);
1344  } elseif (is_null($this->result)) {
1345  return MDB2_OK;
1346  }
1347  return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1348  'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1349  }
1350  $this->rownum = $rownum - 1;
1351  return MDB2_OK;
1352  }
1353 
1354  // }}}
1355  // {{{ valid()
1356 
1363  function valid()
1364  {
1365  $numrows = $this->numRows();
1366  if (PEAR::isError($numrows)) {
1367  return $numrows;
1368  }
1369  return $this->rownum < ($numrows - 1);
1370  }
1371 
1372  // }}}
1373  // {{{ numRows()
1374 
1381  function numRows()
1382  {
1383  $rows = @mysqli_num_rows($this->result);
1384  if (is_null($rows)) {
1385  if ($this->result === false) {
1386  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1387  'resultset has already been freed', __FUNCTION__);
1388  } elseif (is_null($this->result)) {
1389  return 0;
1390  }
1391  return $this->db->raiseError(null, null, null,
1392  'Could not get row count', __FUNCTION__);
1393  }
1394  return $rows;
1395  }
1396 
1397  // }}}
1398  // {{{ nextResult()
1399 
1407  function nextResult()
1408  {
1409  $connection = $this->db->getConnection();
1410  if (PEAR::isError($connection)) {
1411  return $connection;
1412  }
1413 
1414  if (!@mysqli_more_results($connection)) {
1415  return false;
1416  }
1417  if (!@mysqli_next_result($connection)) {
1418  return false;
1419  }
1420  if (!($this->result = @mysqli_store_result($connection))) {
1421  return false;
1422  }
1423  return MDB2_OK;
1424  }
1425 }
1426 
1435 {
1436  // {{{ _execute()
1437 
1446  function &_execute($result_class = true, $result_wrap_class = false)
1447  {
1448  if (is_null($this->statement)) {
1449  $result =& parent::_execute($result_class, $result_wrap_class);
1450  return $result;
1451  }
1452  $this->db->last_query = $this->query;
1453  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
1454  if ($this->db->getOption('disable_query')) {
1455  $result = $this->is_manip ? 0 : null;
1456  return $result;
1457  }
1458 
1459  $connection = $this->db->getConnection();
1460  if (PEAR::isError($connection)) {
1461  return $connection;
1462  }
1463 
1464  if (!is_object($this->statement)) {
1465  $query = 'EXECUTE '.$this->statement;
1466  }
1467  if (!empty($this->positions)) {
1468  $parameters = array(0 => $this->statement, 1 => '');
1469  $lobs = array();
1470  $i = 0;
1471  foreach ($this->positions as $parameter) {
1472  if (!array_key_exists($parameter, $this->values)) {
1473  return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
1474  'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
1475  }
1476  $value = $this->values[$parameter];
1477  $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1478  if (!is_object($this->statement)) {
1479  if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1480  if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1481  if ($match[1] == 'file://') {
1482  $value = $match[2];
1483  }
1484  $value = @fopen($value, 'r');
1485  $close = true;
1486  }
1487  if (is_resource($value)) {
1488  $data = '';
1489  while (!@feof($value)) {
1490  $data.= @fread($value, $this->db->options['lob_buffer_length']);
1491  }
1492  if ($close) {
1493  @fclose($value);
1494  }
1495  $value = $data;
1496  }
1497  }
1498  $quoted = $this->db->quote($value, $type);
1499  if (PEAR::isError($quoted)) {
1500  return $quoted;
1501  }
1502  $param_query = 'SET @'.$parameter.' = '.$quoted;
1503  $result = $this->db->_doQuery($param_query, true, $connection);
1504  if (PEAR::isError($result)) {
1505  return $result;
1506  }
1507  } else {
1508  if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1509  $parameters[] = null;
1510  $parameters[1].= 'b';
1511  $lobs[$i] = $parameter;
1512  } else {
1513  $parameters[] = $this->db->quote($value, $type, false);
1514  $parameters[1].= $this->db->datatype->mapPrepareDatatype($type);
1515  }
1516  ++$i;
1517  }
1518  }
1519 
1520  if (!is_object($this->statement)) {
1521  $query.= ' USING @'.implode(', @', array_values($this->positions));
1522  } else {
1523 
1524  // pear bug #17024: php 5.3 changed mysqli_stmt_bind_param()
1525  $stmt_params = array();
1526  foreach ($parameters as $k => &$value) {
1527  $stmt_params[$k] = &$value;
1528  }
1529 
1530  $result = @call_user_func_array('mysqli_stmt_bind_param', $stmt_params);
1531  if ($result === false) {
1532  $err =& $this->db->raiseError(null, null, null,
1533  'Unable to bind parameters', __FUNCTION__);
1534  return $err;
1535  }
1536 
1537  foreach ($lobs as $i => $parameter) {
1538  $value = $this->values[$parameter];
1539  $close = false;
1540  if (!is_resource($value)) {
1541  $close = true;
1542  if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1543  if ($match[1] == 'file://') {
1544  $value = $match[2];
1545  }
1546  $value = @fopen($value, 'r');
1547  } else {
1548  $fp = @tmpfile();
1549  @fwrite($fp, $value);
1550  @rewind($fp);
1551  $value = $fp;
1552  }
1553  }
1554  while (!@feof($value)) {
1555  $data = @fread($value, $this->db->options['lob_buffer_length']);
1556  @mysqli_stmt_send_long_data($this->statement, $i, $data);
1557  }
1558  if ($close) {
1559  @fclose($value);
1560  }
1561  }
1562  }
1563  }
1564 
1565  if (!is_object($this->statement)) {
1566  $result = $this->db->_doQuery($query, $this->is_manip, $connection);
1567  if (PEAR::isError($result)) {
1568  return $result;
1569  }
1570 
1571  if ($this->is_manip) {
1572  $affected_rows = $this->db->_affectedRows($connection, $result);
1573  return $affected_rows;
1574  }
1575 
1576  $result =& $this->db->_wrapResult($result, $this->result_types,
1577  $result_class, $result_wrap_class, $this->limit, $this->offset);
1578  } else {
1579  if (!@mysqli_stmt_execute($this->statement)) {
1580  $err =& $this->db->raiseError(null, null, null,
1581  'Unable to execute statement', __FUNCTION__);
1582  return $err;
1583  }
1584 
1585  if ($this->is_manip) {
1586  $affected_rows = @mysqli_stmt_affected_rows($this->statement);
1587  return $affected_rows;
1588  }
1589 
1590  if ($this->db->options['result_buffering']) {
1591  @mysqli_stmt_store_result($this->statement);
1592  }
1593 
1594  $result =& $this->db->_wrapResult($this->statement, $this->result_types,
1595  $result_class, $result_wrap_class, $this->limit, $this->offset);
1596  }
1597 
1598  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
1599  return $result;
1600  }
1601 
1602  // }}}
1603  // {{{ free()
1604 
1611  function free()
1612  {
1613  if (is_null($this->positions)) {
1614  return $this->db->raiseError(MDB2_ERROR, null, null,
1615  'Prepared statement has already been freed', __FUNCTION__);
1616  }
1617  $result = MDB2_OK;
1618 
1619  if (is_object($this->statement)) {
1620  if (!@mysqli_stmt_close($this->statement)) {
1621  $result = $this->db->raiseError(null, null, null,
1622  'Could not free statement', __FUNCTION__);
1623  }
1624  } elseif (!is_null($this->statement)) {
1625  $connection = $this->db->getConnection();
1626  if (PEAR::isError($connection)) {
1627  return $connection;
1628  }
1629 
1630  $query = 'DEALLOCATE PREPARE '.$this->statement;
1631  $result = $this->db->_doQuery($query, true, $connection);
1632  }
1633 
1634  parent::free();
1635  return $result;
1636  }
1637 }
1638 ?>