ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
mysql.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: mysql.php,v 1.182 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 = 'mysql';
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  }
107 
108  // }}}
109  // {{{ errorInfo()
110 
118  function errorInfo($error = null)
119  {
120  if ($this->connection) {
121  $native_code = @mysql_errno($this->connection);
122  $native_msg = @mysql_error($this->connection);
123  } else {
124  $native_code = @mysql_errno();
125  $native_msg = @mysql_error();
126  }
127  if (is_null($error)) {
128  static $ecode_map;
129  if (empty($ecode_map)) {
130  $ecode_map = array(
131  1004 => MDB2_ERROR_CANNOT_CREATE,
132  1005 => MDB2_ERROR_CANNOT_CREATE,
133  1006 => MDB2_ERROR_CANNOT_CREATE,
135  1008 => MDB2_ERROR_CANNOT_DROP,
138  1046 => MDB2_ERROR_NODBSELECTED,
139  1048 => MDB2_ERROR_CONSTRAINT,
140  1049 => MDB2_ERROR_NOSUCHDB,
142  1051 => MDB2_ERROR_NOSUCHTABLE,
143  1054 => MDB2_ERROR_NOSUCHFIELD,
146  1064 => MDB2_ERROR_SYNTAX,
147  1091 => MDB2_ERROR_NOT_FOUND,
148  1100 => MDB2_ERROR_NOT_LOCKED,
151  1146 => MDB2_ERROR_NOSUCHTABLE,
152  1216 => MDB2_ERROR_CONSTRAINT,
153  1217 => MDB2_ERROR_CONSTRAINT,
154  1356 => MDB2_ERROR_DIVZERO,
155  1451 => MDB2_ERROR_CONSTRAINT,
156  1452 => MDB2_ERROR_CONSTRAINT,
157  );
158  }
159  if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
160  $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
161  $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
162  $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
163  } else {
164  // Doing this in case mode changes during runtime.
165  $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
166  $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
167  $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
168  }
169  if (isset($ecode_map[$native_code])) {
170  $error = $ecode_map[$native_code];
171  }
172  }
173  return array($error, $native_code, $native_msg);
174  }
175 
176  // }}}
177  // {{{ escape()
178 
190  function escape($text, $escape_wildcards = false)
191  {
192  if ($escape_wildcards) {
193  $text = $this->escapePattern($text);
194  }
195  $connection = $this->getConnection();
196  if (PEAR::isError($connection)) {
197  return $connection;
198  }
199  $text = @mysql_real_escape_string($text, $connection);
200  return $text;
201  }
202 
203  // }}}
204  // {{{
205 
214  function beginTransaction($savepoint = null)
215  {
216  $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
217  $this->_getServerCapabilities();
218  if (!is_null($savepoint)) {
219  if (!$this->supports('savepoints')) {
220  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
221  'savepoints are not supported', __FUNCTION__);
222  }
223  if (!$this->in_transaction) {
224  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
225  'savepoint cannot be released when changes are auto committed', __FUNCTION__);
226  }
227  $query = 'SAVEPOINT '.$savepoint;
228  return $this->_doQuery($query, true);
229  } elseif ($this->in_transaction) {
230  return MDB2_OK; //nothing to do
231  }
232  if (!$this->destructor_registered && $this->opened_persistent) {
233  $this->destructor_registered = true;
234  register_shutdown_function('MDB2_closeOpenTransactions');
235  }
236  $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1';
237  $result =& $this->_doQuery($query, true);
238  if (PEAR::isError($result)) {
239  return $result;
240  }
241  $this->in_transaction = true;
242  return MDB2_OK;
243  }
244 
245  // }}}
246  // {{{ commit()
247 
259  function commit($savepoint = null)
260  {
261  $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
262  if (!$this->in_transaction) {
263  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
264  'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
265  }
266  if (!is_null($savepoint)) {
267  if (!$this->supports('savepoints')) {
268  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
269  'savepoints are not supported', __FUNCTION__);
270  }
271  $server_info = $this->getServerVersion();
272  if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
273  return MDB2_OK;
274  }
275  $query = 'RELEASE SAVEPOINT '.$savepoint;
276  return $this->_doQuery($query, true);
277  }
278 
279  if (!$this->supports('transactions')) {
280  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
281  'transactions are not supported', __FUNCTION__);
282  }
283 
284  $result =& $this->_doQuery('COMMIT', true);
285  if (PEAR::isError($result)) {
286  return $result;
287  }
288  if (!$this->start_transaction) {
289  $query = 'SET AUTOCOMMIT = 0';
290  $result =& $this->_doQuery($query, true);
291  if (PEAR::isError($result)) {
292  return $result;
293  }
294  }
295  $this->in_transaction = false;
296  return MDB2_OK;
297  }
298 
299  // }}}
300  // {{{ rollback()
301 
313  function rollback($savepoint = null)
314  {
315  $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
316  if (!$this->in_transaction) {
317  return $this->raiseError(MDB2_ERROR_INVALID, null, null,
318  'rollback cannot be done changes are auto committed', __FUNCTION__);
319  }
320  if (!is_null($savepoint)) {
321  if (!$this->supports('savepoints')) {
322  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
323  'savepoints are not supported', __FUNCTION__);
324  }
325  $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
326  return $this->_doQuery($query, true);
327  }
328 
329  $query = 'ROLLBACK';
330  $result =& $this->_doQuery($query, true);
331  if (PEAR::isError($result)) {
332  return $result;
333  }
334  if (!$this->start_transaction) {
335  $query = 'SET AUTOCOMMIT = 0';
336  $result =& $this->_doQuery($query, true);
337  if (PEAR::isError($result)) {
338  return $result;
339  }
340  }
341  $this->in_transaction = false;
342  return MDB2_OK;
343  }
344 
345  // }}}
346  // {{{ function setTransactionIsolation()
347 
361  function setTransactionIsolation($isolation)
362  {
363  $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
364  if (!$this->supports('transactions')) {
365  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
366  'transactions are not supported', __FUNCTION__);
367  }
368  switch ($isolation) {
369  case 'READ UNCOMMITTED':
370  case 'READ COMMITTED':
371  case 'REPEATABLE READ':
372  case 'SERIALIZABLE':
373  break;
374  default:
375  return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
376  'isolation level is not supported: '.$isolation, __FUNCTION__);
377  }
378 
379  $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation";
380  return $this->_doQuery($query, true);
381  }
382 
383  // }}}
384  // {{{ connect()
385 
391  function connect()
392  {
393  if (is_resource($this->connection)) {
394  // Performance fix: == is much faster than array_diff
395  #if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
396  if($this->connected_dsn == $this->dsn
397  && $this->opened_persistent == $this->options['persistent']
398  && $this->connected_database_name == $this->database_name
399  ) {
400  return MDB2_OK;
401  }
402  $this->disconnect(false);
403  }
404 
405  if (!PEAR::loadExtension($this->phptype)) {
406  return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
407  'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
408  }
409 
410  $params = array();
411  if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') {
412  $params[0] = ':' . $this->dsn['socket'];
413  } else {
414  $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec']
415  : 'localhost';
416  if ($this->dsn['port']) {
417  $params[0].= ':' . $this->dsn['port'];
418  }
419  }
420  $params[] = $this->dsn['username'] ? $this->dsn['username'] : null;
421  $params[] = $this->dsn['password'] ? $this->dsn['password'] : null;
422  if (!$this->options['persistent']) {
423  if (isset($this->dsn['new_link'])
424  && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true)
425  ) {
426  $params[] = true;
427  } else {
428  $params[] = false;
429  }
430  }
431  if (version_compare(phpversion(), '4.3.0', '>=')) {
432  $params[] = isset($this->dsn['client_flags'])
433  ? $this->dsn['client_flags'] : null;
434  }
435  $connect_function = $this->options['persistent'] ? 'mysql_pconnect' : 'mysql_connect';
436 
437  $connection = @call_user_func_array($connect_function, $params);
438  if (!$connection) {
439  if (($err = @mysql_error()) != '') {
440  return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
441  $err, __FUNCTION__);
442  } else {
443  return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
444  'unable to establish a connection', __FUNCTION__);
445  }
446  }
447 
448  if (!empty($this->dsn['charset'])) {
449  $result = $this->setCharset($this->dsn['charset'], $connection);
450  if (PEAR::isError($result)) {
451  return $result;
452  }
453  }
454 
455  $this->connection = $connection;
456  $this->connected_dsn = $this->dsn;
457  $this->connected_database_name = '';
458  $this->opened_persistent = $this->options['persistent'];
459  $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
460 
461  if ($this->database_name) {
462  if ($this->database_name != $this->connected_database_name) {
463  if (!@mysql_select_db($this->database_name, $connection)) {
464  $err = $this->raiseError(null, null, null,
465  'Could not select the database: '.$this->database_name, __FUNCTION__);
466  return $err;
467  }
468  $this->connected_database_name = $this->database_name;
469  }
470  }
471 
472  $this->supported['transactions'] = $this->options['use_transactions'];
473  if ($this->options['default_table_type']) {
474  switch (strtoupper($this->options['default_table_type'])) {
475  case 'BLACKHOLE':
476  case 'MEMORY':
477  case 'ARCHIVE':
478  case 'CSV':
479  case 'HEAP':
480  case 'ISAM':
481  case 'MERGE':
482  case 'MRG_ISAM':
483  case 'ISAM':
484  case 'MRG_MYISAM':
485  case 'MYISAM':
486  $this->supported['transactions'] = false;
487  $this->warnings[] = $this->options['default_table_type'] .
488  ' is not a supported default table type';
489  break;
490  }
491  }
492 
493  $this->_getServerCapabilities();
494 
495  return MDB2_OK;
496  }
497 
498  // }}}
499  // {{{ setCharset()
500 
509  function setCharset($charset, $connection = null)
510  {
511  if (is_null($connection)) {
512  $connection = $this->getConnection();
513  if (PEAR::isError($connection)) {
514  return $connection;
515  }
516  }
517  $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'";
518  return $this->_doQuery($query, true, $connection);
519  }
520 
521  // }}}
522  // {{{ disconnect()
523 
533  function disconnect($force = true)
534  {
535  if (is_resource($this->connection)) {
536  if ($this->in_transaction) {
537  $dsn = $this->dsn;
539  $persistent = $this->options['persistent'];
540  $this->dsn = $this->connected_dsn;
541  $this->database_name = $this->connected_database_name;
542  $this->options['persistent'] = $this->opened_persistent;
543  $this->rollback();
544  $this->dsn = $dsn;
545  $this->database_name = $database_name;
546  $this->options['persistent'] = $persistent;
547  }
548 
549  if (!$this->opened_persistent || $force) {
550  @mysql_close($this->connection);
551  }
552  }
553  return parent::disconnect($force);
554  }
555 
556  // }}}
557  // {{{ _doQuery()
558 
568  function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
569  {
570  $this->last_query = $query;
571  $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
572  if ($result) {
573  if (PEAR::isError($result)) {
574  return $result;
575  }
576  $query = $result;
577  }
578  if ($this->options['disable_query']) {
579  $result = $is_manip ? 0 : null;
580  return $result;
581  }
582 
583  if (is_null($connection)) {
584  $connection = $this->getConnection();
585  if (PEAR::isError($connection)) {
586  return $connection;
587  }
588  }
589  if (is_null($database_name)) {
591  }
592 
593  if ($database_name) {
594  if ($database_name != $this->connected_database_name) {
595  if (!@mysql_select_db($database_name, $connection)) {
596  $err = $this->raiseError(null, null, null,
597  'Could not select the database: '.$database_name, __FUNCTION__);
598  return $err;
599  }
600  $this->connected_database_name = $database_name;
601  }
602  }
603 
604  $function = $this->options['result_buffering']
605  ? 'mysql_query' : 'mysql_unbuffered_query';
606  $result = @$function($query, $connection);
607  if (!$result) {
608  $err =& $this->raiseError(null, null, null,
609  'Could not execute statement', __FUNCTION__);
610  return $err;
611  }
612 
613  $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
614  return $result;
615  }
616 
617  // }}}
618  // {{{ _affectedRows()
619 
629  {
630  if (is_null($connection)) {
631  $connection = $this->getConnection();
632  if (PEAR::isError($connection)) {
633  return $connection;
634  }
635  }
636  return @mysql_affected_rows($connection);
637  }
638 
639  // }}}
640  // {{{ _modifyQuery()
641 
652  function _modifyQuery($query, $is_manip, $limit, $offset)
653  {
654  if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
655  // "DELETE FROM table" gives 0 affected rows in MySQL.
656  // This little hack lets you know how many rows were deleted.
657  if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
658  $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
659  'DELETE FROM \1 WHERE 1=1', $query);
660  }
661  }
662  if ($limit > 0
663  && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
664  ) {
665  $query = rtrim($query);
666  if (substr($query, -1) == ';') {
667  $query = substr($query, 0, -1);
668  }
669 
670  // LIMIT doesn't always come last in the query
671  // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
672  $after = '';
673  if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
674  $after = $matches[0];
675  $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
676  } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
677  $after = $matches[0];
678  $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
679  } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
680  $after = $matches[0];
681  $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
682  }
683 
684  if ($is_manip) {
685  return $query . " LIMIT $limit" . $after;
686  } else {
687  return $query . " LIMIT $offset, $limit" . $after;
688  }
689  }
690  return $query;
691  }
692 
693  // }}}
694  // {{{ getServerVersion()
695 
703  function getServerVersion($native = false)
704  {
705  $connection = $this->getConnection();
706  if (PEAR::isError($connection)) {
707  return $connection;
708  }
709  if ($this->connected_server_info) {
710  $server_info = $this->connected_server_info;
711  } else {
712  $server_info = @mysql_get_server_info($connection);
713  }
714  if (!$server_info) {
715  return $this->raiseError(null, null, null,
716  'Could not get server information', __FUNCTION__);
717  }
718  // cache server_info
719  $this->connected_server_info = $server_info;
720  if (!$native) {
721  $tmp = explode('.', $server_info, 3);
722  if (isset($tmp[2]) && strpos($tmp[2], '-')) {
723  $tmp2 = explode('-', @$tmp[2], 2);
724  } else {
725  $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
726  $tmp2[1] = null;
727  }
728  $server_info = array(
729  'major' => isset($tmp[0]) ? $tmp[0] : null,
730  'minor' => isset($tmp[1]) ? $tmp[1] : null,
731  'patch' => $tmp2[0],
732  'extra' => $tmp2[1],
733  'native' => $server_info,
734  );
735  }
736  return $server_info;
737  }
738 
739  // }}}
740  // {{{ _getServerCapabilities()
741 
749  {
750  static $already_checked = false;
751  if (!$already_checked) {
752  $already_checked = true;
753 
754  //set defaults
755  $this->supported['sub_selects'] = 'emulated';
756  $this->supported['prepared_statements'] = 'emulated';
757  $this->start_transaction = false;
758  $this->varchar_max_length = 255;
759 
760  $server_info = $this->getServerVersion();
761  if (is_array($server_info)) {
762  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) {
763  $this->supported['sub_selects'] = true;
764  $this->supported['prepared_statements'] = true;
765  }
766 
767  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<')
768  || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<')
769  ) {
770  $this->supported['savepoints'] = true;
771  }
772 
773  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) {
774  $this->start_transaction = true;
775  }
776 
777  if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
778  $this->varchar_max_length = 65532;
779  }
780  }
781  }
782  }
783 
784  // }}}
785  // {{{ function _skipUserDefinedVariable($query, $position)
786 
799  function _skipUserDefinedVariable($query, $position)
800  {
801  $found = strpos(strrev(substr($query, 0, $position)), '@');
802  if ($found === false) {
803  return $position;
804  }
805  $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
806  $substring = substr($query, $pos, $position - $pos + 2);
807  if (preg_match('/^@\w+:=$/', $substring)) {
808  return $position + 1; //found an user defined variable: skip it
809  }
810  return $position;
811  }
812 
813  // }}}
814  // {{{ prepare()
815 
836  function &prepare($query, $types = null, $result_types = null, $lobs = array())
837  {
838  if ($this->options['emulate_prepared']
839  || $this->supported['prepared_statements'] !== true
840  ) {
841  $obj =& parent::prepare($query, $types, $result_types, $lobs);
842  return $obj;
843  }
844  $is_manip = ($result_types === MDB2_PREPARE_MANIP);
847  $this->offset = $this->limit = 0;
848  $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
849  $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
850  if ($result) {
851  if (PEAR::isError($result)) {
852  return $result;
853  }
854  $query = $result;
855  }
856  $placeholder_type_guess = $placeholder_type = null;
857  $question = '?';
858  $colon = ':';
859  $positions = array();
860  $position = 0;
861  while ($position < strlen($query)) {
862  $q_position = strpos($query, $question, $position);
863  $c_position = strpos($query, $colon, $position);
864  if ($q_position && $c_position) {
865  $p_position = min($q_position, $c_position);
866  } elseif ($q_position) {
867  $p_position = $q_position;
868  } elseif ($c_position) {
869  $p_position = $c_position;
870  } else {
871  break;
872  }
873  if (is_null($placeholder_type)) {
874  $placeholder_type_guess = $query[$p_position];
875  }
876 
877  $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
878  if (PEAR::isError($new_pos)) {
879  return $new_pos;
880  }
881  if ($new_pos != $position) {
882  $position = $new_pos;
883  continue; //evaluate again starting from the new position
884  }
885 
886  if ($query[$position] == $placeholder_type_guess) {
887  if (is_null($placeholder_type)) {
888  $placeholder_type = $query[$p_position];
889  $question = $colon = $placeholder_type;
890  }
891  if ($placeholder_type == ':') {
892  //make sure this is not part of an user defined variable
893  $new_pos = $this->_skipUserDefinedVariable($query, $position);
894  if ($new_pos != $position) {
895  $position = $new_pos;
896  continue; //evaluate again starting from the new position
897  }
898  $parameter = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query);
899  if ($parameter === '') {
900  $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
901  'named parameter with an empty name', __FUNCTION__);
902  return $err;
903  }
904  $positions[$p_position] = $parameter;
905  $query = substr_replace($query, '?', $position, strlen($parameter)+1);
906  } else {
907  $positions[$p_position] = count($positions);
908  }
909  $position = $p_position + 1;
910  } else {
911  $position = $p_position;
912  }
913  }
914  $connection = $this->getConnection();
915  if (PEAR::isError($connection)) {
916  return $connection;
917  }
918  $statement_name = sprintf($this->options['statement_format'], $this->phptype, md5(time() + rand()));
919  $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
920  $statement =& $this->_doQuery($query, true, $connection);
921  if (PEAR::isError($statement)) {
922  return $statement;
923  }
924 
925  $class_name = 'MDB2_Statement_'.$this->phptype;
926  $obj =& new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
927  $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
928  return $obj;
929  }
930 
931  // }}}
932  // {{{ replace()
933 
998  function replace($table, $fields)
999  {
1000  $count = count($fields);
1001  $query = $values = '';
1002  $keys = $colnum = 0;
1003  for (reset($fields); $colnum < $count; next($fields), $colnum++) {
1004  $name = key($fields);
1005  if ($colnum > 0) {
1006  $query .= ',';
1007  $values.= ',';
1008  }
1009  $query.= $name;
1010  if (isset($fields[$name]['null']) && $fields[$name]['null']) {
1011  $value = 'NULL';
1012  } else {
1013  $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
1014  $value = $this->quote($fields[$name]['value'], $type);
1015  }
1016  $values.= $value;
1017  if (isset($fields[$name]['key']) && $fields[$name]['key']) {
1018  if ($value === 'NULL') {
1019  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1020  'key value '.$name.' may not be NULL', __FUNCTION__);
1021  }
1022  $keys++;
1023  }
1024  }
1025  if ($keys == 0) {
1026  return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1027  'not specified which fields are keys', __FUNCTION__);
1028  }
1029 
1030  $connection = $this->getConnection();
1031  if (PEAR::isError($connection)) {
1032  return $connection;
1033  }
1034 
1035  $query = "REPLACE INTO $table ($query) VALUES ($values)";
1036  $result =& $this->_doQuery($query, true, $connection);
1037  if (PEAR::isError($result)) {
1038  return $result;
1039  }
1040  return $this->_affectedRows($connection, $result);
1041  }
1042 
1043  // }}}
1044  // {{{ nextID()
1045 
1057  function nextID($seq_name, $ondemand = true)
1058  {
1059  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1060  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1061  $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
1063  $result =& $this->_doQuery($query, true);
1064  $this->popExpect();
1065  if (PEAR::isError($result)) {
1066  if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
1067  $this->loadModule('Manager', null, true);
1068  $result = $this->manager->createSequence($seq_name);
1069  if (PEAR::isError($result)) {
1070  return $this->raiseError($result, null, null,
1071  'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
1072  } else {
1073  return $this->nextID($seq_name, false);
1074  }
1075  }
1076  return $result;
1077  }
1078  $value = $this->lastInsertID();
1079  if (is_numeric($value)) {
1080  $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
1081  $result =& $this->_doQuery($query, true);
1082  if (PEAR::isError($result)) {
1083  $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
1084  }
1085  }
1086  return $value;
1087  }
1088 
1089  // }}}
1090  // {{{ lastInsertID()
1091 
1101  function lastInsertID($table = null, $field = null)
1102  {
1103  // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
1104  return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer');
1105  }
1106 
1107  // }}}
1108  // {{{ currID()
1109 
1117  function currID($seq_name)
1118  {
1119  $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1120  $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1121  $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
1122  return $this->queryOne($query, 'integer');
1123  }
1124 }
1125 
1134 {
1135  // }}}
1136  // {{{ fetchRow()
1137 
1146  function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
1147  {
1148  if (!is_null($rownum)) {
1149  $seek = $this->seek($rownum);
1150  if (PEAR::isError($seek)) {
1151  return $seek;
1152  }
1153  }
1154  if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
1155  $fetchmode = $this->db->fetchmode;
1156  }
1157  if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
1158  $row = @mysql_fetch_assoc($this->result);
1159  if (is_array($row)
1160  && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
1161  ) {
1162  $row = array_change_key_case($row, $this->db->options['field_case']);
1163  }
1164  } else {
1165  $row = @mysql_fetch_row($this->result);
1166  }
1167 
1168  if (!$row) {
1169  if ($this->result === false) {
1170  $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1171  'resultset has already been freed', __FUNCTION__);
1172  return $err;
1173  }
1174  $null = null;
1175  return $null;
1176  }
1177  $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
1178  if ($mode) {
1179  $this->db->_fixResultArrayValues($row, $mode);
1180  }
1181  if (!empty($this->types)) {
1182  $row = $this->db->datatype->convertResultRow($this->types, $row, false);
1183  }
1184  if (!empty($this->values)) {
1185  $this->_assignBindColumns($row);
1186  }
1187  if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
1188  $object_class = $this->db->options['fetch_class'];
1189  if ($object_class == 'stdClass') {
1190  $row = (object) $row;
1191  } else {
1192  $row = &new $object_class($row);
1193  }
1194  }
1195  ++$this->rownum;
1196  return $row;
1197  }
1198 
1199  // }}}
1200  // {{{ _getColumnNames()
1201 
1211  function _getColumnNames()
1212  {
1213  $columns = array();
1214  $numcols = $this->numCols();
1215  if (PEAR::isError($numcols)) {
1216  return $numcols;
1217  }
1218  for ($column = 0; $column < $numcols; $column++) {
1219  $column_name = @mysql_field_name($this->result, $column);
1220  $columns[$column_name] = $column;
1221  }
1222  if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1223  $columns = array_change_key_case($columns, $this->db->options['field_case']);
1224  }
1225  return $columns;
1226  }
1227 
1228  // }}}
1229  // {{{ numCols()
1230 
1238  function numCols()
1239  {
1240  $cols = @mysql_num_fields($this->result);
1241  if (is_null($cols)) {
1242  if ($this->result === false) {
1243  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1244  'resultset has already been freed', __FUNCTION__);
1245  } elseif (is_null($this->result)) {
1246  return count($this->types);
1247  }
1248  return $this->db->raiseError(null, null, null,
1249  'Could not get column count', __FUNCTION__);
1250  }
1251  return $cols;
1252  }
1253 
1254  // }}}
1255  // {{{ free()
1256 
1263  function free()
1264  {
1265  if (is_resource($this->result) && $this->db->connection) {
1266  $free = @mysql_free_result($this->result);
1267  if ($free === false) {
1268  return $this->db->raiseError(null, null, null,
1269  'Could not free result', __FUNCTION__);
1270  }
1271  }
1272  $this->result = false;
1273  return MDB2_OK;
1274  }
1275 }
1276 
1285 {
1286  // }}}
1287  // {{{ seek()
1288 
1296  function seek($rownum = 0)
1297  {
1298  if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) {
1299  if ($this->result === false) {
1300  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1301  'resultset has already been freed', __FUNCTION__);
1302  } elseif (is_null($this->result)) {
1303  return MDB2_OK;
1304  }
1305  return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1306  'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1307  }
1308  $this->rownum = $rownum - 1;
1309  return MDB2_OK;
1310  }
1311 
1312  // }}}
1313  // {{{ valid()
1314 
1321  function valid()
1322  {
1323  $numrows = $this->numRows();
1324  if (PEAR::isError($numrows)) {
1325  return $numrows;
1326  }
1327  return $this->rownum < ($numrows - 1);
1328  }
1329 
1330  // }}}
1331  // {{{ numRows()
1332 
1339  function numRows()
1340  {
1341  $rows = @mysql_num_rows($this->result);
1342  if (is_null($rows)) {
1343  if ($this->result === false) {
1344  return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1345  'resultset has already been freed', __FUNCTION__);
1346  } elseif (is_null($this->result)) {
1347  return 0;
1348  }
1349  return $this->db->raiseError(null, null, null,
1350  'Could not get row count', __FUNCTION__);
1351  }
1352  return $rows;
1353  }
1354 }
1355 
1364 {
1365  // {{{ _execute()
1366 
1375  function &_execute($result_class = true, $result_wrap_class = false)
1376  {
1377  if (is_null($this->statement)) {
1378  $result =& parent::_execute($result_class, $result_wrap_class);
1379  return $result;
1380  }
1381  $this->db->last_query = $this->query;
1382  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
1383  if ($this->db->getOption('disable_query')) {
1384  $result = $this->is_manip ? 0 : null;
1385  return $result;
1386  }
1387 
1388  $connection = $this->db->getConnection();
1389  if (PEAR::isError($connection)) {
1390  return $connection;
1391  }
1392 
1393  $query = 'EXECUTE '.$this->statement;
1394  if (!empty($this->positions)) {
1395  $parameters = array();
1396  foreach ($this->positions as $parameter) {
1397  if (!array_key_exists($parameter, $this->values)) {
1398  return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
1399  'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
1400  }
1401  $value = $this->values[$parameter];
1402  $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1403  if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1404  if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1405  if ($match[1] == 'file://') {
1406  $value = $match[2];
1407  }
1408  $value = @fopen($value, 'r');
1409  $close = true;
1410  }
1411  if (is_resource($value)) {
1412  $data = '';
1413  while (!@feof($value)) {
1414  $data.= @fread($value, $this->db->options['lob_buffer_length']);
1415  }
1416  if ($close) {
1417  @fclose($value);
1418  }
1419  $value = $data;
1420  }
1421  }
1422  $quoted = $this->db->quote($value, $type);
1423  if (PEAR::isError($quoted)) {
1424  return $quoted;
1425  }
1426  $param_query = 'SET @'.$parameter.' = '.$quoted;
1427  $result = $this->db->_doQuery($param_query, true, $connection);
1428  if (PEAR::isError($result)) {
1429  return $result;
1430  }
1431  }
1432  $query.= ' USING @'.implode(', @', array_values($this->positions));
1433  }
1434 
1435  $result = $this->db->_doQuery($query, $this->is_manip, $connection);
1436  if (PEAR::isError($result)) {
1437  return $result;
1438  }
1439 
1440  if ($this->is_manip) {
1441  $affected_rows = $this->db->_affectedRows($connection, $result);
1442  return $affected_rows;
1443  }
1444 
1445  $result =& $this->db->_wrapResult($result, $this->result_types,
1446  $result_class, $result_wrap_class, $this->limit, $this->offset);
1447  $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
1448  return $result;
1449  }
1450 
1451  // }}}
1452  // {{{ free()
1453 
1460  function free()
1461  {
1462  if (is_null($this->positions)) {
1463  return $this->db->raiseError(MDB2_ERROR, null, null,
1464  'Prepared statement has already been freed', __FUNCTION__);
1465  }
1466  $result = MDB2_OK;
1467 
1468  if (!is_null($this->statement)) {
1469  $connection = $this->db->getConnection();
1470  if (PEAR::isError($connection)) {
1471  return $connection;
1472  }
1473  $query = 'DEALLOCATE PREPARE '.$this->statement;
1474  $result = $this->db->_doQuery($query, true, $connection);
1475  }
1476 
1477  parent::free();
1478  return $result;
1479  }
1480 }
1481 ?>