2007-07-19 12:40:28 +02:00
< ? php
/**
* MySQL connector class .
2009-03-12 12:11:35 +01:00
*
* Supported indexes for { @ link requireTable ()} :
*
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ 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
class MySQLDatabase extends SS_Database {
2007-07-19 12:40:28 +02:00
/**
* Connection to the DBMS .
* @ var resource
*/
private $dbConn ;
/**
* True if we are connected to a database .
* @ var boolean
*/
private $active ;
/**
* The name of the database .
* @ var string
*/
private $database ;
2009-06-27 15:26:31 +02:00
private static $connection_charset = null ;
2009-10-01 23:02:00 +02:00
private $supportsTransactions = false ;
2009-06-27 15:26:31 +02:00
/**
* Sets the character set for the MySQL database connection .
*
* The character set connection should be set to 'utf8' for SilverStripe version 2.4 . 0 and
* later .
*
* However , sites created before version 2.4 . 0 should leave this unset or data that isn ' t 7 - bit
* safe will be corrupted . As such , the installer comes with this set in mysite / _config . php by
* default in versions 2.4 . 0 and later .
*/
2009-06-27 15:36:01 +02:00
public static function set_connection_charset ( $charset = 'utf8' ) {
2009-06-27 15:26:31 +02:00
self :: $connection_charset = $charset ;
}
2007-07-19 12:40:28 +02:00
/**
* Connect to a MySQL database .
* @ param array $parameters An map of parameters , which should include :
2008-02-25 03:10:37 +01:00
* - server : The server , eg , localhost
* - username : The username to log on with
* - password : The password to log on with
* - database : The database to connect to
2009-09-04 03:38:29 +02:00
* - timezone : ( optional ) the timezone offset , eg : + 12 : 00 for NZ time
2007-07-19 12:40:28 +02:00
*/
public function __construct ( $parameters ) {
$this -> dbConn = mysql_connect ( $parameters [ 'server' ], $parameters [ 'username' ], $parameters [ 'password' ]);
2009-06-27 15:26:31 +02:00
if ( self :: $connection_charset ) {
2009-06-27 15:36:01 +02:00
$this -> query ( " SET CHARACTER SET ' " . self :: $connection_charset . " ' " );
2009-06-27 15:26:31 +02:00
$this -> query ( " SET NAMES ' " . self :: $connection_charset . " ' " );
}
2007-07-19 12:40:28 +02:00
$this -> active = mysql_select_db ( $parameters [ 'database' ], $this -> dbConn );
$this -> database = $parameters [ 'database' ];
2009-09-04 03:38:29 +02:00
if ( isset ( $parameters [ 'timezone' ])) { //set timezone to custom parameter
mysql_query ( " SET SESSION time_zone=' " . $parameters [ 'timezone' ] . " ' " );
}
2007-07-19 12:40:28 +02:00
if ( ! $this -> dbConn ) {
2007-09-04 11:24:55 +02:00
$this -> databaseError ( " Couldn't connect to MySQL database " );
2007-07-19 12:40:28 +02:00
}
2008-11-22 04:51:04 +01:00
$this -> query ( " SET sql_mode = 'ANSI' " );
2007-07-19 12:40:28 +02:00
}
2007-09-14 03:28:16 +02:00
/**
* Not implemented , needed for PDO
*/
public function getConnect ( $parameters ) {
return null ;
}
2007-07-19 12:40:28 +02:00
/**
* Returns true if this database supports collations
* @ return boolean
*/
public function supportsCollations () {
return $this -> getVersion () >= 4.1 ;
}
/**
* The version of MySQL .
* @ var float
*/
private $mysqlVersion ;
/**
* Get the version of MySQL .
* @ return float
*/
public function getVersion () {
if ( ! $this -> mysqlVersion ) {
2008-10-09 00:05:56 +02:00
$this -> mysqlVersion = ( float ) substr ( trim ( ereg_replace ( " ([A-Za-z-]) " , " " , $this -> query ( " SELECT VERSION() " ) -> value ())), 0 , 3 );
2007-07-19 12:40:28 +02:00
}
return $this -> mysqlVersion ;
}
2007-09-14 03:44:34 +02:00
/**
* Get the database server , namely mysql .
* @ return string
*/
public function getDatabaseServer () {
return " mysql " ;
}
2007-07-19 12:40:28 +02:00
public function query ( $sql , $errorLevel = E_USER_ERROR ) {
if ( isset ( $_REQUEST [ 'previewwrite' ]) && in_array ( strtolower ( substr ( $sql , 0 , strpos ( $sql , ' ' ))), array ( 'insert' , 'update' , 'delete' , 'replace' ))) {
2007-11-15 23:41:44 +01:00
Debug :: message ( " Will execute: $sql " );
2007-07-19 12:40:28 +02:00
return ;
}
if ( isset ( $_REQUEST [ 'showqueries' ])) {
$starttime = microtime ( true );
}
$handle = mysql_query ( $sql , $this -> dbConn );
if ( isset ( $_REQUEST [ 'showqueries' ])) {
$endtime = round ( microtime ( true ) - $starttime , 4 );
Debug :: message ( " \n $sql\n { $endtime } ms \n " , false );
}
if ( ! $handle && $errorLevel ) $this -> databaseError ( " Couldn't run query: $sql | " . mysql_error ( $this -> dbConn ), $errorLevel );
return new MySQLQuery ( $this , $handle );
}
2007-09-14 03:35:54 +02:00
public function getGeneratedID ( $table ) {
2007-07-19 12:40:28 +02:00
return mysql_insert_id ( $this -> dbConn );
}
public function isActive () {
2007-09-14 03:28:16 +02:00
return $this -> active ? true : false ;
2007-07-19 12:40:28 +02:00
}
2008-02-25 03:10:37 +01:00
public function createDatabase () {
2009-01-08 00:00:54 +01:00
$this -> query ( " CREATE DATABASE ` $this->database ` " );
$this -> query ( " USE ` $this->database ` " );
2007-08-15 04:50:39 +02:00
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
2007-07-19 12:40:28 +02:00
if ( mysql_select_db ( $this -> database , $this -> dbConn )) {
$this -> active = true ;
return true ;
}
}
2007-08-15 04:50:39 +02:00
/**
* Drop the database that this object is currently connected to .
* Use with caution .
*/
public function dropDatabase () {
2009-03-04 08:31:23 +01:00
$this -> dropDatabaseByName ( $this -> database );
}
/**
* Drop the database that this object is currently connected to .
* Use with caution .
*/
public function dropDatabaseByName ( $dbName ) {
$this -> query ( " DROP DATABASE \" $dbName\ " " );
2007-08-15 04:50:39 +02:00
}
/**
* Returns the name of the currently selected database
*/
public function currentDatabase () {
return $this -> database ;
}
/**
* Switches to the given database .
* If the database doesn ' t exist , you should call createDatabase () after calling selectDatabase ()
*/
public function selectDatabase ( $dbname ) {
$this -> database = $dbname ;
2008-08-20 06:47:48 +02:00
if ( $this -> databaseExists ( $this -> database )) {
if ( mysql_select_db ( $this -> database , $this -> dbConn )) $this -> active = true ;
}
2007-08-15 04:50:39 +02:00
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
}
/**
* Returns true if the named database exists .
*/
public function databaseExists ( $name ) {
$SQL_name = Convert :: raw2sql ( $name );
return $this -> query ( " SHOW DATABASES LIKE ' $SQL_name ' " ) -> value () ? true : false ;
}
2009-03-04 08:31:23 +01:00
/**
* Returns a column
*/
public function allDatabaseNames () {
return $this -> query ( " SHOW DATABASES " ) -> column ();
}
2007-07-19 12:40:28 +02:00
2009-05-19 05:55:14 +02:00
/**
2009-05-21 07:08:11 +02:00
* Create a new table .
* @ 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 .
2009-05-19 05:55:14 +02:00
*/
2009-10-08 03:22:06 +02:00
public function createTable ( $table , $fields = null , $indexes = null , $options = null , $advancedOptions = null ) {
2007-08-15 04:50:39 +02:00
$fieldSchemas = $indexSchemas = " " ;
2009-05-21 10:42:18 +02:00
2009-08-08 06:23:05 +02:00
$addOptions = empty ( $options [ get_class ( $this )]) ? " ENGINE=MyISAM " : $options [ get_class ( $this )];
2009-02-02 00:49:53 +01:00
if ( ! isset ( $fields [ 'ID' ])) $fields [ 'ID' ] = " int(11) not null auto_increment " ;
2008-11-23 01:31:06 +01:00
if ( $fields ) foreach ( $fields as $k => $v ) $fieldSchemas .= " \" $k\ " $v , \n " ;
2009-03-04 22:54:33 +01:00
if ( $indexes ) foreach ( $indexes as $k => $v ) $indexSchemas .= $this -> getIndexSqlDefinition ( $k , $v ) . " , \n " ;
2009-05-19 05:55:14 +02:00
2009-05-21 07:08:11 +02:00
// Switch to "CREATE TEMPORARY TABLE" for temporary tables
$temporary = empty ( $options [ 'temporary' ]) ? " " : " TEMPORARY " ;
$this -> query ( " CREATE $temporary TABLE \" $table\ " (
2007-08-15 04:50:39 +02:00
$fieldSchemas
$indexSchemas
primary key ( ID )
2009-05-19 05:55:14 +02:00
) { $addOptions } " );
2009-05-21 07:08:11 +02:00
return $table ;
2007-08-15 04:50:39 +02:00
}
/**
* Alter a table ' s schema .
* @ param $table The name of the table to alter
* @ param $newFields New fields , a map of field name => field schema
* @ param $newIndexes New indexes , a map of index name => index type
* @ param $alteredFields Updated fields , a map of field name => field schema
* @ param $alteredIndexes Updated indexes , a map of index name => index type
2009-05-19 05:55:14 +02:00
* @ param $alteredOptions
2007-08-15 04:50:39 +02:00
*/
2009-10-08 03:22:06 +02:00
public function alterTable ( $tableName , $newFields = null , $newIndexes = null , $alteredFields = null , $alteredIndexes = null , $alteredOptions = null , $advancedOptions = null ) {
2007-08-15 04:50:39 +02:00
$fieldSchemas = $indexSchemas = " " ;
2009-05-19 05:55:14 +02:00
$alterList = array ();
2007-08-15 04:50:39 +02:00
2008-11-23 01:31:06 +01:00
if ( $newFields ) foreach ( $newFields as $k => $v ) $alterList [] .= " ADD \" $k\ " $v " ;
2007-08-24 06:45:21 +02:00
if ( $newIndexes ) foreach ( $newIndexes as $k => $v ) $alterList [] .= " ADD " . $this -> getIndexSqlDefinition ( $k , $v );
2008-11-23 01:31:06 +01:00
if ( $alteredFields ) foreach ( $alteredFields as $k => $v ) $alterList [] .= " CHANGE \" $k\ " \ " $k\ " $v " ;
2007-08-15 04:50:39 +02:00
if ( $alteredIndexes ) foreach ( $alteredIndexes as $k => $v ) {
2008-11-23 01:31:06 +01:00
$alterList [] .= " DROP INDEX \" $k\ " " ;
2007-08-15 04:50:39 +02:00
$alterList [] .= " ADD " . $this -> getIndexSqlDefinition ( $k , $v );
}
2009-03-04 22:54:33 +01:00
$alterations = implode ( " , \n " , $alterList );
$this -> query ( " ALTER TABLE \" $tableName\ " $alterations " );
2009-05-19 05:55:14 +02:00
2009-08-08 06:23:05 +02:00
if ( $alteredOptions && isset ( $alteredOptions [ get_class ( $this )])) {
$this -> query ( sprintf ( " ALTER TABLE \" %s \" %s " , $tableName , $alteredOptions [ get_class ( $this )]));
2009-10-26 23:03:29 +01:00
DB :: alteration_message (
2009-08-08 06:23:05 +02:00
sprintf ( " Table %s options changed: %s " , $tableName , $alteredOptions [ get_class ( $this )]),
2009-05-19 05:55:14 +02:00
" changed "
);
}
2007-07-19 12:40:28 +02:00
}
public function renameTable ( $oldTableName , $newTableName ) {
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $oldTableName\ " RENAME \ " $newTableName\ " " );
2007-07-19 12:40:28 +02:00
}
2007-08-15 04:50:39 +02:00
2007-07-19 12:40:28 +02:00
/**
* Checks a table ' s integrity and repairs it if necessary .
* @ var string $tableName The name of the table .
* @ return boolean Return true if the table has integrity after the method is complete .
*/
public function checkAndRepairTable ( $tableName ) {
2008-11-23 01:31:06 +01:00
if ( ! $this -> runTableCheckCommand ( " CHECK TABLE \" $tableName\ " " )) {
2009-10-26 23:03:29 +01:00
DB :: alteration_message ( " Table $tableName : repaired " , " repaired " );
2008-11-23 01:31:06 +01:00
return $this -> runTableCheckCommand ( " REPAIR TABLE \" $tableName\ " USE_FRM " );
2007-07-19 12:40:28 +02:00
} else {
return true ;
}
}
/**
* Helper function used by checkAndRepairTable .
* @ param string $sql Query to run .
* @ return boolean Returns if the query returns a successful result .
*/
protected function runTableCheckCommand ( $sql ) {
$testResults = $this -> query ( $sql );
foreach ( $testResults as $testRecord ) {
if ( strtolower ( $testRecord [ 'Msg_text' ]) != 'ok' ) {
return false ;
}
}
return true ;
}
public function createField ( $tableName , $fieldName , $fieldSpec ) {
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " ADD \ " $fieldName\ " $fieldSpec " );
2007-07-19 12:40:28 +02:00
}
/**
* Change the database type of the given field .
* @ param string $tableName The name of the tbale the field is in .
* @ param string $fieldName The name of the field to change .
* @ param string $fieldSpec The new field specification
*/
public function alterField ( $tableName , $fieldName , $fieldSpec ) {
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " CHANGE \ " $fieldName\ " \ " $fieldName\ " $fieldSpec " );
2007-07-19 12:40:28 +02:00
}
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
*/
public function renameField ( $tableName , $oldName , $newName ) {
$fieldList = $this -> fieldList ( $tableName );
if ( array_key_exists ( $oldName , $fieldList )) {
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " CHANGE \ " $oldName\ " \ " $newName\ " " . $fieldList[$oldName] );
2008-11-10 00:56:24 +01:00
}
}
2009-03-04 04:44:11 +01:00
private static $_cache_collation_info = array ();
2007-07-19 12:40:28 +02:00
public function fieldList ( $table ) {
2008-11-23 01:31:06 +01:00
$fields = DB :: query ( " SHOW FULL FIELDS IN \" $table\ " " );
2007-07-19 12:40:28 +02:00
foreach ( $fields as $field ) {
$fieldSpec = $field [ 'Type' ];
if ( ! $field [ 'Null' ] || $field [ 'Null' ] == 'NO' ) {
$fieldSpec .= ' not null' ;
}
if ( $field [ 'Collation' ] && $field [ 'Collation' ] != 'NULL' ) {
2009-03-04 04:44:11 +01:00
// Cache collation info to cut down on database traffic
if ( ! isset ( self :: $_cache_collation_info [ $field [ 'Collation' ]])) {
self :: $_cache_collation_info [ $field [ 'Collation' ]] = DB :: query ( " SHOW COLLATION LIKE ' $field[Collation] ' " ) -> record ();
}
$collInfo = self :: $_cache_collation_info [ $field [ 'Collation' ]];
2007-07-19 12:40:28 +02:00
$fieldSpec .= " character set $collInfo[Charset] collate $field[Collation] " ;
}
if ( $field [ 'Default' ] || $field [ 'Default' ] === " 0 " ) {
2008-12-17 01:40:24 +01:00
if ( is_numeric ( $field [ 'Default' ]))
$fieldSpec .= " default " . addslashes ( $field [ 'Default' ]);
else
$fieldSpec .= " default ' " . addslashes ( $field [ 'Default' ]) . " ' " ;
2007-07-19 12:40:28 +02:00
}
if ( $field [ 'Extra' ]) $fieldSpec .= " $field[Extra] " ;
$fieldList [ $field [ 'Field' ]] = $fieldSpec ;
}
return $fieldList ;
}
/**
* Create an index on a table .
2009-03-12 12:11:35 +01:00
*
2007-07-19 12:40:28 +02:00
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
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
* @ param string $indexSpec The specification of the index , see { @ link SS_Database :: requireIndex ()} for more details .
2007-07-19 12:40:28 +02:00
*/
public function createIndex ( $tableName , $indexName , $indexSpec ) {
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " ADD " . $this->getIndexSqlDefinition ( $indexName , $indexSpec ));
2007-08-15 04:50:39 +02:00
}
2009-03-12 12:11:35 +01:00
/**
2008-12-17 01:40:24 +01:00
* This takes the index spec which has been provided by a class ( ie static $indexes = blah blah )
* and turns it into a proper string .
* Some indexes may be arrays , such as fulltext and unique indexes , and this allows database - specific
2009-03-12 12:11:35 +01:00
* arrays to be created . See { @ link requireTable ()} for details on the index format .
*
* @ see http :// dev . mysql . com / doc / refman / 5.0 / en / create - index . html
*
* @ param string | array $indexSpec
* @ return string MySQL compatible ALTER TABLE syntax
2008-12-17 01:40:24 +01:00
*/
public function convertIndexSpec ( $indexSpec ){
if ( is_array ( $indexSpec )){
//Here we create a db-specific version of whatever index we need to create.
switch ( $indexSpec [ 'type' ]){
case 'fulltext' :
$indexSpec = 'fulltext (' . str_replace ( ' ' , '' , $indexSpec [ 'value' ]) . ')' ;
break ;
case 'unique' :
$indexSpec = 'unique (' . $indexSpec [ 'value' ] . ')' ;
break ;
2009-03-04 22:54:33 +01:00
case 'btree' :
$indexSpec = 'using btree (' . $indexSpec [ 'value' ] . ')' ;
break ;
case 'hash' :
$indexSpec = 'using hash (' . $indexSpec [ 'value' ] . ')' ;
break ;
2008-12-17 01:40:24 +01:00
}
}
return $indexSpec ;
}
2009-03-12 12:11:35 +01:00
/**
* @ param string $indexName
* @ param string | array $indexSpec See { @ link requireTable ()} for details
* @ return string MySQL compatible ALTER TABLE syntax
*/
2009-03-04 22:54:33 +01:00
protected function getIndexSqlDefinition ( $indexName , $indexSpec = null ) {
2008-12-17 01:40:24 +01:00
$indexSpec = $this -> convertIndexSpec ( $indexSpec );
$indexSpec = trim ( $indexSpec );
2009-03-04 22:54:33 +01:00
if ( $indexSpec [ 0 ] != '(' ) list ( $indexType , $indexFields ) = explode ( ' ' , $indexSpec , 2 );
2007-07-19 12:40:28 +02:00
else $indexFields = $indexSpec ;
2009-03-04 22:54:33 +01:00
if ( ! isset ( $indexType ))
2007-07-19 12:40:28 +02:00
$indexType = " index " ;
2008-12-17 01:40:24 +01:00
2009-03-04 22:54:33 +01:00
if ( $indexType == 'using' )
return " index \" $indexName\ " using $indexFields " ;
else {
return " $indexType \" $indexName\ " $indexFields " ;
}
}
/**
* MySQL does not need any transformations done on the index that ' s created , so we can just return it as - is
*/
function getDbSqlDefinition ( $tableName , $indexName , $indexSpec ){
return $indexName ;
2007-07-19 12:40:28 +02:00
}
/**
* Alter an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
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
* @ param string $indexSpec The specification of the index , see { @ link SS_Database :: requireIndex ()} for more details .
2007-07-19 12:40:28 +02:00
*/
public function alterIndex ( $tableName , $indexName , $indexSpec ) {
2008-12-17 01:40:24 +01:00
$indexSpec = $this -> convertIndexSpec ( $indexSpec );
$indexSpec = trim ( $indexSpec );
2007-07-19 12:40:28 +02:00
if ( $indexSpec [ 0 ] != '(' ) {
list ( $indexType , $indexFields ) = explode ( ' ' , $indexSpec , 2 );
} else {
$indexFields = $indexSpec ;
}
if ( ! $indexType ) {
$indexType = " index " ;
}
2008-11-23 01:31:06 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " DROP INDEX \ " $indexName\ " " );
$this -> query ( " ALTER TABLE \" $tableName\ " ADD $indexType \ " $indexName\ " $indexFields " );
2007-07-19 12:40:28 +02:00
}
/**
* Return the list of indexes in a table .
* @ param string $table The table name .
* @ return array
*/
public function indexList ( $table ) {
2008-11-23 01:31:06 +01:00
$indexes = DB :: query ( " SHOW INDEXES IN \" $table\ " " );
2009-05-22 01:17:25 +02:00
$groupedIndexes = array ();
$indexList = array ();
2007-07-19 12:40:28 +02:00
foreach ( $indexes as $index ) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'fields' ][ $index [ 'Seq_in_index' ]] = $index [ 'Column_name' ];
if ( $index [ 'Index_type' ] == 'FULLTEXT' ) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'type' ] = 'fulltext ' ;
} else if ( ! $index [ 'Non_unique' ]) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'type' ] = 'unique ' ;
2009-03-04 22:54:33 +01:00
} else if ( $index [ 'Index_type' ] == 'HASH' ) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'type' ] = 'hash ' ;
} else if ( $index [ 'Index_type' ] == 'RTREE' ) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'type' ] = 'rtree ' ;
2007-07-19 12:40:28 +02:00
} else {
$groupedIndexes [ $index [ 'Key_name' ]][ 'type' ] = '' ;
}
}
2009-05-22 01:17:25 +02:00
if ( $groupedIndexes ) {
foreach ( $groupedIndexes as $index => $details ) {
ksort ( $details [ 'fields' ]);
$indexList [ $index ] = $details [ 'type' ] . '(' . implode ( ',' , $details [ 'fields' ]) . ')' ;
}
2007-07-19 12:40:28 +02:00
}
return $indexList ;
}
/**
* Returns a list of all the tables in the database .
* @ return array
*/
public function tableList () {
2008-08-13 05:54:04 +02:00
$tables = array ();
2007-07-19 12:40:28 +02:00
foreach ( $this -> query ( " SHOW TABLES " ) as $record ) {
2009-05-07 06:30:54 +02:00
$table = reset ( $record );
2009-10-23 04:15:37 +02:00
$tables [ strtolower ( $table )] = $table ;
2007-07-19 12:40:28 +02:00
}
2008-08-13 05:54:04 +02:00
return $tables ;
2007-07-19 12:40:28 +02:00
}
/**
* Return the number of rows affected by the previous operation .
2009-05-07 06:30:54 +02:00
* @ return int
2007-07-19 12:40:28 +02:00
*/
public function affectedRows () {
return mysql_affected_rows ( $this -> dbConn );
}
2008-09-16 20:12:07 +02:00
function databaseError ( $msg , $errorLevel = E_USER_ERROR ) {
// try to extract and format query
if ( preg_match ( '/Couldn\'t run query: ([^\|]*)\|\s*(.*)/' , $msg , $matches )) {
$formatter = new SQLFormatter ();
$msg = " Couldn't run query: \n " . $formatter -> formatPlain ( $matches [ 1 ]) . " \n \n " . $matches [ 2 ];
}
user_error ( $msg , $errorLevel );
}
2008-11-23 02:01:03 +01:00
/**
* Return a boolean type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function boolean ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'tinyint', 'precision'=>1, 'sign'=>'unsigned', 'null'=>'not null', 'default'=>$this->default);
//DB::requireField($this->tableName, $this->name, "tinyint(1) unsigned not null default '{$this->defaultVal}'");
return 'tinyint(1) unsigned not null default ' . ( int ) $values [ 'default' ];
}
/**
* Return a date type - formatted string
* For MySQL , we simply return the word 'date' , no other parameters are necessary
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function date ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'date');
//DB::requireField($this->tableName, $this->name, "date");
return 'date' ;
}
/**
* Return a decimal type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function decimal ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'decimal', 'precision'=>"$this->wholeSize,$this->decimalSize");
//DB::requireField($this->tableName, $this->name, "decimal($this->wholeSize,$this->decimalSize)");
2009-02-17 04:54:10 +01:00
2009-02-16 04:42:37 +01:00
// Avoid empty strings being put in the db
if ( $values [ 'precision' ] == '' ) {
$precision = 1 ;
} else {
$precision = $values [ 'precision' ];
}
2008-11-23 02:01:03 +01:00
2009-02-16 04:42:37 +01:00
return 'decimal(' . $precision . ') not null' ;
2008-11-23 02:01:03 +01:00
}
/**
* Return a enum type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function enum ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'enum', 'enums'=>$this->enum, 'character set'=>'utf8', 'collate'=> 'utf8_general_ci', 'default'=>$this->default);
//DB::requireField($this->tableName, $this->name, "enum('" . implode("','", $this->enum) . "') character set utf8 collate utf8_general_ci default '{$this->default}'");
2008-12-17 01:40:24 +01:00
return 'enum(\'' . implode ( '\',\'' , $values [ 'enums' ]) . '\') character set utf8 collate utf8_general_ci default \'' . $values [ 'default' ] . '\'' ;
2008-11-23 02:01:03 +01:00
}
/**
* Return a float type - formatted string
* For MySQL , we simply return the word 'date' , no other parameters are necessary
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function float ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'float');
//DB::requireField($this->tableName, $this->name, "float");
return 'float' ;
}
/**
* Return a int type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function int ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'int', 'precision'=>11, 'null'=>'not null', 'default'=>(int)$this->default);
//DB::requireField($this->tableName, $this->name, "int(11) not null default '{$this->defaultVal}'");
return 'int(11) not null default ' . ( int ) $values [ 'default' ];
}
/**
* Return a datetime type - formatted string
* For MySQL , we simply return the word 'datetime' , no other parameters are necessary
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
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
public function ss_datetime ( $values ){
2008-11-23 02:01:03 +01:00
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'datetime');
//DB::requireField($this->tableName, $this->name, $values);
return 'datetime' ;
}
/**
* Return a text type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function text ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'mediumtext', 'character set'=>'utf8', 'collate'=>'utf8_general_ci');
//DB::requireField($this->tableName, $this->name, "mediumtext character set utf8 collate utf8_general_ci");
return 'mediumtext character set utf8 collate utf8_general_ci' ;
}
/**
* Return a time type - formatted string
* For MySQL , we simply return the word 'time' , no other parameters are necessary
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function time ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'time');
//DB::requireField($this->tableName, $this->name, "time");
return 'time' ;
}
/**
* Return a varchar type - formatted string
*
2009-03-22 23:59:14 +01:00
* @ param array $values Contains a tokenised list of info about this data type
2008-11-23 02:01:03 +01:00
* @ return string
*/
public function varchar ( $values ){
//For reference, this is what typically gets passed to this function:
//$parts=Array('datatype'=>'varchar', 'precision'=>$this->size, 'character set'=>'utf8', 'collate'=>'utf8_general_ci');
//DB::requireField($this->tableName, $this->name, "varchar($this->size) character set utf8 collate utf8_general_ci");
return 'varchar(' . $values [ 'precision' ] . ') character set utf8 collate utf8_general_ci' ;
}
2008-11-24 00:20:02 +01:00
2009-02-17 04:54:10 +01:00
/*
* Return the MySQL - proprietary 'Year' datatype
*/
public function year ( $values ){
return 'year(4)' ;
}
2009-02-13 03:37:58 +01:00
/**
* This returns the column which is the primary key for each table
* In Postgres , it is a SERIAL8 , which is the equivalent of an auto_increment
*
* @ return string
*/
function IdColumn (){
return 'int(11) not null auto_increment' ;
}
2009-03-11 23:00:01 +01:00
/**
* Returns the SQL command to get all the tables in this database
*/
function allTablesSQL (){
return " SHOW TABLES; " ;
}
2008-11-24 00:20:02 +01:00
/**
* Returns true if the given table is exists in the current database
* NOTE : Experimental ; introduced for db - abstraction and may changed before 2.4 is released .
*/
public function hasTable ( $table ) {
$SQL_table = Convert :: raw2sql ( $table );
return ( bool )( $this -> query ( " SHOW TABLES LIKE ' $SQL_table ' " ) -> value ());
}
/**
* Returns the values of the given enum field
* NOTE : Experimental ; introduced for db - abstraction and may changed before 2.4 is released .
*/
public function enumValuesForField ( $tableName , $fieldName ) {
// Get the enum of all page types from the SiteTree table
$classnameinfo = DB :: query ( " DESCRIBE \" $tableName\ " \ " $fieldName\ " " )->first();
preg_match_all ( " /'[^,]+'/ " , $classnameinfo [ " Type " ], $matches );
foreach ( $matches [ 0 ] as $value ) {
$classes [] = trim ( $value , " ' " );
}
return $classes ;
}
2009-03-11 22:48:30 +01:00
2009-06-05 05:44:28 +02:00
/**
* The core search engine , used by this class and its subclasses to do fun stuff .
* Searches both SiteTree and File .
*
* @ param string $keywords Keywords as a string .
*/
2009-06-05 06:37:45 +02:00
public function searchEngine ( $classesToSearch , $keywords , $start , $pageLength , $sortBy = " Relevance DESC " , $extraFilter = " " , $booleanSearch = false , $alternativeFileFilter = " " , $invertedMatch = false ) {
2009-06-05 05:44:28 +02:00
$fileFilter = '' ;
$keywords = Convert :: raw2sql ( $keywords );
$htmlEntityKeywords = htmlentities ( $keywords );
$extraFilters = array ( 'SiteTree' => '' , 'File' => '' );
if ( $booleanSearch ) $boolean = " IN BOOLEAN MODE " ;
if ( $extraFilter ) {
$extraFilters [ 'SiteTree' ] = " AND $extraFilter " ;
if ( $alternativeFileFilter ) $extraFilters [ 'File' ] = " AND $alternativeFileFilter " ;
else $extraFilters [ 'File' ] = $extraFilters [ 'SiteTree' ];
}
// Always ensure that only pages with ShowInSearch = 1 can be searched
$extraFilters [ 'SiteTree' ] .= " AND ShowInSearch <> 0 " ;
$limit = $start . " , " . ( int ) $pageLength ;
$notMatch = $invertedMatch ? " NOT " : " " ;
if ( $keywords ) {
$match [ 'SiteTree' ] = "
MATCH ( Title , MenuTitle , Content , MetaTitle , MetaDescription , MetaKeywords ) AGAINST ( '$keywords' $boolean )
+ MATCH ( Title , MenuTitle , Content , MetaTitle , MetaDescription , MetaKeywords ) AGAINST ( '$htmlEntityKeywords' $boolean )
" ;
$match [ 'File' ] = " MATCH (Filename, Title, Content) AGAINST (' $keywords ' $boolean ) AND ClassName = 'File' " ;
// We make the relevance search by converting a boolean mode search into a normal one
$relevanceKeywords = str_replace ( array ( '*' , '+' , '-' ), '' , $keywords );
$htmlEntityRelevanceKeywords = str_replace ( array ( '*' , '+' , '-' ), '' , $htmlEntityKeywords );
$relevance [ 'SiteTree' ] = " MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST (' $relevanceKeywords ') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST (' $htmlEntityRelevanceKeywords ') " ;
$relevance [ 'File' ] = " MATCH (Filename, Title, Content) AGAINST (' $relevanceKeywords ') " ;
} else {
$relevance [ 'SiteTree' ] = $relevance [ 'File' ] = 1 ;
$match [ 'SiteTree' ] = $match [ 'File' ] = " 1 = 1 " ;
}
// Generate initial queries and base table names
$baseClasses = array ( 'SiteTree' => '' , 'File' => '' );
foreach ( $classesToSearch as $class ) {
$queries [ $class ] = singleton ( $class ) -> extendedSQL ( $notMatch . $match [ $class ] . $extraFilters [ $class ], " " );
$baseClasses [ $class ] = reset ( $queries [ $class ] -> from );
}
// Make column selection lists
$select = array (
'SiteTree' => array ( " ClassName " , " $baseClasses[SiteTree] .ID " , " ParentID " , " Title " , " URLSegment " , " Content " , " LastEdited " , " Created " , " _utf8'' AS Filename " , " _utf8'' AS Name " , " $relevance[SiteTree] AS Relevance " , " CanViewType " ),
'File' => array ( " ClassName " , " $baseClasses[File] .ID " , " _utf8'' AS ParentID " , " Title " , " _utf8'' AS URLSegment " , " Content " , " LastEdited " , " Created " , " Filename " , " Name " , " $relevance[File] AS Relevance " , " NULL AS CanViewType " ),
);
// Process queries
foreach ( $classesToSearch as $class ) {
// There's no need to do all that joining
$queries [ $class ] -> from = array ( str_replace ( '`' , '' , $baseClasses [ $class ]) => $baseClasses [ $class ]);
$queries [ $class ] -> select = $select [ $class ];
$queries [ $class ] -> orderby = null ;
}
// Combine queries
$querySQLs = array ();
$totalCount = 0 ;
foreach ( $queries as $query ) {
$querySQLs [] = $query -> sql ();
$totalCount += $query -> unlimitedRowCount ();
}
$fullQuery = implode ( " UNION " , $querySQLs ) . " ORDER BY $sortBy LIMIT $limit " ;
// Get records
$records = DB :: query ( $fullQuery );
foreach ( $records as $record )
$objects [] = new $record [ 'ClassName' ]( $record );
2009-09-17 02:05:23 +02:00
2009-06-05 05:44:28 +02:00
if ( isset ( $objects )) $doSet = new DataObjectSet ( $objects );
else $doSet = new DataObjectSet ();
$doSet -> setPageLimits ( $start , $pageLength , $totalCount );
return $doSet ;
}
2009-03-11 23:24:50 +01:00
/**
* Because NOW () doesn ' t always work ...
* MSSQL , I ' m looking at you
*
*/
function now (){
return 'NOW()' ;
}
2009-09-17 02:05:23 +02:00
/*
* Returns the database - specific version of the random () function
*/
function random (){
return 'RAND()' ;
}
2009-03-11 22:48:30 +01:00
/*
* This will return text which has been escaped in a database - friendly manner
* Using PHP 's addslashes method won' t work in MSSQL
*/
function addslashes ( $value ){
2009-07-09 01:05:01 +02:00
return mysql_real_escape_string ( $value , $this -> dbConn );
2009-03-11 22:48:30 +01:00
}
2009-03-17 20:56:11 +01:00
/*
* This changes the index name depending on database requirements .
* MySQL doesn ' t need any changes .
*/
function modifyIndex ( $index ){
return $index ;
}
2009-05-21 07:08:11 +02:00
/**
* Returns a SQL fragment for querying a fulltext search index
* @ param $fields array The list of field names to search on
* @ param $keywords string The search query
* @ param $booleanSearch A MySQL - specific flag to switch to boolean search
*/
function fullTextSearchSQL ( $fields , $keywords , $booleanSearch = false ) {
$boolean = $booleanSearch ? " IN BOOLEAN MODE " : " " ;
$fieldNames = '"' . implode ( '", "' , $fields ) . '"' ;
$SQL_keywords = Convert :: raw2sql ( $keywords );
$SQL_htmlEntityKeywords = Convert :: raw2sql ( htmlentities ( $keywords ));
return " (MATCH ( $fieldNames ) AGAINST (' $SQL_keywords ' $boolean ) + MATCH ( $fieldNames ) AGAINST (' $SQL_htmlEntityKeywords ' $boolean )) " ;
}
2009-10-01 23:02:00 +02:00
/*
* Does this database support transactions ?
*/
public function supportsTransactions (){
return $this -> supportsTransactions ;
}
2009-10-08 03:22:06 +02:00
/*
* This is a quick lookup to discover if the database supports particular extensions
* Currently , MySQL supports no extensions
*/
public function supportsExtensions ( $extensions = Array ( 'partitions' , 'tablespaces' , 'clustering' )){
if ( isset ( $extensions [ 'partitions' ]))
return false ;
elseif ( isset ( $extensions [ 'tablespaces' ]))
return false ;
elseif ( isset ( $extensions [ 'clustering' ]))
return false ;
else
return false ;
}
2009-10-01 23:02:00 +02:00
/*
* Start a prepared transaction
* See http :// developer . postgresql . org / pgdocs / postgres / sql - set - transaction . html for details on transaction isolation options
*/
public function startTransaction ( $transaction_mode = false , $session_characteristics = false ){
//Transactions not set up for MySQL yet
}
/*
* Create a savepoint that you can jump back to if you encounter problems
*/
public function transactionSavepoint ( $savepoint ){
//Transactions not set up for MySQL yet
}
/*
* 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
*/
public function transactionRollback ( $savepoint = false ){
//Transactions not set up for MySQL yet
}
/*
* Commit everything inside this transaction so far
*/
public function endTransaction (){
//Transactions not set up for MySQL yet
}
2007-07-19 12:40:28 +02:00
}
/**
* A result - set from a MySQL database .
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ 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
class MySQLQuery extends SS_Query {
2007-07-19 12:40:28 +02:00
/**
* The MySQLDatabase object that created this result set .
* @ var MySQLDatabase
*/
private $database ;
/**
* The internal MySQL handle that points to the result set .
* @ var resource
*/
private $handle ;
/**
* Hook the result - set given into a Query class , suitable for use by sapphire .
* @ param database The database object that created this query .
* @ param handle the internal mysql handle that is points to the resultset .
*/
public function __construct ( MySQLDatabase $database , $handle ) {
$this -> database = $database ;
$this -> handle = $handle ;
}
public function __destroy () {
mysql_free_result ( $this -> handle );
}
public function seek ( $row ) {
return mysql_data_seek ( $this -> handle , $row );
}
public function numRecords () {
return mysql_num_rows ( $this -> handle );
}
public function nextRecord () {
2009-08-19 06:34:28 +02:00
// Coalesce rather than replace common fields.
if ( $data = mysql_fetch_row ( $this -> handle )) {
foreach ( $data as $columnIdx => $value ) {
$columnName = mysql_field_name ( $this -> handle , $columnIdx );
// $value || !$ouput[$columnName] means that the *last* occurring value is shown
// !$ouput[$columnName] means that the *first* occurring value is shown
if ( isset ( $value ) || ! isset ( $output [ $columnName ])) {
$output [ $columnName ] = $value ;
}
}
return $output ;
} else {
return false ;
}
2007-07-19 12:40:28 +02:00
}
}
2007-11-15 23:40:48 +01:00
?>