2009-03-25 04:18:09 +01:00
< ? php
2007-07-19 12:40:28 +02:00
/**
* Abstract database connectivity class .
* Sub - classes of this implement the actual database connection libraries
2012-04-12 08:02:46 +02:00
* @ package framework
2008-02-25 03:10:37 +01:00
* @ subpackage model
2007-07-19 12:40:28 +02:00
*/
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
abstract class SS_Database {
2007-07-19 12:40:28 +02:00
/**
* Connection object to the database .
* @ param resource
*/
static $globalConn ;
2011-08-22 18:55:37 +02:00
/**
* @ var boolean Check tables when running / dev / build , and repair them if necessary .
* In case of large databases or more fine - grained control on how to handle
* data corruption in tables , you can disable this behaviour and handle it
* outside of this class , e . g . through a nightly system task with extended logging capabilities .
*/
static $check_and_repair_on_build = true ;
2007-07-19 12:40:28 +02:00
/**
* If this is false , then information about database operations
* will be displayed , eg creation of tables .
* @ param boolean
*/
2009-10-26 23:03:29 +01:00
protected $supressOutput = false ;
2007-07-19 12:40:28 +02:00
/**
* Execute the given SQL query .
* This abstract function must be defined by subclasses as part of the actual implementation .
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
* It should return a subclass of SS_Query as the result .
2007-07-19 12:40:28 +02:00
* @ param string $sql The SQL query to execute
* @ param int $errorLevel The level of error reporting to enable for the query
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
* @ return SS_Query
2007-07-19 12:40:28 +02:00
*/
abstract function query ( $sql , $errorLevel = E_USER_ERROR );
/**
* Get the autogenerated ID from the previous INSERT query .
* @ return int
*/
2007-09-14 03:35:54 +02:00
abstract function getGeneratedID ( $table );
2007-07-19 12:40:28 +02:00
/**
* Check if the connection to the database is active .
* @ return boolean
*/
abstract function isActive ();
/**
* Create the database and connect to it . This can be called if the
* initial database connection is not successful because the database
* does not exist .
2008-02-25 03:10:37 +01:00
*
* It takes no parameters , and should create the database from the information
* specified in the constructor .
*
2007-07-19 12:40:28 +02:00
* @ return boolean Returns true if successful
*/
2008-02-25 03:10:37 +01:00
abstract function createDatabase ();
2007-09-14 03:28:16 +02:00
/**
* Build the connection string from input
* @ param array $parameters The connection details
* @ return string $connect The connection string
**/
2008-02-25 03:10:37 +01:00
abstract function getConnect ( $parameters );
2007-07-19 12:40:28 +02:00
/**
* Create a new table .
2009-05-21 07:08:11 +02:00
* @ param $tableName The name of the table
* @ param $fields A map of field names to field types
* @ param $indexes A map of indexes
* @ param $options An map of additional options . The available keys are as follows :
* - 'MSSQLDatabase' / 'MySQLDatabase' / 'PostgreSQLDatabase' - database - specific options such as " engine " for MySQL .
* - 'temporary' - If true , then a temporary table will be created
* @ return The table name generated . This may be different from the table name , for example with temporary tables .
2007-07-19 12:40:28 +02:00
*/
2009-10-08 03:20:30 +02:00
abstract function createTable ( $table , $fields = null , $indexes = null , $options = null , $advancedOptions = null );
2007-08-15 04:50:39 +02:00
/**
* Alter a table ' s schema .
*/
2009-10-08 03:20:30 +02:00
abstract function alterTable ( $table , $newFields = null , $newIndexes = null , $alteredFields = null , $alteredIndexes = null , $alteredOptions = null , $advancedOptions = null );
2007-07-19 12:40:28 +02:00
/**
* Rename a table .
* @ param string $oldTableName The old table name .
* @ param string $newTableName The new table name .
*/
abstract function renameTable ( $oldTableName , $newTableName );
/**
* Create a new field on a table .
* @ param string $table Name of the table .
* @ param string $field Name of the field to add .
* @ param string $spec The field specification , eg 'INTEGER NOT NULL'
*/
abstract function createField ( $table , $field , $spec );
2008-11-10 00:56:24 +01:00
/**
* Change the database column name of the given field .
*
* @ param string $tableName The name of the tbale the field is in .
* @ param string $oldName The name of the field to change .
* @ param string $newName The new name of the field
*/
abstract function renameField ( $tableName , $oldName , $newName );
2007-07-19 12:40:28 +02:00
/**
* Get a list of all the fields for the given table .
* Returns a map of field name => field spec .
* @ param string $table The table name .
* @ return array
*/
protected abstract function fieldList ( $table );
/**
* Returns a list of all tables in the database .
2009-10-23 04:15:37 +02:00
* Keys are table names in lower case , values are table names in case that
* database expects .
2007-07-19 12:40:28 +02:00
* @ return array
*/
2011-03-11 02:22:59 +01:00
/**
*
* This is a stub function . Postgres caches the fieldlist results .
*
* @ param string $tableName
*
* @ return boolean
*/
function clearCachedFieldlist ( $tableName = false ){
return true ;
}
2007-07-19 12:40:28 +02:00
protected abstract function tableList ();
2008-11-24 00:20:02 +01:00
/**
* Returns true if the given table exists in the database
*/
abstract function hasTable ( $tableName );
2012-08-29 05:08:26 +02:00
2008-11-24 00:20:02 +01:00
/**
* Returns the enum values available on the given field
*/
abstract function enumValuesForField ( $tableName , $fieldName );
2011-06-10 06:36:13 +02:00
/**
* Returns an escaped string .
*
* @ param string
* @ return string - escaped string
*/
abstract function addslashes ( $val );
2007-07-19 12:40:28 +02:00
/**
* The table list , generated by the tableList () function .
* Used by the requireTable () function .
* @ var array
*/
protected $tableList ;
/**
* The field list , generated by the fieldList () function .
* An array of maps of field name => field spec , indexed
* by table name .
* @ var array
*/
protected $fieldList ;
/**
* The index list for each table , generated by the indexList () function .
* An map from table name to an array of index names .
* @ var array
*/
protected $indexList ;
2007-08-15 04:50:39 +02:00
/**
* Large array structure that represents a schema update transaction
*/
protected $schemaUpdateTransaction ;
2009-08-08 04:55:32 +02:00
2007-08-15 04:50:39 +02:00
/**
* Start a schema - updating transaction .
* All calls to requireTable / Field / Index will keep track of the changes requested , but not actually do anything .
* Once
*/
function beginSchemaUpdate () {
2009-05-07 06:30:54 +02:00
$this -> tableList = array ();
$tables = $this -> tableList ();
2009-10-23 04:15:37 +02:00
foreach ( $tables as $table ) $this -> tableList [ strtolower ( $table )] = $table ;
2009-05-07 06:30:54 +02:00
2007-08-15 04:50:39 +02:00
$this -> indexList = null ;
$this -> fieldList = null ;
$this -> schemaUpdateTransaction = array ();
}
2010-04-13 01:43:10 +02:00
/**
* Completes a schema - updated transaction , executing all the schema chagnes .
*/
2007-08-15 04:50:39 +02:00
function endSchemaUpdate () {
foreach ( $this -> schemaUpdateTransaction as $tableName => $changes ) {
switch ( $changes [ 'command' ]) {
case 'create' :
2009-10-08 03:20:30 +02:00
$this -> createTable ( $tableName , $changes [ 'newFields' ], $changes [ 'newIndexes' ], $changes [ 'options' ], @ $changes [ 'advancedOptions' ]);
2007-08-15 04:50:39 +02:00
break ;
case 'alter' :
$this -> alterTable ( $tableName , $changes [ 'newFields' ], $changes [ 'newIndexes' ],
2009-10-08 03:20:30 +02:00
$changes [ 'alteredFields' ], $changes [ 'alteredIndexes' ], $changes [ 'alteredOptions' ], @ $changes [ 'advancedOptions' ]);
2007-08-15 04:50:39 +02:00
break ;
}
}
$this -> schemaUpdateTransaction = null ;
}
2010-04-13 01:43:10 +02:00
/**
* Cancels the schema updates requested after a beginSchemaUpdate () call .
*/
function cancelSchemaUpdate () {
$this -> schemaUpdateTransaction = null ;
}
/**
* Returns true if schema modifications were requested after a beginSchemaUpdate () call .
*/
function doesSchemaNeedUpdating () {
2012-05-10 04:11:33 +02:00
return ( bool ) $this -> schemaUpdateTransaction ;
2010-04-13 01:43:10 +02:00
}
2012-05-10 04:11:33 +02:00
2007-08-15 04:50:39 +02:00
// Transactional schema altering functions - they don't do anyhting except for update schemaUpdateTransaction
2009-05-19 05:55:14 +02:00
/**
* @ param string $table
* @ param string $options
*/
2009-10-08 03:20:30 +02:00
function transCreateTable ( $table , $options = null , $advanced_options = null ) {
$this -> schemaUpdateTransaction [ $table ] = array ( 'command' => 'create' , 'newFields' => array (), 'newIndexes' => array (), 'options' => $options , 'advancedOptions' => $advanced_options );
2009-05-19 05:55:14 +02:00
}
/**
* @ param string $table
* @ param array $options
*/
2009-10-08 03:20:30 +02:00
function transAlterTable ( $table , $options , $advanced_options ) {
2009-05-19 05:55:14 +02:00
$this -> transInitTable ( $table );
$this -> schemaUpdateTransaction [ $table ][ 'alteredOptions' ] = $options ;
2009-10-08 03:20:30 +02:00
$this -> schemaUpdateTransaction [ $table ][ 'advancedOptions' ] = $advanced_options ;
2007-08-15 04:50:39 +02:00
}
2009-05-19 05:55:14 +02:00
2007-08-15 04:50:39 +02:00
function transCreateField ( $table , $field , $schema ) {
2007-08-16 08:33:41 +02:00
$this -> transInitTable ( $table );
2007-08-15 04:50:39 +02:00
$this -> schemaUpdateTransaction [ $table ][ 'newFields' ][ $field ] = $schema ;
}
function transCreateIndex ( $table , $index , $schema ) {
2007-08-21 05:40:09 +02:00
$this -> transInitTable ( $table );
2007-08-15 04:50:39 +02:00
$this -> schemaUpdateTransaction [ $table ][ 'newIndexes' ][ $index ] = $schema ;
}
function transAlterField ( $table , $field , $schema ) {
2007-08-21 05:40:09 +02:00
$this -> transInitTable ( $table );
2007-08-15 04:50:39 +02:00
$this -> schemaUpdateTransaction [ $table ][ 'alteredFields' ][ $field ] = $schema ;
}
function transAlterIndex ( $table , $index , $schema ) {
2007-08-21 05:40:09 +02:00
$this -> transInitTable ( $table );
2007-08-15 04:50:39 +02:00
$this -> schemaUpdateTransaction [ $table ][ 'alteredIndexes' ][ $index ] = $schema ;
}
2007-08-16 08:33:41 +02:00
/**
* Handler for the other transXXX methods - mark the given table as being altered
* if it doesn ' t already exist
*/
protected function transInitTable ( $table ) {
2007-08-17 06:44:44 +02:00
if ( ! isset ( $this -> schemaUpdateTransaction [ $table ])) {
$this -> schemaUpdateTransaction [ $table ] = array (
'command' => 'alter' ,
'newFields' => array (),
'newIndexes' => array (),
'alteredFields' => array (),
'alteredIndexes' => array (),
2009-05-19 05:55:14 +02:00
'alteredOptions' => ''
2007-08-17 06:44:44 +02:00
);
}
2007-08-16 08:33:41 +02:00
}
2007-08-15 04:50:39 +02:00
2007-07-19 12:40:28 +02:00
/**
* Generate the following table in the database , modifying whatever already exists
* as necessary .
2009-05-19 05:55:14 +02:00
* @ todo Change detection for CREATE TABLE $options other than " Engine "
*
2007-07-19 12:40:28 +02:00
* @ param string $table The name of the table
* @ param string $fieldSchema A list of the fields to create , in the same form as DataObject :: $db
2009-03-12 12:11:35 +01:00
* @ param string $indexSchema A list of indexes to create . See { @ link requireIndex ()}
2009-05-19 05:55:14 +02:00
* @ param array $options
2007-07-19 12:40:28 +02:00
*/
2009-10-08 03:20:30 +02:00
function requireTable ( $table , $fieldSchema = null , $indexSchema = null , $hasAutoIncPK = true , $options = Array (), $extensions = false ) {
2007-07-19 12:40:28 +02:00
if ( ! isset ( $this -> tableList [ strtolower ( $table )])) {
2009-10-08 03:20:30 +02:00
$this -> transCreateTable ( $table , $options , $extensions );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Table $table : created " , " created " );
2007-07-19 12:40:28 +02:00
} else {
2011-08-22 18:55:37 +02:00
if ( self :: $check_and_repair_on_build ) $this -> checkAndRepairTable ( $table , $options );
2009-05-19 05:55:14 +02:00
// Check if options changed
2009-10-08 03:20:30 +02:00
$tableOptionsChanged = false ;
if ( isset ( $options [ get_class ( $this )]) || true ) {
if ( isset ( $options [ get_class ( $this )])) {
if ( preg_match ( '/ENGINE=([^\s]*)/' , $options [ get_class ( $this )], $alteredEngineMatches )) {
$alteredEngine = $alteredEngineMatches [ 1 ];
$tableStatus = DB :: query ( sprintf (
2010-10-15 01:52:16 +02:00
'SHOW TABLE STATUS LIKE \'%s\'' ,
2009-10-08 03:20:30 +02:00
$table
)) -> first ();
$tableOptionsChanged = ( $tableStatus [ 'Engine' ] != $alteredEngine );
}
2009-05-19 05:55:14 +02:00
}
}
2009-10-08 03:20:30 +02:00
if ( $tableOptionsChanged || ( $extensions && DB :: getConn () -> supportsExtensions ()))
$this -> transAlterTable ( $table , $options , $extensions );
2007-07-19 12:40:28 +02:00
}
2009-02-02 00:49:53 +01:00
2009-02-13 03:37:58 +01:00
//DB ABSTRACTION: we need to convert this to a db-specific version:
2009-03-11 22:48:59 +01:00
$this -> requireField ( $table , 'ID' , DB :: getConn () -> IdColumn ( false , $hasAutoIncPK ));
2009-02-13 03:37:58 +01:00
2007-07-19 12:40:28 +02:00
// Create custom fields
if ( $fieldSchema ) {
foreach ( $fieldSchema as $fieldName => $fieldSpec ) {
2009-09-29 23:59:39 +02:00
//Is this an array field?
$arrayValue = '' ;
if ( strpos ( $fieldSpec , '[' ) !== false ){
//If so, remove it and store that info separately
$pos = strpos ( $fieldSpec , '[' );
$arrayValue = substr ( $fieldSpec , $pos );
$fieldSpec = substr ( $fieldSpec , 0 , $pos );
}
2010-10-04 06:46:41 +02:00
$fieldObj = Object :: create_from_string ( $fieldSpec , $fieldName );
2009-09-29 23:59:39 +02:00
$fieldObj -> arrayValue = $arrayValue ;
2007-07-19 12:40:28 +02:00
$fieldObj -> setTable ( $table );
$fieldObj -> requireField ();
}
2009-05-19 05:55:14 +02:00
}
2009-10-08 03:20:30 +02:00
2007-07-19 12:40:28 +02:00
// Create custom indexes
if ( $indexSchema ) {
foreach ( $indexSchema as $indexName => $indexDetails ) {
$this -> requireIndex ( $table , $indexName , $indexDetails );
}
2007-09-15 02:03:12 +02:00
}
2007-07-19 12:40:28 +02:00
}
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
* If the given table exists , move it out of the way by renaming it to _obsolete_ ( tablename ) .
* @ param string $table The table name .
*/
function dontRequireTable ( $table ) {
if ( isset ( $this -> tableList [ strtolower ( $table )])) {
2008-04-05 03:39:13 +02:00
$suffix = '' ;
while ( isset ( $this -> tableList [ strtolower ( " _obsolete_ { $table } $suffix " )])) {
2007-07-19 12:40:28 +02:00
$suffix = $suffix ? ( $suffix + 1 ) : 2 ;
2007-09-15 02:03:12 +02:00
}
2007-07-19 12:40:28 +02:00
$this -> renameTable ( $table , " _obsolete_ { $table } $suffix " );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Table $table : renamed to _obsolete_ { $table } $suffix " , " obsolete " );
2007-07-19 12:40:28 +02:00
}
}
/**
* Generate the given index in the database , modifying whatever already exists as necessary .
2009-03-12 12:11:35 +01:00
*
* The keys of the array are the names of the index .
* The values of the array can be one of :
* - true : Create a single column index on the field named the same as the index .
* - array ( 'type' => 'index|unique|fulltext' , 'value' => 'FieldA, FieldB' ) : This gives you full
* control over the index .
*
2007-07-19 12:40:28 +02:00
* @ param string $table The table name .
* @ param string $index The index name .
* @ param string | boolean $spec The specification of the index . See requireTable () for more information .
*/
function requireIndex ( $table , $index , $spec ) {
2007-08-17 06:44:44 +02:00
$newTable = false ;
2008-12-17 01:40:24 +01:00
//DB Abstraction: remove this ===true option as a possibility?
2007-07-19 12:40:28 +02:00
if ( $spec === true ) {
2012-06-02 15:38:12 +02:00
$spec = " ( \" $index\ " ) " ;
2007-07-19 12:40:28 +02:00
}
2008-12-17 01:40:24 +01:00
//Indexes specified as arrays cannot be checked with this line: (it flattens out the array)
2012-02-27 22:14:02 +01:00
if ( ! is_array ( $spec )) {
$spec = preg_replace ( '/\s*,\s*/' , ',' , $spec );
}
2007-07-19 12:40:28 +02:00
2007-08-15 04:50:39 +02:00
if ( ! isset ( $this -> tableList [ strtolower ( $table )])) $newTable = true ;
2009-03-10 03:02:14 +01:00
if ( ! $newTable && ! isset ( $this -> indexList [ $table ])) {
2007-07-19 12:40:28 +02:00
$this -> indexList [ $table ] = $this -> indexList ( $table );
}
2009-03-17 23:22:46 +01:00
2009-03-04 22:54:33 +01:00
//Fix up the index for database purposes
$index = DB :: getConn () -> getDbSqlDefinition ( $table , $index , null , true );
2009-03-17 23:22:46 +01:00
//Fix the key for database purposes
$index_alt = DB :: getConn () -> modifyIndex ( $index , $spec );
2009-03-10 03:02:14 +01:00
if ( ! $newTable ) {
2009-03-23 02:00:33 +01:00
if ( isset ( $this -> indexList [ $table ][ $index_alt ])) {
if ( is_array ( $this -> indexList [ $table ][ $index_alt ])) {
$array_spec = $this -> indexList [ $table ][ $index_alt ][ 'spec' ];
} else {
$array_spec = $this -> indexList [ $table ][ $index_alt ];
}
2009-03-10 03:02:14 +01:00
}
}
2009-03-17 06:02:40 +01:00
if ( $newTable || ! isset ( $this -> indexList [ $table ][ $index_alt ])) {
2007-08-15 04:50:39 +02:00
$this -> transCreateIndex ( $table , $index , $spec );
2010-11-19 00:29:30 +01:00
$this -> alterationMessage ( " Index $table . $index : created as " . DB :: getConn () -> convertIndexSpec ( $spec ), " created " );
2009-03-04 22:54:33 +01:00
} else if ( $array_spec != DB :: getConn () -> convertIndexSpec ( $spec )) {
2007-08-15 04:50:39 +02:00
$this -> transAlterIndex ( $table , $index , $spec );
2008-12-17 01:40:24 +01:00
$spec_msg = DB :: getConn () -> convertIndexSpec ( $spec );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Index $table . $index : changed to $spec_msg <i style= \" color: #AAA \" >(from { $array_spec } )</i> " , " changed " );
2007-07-19 12:40:28 +02:00
}
}
2012-08-29 05:08:26 +02:00
/**
* Return true if the table exists and already has a the field specified
* @ param string $tableName - The table to check
* @ param string $fieldName - The field to check
* @ return bool - True if the table exists and the field exists on the table
*/
function hasField ( $tableName , $fieldName ) {
if ( ! $this -> hasTable ( $tableName )) return false ;
$fields = $this -> fieldList ( $tableName );
return array_key_exists ( $fieldName , $fields );
}
2007-07-19 12:40:28 +02:00
/**
* Generate the given field on the table , modifying whatever already exists as necessary .
* @ param string $table The table name .
* @ param string $field The field name .
2009-01-11 23:20:51 +01:00
* @ param array | string $spec The field specification . If passed in array syntax , the specific database
* driver takes care of the ALTER TABLE syntax . If passed as a string , its assumed to
* be prepared as a direct SQL framgment ready for insertion into ALTER TABLE . In this case you ' ll
* need to take care of database abstraction in your DBField subclass .
2007-07-19 12:40:28 +02:00
*/
function requireField ( $table , $field , $spec ) {
2009-02-17 03:49:39 +01:00
//TODO: this is starting to get extremely fragmented.
//There are two different versions of $spec floating around, and their content changes depending
//on how they are structured. This needs to be tidied up.
2009-03-25 04:17:19 +01:00
$fieldValue = null ;
2007-08-17 06:44:44 +02:00
$newTable = false ;
2009-10-21 04:15:29 +02:00
2007-08-15 04:50:39 +02:00
Profiler :: mark ( 'requireField' );
2008-11-23 02:01:03 +01:00
2009-01-11 23:20:51 +01:00
// backwards compatibility patch for pre 2.4 requireField() calls
2009-02-17 03:49:39 +01:00
$spec_orig = $spec ;
2009-10-21 04:15:29 +02:00
2009-01-11 23:20:51 +01:00
if ( ! is_string ( $spec )) {
$spec [ 'parts' ][ 'name' ] = $field ;
2009-02-17 03:49:39 +01:00
$spec_orig [ 'parts' ][ 'name' ] = $field ;
2009-01-11 23:20:51 +01:00
//Convert the $spec array into a database-specific string
2009-02-17 03:49:39 +01:00
$spec = DB :: getConn () -> $spec [ 'type' ]( $spec [ 'parts' ], true );
2009-01-11 23:20:51 +01:00
}
2009-02-17 04:54:39 +01:00
2007-07-19 12:40:28 +02:00
// Collations didn't come in until MySQL 4.1. Anything earlier will throw a syntax error if you try and use
// collations.
2009-03-12 03:43:10 +01:00
// TODO: move this to the MySQLDatabase file, or drop it altogether?
2007-07-19 12:40:28 +02:00
if ( ! $this -> supportsCollations ()) {
2012-02-27 22:14:02 +01:00
$spec = preg_replace ( '/ *character set [^ ]+( collate [^ ]+)?( |$)/' , '\\2' , $spec );
2007-07-19 12:40:28 +02:00
}
2009-03-12 03:43:10 +01:00
2007-08-15 04:50:39 +02:00
if ( ! isset ( $this -> tableList [ strtolower ( $table )])) $newTable = true ;
2007-07-19 12:40:28 +02:00
2007-08-15 04:50:39 +02:00
if ( ! $newTable && ! isset ( $this -> fieldList [ $table ])) {
2007-07-19 12:40:28 +02:00
$this -> fieldList [ $table ] = $this -> fieldList ( $table );
}
2009-10-21 04:15:29 +02:00
if ( is_array ( $spec )) {
$specValue = DB :: getConn () -> $spec_orig [ 'type' ]( $spec_orig [ 'parts' ]);
} else {
$specValue = $spec ;
}
2009-03-03 22:45:54 +01:00
// We need to get db-specific versions of the ID column:
2009-03-12 03:43:10 +01:00
if ( $spec_orig == DB :: getConn () -> IdColumn () || $spec_orig == DB :: getConn () -> IdColumn ( true ))
2009-03-03 22:45:54 +01:00
$specValue = DB :: getConn () -> IdColumn ( true );
2009-03-12 03:43:10 +01:00
2009-03-10 03:02:14 +01:00
if ( ! $newTable ) {
2009-03-25 04:17:19 +01:00
if ( isset ( $this -> fieldList [ $table ][ $field ])) {
if ( is_array ( $this -> fieldList [ $table ][ $field ])) {
$fieldValue = $this -> fieldList [ $table ][ $field ][ 'data_type' ];
} else {
$fieldValue = $this -> fieldList [ $table ][ $field ];
}
2009-03-10 03:02:14 +01:00
}
}
2009-03-12 03:43:10 +01:00
2009-03-03 22:45:54 +01:00
// Get the version of the field as we would create it. This is used for comparison purposes to see if the
// existing field is different to what we now want
if ( is_array ( $spec_orig )) {
2009-02-17 03:49:39 +01:00
$spec_orig = DB :: getConn () -> $spec_orig [ 'type' ]( $spec_orig [ 'parts' ]);
2009-02-17 04:54:39 +01:00
}
2009-02-17 03:49:39 +01:00
if ( $newTable || $fieldValue == '' ) {
2007-08-15 04:50:39 +02:00
Profiler :: mark ( 'createField' );
2009-02-17 03:49:39 +01:00
$this -> transCreateField ( $table , $field , $spec_orig );
2007-08-15 04:50:39 +02:00
Profiler :: unmark ( 'createField' );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Field $table . $field : created as $spec_orig " , " created " );
2009-02-17 03:49:39 +01:00
} else if ( $fieldValue != $specValue ) {
2010-04-13 03:49:58 +02:00
// If enums/sets are being modified, then we need to fix existing data in the table.
2008-02-25 03:10:37 +01:00
// Update any records where the enum is set to a legacy value to be set to the default.
// One hard-coded exception is SiteTree - the default for this is Page.
2010-04-13 03:49:58 +02:00
foreach ( array ( 'enum' , 'set' ) as $enumtype ) {
if ( preg_match ( " /^ $enumtype /i " , $specValue )) {
$newStr = preg_replace ( " /(^ $enumtype\s * \ (')|(' $\ ).*)/i " , " " , $spec_orig );
$new = preg_split ( " /' \ s*, \ s*'/ " , $newStr );
2008-12-01 06:03:34 +01:00
2010-04-13 03:49:58 +02:00
$oldStr = preg_replace ( " /(^ $enumtype\s * \ (')|(' $\ ).*)/i " , " " , $fieldValue );
$old = preg_split ( " /' \ s*, \ s*'/ " , $newStr );
2008-12-01 06:03:34 +01:00
2010-04-13 03:49:58 +02:00
$holder = array ();
foreach ( $old as $check ) {
if ( ! in_array ( $check , $new )) {
$holder [] = $check ;
}
2008-02-25 03:10:37 +01:00
}
2010-04-13 03:49:58 +02:00
if ( count ( $holder )) {
$default = explode ( 'default ' , $spec_orig );
$default = $default [ 1 ];
if ( $default == " 'SiteTree' " ) $default = " 'Page' " ;
$query = " UPDATE \" $table\ " SET $field = $default WHERE $field IN ( " ;
for ( $i = 0 ; $i + 1 < count ( $holder ); $i ++ ) {
$query .= " ' { $holder [ $i ] } ', " ;
}
$query .= " ' { $holder [ $i ] } ') " ;
DB :: query ( $query );
$amount = DB :: affectedRows ();
$this -> alterationMessage ( " Changed $amount rows to default value of field $field (Value: $default ) " );
2008-02-25 03:10:37 +01:00
}
}
}
2007-08-15 04:50:39 +02:00
Profiler :: mark ( 'alterField' );
2009-02-17 03:49:39 +01:00
$this -> transAlterField ( $table , $field , $spec_orig );
2007-08-15 04:50:39 +02:00
Profiler :: unmark ( 'alterField' );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Field $table . $field : changed to $specValue <i style= \" color: #AAA \" >(from { $fieldValue } )</i> " , " changed " );
2007-07-19 12:40:28 +02:00
}
2007-08-15 04:50:39 +02:00
Profiler :: unmark ( 'requireField' );
2007-07-19 12:40:28 +02:00
}
2008-11-10 00:56:24 +01:00
/**
* If the given field exists , move it out of the way by renaming it to _obsolete_ ( fieldname ) .
*
* @ param string $table
* @ param string $fieldName
*/
function dontRequireField ( $table , $fieldName ) {
$fieldList = $this -> fieldList ( $table );
if ( array_key_exists ( $fieldName , $fieldList )) {
$suffix = '' ;
while ( isset ( $fieldList [ strtolower ( " _obsolete_ { $fieldName } $suffix " )])) {
$suffix = $suffix ? ( $suffix + 1 ) : 2 ;
}
$this -> renameField ( $table , $fieldName , " _obsolete_ { $fieldName } $suffix " );
2009-10-26 23:03:29 +01:00
$this -> alterationMessage ( " Field $table . $fieldName : renamed to $table ._obsolete_ { $fieldName } $suffix " , " obsolete " );
2008-11-10 00:56:24 +01:00
}
}
2007-07-19 12:40:28 +02:00
/**
* Execute a complex manipulation on the database .
* A manipulation is an array of insert / or update sequences . The keys of the array are table names ,
* and the values are map containing 'command' and 'fields' . Command should be 'insert' or 'update' ,
* and fields should be a map of field names to field values , including quotes . The field value can
* also be a SQL function or similar .
* @ param array $manipulation
*/
function manipulate ( $manipulation ) {
2009-03-25 23:26:03 +01:00
if ( $manipulation ) foreach ( $manipulation as $table => $writeInfo ) {
2009-03-11 22:48:59 +01:00
2007-07-19 12:40:28 +02:00
if ( isset ( $writeInfo [ 'fields' ]) && $writeInfo [ 'fields' ]) {
2008-11-24 00:20:02 +01:00
$fieldList = $columnList = $valueList = array ();
2007-07-19 12:40:28 +02:00
foreach ( $writeInfo [ 'fields' ] as $fieldName => $fieldVal ) {
2008-11-23 01:31:06 +01:00
$fieldList [] = " \" $fieldName\ " = $fieldVal " ;
2008-11-24 00:20:02 +01:00
$columnList [] = " \" $fieldName\ " " ;
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
// Empty strings inserted as null in INSERTs. Replacement of SS_Database::replace_with_null().
2008-11-24 00:20:02 +01:00
if ( $fieldVal === " '' " ) $valueList [] = " null " ;
else $valueList [] = $fieldVal ;
2007-07-19 12:40:28 +02:00
}
if ( ! isset ( $writeInfo [ 'where' ]) && isset ( $writeInfo [ 'id' ])) {
2008-11-24 00:20:02 +01:00
$writeInfo [ 'where' ] = " \" ID \" = " . ( int ) $writeInfo [ 'id' ];
2007-07-19 12:40:28 +02:00
}
switch ( $writeInfo [ 'command' ]) {
case " update " :
2008-11-24 20:28:46 +01:00
// Test to see if this update query shouldn't, in fact, be an insert
if ( $this -> query ( " SELECT \" ID \" FROM \" $table\ " WHERE $writeInfo [ where ] " )->value()) {
$fieldList = implode ( " , " , $fieldList );
2009-04-27 03:34:23 +02:00
$sql = " UPDATE \" $table\ " SET $fieldList where $writeInfo [ where ] " ;
2008-11-24 20:28:46 +01:00
$this -> query ( $sql );
break ;
}
2008-11-24 00:20:02 +01:00
2008-11-24 20:28:46 +01:00
// ...if not, we'll skip on to the insert code
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
case " insert " :
if ( ! isset ( $writeInfo [ 'fields' ][ 'ID' ]) && isset ( $writeInfo [ 'id' ])) {
2008-11-24 10:31:14 +01:00
$columnList [] = " \" ID \" " ;
2008-11-24 00:20:02 +01:00
$valueList [] = ( int ) $writeInfo [ 'id' ];
2007-07-19 12:40:28 +02:00
}
2009-03-11 22:48:59 +01:00
2008-11-24 00:20:02 +01:00
$columnList = implode ( " , " , $columnList );
$valueList = implode ( " , " , $valueList );
2009-04-27 03:34:23 +02:00
$sql = " INSERT INTO \" $table\ " ( $columnList ) VALUES ( $valueList ) " ;
2007-07-19 12:40:28 +02:00
$this -> query ( $sql );
break ;
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
default :
$sql = null ;
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
user_error ( " SS_Database::manipulate() Can't recognise command ' $writeInfo[command] ' " , E_USER_ERROR );
2007-07-19 12:40:28 +02:00
}
}
}
}
2007-10-02 06:46:33 +02:00
2008-02-25 03:10:37 +01:00
/** Replaces " \ ' \ ' " with " null " , recursively walks through the given array .
2007-10-02 06:46:33 +02:00
* @ param string $array Array where the replacement should happen
*/
static function replace_with_null ( & $array ) {
2012-02-27 22:14:02 +01:00
$array = preg_replace ( '/= *\'\'/' , '= null' , $array );
2007-10-02 06:46:33 +02:00
if ( is_array ( $array )) {
foreach ( $array as $key => $value ) {
if ( is_array ( $value )) {
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
array_walk ( $array , array ( SS_Database , 'replace_with_null' ));
2007-10-02 06:46:33 +02:00
}
}
}
return $array ;
}
2007-07-19 12:40:28 +02:00
2007-09-15 02:03:12 +02:00
/**
2007-07-19 12:40:28 +02:00
* Error handler for database errors .
* All database errors will call this function to report the error . It isn ' t a static function ;
* it will be called on the object itself and as such can be overridden in a subclass .
* @ todo hook this into a more well - structured error handling system .
* @ param string $msg The error message .
* @ param int $errorLevel The level of the error to throw .
*/
function databaseError ( $msg , $errorLevel = E_USER_ERROR ) {
2008-09-16 20:12:07 +02:00
user_error ( $msg , $errorLevel );
2007-07-19 12:40:28 +02:00
}
/**
* Enable supression of database messages .
*/
function quiet () {
2009-10-26 23:03:29 +01:00
$this -> supressOutput = true ;
2007-07-19 12:40:28 +02:00
}
2009-10-26 23:03:29 +01:00
/**
2010-12-11 06:43:08 +01:00
* Show a message about database alteration
*
* @ param string message to display
* @ param string type one of [ created | changed | repaired | obsolete | deleted | error ]
2009-10-26 23:03:29 +01:00
*/
function alterationMessage ( $message , $type = " " ){
if ( ! $this -> supressOutput ) {
2009-12-16 06:42:21 +01:00
if ( Director :: is_cli ()) {
switch ( $type ){
case " created " :
case " changed " :
case " repaired " :
$sign = " + " ;
break ;
case " obsolete " :
case " deleted " :
$sign = '-' ;
break ;
case " error " :
$sign = " ! " ;
break ;
default :
$sign = " " ;
}
$message = strip_tags ( $message );
echo " $sign $message\n " ;
} else {
switch ( $type ){
case " created " :
$color = " green " ;
break ;
case " obsolete " :
$color = " red " ;
break ;
case " error " :
$color = " red " ;
break ;
case " deleted " :
$color = " red " ;
2012-05-03 09:34:16 +02:00
break ;
2009-12-16 06:42:21 +01:00
case " changed " :
$color = " blue " ;
break ;
case " repaired " :
$color = " blue " ;
break ;
default :
$color = " " ;
}
echo " <li style= \" color: $color\ " > $message </ li > " ;
2008-11-24 20:28:46 +01:00
}
}
}
/**
2012-05-03 09:34:16 +02:00
* Returns the SELECT clauses ready for inserting into a query .
* @ param array $select Select columns
* @ param boolean $distinct Distinct select ?
* @ return string
2008-11-24 20:28:46 +01:00
*/
2012-05-03 09:34:16 +02:00
public function sqlSelectToString ( $select , $distinct = false ) {
$clauses = array ();
foreach ( $select as $alias => $field ) {
// Don't include redundant aliases.
if ( $alias === $field || preg_match ( '/"' . preg_quote ( $alias ) . '"$/' , $field )) $clauses [] = $field ;
else $clauses [] = " $field AS \" $alias\ " " ;
2008-11-24 20:28:46 +01:00
}
2012-05-03 09:34:16 +02:00
$text = 'SELECT ' ;
if ( $distinct ) $text .= 'DISTINCT ' ;
return $text .= implode ( ', ' , $clauses );
}
/**
* Return the FROM clause ready for inserting into a query .
* @ return string
*/
public function sqlFromToString ( $from ) {
return ' FROM ' . implode ( ' ' , $from );
}
/**
* Returns the WHERE clauses ready for inserting into a query .
* @ return string
*/
public function sqlWhereToString ( $where , $connective ) {
return ' WHERE (' . implode ( " ) { $connective } ( " , $where ) . ')' ;
}
/**
* Returns the ORDER BY clauses ready for inserting into a query .
* @ return string
*/
public function sqlOrderByToString ( $orderby ) {
$statements = array ();
foreach ( $orderby as $clause => $dir ) {
$statements [] = trim ( $clause . ' ' . $dir );
}
return ' ORDER BY ' . implode ( ', ' , $statements );
}
/**
* Returns the GROUP BY clauses ready for inserting into a query .
* @ return string
*/
public function sqlGroupByToString ( $groupby ) {
return ' GROUP BY ' . implode ( ', ' , $groupby );
}
/**
* Returns the HAVING clauses ready for inserting into a query .
* @ return string
*/
public function sqlHavingToString ( $having ) {
2012-05-07 06:35:01 +02:00
return ' HAVING ( ' . implode ( ' ) AND ( ' , $having ) . ')' ;
2012-05-03 09:34:16 +02:00
}
/**
* Return the LIMIT clause ready for inserting into a query .
* @ return string
*/
public function sqlLimitToString ( $limit ) {
$clause = '' ;
// Pass limit as array or SQL string value
if ( is_array ( $limit )) {
if ( ! array_key_exists ( 'limit' , $limit )) throw new InvalidArgumentException ( 'Database::sqlLimitToString(): Wrong format for $limit: ' . var_export ( $limit , true ));
if ( isset ( $limit [ 'start' ]) && is_numeric ( $limit [ 'start' ]) && isset ( $limit [ 'limit' ]) && is_numeric ( $limit [ 'limit' ])) {
$combinedLimit = $limit [ 'start' ] ? " $limit[limit] OFFSET $limit[start] " : " $limit[limit] " ;
} elseif ( isset ( $limit [ 'limit' ]) && is_numeric ( $limit [ 'limit' ])) {
$combinedLimit = ( int ) $limit [ 'limit' ];
2008-11-24 20:28:46 +01:00
} else {
2012-05-03 09:34:16 +02:00
$combinedLimit = false ;
2007-08-24 05:31:14 +02:00
}
2012-05-03 09:34:16 +02:00
if ( ! empty ( $combinedLimit )) $clause .= ' LIMIT ' . $combinedLimit ;
} else {
$clause .= ' LIMIT ' . $limit ;
2008-11-24 20:28:46 +01:00
}
2012-05-03 09:34:16 +02:00
return $clause ;
}
/**
* Convert a SQLQuery object into a SQL statement
* @ param $query SQLQuery
*/
public function sqlQueryToString ( SQLQuery $query ) {
if ( $query -> getDelete ()) {
$text = 'DELETE ' ;
} else {
$text = $this -> sqlSelectToString ( $query -> getSelect (), $query -> getDistinct ());
}
if ( $query -> getFrom ()) $text .= $this -> sqlFromToString ( $query -> getFrom ());
if ( $query -> getWhere ()) $text .= $this -> sqlWhereToString ( $query -> getWhere (), $query -> getConnective ());
// these clauses only make sense in SELECT queries, not DELETE
if ( ! $query -> getDelete ()) {
if ( $query -> getGroupBy ()) $text .= $this -> sqlGroupByToString ( $query -> getGroupBy ());
2012-05-07 06:35:01 +02:00
if ( $query -> getHaving ()) $text .= $this -> sqlHavingToString ( $query -> getHaving ());
2012-05-03 09:34:16 +02:00
if ( $query -> getOrderBy ()) $text .= $this -> sqlOrderByToString ( $query -> getOrderBy ());
if ( $query -> getLimit ()) $text .= $this -> sqlLimitToString ( $query -> getLimit ());
}
2008-11-24 20:28:46 +01:00
return $text ;
2007-08-24 05:31:14 +02:00
}
2012-03-20 09:20:35 +01:00
/**
* Wrap a string into DB - specific quotes . MySQL , PostgreSQL and SQLite3 only need single quotes around the string .
* MSSQL will overload this and include it ' s own N prefix to mark the string as unicode , so characters like macrons
* are saved correctly .
*
* @ param string $string String to be prepared for database query
* @ return string Prepared string
*/
public function prepStringForDB ( $string ) {
return " ' " . Convert :: raw2sql ( $string ) . " ' " ;
}
2010-10-13 03:35:19 +02:00
/**
* Function to return an SQL datetime expression that can be used with the adapter in use
* used for querying a datetime in a certain format
* @ param string $date to be formated , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $format to be used , supported specifiers :
* % Y = Year ( four digits )
* % m = Month ( 01. . 12 )
* % d = Day ( 01. . 31 )
* % H = Hour ( 00. . 23 )
* % i = Minutes ( 00. . 59 )
* % s = Seconds ( 00. . 59 )
* % U = unix timestamp , can only be used on it ' s own
* @ return string SQL datetime expression to query for a formatted datetime
*/
abstract function formattedDatetimeClause ( $date , $format );
/**
* Function to return an SQL datetime expression that can be used with the adapter in use
* used for querying a datetime addition
* @ param string $date , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $interval to be added , use the format [ sign ][ integer ] [ qualifier ], e . g . - 1 Day , + 15 minutes , + 1 YEAR
* supported qualifiers :
* - years
* - months
* - days
* - hours
* - minutes
* - seconds
* This includes the singular forms as well
* @ return string SQL datetime expression to query for a datetime ( YYYY - MM - DD hh : mm : ss ) which is the result of the addition
*/
abstract function datetimeIntervalClause ( $date , $interval );
/**
* Function to return an SQL datetime expression that can be used with the adapter in use
* used for querying a datetime substraction
* @ param string $date1 , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $date2 to be substracted of $date1 , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which is the result of the substraction
*/
abstract function datetimeDifferenceClause ( $date1 , $date2 );
2012-07-05 16:16:36 +02:00
/**
* Can the database override timezone as a connection setting ,
* or does it use the system timezone exclusively ?
*
* @ return Boolean
*/
abstract function supportsTimezoneOverride ();
2011-03-11 04:18:15 +01:00
/*
* Does this database support transactions ?
*
* @ return boolean
*/
abstract function supportsTransactions ();
/*
* Start a prepared transaction
* See http :// developer . postgresql . org / pgdocs / postgres / sql - set - transaction . html for details on transaction isolation options
*/
abstract function transactionStart ( $transaction_mode = false , $session_characteristics = false );
2010-10-13 03:35:19 +02:00
2011-03-11 04:18:15 +01:00
/*
* Create a savepoint that you can jump back to if you encounter problems
*/
abstract function transactionSavepoint ( $savepoint );
/*
* Rollback or revert to a savepoint if your queries encounter problems
* If you encounter a problem at any point during a transaction , you may
* need to rollback that particular query , or return to a savepoint
*/
abstract function transactionRollback ( $savepoint = false );
/*
* Commit everything inside this transaction so far
*/
abstract function transactionEnd ();
2011-09-22 16:28:58 +02:00
/**
* Determines if the used database supports application - level locks ,
* which is different from table - or row - level locking .
* See { @ link getLock ()} for details .
*
* @ return boolean
*/
function supportsLocks () {
return false ;
}
/**
* Returns if the lock is available .
* See { @ link supportsLocks ()} to check if locking is generally supported .
*
* @ return Boolean
*/
function canLock ( $name ) {
return false ;
}
/**
* Sets an application - level lock so that no two processes can run at the same time ,
* also called a " cooperative advisory lock " .
*
* Return FALSE if acquiring the lock fails ; otherwise return TRUE , if lock was acquired successfully .
* Lock is automatically released if connection to the database is broken ( either normally or abnormally ),
* making it less prone to deadlocks than session - or file - based locks .
* Should be accompanied by a { @ link releaseLock ()} call after the logic requiring the lock has completed .
* Can be called multiple times , in which case locks " stack " ( PostgreSQL , SQL Server ),
* or auto - releases the previous lock ( MySQL ) .
*
* Note that this might trigger the database to wait for the lock to be released , delaying further execution .
*
* @ param String
* @ param Int Timeout in seconds
* @ return Boolean
*/
function getLock ( $name , $timeout = 5 ) {
return false ;
}
/**
* Remove an application - level lock file to allow another process to run
* ( if the execution aborts ( e . g . due to an error ) all locks are automatically released ) .
*
* @ param String
* @ return Boolean
*/
function releaseLock ( $name ) {
return false ;
}
2007-07-19 12:40:28 +02:00
}
/**
* Abstract query - result class .
* Once again , this should be subclassed by an actual database implementation . It will only
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
* ever be constructed by a subclass of SS_Database . The result of a database query - an iteratable object that ' s returned by DB :: SS_Query
2007-07-19 12:40:28 +02:00
*
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
* Primarily , the SS_Query class takes care of the iterator plumbing , letting the subclasses focusing
2007-07-19 12:40:28 +02:00
* on providing the specific data - access methods that are required : { @ link nextRecord ()}, { @ link numRecords ()}
* and { @ link seek ()}
2012-04-12 08:02:46 +02:00
* @ package framework
2008-02-25 03:10:37 +01:00
* @ subpackage model
2007-07-19 12:40:28 +02:00
*/
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
abstract class SS_Query implements Iterator {
2007-07-19 12:40:28 +02:00
/**
* The current record in the interator .
* @ var array
*/
private $currentRecord = null ;
/**
* The number of the current row in the interator .
* @ var int
*/
private $rowNum = - 1 ;
2009-05-28 04:06:12 +02:00
/**
* Flag to keep track of whether iteration has begun , to prevent unnecessary seeks
*/
private $queryHasBegun = false ;
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
2009-06-08 02:17:37 +02:00
* Return an array containing all the values from a specific column . If no column is set , then the first will be
* returned
*
* @ param string $column
2007-07-19 12:40:28 +02:00
* @ return array
*/
2009-06-08 02:17:37 +02:00
public function column ( $column = null ) {
$result = array ();
2009-08-11 10:51:05 +02:00
while ( $record = $this -> next ()) {
if ( $column ) $result [] = $record [ $column ];
else $result [] = $record [ key ( $record )];
2007-07-19 12:40:28 +02:00
}
2009-08-11 10:51:05 +02:00
2009-06-08 02:17:37 +02:00
return $result ;
2007-07-19 12:40:28 +02:00
}
/**
* Return an array containing all values in the leftmost column , where the keys are the
* same as the values .
* @ return array
*/
public function keyedColumn () {
2009-02-02 00:49:53 +01:00
$column = array ();
2007-07-19 12:40:28 +02:00
foreach ( $this as $record ) {
2009-08-11 10:51:05 +02:00
$val = $record [ key ( $record )];
2007-07-19 12:40:28 +02:00
$column [ $val ] = $val ;
}
return $column ;
}
/**
* Return a map from the first column to the second column .
* @ return array
*/
public function map () {
2009-02-02 00:49:53 +01:00
$column = array ();
2007-07-19 12:40:28 +02:00
foreach ( $this as $record ) {
$key = reset ( $record );
$val = next ( $record );
$column [ $key ] = $val ;
}
return $column ;
}
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns the next record in the iterator .
* @ return array
*/
public function record () {
return $this -> next ();
}
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns the first column of the first record .
* @ return string
*/
public function value () {
2009-08-11 10:51:05 +02:00
$record = $this -> next ();
if ( $record ) return $record [ key ( $record )];
2007-07-19 12:40:28 +02:00
}
2007-09-15 02:03:12 +02:00
2007-08-16 08:33:41 +02:00
/**
* Return an HTML table containing the full result - set
*/
public function table () {
$first = true ;
$result = " <table> \n " ;
foreach ( $this as $record ) {
if ( $first ) {
$result .= " <tr> " ;
foreach ( $record as $k => $v ) {
$result .= " <th> " . Convert :: raw2xml ( $k ) . " </th> " ;
}
$result .= " </tr> \n " ;
}
$result .= " <tr> " ;
foreach ( $record as $k => $v ) {
$result .= " <td> " . Convert :: raw2xml ( $v ) . " </td> " ;
}
$result .= " </tr> \n " ;
$first = false ;
}
2010-10-19 03:29:55 +02:00
$result .= " </table> \n " ;
2007-08-16 08:33:41 +02:00
if ( $first ) return " No records found " ;
return $result ;
}
2007-07-19 12:40:28 +02:00
/**
* Iterator function implementation . Rewind the iterator to the first item and return it .
* Makes use of { @ link seek ()} and { @ link numRecords ()}, takes care of the plumbing .
* @ return array
*/
public function rewind () {
2009-05-28 04:06:12 +02:00
if ( $this -> queryHasBegun && $this -> numRecords () > 0 ) {
$this -> queryHasBegun = false ;
2007-07-19 12:40:28 +02:00
return $this -> seek ( 0 );
}
}
/**
* Iterator function implementation . Return the current item of the iterator .
* @ return array
*/
public function current () {
if ( ! $this -> currentRecord ) {
return $this -> next ();
} else {
return $this -> currentRecord ;
}
}
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
* Iterator function implementation . Return the first item of this iterator .
* @ return array
*/
public function first () {
$this -> rewind ();
return $this -> current ();
}
/**
* Iterator function implementation . Return the row number of the current item .
* @ return int
*/
public function key () {
return $this -> rowNum ;
}
/**
* Iterator function implementation . Return the next record in the iterator .
* Makes use of { @ link nextRecord ()}, takes care of the plumbing .
* @ return array
*/
public function next () {
2009-05-28 04:06:12 +02:00
$this -> queryHasBegun = true ;
2007-09-15 02:03:12 +02:00
$this -> currentRecord = $this -> nextRecord ();
2007-07-19 12:40:28 +02:00
$this -> rowNum ++ ;
return $this -> currentRecord ;
}
/**
* Iterator function implementation . Check if the iterator is pointing to a valid item .
* @ return boolean
*/
public function valid () {
2010-10-19 03:27:51 +02:00
if ( ! $this -> queryHasBegun ) $this -> next ();
2009-08-11 10:51:05 +02:00
return $this -> currentRecord !== false ;
2007-07-19 12:40:28 +02:00
}
2007-09-15 02:03:12 +02:00
2007-07-19 12:40:28 +02:00
/**
* Return the next record in the query result .
* @ return array
*/
abstract function nextRecord ();
/**
* Return the total number of items in the query result .
* @ return int
*/
abstract function numRecords ();
/**
* Go to a specific row number in the query result and return the record .
* @ param int $rowNum Tow number to go to .
* @ return array
*/
abstract function seek ( $rowNum );
}
2012-02-12 21:22:11 +01:00