Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
driver.php
0001 <?php
0002 /**
0003 *
0004 * This file is part of the phpBB Forum Software package.
0005 *
0006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007 * @license GNU General Public License, version 2 (GPL-2.0)
0008 *
0009 * For full copyright and license information, please see
0010 * the docs/CREDITS.txt file.
0011 *
0012 */
0013
0014 namespace phpbb\db\driver;
0015
0016 /**
0017 * Database Abstraction Layer
0018 */
0019 abstract class driver implements driver_interface
0020 {
0021 var $db_connect_id;
0022 var $query_result;
0023 var $return_on_error = false;
0024 var $transaction = false;
0025 var $sql_time = 0;
0026 var $num_queries = array();
0027 var $open_queries = array();
0028
0029 var $curtime = 0;
0030 var $query_hold = '';
0031 var $html_hold = '';
0032 var $sql_report = '';
0033
0034 var $persistency = false;
0035 var $user = '';
0036 var $server = '';
0037 var $dbname = '';
0038
0039 // Set to true if error triggered
0040 var $sql_error_triggered = false;
0041
0042 // Holding the last sql query on sql error
0043 var $sql_error_sql = '';
0044 // Holding the error information - only populated if sql_error_triggered is set
0045 var $sql_error_returned = array();
0046
0047 // Holding transaction count
0048 var $transactions = 0;
0049
0050 // Supports multi inserts?
0051 var $multi_insert = false;
0052
0053 /**
0054 * Current sql layer
0055 */
0056 var $sql_layer = '';
0057
0058 /**
0059 * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions
0060 */
0061 var $any_char;
0062 var $one_char;
0063
0064 /**
0065 * Exact version of the DBAL, directly queried
0066 */
0067 var $sql_server_version = false;
0068
0069 const LOGICAL_OP = 0;
0070 const STATEMENTS = 1;
0071 const LEFT_STMT = 0;
0072 const COMPARE_OP = 1;
0073 const RIGHT_STMT = 2;
0074 const SUBQUERY_OP = 3;
0075 const SUBQUERY_SELECT_TYPE = 4;
0076 const SUBQUERY_BUILD = 5;
0077
0078 /**
0079 * @var bool
0080 */
0081 protected $debug_load_time = false;
0082
0083 /**
0084 * @var bool
0085 */
0086 protected $debug_sql_explain = false;
0087
0088 /**
0089 * Constructor
0090 */
0091 function __construct()
0092 {
0093 $this->num_queries = array(
0094 'cached' => 0,
0095 'normal' => 0,
0096 'total' => 0,
0097 );
0098
0099 // Fill default sql layer based on the class being called.
0100 // This can be changed by the specified layer itself later if needed.
0101 $this->sql_layer = substr(get_class($this), strlen('phpbb\db\driver\\'));
0102
0103 // Do not change this please! This variable is used to easy the use of it - and is hardcoded.
0104 $this->any_char = chr(0) . '%';
0105 $this->one_char = chr(0) . '_';
0106 }
0107
0108 /**
0109 * {@inheritdoc}
0110 */
0111 public function set_debug_load_time($value)
0112 {
0113 $this->debug_load_time = $value;
0114 }
0115
0116 /**
0117 * {@inheritdoc}
0118 */
0119 public function set_debug_sql_explain($value)
0120 {
0121 $this->debug_sql_explain = $value;
0122 }
0123
0124 /**
0125 * {@inheritdoc}
0126 */
0127 public function get_sql_layer()
0128 {
0129 return $this->sql_layer;
0130 }
0131
0132 /**
0133 * {@inheritdoc}
0134 */
0135 public function get_db_name()
0136 {
0137 return $this->dbname;
0138 }
0139
0140 /**
0141 * {@inheritdoc}
0142 */
0143 public function get_any_char()
0144 {
0145 return $this->any_char;
0146 }
0147
0148 /**
0149 * {@inheritdoc}
0150 */
0151 public function get_one_char()
0152 {
0153 return $this->one_char;
0154 }
0155
0156 /**
0157 * {@inheritdoc}
0158 */
0159 public function get_db_connect_id()
0160 {
0161 return $this->db_connect_id;
0162 }
0163
0164 /**
0165 * {@inheritdoc}
0166 */
0167 public function get_sql_error_triggered()
0168 {
0169 return $this->sql_error_triggered;
0170 }
0171
0172 /**
0173 * {@inheritdoc}
0174 */
0175 public function get_sql_error_sql()
0176 {
0177 return $this->sql_error_sql;
0178 }
0179
0180 /**
0181 * {@inheritdoc}
0182 */
0183 public function get_transaction()
0184 {
0185 return $this->transaction;
0186 }
0187
0188 /**
0189 * {@inheritdoc}
0190 */
0191 public function get_sql_time()
0192 {
0193 return $this->sql_time;
0194 }
0195
0196 /**
0197 * {@inheritdoc}
0198 */
0199 public function get_sql_error_returned()
0200 {
0201 return $this->sql_error_returned;
0202 }
0203
0204 /**
0205 * {@inheritdoc}
0206 */
0207 public function get_multi_insert()
0208 {
0209 return $this->multi_insert;
0210 }
0211
0212 /**
0213 * {@inheritdoc}
0214 */
0215 public function set_multi_insert($multi_insert)
0216 {
0217 $this->multi_insert = $multi_insert;
0218 }
0219
0220 /**
0221 * {@inheritDoc}
0222 */
0223 function sql_return_on_error($fail = false)
0224 {
0225 $this->sql_error_triggered = false;
0226 $this->sql_error_sql = '';
0227
0228 $this->return_on_error = $fail;
0229 }
0230
0231 /**
0232 * {@inheritDoc}
0233 */
0234 function sql_num_queries($cached = false)
0235 {
0236 return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal'];
0237 }
0238
0239 /**
0240 * {@inheritDoc}
0241 */
0242 function sql_add_num_queries($cached = false)
0243 {
0244 $this->num_queries['cached'] += ($cached !== false) ? 1 : 0;
0245 $this->num_queries['normal'] += ($cached !== false) ? 0 : 1;
0246 $this->num_queries['total'] += 1;
0247 }
0248
0249 /**
0250 * {@inheritDoc}
0251 */
0252 function sql_close()
0253 {
0254 if (!$this->db_connect_id)
0255 {
0256 return false;
0257 }
0258
0259 if ($this->transaction)
0260 {
0261 do
0262 {
0263 $this->sql_transaction('commit');
0264 }
0265 while ($this->transaction);
0266 }
0267
0268 foreach ($this->open_queries as $query_id)
0269 {
0270 $this->sql_freeresult($query_id);
0271 }
0272
0273 // Connection closed correctly. Set db_connect_id to false to prevent errors
0274 if ($result = $this->_sql_close())
0275 {
0276 $this->db_connect_id = false;
0277 }
0278
0279 return $result;
0280 }
0281
0282 /**
0283 * {@inheritDoc}
0284 */
0285 function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
0286 {
0287 if (empty($query))
0288 {
0289 return false;
0290 }
0291
0292 // Never use a negative total or offset
0293 $total = ($total < 0) ? 0 : $total;
0294 $offset = ($offset < 0) ? 0 : $offset;
0295
0296 return $this->_sql_query_limit($query, $total, $offset, $cache_ttl);
0297 }
0298
0299 /**
0300 * {@inheritDoc}
0301 */
0302 function sql_fetchrowset($query_id = false)
0303 {
0304 if ($query_id === false)
0305 {
0306 $query_id = $this->query_result;
0307 }
0308
0309 if ($query_id)
0310 {
0311 $result = array();
0312 while ($row = $this->sql_fetchrow($query_id))
0313 {
0314 $result[] = $row;
0315 }
0316
0317 return $result;
0318 }
0319
0320 return false;
0321 }
0322
0323 /**
0324 * {@inheritDoc}
0325 */
0326 function sql_rowseek($rownum, &$query_id)
0327 {
0328 global $cache;
0329
0330 if ($query_id === false)
0331 {
0332 $query_id = $this->query_result;
0333 }
0334
0335 if ($cache && $cache->sql_exists($query_id))
0336 {
0337 return $cache->sql_rowseek($rownum, $query_id);
0338 }
0339
0340 if (!$query_id)
0341 {
0342 return false;
0343 }
0344
0345 $this->sql_freeresult($query_id);
0346 $query_id = $this->sql_query($this->last_query_text);
0347
0348 if (!$query_id)
0349 {
0350 return false;
0351 }
0352
0353 // We do not fetch the row for rownum == 0 because then the next resultset would be the second row
0354 for ($i = 0; $i < $rownum; $i++)
0355 {
0356 if (!$this->sql_fetchrow($query_id))
0357 {
0358 return false;
0359 }
0360 }
0361
0362 return true;
0363 }
0364
0365 /**
0366 * {@inheritDoc}
0367 */
0368 function sql_fetchfield($field, $rownum = false, $query_id = false)
0369 {
0370 global $cache;
0371
0372 if ($query_id === false)
0373 {
0374 $query_id = $this->query_result;
0375 }
0376
0377 if ($query_id)
0378 {
0379 if ($rownum !== false)
0380 {
0381 $this->sql_rowseek($rownum, $query_id);
0382 }
0383
0384 if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
0385 {
0386 return $cache->sql_fetchfield($query_id, $field);
0387 }
0388
0389 $row = $this->sql_fetchrow($query_id);
0390 return (isset($row[$field])) ? $row[$field] : false;
0391 }
0392
0393 return false;
0394 }
0395
0396 /**
0397 * {@inheritDoc}
0398 */
0399 function sql_like_expression($expression)
0400 {
0401 $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression);
0402 $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);
0403
0404 return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\'');
0405 }
0406
0407 /**
0408 * {@inheritDoc}
0409 */
0410 function sql_not_like_expression($expression)
0411 {
0412 $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression);
0413 $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);
0414
0415 return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\'');
0416 }
0417
0418 /**
0419 * {@inheritDoc}
0420 */
0421 public function sql_case($condition, $action_true, $action_false = false)
0422 {
0423 $sql_case = 'CASE WHEN ' . $condition;
0424 $sql_case .= ' THEN ' . $action_true;
0425 $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : '';
0426 $sql_case .= ' END';
0427 return $sql_case;
0428 }
0429
0430 /**
0431 * {@inheritDoc}
0432 */
0433 public function sql_concatenate($expr1, $expr2)
0434 {
0435 return $expr1 . ' || ' . $expr2;
0436 }
0437
0438 /**
0439 * {@inheritDoc}
0440 */
0441 function sql_buffer_nested_transactions()
0442 {
0443 return false;
0444 }
0445
0446 /**
0447 * {@inheritDoc}
0448 */
0449 function sql_transaction($status = 'begin')
0450 {
0451 switch ($status)
0452 {
0453 case 'begin':
0454 // If we are within a transaction we will not open another one, but enclose the current one to not loose data (preventing auto commit)
0455 if ($this->transaction)
0456 {
0457 $this->transactions++;
0458 return true;
0459 }
0460
0461 $result = $this->_sql_transaction('begin');
0462
0463 if (!$result)
0464 {
0465 $this->sql_error();
0466 }
0467
0468 $this->transaction = true;
0469 break;
0470
0471 case 'commit':
0472 // If there was a previously opened transaction we do not commit yet...
0473 // but count back the number of inner transactions
0474 if ($this->transaction && $this->transactions)
0475 {
0476 $this->transactions--;
0477 return true;
0478 }
0479
0480 // Check if there is a transaction (no transaction can happen if
0481 // there was an error, with a combined rollback and error returning enabled)
0482 // This implies we have transaction always set for autocommit db's
0483 if (!$this->transaction)
0484 {
0485 return false;
0486 }
0487
0488 $result = $this->_sql_transaction('commit');
0489
0490 if (!$result)
0491 {
0492 $this->sql_error();
0493 }
0494
0495 $this->transaction = false;
0496 $this->transactions = 0;
0497 break;
0498
0499 case 'rollback':
0500 $result = $this->_sql_transaction('rollback');
0501 $this->transaction = false;
0502 $this->transactions = 0;
0503 break;
0504
0505 default:
0506 $result = $this->_sql_transaction($status);
0507 break;
0508 }
0509
0510 return $result;
0511 }
0512
0513 /**
0514 * {@inheritDoc}
0515 */
0516 function sql_build_array($query, $assoc_ary = false)
0517 {
0518 if (!is_array($assoc_ary))
0519 {
0520 return false;
0521 }
0522
0523 $fields = $values = array();
0524
0525 if ($query == 'INSERT' || $query == 'INSERT_SELECT')
0526 {
0527 foreach ($assoc_ary as $key => $var)
0528 {
0529 $fields[] = $key;
0530
0531 if (is_array($var) && is_string($var[0]))
0532 {
0533 // This is used for INSERT_SELECT(s)
0534 $values[] = $var[0];
0535 }
0536 else
0537 {
0538 $values[] = $this->_sql_validate_value($var);
0539 }
0540 }
0541
0542 $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' ';
0543 }
0544 else if ($query == 'MULTI_INSERT')
0545 {
0546 trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR);
0547 }
0548 else if ($query == 'UPDATE' || $query == 'SELECT' || $query == 'DELETE')
0549 {
0550 $values = array();
0551 foreach ($assoc_ary as $key => $var)
0552 {
0553 $values[] = "$key = " . $this->_sql_validate_value($var);
0554 }
0555 $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values);
0556 }
0557
0558 return $query;
0559 }
0560
0561 /**
0562 * {@inheritDoc}
0563 */
0564 function sql_in_set($field, $array, $negate = false, $allow_empty_set = false)
0565 {
0566 $array = (array) $array;
0567
0568 if (!count($array))
0569 {
0570 if (!$allow_empty_set)
0571 {
0572 // Print the backtrace to help identifying the location of the problematic code
0573 $this->sql_error('No values specified for SQL IN comparison');
0574 }
0575 else
0576 {
0577 // NOT IN () actually means everything so use a tautology
0578 if ($negate)
0579 {
0580 return '1=1';
0581 }
0582 // IN () actually means nothing so use a contradiction
0583 else
0584 {
0585 return '1=0';
0586 }
0587 }
0588 }
0589
0590 if (count($array) == 1)
0591 {
0592 @reset($array);
0593 $var = current($array);
0594
0595 return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var);
0596 }
0597 else
0598 {
0599 return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')';
0600 }
0601 }
0602
0603 /**
0604 * {@inheritDoc}
0605 */
0606 function sql_bit_and($column_name, $bit, $compare = '')
0607 {
0608 if (method_exists($this, '_sql_bit_and'))
0609 {
0610 return $this->_sql_bit_and($column_name, $bit, $compare);
0611 }
0612
0613 return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
0614 }
0615
0616 /**
0617 * {@inheritDoc}
0618 */
0619 function sql_bit_or($column_name, $bit, $compare = '')
0620 {
0621 if (method_exists($this, '_sql_bit_or'))
0622 {
0623 return $this->_sql_bit_or($column_name, $bit, $compare);
0624 }
0625
0626 return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
0627 }
0628
0629 /**
0630 * {@inheritDoc}
0631 */
0632 function cast_expr_to_bigint($expression)
0633 {
0634 return $expression;
0635 }
0636
0637 /**
0638 * {@inheritDoc}
0639 */
0640 public function sql_nextid()
0641 {
0642 return $this->sql_last_inserted_id();
0643 }
0644
0645 /**
0646 * {@inheritDoc}
0647 */
0648 function cast_expr_to_string($expression)
0649 {
0650 return $expression;
0651 }
0652
0653 /**
0654 * {@inheritDoc}
0655 */
0656 function sql_lower_text($column_name)
0657 {
0658 return "LOWER($column_name)";
0659 }
0660
0661 /**
0662 * {@inheritDoc}
0663 */
0664 function sql_multi_insert($table, $sql_ary)
0665 {
0666 if (!count($sql_ary))
0667 {
0668 return false;
0669 }
0670
0671 if ($this->multi_insert)
0672 {
0673 $ary = array();
0674 foreach ($sql_ary as $id => $_sql_ary)
0675 {
0676 // If by accident the sql array is only one-dimensional we build a normal insert statement
0677 if (!is_array($_sql_ary))
0678 {
0679 return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary));
0680 }
0681
0682 $values = array();
0683 foreach ($_sql_ary as $key => $var)
0684 {
0685 $values[] = $this->_sql_validate_value($var);
0686 }
0687 $ary[] = '(' . implode(', ', $values) . ')';
0688 }
0689
0690 return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary));
0691 }
0692 else
0693 {
0694 foreach ($sql_ary as $ary)
0695 {
0696 if (!is_array($ary))
0697 {
0698 return false;
0699 }
0700
0701 $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary));
0702
0703 if (!$result)
0704 {
0705 return false;
0706 }
0707 }
0708 }
0709
0710 return true;
0711 }
0712
0713 /**
0714 * Function for validating values
0715 * @access private
0716 */
0717 function _sql_validate_value($var)
0718 {
0719 if (is_null($var))
0720 {
0721 return 'NULL';
0722 }
0723 else if (is_string($var))
0724 {
0725 return "'" . $this->sql_escape($var) . "'";
0726 }
0727 else
0728 {
0729 return (is_bool($var)) ? intval($var) : $var;
0730 }
0731 }
0732
0733 /**
0734 * {@inheritDoc}
0735 */
0736 function sql_build_query($query, $array)
0737 {
0738 $sql = '';
0739 switch ($query)
0740 {
0741 case 'SELECT':
0742 case 'SELECT_DISTINCT';
0743
0744 $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM ';
0745
0746 // Build table array. We also build an alias array for later checks.
0747 $table_array = $aliases = array();
0748 $used_multi_alias = false;
0749
0750 foreach ($array['FROM'] as $table_name => $alias)
0751 {
0752 if (is_array($alias))
0753 {
0754 $used_multi_alias = true;
0755
0756 foreach ($alias as $multi_alias)
0757 {
0758 $table_array[] = $table_name . ' ' . $multi_alias;
0759 $aliases[] = $multi_alias;
0760 }
0761 }
0762 else
0763 {
0764 $table_array[] = $table_name . ' ' . $alias;
0765 $aliases[] = $alias;
0766 }
0767 }
0768
0769 // We run the following code to determine if we need to re-order the table array. ;)
0770 // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison.
0771 // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is.
0772 if (!empty($array['LEFT_JOIN']) && count($array['FROM']) > 1 && $used_multi_alias !== false)
0773 {
0774 // Take first LEFT JOIN
0775 $join = current($array['LEFT_JOIN']);
0776
0777 // Determine the table used there (even if there are more than one used, we only want to have one
0778 preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches);
0779
0780 // If there is a first join match, we need to make sure the table order is correct
0781 if (!empty($matches[1]))
0782 {
0783 $first_join_match = trim($matches[1]);
0784 $table_array = $last = array();
0785
0786 foreach ($array['FROM'] as $table_name => $alias)
0787 {
0788 if (is_array($alias))
0789 {
0790 foreach ($alias as $multi_alias)
0791 {
0792 ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias;
0793 }
0794 }
0795 else
0796 {
0797 ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias;
0798 }
0799 }
0800
0801 $table_array = array_merge($table_array, $last);
0802 }
0803 }
0804
0805 $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array));
0806
0807 if (!empty($array['LEFT_JOIN']))
0808 {
0809 foreach ($array['LEFT_JOIN'] as $join)
0810 {
0811 $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')';
0812 }
0813 }
0814
0815 if (!empty($array['WHERE']))
0816 {
0817 $sql .= ' WHERE ';
0818
0819 if (is_array($array['WHERE']))
0820 {
0821 $sql_where = $this->_process_boolean_tree_first($array['WHERE']);
0822 }
0823 else
0824 {
0825 $sql_where = $array['WHERE'];
0826 }
0827
0828 $sql .= $this->_sql_custom_build('WHERE', $sql_where);
0829 }
0830
0831 if (!empty($array['GROUP_BY']))
0832 {
0833 $sql .= ' GROUP BY ' . $array['GROUP_BY'];
0834 }
0835
0836 if (!empty($array['ORDER_BY']))
0837 {
0838 $sql .= ' ORDER BY ' . $array['ORDER_BY'];
0839 }
0840
0841 break;
0842 }
0843
0844 return $sql;
0845 }
0846
0847
0848 protected function _process_boolean_tree_first($operations_ary)
0849 {
0850 // In cases where an array exists but there is no head condition,
0851 // it should be because there's only 1 WHERE clause. This seems the best way to deal with it.
0852 if ($operations_ary[self::LOGICAL_OP] !== 'AND' &&
0853 $operations_ary[self::LOGICAL_OP] !== 'OR')
0854 {
0855 $operations_ary = array('AND', array($operations_ary));
0856 }
0857 return $this->_process_boolean_tree($operations_ary) . "\n";
0858 }
0859
0860 protected function _process_boolean_tree($operations_ary)
0861 {
0862 $operation = $operations_ary[self::LOGICAL_OP];
0863
0864 foreach ($operations_ary[self::STATEMENTS] as &$condition)
0865 {
0866 switch ($condition[self::LOGICAL_OP])
0867 {
0868 case 'AND':
0869 case 'OR':
0870
0871 $condition = ' ( ' . $this->_process_boolean_tree($condition) . ') ';
0872
0873 break;
0874 case 'NOT':
0875
0876 $condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') ';
0877
0878 break;
0879
0880 default:
0881
0882 switch (count($condition))
0883 {
0884 case 3:
0885
0886 // Typical 3 element clause with {left hand} {operator} {right hand}
0887 switch ($condition[self::COMPARE_OP])
0888 {
0889 case 'IN':
0890 case 'NOT_IN':
0891
0892 // As this is used with an IN, assume it is a set of elements for sql_in_set()
0893 $condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true);
0894
0895 break;
0896
0897 case 'LIKE':
0898
0899 $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' ';
0900
0901 break;
0902
0903 case 'NOT_LIKE':
0904
0905 $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' ';
0906
0907 break;
0908
0909 case 'IS_NOT':
0910
0911 $condition[self::COMPARE_OP] = 'IS NOT';
0912
0913 // no break
0914 case 'IS':
0915
0916 // If the value is NULL, the string of it is the empty string ('') which is not the intended result.
0917 // this should solve that
0918 if ($condition[self::RIGHT_STMT] === null)
0919 {
0920 $condition[self::RIGHT_STMT] = 'NULL';
0921 }
0922
0923 $condition = implode(' ', $condition);
0924
0925 break;
0926
0927 default:
0928
0929 $condition = implode(' ', $condition);
0930
0931 break;
0932 }
0933
0934 break;
0935
0936 case 5:
0937
0938 // Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query}
0939
0940 $result = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( ';
0941 $result .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]);
0942 $result .= ' )';
0943 $condition = $result;
0944
0945 break;
0946
0947 default:
0948 // This is an unpredicted clause setup. Just join all elements.
0949 $condition = implode(' ', $condition);
0950
0951 break;
0952 }
0953
0954 break;
0955 }
0956
0957 }
0958
0959 if ($operation === 'NOT')
0960 {
0961 $operations_ary = implode("", $operations_ary[self::STATEMENTS]);
0962 }
0963 else
0964 {
0965 $operations_ary = implode(" \n $operation ", $operations_ary[self::STATEMENTS]);
0966 }
0967
0968 return $operations_ary;
0969 }
0970
0971
0972 /**
0973 * {@inheritDoc}
0974 */
0975 function sql_error($sql = '')
0976 {
0977 global $auth, $user, $config;
0978
0979 // Set var to retrieve errored status
0980 $this->sql_error_triggered = true;
0981 $this->sql_error_sql = $sql;
0982
0983 $this->sql_error_returned = $this->_sql_error();
0984
0985 if (!$this->return_on_error)
0986 {
0987 $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]<br /><br />' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']';
0988
0989 // Show complete SQL error and path to administrators only
0990 // Additionally show complete error on installation or if extended debug mode is enabled
0991 // The DEBUG constant is for development only!
0992 if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || $this->debug_sql_explain)
0993 {
0994 $message .= ($sql) ? '<br /><br />SQL<br /><br />' . htmlspecialchars($sql, ENT_COMPAT) : '';
0995 }
0996 else
0997 {
0998 // If error occurs in initiating the session we need to use a pre-defined language string
0999 // This could happen if the connection could not be established for example (then we are not able to grab the default language)
1000 if (!isset($user->lang['SQL_ERROR_OCCURRED']))
1001 {
1002 $message .= '<br /><br />An sql error occurred while fetching this page. Please contact an administrator if this problem persists.';
1003 }
1004 else
1005 {
1006 if (!empty($config['board_contact']))
1007 {
1008 $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '<a href="mailto:' . htmlspecialchars($config['board_contact'], ENT_COMPAT) . '">', '</a>');
1009 }
1010 else
1011 {
1012 $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', '');
1013 }
1014 }
1015 }
1016
1017 if ($this->transaction)
1018 {
1019 $this->sql_transaction('rollback');
1020 }
1021
1022 if (strlen($message) > 1024)
1023 {
1024 // We need to define $msg_long_text here to circumvent text stripping.
1025 global $msg_long_text;
1026 $msg_long_text = $message;
1027
1028 trigger_error(false, E_USER_ERROR);
1029 }
1030
1031 trigger_error($message, E_USER_ERROR);
1032 }
1033
1034 if ($this->transaction)
1035 {
1036 $this->sql_transaction('rollback');
1037 }
1038
1039 return $this->sql_error_returned;
1040 }
1041
1042 /**
1043 * {@inheritDoc}
1044 */
1045 function sql_report($mode, $query = '')
1046 {
1047 global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper;
1048
1049 if (!$query && $this->query_hold != '')
1050 {
1051 $query = $this->query_hold;
1052 }
1053
1054 switch ($mode)
1055 {
1056 case 'display':
1057 if (!empty($cache))
1058 {
1059 $cache->unload();
1060 }
1061 $this->sql_close();
1062
1063 $mtime = explode(' ', microtime());
1064 $totaltime = $mtime[0] + $mtime[1] - $starttime;
1065
1066 echo '<!DOCTYPE html>
1067 <html dir="ltr">
1068 <head>
1069 <meta charset="utf-8">
1070 <meta http-equiv="X-UA-Compatible" content="IE=edge">
1071 <title>SQL Report</title>
1072 <link href="' . htmlspecialchars($phpbb_path_helper->update_web_root_path($phpbb_root_path) . $phpbb_path_helper->get_adm_relative_path(), ENT_COMPAT) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" />
1073 </head>
1074 <body id="errorpage">
1075 <div id="wrap">
1076 <div id="page-header">
1077 <a href="' . build_url('explain') . '">Return to previous page</a>
1078 </div>
1079 <div id="page-body">
1080 <div id="acp">
1081 <div class="panel">
1082 <span class="corners-top"><span></span></span>
1083 <div id="content">
1084 <h1>SQL Report</h1>
1085 <br />
1086 <p><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></p>
1087
1088 <p>Time spent on ' . $this->sql_layer . ' queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></p>
1089
1090 <br /><br />
1091 ' . $this->sql_report . '
1092 </div>
1093 <span class="corners-bottom"><span></span></span>
1094 </div>
1095 </div>
1096 </div>
1097 <div id="page-footer">
1098 Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Limited
1099 </div>
1100 </div>
1101 </body>
1102 </html>';
1103
1104 exit_handler();
1105
1106 break;
1107
1108 case 'stop':
1109 $endtime = explode(' ', microtime());
1110 $endtime = $endtime[0] + $endtime[1];
1111
1112 $this->sql_report .= '
1113
1114 <table cellspacing="1">
1115 <thead>
1116 <tr>
1117 <th>Query #' . $this->num_queries['total'] . '</th>
1118 </tr>
1119 </thead>
1120 <tbody>
1121 <tr>
1122 <td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query), ENT_COMPAT)) . '</textarea></td>
1123 </tr>
1124 </tbody>
1125 </table>
1126
1127 ' . $this->html_hold . '
1128
1129 <p style="text-align: center;">
1130 ';
1131
1132 if ($this->query_result)
1133 {
1134 if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query))
1135 {
1136 $this->sql_report .= 'Affected rows: <b>' . $this->sql_affectedrows() . '</b> | ';
1137 }
1138 $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $this->curtime) . 's</b>';
1139 }
1140 else
1141 {
1142 $error = $this->sql_error();
1143 $this->sql_report .= '<b style="color: red">FAILED</b> - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message'], ENT_COMPAT);
1144 }
1145
1146 $this->sql_report .= '</p><br /><br />';
1147
1148 $this->sql_time += $endtime - $this->curtime;
1149 break;
1150
1151 case 'start':
1152 $this->query_hold = $query;
1153 $this->html_hold = '';
1154
1155 $this->_sql_report($mode, $query);
1156
1157 $this->curtime = explode(' ', microtime());
1158 $this->curtime = $this->curtime[0] + $this->curtime[1];
1159
1160 break;
1161
1162 case 'add_select_row':
1163
1164 $html_table = func_get_arg(2);
1165 $row = func_get_arg(3);
1166
1167 if (!$html_table && count($row))
1168 {
1169 $html_table = true;
1170 $this->html_hold .= '<table cellspacing="1"><tr>';
1171
1172 foreach (array_keys($row) as $val)
1173 {
1174 $this->html_hold .= '<th>' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>';
1175 }
1176 $this->html_hold .= '</tr>';
1177 }
1178 $this->html_hold .= '<tr>';
1179
1180 $class = 'row1';
1181 foreach (array_values($row) as $val)
1182 {
1183 $class = ($class == 'row1') ? 'row2' : 'row1';
1184 $this->html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>';
1185 }
1186 $this->html_hold .= '</tr>';
1187
1188 return $html_table;
1189
1190 break;
1191
1192 case 'fromcache':
1193
1194 $this->_sql_report($mode, $query);
1195
1196 break;
1197
1198 case 'record_fromcache':
1199
1200 $endtime = func_get_arg(2);
1201 $splittime = func_get_arg(3);
1202
1203 $time_cache = $endtime - $this->curtime;
1204 $time_db = $splittime - $endtime;
1205 $color = ($time_db > $time_cache) ? 'green' : 'red';
1206
1207 $this->sql_report .= '<table cellspacing="1"><thead><tr><th>Query results obtained from the cache</th></tr></thead><tbody><tr>';
1208 $this->sql_report .= '<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query), ENT_COMPAT)) . '</textarea></td></tr></tbody></table>';
1209 $this->sql_report .= '<p style="text-align: center;">';
1210 $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p><br /><br />';
1211
1212 // Pad the start time to not interfere with page timing
1213 $starttime += $time_db;
1214
1215 break;
1216
1217 default:
1218
1219 $this->_sql_report($mode, $query);
1220
1221 break;
1222 }
1223
1224 return true;
1225 }
1226
1227 /**
1228 * {@inheritDoc}
1229 */
1230 function get_estimated_row_count($table_name)
1231 {
1232 return $this->get_row_count($table_name);
1233 }
1234
1235 /**
1236 * {@inheritDoc}
1237 */
1238 function get_row_count($table_name)
1239 {
1240 $sql = 'SELECT COUNT(*) AS rows_total
1241 FROM ' . $this->sql_escape($table_name);
1242 $result = $this->sql_query($sql);
1243 $rows_total = $this->sql_fetchfield('rows_total');
1244 $this->sql_freeresult($result);
1245
1246 return $rows_total;
1247 }
1248
1249 /**
1250 * {@inheritDoc}
1251 */
1252 public function clean_query_id($query_id)
1253 {
1254 // Some DBMS functions accept/return objects and/or resources instead if identifiers
1255 // Attempting to use objects/resources as array keys will throw error, hence correctly handle all cases
1256 if (is_resource($query_id))
1257 {
1258 return function_exists('get_resource_id') ? get_resource_id($query_id) : (int) $query_id;
1259 }
1260 else
1261 {
1262 return is_object($query_id) ? spl_object_id($query_id) : $query_id;
1263 }
1264 }
1265 }
1266