2009-02-25 06:44:52 +01:00
< ? php
2009-04-22 00:19:45 +02:00
2009-02-25 06:44:52 +01:00
/**
2009-04-22 00:19:45 +02:00
* @ package sapphire
* @ subpackage model
2009-02-25 06:44:52 +01:00
*/
2009-04-14 00:02:55 +02:00
2009-04-22 00:19:45 +02:00
/**
* MS SQL connector class .
* @ package sapphire
* @ subpackage model
*/
class MSSQLDatabase extends Database {
2009-02-25 06:44:52 +01: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-05-08 05:37:31 +02:00
/**
* Does this database have full - text index supprt
*/
2009-06-05 05:44:28 +02:00
protected $fullTextEnabled = true ;
2009-05-08 05:37:31 +02:00
2009-05-28 04:07:11 +02:00
/**
* If true , use the mssql_ ... functions .
* If false use the sqlsrv_ ... functions
*/
private $mssql = null ;
/**
* Sorts the last query ' s affected row count , for sqlsrv module only .
* @ todo This is a bit clumsy ; affectedRows () should be moved to { @ link Query } object , so that this isn ' t necessary .
*/
private $lastAffectedRows ;
2009-07-09 03:11:02 +02:00
/**
* Words that will trigger an error if passed to a SQL Server fulltext search
*/
protected $noiseWords = array ( " about " , " 1 " , " after " , " 2 " , " all " , " also " , " 3 " , " an " , " 4 " , " and " , " 5 " , " another " , " 6 " , " any " , " 7 " , " are " , " 8 " , " as " , " 9 " , " at " , " 0 " , " be " , " $ " , " because " , " been " , " before " , " being " , " between " , " both " , " but " , " by " , " came " , " can " , " come " , " could " , " did " , " do " , " does " , " each " , " else " , " for " , " from " , " get " , " got " , " has " , " had " , " he " , " have " , " her " , " here " , " him " , " himself " , " his " , " how " , " if " , " in " , " into " , " is " , " it " , " its " , " just " , " like " , " make " , " many " , " me " , " might " , " more " , " most " , " much " , " must " , " my " , " never " , " no " , " now " , " of " , " on " , " only " , " or " , " other " , " our " , " out " , " over " , " re " , " said " , " same " , " see " , " should " , " since " , " so " , " some " , " still " , " such " , " take " , " than " , " that " , " the " , " their " , " them " , " then " , " there " , " these " , " they " , " this " , " those " , " through " , " to " , " too " , " under " , " up " , " use " , " very " , " want " , " was " , " way " , " we " , " well " , " were " , " what " , " when " , " where " , " which " , " while " , " who " , " will " , " with " , " would " , " you " , " your " , " a " , " b " , " c " , " d " , " e " , " f " , " g " , " h " , " i " , " j " , " k " , " l " , " m " , " n " , " o " , " p " , " q " , " r " , " s " , " t " , " u " , " v " , " w " , " x " , " y " , " z " );
2009-02-25 06:44:52 +01:00
/**
* Connect to a MS SQL database .
* @ param array $parameters An map of parameters , which should include :
* - 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
*/
public function __construct ( $parameters ) {
2009-05-27 02:36:53 +02:00
parent :: __construct ();
2009-05-28 04:07:11 +02:00
if ( function_exists ( 'mssql_connect' )) {
$this -> mssql = true ;
} else if ( function_exists ( 'sqlsrv_connect' )) {
$this -> mssql = false ;
} else {
user_error ( " Neither the mssql_connect() nor the sqlsrv_connect() functions are available. Please install the PHP native mssql module, or the Microsoft-provided sqlsrv module. " , E_USER_ERROR );
}
if ( $this -> mssql ) {
$this -> dbConn = mssql_connect ( $parameters [ 'server' ], $parameters [ 'username' ], $parameters [ 'password' ]);
} else {
$this -> dbConn = sqlsrv_connect ( $parameters [ 'server' ], array (
'UID' => $parameters [ 'username' ],
'PWD' => $parameters [ 'password' ],
));
}
2009-05-27 02:36:53 +02:00
2009-02-25 06:44:52 +01:00
if ( ! $this -> dbConn ) {
$this -> databaseError ( " Couldn't connect to MS SQL database " );
2009-05-28 04:07:11 +02:00
2009-02-25 06:44:52 +01:00
} else {
$this -> database = $parameters [ 'database' ];
2009-05-27 02:36:53 +02:00
$this -> selectDatabase ( $this -> database );
2009-02-25 06:44:52 +01:00
2009-05-27 02:36:53 +02:00
// Configure the connection
$this -> query ( 'SET QUOTED_IDENTIFIER ON' );
2009-05-27 02:51:47 +02:00
$this -> query ( 'SET TEXTSIZE 2147483647' );
2009-05-27 02:36:53 +02:00
}
2009-02-25 06:44:52 +01:00
}
2009-05-28 04:07:11 +02:00
/**
* Throw a database error
*/
function databaseError ( $message , $errorLevel = E_USER_ERROR ) {
if ( ! $this -> mssql ) {
$errorMessages = array ();
foreach ( sqlsrv_errors () as $error ) {
$errorMessages [] = $error [ 'message' ];
}
$message .= " : \n " . implode ( " ; " , $errorMessages );
}
return parent :: databaseError ( $message , $errorLevel );
}
2009-03-17 04:58:58 +01:00
/**
* This will set up the full text search capabilities .
* Theoretically , you don 't need to ' enable ' it every time ...
*
* TODO : make this a _config . php setting
2009-03-24 23:07:47 +01:00
* TODO : VERY IMPORTANT : move this so it only gets called upon a dev / build action
2009-03-17 04:58:58 +01:00
*/
function createFullTextCatalog (){
2009-05-08 05:37:31 +02:00
if ( $this -> fullTextEnabled ) {
$this -> query ( " exec sp_fulltext_database 'enable'; " );
$result = $this -> query ( " SELECT name FROM sys.fulltext_catalogs WHERE name = 'ftCatalog'; " ) -> value ();
if ( ! $result ) $this -> query ( " CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT; " );
}
2009-03-17 04:58:58 +01:00
}
2009-02-25 06:44:52 +01:00
/**
* Not implemented , needed for PDO
*/
public function getConnect ( $parameters ) {
return null ;
}
/**
* Returns true if this database supports collations
* @ return boolean
*/
public function supportsCollations () {
return true ;
}
/**
* The version of MSSQL
* @ var float
*/
private $mssqlVersion ;
/**
2009-04-14 00:02:55 +02:00
* Get the version of MSSQL .
2009-04-22 00:19:45 +02:00
* NOTE : not yet implemented for MSSQL , we just return 2008 ; the minimum supported version
2009-02-25 06:44:52 +01:00
* @ return float
*/
public function getVersion () {
user_error ( " getVersion not implemented " , E_USER_WARNING );
return 2008 ;
}
/**
2009-04-14 00:02:55 +02:00
* Get the database server , namely mssql .
2009-02-25 06:44:52 +01:00
* @ return string
*/
public function getDatabaseServer () {
return " mssql " ;
}
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' ))) {
Debug :: message ( " Will execute: $sql " );
return ;
}
if ( isset ( $_REQUEST [ 'showqueries' ])) {
$starttime = microtime ( true );
}
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
$handle = mssql_query ( $sql , $this -> dbConn );
} else {
$handle = sqlsrv_query ( $this -> dbConn , $sql );
}
2009-04-09 00:11:38 +02:00
2009-02-25 06:44:52 +01:00
if ( isset ( $_REQUEST [ 'showqueries' ])) {
$endtime = round ( microtime ( true ) - $starttime , 4 );
Debug :: message ( " \n $sql\n { $endtime } ms \n " , false );
}
DB :: $lastQuery = $handle ;
if ( ! $handle && $errorLevel ) $this -> databaseError ( " Couldn't run query: $sql " , $errorLevel );
2009-06-02 03:33:10 +02:00
if ( ! $this -> mssql ) {
$this -> lastAffectedRows = sqlsrv_rows_affected ( $handle );
}
2009-05-28 04:07:11 +02:00
return new MSSQLQuery ( $this , $handle , $this -> mssql );
2009-02-25 06:44:52 +01:00
}
public function getGeneratedID ( $table ) {
2009-03-30 05:06:25 +02:00
//return $this->query("SELECT @@IDENTITY FROM \"$table\"")->value();
return $this -> query ( " SELECT IDENT_CURRENT(' $table ') " ) -> value ();
2009-02-25 06:44:52 +01:00
}
2009-04-22 00:19:45 +02:00
/*
* This is a handy helper function which will return the primary key for any paricular table
* In MSSQL , the primary key is often an internal identifier , NOT the standard name ( ie , 'ID' ),
* so we need to do a lookup for it .
*/
2009-03-23 01:51:28 +01:00
function getPrimaryKey ( $tableName ){
$indexes = DB :: query ( " EXEC sp_helpindex ' $tableName '; " );
$primary_key = '' ;
foreach ( $indexes as $this_index ){
if ( $this_index [ 'index_keys' ] == 'ID' ){
$primary_key = $this_index [ 'index_name' ];
break ;
}
}
return $primary_key ;
}
2009-04-28 07:13:23 +02:00
2009-02-25 06:44:52 +01:00
/**
* OBSOLETE : Get the ID for the next new record for the table .
*
* @ var string $table The name od the table .
2009-04-22 00:19:45 +02:00
*
2009-02-25 06:44:52 +01:00
* @ return int
*/
public function getNextID ( $table ) {
user_error ( 'getNextID is OBSOLETE (and will no longer work properly)' , E_USER_WARNING );
$result = $this -> query ( " SELECT MAX(ID)+1 FROM \" $table\ " " )->value();
return $result ? $result : 1 ;
}
public function isActive () {
return $this -> active ? true : false ;
}
2009-04-22 00:19:45 +02:00
/*
* TODO : test this , as far as I know we haven ' t got the create method working ...
2009-06-04 05:02:35 +02:00
* 4 th Jun 09 / Tom Rix - worked for me
2009-04-22 00:19:45 +02:00
*/
2009-02-25 06:44:52 +01:00
public function createDatabase () {
2009-05-28 04:07:11 +02:00
$this -> query ( " CREATE DATABASE \" $this->database\ " " );
2009-06-04 01:35:24 +02:00
$this -> selectDatabase ( $this -> database );
2009-02-25 06:44:52 +01:00
}
/**
* Drop the database that this object is currently connected to .
* Use with caution .
*/
public function dropDatabase () {
2009-06-16 04:13:00 +02:00
$db = $this -> database ;
2009-05-28 04:07:11 +02:00
$this -> selectDatabase ( 'master' );
2009-06-16 04:13:00 +02:00
$this -> query ( " DROP DATABASE \" $db\ " " );
2009-05-28 04:07:11 +02:00
$this -> active = false ;
2009-02-25 06:44:52 +01: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 ;
2009-04-28 07:33:26 +02:00
if ( $this -> databaseExists ( $this -> database )) {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
if ( mssql_select_db ( $this -> database , $this -> dbConn )) {
$this -> active = true ;
}
} else {
$this -> query ( " USE \" $this->database\ " " );
2009-04-28 07:41:43 +02:00
$this -> active = true ;
}
2009-04-28 07:33:26 +02:00
}
2009-02-25 06:44:52 +01:00
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
}
/**
* Returns true if the named database exists .
2009-04-28 07:33:26 +02:00
* @ return boolean
2009-02-25 06:44:52 +01:00
*/
public function databaseExists ( $name ) {
2009-04-28 07:13:23 +02:00
$listDBs = $this -> query ( 'SELECT NAME FROM sys.sysdatabases' );
2009-04-28 07:33:26 +02:00
if ( $listDBs ) {
foreach ( $listDBs as $listedDB ) {
if ( $listedDB [ 'NAME' ] == $name ) return true ;
}
2009-04-28 07:13:23 +02:00
}
return false ;
2009-02-25 06:44:52 +01:00
}
2009-05-21 07:10:46 +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 22:33:11 +02:00
public function createTable ( $tableName , $fields = null , $indexes = null , $options = null ) {
2009-02-25 06:44:52 +01:00
$fieldSchemas = $indexSchemas = " " ;
if ( $fields ) foreach ( $fields as $k => $v ) $fieldSchemas .= " \" $k\ " $v , \n " ;
2009-05-21 07:10:46 +02:00
// Temporary tables start with "#" in MSSQL-land
if ( ! empty ( $options [ 'temporary' ])) $tableName = " # $tableName " ;
2009-02-25 06:44:52 +01:00
$this -> query ( " CREATE TABLE \" $tableName\ " (
$fieldSchemas
primary key ( \ " ID \" )
2009-03-17 04:58:58 +01:00
); " );
//we need to generate indexes like this: CREATE INDEX IX_vault_to_export ON vault (to_export);
//This needs to be done AFTER the table creation, so we can set up the fulltext indexes correctly
2009-05-13 01:07:21 +02:00
if ( $indexes ) foreach ( $indexes as $k => $v ) {
$indexSchemas .= $this -> getIndexSqlDefinition ( $tableName , $k , $v ) . " \n " ;
}
if ( $indexSchemas ) $this -> query ( $indexSchemas );
2009-05-21 07:10:46 +02:00
return $tableName ;
2009-02-25 06:44:52 +01: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
*/
public function alterTable ( $tableName , $newFields = null , $newIndexes = null , $alteredFields = null , $alteredIndexes = null ) {
$fieldSchemas = $indexSchemas = " " ;
2009-04-22 00:19:45 +02:00
$alterList = array ();
2009-04-22 01:14:43 +02:00
if ( $newFields ) foreach ( $newFields as $k => $v ) $alterList [] .= " ALTER TABLE \" $tableName\ " ADD \ " $k\ " $v " ;
2009-02-25 06:44:52 +01:00
2009-04-22 00:19:45 +02:00
$indexList = $this -> IndexList ( $tableName );
2009-04-01 05:37:39 +02:00
2009-02-25 06:44:52 +01:00
if ( $alteredFields ) {
foreach ( $alteredFields as $k => $v ) {
2009-04-22 00:19:45 +02:00
$val = $this -> alterTableAlterColumn ( $tableName , $k , $v , $indexList );
if ( $val != '' )
$alterList [] .= $val ;
2009-02-25 06:44:52 +01:00
}
}
2009-03-17 04:58:58 +01:00
//DB ABSTRACTION: we need to change the constraints to be a separate 'add' command,
2009-04-22 00:19:45 +02:00
$alterIndexList = Array ();
2009-03-17 04:58:58 +01:00
if ( $alteredIndexes ) foreach ( $alteredIndexes as $v ) {
2009-04-01 05:37:39 +02:00
//TODO: I don't think that these drop index commands will work:
2009-03-17 04:58:58 +01:00
if ( $v [ 'type' ] != 'fulltext' ){
if ( is_array ( $v ))
$alterIndexList [] = 'DROP INDEX ix_' . strtolower ( $tableName ) . '_' . strtolower ( $v [ 'value' ]) . ' ON ' . $tableName . ';' ;
else
$alterIndexList [] = 'DROP INDEX ix_' . strtolower ( $tableName ) . '_' . strtolower ( trim ( $v , '()' )) . ' ON ' . $tableName . ';' ;
if ( is_array ( $v ))
$k = $v [ 'value' ];
else $k = trim ( $v , '()' );
$alterIndexList [] = $this -> getIndexSqlDefinition ( $tableName , $k , $v );
}
}
//Add the new indexes:
if ( $newIndexes ) foreach ( $newIndexes as $k => $v ){
$alterIndexList [] = $this -> getIndexSqlDefinition ( $tableName , $k , $v );
2009-02-25 06:44:52 +01:00
}
2009-04-22 00:19:45 +02:00
if ( $alterList ) {
foreach ( $alterList as $this_alteration ){
2009-04-22 01:14:43 +02:00
if ( $this_alteration != '' ){
$this -> query ( $this_alteration );
}
2009-04-22 00:19:45 +02:00
}
2009-02-25 06:44:52 +01:00
}
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
foreach ( $alterIndexList as $alteration ) {
if ( $alteration != '' ) $this -> query ( $alteration );
}
2009-02-25 06:44:52 +01:00
}
2009-04-06 00:23:58 +02:00
/*
* This is a private MSSQL - only function which returns specific details about a column ' s constraints ( if any )
*/
private function ColumnConstraints ( $tableName , $columnName ){
2009-05-06 02:02:43 +02:00
$constraint = $this -> query ( " SELECT CC.CONSTRAINT_NAME, CAST(CHECK_CLAUSE AS TEXT) AS CHECK_CLAUSE, COLUMN_NAME FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS AS CC INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS CCU ON CCU.CONSTRAINT_NAME=CC.CONSTRAINT_NAME WHERE TABLE_NAME=' $tableName ' AND COLUMN_NAME=' " . $columnName . " '; " ) -> first ();
2009-04-06 00:23:58 +02:00
return $constraint ;
}
/*
* Get the actual enum fields from the constraint value :
*/
private function EnumValuesFromConstraint ( $constraint ){
2009-06-10 03:21:15 +02:00
$segments = preg_split ( '/ +OR *\[/i' , $constraint );
2009-04-06 00:23:58 +02:00
$constraints = Array ();
foreach ( $segments as $this_segment ){
2009-06-10 03:21:15 +02:00
$bits = preg_split ( '/ *= */' , $this_segment );
2009-04-06 00:23:58 +02:00
for ( $i = 1 ; $i < sizeof ( $bits ); $i += 2 )
array_unshift ( $constraints , substr ( rtrim ( $bits [ $i ], ')' ), 1 , - 1 ));
}
return $constraints ;
}
2009-02-25 06:44:52 +01:00
/*
* Creates an ALTER expression for a column in MS SQL
*
* @ param $tableName Name of the table to be altered
* @ param $colName Name of the column to be altered
* @ param $colSpec String which contains conditions for a column
* @ return string
*/
2009-04-01 05:37:39 +02:00
private function alterTableAlterColumn ( $tableName , $colName , $colSpec , $indexList ){
2009-05-19 04:24:27 +02:00
2009-02-25 06:44:52 +01:00
// First, we split the column specifications into parts
// TODO: this returns an empty array for the following string: int(11) not null auto_increment
// on second thoughts, why is an auto_increment field being passed through?
2009-05-07 06:55:09 +02:00
$pattern = '/^([\w()]+)\s?((?:not\s)?null)?\s?(default\s[\w\']+)?\s?(check\s?[\w()\'",\s]+)?$/i' ;
2009-05-19 04:24:27 +02:00
$matches = Array ();
2009-02-25 06:44:52 +01:00
preg_match ( $pattern , $colSpec , $matches );
2009-04-01 05:37:39 +02:00
//if($matches[1]=='serial8')
// return '';
//drop the index if it exists:
$alterCol = '' ;
if ( isset ( $indexList [ $colName ])){
2009-04-22 01:14:43 +02:00
$alterCol = " \n DROP INDEX \" $tableName\ " . ix_ { $tableName } _ { $colName }; " ;
2009-04-01 05:37:39 +02:00
}
2009-04-22 00:19:45 +02:00
$prefix = " ALTER TABLE \" " . $tableName . " \" " ;
2009-02-25 06:44:52 +01:00
if ( isset ( $matches [ 1 ])) {
2009-04-01 05:37:39 +02:00
$alterCol .= " \n $prefix ALTER COLUMN \" $colName\ " $matches [ 1 ] \n " ;
2009-02-25 06:44:52 +01:00
// SET null / not null
2009-04-01 05:37:39 +02:00
if ( ! empty ( $matches [ 2 ])) $alterCol .= " ; \n $prefix ALTER COLUMN \" $colName\ " $matches [ 1 ] $matches [ 2 ] " ;
2009-02-25 06:44:52 +01:00
// SET default (we drop it first, for reasons of precaution)
2009-04-01 05:37:39 +02:00
//TODO: changing default values not implemented yet:
2009-02-25 06:44:52 +01:00
if ( ! empty ( $matches [ 3 ])) {
2009-05-18 23:23:41 +02:00
$constraint_name = " { $tableName } _ { $colName } _default " ;
2009-04-01 05:37:39 +02:00
//$alterCol .= ";\n$prefix ALTER COLUMN \"$colName\" DROP DEFAULT";
//$alterCol .= ";\n$prefix ALTER COLUMN \"$colName\" SET $matches[3]";
2009-05-19 04:24:27 +02:00
/* $constraint_query = "
2009-05-18 23:23:41 +02:00
SELECT OBJECT_NAME ( OBJECT_ID ) AS NameofConstraint ,
SCHEMA_NAME ( schema_id ) AS SchemaName ,
OBJECT_NAME ( parent_object_id ) AS TableName ,
type_desc AS ConstraintType
FROM sys . objects
WHERE type_desc LIKE '%CONSTRAINT' AND OBJECT_NAME ( parent_object_id ) = '{$tableName}' AND type_desc = 'DEFAULT_CONSTRAINT' AND OBJECT_NAME ( OBJECT_ID ) = '$constraint_name' " ;
$constraint_result = $this -> query ( $constraint_query ) -> first ();
if ( $constraint_result ){
//$alterCol.= ";\n$prefix DROP DEFAULT";
$alterCol .= " ; \n $prefix DROP CONSTRAINT $constraint_name " ;
}
2009-05-19 04:24:27 +02:00
$alterCol .= " ; \n $prefix ADD CONSTRAINT \" $constraint_name\ " { $matches [ 3 ]} FOR $colName " ;*/
2009-02-25 06:44:52 +01:00
}
// SET check constraint (The constraint HAS to be dropped)
if ( ! empty ( $matches [ 4 ])) {
2009-04-06 00:23:58 +02:00
$constraint = $this -> ColumnConstraints ( $tableName , $colName );
2009-04-01 05:37:39 +02:00
if ( $constraint )
$alterCol .= " ; \n $prefix DROP CONSTRAINT { $constraint [ 'CONSTRAINT_NAME' ] } " ;
2009-05-18 23:23:41 +02:00
//NOTE: 'with nocheck' seems to solve a few problems I've been having for modifying existing tables.
$alterCol .= " ; \n $prefix WITH NOCHECK ADD CONSTRAINT \" { $tableName } _ { $colName } _check \" $matches[4] " ;
2009-05-19 04:24:27 +02:00
2009-02-25 06:44:52 +01:00
}
}
return isset ( $alterCol ) ? $alterCol : '' ;
}
public function renameTable ( $oldTableName , $newTableName ) {
2009-06-15 03:34:22 +02:00
$this -> query ( " EXEC sp_rename \" $oldTableName\ " , \ " $newTableName\ " " );
2009-02-25 06:44:52 +01:00
}
/**
* Checks a table ' s integrity and repairs it if necessary .
2009-04-22 00:19:45 +02:00
* NOTE : MSSQL does not appear to support any vacuum or optimise commands
*
2009-02-25 06:44:52 +01:00
* @ 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 ) {
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 ) {
$this -> query ( " ALTER TABLE \" $tableName\ " ADD \ " $fieldName\ " $fieldSpec " );
}
/**
* 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 ) {
$this -> query ( " ALTER TABLE \" $tableName\ " CHANGE \ " $fieldName\ " \ " $fieldName\ " $fieldSpec " );
}
/**
* 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 )) {
2009-05-07 08:32:08 +02:00
$this -> query ( " EXEC sp_rename @objname = ' $tableName . $oldName ', @newname = ' $newName ', @objtype = 'COLUMN' " );
2009-02-25 06:44:52 +01:00
}
}
public function fieldList ( $table ) {
//This gets us more information than we need, but I've included it all for the moment....
$fields = $this -> query ( " SELECT ordinal_position, column_name, data_type, column_default, is_nullable, character_maximum_length, numeric_precision FROM information_schema.columns WHERE table_name = ' $table ' ORDER BY ordinal_position; " );
$output = array ();
if ( $fields ) foreach ( $fields as $field ) {
2009-03-12 03:43:10 +01:00
switch ( $field [ 'data_type' ]){
case 'varchar' :
//Check to see if there's a constraint attached to this column:
2009-04-06 00:23:58 +02:00
$constraint = $this -> ColumnConstraints ( $table , $field [ 'column_name' ]);
2009-03-12 03:43:10 +01:00
if ( $constraint ){
2009-04-06 00:23:58 +02:00
$constraints = $this -> EnumValuesFromConstraint ( $constraint [ 'CHECK_CLAUSE' ]);
2009-03-12 03:43:10 +01:00
$default = substr ( $field [ 'column_default' ], 2 , - 2 );
2009-05-18 23:23:41 +02:00
$field [ 'data_type' ] = $this -> enum ( Array ( 'default' => $default , 'name' => $field [ 'column_name' ], 'enums' => $constraints , 'table' => $table ));
2009-03-12 03:43:10 +01:00
}
$output [ $field [ 'column_name' ]] = $field ;
2009-02-25 06:44:52 +01:00
break ;
default :
$output [ $field [ 'column_name' ]] = $field ;
}
}
return $output ;
}
/**
* Create an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
* @ param string $indexSpec The specification of the index , see Database :: requireIndex () for more details .
*/
public function createIndex ( $tableName , $indexName , $indexSpec ) {
$this -> query ( $this -> getIndexSqlDefinition ( $tableName , $indexName , $indexSpec ));
}
/*
* 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
* arrays to be created .
*/
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 ;
}
}
return $indexSpec ;
}
protected function getIndexSqlDefinition ( $tableName , $indexName , $indexSpec ) {
2009-03-29 23:28:55 +02:00
2009-02-25 06:44:52 +01:00
if ( ! is_array ( $indexSpec )){
$indexSpec = trim ( $indexSpec , '()' );
$bits = explode ( ',' , $indexSpec );
$indexes = " \" " . implode ( " \" , \" " , $bits ) . " \" " ;
return 'create index ix_' . $tableName . '_' . $indexName . " ON \" " . $tableName . " \" ( " . $indexes . " ); " ;
} else {
//create a type-specific index
2009-03-17 04:58:58 +01:00
if ( $indexSpec [ 'type' ] == 'fulltext' ){
2009-05-08 05:37:31 +02:00
if ( $this -> fullTextEnabled ) {
//Enable full text search.
$this -> createFullTextCatalog ();
2009-04-01 05:37:39 +02:00
2009-05-08 05:37:31 +02:00
$primary_key = $this -> getPrimaryKey ( $tableName );
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
//First, we need to see if a full text search already exists:
$result = $this -> query ( " SELECT object_id FROM sys.fulltext_indexes WHERE object_id=object_id(' $tableName '); " ) -> first ();
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
$drop = '' ;
if ( $result )
$drop = " DROP FULLTEXT INDEX ON \" " . $tableName . " \" ; " ;
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
return $drop . " CREATE FULLTEXT INDEX ON \" $tableName\ " ({ $indexSpec [ 'value' ]}) " .
" KEY INDEX $primary_key WITH CHANGE_TRACKING AUTO; " ;
}
2009-03-17 04:58:58 +01:00
}
2009-02-25 06:44:52 +01:00
if ( $indexSpec [ 'type' ] == 'unique' )
return 'create unique index ix_' . $tableName . '_' . $indexName . " ON \" " . $tableName . " \" ( \" " . $indexSpec [ 'value' ] . " \" ); " ;
}
}
2009-03-11 22:47:06 +01:00
function getDbSqlDefinition ( $tableName , $indexName , $indexSpec ){
return $indexName ;
}
2009-02-25 06:44:52 +01:00
/**
* Alter an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
* @ param string $indexSpec The specification of the index , see Database :: requireIndex () for more details .
*/
public function alterIndex ( $tableName , $indexName , $indexSpec ) {
$indexSpec = trim ( $indexSpec );
if ( $indexSpec [ 0 ] != '(' ) {
list ( $indexType , $indexFields ) = explode ( ' ' , $indexSpec , 2 );
} else {
$indexFields = $indexSpec ;
}
if ( ! $indexType ) {
$indexType = " index " ;
}
2009-03-17 04:58:58 +01:00
$this -> query ( " DROP INDEX $indexName ON $tableName ; " );
2009-02-25 06:44:52 +01:00
$this -> query ( " ALTER TABLE \" $tableName\ " ADD $indexType \ " $indexName\ " $indexFields " );
}
/**
* Return the list of indexes in a table .
* @ param string $table The table name .
* @ return array
*/
public function indexList ( $table ) {
2009-03-17 04:58:58 +01:00
$indexes = DB :: query ( " EXEC sp_helpindex ' $table '; " );
2009-03-24 23:07:47 +01:00
$prefix = '' ;
2009-06-15 08:41:36 +02:00
$indexList = array ();
2009-03-17 04:58:58 +01:00
foreach ( $indexes as $index ) {
2009-03-29 23:28:55 +02:00
2009-03-17 04:58:58 +01:00
//Check for uniques:
if ( strpos ( $index [ 'index_description' ], 'unique' ) !== false )
$prefix = 'unique ' ;
$key = str_replace ( ', ' , ',' , $index [ 'index_keys' ]);
$indexList [ $key ][ 'indexname' ] = $index [ 'index_name' ];
$indexList [ $key ][ 'spec' ] = $prefix . '(' . $key . ')' ;
}
//Now we need to check to see if we have any fulltext indexes attached to this table:
2009-05-08 05:37:31 +02:00
if ( $this -> fullTextEnabled ) {
$result = DB :: query ( 'EXEC sp_help_fulltext_columns;' );
$columns = '' ;
foreach ( $result as $row ){
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
if ( $row [ 'TABLE_NAME' ] == $table )
$columns .= $row [ 'FULLTEXT_COLUMN_NAME' ] . ',' ;
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
}
2009-03-17 04:58:58 +01:00
2009-05-08 05:37:31 +02:00
if ( $columns != '' ){
$columns = trim ( $columns , ',' );
$indexList [ 'SearchFields' ][ 'indexname' ] = 'SearchFields' ;
$indexList [ 'SearchFields' ][ 'spec' ] = 'fulltext (' . $columns . ')' ;
}
2009-06-15 08:41:36 +02:00
}
2009-02-25 06:44:52 +01:00
2009-06-15 08:41:36 +02:00
return $indexList ;
2009-02-25 06:44:52 +01:00
}
/**
* Returns a list of all the tables in the database .
* Table names will all be in lowercase .
* @ return array
*/
public function tableList () {
2009-05-07 05:47:59 +02:00
$tables = array ();
foreach ( $this -> query ( " EXEC sp_tables @table_owner = 'dbo'; " ) as $record ) {
2009-02-25 06:44:52 +01:00
$table = strtolower ( $record [ 'TABLE_NAME' ]);
$tables [ $table ] = $table ;
}
2009-05-07 05:47:59 +02:00
return $tables ;
}
/**
* Empty the given table of call contentTR
*/
public function clearTable ( $table ) {
$this -> query ( " TRUNCATE TABLE \" $table\ " " );
2009-02-25 06:44:52 +01:00
}
/**
* Return the number of rows affected by the previous operation .
* @ return int
*/
public function affectedRows () {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
return mssql_rows_affected ( $this -> dbConn );
} else {
return $this -> lastAffectedRows ;
}
2009-02-25 06:44:52 +01:00
}
/**
* A function to return the field names and datatypes for the particular table
*/
public function tableDetails ( $tableName ){
user_error ( " tableDetails not implemented " , E_USER_WARNING );
return array ();
/*
$query = " SELECT a.attname as \" Column \" , pg_catalog.format_type(a.atttypid, a.atttypmod) as \" Datatype \" FROM pg_catalog.pg_attribute a WHERE a.attnum > 0 AND NOT a.attisdropped AND a.attrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname ~ '^( $tableName ) $ ' AND pg_catalog.pg_table_is_visible(c.oid)); " ;
$result = DB :: query ( $query );
$table = Array ();
while ( $row = pg_fetch_assoc ( $result )){
$table [] = Array ( 'Column' => $row [ 'Column' ], 'DataType' => $row [ 'DataType' ]);
}
return $table ;
*/
}
/**
* Return a boolean type - formatted string
2009-03-17 04:58:58 +01:00
* We use 'bit' so that we can do numeric - based comparisons
2009-02-25 06:44:52 +01:00
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function boolean ( $values , $asDbValue = false ){
//Annoyingly, we need to do a good ol' fashioned switch here:
( $values [ 'default' ]) ? $default = '1' : $default = '0' ;
2009-03-12 03:43:10 +01:00
if ( $asDbValue )
return 'bit' ;
else
return 'bit not null default ' . $default ;
2009-02-25 06:44:52 +01:00
}
/**
2009-04-28 07:13:23 +02:00
* Return a date type - formatted string .
2009-02-25 06:44:52 +01:00
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
2009-03-12 03:43:10 +01:00
public function date ( $values , $asDbValue = false ){
if ( $asDbValue )
2009-05-27 02:36:53 +02:00
return 'datetime' ;
2009-03-12 03:43:10 +01:00
else
2009-05-27 02:36:53 +02:00
return 'datetime null' ;
2009-02-25 06:44:52 +01:00
}
/**
* Return a decimal type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function decimal ( $values , $asDbValue = false ){
// Avoid empty strings being put in the db
if ( $values [ 'precision' ] == '' ) {
$precision = 1 ;
} else {
$precision = $values [ 'precision' ];
}
if ( $asDbValue )
2009-03-12 03:43:10 +01:00
return Array ( 'data_type' => 'decimal' , 'numeric_precision' => '9,2' );
2009-05-07 07:55:05 +02:00
else return 'decimal(' . $precision . ') not null default 0' ;
2009-02-25 06:44:52 +01:00
}
/**
* Return a enum type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function enum ( $values ){
//Enums are a bit different. We'll be creating a varchar(255) with a constraint of all the usual enum options.
//NOTE: In this one instance, we are including the table name in the values array
2009-05-19 04:24:27 +02:00
return " varchar(255) not null default ' " . $values [ 'default' ] . " ' check( \" " . $values [ 'name' ] . " \" in (' " . implode ( '\', \'' , $values [ 'enums' ]) . " ')) " ;
//return "varchar(255) not null check(\"" . $values['name'] . "\" in ('" . implode('\', \'', $values['enums']) . "')); add constraint \"" . $values['table'] . '_' . $values['name'] . "_default\" default '" . $values['default'] . "' for \"" . $values['name'] . "\"";
2009-02-25 06:44:52 +01:00
}
/**
2009-04-28 07:13:23 +02:00
* Return a float type - formatted string .
2009-02-25 06:44:52 +01:00
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function float ( $values , $asDbValue = false ){
if ( $asDbValue )
2009-03-12 03:43:10 +01:00
return Array ( 'data_type' => 'float' );
2009-02-25 06:44:52 +01:00
else return 'float' ;
}
/**
* Return a int type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function int ( $values , $asDbValue = false ){
//We'll be using an 8 digit precision to keep it in line with the serial8 datatype for ID columns
if ( $asDbValue )
2009-06-25 04:27:02 +02:00
return Array ( 'data_type' => 'numeric' , 'numeric_precision' => '8' , 'default' => ( int ) $values [ 'default' ]);
2009-02-25 06:44:52 +01:00
else
return 'numeric(8) not null default ' . ( int ) $values [ 'default' ];
}
/**
* Return a datetime type - formatted string
* For MS SQL , we simply return the word 'timestamp' , no other parameters are necessary
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function ssdatetime ( $values , $asDbValue = false ){
if ( $asDbValue )
2009-03-12 03:43:10 +01:00
return Array ( 'data_type' => 'datetime' );
2009-02-25 06:44:52 +01:00
else
2009-03-12 00:04:01 +01:00
return 'datetime null' ;
2009-02-25 06:44:52 +01:00
}
/**
* Return a text type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function text ( $values , $asDbValue = false ){
if ( $asDbValue )
2009-05-27 02:50:48 +02:00
return Array ( 'data_type' => 'varchar(max)' );
2009-02-25 06:44:52 +01:00
else
2009-05-27 02:50:48 +02:00
return 'varchar(max) null' ;
2009-02-25 06:44:52 +01:00
}
/**
2009-04-28 07:13:23 +02:00
* Return a time type - formatted string .
2009-02-25 06:44:52 +01:00
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function time ( $values ){
2009-05-16 03:33:45 +02:00
return 'time null' ;
2009-02-25 06:44:52 +01:00
}
/**
* Return a varchar type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function varchar ( $values , $asDbValue = false ){
if ( $asDbValue )
2009-03-12 03:43:10 +01:00
return Array ( 'data_type' => 'varchar' , 'character_maximum_length' => '255' );
2009-02-25 06:44:52 +01:00
else
2009-03-10 02:19:46 +01:00
return 'varchar(' . $values [ 'precision' ] . ') null' ;
2009-02-25 06:44:52 +01:00
}
2009-04-28 07:13:23 +02:00
/**
* Return a 4 digit numeric type .
* @ return string
2009-02-25 06:44:52 +01:00
*/
public function year ( $values , $asDbValue = false ){
if ( $asDbValue )
return Array ( 'data_type' => 'numeric' , 'numeric_precision' => '4' );
else return 'numeric(4)' ;
}
function escape_character ( $escape = false ){
if ( $escape )
return " \\ \" " ;
else
return " \" " ;
}
/**
2009-04-28 07:13:23 +02:00
* Create a fulltext search datatype for MSSQL .
2009-02-25 06:44:52 +01:00
*
* @ param array $spec
*/
function fulltext ( $table , $spec ){
//$spec['name'] is the column we've created that holds all the words we want to index.
//This is a coalesced collection of multiple columns if necessary
2009-03-17 04:58:58 +01:00
//$spec='create index ix_' . $table . '_' . $spec['name'] . ' on ' . $table . ' using gist(' . $spec['name'] . ');';
2009-02-25 06:44:52 +01:00
2009-03-17 04:58:58 +01:00
//return $spec;
echo '<span style="color: Red">full text just got called!</span><br>' ;
return '' ;
2009-02-25 06:44:52 +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
*/
2009-03-11 22:47:06 +01:00
function IdColumn ( $asDbValue = false , $hasAutoIncPK = true ){
2009-03-12 03:43:10 +01:00
if ( $asDbValue )
return 'bigint' ;
else {
if ( $hasAutoIncPK )
return 'bigint identity(1,1)' ;
else return 'bigint' ;
}
2009-02-25 06:44:52 +01:00
}
2009-03-12 00:04:01 +01:00
/**
* Returns the SQL command to get all the tables in this database
*/
function allTablesSQL (){
2009-03-24 23:07:47 +01:00
return " SELECT name FROM { $this -> database } ..sysobjects WHERE xtype = 'U'; " ;
2009-03-12 00:04:01 +01:00
}
2009-02-25 06:44:52 +01:00
/**
* Returns true if this table exists
* @ todo Make a proper implementation
*/
function hasTable ( $tableName ) {
return true ;
}
/**
2009-04-06 00:23:58 +02:00
* Returns the values of the given enum field
* NOTE : Experimental ; introduced for db - abstraction and may changed before 2.4 is released .
2009-02-25 06:44:52 +01:00
*/
2009-04-06 00:23:58 +02:00
public function enumValuesForField ( $tableName , $fieldName ) {
// Get the enum of all page types from the SiteTree table
$constraints = $this -> ColumnConstraints ( $tableName , $fieldName );
$classes = Array ();
if ( $constraints ){
$constraints = $this -> EnumValuesFromConstraint ( $constraints [ 'CHECK_CLAUSE' ]);
$classes = $constraints ;
}
return $classes ;
2009-02-25 06:44:52 +01:00
}
2009-03-11 22:47:06 +01:00
/**
* Because NOW () doesn ' t always work ...
* MSSQL , I ' m looking at you
*
*/
function now (){
return 'CURRENT_TIMESTAMP' ;
}
2009-02-25 06:44:52 +01:00
/**
2009-04-28 07:13:23 +02:00
* Convert a SQLQuery object into a SQL statement .
2009-02-25 06:44:52 +01:00
*/
public function sqlQueryToString ( SQLQuery $sqlQuery ) {
if ( ! $sqlQuery -> from ) return '' ;
2009-03-23 01:51:28 +01:00
2009-05-07 07:55:05 +02:00
if ( $sqlQuery -> orderby && strtoupper ( trim ( $sqlQuery -> orderby )) == 'RAND()' ) $sqlQuery -> orderby = " NEWID() " ;
2009-03-23 01:51:28 +01:00
//Get the limit and offset
$limit = '' ;
$offset = '0' ;
if ( is_array ( $sqlQuery -> limit )){
$limit = $sqlQuery -> limit [ 'limit' ];
if ( isset ( $sqlQuery -> limit [ 'start' ]))
$offset = $sqlQuery -> limit [ 'start' ];
2009-06-08 05:45:54 +02:00
} else if ( preg_match ( '/^([0-9]+) offset ([0-9]+)$/i' , trim ( $sqlQuery -> limit ), $matches )) {
$limit = $matches [ 1 ];
$offset = $matches [ 2 ];
2009-03-23 01:51:28 +01:00
} else {
//could be a comma delimited string
$bits = explode ( ',' , $sqlQuery -> limit );
2009-06-16 06:04:01 +02:00
if ( sizeof ( $bits ) > 1 ) {
list ( $offset , $limit ) = $bits ;
} else {
$limit = $bits [ 0 ];
}
2009-03-23 01:51:28 +01:00
}
2009-06-16 04:51:42 +02:00
$text = '' ;
$suffixText = '' ;
$nestedQuery = false ;
// DELETE queries
if ( $sqlQuery -> delete ) {
2009-03-23 01:51:28 +01:00
$text = 'DELETE ' ;
2009-06-16 04:51:42 +02:00
// SELECT queries
} else {
$distinct = $sqlQuery -> distinct ? " DISTINCT " : " " ;
// If there's a limit but no offset, just use 'TOP X'
// rather than the more complex sub-select method
if ( $limit != 0 && $offset == 0 ) {
2009-06-29 02:08:22 +02:00
$text = " SELECT $distinct TOP $limit " ;
2009-06-16 04:51:42 +02:00
// If there's a limit and an offset, then we need to do a subselect
} else if ( $limit && $offset ) {
2009-06-16 06:04:01 +02:00
if ( $sqlQuery -> orderby ) {
$rowNumber = " ROW_NUMBER() OVER (ORDER BY $sqlQuery->orderby ) AS Number " ;
} else {
$firstCol = reset ( $sqlQuery -> select );
$rowNumber = " ROW_NUMBER() OVER (ORDER BY $firstCol ) AS Number " ;
}
2009-06-16 07:32:31 +02:00
$text = " SELECT * FROM ( SELECT $distinct $rowNumber , " ;
2009-06-16 06:04:01 +02:00
$suffixText .= " ) AS Numbered WHERE Number BETWEEN " . ( $offset + 1 ) . " AND " . ( $offset + $limit )
2009-06-16 04:51:42 +02:00
. " ORDER BY Number " ;
$nestedQuery = true ;
// Otherwise a simple query
} else {
2009-06-16 07:32:31 +02:00
$text = " SELECT $distinct " ;
2009-06-16 04:51:42 +02:00
}
// Now add the columns to be selected
$text .= implode ( " , " , $sqlQuery -> select );
2009-02-25 06:44:52 +01:00
}
2009-06-16 04:51:42 +02:00
$text .= " FROM " . implode ( " " , $sqlQuery -> from );
2009-02-25 06:44:52 +01:00
if ( $sqlQuery -> where ) $text .= " WHERE ( " . $sqlQuery -> getFilter () . " ) " ;
if ( $sqlQuery -> groupby ) $text .= " GROUP BY " . implode ( " , " , $sqlQuery -> groupby );
if ( $sqlQuery -> having ) $text .= " HAVING ( " . implode ( " ) AND ( " , $sqlQuery -> having ) . " ) " ;
2009-06-16 04:51:42 +02:00
if ( ! $nestedQuery && $sqlQuery -> orderby ) $text .= " ORDER BY " . $sqlQuery -> orderby ;
// $suffixText is used by the nested queries to create an offset limit
if ( $suffixText ) $text .= $suffixText ;
2009-06-04 05:02:35 +02:00
2009-02-25 06:44:52 +01:00
return $text ;
}
2009-03-11 22:47:06 +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 ){
$value = stripslashes ( $value );
$value = str_replace ( " ' " , " '' " , $value );
$value = str_replace ( " \0 " , " [NULL] " , $value );
return $value ;
}
2009-03-17 20:27:15 +01:00
/*
* This changes the index name depending on database requirements .
* MSSQL requires commas to be replaced with underscores
*/
function modifyIndex ( $index ){
return str_replace ( '_' , ',' , $index );
}
2009-03-23 01:51:28 +01: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-07-07 07:38:16 +02:00
if ( $this -> fullTextEnabled ) {
$keywords = Convert :: raw2sql ( trim ( $keywords ));
$htmlEntityKeywords = htmlentities ( $keywords );
$keywordList = explode ( ' ' , $keywords );
if ( $keywordList ) {
foreach ( $keywordList as $index => $keyword ) {
$keywordList [ $index ] = " \" { $keyword } \" " ;
}
$keywords = implode ( ' AND ' , $keywordList );
}
$htmlEntityKeywordList = explode ( ' ' , $htmlEntityKeywords );
if ( $htmlEntityKeywordList ) {
foreach ( $htmlEntityKeywordList as $index => $keyword ) {
$htmlEntityKeywordList [ $index ] = " \" { $keyword } \" " ;
}
$htmlEntityKeywords = implode ( ' AND ' , $htmlEntityKeywordList );
}
2009-06-23 02:20:39 +02:00
2009-05-08 05:37:31 +02:00
//Get a list of all the tables and columns we'll be searching on:
2009-06-05 05:44:28 +02:00
$result = DB :: query ( 'EXEC sp_help_fulltext_columns' );
if ( ! $result -> numRecords ()) throw Exception ( 'there are no full text columns to search' );
$tables = array ();
2009-05-08 05:37:31 +02:00
foreach ( $result as $row ){
2009-06-23 02:20:39 +02:00
if ( substr ( $row [ 'TABLE_NAME' ], - 5 ) != '_Live' && substr ( $row [ 'TABLE_NAME' ], - 9 ) != '_versions' ) {
$thisSql = " SELECT ID, ' { $row [ 'TABLE_NAME' ] } ' AS Source FROM \" { $row [ 'TABLE_NAME' ] } \" WHERE ( " .
" (CONTAINS( \" { $row [ 'FULLTEXT_COLUMN_NAME' ] } \" , ' $keywords ') OR CONTAINS( \" { $row [ 'FULLTEXT_COLUMN_NAME' ] } \" , ' $htmlEntityKeywords ')) " ;
if ( strpos ( $row [ 'TABLE_NAME' ], 'SiteTree' ) === 0 ) {
$thisSql .= " AND ShowInSearch != 0) " ; //" OR (Title LIKE '%$keywords%' OR Title LIKE '%$htmlEntityKeywords%')";
} else {
$thisSql .= ')' ;
}
$tables [] = $thisSql ;
}
2009-05-08 05:37:31 +02:00
}
2009-06-05 05:44:28 +02:00
$totalCount = 0 ;
$this -> forceNumRows = true ;
foreach ( $tables as $q ) {
$qR = DB :: query ( $q );
$totalCount += $qR -> numRecords ();
}
$this -> forceNumRows = false ;
//We'll do a union query on all of these tables... it's easier!
2009-05-08 05:37:31 +02:00
$query = implode ( ' UNION ' , $tables );
2009-06-05 05:44:28 +02:00
2009-05-08 05:37:31 +02:00
$result = DB :: query ( $query );
$searchResults = new DataObjectSet ();
2009-06-05 05:44:28 +02:00
2009-05-08 05:37:31 +02:00
foreach ( $result as $row ){
$row_result = DataObject :: get_by_id ( $row [ 'Source' ], $row [ 'ID' ]);
$searchResults -> push ( $row_result );
}
2009-06-05 05:44:28 +02:00
2009-05-08 05:37:31 +02:00
$searchResults -> setPageLimits ( $start , $pageLength , $totalCount );
2009-03-23 01:51:28 +01:00
}
2009-06-05 05:44:28 +02:00
2009-03-24 23:07:47 +01:00
return $searchResults ;
2009-03-23 01:51:28 +01:00
}
2009-03-30 05:06:25 +02:00
2009-05-07 07:55:05 +02:00
/**
* Allow auto - increment primary key editing on the given table .
* Some databases need to enable this specially .
* @ param $table The name of the table to have PK editing allowed on
* @ param $allow True to start , false to finish
*/
function allowPrimaryKeyEditing ( $table , $allow = true ) {
$this -> query ( " SET IDENTITY_INSERT \" $table\ " " . ( $allow ? " ON " : " OFF " ));
}
2009-05-07 08:32:08 +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 ) {
$fieldNames = '"' . implode ( '", "' , $fields ) . '"' ;
$SQL_keywords = Convert :: raw2sql ( $keywords );
return " FREETEXT (( $fieldNames ), ' $SQL_keywords ') " ;
}
2009-05-07 07:55:05 +02:00
2009-07-09 03:11:02 +02:00
/**
* Remove noise words that would kill a MSSQL full - text query
*
* @ param string $keywords
* @ return string $keywords with noise words removed
* @ author Tom Rix
*/
static public function removeNoiseWords ( $keywords ) {
$goodWords = array ();
foreach ( explode ( ' ' , $keywords ) as $word ) {
// @todo we may want to remove +'s -'s etc too
if ( ! in_array ( $word , $this -> noiseWords )) {
$goodWords [] = $word ;
}
}
return join ( ' ' , $goodWords );
}
2009-02-25 06:44:52 +01:00
}
/**
2009-04-28 07:13:23 +02:00
* A result - set from a MSSQL database .
2009-02-25 06:44:52 +01:00
* @ package sapphire
* @ subpackage model
*/
class MSSQLQuery extends Query {
/**
2009-04-28 07:13:23 +02:00
* The MSSQLDatabase object that created this result set .
* @ var MSSQLDatabase
2009-02-25 06:44:52 +01:00
*/
private $database ;
/**
2009-04-28 07:13:23 +02:00
* The internal MSSQL handle that points to the result set .
2009-02-25 06:44:52 +01:00
* @ var resource
*/
private $handle ;
2009-05-28 04:07:11 +02:00
/**
* If true , use the mssql_ ... functions .
* If false use the sqlsrv_ ... functions
*/
private $mssql = null ;
2009-02-25 06:44:52 +01:00
/**
* Hook the result - set given into a Query class , suitable for use by sapphire .
* @ param database The database object that created this query .
2009-04-28 07:13:23 +02:00
* @ param handle the internal mssql handle that is points to the resultset .
2009-02-25 06:44:52 +01:00
*/
2009-05-28 04:07:11 +02:00
public function __construct ( MSSQLDatabase $database , $handle , $mssql ) {
2009-03-29 23:43:48 +02:00
2009-02-25 06:44:52 +01:00
$this -> database = $database ;
$this -> handle = $handle ;
2009-05-28 04:07:11 +02:00
$this -> mssql = $mssql ;
2009-02-25 06:44:52 +01:00
parent :: __construct ();
}
public function __destroy () {
2009-05-28 04:07:11 +02:00
Debug :: message ( " Destroying query " );
if ( $this -> mssql ) {
mssql_free_result ( $this -> handle );
} else {
2009-06-02 23:01:15 +02:00
sqlsrv_free_stmt ( $this -> handle );
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2009-04-07 01:08:08 +02:00
/*
* Please see the comments below for numRecords
*
*/
2009-02-25 06:44:52 +01:00
public function seek ( $row ) {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
return mssql_data_seek ( $this -> handle , $row );
} else {
user_error ( " MSSQLQuery::seek() sqlserv doesn't support seek. " , E_USER_WARNING );
}
2009-02-25 06:44:52 +01:00
}
2009-04-07 01:08:08 +02:00
/*
2009-06-29 02:08:22 +02:00
* If we ' re running the sqlsrv set of functions , then the dataobject set is a forward - only cursor
2009-04-07 01:08:08 +02:00
* Therefore , we do not have access to the number of rows that this result contains
* This is ( usually ) called from Database :: rewind (), which in turn seems to be called when a foreach ...
* is started on a recordset
2009-06-02 23:01:15 +02:00
*
* If you are using SQLSRV , this functon will just return a true or false based on whether you got
2009-06-05 05:44:28 +02:00
* / ANY / rows . UNLESS you set $this -> forceNumRows to true , in which case , it will loop over the whole
* rowset , cache it , and then do the count on that . This is probably resource intensive .
2009-04-07 01:08:08 +02:00
*
* For this function , and seek () ( above ), we will be returning false .
*
*/
2009-02-25 06:44:52 +01:00
public function numRecords () {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
return mssql_num_rows ( $this -> handle );
} else {
2009-06-05 05:44:28 +02:00
// Setting forceNumRows to true will cache all records, but will
// be able to give a reliable number of results found.
if ( isset ( $this -> forceNumRows ) && $this -> forceNumRows ) {
if ( isset ( $this -> numRecords )) return $this -> numRecords ;
$this -> cachedRecords = array ();
// We can't have nextRecord() return the row we just added =)
$this -> cachingRows = true ;
foreach ( $this as $record ) {
$this -> cachedRecords [] = $record ;
}
$this -> cachingRows = false ;
// Assign it to a var, otherwise the value will change when
// something is shifted off the beginning.
$this -> numRecords = count ( $this -> cachedRecords );
return $this -> numRecords ;
} else {
$this -> cachedRecords = array ( $this -> nextRecord ());
return count ( $this -> cachedRecords [ 0 ]) ? true : false ;
}
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
public function nextRecord () {
// Coalesce rather than replace common fields.
2009-06-05 05:44:28 +02:00
if ( $this -> mssql ) {
2009-05-28 04:07:11 +02:00
if ( $data = mssql_fetch_row ( $this -> handle )) {
2009-06-05 05:44:28 +02:00
2009-05-28 04:07:11 +02:00
foreach ( $data as $columnIdx => $value ) {
$columnName = mssql_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 ;
}
2009-02-25 06:44:52 +01:00
}
2009-06-05 05:44:28 +02:00
2009-05-28 04:07:11 +02:00
return $output ;
} else {
return false ;
2009-02-25 06:44:52 +01:00
}
2009-04-06 07:21:06 +02:00
2009-02-25 06:44:52 +01:00
} else {
2009-06-05 05:44:28 +02:00
// If we have cached rows (if numRecords as been called) and
// returning cached rows hasn't specifically been disabled,
// check for cached rows and return 'em.
if ( isset ( $this -> cachedRecords ) && count ( $this -> cachedRecords ) && ( ! isset ( $this -> cachingRows ) || ! $this -> cachingRows )) {
return array_shift ( $this -> cachedRecords );
2009-06-02 23:01:15 +02:00
}
2009-05-28 04:07:11 +02:00
if ( $data = sqlsrv_fetch_array ( $this -> handle , SQLSRV_FETCH_NUMERIC )) {
$output = array ();
$fields = sqlsrv_field_metadata ( $this -> handle );
foreach ( $fields as $columnIdx => $field ) {
$value = $data [ $columnIdx ];
2009-06-30 04:09:02 +02:00
if ( $value instanceof DateTime ) $value = $value -> format ( 'Y-m-d H:i:s' );
2009-05-28 04:07:11 +02:00
// $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 [ $field [ 'Name' ]])) {
$output [ $field [ 'Name' ]] = $value ;
}
}
return $output ;
} else {
return false ;
}
2009-02-25 06:44:52 +01:00
}
}
2009-04-22 00:19:45 +02:00
2009-02-25 06:44:52 +01:00
}
2009-04-22 00:19:45 +02:00
2009-06-15 03:34:22 +02:00
?>