mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-08-30 18:37:12 +00:00
These AwlQuery classes are ow thoroughly migrated into AWL.
This commit is contained in:
parent
f3a6cb59f6
commit
737aa5e228
@ -1,361 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* AwlDatabase - support for different SQL dialects
|
||||
*
|
||||
* This subpackage provides dialect specific support for PostgreSQL, and
|
||||
* may, over time, be extended to provide support for other SQL dialects.
|
||||
*
|
||||
* See http://wiki.davical.org/w/Coding/AwlQuery for design and usage information.
|
||||
*
|
||||
* @package awl
|
||||
* @subpackage AwlDatabase
|
||||
* @author Andrew McMillan <andrew@morphoss.com>
|
||||
* @copyright Morphoss Ltd
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @compatibility Requires PHP 5.1 or later
|
||||
*/
|
||||
|
||||
if ( !defined('E_USER_ERROR') ) define('E_USER_ERROR',256);
|
||||
|
||||
/**
|
||||
* The AwlDBDialect class handles support for different SQL dialects
|
||||
*
|
||||
* This subpackage provides dialect specific support for PostgreSQL, and
|
||||
* may, over time, be extended to provide support for other SQL dialects.
|
||||
*
|
||||
* If you are looking for the place to add support for other SQL dialects,
|
||||
* this is the class that you should be looking at. You might also look at
|
||||
* the AwlDatabase class which extends this one, but these are the core
|
||||
* capabilities which most probably need attention.
|
||||
*
|
||||
* @package awl
|
||||
*/
|
||||
class AwlDBDialect {
|
||||
/**#@+
|
||||
* @access private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds the name of the database dialect
|
||||
*/
|
||||
protected $dialect;
|
||||
|
||||
/**
|
||||
* Holds the PDO database connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Holds the version
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**#@-*/
|
||||
|
||||
|
||||
/**
|
||||
* Parses the connection string to ascertain the database dialect. Returns true if the dialect is supported
|
||||
* and fails if the dialect is not supported. All code to support any given database should be within in an
|
||||
* external include.
|
||||
*
|
||||
* The database will be opened.
|
||||
*
|
||||
* @param string $connection_string The PDO connection string, in all it's glory
|
||||
* @param string $dbuser The database username to connect as
|
||||
* @param string $dbpass The database password to connect with
|
||||
* @param array $options An array of driver options
|
||||
*/
|
||||
function __construct( $connection_string, $dbuser=null, $dbpass=null, $options=null ) {
|
||||
if ( preg_match( '/^(pgsql):/', $connection_string, $matches ) ) {
|
||||
$this->dialect = $matches[1];
|
||||
}
|
||||
else {
|
||||
trigger_error("Unsupported database connection '".$connection_string."'",E_USER_ERROR);
|
||||
}
|
||||
try {
|
||||
$this->db = new PDO( $connection_string, $dbuser, $dbpass, $options );
|
||||
} catch (PDOException $e) {
|
||||
trigger_error("PDO connection error '".$connection_string."': ".$e->getMessage(),E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the current search path for the database.
|
||||
*/
|
||||
function SetSearchPath( $search_path = null ) {
|
||||
if ( !isset($this->dialect) ) {
|
||||
trigger_error("Unsupported database dialect",E_USER_ERROR);
|
||||
}
|
||||
|
||||
switch ( $this->dialect ) {
|
||||
case 'pgsql':
|
||||
if ( $search_path == null ) $search_path = 'public';
|
||||
$sql = "SET search_path TO " . $this->Quote( $search_path, 'identifier' );
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the current search path for the database.
|
||||
* @param handle $pdo A handle to an opened database
|
||||
*/
|
||||
function GetVersion( ) {
|
||||
if ( isset($this->version) ) return $this->version;
|
||||
if ( !isset($this->dialect) ) {
|
||||
trigger_error("Unsupported database dialect", E_USER_ERROR);
|
||||
}
|
||||
|
||||
$version = $this->dialect.':';
|
||||
|
||||
switch ( $this->dialect ) {
|
||||
case 'pgsql':
|
||||
$sql = "SELECT version()";
|
||||
if ( $sth = $this->db->query($sql) ) {
|
||||
$row = $sth->fetch(PDO::FETCH_NUM);
|
||||
$version .= preg_replace( '/^PostgreSQL (\d+\.\d+)\..*$/i', '$1', $row[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
$this->version = $version;
|
||||
return $version;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the SQL for the current database dialect which will return a two-column resultset containing a
|
||||
* list of fields and their associated data types.
|
||||
* @param string $tablename_string The name of the table we want fields from
|
||||
*/
|
||||
function GetFields( $tablename_string ) {
|
||||
if ( !isset($this->dialect) ) {
|
||||
trigger_error("Unsupported database dialect", E_USER_ERROR);
|
||||
}
|
||||
|
||||
switch ( $this->dialect ) {
|
||||
case 'pgsql':
|
||||
$tablename_string = $this->Quote($tablename_string, 'identifier');
|
||||
$sql = "SELECT f.attname, t.typname FROM pg_attribute f ";
|
||||
$sql .= "JOIN pg_class c ON ( f.attrelid = c.oid ) ";
|
||||
$sql .= "JOIN pg_type t ON ( f.atttypid = t.oid ) ";
|
||||
$sql .= "WHERE relname = $tablename_string AND attnum >= 0 order by f.attnum;";
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the given SQL string into a form that will hopefully work for this database dialect. This hook
|
||||
* is intended to be used by developers to provide support for differences in database operation by translating
|
||||
* the query string in an arbitrary way, such as through a file or database lookup.
|
||||
*
|
||||
* The actual translation to other SQL dialects will be application-specific, so that any routines
|
||||
* called by this will be external to this library, or will use resources loaded from some source
|
||||
* external to this library.
|
||||
*
|
||||
* The application developer is expected to use this functionality to solve harder translation problems,
|
||||
* but is less likely to call this directly, hopefully switching ->Prepare to ->PrepareTranslated in those
|
||||
* cases, and then adding that statement to whatever SQL translation infrastructure is in place.
|
||||
*/
|
||||
function TranslateSQL( $sql_string ) {
|
||||
// Noop for the time being...
|
||||
return $sql_string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns $value escaped in an appropriate way for this database dialect.
|
||||
* @param mixed $value The value to be escaped
|
||||
* @param string $value_type The type of escaping desired. If blank this will
|
||||
* be worked out from the type of the $value. The special type
|
||||
* of 'identifier' can also be used for escaping of SQL identifiers.
|
||||
*/
|
||||
function Quote( $value, $value_type = null ) {
|
||||
if ( isset($value_type) && $value_type == 'identifier' ) {
|
||||
if ( $this->dialect == 'mysql' ) {
|
||||
/** @TODO: Someone should confirm this is correct for MySql */
|
||||
$rv = '`' . str_replace('`', '\\`', $value ) . '`';
|
||||
}
|
||||
else {
|
||||
$rv = '"' . str_replace('"', '\\"', $value ) . '"';
|
||||
}
|
||||
return $rv;
|
||||
}
|
||||
|
||||
if ( !isset($value_type) ) {
|
||||
if ( !isset($value) ) $value_type = PDO::PARAM_NULL;
|
||||
elseif ( is_bool($value) ) $value_type = PDO::PARAM_BOOL;
|
||||
elseif ( is_float($value) ) $value_type = PDO::PARAM_INT;
|
||||
elseif ( is_numeric($value)) {
|
||||
if ( preg_match('{^(19|20)\d\d(0[1-9]|1[012])([012]\d|30|31)$}', $value) )
|
||||
$value_type = PDO::PARAM_STR; // YYYYMMDD
|
||||
elseif ( preg_match('{^[0-9+-]+e[0-9+-]+$}i', $value) )
|
||||
$value_type = PDO::PARAM_STR; // 72e57650 could easily be a string
|
||||
else
|
||||
$value_type = PDO::PARAM_INT;
|
||||
}
|
||||
else
|
||||
$value_type = PDO::PARAM_STR;
|
||||
}
|
||||
|
||||
if ( is_string($value_type) ) {
|
||||
switch( $value_type ) {
|
||||
case 'null':
|
||||
$value_type = PDO::PARAM_NULL;
|
||||
break;
|
||||
case 'integer':
|
||||
case 'double' :
|
||||
$value_type = PDO::PARAM_INT;
|
||||
break;
|
||||
case 'boolean':
|
||||
$value_type = PDO::PARAM_BOOL;
|
||||
break;
|
||||
case 'string':
|
||||
$value_type = PDO::PARAM_STR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $value_type ) {
|
||||
case PDO::PARAM_NULL:
|
||||
$rv = 'NULL';
|
||||
break;
|
||||
case PDO::PARAM_INT:
|
||||
$rv = $value;
|
||||
break;
|
||||
case PDO::PARAM_BOOL:
|
||||
$rv = ($value ? 'TRUE' : 'FALSE');
|
||||
break;
|
||||
case PDO::PARAM_STR:
|
||||
default:
|
||||
/**
|
||||
* PDO handling of \ seems unreliable. We can't use $$string$$ syntax because it also doesn't
|
||||
* work. We need to replace ':' so no other named parameters accidentally rewrite the content
|
||||
* inside this string(!), and since we're using ' to delimit the string we need SQL92-compliant
|
||||
* '' to replace it.
|
||||
*/
|
||||
$rv = "'".str_replace("'", "''", str_replace(':', '\\x3a', str_replace('\\', '\\x5c', $value)))."'";
|
||||
|
||||
if ( $this->dialect == 'pgsql' && strpos( $rv, '\\' ) !== false ) {
|
||||
/**
|
||||
* PostgreSQL wants to know when a string might contain escapes, and if this
|
||||
* happens old versions of PHP::PDO need the ? escaped as well...
|
||||
*/
|
||||
$rv = 'E'.str_replace('?', '\\x3f', $rv);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $rv;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces query parameters with appropriately escaped substitutions.
|
||||
*
|
||||
* The function takes a variable number of arguments, the first is the
|
||||
* SQL string, with replaceable '?' characters (a la DBI). The subsequent
|
||||
* parameters being the values to replace into the SQL string.
|
||||
*
|
||||
* The values passed to the routine are analyzed for type, and quoted if
|
||||
* they appear to need quoting. This can go wrong for (e.g.) NULL or
|
||||
* other special SQL values which are not straightforwardly identifiable
|
||||
* as needing quoting (or not). In such cases the parameter can be forced
|
||||
* to be inserted unquoted by passing it as "array( 'plain' => $param )".
|
||||
*
|
||||
* @param string The query string with replacable '?' characters.
|
||||
* @param mixed The values to replace into the SQL string.
|
||||
* @return The built query string
|
||||
*/
|
||||
function ReplaceParameters() {
|
||||
$argc = func_num_args();
|
||||
$args = func_get_args();
|
||||
|
||||
if ( is_array($args[0]) ) {
|
||||
/**
|
||||
* If the first argument is an array we treat that as our arguments instead
|
||||
*/
|
||||
$args = $args[0];
|
||||
$argc = count($args);
|
||||
}
|
||||
$qry = array_shift($args);
|
||||
|
||||
if ( is_array($args[0]) ) {
|
||||
$args = $args[0];
|
||||
$argc = count($args);
|
||||
}
|
||||
|
||||
if ( ! isset($args[0]) ) return $this->ReplaceNamedParameters($qry,$args);
|
||||
|
||||
/**
|
||||
* We only split into a maximum of $argc chunks. Any leftover ? will remain in
|
||||
* the string and may be replaced at Exec rather than Prepare. Scary!
|
||||
*/
|
||||
$parts = explode( '?', $qry, $argc + 1 );
|
||||
$querystring = $parts[0];
|
||||
$z = count($parts);
|
||||
|
||||
for( $i = 0; $i < $argc; $i++ ) {
|
||||
$arg = $args[$i];
|
||||
$querystring .= $this->Quote($arg); //parameter
|
||||
$z = $i+1;
|
||||
if ( isset($parts[$z]) ) $querystring .= $parts[$z];
|
||||
}
|
||||
|
||||
return $querystring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces named query parameters of the form :name with appropriately
|
||||
* escaped substitutions.
|
||||
*
|
||||
* The function takes a variable number of arguments, the first is the
|
||||
* SQL string, with replaceable ':name' characters (a la DBI). The
|
||||
* subsequent parameters being the values to replace into the SQL string.
|
||||
*
|
||||
* The values passed to the routine are analyzed for type, and quoted if
|
||||
* they appear to need quoting. This can go wrong for (e.g.) NULL or
|
||||
* other special SQL values which are not straightforwardly identifiable
|
||||
* as needing quoting (or not).
|
||||
*
|
||||
* @param string The query string with replacable ':name' identifiers
|
||||
* @param mixed A ':name' => 'value' hash of values to replace into the
|
||||
* SQL string.
|
||||
* @return The built query string
|
||||
*/
|
||||
function ReplaceNamedParameters() {
|
||||
$argc = func_num_args();
|
||||
$args = func_get_args();
|
||||
|
||||
if ( is_array($args[0]) ) {
|
||||
/**
|
||||
* If the first argument is an array we treat that as our arguments instead
|
||||
*/
|
||||
$args = $args[0];
|
||||
$argc = count($args);
|
||||
}
|
||||
$querystring = array_shift($args);
|
||||
|
||||
if ( is_array($args[0]) ) {
|
||||
$args = $args[0];
|
||||
$argc = count($args);
|
||||
}
|
||||
|
||||
foreach( $args AS $name => $value ) {
|
||||
if ( substr($name, 0, 1) != ':' ) {
|
||||
dbg_error_log( "ERROR", "AwlDBDialect: Named parameter '%s' does not begin with a colon.", $name);
|
||||
}
|
||||
$replacement = str_replace('$', '\\$', $this->Quote($value)); // No positional replacement in $replacement!
|
||||
$querystring = preg_replace( '{\Q'.$name.'\E\b}s', $replacement, $querystring );
|
||||
}
|
||||
|
||||
return $querystring;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,178 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* AwlDatabase query/statement class and associated functions
|
||||
*
|
||||
* This subpackage provides some functions that are useful around database
|
||||
* activity and a AwlDBDialect, AwlDatabase and AwlStatement classes to simplify
|
||||
* handling of database queries and provide some access for a limited
|
||||
* ability to handle varying database dialects.
|
||||
*
|
||||
* The class is intended to be a very lightweight wrapper with some features
|
||||
* that have proved useful in developing and debugging web-based applications:
|
||||
* - All queries are timed, and an expected time can be provided.
|
||||
* - Parameters replaced into the SQL will be escaped correctly in order to
|
||||
* minimise the chances of SQL injection errors.
|
||||
* - Queries which fail, or which exceed their expected execution time, will
|
||||
* be logged for potential further analysis.
|
||||
* - Debug logging of queries may be enabled globally, or restricted to
|
||||
* particular sets of queries.
|
||||
* - Simple syntax for iterating through a result set.
|
||||
*
|
||||
* See http://wiki.davical.org/w/AwlDatabase for design and usage information.
|
||||
*
|
||||
* If not already connected, AwlDatabase will attempt to connect to the database,
|
||||
* successively applying connection parameters from the array in $c->pdo_connect.
|
||||
*
|
||||
* We will die if the database is not currently connected and we fail to find
|
||||
* a working connection.
|
||||
*
|
||||
* @package awl
|
||||
* @subpackage AwlDatabase
|
||||
* @author Andrew McMillan <andrew@morphoss.com>
|
||||
* @copyright Morphoss Ltd
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @compatibility Requires PHP 5.1 or later
|
||||
*/
|
||||
|
||||
if ( !class_exists('AwlDBDialect') ) require('AwlDBDialect.php');
|
||||
|
||||
if ( !defined('E_USER_ERROR') ) define('E_USER_ERROR',256);
|
||||
|
||||
|
||||
/**
|
||||
* Methods in the AwlDBDialect class which we inherit, include:
|
||||
* __construct()
|
||||
* SetSearchPath( $search_path )
|
||||
* GetVersion()
|
||||
* GetFields( $tablename_string )
|
||||
* TranslateSQL( $sql_string )
|
||||
* Quote( $value, $value_type = null )
|
||||
* ReplaceParameters( $query_string [, param [, ...]] )
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Typically there will only be a single instance of the database level class in an application.
|
||||
* @package awl
|
||||
*/
|
||||
class AwlDatabase extends AwlDBDialect {
|
||||
/**#@+
|
||||
* @access private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds the state of the transaction 0 = not started, 1 = in progress, -1 = error pending rollback/commit
|
||||
*/
|
||||
protected $txnstate = 0;
|
||||
|
||||
/**
|
||||
* Holds whether we are translating all statements.
|
||||
*/
|
||||
protected $translate_all = false;
|
||||
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Returns a PDOStatement object created using this database, the supplied SQL string, and any parameters given.
|
||||
* @param string $sql_query_string The SQL string containing optional variable replacements
|
||||
* @param array $driver_options PDO driver options to the prepare statement, commonly to do with cursors
|
||||
*/
|
||||
function prepare( $statement, $driver_options = array() ) {
|
||||
if ( isset($this->translate_all) && $this->translate_all ) {
|
||||
$statement = $this->TranslateSQL( $statement );
|
||||
}
|
||||
return $this->db->prepare( $statement, $driver_options );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a PDOStatement object created using this database, the supplied SQL string, and any parameters given.
|
||||
* @param string $sql_query_string The SQL string containing optional variable replacements
|
||||
* @param mixed ... Subsequent arguments are positionally replaced into the $sql_query_string
|
||||
*/
|
||||
function query( $statement ) {
|
||||
return $this->db->query( $statement );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begin a transaction.
|
||||
*/
|
||||
function Begin() {
|
||||
if ( $this->txnstate == 0 ) {
|
||||
$this->db->beginTransaction();
|
||||
$this->txnstate = 1;
|
||||
}
|
||||
else {
|
||||
trigger_error("Cannot begin a transaction while a transaction is already active.",E_USER_ERROR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete a transaction.
|
||||
*/
|
||||
function Commit() {
|
||||
if ( $this->txnstate != 0 ) {
|
||||
$this->db->commit();
|
||||
$this->txnstate = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel a transaction in progress.
|
||||
*/
|
||||
function Rollback() {
|
||||
if ( $this->txnstate != 0 ) {
|
||||
$this->db->rollBack();
|
||||
$this->txnstate = 0;
|
||||
}
|
||||
else {
|
||||
trigger_error("Cannot rollback unless a transaction is already active.",E_USER_ERROR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current state of a transaction, indicating if we have begun a transaction, whether the transaction
|
||||
* has failed, or if we are not in a transaction.
|
||||
*/
|
||||
function TransactionState() {
|
||||
return $this->txnstate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operates identically to AwlDatabase::Prepare, except that $this->Translate() will be called on the query
|
||||
* before any processing.
|
||||
*/
|
||||
function PrepareTranslated( $statement, $driver_options = array() ) {
|
||||
$statement = $this->TranslateSQL( $statement );
|
||||
return $this->prepare( $statement, $driver_options );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Switches on or off the processing flag controlling whether subsequent calls to AwlDatabase::Prepare are translated
|
||||
* as if PrepareTranslated() had been called.
|
||||
*/
|
||||
function TranslateAll( $onoff_boolean ) {
|
||||
$this->translate_all = $onoff_boolean;
|
||||
return $onoff_boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ErrorInfo() {
|
||||
return $this->db->errorInfo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
606
inc/AwlQuery.php
606
inc/AwlQuery.php
@ -1,606 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package awl
|
||||
* @subpackage AwlDatabase
|
||||
* @author Andrew McMillan <andrew@morphoss.com>
|
||||
* @copyright Morphoss Ltd
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @compatibility Requires PHP 5.1 or later
|
||||
*/
|
||||
|
||||
require_once('AwlDatabase.php');
|
||||
|
||||
/**
|
||||
* Database query class and associated functions
|
||||
*
|
||||
* This subpackage provides some functions that are useful around database
|
||||
* activity and an AwlQuery class to simplify handling of database queries.
|
||||
*
|
||||
* The class is intended to be a very lightweight wrapper with no pretentions
|
||||
* towards database independence, but it does include some features that have
|
||||
* proved useful in developing and debugging web-based applications:
|
||||
* - All queries are timed, and an expected time can be provided.
|
||||
* - Parameters replaced into the SQL will be escaped correctly in order to
|
||||
* minimise the chances of SQL injection errors.
|
||||
* - Queries which fail, or which exceed their expected execution time, will
|
||||
* be logged for potential further analysis.
|
||||
* - Debug logging of queries may be enabled globally, or restricted to
|
||||
* particular sets of queries.
|
||||
* - Simple syntax for iterating through a result set.
|
||||
*
|
||||
* This class is intended as a transitional mechanism for moving from the
|
||||
* PostgreSQL-specific Pg Query class to something which uses PDO in a more
|
||||
* replaceable manner.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Connect to the database defined in the $c->db_connect[] (or $c->pg_connect) arrays
|
||||
*/
|
||||
function _awl_connect_configured_database() {
|
||||
global $c, $_awl_dbconn;
|
||||
|
||||
/**
|
||||
* Attempt to connect to the configured connect strings
|
||||
*/
|
||||
$_awl_dbconn = false;
|
||||
|
||||
if ( isset($c->db_connect) ) {
|
||||
$connection_strings = $c->db_connect;
|
||||
}
|
||||
elseif ( isset($c->pg_connect) ) {
|
||||
$connection_strings = $c->pg_connect;
|
||||
}
|
||||
|
||||
foreach( $connection_strings AS $k => $v ) {
|
||||
$dbuser = null;
|
||||
$dbpass = null;
|
||||
if ( is_array($v) ) {
|
||||
$dsn = $v['dsn'];
|
||||
if ( isset($v['dbuser']) ) $dbuser = $v['dbuser'];
|
||||
if ( isset($v['dbpass']) ) $dbpass = $v['dbpass'];
|
||||
}
|
||||
elseif ( preg_match( '/^(\S+:)?(.*)( user=(\S+))?( password=(\S+))?$/', $v, $matches ) ) {
|
||||
$dsn = $matches[2];
|
||||
if ( isset($matches[1]) && $matches[1] != '' ) {
|
||||
$dsn = $matches[1] . $dsn;
|
||||
}
|
||||
else {
|
||||
$dsn = 'pgsql:' . $dsn;
|
||||
}
|
||||
if ( isset($matches[4]) && $matches[4] != '' ) $dbuser = $matches[4];
|
||||
if ( isset($matches[6]) && $matches[6] != '' ) $dbpass = $matches[6];
|
||||
}
|
||||
if ( $_awl_dbconn = new AwlDatabase( $dsn, $dbuser, $dbpass, (isset($c->use_persistent) && $c->use_persistent ? array(PDO::ATTR_PERSISTENT => true) : null) ) ) break;
|
||||
}
|
||||
|
||||
if ( ! $_awl_dbconn ) {
|
||||
echo <<<EOERRMSG
|
||||
<html><head><title>Database Connection Failure</title></head><body>
|
||||
<h1>Database Error</h1>
|
||||
<h3>Could not connect to database</h3>
|
||||
</body>
|
||||
</html>
|
||||
EOERRMSG;
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( isset($c->db_schema) && $c->db_schema != '' ) {
|
||||
$_awl_dbconn->SetSearchPath( $c->db_schema . ',public' );
|
||||
}
|
||||
|
||||
$c->_awl_dbversion = $_awl_dbconn->GetVersion();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The AwlQuery Class.
|
||||
*
|
||||
* This class builds and executes SQL Queries and traverses the
|
||||
* set of results returned from the query.
|
||||
*
|
||||
* <b>Example usage</b>
|
||||
* <code>
|
||||
* $sql = "SELECT * FROM mytable WHERE mytype = ?";
|
||||
* $qry = new AwlQuery( $sql, $myunsanitisedtype );
|
||||
* if ( $qry->Exec("typeselect", __line__, __file__ )
|
||||
* && $qry->rows > 0 )
|
||||
* {
|
||||
* while( $row = $qry->Fetch() ) {
|
||||
* do_something_with($row);
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @package awl
|
||||
*/
|
||||
class AwlQuery
|
||||
{
|
||||
/**#@+
|
||||
* @access private
|
||||
*/
|
||||
/**
|
||||
* Our database connection, normally copied from a global one
|
||||
* @var resource
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The original query string
|
||||
* @var string
|
||||
*/
|
||||
protected $querystring;
|
||||
|
||||
/**
|
||||
* The actual query string, after we've replaced parameters in it
|
||||
* @var string
|
||||
*/
|
||||
protected $bound_querystring;
|
||||
|
||||
/**
|
||||
* The current array of bound parameters
|
||||
* @var array
|
||||
*/
|
||||
protected $bound_parameters;
|
||||
|
||||
/**
|
||||
* The PDO statement handle, or null if we don't have one yet.
|
||||
* @var string
|
||||
*/
|
||||
protected $sth;
|
||||
|
||||
/**
|
||||
* Result of the last execution
|
||||
* @var resource
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* number of current row - use accessor to get/set
|
||||
* @var int
|
||||
*/
|
||||
protected $rownum = null;
|
||||
|
||||
/**
|
||||
* number of rows from pg_numrows - use accessor to get value
|
||||
* @var int
|
||||
*/
|
||||
protected $rows;
|
||||
|
||||
/**
|
||||
* The Database error information, if the query fails.
|
||||
* @var string
|
||||
*/
|
||||
protected $error_info;
|
||||
|
||||
/**
|
||||
* Stores the query execution time - used to deal with long queries.
|
||||
* should be read-only
|
||||
* @var string
|
||||
*/
|
||||
protected $execution_time;
|
||||
|
||||
/**#@-*/
|
||||
|
||||
/**#@+
|
||||
* @access public
|
||||
*/
|
||||
/**
|
||||
* Where we called this query from so we can find it in our code!
|
||||
* Debugging may also be selectively enabled for a $location.
|
||||
* @var string
|
||||
*/
|
||||
public $location;
|
||||
|
||||
/**
|
||||
* How long the query should take before a warning is issued.
|
||||
*
|
||||
* This is writable, but a method to set it might be a better interface.
|
||||
* The default is 0.3 seconds.
|
||||
* @var double
|
||||
*/
|
||||
public $query_time_warning = 0.3;
|
||||
/**#@-*/
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param string The query string in PDO syntax with replacable '?' characters or bindable parameters.
|
||||
* @param mixed The values to replace into the SQL string.
|
||||
* @return The AwlQuery object
|
||||
*/
|
||||
function __construct() {
|
||||
global $_awl_dbconn;
|
||||
$this->rows = null;
|
||||
$this->execution_time = 0;
|
||||
$this->error_info = null;
|
||||
$this->rownum = -1;
|
||||
if ( isset($_awl_dbconn) ) $this->connection = $_awl_dbconn;
|
||||
else $this->connection = null;
|
||||
|
||||
$argc = func_num_args();
|
||||
$args = func_get_args();
|
||||
|
||||
$this->querystring = array_shift($args);
|
||||
if ( 1 < $argc ) {
|
||||
if ( is_array($args[0]) )
|
||||
$this->bound_parameters = $args[0];
|
||||
else
|
||||
$this->bound_parameters = $args;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use a different database connection for this query
|
||||
* @param resource $new_connection The database connection to use.
|
||||
*/
|
||||
function SetConnection( $new_connection, $options = null ) {
|
||||
if ( is_string($new_connection) || is_array($new_connection) ) {
|
||||
$dbuser = null;
|
||||
$dbpass = null;
|
||||
if ( is_array($new_connection) ) {
|
||||
$dsn = $new_connection['dsn'];
|
||||
if ( isset($new_connection['dbuser']) ) $dbuser = $new_connection['dbuser'];
|
||||
if ( isset($new_connection['dbpass']) ) $dbpass = $new_connection['dbpass'];
|
||||
}
|
||||
elseif ( preg_match( '/^(\S+:)?(.*)( user=(\S+))?( password=(\S+))?$/', $new_connection, $matches ) ) {
|
||||
$dsn = $matches[2];
|
||||
if ( isset($matches[1]) && $matches[1] != '' ) {
|
||||
$dsn = $matches[1] . $dsn;
|
||||
}
|
||||
else {
|
||||
$dsn = 'pgsql:' . $dsn;
|
||||
}
|
||||
if ( isset($matches[4]) && $matches[4] != '' ) $dbuser = $matches[4];
|
||||
if ( isset($matches[6]) && $matches[6] != '' ) $dbpass = $matches[6];
|
||||
}
|
||||
if ( $new_connection = new AwlDatabase( $dsn, $dbuser, $dbpass, $options ) ) break;
|
||||
}
|
||||
$this->connection = $new_connection;
|
||||
return $new_connection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Log query, optionally with file and line location of the caller.
|
||||
*
|
||||
* This function should not really be used outside of AwlQuery. For a more
|
||||
* useful generic logging interface consider calling dbg_error_log(...);
|
||||
*
|
||||
* @param string $locn A string identifying the calling location.
|
||||
* @param string $tag A tag string, e.g. identifying the type of event.
|
||||
* @param string $string The information to be logged.
|
||||
* @param int $line The line number where the logged event occurred.
|
||||
* @param string $file The file name where the logged event occurred.
|
||||
*/
|
||||
function _log_query( $locn, $tag, $string, $line = 0, $file = "") {
|
||||
// replace more than one space with one space
|
||||
$string = preg_replace('/\s+/', ' ', $string);
|
||||
|
||||
if ( ($tag == 'QF' || $tag == 'SQ') && ( $line != 0 && $file != "" ) ) {
|
||||
dbg_error_log( "LOG-$locn", " Query: %s: %s in '%s' on line %d", ($tag == 'QF' ? 'Error' : 'Possible slow query'), $tag, $file, $line );
|
||||
}
|
||||
|
||||
while( strlen( $string ) > 0 ) {
|
||||
dbg_error_log( "LOG-$locn", " Query: %s: %s", $tag, substr( $string, 0, 240) );
|
||||
$string = substr( "$string", 240 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Quote the given string so it can be safely used within string delimiters
|
||||
* in a query. To be avoided, in general.
|
||||
*
|
||||
* @param mixed $str Data to be converted to a string suitable for including as a value in SQL.
|
||||
* @return string NULL, TRUE, FALSE, a plain number, or the original string quoted and with ' and \ characters escaped
|
||||
*/
|
||||
function quote($str = null) {
|
||||
if ( !isset($this->connection) ) {
|
||||
_awl_connect_configured_database();
|
||||
$this->connection = $GLOBALS['_awl_dbconn'];
|
||||
}
|
||||
return $this->connection->Quote($str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bind some parameters. This can be called in three ways:
|
||||
* 1) As Bind(':key','value), when using named parameters
|
||||
* 2) As Bind('value'), when using ? placeholders
|
||||
* 3) As Bind(array()), to overwrite the existing bound parameters. The array may
|
||||
* be ':name' => 'value' pairs or ordinal values, depending on whether the SQL
|
||||
* is using ':name' or '?' style placeholders.
|
||||
*
|
||||
* @param mixed $args See details above.
|
||||
*/
|
||||
function Bind() {
|
||||
$argc = func_num_args();
|
||||
$args = func_get_args();
|
||||
|
||||
if ( $argc == 1 ) {
|
||||
if ( gettype($args[0]) == 'array' ) {
|
||||
$this->bound_parameters = $args[0];
|
||||
}
|
||||
else {
|
||||
$this->bound_parameters[] = $args[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->bound_parameters[$args[0]] = $args[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tell the database to prepare the query that we will execute
|
||||
*/
|
||||
function Prepare() {
|
||||
global $c;
|
||||
|
||||
if ( isset($this->sth) ) return; // Already prepared
|
||||
if ( isset($c->expand_pdo_parameters) && $c->expand_pdo_parameters ) return; // No-op if we're expanding internally
|
||||
|
||||
if ( !isset($this->connection) ) {
|
||||
_awl_connect_configured_database();
|
||||
$this->connection = $GLOBALS['_awl_dbconn'];
|
||||
}
|
||||
|
||||
$this->sth = $this->connection->prepare( $this->querystring );
|
||||
|
||||
if ( ! $this->sth ) {
|
||||
$this->error_info = $this->connection->errorInfo();
|
||||
}
|
||||
else $this->error_info = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tell the database to execute the query
|
||||
*/
|
||||
function Execute() {
|
||||
global $c;
|
||||
|
||||
if ( !isset($this->connection) ) {
|
||||
_awl_connect_configured_database();
|
||||
$this->connection = $GLOBALS['_awl_dbconn'];
|
||||
}
|
||||
|
||||
if ( isset($c->expand_pdo_parameters) && $c->expand_pdo_parameters ) {
|
||||
if ( isset($this->bound_parameters) ) {
|
||||
$this->bound_querystring = $this->connection->ReplaceParameters($this->querystring,$this->bound_parameters);
|
||||
// printf( "\n=============================================================== OQ\n%s\n", $this->querystring);
|
||||
// printf( "\n=============================================================== QQ\n%s\n", $this->bound_querystring);
|
||||
// print_r( $this->bound_parameters );
|
||||
}
|
||||
else {
|
||||
$this->bound_querystring = $this->querystring;
|
||||
}
|
||||
}
|
||||
|
||||
$t1 = microtime(true); // get start time
|
||||
if ( isset($this->bound_querystring) ) {
|
||||
$this->sth = $this->connection->query($this->bound_querystring);
|
||||
$this->bound_querystring = null;
|
||||
if ( ! $this->sth ) {
|
||||
$this->error_info = $this->connection->errorInfo();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( ! $this->sth->execute( $this->bound_parameters ) ) {
|
||||
$this->error_info = $this->sth->errorInfo();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->rows = $this->sth->rowCount();
|
||||
$i_took = microtime(true) - $t1;
|
||||
$c->total_query_time += $i_took;
|
||||
$this->execution_time = sprintf( "%2.06lf", $i_took);
|
||||
|
||||
$this->error_info = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the query string we are planning to execute
|
||||
*/
|
||||
function QueryString() {
|
||||
return $this->querystring;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the parameters we are planning to substitute into the query string
|
||||
*/
|
||||
function Parameters() {
|
||||
return $this->bound_parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the count of rows retrieved/affected
|
||||
*/
|
||||
function rows() {
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap the parent DB class Begin() so we can $qry->Begin() sometime before we $qry->Exec()
|
||||
*/
|
||||
public function Begin() {
|
||||
global $_awl_dbconn;
|
||||
if ( !isset($this->connection) ) {
|
||||
if ( !isset($_awl_dbconn) ) _awl_connect_configured_database();
|
||||
$this->connection = $_awl_dbconn;
|
||||
}
|
||||
return $this->connection->Begin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap the parent DB class Commit() so we can $qry->Commit() sometime after we $qry->Exec()
|
||||
*/
|
||||
public function Commit() {
|
||||
if ( !isset($this->connection) ) {
|
||||
trigger_error("Cannot commit a transaction without an active statement.", E_USER_ERROR);
|
||||
}
|
||||
return $this->connection->Commit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap the parent DB class Rollback() so we can $qry->Rollback() sometime after we $qry->Exec()
|
||||
*/
|
||||
public function Rollback() {
|
||||
if ( !isset($this->connection) ) {
|
||||
trigger_error("Cannot rollback a transaction without an active statement.", E_USER_ERROR);
|
||||
}
|
||||
return $this->connection->Rollback();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple SetSql() class which will reset the object with the querystring from the first argument.
|
||||
* @param string The query string in PDO syntax with replacable '?' characters or bindable parameters.
|
||||
*/
|
||||
public function SetSql( $sql ) {
|
||||
$this->rows = null;
|
||||
$this->execution_time = 0;
|
||||
$this->error_info = null;
|
||||
$this->rownum = -1;
|
||||
$this->bound_parameters = null;
|
||||
$this->bound_querystring = null;
|
||||
$this->sth = null;
|
||||
|
||||
$this->querystring = $sql;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple QDo() class which will re-use this query for whatever was passed in, and execute it
|
||||
* returning the result of the Exec() call. We can't call it Do() since that's a reserved word...
|
||||
* @param string The query string in PDO syntax with replacable '?' characters or bindable parameters.
|
||||
* @param mixed The values to replace into the SQL string.
|
||||
* @return boolean Success (true) or Failure (false)
|
||||
*/
|
||||
public function QDo() {
|
||||
$argc = func_num_args();
|
||||
$args = func_get_args();
|
||||
|
||||
$this->SetSql( array_shift($args) );
|
||||
if ( 1 < $argc ) {
|
||||
if ( is_array($args[0]) )
|
||||
$this->bound_parameters = $args[0];
|
||||
else
|
||||
$this->bound_parameters = $args;
|
||||
}
|
||||
|
||||
return $this->Exec();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the query, logging any debugging.
|
||||
*
|
||||
* <b>Example</b>
|
||||
* So that you can nicely enable/disable the queries for a particular class, you
|
||||
* could use some of PHPs magic constants in your call.
|
||||
* <code>
|
||||
* $qry->Exec(__CLASS__, __LINE__, __FILE__);
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param string $location The name of the location for enabling debugging or just
|
||||
* to help our children find the source of a problem.
|
||||
* @param int $line The line number where Exec was called
|
||||
* @param string $file The file where Exec was called
|
||||
* @return boolean Success (true) or Failure (false)
|
||||
*/
|
||||
function Exec( $location = null, $line = null, $file = null ) {
|
||||
global $c;
|
||||
if ( isset($location) ) $this->location = trim($location);
|
||||
if ( !isset($this->location) || $this->location == "" ) $this->location = substr($_SERVER['PHP_SELF'],1);
|
||||
|
||||
if ( isset($line) ) $this->location_line = intval($line);
|
||||
else if ( isset($this->location_line) ) $line = $this->location_line;
|
||||
|
||||
if ( isset($file) ) $this->location_file = trim($file);
|
||||
else if ( isset($this->location_file) ) $file = $this->location_file;
|
||||
|
||||
if ( isset($c->dbg['querystring']) || isset($c->dbg['ALL']) ) {
|
||||
$this->_log_query( $this->location, 'DBGQ', $this->querystring, $line, $file );
|
||||
if ( isset($this->bound_parameters) && !isset($this->sth) ) {
|
||||
foreach( $this->bound_parameters AS $k => $v ) {
|
||||
$this->_log_query( $this->location, 'DBGQ', sprintf(' "%s" => "%s"', $k, $v), $line, $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset($this->bound_parameters) ) {
|
||||
$this->Prepare();
|
||||
}
|
||||
|
||||
$success = $this->Execute();
|
||||
|
||||
if ( ! $success ) {
|
||||
// query failed
|
||||
$this->errorstring = sprintf( 'SQL error "%s" - %s"', $this->error_info[0], (isset($this->error_info[2]) ? $this->error_info[2] : ''));
|
||||
if ( isset($c->dbg['print_query_errors']) && $c->dbg['print_query_errors'] ) {
|
||||
printf( "\n=====================\n" );
|
||||
printf( "%s[%d] QF: %s\n", $file, $line, $this->errorstring);
|
||||
printf( "%s\n", $this->querystring );
|
||||
foreach( $this->bound_parameters AS $k => $v ) {
|
||||
printf( " %-18s \t=> '%s'\n", "'$k'", $v );
|
||||
}
|
||||
printf( ".....................\n" );
|
||||
}
|
||||
$this->_log_query( $this->location, 'QF', $this->errorstring, $line, $file );
|
||||
$this->_log_query( $this->location, 'QF', $this->querystring, $line, $file );
|
||||
if ( isset($this->bound_parameters) && ! ( isset($c->dbg['querystring']) || isset($c->dbg['ALL']) ) ) {
|
||||
foreach( $this->bound_parameters AS $k => $v ) {
|
||||
dbg_error_log( 'LOG-'.$this->location, ' Query: QF: "%s" => "%s"', $k, $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( $this->execution_time > $this->query_time_warning ) {
|
||||
// if execution time is too long
|
||||
$this->_log_query( $this->location, 'SQ', "Took: $this->execution_time for $this->querystring", $line, $file ); // SQ == Slow Query :-)
|
||||
}
|
||||
elseif ( isset($c->dbg['querystring']) || isset($c->dbg[strtolower($this->location)]) || isset($c->dbg['ALL']) ) {
|
||||
// query successful, but we're debugging and want to know how long it took anyway
|
||||
$this->_log_query( $this->location, 'DBGQ', "Took: $this->execution_time to find $this->rows rows.", $line, $file );
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch the next row from the query results
|
||||
* @param boolean $as_array True if thing to be returned is array
|
||||
* @return mixed query row
|
||||
*/
|
||||
function Fetch($as_array = false) {
|
||||
|
||||
if ( ! $this->sth || $this->rows == 0 ) return false; // no results
|
||||
if ( $this->rownum == null ) $this->rownum = -1;
|
||||
if ( ($this->rownum + 1) >= $this->rows ) return false; // reached the end of results
|
||||
|
||||
$this->rownum++;
|
||||
$row = $this->sth->fetch( ($as_array ? PDO::FETCH_NUM : PDO::FETCH_OBJ) );
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package awl
|
||||
* @subpackage AwlDatabase
|
||||
* @author Andrew McMillan <andrew@morphoss.com>
|
||||
* @copyright Morphoss Ltd
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
|
||||
* @compatibility Requires PHP 5.1 or later
|
||||
*/
|
||||
|
||||
require_once('AwlQuery.php');
|
||||
|
||||
/**
|
||||
* Database upgrader class and associated functions
|
||||
*
|
||||
* This subpackage provides some functions that are useful around database
|
||||
* schema creation and changes.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* The AwlUpgrader Class.
|
||||
*
|
||||
* This class updates an Awl database to a newer schema version.
|
||||
*
|
||||
*
|
||||
* @package awl
|
||||
*/
|
||||
class AwlUpgrader
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
* @return The AwlUpgrader object
|
||||
*/
|
||||
function __construct() {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user