2007-07-19 12:40:28 +02:00
< ? php
/**
* MySQL connector class .
2011-01-11 22:52:23 +01:00
*
2009-03-12 12:11:35 +01:00
* Supported indexes for { @ link requireTable ()} :
2011-01-11 22:52:23 +01:00
*
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
class MySQLDatabase extends SS_Database {
2007-07-19 12:40:28 +02:00
/**
* Connection to the DBMS .
* @ var resource
*/
private $dbConn ;
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* True if we are connected to a database .
* @ var boolean
*/
private $active ;
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* The name of the database .
* @ var string
*/
private $database ;
2011-01-11 22:52:23 +01:00
2009-06-27 15:26:31 +02:00
private static $connection_charset = null ;
2011-01-11 22:52:23 +01:00
2011-10-29 02:34:11 +02:00
private $supportsTransactions = true ;
2011-01-11 22:52:23 +01:00
2009-06-27 15:26:31 +02:00
/**
* Sets the character set for the MySQL database connection .
2011-01-11 22:52:23 +01:00
*
2009-06-27 15:26:31 +02:00
* The character set connection should be set to 'utf8' for SilverStripe version 2.4 . 0 and
* later .
2011-01-11 22:52:23 +01:00
*
2009-06-27 15:26:31 +02:00
* 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 ;
}
2011-01-11 22:52:23 +01:00
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
2010-10-19 07:04:40 +02:00
* - timezone : ( optional ) The timezone offset . For example : + 12 : 00 , " Pacific/Auckland " , or " SYSTEM "
2007-07-19 12:40:28 +02:00
*/
public function __construct ( $parameters ) {
2011-10-29 02:34:11 +02:00
$this -> dbConn = new MySQLi ( $parameters [ 'server' ], $parameters [ 'username' ], $parameters [ 'password' ]);
if ( $this -> dbConn -> connect_error ) {
$this -> databaseError ( " Couldn't connect to MySQL database | " . $this -> dbConn -> connect_error );
}
$this -> query ( " SET sql_mode = 'ANSI' " );
2009-06-27 15:26:31 +02:00
if ( self :: $connection_charset ) {
2011-10-29 02:34:11 +02:00
$this -> dbConn -> set_charset ( self :: $connection_charset );
2009-06-27 15:26:31 +02:00
}
2011-10-29 02:34:11 +02:00
$this -> active = $this -> dbConn -> select_db ( $parameters [ 'database' ]);
2007-07-19 12:40:28 +02:00
$this -> database = $parameters [ 'database' ];
2010-10-19 07:04:40 +02:00
if ( isset ( $parameters [ 'timezone' ])) $this -> query ( sprintf ( " SET SESSION time_zone = '%s' " , $parameters [ 'timezone' ]));
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2007-09-14 03:28:16 +02:00
/**
* Not implemented , needed for PDO
*/
public function getConnect ( $parameters ) {
return null ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* Returns true if this database supports collations
* @ return boolean
*/
public function supportsCollations () {
2011-10-29 02:34:11 +02:00
return true ;
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* Get the version of MySQL .
2010-10-19 07:03:03 +02:00
* @ return string
2007-07-19 12:40:28 +02:00
*/
public function getVersion () {
2011-10-29 02:34:11 +02:00
return $this -> dbConn -> server_info ;
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2007-09-14 03:44:34 +02:00
/**
* Get the database server , namely mysql .
* @ return string
*/
public function getDatabaseServer () {
return " mysql " ;
}
2011-01-11 22:52:23 +01:00
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 ;
}
2012-03-09 02:15:34 +01:00
if ( isset ( $_REQUEST [ 'showqueries' ]) && Director :: isDev ( true )) {
2007-07-19 12:40:28 +02:00
$starttime = microtime ( true );
}
2011-01-11 22:52:23 +01:00
2011-10-29 02:34:11 +02:00
$handle = $this -> dbConn -> query ( $sql );
2011-01-11 22:52:23 +01:00
2012-03-09 02:15:34 +01:00
if ( isset ( $_REQUEST [ 'showqueries' ]) && Director :: isDev ( true )) {
2007-07-19 12:40:28 +02:00
$endtime = round ( microtime ( true ) - $starttime , 4 );
2010-12-10 00:26:22 +01:00
Debug :: message ( " \n $sql\n { $endtime } ms \n " , false );
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2011-10-29 02:34:11 +02:00
if ( ! $handle && $errorLevel ) $this -> databaseError ( " Couldn't run query: $sql | " . $this -> dbConn -> error , $errorLevel );
2007-07-19 12:40:28 +02:00
return new MySQLQuery ( $this , $handle );
}
2011-01-11 22:52:23 +01:00
2007-09-14 03:35:54 +02:00
public function getGeneratedID ( $table ) {
2011-10-29 02:34:11 +02:00
return $this -> dbConn -> insert_id ;
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
public function isActive () {
2007-09-14 03:28:16 +02:00
return $this -> active ? true : false ;
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
2008-02-25 03:10:37 +01:00
public function createDatabase () {
2011-10-29 02:34:11 +02: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 ;
2011-10-29 02:34:11 +02:00
$this -> active = $this -> dbConn -> select_db ( $this -> database );
return $this -> active ;
2007-07-19 12:40:28 +02:00
}
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
}
2011-01-11 22:52:23 +01:00
2007-08-15 04:50:39 +02:00
/**
* Returns the name of the currently selected database
*/
public function currentDatabase () {
return $this -> database ;
}
2011-01-11 22:52:23 +01:00
2007-08-15 04:50:39 +02:00
/**
* 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 ;
2011-10-29 02:34:11 +02:00
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
$this -> active = false ;
2008-08-20 06:47:48 +02:00
if ( $this -> databaseExists ( $this -> database )) {
2011-10-29 02:34:11 +02:00
$this -> active = $this -> dbConn -> select_db ( $this -> database );
2008-08-20 06:47:48 +02:00
}
2011-10-29 02:34:11 +02:00
return $this -> active ;
2007-08-15 04:50:39 +02:00
}
/**
* 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
/**
2011-01-11 22:52:23 +01:00
* Returns a column
2009-03-04 08:31:23 +01:00
*/
public function allDatabaseNames () {
return $this -> query ( " SHOW DATABASES " ) -> column ();
}
2011-01-11 22:52:23 +01: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 = " " ;
2011-10-29 02:34:11 +02:00
if ( ! empty ( $options [ get_class ( $this )])) {
$addOptions = $options [ get_class ( $this )];
} elseif ( ! empty ( $options [ get_parent_class ( $this )])) {
$addOptions = $options [ get_parent_class ( $this )];
} else {
$addOptions = " ENGINE=InnoDB " ;
}
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 } " );
2011-10-29 02:34:11 +02:00
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 ) {
2011-10-29 02:34:11 +02:00
if ( $this -> isView ( $tableName )) {
DB :: alteration_message (
sprintf ( " Table %s not changed as it is a view " , $tableName ),
" changed "
);
return ;
}
2007-08-15 04:50:39 +02:00
$fieldSchemas = $indexSchemas = " " ;
2009-05-19 05:55:14 +02:00
$alterList = array ();
2011-01-11 22:52:23 +01: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 );
}
2011-10-29 05:47:18 +02:00
if ( $alteredOptions && isset ( $alteredOptions [ get_class ( $this )])) {
if ( ! isset ( $this -> indexList [ $tableName ])) {
$this -> indexList [ $tableName ] = $this -> indexList ( $tableName );
}
$skip = false ;
foreach ( $this -> indexList [ $tableName ] as $index ) {
if ( strpos ( $index , 'fulltext ' ) === 0 ) {
$skip = true ;
break ;
}
}
if ( $skip ) {
DB :: alteration_message (
sprintf ( " Table %s options not changed to %s due to fulltextsearch index " , $tableName , $alteredOptions [ get_class ( $this )]),
" changed "
);
} else {
$this -> query ( sprintf ( " ALTER TABLE \" %s \" %s " , $tableName , $alteredOptions [ get_class ( $this )]));
DB :: alteration_message (
sprintf ( " Table %s options changed: %s " , $tableName , $alteredOptions [ get_class ( $this )]),
" changed "
);
}
}
2011-01-11 22:52:23 +01:00
2009-03-04 22:54:33 +01:00
$alterations = implode ( " , \n " , $alterList );
$this -> query ( " ALTER TABLE \" $tableName\ " $alterations " );
2007-07-19 12:40:28 +02:00
}
2011-10-29 02:34:11 +02:00
public function isView ( $tableName ) {
$info = $this -> query ( " SHOW /*!50002 FULL*/ TABLES LIKE ' $tableName ' " ) -> record ();
return $info && strtoupper ( $info [ 'Table_type' ]) == 'VIEW' ;
}
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
}
2011-01-11 22:52:23 +01: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-31 01:38:38 +01:00
if ( $this -> runTableCheckCommand ( " CHECK TABLE \" " . strtolower ( $tableName ) . " \" " )){
2009-12-16 06:35:15 +01:00
DB :: alteration_message ( " Table $tableName : renamed from lowercase " , " repaired " );
2009-10-31 01:38:38 +01:00
return $this -> renameTable ( strtolower ( $tableName ), $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 ;
}
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* 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 ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
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
}
2011-01-11 22:52:23 +01:00
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
}
2011-01-11 22:52:23 +01:00
2008-11-10 00:56:24 +01:00
/**
* Change the database column name of the given field .
2011-01-11 22:52:23 +01:00
*
2008-11-10 00:56:24 +01:00
* @ 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 ) {
2011-01-11 22:52:23 +01:00
$fieldList = $this -> fieldList ( $tableName );
if ( array_key_exists ( $oldName , $fieldList )) {
$this -> query ( " ALTER TABLE \" $tableName\ " CHANGE \ " $oldName\ " \ " $newName\ " " . $fieldList[$oldName] );
2010-10-19 07:08:46 +02:00
}
2008-11-10 00:56:24 +01:00
}
2011-01-11 22:52:23 +01:00
2009-03-04 04:44:11 +01:00
private static $_cache_collation_info = array ();
2011-01-11 22:52:23 +01:00
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' ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
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] " ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
if ( $field [ 'Default' ] || $field [ 'Default' ] === " 0 " ) {
2008-12-17 01:40:24 +01:00
if ( is_numeric ( $field [ 'Default' ]))
2011-09-15 14:15:41 +02:00
$fieldSpec .= " default " . Convert :: raw2sql ( $field [ 'Default' ]);
2008-12-17 01:40:24 +01:00
else
2011-09-15 14:15:41 +02:00
$fieldSpec .= " default ' " . Convert :: raw2sql ( $field [ 'Default' ]) . " ' " ;
2007-07-19 12:40:28 +02:00
}
if ( $field [ 'Extra' ]) $fieldSpec .= " $field[Extra] " ;
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
$fieldList [ $field [ 'Field' ]] = $fieldSpec ;
}
return $fieldList ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* Create an index on a table .
2011-01-11 22:52:23 +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
}
2011-01-11 22:52:23 +01: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 .
2011-01-11 22:52:23 +01:00
*
2009-03-12 12:11:35 +01:00
* @ see http :// dev . mysql . com / doc / refman / 5.0 / en / create - index . html
2011-01-11 22:52:23 +01:00
*
2009-03-12 12:11:35 +01:00
* @ 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' :
2010-11-30 07:25:16 +01:00
case 'index' :
2009-03-04 22:54:33 +01:00
$indexSpec = 'using btree (' . $indexSpec [ 'value' ] . ')' ;
break ;
case 'hash' :
$indexSpec = 'using hash (' . $indexSpec [ 'value' ] . ')' ;
break ;
2008-12-17 01:40:24 +01:00
}
}
2011-01-11 22:52:23 +01:00
2008-12-17 01:40:24 +01:00
return $indexSpec ;
}
2011-01-11 22:52:23 +01:00
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 ) {
2011-01-11 22:52:23 +01:00
2008-12-17 01:40:24 +01:00
$indexSpec = $this -> convertIndexSpec ( $indexSpec );
2011-01-11 22:52:23 +01:00
2008-12-17 01:40:24 +01:00
$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 ;
2011-01-11 22:52:23 +01:00
2009-03-04 22:54:33 +01:00
if ( ! isset ( $indexType ))
2007-07-19 12:40:28 +02:00
$indexType = " index " ;
2011-01-11 22:52:23 +01:00
2009-03-04 22:54:33 +01:00
if ( $indexType == 'using' )
2011-01-11 22:52:23 +01:00
return " index \" $indexName\ " using $indexFields " ;
2009-03-04 22:54:33 +01:00
else {
return " $indexType \" $indexName\ " $indexFields " ;
}
}
2011-01-11 22:52:23 +01:00
2009-03-04 22:54:33 +01:00
/**
* 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
}
2011-01-11 22:52:23 +01:00
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 ) {
2011-01-11 22:52:23 +01:00
2008-12-17 01:40:24 +01:00
$indexSpec = $this -> convertIndexSpec ( $indexSpec );
2011-01-11 22:52:23 +01:00
2008-12-17 01:40:24 +01:00
$indexSpec = trim ( $indexSpec );
2007-07-19 12:40:28 +02:00
if ( $indexSpec [ 0 ] != '(' ) {
list ( $indexType , $indexFields ) = explode ( ' ' , $indexSpec , 2 );
} else {
$indexFields = $indexSpec ;
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
if ( ! $indexType ) {
$indexType = " index " ;
}
2011-01-11 22:52:23 +01:00
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
}
2011-01-11 22:52:23 +01:00
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 ();
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
foreach ( $indexes as $index ) {
$groupedIndexes [ $index [ 'Key_name' ]][ 'fields' ][ $index [ 'Seq_in_index' ]] = $index [ 'Column_name' ];
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
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
}
2011-01-11 22:52:23 +01:00
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
}
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* Return the number of rows affected by the previous operation .
2011-01-11 22:52:23 +01:00
* @ return int
2007-07-19 12:40:28 +02:00
*/
public function affectedRows () {
2011-10-29 02:34:11 +02:00
return $this -> dbConn -> affected_rows ;
2007-07-19 12:40:28 +02:00
}
2011-01-11 22:52:23 +01:00
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 ];
}
2011-01-11 22:52:23 +01:00
2008-09-16 20:12:07 +02:00
user_error ( $msg , $errorLevel );
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a boolean type - formatted string
2011-01-11 22:52:23 +01:00
*
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}'");
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
return 'tinyint(1) unsigned not null default ' . ( int ) $values [ 'default' ];
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a date type - formatted string
* For MySQL , we simply return the word 'date' , no other parameters are necessary
2011-01-11 22:52:23 +01:00
*
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' ;
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a decimal type - formatted string
2011-01-11 22:52:23 +01:00
*
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
2010-04-13 03:53:13 +02:00
$defaultValue = '' ;
if ( isset ( $values [ 'default' ]) && is_numeric ( $values [ 'default' ])) {
2011-10-29 02:34:11 +02:00
$decs = strpos ( $precision , ',' ) !== false ? ( int ) substr ( $precision , strpos ( $precision , ',' ) + 1 ) : 0 ;
$defaultValue = ' default ' . number_format ( $values [ 'default' ], $decs , '.' , '' );
2010-04-13 03:53:13 +02:00
}
return 'decimal(' . $precision . ') not null' . $defaultValue ;
2008-11-23 02:01:03 +01:00
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a enum type - formatted string
2011-01-11 22:52:23 +01:00
*
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}'");
2011-01-11 22:52:23 +01:00
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
}
2011-01-11 22:52:23 +01:00
2010-04-13 03:49:40 +02:00
/**
* Return a set type - formatted string
2011-01-11 22:52:23 +01:00
*
2010-04-13 03:49:40 +02:00
* @ param array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function set ( $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}'");
$default = empty ( $values [ 'default' ]) ? '' : " default ' $values[default] ' " ;
return 'set(\'' . implode ( '\',\'' , $values [ 'enums' ]) . '\') character set utf8 collate utf8_general_ci' . $default ;
}
2011-01-11 22:52:23 +01:00
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
2011-01-11 22:52:23 +01:00
*
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");
2011-01-11 22:52:23 +01:00
2010-10-13 03:45:24 +02:00
return 'float not null default ' . $values [ 'default' ];
2008-11-23 02:01:03 +01:00
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a int type - formatted string
2011-01-11 22:52:23 +01:00
*
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' ];
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a datetime type - formatted string
* For MySQL , we simply return the word 'datetime' , no other parameters are necessary
2011-01-11 22:52:23 +01:00
*
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' ;
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a text type - formatted string
2011-01-11 22:52:23 +01:00
*
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");
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
return 'mediumtext character set utf8 collate utf8_general_ci' ;
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a time type - formatted string
* For MySQL , we simply return the word 'time' , no other parameters are necessary
2011-01-11 22:52:23 +01:00
*
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");
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
return 'time' ;
}
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
/**
* Return a varchar type - formatted string
2011-01-11 22:52:23 +01:00
*
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");
2011-01-11 22:52:23 +01:00
2008-11-23 02:01:03 +01:00
return 'varchar(' . $values [ 'precision' ] . ') character set utf8 collate utf8_general_ci' ;
}
2011-01-11 22:52:23 +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' ;
}
2011-01-11 22:52:23 +01:00
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; " ;
}
2011-01-11 22:52:23 +01:00
2008-11-24 00:20:02 +01:00
/**
2011-01-11 22:52:23 +01:00
* Returns true if the given table is exists in the current database
2008-11-24 00:20:02 +01:00
* 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 );
2011-01-11 22:52:23 +01:00
2010-04-13 01:43:52 +02:00
$classes = array ();
2008-11-24 00:20:02 +01:00
foreach ( $matches [ 0 ] as $value ) {
$classes [] = trim ( $value , " ' " );
}
return $classes ;
}
2011-01-11 22:52:23 +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 .
2011-01-11 22:52:23 +01:00
*
2009-06-05 05:44:28 +02:00
* @ 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 ) {
2011-03-23 00:16:40 +01:00
if ( ! class_exists ( 'SiteTree' )) throw new Exception ( 'MySQLDatabase->searchEngine() requires "SiteTree" class' );
if ( ! class_exists ( 'File' )) throw new Exception ( 'MySQLDatabase->searchEngine() requires "File" class' );
2011-01-11 22:52:23 +01:00
$fileFilter = '' ;
2009-06-05 05:44:28 +02:00
$keywords = Convert :: raw2sql ( $keywords );
2010-12-08 00:53:42 +01:00
$htmlEntityKeywords = htmlentities ( $keywords , ENT_NOQUOTES , 'UTF-8' );
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
$extraFilters = array ( 'SiteTree' => '' , 'File' => '' );
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
if ( $booleanSearch ) $boolean = " IN BOOLEAN MODE " ;
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
if ( $extraFilter ) {
$extraFilters [ 'SiteTree' ] = " AND $extraFilter " ;
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
if ( $alternativeFileFilter ) $extraFilters [ 'File' ] = " AND $alternativeFileFilter " ;
else $extraFilters [ 'File' ] = $extraFilters [ 'SiteTree' ];
}
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
// Always ensure that only pages with ShowInSearch = 1 can be searched
$extraFilters [ 'SiteTree' ] .= " AND ShowInSearch <> 0 " ;
2011-09-15 16:00:54 +02:00
// File.ShowInSearch was added later, keep the database driver backwards compatible
// by checking for its existence first
$fields = $this -> fieldList ( 'File' );
if ( array_key_exists ( 'ShowInSearch' , $fields )) $extraFilters [ 'File' ] .= " AND ShowInSearch <> 0 " ;
2009-06-05 05:44:28 +02:00
$limit = $start . " , " . ( int ) $pageLength ;
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
$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' " ;
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
// 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 " ;
}
2011-10-29 06:12:37 +02:00
// Generate initial DataLists and base table names
$lists = array ();
2009-06-05 05:44:28 +02:00
$baseClasses = array ( 'SiteTree' => '' , 'File' => '' );
foreach ( $classesToSearch as $class ) {
2011-10-29 06:12:37 +02:00
$lists [ $class ] = DataList :: create ( $class ) -> where ( $notMatch . $match [ $class ] . $extraFilters [ $class ], " " );
$baseClasses [ $class ] = '"' . $class . '"' ;
2009-06-05 05:44:28 +02:00
}
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
// Make column selection lists
$select = array (
2012-05-01 07:44:31 +02:00
'SiteTree' => array ( " ClassName " , " $baseClasses[SiteTree] . \" ID \" " , " ParentID " , " Title " , " MenuTitle " , " URLSegment " , " Content " , " LastEdited " , " Created " , " Filename " => " _utf8'' " , " Name " => " _utf8'' " , " Relevance " => $relevance [ 'SiteTree' ], " CanViewType " ),
'File' => array ( " ClassName " , " $baseClasses[File] . \" ID \" " , " ParentID " => " _utf8'' " , " Title " , " MenuTitle " => " _utf8'' " , " URLSegment " => " _utf8'' " , " Content " , " LastEdited " , " Created " , " Filename " , " Name " , " Relevance " => $relevance [ 'File' ], " CanViewType " => " NULL " ),
2009-06-05 05:44:28 +02:00
);
2011-01-11 22:52:23 +01:00
2011-10-29 06:12:37 +02:00
// Process and combine queries
2009-06-05 05:44:28 +02:00
$querySQLs = array ();
$totalCount = 0 ;
2011-10-29 06:12:37 +02:00
foreach ( $lists as $class => $list ) {
$query = $list -> dataQuery () -> query ();
// There's no need to do all that joining
2012-05-03 09:34:16 +02:00
$query -> setFrom ( array ( str_replace ( array ( '"' , '`' ), '' , $baseClasses [ $class ]) => $baseClasses [ $class ]));
$query -> setSelect ( $select [ $class ]);
$query -> setOrderBy ( array ());
2011-10-29 06:12:37 +02:00
2009-06-05 05:44:28 +02:00
$querySQLs [] = $query -> sql ();
$totalCount += $query -> unlimitedRowCount ();
}
$fullQuery = implode ( " UNION " , $querySQLs ) . " ORDER BY $sortBy LIMIT $limit " ;
2011-01-11 22:52:23 +01:00
2009-06-05 05:44:28 +02:00
// Get records
$records = DB :: query ( $fullQuery );
2011-05-05 23:08:33 +02:00
$objects = array ();
2010-12-05 09:44:37 +01:00
2011-05-05 23:08:33 +02:00
foreach ( $records as $record ) {
$objects [] = new $record [ 'ClassName' ]( $record );
}
2010-12-05 09:44:20 +01:00
2011-05-05 23:08:33 +02:00
$list = new PaginatedList ( new ArrayList ( $objects ));
2011-04-01 06:10:27 +02:00
$list -> setPageStart ( $start );
$list -> setPageLEngth ( $pageLength );
$list -> setTotalItems ( $totalCount );
2011-01-11 22:52:23 +01:00
2011-04-01 06:10:27 +02:00
return $list ;
2009-06-05 05:44:28 +02:00
}
2011-01-11 22:52:23 +01:00
2009-03-11 23:24:50 +01:00
/**
2010-04-13 03:53:25 +02:00
* MySQL uses NOW () to return the current date / time .
2009-03-11 23:24:50 +01:00
*/
function now (){
return 'NOW()' ;
}
2011-01-11 22:52:23 +01:00
2009-09-17 02:05:23 +02:00
/*
* Returns the database - specific version of the random () function
*/
function random (){
return 'RAND()' ;
}
2011-01-11 22:52:23 +01:00
2010-05-25 06:21:21 +02:00
/*
* This is a lookup table for data types .
* For instance , Postgres uses 'INT' , while MySQL uses 'UNSIGNED'
* So this is a DB - specific list of equivilents .
*/
function dbDataType ( $type ){
$values = Array (
'unsigned integer' => 'UNSIGNED'
);
2011-01-11 22:52:23 +01:00
2010-05-25 06:21:21 +02:00
if ( isset ( $values [ $type ]))
return $values [ $type ];
else return '' ;
}
2011-01-11 22:52:23 +01:00
2009-03-11 22:48:30 +01:00
/*
2011-09-15 14:15:41 +02:00
* This will return text which has been escaped in a database - friendly manner .
2009-03-11 22:48:30 +01:00
*/
function addslashes ( $value ){
2011-10-29 02:34:11 +02:00
return $this -> dbConn -> real_escape_string ( $value );
2009-03-11 22:48:30 +01:00
}
2011-01-11 22:52:23 +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 );
2010-12-08 00:53:42 +01:00
$SQL_htmlEntityKeywords = Convert :: raw2sql ( htmlentities ( $keywords , ENT_NOQUOTES , 'UTF-8' ));
2009-05-21 07:08:11 +02:00
return " (MATCH ( $fieldNames ) AGAINST (' $SQL_keywords ' $boolean ) + MATCH ( $fieldNames ) AGAINST (' $SQL_htmlEntityKeywords ' $boolean )) " ;
}
2011-01-11 22:52:23 +01:00
2009-10-01 23:02:00 +02:00
/*
* Does this database support transactions ?
*/
public function supportsTransactions (){
return $this -> supportsTransactions ;
}
2011-01-11 22:52:23 +01:00
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 ;
}
2011-01-11 22:52:23 +01:00
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
*/
2011-01-11 22:52:23 +01:00
public function transactionStart ( $transaction_mode = false , $session_characteristics = false ){
2011-10-29 02:34:11 +02:00
// This sets the isolation level for the NEXT transaction, not the current one.
if ( $transaction_mode ) {
$this -> query ( 'SET TRANSACTION ' . $transaction_mode . ';' );
}
$this -> query ( 'START TRANSACTION;' );
if ( $session_characteristics ) {
$this -> query ( 'SET SESSION TRANSACTION ' . $session_characteristics . ';' );
}
2009-10-01 23:02:00 +02:00
}
2011-01-11 22:52:23 +01:00
2009-10-01 23:02:00 +02:00
/*
* Create a savepoint that you can jump back to if you encounter problems
*/
public function transactionSavepoint ( $savepoint ){
2011-10-29 02:34:11 +02:00
$this -> query ( " SAVEPOINT $savepoint ; " );
2009-10-01 23:02:00 +02:00
}
2011-01-11 22:52:23 +01:00
2009-10-01 23:02:00 +02:00
/*
* 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
*/
2011-10-29 02:34:11 +02:00
public function transactionRollback ( $savepoint = false ){
if ( $savepoint ) {
$this -> query ( 'ROLLBACK TO ' . $savepoint . ';' );
} else {
$this -> query ( 'ROLLBACK' );
}
2009-10-01 23:02:00 +02:00
}
2011-01-11 22:52:23 +01:00
2009-10-01 23:02:00 +02:00
/*
* Commit everything inside this transaction so far
*/
2011-10-29 02:34:11 +02:00
public function transactionEnd ( $chain = false ){
$this -> query ( 'COMMIT AND ' . ( $chain ? '' : 'NO ' ) . 'CHAIN;' );
2009-10-01 23:02:00 +02:00
}
2010-04-13 03:49:40 +02:00
/**
2010-04-13 04:28:13 +02:00
* Function to return an SQL datetime expression that can be used with MySQL
2010-04-13 03:49:40 +02:00
* 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
*/
function formattedDatetimeClause ( $date , $format ) {
preg_match_all ( '/%(.)/' , $format , $matches );
foreach ( $matches [ 1 ] as $match ) if ( array_search ( $match , array ( 'Y' , 'm' , 'd' , 'H' , 'i' , 's' , 'U' )) === false ) user_error ( 'formattedDatetimeClause(): unsupported format character %' . $match , E_USER_WARNING );
if ( preg_match ( '/^now$/i' , $date )) {
$date = " NOW() " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
$date = " ' $date ' " ;
}
if ( $format == '%U' ) return " UNIX_TIMESTAMP( $date ) " ;
2010-02-03 05:37:35 +01:00
2010-04-13 03:49:40 +02:00
return " DATE_FORMAT( $date , ' $format ') " ;
2010-02-03 05:37:35 +01:00
2010-04-13 03:49:40 +02:00
}
2010-02-03 05:37:35 +01:00
2010-04-13 03:49:40 +02:00
/**
2010-04-13 04:28:13 +02:00
* Function to return an SQL datetime expression that can be used with MySQL
2010-04-13 03:49:40 +02:00
* 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
*/
function datetimeIntervalClause ( $date , $interval ) {
$interval = preg_replace ( '/(year|month|day|hour|minute|second)s/i' , '$1' , $interval );
if ( preg_match ( '/^now$/i' , $date )) {
$date = " NOW() " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
$date = " ' $date ' " ;
}
return " $date + INTERVAL $interval " ;
}
/**
2010-04-13 04:28:13 +02:00
* Function to return an SQL datetime expression that can be used with MySQL
2010-04-13 03:49:40 +02:00
* 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
*/
function datetimeDifferenceClause ( $date1 , $date2 ) {
if ( preg_match ( '/^now$/i' , $date1 )) {
$date1 = " NOW() " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date1 )) {
$date1 = " ' $date1 ' " ;
}
if ( preg_match ( '/^now$/i' , $date2 )) {
$date2 = " NOW() " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date2 )) {
$date2 = " ' $date2 ' " ;
}
return " UNIX_TIMESTAMP( $date1 ) - UNIX_TIMESTAMP( $date2 ) " ;
}
2011-09-22 16:28:58 +02:00
function supportsLocks () {
return true ;
}
function canLock ( $name ) {
$id = $this -> getLockIdentifier ( $name );
return ( bool ) DB :: query ( sprintf ( " SELECT IS_FREE_LOCK('%s') " , $id )) -> value ();
}
function getLock ( $name , $timeout = 5 ) {
$id = $this -> getLockIdentifier ( $name );
// MySQL auto-releases existing locks on subsequent GET_LOCK() calls,
// in contrast to PostgreSQL and SQL Server who stack the locks.
return ( bool ) DB :: query ( sprintf ( " SELECT GET_LOCK('%s', %d) " , $id , $timeout )) -> value ();
}
function releaseLock ( $name ) {
$id = $this -> getLockIdentifier ( $name );
return ( bool ) DB :: query ( sprintf ( " SELECT RELEASE_LOCK('%s') " , $id )) -> value ();
}
protected function getLockIdentifier ( $name ) {
// Prefix with database name
return Convert :: raw2sql ( $this -> database . '_' . Convert :: raw2sql ( $name ));
}
2007-07-19 12:40:28 +02:00
}
/**
* A result - set from a MySQL database .
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
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 ;
2011-01-11 22:52:23 +01:00
2007-07-19 12:40:28 +02:00
/**
* The internal MySQL handle that points to the result set .
* @ var resource
*/
private $handle ;
/**
2012-03-24 04:38:57 +01:00
* Hook the result - set given into a Query class , suitable for use by SilverStripe .
2007-07-19 12:40:28 +02:00
* @ 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 ;
}
2011-01-11 22:52:23 +01:00
2010-10-19 07:08:59 +02:00
public function __destruct () {
2011-10-29 02:34:11 +02:00
if ( is_object ( $this -> handle )) $this -> handle -> free ();
2007-07-19 12:40:28 +02:00
}
2011-10-29 02:34:11 +02:00
2007-07-19 12:40:28 +02:00
public function seek ( $row ) {
2011-10-29 02:34:11 +02:00
if ( is_object ( $this -> handle )) return $this -> handle -> data_seek ( $row );
2007-07-19 12:40:28 +02:00
}
2011-10-29 02:34:11 +02:00
2007-07-19 12:40:28 +02:00
public function numRecords () {
2011-10-29 02:34:11 +02:00
if ( is_object ( $this -> handle )) return $this -> handle -> num_rows ;
2007-07-19 12:40:28 +02:00
}
2012-04-30 12:44:31 +02:00
2007-07-19 12:40:28 +02:00
public function nextRecord () {
2012-04-30 12:44:31 +02:00
if ( is_object ( $this -> handle ) && ( $data = $this -> handle -> fetch_assoc ())) {
return $data ;
2009-08-19 06:34:28 +02:00
} else {
return false ;
}
2007-07-19 12:40:28 +02:00
}
}