ConLite/conlib/db_sql_abstract.inc
o.pinke 0ebf3dfabf add using strings to prepare Query
add edit_area to source_editor for testing
2021-09-04 10:16:14 +02:00

827 Zeilen
24 KiB
PHP

<?php
/**
* Project:
* Contenido Content Management System
*
* Description:
* Base (abstract) database class. Each database driver file should extend this!
* @todo Better error handling, use exceptions!
*
* Requirements:
* @con_php_req 5
*
*
* @package Contenido Database
* @version 0.1.2
* @author Murat Purc <murat@purc.de>
* @copyright four for business AG <www.4fb.de>
* @license http://www.contenido.org/license/LIZENZ.txt
* @link http://www.4fb.de
* @link http://www.contenido.org
* @since file available since Contenido release 4.8.15
*
* {@internal
* created 2011-03-03
* modified 2011-03-13, Murat Purc, Added FETCH_* constants, extended toArray(),
* moved close(), table_names() and server_info()
* from child implementations.
*
* $Id$:
* }}
*
*/
if (!defined('CON_FRAMEWORK')) {
die('Illegal call');
}
abstract class DB_Sql_Abstract {
const HALT_YES = 'yes';
const HALT_NO = 'no';
const HALT_REPORT = 'report';
const FETCH_NUMERIC = 'numeric';
const FETCH_ASSOC = 'assoc';
const FETCH_BOTH = 'both';
/**
* Assoziative list of database connections
* @array
*/
protected static $_connectionCache = array();
/**
* Assoziative list of database tables metadata
* @array
*/
protected static $_metaCache = array();
/**
* Sequence table name
* @var string
*/
public $Seq_Table = '';
/**
* Flag to print debug messages
* @var bool
*/
public $Debug = false;
/**
* Database name
* @var string
*/
public $Database = '';
/**
* Database connection user name
* @var string
*/
public $User = '';
/**
* Database connection password
* @var string
*/
public $Password = '';
/**
* Database connection resource
* @var resource|int
*/
public $Link_ID = 0;
/**
* Resource identifier of last executed query
* @var resource
*/
public $Query_ID = 0;
/**
* Recordset data array. Could contain either indexed or assoziative result set (or both)
* @var array
*/
public $Record = array();
/**
* The row position inside last executed select result
* @var int
*/
public $Row;
/**
* Database error number
* @var int
*/
public $Errno = 0;
/**
* Database error message
* @var string
*/
public $Error = '';
/**
* Flag to automatic free results
* @var int
*/
public $Auto_Free = 0;
/**
* Database identification string.
* @var string
*/
public $type = '';
/**
* Revision. This is an api revision, not a CVS revision
* @var string
*/
public $revision = '1.3';
/**
* Halt status during occured errors. Feasible values are
* - "yes" (halt with message)
* - "no" (ignore errors quietly)
* - "report" (ignore errror, but spit a warning)
* @var string
*/
public $Halt_On_Error = 'no';
/**
* Text to prepend to the halt message
* @var string
*/
protected $_sHaltMsgPrefix = '';
/**
* Default database connection for all instances
* @var array
*/
protected static $_aDefaultDbCfg = array();
/**
* Database connection configuration for current instance
* @var array
*/
protected $_aDbCfg;
/**
* Enable profiling
* @var bool
*/
protected $_bProfile = false;
/**
* Don't lock tables
* @var bool
*/
protected $_bNolock = false;
/**
* Profile data array
* @var array
*/
protected static $_aProfileData = array();
// @todo intended to map new protected properties against old public
// properties by using magic __call method
protected $_oldProperties = array(
'Seq_Table' => '_sSeqTable',
'Debug' => '_bDebug',
'Database' => '_sDatabase',
'User' => '_sUser',
'Password' => '_sPassword',
'Link_ID' => '_mLinkId',
'Query_ID' => '_mQueryId',
'Auto_Free' => '_iAutoFree',
'type' => '_sType',
'revision' => '_sRevision',
'Halt_On_Error' => '_sHaltOnError',
);
/**
* Constructor, sets passed options and connects to the DBMS, if not done before.
*
* Uses default connection settings, passed $options['db_connection'] settings
* will overwrite connection settings for current instance.
*
* @param array $options Assoziative options as follows:
* - $options['sequenceTable'] (string) Optional, the sequence table name
* - $options['nolock'] (bool) Optional, not lock table
* - $options['haltBehavior'] (string) Optional, halt behavior on occured errors
* - $options['haltMsgPrefix'] (string) Optional, Text to prepend to the halt message
* - $options['type'] (string) Database driver name
* - $options['enableProfiling'] (bool) Optional, flag to enable profiling
* - $options['connection'] (array) Optional, assoziative connection settings
* @return void
*/
protected function __construct(array $options) {
// use default connection configuration, but overwrite it by passed options
$this->_aDbCfg = array_merge(self::$_aDefaultDbCfg, $options);
if (isset($this->_aDbCfg['sequenceTable']) && is_string($this->_aDbCfg['sequenceTable'])) {
$this->Seq_Table = $this->_aDbCfg['sequenceTable'];
}
if (isset($this->_aDbCfg['haltBehavior'])) {
switch ($this->_aDbCfg['haltBehavior']) {
case self::HALT_YES:
$this->Halt_On_Error = self::HALT_YES;
break;
case self::HALT_NO:
$this->Halt_On_Error = self::HALT_NO;
break;
case self::HALT_REPORT:
$this->Halt_On_Error = self::HALT_REPORT;
break;
}
}
if (isset($this->_aDbCfg['haltMsgPrefix']) && is_string($this->_aDbCfg['haltMsgPrefix'])) {
$this->_sHaltMsgPrefix = $this->_aDbCfg['haltMsgPrefix'];
}
if (isset($this->_aDbCfg['type']) && is_string($this->_aDbCfg['type'])) {
$this->type = $this->_aDbCfg['type'];
}
if (isset($this->_aDbCfg['nolock']) && is_bool($this->_aDbCfg['nolock'])) {
$this->_bNolock = (bool) $this->_aDbCfg['nolock'];
}
if (isset($this->_aDbCfg['enableProfiling']) && is_bool($this->_aDbCfg['enableProfiling'])) {
$this->_bProfile = (bool) $this->_aDbCfg['enableProfiling'];
}
$this->connect();
}
/**
* Setter for default database configuration, the connection values.
*
* @param array $defaultDbCfg
*/
public static function setDefaultConfiguration(array $defaultDbCfg) {
self::$_aDefaultDbCfg = $defaultDbCfg;
}
/**
* Establishes a connection to the database server.
*
* @return object|resource|int|null Connection handler. Return value depends on
* used driver and is null in case of an error.
*/
public function connect() {
if ($this->Link_ID = $this->_getConnection($this->_aDbCfg['connection'])) {
return $this->Link_ID;
} else {
if ($this->Link_ID = $this->_connect()) {
$this->_setConnection($this->_aDbCfg['connection'], $this->Link_ID);
return $this->Link_ID;
}
}
return null;
}
/**
* Resource id/object of current connection
*
* @return mixed The resource id/object of current connection
*/
public function link_id() {
return $this->Link_ID;
}
/**
* Returns connection from connection cache
*
* @param mixed $data Connection data array
* @return mixed Either The connection (object, resource, integer) or null
*/
protected function _getConnection($data) {
$hash = md5($this->type . '-' . (is_array($data) ? implode('-', $data) : (string) $data));
return (isset(self::$_connectionCache[$hash])) ? self::$_connectionCache[$hash] : null;
}
/**
* Stores connection in connection cache
*
* @param mixed $data Connection data array
* @param mixed $connection The connection to store in cache
* @return void
*/
protected function _setConnection($data, $connection) {
$hash = md5($this->type . '-' . (is_array($data) ? implode('-', $data) : (string) $data));
self::$_connectionCache[$hash] = $connection;
}
/**
* Removes connection from cache
*
* @param mixed $connection The connection to remove in cache
* @return void
*/
protected function _removeConnection($connection) {
foreach (self::$_connectionCache as $hash => $res) {
if ($res == $connection) {
unset(self::$_connectionCache[$hash]);
return;
}
}
}
/**
* Returns the current cursor
*
* @return resource Current cursor
*/
public function query_id() {
return $this->Query_ID;
}
/**
* Connects to the database.
*
* @return object|resource|int|null Connection handler. Return value depends on
* used driver and is null in case of an error.
*/
abstract protected function _connect();
/**
* Executes the query.
*
* @param string $sQuery The query to execute.
* @param mixed Accepts additional unlimited parameter, where the parameter
* will be replaced against format definitions in query.
* @return resource|int|object|bool Depends on used database driver, false on error
*/
public function query($sQuery) {
// No empty queries, please, since PHP4 chokes on them
if ($sQuery == '') {
// The empty query string is passed on from the constructor, when calling
// the class without a query, e.g. in situations '$db = new DB_Sql_Subclass;'
return false;
}
$aArgs = func_get_args();
if (count($aArgs) > 1) {
array_shift($aArgs);
$sQuery = $this->_prepareQueryf($sQuery, $aArgs);
}
if (!$this->connect()) {
return false;
}
// new query, discard previous result
if ($this->Query_ID) {
$this->free();
}
$this->_debug('Debug: query = ' . $sQuery);
if ($this->_bProfile) {
$fStart = microtime(true);
}
$this->_query($sQuery);
if ($this->_bProfile) {
$fEnd = microtime(true);
$this->_addProfileData($fStart, $fEnd, $sQuery);
}
// Will return nada if it fails. That's fine.
return $this->Query_ID;
}
/**
* Executes the query.
*
* @param string $sQuery The query to execute
* @return void
*/
abstract protected function _query($sQuery);
/**
* Prepares a query with parameter for execution.
*
* @param string $sQuery
* @param array $aArgs Arguments array containing the query with formatting
* signs and the entries.
* Examples:
* <pre>
* $obj->_prepareQueryf('SELECT * FROM `%s` WHERE id = %d', 'tablename', 123);
* $obj->_prepareQueryf('SELECT * FROM `%s` WHERE id = %d AND user = %d', 'tablename', 123, 3);
* </pre>
* @return string
*/
public function _prepareQueryf($sQuery, array $aArgs) {
var_dump($sQuery);
print_r($aArgs);
if (count($aArgs) > 0) {
if (count(array_filter(array_keys($aArgs), 'is_string')) > 0) {
foreach ($aArgs as $key => $value) {
$param = ':' . $key;
$statement = $sQuery;
if (cSecurity::isInteger($value)) {
$statement = preg_replace('/' . $param . '/', cSecurity::toInteger($value), $statement);
$statement = preg_replace('/\'' . $param . '\'/', '\'' . cSecurity::toInteger($value) . '\'', $statement);
} else {
$param = cSecurity::toString($param);
$statement = preg_replace('/' . $param . '/', cSecurity::escapeString($value), $statement);
$statement = preg_replace('/\'' . $param . '\'/', '\'' . cSecurity::escapeString($value) . '\'', $statement);
$statement = preg_replace('/`' . $param . '`/', '`' . cSecurity::escapeString($value) . '`', $statement);
}
$sQuery = $statement;
}
} else {
$aArgs = array_map(array($this, 'escape'), $aArgs);
array_unshift($aArgs, $sQuery);
$sQuery = call_user_func_array('sprintf', $aArgs);
}
}
var_dump($sQuery);
return $sQuery;
}
/**
* Moves the result to the next record, if exists and returns the status of the movement
*
* @return int Flag about move status 1 on success or 0
*/
abstract public function next_record();
/**
* Moves the cursor (position inside current result sets).
*
* @param int $pos The positon to move to inside the current result set
* @return void
*/
abstract public function seek($pos = 0);
/**
* Locks the desired table.
*
* @param string $table The table to lock
* @param string $mode The lock mode. Only mode 'write' is supported!
* @return int Status of lock success (1 or 0)
*/
abstract public function lock($table, $mode = 'write');
/**
* Frees a previous locked result.
*
* @return int Status of lock success (1 or 0)
*/
abstract public function unlock();
/**
* Parses te table structure and generates a metadata from it.
*
* @param string $table The table to get metadata or empty string to retrieve
* metadata of all tables
* @param bool $full Flag to load full metada
* @return array Depends on used database and on parameter $full
*/
public function metadata($table = '', $full = false) {
$key = (string) $this->Database . '_' . $table . '_' . (($full) ? '1' : '0');
if (!isset(self::$_metaCache[$key])) {
// get meta data
self::$_metaCache[$key] = $this->_metaData($table, $full);
}
return self::$_metaCache[$key];
}
/**
* Parses te table structure and generates a metadata from it.
*
* @param string $table The table to get metadata or empty string to retrieve
* metadata of all tables
* @param bool $full Flag to load full metada
* @return array Depends on used database and on parameter $full
*/
abstract protected function _metaData($table = '', $full = false);
/**
* Returns number of affected rows from last executed query (update, delete)
*
* @return int Number of affected rows
*/
abstract public function affected_rows();
/**
* Returns the number of rows from last executed select query.
*
* @return int The number of rows from last select query result
*/
abstract public function num_rows();
/**
* Returns the number of fields (colums) from current record set
*
* @return int Number of fields
*/
abstract public function num_fields();
/**
* Returns the number of rows (record set).
* Shortcut for function num_rows().
*
* @return int Number of rows
*/
public function nf() {
return $this->num_rows();
}
/**
* Displays the number of rows (record set) using print.
*
* @return void
*/
public function np() {
print $this->num_rows();
}
/**
* Returns the desired field value from current record set.
*
* @param mixed The field name or index position
* @param mixed The default value to return
* @return mixed The value of field
*/
public function f($Name, $default = null) {
return (isset($this->Record[$Name])) ? $this->Record[$Name] : $default;
}
/**
* Displays the desired field value from current record set using print.
*
* @param mixed The field name or index position
* @return void
*/
public function p($Name) {
if (isset($this->Record[$Name])) {
print $this->Record[$Name];
}
}
/**
* Returns current recordset as a assoziative and/or indexed array.
*
* @param string $fetchMode One of DB_SQL_Abstract::FETCH_* constants
* @return array
*/
public function toArray($fetchMode = DB_SQL_Abstract::FETCH_ASSOC) {
switch ($fetchMode) {
case self::FETCH_NUMERIC:
case self::FETCH_ASSOC:
case self::FETCH_BOTH:
// donut
break;
default:
$fetchMode = DB_SQL_Abstract::FETCH_ASSOC;
break;
}
$aResult = array();
if (is_array($this->Record)) {
foreach ($this->Record as $key => $value) {
if ($fetchMode == self::FETCH_ASSOC && !is_numeric($key)) {
$aResult[$key] = $value;
} elseif ($fetchMode == self::FETCH_NUMERIC && is_numeric($key)) {
$aResult[$key] = $value;
} else {
$aResult[$key] = $value;
}
}
}
return $aResult;
}
/**
* Returns current recordset as a object
*
* @return stdClass
*/
public function toObject() {
return (object) $this->toArray(self::FETCH_ASSOC);
}
/**
* Returns the next id (sequence number).
*
* @param string $seq_name The sequence name to get the next id from
* @return int The next id or 0 on error
*/
abstract public function nextid($seq_name);
/**
* Wrapper method for disconnect()
* @see DB_Sql_Abstract::disconnect()
*/
public function close() {
$this->disconnect();
}
/**
* Closes the connection and frees the query id.
*
* @return void
*/
abstract public function disconnect();
/**
* Error handling
*
* Error handler function, delegates passed message to the function haltmsg() if propery
* $this->Halt_On_Error is not set to self::HALT_REPORT.
*
* Terminates further script execution if $this->Halt_On_Error is set to self::HALT_YES
*
* @param string $sMsg The message to use for error handling
* @return void
*/
public function halt($sMsg) {
if ($this->Halt_On_Error == self::HALT_REPORT) {
$this->haltmsg($this->_sHaltMsgPrefix . $sMsg);
}
if ($this->Halt_On_Error == self::HALT_YES) {
die('Session halted.');
}
}
/**
* Logs passed message, basically the last db error to the error log.
* Concatenates a detailed error message and invokey PHP's error_log() method.
*
* @param string $sMsg
* @return void
*/
public function haltmsg($sMsg) {
$sName = 'ConLite DB';
if (!$this->Error) {
$this->Error = $this->_getErrorMessage();
}
if (!$this->Errno) {
$this->Errno = $this->_getErrorNumber();
}
$sMsg = sprintf("%s error: %s (%s) - info: %s\n", $sName, $this->Errno, $this->Error, $sMsg);
error_log($sMsg);
}
/**
* Returns names of existing tables.
*
* @return array|null Indexed array containing assoziative table data as
* follows or null:
* - $info[$i]['table_name']
* - $info[$i]['tablespace_name']
* - $info[$i]['database']
*/
public function table_names() {
if (!$this->connect()) {
return null;
}
if (method_exists($this, '_tableNames')) {
return $this->_tableNames();
}
return null;
}
/**
* Implemetation for table_names() in child classes
* @see DB_Sql_Abstract::table_names()
*/
abstract protected function _tableNames();
/**
* Escape string for using in SQL-Statement.
*
* @param string $sString The string to escape
* @return string Escaped string
*/
abstract public function escape($sString);
/**
* Returns information about DB server. The return value depends allways on
* used DBMS.
*
* @return array|null Assoziative array as follows or null:
* - $arr['description'] (string) Optional, server description
* - $arr['version'] (string) Optional, server version
*/
public function server_info() {
if (!$this->connect()) {
return null;
}
if (method_exists($this, '_serverInfo')) {
return $this->_serverInfo();
}
return null;
}
/**
* Implemetation for server_info() in child classes.
* @see DB_Sql_Abstract::server_info()
*/
abstract protected function _serverInfo();
/**
* Returns error message of last occured error.
*
* @return string
*/
public function getErrorMessage() {
return $this->Error;
}
/**
* Returns error message of last occured error by using databases interface.
*
* @return string
*/
abstract protected function _getErrorMessage();
/**
* Returns error code of last occured error by using databases interface.
*
* @return int
*/
public function getErrorNumber() {
return $this->Errno;
}
/**
* Returns error code of last occured error by using databases interface.
*
* @return int
*/
abstract protected function _getErrorNumber();
/**
* Adds a entry to the profile data.
*
* @param float $fStartTime
* @param float $fEndTime
* @param string $sQuery
* @return void
*/
protected static function _addProfileData($fStartTime, $fEndTime, $sQuery) {
self::$_aProfileData[] = array(
'time' => $fEndTime - $fStartTime,
'query' => $sQuery
/* ,
'ErrNo' => static::_getErrorNumber(),
'ErrMess' => static::_getErrorMessage() */
);
}
/**
* Returns collected profile data.
*
* @return array Profile data array like:
* - $arr[$i]['time'] (float) Elapsed time to execute the query
* - $arr[$i]['query'] (string) The query itself
*/
public static function getProfileData() {
return self::$_aProfileData;
}
/**
* Displays the passed message, if debug is enabled (see $this->Debug)
*
* @param string $sMsg The message to display
* @return void
*/
protected function _debug($sMsg) {
if ($this->Debug) {
printf("<pre>" . $sMsg . "</pre>\n");
}
}
/**
* returns info about db-server
*
* return string
*/
abstract public function getServerInfo();
/**
* returns client encoding
*
* return string
*/
abstract public function getClientEncoding();
/**
* returns client info
*
* return string
*/
abstract public function getClientInfo();
}