40 var $row = array(); |
40 var $row = array(); |
41 var $rowset = array(); |
41 var $rowset = array(); |
42 var $errhandler; |
42 var $errhandler; |
43 var $dbms_name = 'MySQL'; |
43 var $dbms_name = 'MySQL'; |
44 |
44 |
45 function enable_errorhandler() |
45 /** |
46 { |
46 * Get a flat textual list of queries that have been made. |
47 if ( !defined('ENANO_DEBUG') ) |
47 */ |
48 return true; |
48 |
49 // echo "DBAL: enabling error handler<br />"; |
49 function sql_backtrace() |
50 if ( function_exists('debug_backtrace') ) |
50 { |
51 { |
51 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
52 $this->errhandler = set_error_handler('db_error_handler'); |
52 } |
53 } |
53 |
54 } |
54 /** |
55 |
55 * Connect to the database, but only if a connection isn't already up. |
56 function disable_errorhandler() |
56 */ |
57 { |
57 |
58 if ( !defined('ENANO_DEBUG') ) |
58 function ensure_connection() |
59 return true; |
59 { |
60 // echo "DBAL: disabling error handler<br />"; |
60 if(!$this->_conn) |
61 if ( $this->errhandler ) |
61 { |
62 { |
62 $this->connect(); |
63 set_error_handler($this->errhandler); |
63 } |
64 } |
64 } |
|
65 |
|
66 /** |
|
67 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
|
68 * @param string Description or location of error; defaults to none |
|
69 */ |
|
70 |
|
71 function _die($t = '') |
|
72 { |
|
73 if ( defined('ENANO_HEADERS_SENT') ) |
|
74 ob_clean(); |
|
75 |
|
76 $internal_text = $this->get_error($t); |
|
77 |
|
78 if ( defined('ENANO_CONFIG_FETCHED') ) |
|
79 // config is in, we can show a slightly nicer looking error page |
|
80 die_semicritical('Database error', $internal_text); |
65 else |
81 else |
66 { |
82 // no config, display using no-DB template engine |
67 restore_error_handler(); |
83 grinding_halt('Database error', $internal_text); |
68 } |
84 |
69 } |
85 exit; |
70 |
86 } |
71 function sql_backtrace() |
87 |
72 { |
88 /** |
73 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
89 * Get the internal text used for a database error message. |
74 } |
90 * @param string Description or location of error; defaults to none |
75 |
91 */ |
76 function ensure_connection() |
92 |
77 { |
93 function get_error($t = '') |
78 if(!$this->_conn) |
94 { |
79 { |
95 @header('HTTP/1.1 500 Internal Server Error'); |
80 $this->connect(); |
96 |
81 } |
97 $bt = $this->latest_query; |
82 } |
98 $e = htmlspecialchars($this->sql_error()); |
83 |
99 if ( empty($e) ) |
84 function _die($t = '') { |
100 $e = '<none>'; |
85 if ( defined('ENANO_HEADERS_SENT') ) |
101 |
86 { |
|
87 ob_clean(); |
|
88 } |
|
89 |
|
90 if ( !headers_sent() ) |
|
91 header('HTTP/1.1 500 Internal Server Error'); |
|
92 |
|
93 $bt = $this->latest_query; // $this->sql_backtrace(); |
|
94 $e = htmlspecialchars(mysql_error()); |
|
95 if($e=='') $e='<none>'; |
|
96 $t = ( !empty($t) ) ? $t : '<No error description provided>'; |
|
97 global $email; |
102 global $email; |
98 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' : ''; |
103 |
99 $internal_text = '<h3>The site was unable to finish serving your request.</h3> |
104 // As long as the admin's e-mail is accessible, display it. |
100 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p> |
105 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
101 <p>Description or location of error: '.$t.'<br /> |
106 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
102 Error returned by MySQL extension: ' . $e . '<br /> |
107 : ''; |
103 Most recent SQL query:</p> |
108 |
104 <pre>'.$bt.'</pre>'; |
109 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
105 if ( defined('IN_ENANO_INSTALL') && is_object(@$GLOBALS['ui']) ) |
110 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
106 { |
111 <p>Description or location of error: $t<br /> |
107 global $ui; |
112 Error returned by $this->dbms_name extension: $e</p> |
108 echo '<h2>Database error!</h2>'; |
113 <p>Most recent SQL query:</p> |
109 echo $internal_text; |
114 <pre>$bt</pre>"; |
110 $ui->show_footer(); |
115 return $internal_text; |
111 |
116 } |
112 exit; |
117 |
113 } |
118 /** |
114 if(defined('ENANO_CONFIG_FETCHED')) die_semicritical('Database error', $internal_text); |
119 * Exit Enano and output a JSON format datbase error. |
115 else grinding_halt('Database error', $internal_text); |
120 * @param string Description or location of error; defaults to none |
116 exit; |
121 */ |
117 } |
|
118 |
122 |
119 function die_json($loc = false) |
123 function die_json($loc = false) |
120 { |
124 { |
121 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars(mysql_error()))); |
125 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
122 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
126 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
123 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
127 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
124 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
128 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
125 $loc = str_replace("\n", "\\n", $loc); |
129 $loc = str_replace("\n", "\\n", $loc); |
126 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by MySQL: $e$loc\"}"; |
130 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
127 die($t); |
131 die($t); |
128 } |
132 } |
129 |
133 |
130 function get_error($t = '') { |
134 /** |
131 header('HTTP/1.1 500 Internal Server Error'); |
135 * Connect to the database. |
132 $bt = $this->sql_backtrace(); |
136 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
133 $e = htmlspecialchars(mysql_error()); |
137 * @param string Database server hostname |
134 if($e=='') $e='<none>'; |
138 * @param string Database server username |
135 global $email; |
139 * @param string Database server password |
136 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' : ''; |
140 * @param string Name of the database |
137 $internal_text = '<h3>The site was unable to finish serving your request.</h3> |
141 * @param int Optional port number to connect over |
138 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p> |
142 */ |
139 <p>Description or location of error: '.$t.'<br /> |
|
140 Error returned by MySQL extension: ' . $e . '<br /> |
|
141 Most recent SQL query:</p> |
|
142 <pre>'.$bt.'</pre>'; |
|
143 return $internal_text; |
|
144 } |
|
145 |
143 |
146 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
144 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
147 { |
145 { |
148 if ( !defined('ENANO_SQL_CONSTANTS') ) |
146 if ( !defined('ENANO_SQL_CONSTANTS') ) |
149 { |
147 { |
423 function insert_id() |
428 function insert_id() |
424 { |
429 { |
425 return @mysql_insert_id(); |
430 return @mysql_insert_id(); |
426 } |
431 } |
427 |
432 |
428 function fetchrow($r = false) { |
433 /** |
|
434 * Fetch one row from the given query as an associative array. |
|
435 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
436 * @return array |
|
437 */ |
|
438 |
|
439 function fetchrow($r = false) |
|
440 { |
429 if ( !$this->_conn ) |
441 if ( !$this->_conn ) |
430 return false; |
442 return false; |
431 |
443 |
432 if ( !$r ) |
444 if ( !$r ) |
433 $r = $this->latest_result; |
445 $r = $this->latest_result; |
434 |
446 |
435 if ( !$r ) |
447 if ( !$r ) |
436 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
448 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
437 |
449 |
438 $row = mysql_fetch_assoc($r); |
450 $row = mysql_fetch_assoc($r); |
439 /* |
|
440 if ( empty($row) ) |
|
441 { |
|
442 $GLOBALS['do_gzip'] = false; |
|
443 echo '<pre>' . enano_debug_print_backtrace(true) . '</pre>'; |
|
444 } |
|
445 */ |
|
446 |
451 |
447 return integerize_array($row); |
452 return integerize_array($row); |
448 } |
453 } |
449 |
454 |
450 function fetchrow_num($r = false) { |
455 /** |
451 if(!$r) $r = $this->latest_result; |
456 * Fetch one row from the given query as a numeric array. |
452 if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
457 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
458 * @return array |
|
459 */ |
|
460 |
|
461 function fetchrow_num($r = false) |
|
462 { |
|
463 if ( !$r ) |
|
464 $r = $this->latest_result; |
|
465 if ( !$r ) |
|
466 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
|
467 |
453 $row = mysql_fetch_row($r); |
468 $row = mysql_fetch_row($r); |
454 return integerize_array($row); |
469 return integerize_array($row); |
455 } |
470 } |
456 |
471 |
457 function numrows($r = false) { |
472 /** |
458 if(!$r) $r = $this->latest_result; |
473 * Get the number of results for a given query. |
459 if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
474 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
460 $n = mysql_num_rows($r); |
475 * @return array |
461 return $n; |
476 */ |
462 } |
477 |
|
478 function numrows($r = false) |
|
479 { |
|
480 if ( !$r ) |
|
481 $r = $this->latest_result; |
|
482 if ( !$r ) |
|
483 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
|
484 |
|
485 return mysql_num_rows($r); |
|
486 } |
|
487 |
|
488 /** |
|
489 * Escape a string so that it may safely be included in a SQL query. |
|
490 * @param string String to escape |
|
491 * @return string Escaped string |
|
492 */ |
463 |
493 |
464 function escape($str) |
494 function escape($str) |
465 { |
495 { |
466 $str = mysql_real_escape_string($str); |
496 $str = mysql_real_escape_string($str); |
467 return $str; |
497 return $str; |
468 } |
498 } |
469 |
499 |
|
500 /** |
|
501 * Free the given result from memory. Use this when completely finished with a result resource. |
|
502 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
503 * @return null |
|
504 */ |
|
505 |
470 function free_result($result = false) |
506 function free_result($result = false) |
471 { |
507 { |
472 if(!$result) |
508 if ( !$result ) |
473 $result = $this->latest_result; |
509 $result = $this->latest_result; |
474 if(!$result) |
510 if ( !$result ) |
475 { |
|
476 return null; |
511 return null; |
477 } |
512 |
478 @mysql_free_result($result); |
513 @mysql_free_result($result); |
479 return null; |
514 return null; |
480 } |
515 } |
481 |
516 |
482 function close() { |
517 /** |
|
518 * Close the database connection |
|
519 */ |
|
520 |
|
521 function close() |
|
522 { |
483 @mysql_close($this->_conn); |
523 @mysql_close($this->_conn); |
484 unset($this->_conn); |
524 unset($this->_conn); |
485 } |
|
486 |
|
487 // phpBB DBAL compatibility |
|
488 function sql_fetchrow($r = false) |
|
489 { |
|
490 return $this->fetchrow($r); |
|
491 } |
|
492 function sql_freeresult($r = false) |
|
493 { |
|
494 if(!$this->_conn) return false; |
|
495 if(!$r) $r = $this->latest_result; |
|
496 if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
|
497 @mysql_free_result($r); |
|
498 } |
|
499 function sql_numrows($r = false) |
|
500 { |
|
501 if(!$this->_conn) return false; |
|
502 if(!$r) $r = $this->latest_result; |
|
503 if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
|
504 return mysql_num_rows($r); |
|
505 } |
|
506 function sql_affectedrows($r = false, $f = false, $n = false) |
|
507 { |
|
508 if(!$this->_conn) return false; |
|
509 if(!$r) $r = $this->latest_result; |
|
510 if(!$r) $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
|
511 return mysql_affected_rows(); |
|
512 } |
525 } |
513 |
526 |
514 /** |
527 /** |
515 * Get a list of columns in the given table |
528 * Get a list of columns in the given table |
516 * @param string Table |
529 * @param string Table |
531 $columns[] = $row[0]; |
544 $columns[] = $row[0]; |
532 } |
545 } |
533 return $columns; |
546 return $columns; |
534 } |
547 } |
535 |
548 |
536 function sql_type_cast(&$value) |
549 /** |
537 { |
550 * Get the text of the most recent error. |
538 if ( is_float($value) ) |
551 * @return string |
539 { |
552 */ |
540 return doubleval($value); |
553 |
541 } |
554 function sql_error() |
542 if ( is_integer($value) || is_bool($value) ) |
|
543 { |
|
544 return intval($value); |
|
545 } |
|
546 if ( is_string($value) || empty($value) ) |
|
547 { |
|
548 return '\'' . $this->sql_escape_string($value) . '\''; |
|
549 } |
|
550 // uncastable var : let's do a basic protection on it to prevent sql injection attempt |
|
551 return '\'' . $this->sql_escape_string(htmlspecialchars($value)) . '\''; |
|
552 } |
|
553 |
|
554 function sql_statement(&$fields, $fields_inc='') |
|
555 { |
|
556 // init result |
|
557 $this->sql_fields = $this->sql_values = $this->sql_update = ''; |
|
558 if ( empty($fields) && empty($fields_inc) ) |
|
559 { |
|
560 return; |
|
561 } |
|
562 |
|
563 // process |
|
564 if ( !empty($fields) ) |
|
565 { |
|
566 $first = true; |
|
567 foreach ( $fields as $field => $value ) |
|
568 { |
|
569 // field must contain a field name |
|
570 if ( !empty($field) && is_string($field) ) |
|
571 { |
|
572 $value = $this->sql_type_cast($value); |
|
573 $this->sql_fields .= ( $first ? '' : ', ' ) . $field; |
|
574 $this->sql_values .= ( $first ? '' : ', ' ) . $value; |
|
575 $this->sql_update .= ( $first ? '' : ', ' ) . $field . ' = ' . $value; |
|
576 $first = false; |
|
577 } |
|
578 } |
|
579 } |
|
580 if ( !empty($fields_inc) ) |
|
581 { |
|
582 foreach ( $fields_inc as $field => $indent ) |
|
583 { |
|
584 if ( $indent != 0 ) |
|
585 { |
|
586 $this->sql_update .= (empty($this->sql_update) ? '' : ', ') . $field . ' = ' . $field . ($indent < 0 ? ' - ' : ' + ') . abs($indent); |
|
587 } |
|
588 } |
|
589 } |
|
590 } |
|
591 |
|
592 function sql_stack_reset($id='') |
|
593 { |
|
594 if ( empty($id) ) |
|
595 { |
|
596 $this->sql_stack_fields = array(); |
|
597 $this->sql_stack_values = array(); |
|
598 } |
|
599 else |
|
600 { |
|
601 $this->sql_stack_fields[$id] = array(); |
|
602 $this->sql_stack_values[$id] = array(); |
|
603 } |
|
604 } |
|
605 |
|
606 function sql_stack_statement(&$fields, $id='') |
|
607 { |
|
608 $this->sql_statement($fields); |
|
609 if ( empty($id) ) |
|
610 { |
|
611 $this->sql_stack_fields = $this->sql_fields; |
|
612 $this->sql_stack_values[] = '(' . $this->sql_values . ')'; |
|
613 } |
|
614 else |
|
615 { |
|
616 $this->sql_stack_fields[$id] = $this->sql_fields; |
|
617 $this->sql_stack_values[$id][] = '(' . $this->sql_values . ')'; |
|
618 } |
|
619 } |
|
620 |
|
621 function sql_stack_insert($table, $transaction=false, $line='', $file='', $break_on_error=true, $id='') |
|
622 { |
|
623 if ( (empty($id) && empty($this->sql_stack_values)) || (!empty($id) && empty($this->sql_stack_values[$id])) ) |
|
624 { |
|
625 return false; |
|
626 } |
|
627 switch( SQL_LAYER ) |
|
628 { |
|
629 case 'mysql': |
|
630 case 'mysql4': |
|
631 if ( empty($id) ) |
|
632 { |
|
633 $sql = 'INSERT INTO ' . $table . ' |
|
634 (' . $this->sql_stack_fields . ') VALUES ' . implode(",\n", $this->sql_stack_values); |
|
635 } |
|
636 else |
|
637 { |
|
638 $sql = 'INSERT INTO ' . $table . ' |
|
639 (' . $this->sql_stack_fields[$id] . ') VALUES ' . implode(",\n", $this->sql_stack_values[$id]); |
|
640 } |
|
641 $this->sql_stack_reset($id); |
|
642 return $this->sql_query($sql, $transaction, $line, $file, $break_on_error); |
|
643 break; |
|
644 default: |
|
645 $count_sql_stack_values = empty($id) ? count($this->sql_stack_values) : count($this->sql_stack_values[$id]); |
|
646 $result = !empty($count_sql_stack_values); |
|
647 for ( $i = 0; $i < $count_sql_stack_values; $i++ ) |
|
648 { |
|
649 if ( empty($id) ) |
|
650 { |
|
651 $sql = 'INSERT INTO ' . $table . ' |
|
652 (' . $this->sql_stack_fields . ') VALUES ' . $this->sql_stack_values[$i]; |
|
653 } |
|
654 else |
|
655 { |
|
656 $sql = 'INSERT INTO ' . $table . ' |
|
657 (' . $this->sql_stack_fields[$id] . ') VALUES ' . $this->sql_stack_values[$id][$i]; |
|
658 } |
|
659 $result &= $this->sql_query($sql, $transaction, $line, $file, $break_on_error); |
|
660 } |
|
661 $this->sql_stack_reset($id); |
|
662 return $result; |
|
663 break; |
|
664 } |
|
665 } |
|
666 |
|
667 function sql_subquery($field, $sql, $line='', $file='', $break_on_error=true, $type=TYPE_INT) |
|
668 { |
|
669 // sub-queries doable |
|
670 $this->sql_get_version(); |
|
671 if ( !in_array(SQL_LAYER, array('mysql', 'mysql4')) || (($this->sql_version[0] + ($this->sql_version[1] / 100)) >= 4.01) ) |
|
672 { |
|
673 return $sql; |
|
674 } |
|
675 |
|
676 // no sub-queries |
|
677 $ids = array(); |
|
678 $result = $this->sql_query(trim($sql), false, $line, $file, $break_on_error); |
|
679 while ( $row = $this->sql_fetchrow($result) ) |
|
680 { |
|
681 $ids[] = $type == TYPE_INT ? intval($row[$field]) : '\'' . $this->sql_escape_string($row[$field]) . '\''; |
|
682 } |
|
683 $this->sql_freeresult($result); |
|
684 return empty($ids) ? 'NULL' : implode(', ', $ids); |
|
685 } |
|
686 |
|
687 function sql_col_id($expr, $alias) |
|
688 { |
|
689 $this->sql_get_version(); |
|
690 return in_array(SQL_LAYER, array('mysql', 'mysql4')) && (($this->sql_version[0] + ($this->sql_version[1] / 100)) <= 4.01) ? $alias : $expr; |
|
691 } |
|
692 |
|
693 function sql_get_version() |
|
694 { |
|
695 if ( empty($this->sql_version) ) |
|
696 { |
|
697 $this->sql_version = array(0, 0, 0); |
|
698 switch ( SQL_LAYER ) |
|
699 { |
|
700 case 'mysql': |
|
701 case 'mysql4': |
|
702 if ( function_exists('mysql_get_server_info') ) |
|
703 { |
|
704 $lo_version = explode('-', mysql_get_server_info()); |
|
705 $this->sql_version = explode('.', $lo_version[0]); |
|
706 $this->sql_version = array(intval($this->sql_version[0]), intval($this->sql_version[1]), intval($this->sql_version[2]), $lo_version[1]); |
|
707 } |
|
708 break; |
|
709 |
|
710 case 'postgresql': |
|
711 case 'mssql': |
|
712 case 'mssql-odbc': |
|
713 default: |
|
714 break; |
|
715 } |
|
716 } |
|
717 return $this->sql_version; |
|
718 } |
|
719 |
|
720 function sql_error() |
|
721 { |
555 { |
722 return mysql_error(); |
556 return mysql_error(); |
723 } |
557 } |
724 function sql_escape_string($t) |
558 |
725 { |
|
726 return mysql_real_escape_string($t); |
|
727 } |
|
728 function sql_close() |
|
729 { |
|
730 $this->close(); |
|
731 } |
|
732 function sql_fetchrowset($query_id = 0) |
|
733 { |
|
734 if( !$query_id ) |
|
735 { |
|
736 $query_id = $this->query_result; |
|
737 } |
|
738 |
|
739 if( $query_id ) |
|
740 { |
|
741 unset($this->rowset[$query_id]); |
|
742 unset($this->row[$query_id]); |
|
743 |
|
744 while($this->rowset[$query_id] = mysql_fetch_array($query_id, MYSQL_ASSOC)) |
|
745 { |
|
746 $result[] = $this->rowset[$query_id]; |
|
747 } |
|
748 |
|
749 return $result; |
|
750 } |
|
751 else |
|
752 { |
|
753 return false; |
|
754 } |
|
755 } |
|
756 /** |
559 /** |
757 * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with. |
560 * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with. |
758 */ |
561 */ |
759 |
562 |
760 function sql_report() |
563 function sql_report() |
826 </div>'; |
629 </div>'; |
827 $template->footer(); |
630 $template->footer(); |
828 } |
631 } |
829 } |
632 } |
830 |
633 |
831 class postgresql { |
634 class postgresql |
|
635 { |
832 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
636 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
833 var $row = array(); |
637 var $row = array(); |
834 var $rowset = array(); |
638 var $rowset = array(); |
835 var $errhandler; |
639 var $errhandler; |
836 var $dbms_name = 'PostgreSQL'; |
640 var $dbms_name = 'PostgreSQL'; |
837 |
641 |
|
642 /** |
|
643 * Get a flat textual list of queries that have been made. |
|
644 */ |
|
645 |
838 function sql_backtrace() |
646 function sql_backtrace() |
839 { |
647 { |
840 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
648 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
841 } |
649 } |
842 |
650 |
|
651 /** |
|
652 * Connect to the database, but only if a connection isn't already up. |
|
653 */ |
|
654 |
843 function ensure_connection() |
655 function ensure_connection() |
844 { |
656 { |
845 if(!$this->_conn) |
657 if(!$this->_conn) |
846 { |
658 { |
847 $this->connect(); |
659 $this->connect(); |
848 } |
660 } |
849 } |
661 } |
850 |
662 |
851 function _die($t = '') { |
663 /** |
852 if(defined('ENANO_HEADERS_SENT')) { |
664 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
|
665 * @param string Description or location of error; defaults to none |
|
666 */ |
|
667 |
|
668 function _die($t = '') |
|
669 { |
|
670 if ( defined('ENANO_HEADERS_SENT') ) |
853 ob_clean(); |
671 ob_clean(); |
854 } |
672 |
855 header('HTTP/1.1 500 Internal Server Error'); |
673 $internal_text = $this->get_error($t); |
856 $bt = $this->latest_query; // $this->sql_backtrace(); |
674 |
857 $e = htmlspecialchars(pg_last_error()); |
675 if ( defined('ENANO_CONFIG_FETCHED') ) |
858 if($e=='') $e='<none>'; |
676 // config is in, we can show a slightly nicer looking error page |
859 $t = ( !empty($t) ) ? $t : '<No error description provided>'; |
677 die_semicritical('Database error', $internal_text); |
|
678 else |
|
679 // no config, display using no-DB template engine |
|
680 grinding_halt('Database error', $internal_text); |
|
681 |
|
682 exit; |
|
683 } |
|
684 |
|
685 /** |
|
686 * Get the internal text used for a database error message. |
|
687 * @param string Description or location of error; defaults to none |
|
688 */ |
|
689 |
|
690 function get_error($t = '') |
|
691 { |
|
692 @header('HTTP/1.1 500 Internal Server Error'); |
|
693 |
|
694 $bt = $this->latest_query; |
|
695 $e = htmlspecialchars($this->sql_error()); |
|
696 if ( empty($e) ) |
|
697 $e = '<none>'; |
|
698 |
860 global $email; |
699 global $email; |
861 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' : ''; |
700 |
862 $internal_text = '<h3>The site was unable to finish serving your request.</h3> |
701 // As long as the admin's e-mail is accessible, display it. |
863 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p> |
702 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
864 <p>Description or location of error: '.$t.'<br /> |
703 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
865 Error returned by PostgreSQL extension: ' . $e . '<br /> |
704 : ''; |
866 Most recent SQL query:</p> |
705 |
867 <pre>'.$bt.'</pre>'; |
706 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
868 if(defined('ENANO_CONFIG_FETCHED')) die_semicritical('Database error', $internal_text); |
707 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
869 else grinding_halt('Database error', $internal_text); |
708 <p>Description or location of error: $t<br /> |
870 exit; |
709 Error returned by $this->dbms_name extension: $e</p> |
871 } |
710 <p>Most recent SQL query:</p> |
|
711 <pre>$bt</pre>"; |
|
712 return $internal_text; |
|
713 } |
|
714 |
|
715 /** |
|
716 * Exit Enano and output a JSON format datbase error. |
|
717 * @param string Description or location of error; defaults to none |
|
718 */ |
872 |
719 |
873 function die_json($loc = false) |
720 function die_json($loc = false) |
874 { |
721 { |
875 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars(pg_last_error()))); |
722 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
876 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
723 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
877 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
724 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
878 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
725 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
879 $loc = str_replace("\n", "\\n", $loc); |
726 $loc = str_replace("\n", "\\n", $loc); |
880 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
727 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
881 die($t); |
728 die($t); |
882 } |
729 } |
883 |
730 |
884 function get_error($t = '') { |
731 /** |
885 @header('HTTP/1.1 500 Internal Server Error'); |
732 * Connect to the database. |
886 $bt = $this->sql_backtrace(); |
733 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
887 $e = htmlspecialchars(pg_last_error()); |
734 * @param string Database server hostname |
888 if($e=='') $e='<none>'; |
735 * @param string Database server username |
889 global $email; |
736 * @param string Database server password |
890 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' : ''; |
737 * @param string Name of the database |
891 $internal_text = '<h3>The site was unable to finish serving your request.</h3> |
738 * @param int Optional port number to connect over |
892 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site' . $email_info . '.</p> |
739 */ |
893 <p>Description or location of error: '.$t.'<br /> |
|
894 Error returned by ' . $this->dbms_name . ' extension: ' . $e . '<br /> |
|
895 Most recent SQL query:</p> |
|
896 <pre>'.$bt.'</pre>'; |
|
897 return $internal_text; |
|
898 } |
|
899 |
740 |
900 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
741 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
901 { |
742 { |
902 if ( !defined('ENANO_SQL_CONSTANTS') ) |
743 if ( !defined('ENANO_SQL_CONSTANTS') ) |
903 { |
744 { |
1170 } |
1017 } |
1171 } |
1018 } |
1172 return false; |
1019 return false; |
1173 } |
1020 } |
1174 |
1021 |
1175 function fetchrow($r = false) { |
1022 /** |
1176 if(!$this->_conn) return false; |
1023 * Fetch one row from the given query as an associative array. |
1177 if(!$r) $r = $this->latest_result; |
1024 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1178 if(!$r) $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1025 * @return array |
|
1026 */ |
|
1027 |
|
1028 function fetchrow($r = false) |
|
1029 { |
|
1030 if ( !$this->_conn ) |
|
1031 return false; |
|
1032 if ( !$r ) |
|
1033 $r = $this->latest_result; |
|
1034 if ( !$r ) |
|
1035 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
|
1036 |
1179 $row = pg_fetch_assoc($r); |
1037 $row = pg_fetch_assoc($r); |
1180 return integerize_array($row); |
1038 return integerize_array($row); |
1181 } |
1039 } |
1182 |
1040 |
1183 function fetchrow_num($r = false) { |
1041 /** |
1184 if(!$r) $r = $this->latest_result; |
1042 * Fetch one row from the given query as a numeric array. |
1185 if(!$r) $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1043 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
1044 * @return array |
|
1045 */ |
|
1046 |
|
1047 function fetchrow_num($r = false) |
|
1048 { |
|
1049 if ( !$r ) |
|
1050 $r = $this->latest_result; |
|
1051 if ( !$r ) |
|
1052 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
|
1053 |
1186 $row = pg_fetch_row($r); |
1054 $row = pg_fetch_row($r); |
1187 return integerize_array($row); |
1055 return integerize_array($row); |
1188 } |
1056 } |
1189 |
1057 |
1190 function numrows($r = false) { |
1058 /** |
1191 if(!$r) $r = $this->latest_result; |
1059 * Get the number of results for a given query. |
1192 if(!$r) $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1060 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
1061 * @return array |
|
1062 */ |
|
1063 |
|
1064 function numrows($r = false) |
|
1065 { |
|
1066 if ( !$r ) |
|
1067 $r = $this->latest_result; |
|
1068 if ( !$r ) |
|
1069 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
|
1070 |
1193 $n = pg_num_rows($r); |
1071 $n = pg_num_rows($r); |
1194 return $n; |
1072 return $n; |
1195 } |
1073 } |
1196 |
1074 |
|
1075 /** |
|
1076 * Escape a string so that it may safely be included in a SQL query. |
|
1077 * @param string String to escape |
|
1078 * @return string Escaped string |
|
1079 */ |
|
1080 |
1197 function escape($str) |
1081 function escape($str) |
1198 { |
1082 { |
1199 $str = pg_escape_string($this->_conn, $str); |
1083 $str = pg_escape_string($this->_conn, $str); |
1200 return $str; |
1084 return $str; |
1201 } |
1085 } |
1202 |
1086 |
|
1087 /** |
|
1088 * Free the given result from memory. Use this when completely finished with a result resource. |
|
1089 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
|
1090 * @return null |
|
1091 */ |
|
1092 |
1203 function free_result($result = false) |
1093 function free_result($result = false) |
1204 { |
1094 { |
1205 if(!$result) |
1095 if ( !$result ) |
1206 $result = $this->latest_result; |
1096 $result = $this->latest_result; |
1207 if(!$result) |
1097 |
1208 { |
1098 if ( !$result ) |
1209 return null; |
1099 return null; |
1210 } |
1100 |
1211 @pg_free_result($result); |
1101 @pg_free_result($result); |
1212 return null; |
1102 return null; |
1213 } |
1103 } |
1214 |
1104 |
1215 function close() { |
1105 /** |
|
1106 * Close the database connection |
|
1107 */ |
|
1108 |
|
1109 function close() |
|
1110 { |
1216 @pg_close($this->_conn); |
1111 @pg_close($this->_conn); |
1217 unset($this->_conn); |
1112 unset($this->_conn); |
1218 } |
|
1219 |
|
1220 // phpBB DBAL compatibility |
|
1221 function sql_fetchrow($r = false) |
|
1222 { |
|
1223 return $this->fetchrow($r); |
|
1224 } |
|
1225 function sql_freeresult($r = false) |
|
1226 { |
|
1227 if(!$this->_conn) return false; |
|
1228 if(!$r) $r = $this->latest_result; |
|
1229 if(!$r) $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
|
1230 $this->free_result($r); |
|
1231 } |
|
1232 function sql_numrows($r = false) |
|
1233 { |
|
1234 return $this->numrows(); |
|
1235 } |
|
1236 function sql_affectedrows($r = false, $f = false, $n = false) |
|
1237 { |
|
1238 if(!$this->_conn) return false; |
|
1239 if(!$r) $r = $this->latest_result; |
|
1240 if(!$r) $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
|
1241 return pg_affected_rows(); |
|
1242 } |
1113 } |
1243 |
1114 |
1244 /** |
1115 /** |
1245 * Get a list of columns in the given table |
1116 * Get a list of columns in the given table |
1246 * @param string Table |
1117 * @param string Table |
1263 $row = $this->fetchrow(); |
1134 $row = $this->fetchrow(); |
1264 $this->free_result(); |
1135 $this->free_result(); |
1265 return array_keys($row); |
1136 return array_keys($row); |
1266 } |
1137 } |
1267 |
1138 |
1268 function sql_type_cast(&$value) |
|
1269 { |
|
1270 if ( is_float($value) ) |
|
1271 { |
|
1272 return doubleval($value); |
|
1273 } |
|
1274 if ( is_integer($value) || is_bool($value) ) |
|
1275 { |
|
1276 return intval($value); |
|
1277 } |
|
1278 if ( is_string($value) || empty($value) ) |
|
1279 { |
|
1280 return '\'' . $this->sql_escape_string($value) . '\''; |
|
1281 } |
|
1282 // uncastable var : let's do a basic protection on it to prevent sql injection attempt |
|
1283 return '\'' . $this->sql_escape_string(htmlspecialchars($value)) . '\''; |
|
1284 } |
|
1285 |
|
1286 function sql_statement(&$fields, $fields_inc='') |
|
1287 { |
|
1288 // init result |
|
1289 $this->sql_fields = $this->sql_values = $this->sql_update = ''; |
|
1290 if ( empty($fields) && empty($fields_inc) ) |
|
1291 { |
|
1292 return; |
|
1293 } |
|
1294 |
|
1295 // process |
|
1296 if ( !empty($fields) ) |
|
1297 { |
|
1298 $first = true; |
|
1299 foreach ( $fields as $field => $value ) |
|
1300 { |
|
1301 // field must contain a field name |
|
1302 if ( !empty($field) && is_string($field) ) |
|
1303 { |
|
1304 $value = $this->sql_type_cast($value); |
|
1305 $this->sql_fields .= ( $first ? '' : ', ' ) . $field; |
|
1306 $this->sql_values .= ( $first ? '' : ', ' ) . $value; |
|
1307 $this->sql_update .= ( $first ? '' : ', ' ) . $field . ' = ' . $value; |
|
1308 $first = false; |
|
1309 } |
|
1310 } |
|
1311 } |
|
1312 if ( !empty($fields_inc) ) |
|
1313 { |
|
1314 foreach ( $fields_inc as $field => $indent ) |
|
1315 { |
|
1316 if ( $indent != 0 ) |
|
1317 { |
|
1318 $this->sql_update .= (empty($this->sql_update) ? '' : ', ') . $field . ' = ' . $field . ($indent < 0 ? ' - ' : ' + ') . abs($indent); |
|
1319 } |
|
1320 } |
|
1321 } |
|
1322 } |
|
1323 |
|
1324 function sql_stack_reset($id='') |
|
1325 { |
|
1326 if ( empty($id) ) |
|
1327 { |
|
1328 $this->sql_stack_fields = array(); |
|
1329 $this->sql_stack_values = array(); |
|
1330 } |
|
1331 else |
|
1332 { |
|
1333 $this->sql_stack_fields[$id] = array(); |
|
1334 $this->sql_stack_values[$id] = array(); |
|
1335 } |
|
1336 } |
|
1337 |
|
1338 function sql_stack_statement(&$fields, $id='') |
|
1339 { |
|
1340 $this->sql_statement($fields); |
|
1341 if ( empty($id) ) |
|
1342 { |
|
1343 $this->sql_stack_fields = $this->sql_fields; |
|
1344 $this->sql_stack_values[] = '(' . $this->sql_values . ')'; |
|
1345 } |
|
1346 else |
|
1347 { |
|
1348 $this->sql_stack_fields[$id] = $this->sql_fields; |
|
1349 $this->sql_stack_values[$id][] = '(' . $this->sql_values . ')'; |
|
1350 } |
|
1351 } |
|
1352 |
|
1353 function sql_stack_insert($table, $transaction=false, $line='', $file='', $break_on_error=true, $id='') |
|
1354 { |
|
1355 if ( (empty($id) && empty($this->sql_stack_values)) || (!empty($id) && empty($this->sql_stack_values[$id])) ) |
|
1356 { |
|
1357 return false; |
|
1358 } |
|
1359 switch( SQL_LAYER ) |
|
1360 { |
|
1361 case 'mysql': |
|
1362 case 'mysql4': |
|
1363 if ( empty($id) ) |
|
1364 { |
|
1365 $sql = 'INSERT INTO ' . $table . ' |
|
1366 (' . $this->sql_stack_fields . ') VALUES ' . implode(",\n", $this->sql_stack_values); |
|
1367 } |
|
1368 else |
|
1369 { |
|
1370 $sql = 'INSERT INTO ' . $table . ' |
|
1371 (' . $this->sql_stack_fields[$id] . ') VALUES ' . implode(",\n", $this->sql_stack_values[$id]); |
|
1372 } |
|
1373 $this->sql_stack_reset($id); |
|
1374 return $this->sql_query($sql, $transaction, $line, $file, $break_on_error); |
|
1375 break; |
|
1376 default: |
|
1377 $count_sql_stack_values = empty($id) ? count($this->sql_stack_values) : count($this->sql_stack_values[$id]); |
|
1378 $result = !empty($count_sql_stack_values); |
|
1379 for ( $i = 0; $i < $count_sql_stack_values; $i++ ) |
|
1380 { |
|
1381 if ( empty($id) ) |
|
1382 { |
|
1383 $sql = 'INSERT INTO ' . $table . ' |
|
1384 (' . $this->sql_stack_fields . ') VALUES ' . $this->sql_stack_values[$i]; |
|
1385 } |
|
1386 else |
|
1387 { |
|
1388 $sql = 'INSERT INTO ' . $table . ' |
|
1389 (' . $this->sql_stack_fields[$id] . ') VALUES ' . $this->sql_stack_values[$id][$i]; |
|
1390 } |
|
1391 $result &= $this->sql_query($sql, $transaction, $line, $file, $break_on_error); |
|
1392 } |
|
1393 $this->sql_stack_reset($id); |
|
1394 return $result; |
|
1395 break; |
|
1396 } |
|
1397 } |
|
1398 |
|
1399 function sql_subquery($field, $sql, $line='', $file='', $break_on_error=true, $type=TYPE_INT) |
|
1400 { |
|
1401 // sub-queries doable |
|
1402 $this->sql_get_version(); |
|
1403 if ( !in_array(SQL_LAYER, array('mysql', 'mysql4')) || (($this->sql_version[0] + ($this->sql_version[1] / 100)) >= 4.01) ) |
|
1404 { |
|
1405 return $sql; |
|
1406 } |
|
1407 |
|
1408 // no sub-queries |
|
1409 $ids = array(); |
|
1410 $result = $this->sql_query(trim($sql), false, $line, $file, $break_on_error); |
|
1411 while ( $row = $this->sql_fetchrow($result) ) |
|
1412 { |
|
1413 $ids[] = $type == TYPE_INT ? intval($row[$field]) : '\'' . $this->sql_escape_string($row[$field]) . '\''; |
|
1414 } |
|
1415 $this->sql_freeresult($result); |
|
1416 return empty($ids) ? 'NULL' : implode(', ', $ids); |
|
1417 } |
|
1418 |
|
1419 function sql_col_id($expr, $alias) |
|
1420 { |
|
1421 $this->sql_get_version(); |
|
1422 return in_array(SQL_LAYER, array('mysql', 'mysql4')) && (($this->sql_version[0] + ($this->sql_version[1] / 100)) <= 4.01) ? $alias : $expr; |
|
1423 } |
|
1424 |
|
1425 function sql_get_version() |
|
1426 { |
|
1427 if ( empty($this->sql_version) ) |
|
1428 { |
|
1429 $this->sql_version = array(0, 0, 0); |
|
1430 switch ( SQL_LAYER ) |
|
1431 { |
|
1432 case 'mysql': |
|
1433 case 'mysql4': |
|
1434 if ( function_exists('mysql_get_server_info') ) |
|
1435 { |
|
1436 $lo_version = explode('-', mysql_get_server_info()); |
|
1437 $this->sql_version = explode('.', $lo_version[0]); |
|
1438 $this->sql_version = array(intval($this->sql_version[0]), intval($this->sql_version[1]), intval($this->sql_version[2]), $lo_version[1]); |
|
1439 } |
|
1440 break; |
|
1441 |
|
1442 case 'postgresql': |
|
1443 case 'mssql': |
|
1444 case 'mssql-odbc': |
|
1445 default: |
|
1446 break; |
|
1447 } |
|
1448 } |
|
1449 return $this->sql_version; |
|
1450 } |
|
1451 |
|
1452 function sql_error() |
1139 function sql_error() |
1453 { |
1140 { |
1454 if ( $this->_conn ) |
1141 if ( $this->_conn ) |
1455 { |
1142 { |
1456 return pg_last_error(); |
1143 return pg_last_error(); |