diff options
Diffstat (limited to 'include/dba')
-rwxr-xr-x | include/dba/dba_driver.php | 286 | ||||
-rw-r--r-- | include/dba/dba_postgres.php | 112 |
2 files changed, 356 insertions, 42 deletions
diff --git a/include/dba/dba_driver.php b/include/dba/dba_driver.php index 3e19b7aa4..ddff80d7c 100755 --- a/include/dba/dba_driver.php +++ b/include/dba/dba_driver.php @@ -1,42 +1,124 @@ -<?php /** @file */ +<?php +/** + * @file dba_driver.php + * @brief some database related functions and abstract driver class. + * + * This file contains the abstract database driver class dba_driver and some + * functions for working with databases. + */ -function dba_factory($server, $port,$user,$pass,$db,$install = false) { +/** + * @brief Returns the database driver object. + * + * If available it will use PHP's mysqli otherwise mysql driver. + * + * @param string $server DB server name + * @param string $port DB port + * @param string $user DB username + * @param string $pass DB password + * @param string $db database name + * @param string $dbtype 0 for mysql, 1 for postgres + * @param bool $install Defaults to false + * @return null|dba_driver A database driver object (dba_mysql|dba_mysqli) or null if no driver found. + */ +function dba_factory($server, $port,$user,$pass,$db,$dbtype,$install = false) { $dba = null; - if(class_exists('mysqli')) { - if (is_null($port)) $port = ini_get("mysqli.default_port"); - require_once('include/dba/dba_mysqli.php'); - $dba = new dba_mysqli($server, $port,$user,$pass,$db,$install); - } - else { - if (is_null($port)) $port = "3306"; - require_once('include/dba/dba_mysql.php'); - $dba = new dba_mysql($server, $port,$user,$pass,$db,$install); + if($dbtype == 1) { + require_once('include/dba/dba_postgres.php'); + if(is_null($port)) $port = 5432; + $dba = new dba_postgres($server, $port, $user, $pass, $db, $install); + } else { + if(class_exists('mysqli')) { + if (is_null($port)) $port = ini_get("mysqli.default_port"); + require_once('include/dba/dba_mysqli.php'); + $dba = new dba_mysqli($server, $port,$user,$pass,$db,$install); + } else { + if (is_null($port)) $port = "3306"; + require_once('include/dba/dba_mysql.php'); + $dba = new dba_mysql($server, $port,$user,$pass,$db,$install); + } } - + define('NULL_DATE', $dba->get_null_date()); + define('ACTIVE_DBTYPE', $dbtype); return $dba; } - +/** + * @brief abstract database driver class. + * + * This class gets extended by the real database driver classes, e.g. dba_mysql, + * dba_mysqli. + */ abstract class dba_driver { - + // legacy behavior + const INSTALL_SCRIPT='install/schema_mysql.sql'; + const NULL_DATE = '0000-00-00 00:00:00'; + const UTC_NOW = 'UTC_TIMESTAMP()'; + protected $debug = 0; protected $db; public $connected = false; public $error = false; - abstract function connect($server, $port, $user,$pass,$db); + /** + * @brief Connect to the database. + * + * This abstract function needs to be implemented in the real driver. + * + * @param string $server DB server name + * @param string $port DB port + * @param string $user DB username + * @param string $pass DB password + * @param string $db database name + * @return bool + */ + abstract function connect($server, $port, $user, $pass, $db); + + /** + * @brief Perform a DB query with the SQL statement $sql. + * + * This abstract function needs to be implemented in the real driver. + * + * @param string $sql The SQL query to execute + */ abstract function q($sql); + + /** + * @brief Escape a string before being passed to a DB query. + * + * This abstract function needs to be implemented in the real driver. + * + * @param string $str The string to escape. + */ abstract function escape($str); + + /** + * @brief Close the database connection. + * + * This abstract function needs to be implemented in the real driver. + */ abstract function close(); + function __construct($server, $port, $user,$pass,$db,$install = false) { - if(($install) && (! $this->install($server, $port, $user,$pass,$db))) { + if(($install) && (! $this->install($server, $port, $user, $pass, $db))) { return; } - $this->connect($server, $port, $user,$pass,$db); + $this->connect($server, $port, $user, $pass, $db); } + function get_null_date() { + return static::NULL_DATE; + } + + function get_install_script() { + return static::INSTALL_SCRIPT; + } + + function utcnow() { + return static::UTC_NOW; + } function install($server,$user,$pass,$db) { if (!(strlen($server) && strlen($user))){ @@ -56,7 +138,11 @@ abstract class dba_driver { return true; } - + /** + * @brief Sets the database driver's debugging state. + * + * @param int $dbg 0 to disable debugging + */ function dbg($dbg) { $this->debug = $dbg; } @@ -67,10 +153,31 @@ abstract class dba_driver { } } -} + function quote_interval($txt) { + return $txt; + } + + function optimize_table($table) { + q('OPTIMIZE TABLE '.$table); + } + + function concat($fld, $sep) { + return 'GROUP_CONCAT(DISTINCT '.$fld.' SEPARATOR \''.$sep.'\')'; + } + + function escapebin($str) { + return $this->escape($str); + } + + function unescapebin($str) { + return $str; + } +} // end abstract dba_driver class +// Procedural functions + function printable($s) { $s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s); $s = str_replace("\x00",'.',$s); @@ -79,24 +186,79 @@ function printable($s) { return $s; } -// Procedural functions - +/** + * @brief set database driver debugging state. + * + * @param int $state 0 to disable debugging + */ function dbg($state) { global $db; + if($db) - $db->dbg($state); + $db->dbg($state); } - +/** + * @brief Escape strings being passed to DB queries. + * + * Always escape strings being used in DB queries. This function returns the + * escaped string. Integer DB parameters should all be proven integers by + * wrapping with intval(). + * + * @param string $str A string to pass to a DB query + * @return Return an escaped string of the value to pass to a DB query. + */ function dbesc($str) { global $db; + if($db && $db->connected) return($db->escape($str)); else - return(str_replace("'","\\'",$str)); + return(str_replace("'", "\\'", $str)); +} +function dbescbin($str) { + global $db; + return $db->escapebin($str); +} + +function dbunescbin($str) { + global $db; + return $db->unescapebin($str); } +function dbescdate($date) { + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES && $date == '0000-00-00 00:00:00') { + $date = NULL_DATE; + } else if(ACTIVE_DBTYPE != DBTYPE_POSTGRES && $date == '0001-01-01 00:00:00') { + $date = NULL_DATE; + } + return $date; +} + +function db_quoteinterval($txt) { + global $db; + return $db->quote_interval($txt); +} + +function dbesc_identifier($str) { + global $db; + return $db->escape_identifier($txt); +} + +function db_utcnow() { + global $db; + return $db->utcnow(); +} + +function db_optimizetable($table) { + global $db; + $db->optimize_table($table); +} +function db_concat($fld, $sep) { + global $db; + return $db->concat($fld, $sep); +} // Function: q($sql,$args); // Description: execute SQL query with printf style args. @@ -104,68 +266,108 @@ function dbesc($str) { // 'user', 1); +/** + * @brief Execute a SQL query with printf style args. + * + * printf style arguments %s and %d are replaced with variable arguments, which + * should each be appropriately dbesc() or intval(). + * SELECT queries return an array of results or false if SQL or DB error. Other + * queries return true if the command was successful or false if it wasn't. + * + * Example: + * $r = q("SELECT * FROM `%s` WHERE `uid` = %d", + * 'user', 1); + * + * @param string $sql The SQL query to execute + * @return bool|array + */ function q($sql) { - global $db; + $args = func_get_args(); unset($args[0]); if($db && $db->connected) { - $stmt = vsprintf($sql,$args); + $stmt = vsprintf($sql, $args); if($stmt === false) { - if(version_compare(PHP_VERSION,'5.4.0') >= 0) - logger('dba: vsprintf error: ' . print_r(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT,1),true)); + if(version_compare(PHP_VERSION, '5.4.0') >= 0) + logger('dba: vsprintf error: ' . + print_r(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1), true)); else - logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true)); + logger('dba: vsprintf error: ' . print_r(debug_backtrace(), true)); } return $db->q($stmt); } - /** - * + /* * This will happen occasionally trying to store the * session data after abnormal program termination - * */ logger('dba: no database: ' . print_r($args,true)); - return false; + return false; } /** + * @brief Raw DB query, no arguments. * - * Raw db query, no arguments + * This function executes a raw DB query without any arguments. * + * @param string $sql The SQL query to execute */ - - function dbq($sql) { - global $db; + if($db && $db->connected) $ret = $db->q($sql); else $ret = false; + return $ret; } -// Caller is responsible for ensuring that any integer arguments to + +// Caller is responsible for ensuring that any integer arguments to // dbesc_array are actually integers and not malformed strings containing // SQL injection vectors. All integer array elements should be specifically // cast to int to avoid trouble. - - function dbesc_array_cb(&$item, $key) { - if(is_string($item)) + if(is_string($item)) { + if($item == '0000-00-00 00:00:00' && ACTIVE_DBTYPE == DBTYPE_POSTGRES) + $item = '0001-01-01 00:00:00'; $item = dbesc($item); + } } - function dbesc_array(&$arr) { if(is_array($arr) && count($arr)) { array_walk($arr,'dbesc_array_cb'); } } + +function db_getfunc($f) { + $lookup = array( + 'rand'=>array( + DBTYPE_MYSQL=>'RAND()', + DBTYPE_POSTGRES=>'RANDOM()' + ), + 'utc_timestamp'=>array( + DBTYPE_MYSQL=>'UTC_TIMESTAMP()', + DBTYPE_POSTGRES=>"now() at time zone 'UTC'" + ), + 'regexp'=>array( + DBTYPE_MYSQL=>'REGEXP', + DBTYPE_POSTGRES=>'~' + ) + ); + $f = strtolower($f); + if(isset($lookup[$f]) && isset($lookup[$f][ACTIVE_DBTYPE])) + return $lookup[$f][ACTIVE_DBTYPE]; + + logger('Unable to abstract DB function "'. $f . '"', LOG_DEBUG); + return $f; +} + diff --git a/include/dba/dba_postgres.php b/include/dba/dba_postgres.php new file mode 100644 index 000000000..a390292a5 --- /dev/null +++ b/include/dba/dba_postgres.php @@ -0,0 +1,112 @@ +<?php + +require_once('include/dba/dba_driver.php'); + + +class dba_postgres extends dba_driver { + const INSTALL_SCRIPT='install/schema_postgres.sql'; + const NULL_DATE = '0001-01-01 00:00:00'; + const UTC_NOW = "now() at time zone 'UTC'"; + + function connect($server,$port,$user,$pass,$db) { + if(!$port) $port = 5432; + $connstr = 'host=' . $server . ' port='.$port . ' user=' . $user . ' password=' . $pass . ' dbname='. $db; + $this->db = pg_connect($connstr); + if($this->db !== false) { + $this->connected = true; + } else { + $this->connected = false; + } + $this->q("SET standard_conforming_strings = 'off'; SET backslash_quote = 'on';"); // emulate mysql string escaping to prevent massive code-clobber + return $this->connected; + } + + function q($sql) { + if((! $this->db) || (! $this->connected)) + return false; + + if(!strpos($sql, ';')) + $sql .= ';'; + + if(strpos($sql, '`')) // this is a hack. quoted identifiers should be replaced everywhere in the code with dbesc_identifier(), remove this once it is + $sql = str_replace('`', '"', $sql); + + $this->error = ''; + $result = @pg_query($this->db, $sql); + if(file_exists('db-allqueries.out')) { + $bt = debug_backtrace(); + $trace = array(); + foreach($bt as $frame) { + if(!empty($frame['file']) && @strstr($frame['file'], $_SERVER['DOCUMENT_ROOT'])) + $frame['file'] = substr($frame['file'], strlen($_SERVER['DOCUMENT_ROOT'])+1); + + $trace[] = $frame['file'] . ':' . $frame['function'] . '():' . $frame['line'] ; + } + $compact = join(', ', $trace); + file_put_contents('db-allqueries.out', datetime_convert() . ": " . $sql . ' is_resource: '.var_export(is_resource($result), true).', backtrace: '.$compact."\n\n", FILE_APPEND); + } + + if($result === false) + $this->error = pg_last_error($this->db); + + if($result === false || $this->error) { + //logger('dba_postgres: ' . printable($sql) . ' returned false.' . "\n" . $this->error); + if(file_exists('dbfail.out')) + file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND); + } + + if(($result === true) || ($result === false)) + return $result; + + if(pg_result_status($result) == PGSQL_COMMAND_OK) + return true; + + $r = array(); + if(pg_num_rows($result)) { + while($x = pg_fetch_array($result, null, PGSQL_ASSOC)) + $r[] = $x; + pg_free_result($result); + if($this->debug) + logger('dba_postgres: ' . printable(print_r($r,true))); + } + return $r; + } + + function escape($str) { + if($this->db && $this->connected) { + $x = @pg_escape_string($this->db, $str); + return $x; + } + } + + function escapebin($str) { + return pg_escape_bytea($str); + } + + function unescapebin($str) { + return pg_unescape_bytea($str); + } + + function close() { + if($this->db) + pg_close($this->db); + $this->connected = false; + } + + function quote_interval($txt) { + return "'$txt'"; + } + + function escape_identifier($str) { + return pg_escape_identifier($this->db, $str); + } + + function optimize_table($table) { + // perhaps do some equivalent thing here, vacuum, etc? I think this is the DBA's domain anyway. Applications should not need to muss with this. + // for now do nothing without a compelling reason. function overrides default legacy mysql. + } + + function concat($fld, $sep) { + return 'string_agg(' . $fld . ',\'' . $sep . '\')'; + } +}
\ No newline at end of file |