2009-02-25 06:44:52 +01:00
< ? php
2009-04-22 00:19:45 +02:00
/**
2010-05-28 00:25:45 +02:00
* Microsoft SQL Server 2008 + connector class .
2009-07-16 01:39:35 +02:00
*
2010-05-26 12:05:21 +02:00
* < h2 > Connecting using Windows </ h2 >
*
2009-10-22 06:02:40 +02:00
* If you 've got your website running on Windows, it' s highly recommended you
2010-05-26 12:05:21 +02:00
* use Microsoft SQL Server Driver for PHP " sqlsrv " .
*
* @ see http :// sqlsrvphp . codeplex . com /
*
* < h2 > Connecting using Linux or Mac OS X </ h2 >
*
2010-05-28 00:25:45 +02:00
* The following commands assume you used the default package manager
* to install PHP with the operating system .
2010-05-26 12:05:21 +02:00
*
2010-05-28 00:25:45 +02:00
* Debian , and Ubuntu :
* < code > apt - get install php5 - sybase </ code >
*
* Fedora , CentOS and RedHat :
* < code > yum install php - mssql </ code >
*
* Mac OS X ( MacPorts ) :
* < code > port install php5 - mssql </ code >
2010-05-26 12:05:21 +02:00
*
2010-05-28 00:25:45 +02:00
* These packages will install the mssql extension for PHP , as well
2010-05-28 01:05:32 +02:00
* as FreeTDS , which will let you connect to SQL Server from Linux and Mac OS X .
2010-05-26 12:05:21 +02:00
*
* More information available in the SilverStripe developer wiki :
* @ see http :// doc . silverstripe . org / modules : mssql
2009-10-22 06:01:17 +02:00
*
2010-05-28 00:25:45 +02:00
* References :
* @ see http :// freetds . org
*
2009-10-22 06:01:17 +02:00
* @ package mssql
2009-04-22 00:19:45 +02:00
*/
2009-10-26 21:59:29 +01:00
class MSSQLDatabase extends SS_Database {
2009-07-16 01:39:35 +02:00
2009-02-25 06:44:52 +01:00
/**
* Connection to the DBMS .
* @ var resource
*/
2010-02-01 22:45:01 +01:00
protected $dbConn ;
2009-02-25 06:44:52 +01:00
/**
* True if we are connected to a database .
* @ var boolean
*/
2010-02-01 22:45:01 +01:00
protected $active ;
2009-02-25 06:44:52 +01:00
/**
* The name of the database .
* @ var string
*/
2010-02-01 22:45:01 +01:00
protected $database ;
2009-02-25 06:44:52 +01:00
2009-05-28 04:07:11 +02:00
/**
* If true , use the mssql_ ... functions .
* If false use the sqlsrv_ ... functions
*/
2010-02-01 22:45:01 +01:00
protected $mssql = null ;
2010-03-11 09:47:04 +01:00
/**
* Stores the affected rows of the last query .
* Used by sqlsrv functions only , as sqlsrv_rows_affected
* accepts a result instead of a database handle .
*/
protected $lastAffectedRows ;
2009-05-28 04:07:11 +02:00
2009-07-09 03:11:02 +02:00
/**
* Words that will trigger an error if passed to a SQL Server fulltext search
*/
2009-07-09 05:59:44 +02:00
public static $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-07-09 03:11:02 +02:00
2010-02-23 02:23:42 +01:00
protected $supportsTransactions = false ;
/**
* Cached flag to determine if full - text is enabled . This is set by
* { @ link MSSQLDatabase :: fullTextEnabled ()}
*
* @ var boolean
*/
protected $fullTextEnabled = null ;
2009-10-01 23:11:18 +02:00
2010-06-03 02:58:31 +02:00
/**
* @ ignore
*/
protected static $collation = null ;
/**
* Set the default collation of the MSSQL nvarchar fields that we create .
* We don ' t apply this to the database as a whole , so that we can use unicode collations .
*/
public static function set_collation ( $collation ) {
self :: $collation = $collation ;
}
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-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 );
}
2009-07-20 09:23:38 +02:00
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
2010-06-03 02:58:31 +02:00
// Switch to utf8 connection charset
ini_set ( 'mssql.charset' , 'utf8' );
2009-10-16 00:04:55 +02:00
$this -> dbConn = mssql_connect ( $parameters [ 'server' ], $parameters [ 'username' ], $parameters [ 'password' ], true );
2009-05-28 04:07:11 +02:00
} else {
2010-03-05 03:40:20 +01:00
// Disable default warnings as errors behaviour for sqlsrv to keep it in line with mssql functions
2010-03-11 10:44:51 +01:00
if ( ini_get ( 'sqlsrv.WarningsReturnAsErrors' )) {
2010-03-05 03:40:20 +01:00
ini_set ( 'sqlsrv.WarningsReturnAsErrors' , 'Off' );
}
2009-11-12 10:23:46 +01:00
// Windows authentication doesn't require a username and password
if ( defined ( 'MSSQL_USE_WINDOWS_AUTHENTICATION' ) && MSSQL_USE_WINDOWS_AUTHENTICATION == true ) {
$connectionInfo = array ();
} else {
$connectionInfo = array (
'UID' => $parameters [ 'username' ],
'PWD' => $parameters [ 'password' ],
2010-06-03 02:58:31 +02:00
'CharacterSet' => 'utf8' ,
2009-11-12 10:23:46 +01:00
);
}
$this -> dbConn = sqlsrv_connect ( $parameters [ 'server' ], $connectionInfo );
2009-05-28 04:07:11 +02:00
}
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-07-20 09:23:38 +02:00
public function __destruct () {
2009-07-28 06:07:42 +02:00
if ( is_resource ( $this -> dbConn )) {
2009-10-22 06:01:17 +02:00
if ( $this -> mssql ) {
mssql_close ( $this -> dbConn );
} else {
sqlsrv_close ( $this -> dbConn );
}
2009-07-20 09:23:38 +02:00
}
}
2010-02-03 09:11:31 +01:00
/**
2010-02-12 00:09:37 +01:00
* Checks whether the current SQL Server version has full - text
* support installed and full - text is enabled for this database .
2010-02-03 09:11:31 +01:00
*
* @ return boolean
*/
public function fullTextEnabled () {
2010-02-23 02:23:42 +01:00
if ( $this -> fullTextEnabled === null ) {
$isInstalled = ( boolean ) DB :: query ( " SELECT fulltextserviceproperty('isfulltextinstalled') " ) -> value ();
$enabledForDb = ( boolean ) DB :: query ( "
SELECT is_fulltext_enabled
FROM sys . databases
WHERE name = '$this->database'
" )->value();
2010-03-11 10:46:16 +01:00
$this -> fullTextEnabled = ( boolean ) ( $isInstalled && $enabledForDb );
2010-02-23 02:23:42 +01:00
}
return $this -> fullTextEnabled ;
2010-02-03 09:11:31 +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 .
*
* 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
*/
2010-02-03 09:24:33 +01:00
function createFullTextCatalog () {
2010-02-03 09:11:31 +01:00
if ( $this -> fullTextEnabled ()) {
2009-05-08 05:37:31 +02:00
$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-07-20 11:09:53 +02: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 ;
}
/**
2009-04-14 00:02:55 +02:00
* Get the version of MSSQL .
2010-05-26 07:24:15 +02:00
* @ return string
2009-02-25 06:44:52 +01:00
*/
public function getVersion () {
2010-05-26 07:24:15 +02:00
return trim ( $this -> query ( " SELECT CONVERT(char(15), SERVERPROPERTY('ProductVersion')) " ) -> value ());
2009-02-25 06:44:52 +01:00
}
/**
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 );
}
2010-05-11 00:56:43 +02:00
2010-05-11 02:12:13 +02:00
$error = '' ;
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
$handle = mssql_query ( $sql , $this -> dbConn );
2010-05-11 02:12:13 +02:00
$error = mssql_get_last_message ();
2009-05-28 04:07:11 +02:00
} else {
$handle = sqlsrv_query ( $this -> dbConn , $sql );
2010-03-11 09:47:04 +01:00
if ( $handle ) $this -> lastAffectedRows = sqlsrv_rows_affected ( $handle );
2010-05-11 02:12:13 +02:00
if ( function_exists ( 'sqlsrv_errors' )) {
$error = sqlsrv_errors ();
}
2009-05-28 04:07:11 +02:00
}
2010-05-11 00:56:43 +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 );
}
2010-05-11 00:56:43 +02:00
if ( ! $handle && $errorLevel ) $this -> databaseError ( " Couldn't run query ( $error ): $sql " , $errorLevel );
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 .
2009-07-20 11:09:53 +02:00
* @ param string $table The name of the table
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-07-20 11:09:53 +02:00
/**
* Create the database that is currently selected .
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 .
2009-07-20 11:09:53 +02:00
*
* If the database doesn ' t exist , you should call
* createDatabase () after calling selectDatabase ()
*
* @ param string $dbname The database name to switch to
2009-02-25 06:44:52 +01:00
*/
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
}
2010-02-23 02:23:42 +01:00
$this -> tableList = $this -> fieldList = $this -> indexList = $this -> fullTextEnabled = null ;
2009-02-25 06:44:52 +01:00
}
/**
2009-07-20 11:09:53 +02:00
* Check if the given database exists .
* @ param string $name Name of database to check 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-10-14 07:51:43 +02:00
public function createTable ( $tableName , $fields = null , $indexes = null , $options = null , $advancedOptions = 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
2009-12-15 00:57:30 +01:00
if ( ! empty ( $options [ 'temporary' ])) {
2009-12-15 01:58:58 +01:00
// Randomize the temp table name to avoid conflicts in the tempdb table which derived databases share
$tableName = " # $tableName " . '-' . rand ( 1000000 , 9999999 );
2009-12-15 00:57:30 +01:00
}
2009-05-21 07:10:46 +02:00
2009-02-25 06:44:52 +01:00
$this -> query ( " CREATE TABLE \" $tableName\ " (
2009-12-15 00:57:30 +01:00
$fieldSchemas
primary key ( \ " ID \" )
); " );
2009-12-15 01:58:58 +01:00
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
*/
2009-10-14 07:51:43 +02:00
public function alterTable ( $tableName , $newFields = null , $newIndexes = null , $alteredFields = null , $alteredIndexes = null , $alteredOptions = null , $advancedOptions = null ) {
2009-02-25 06:44:52 +01:00
$fieldSchemas = $indexSchemas = " " ;
2009-04-22 00:19:45 +02:00
$alterList = array ();
2009-10-22 08:51:35 +02:00
$indexList = $this -> indexList ( $tableName );
2009-04-01 05:37:39 +02:00
2009-10-22 08:51:35 +02:00
if ( $newFields ) foreach ( $newFields as $k => $v ) $alterList [] .= " ALTER TABLE \" $tableName\ " ADD \ " $k\ " $v " ;
2010-06-03 02:58:32 +02:00
if ( $alteredFields ) {
// fulltext indexes need to be dropped if alterting a table
if ( $this -> fulltextIndexExists ( $tableName ) === true ) {
$alterList [] = " \n DROP FULLTEXT INDEX ON \" $tableName\ " ; " ;
}
foreach ( $alteredFields as $k => $v ) {
$val = $this -> alterTableAlterColumn ( $tableName , $k , $v , $indexList );
if ( $val != '' ) $alterList [] .= $val ;
}
2009-02-25 06:44:52 +01:00
}
2009-10-22 08:51:35 +02:00
if ( $alteredIndexes ) foreach ( $alteredIndexes as $k => $v ) $alterList [] .= $this -> getIndexSqlDefinition ( $tableName , $k , $v );
if ( $newIndexes ) foreach ( $newIndexes as $k => $v ) $alterList [] .= $this -> getIndexSqlDefinition ( $tableName , $k , $v );
2009-02-25 06:44:52 +01:00
2009-10-22 08:51:35 +02:00
if ( $alterList ) {
foreach ( $alterList as $alteration ) {
if ( $alteration != '' ) {
$this -> query ( $alteration );
2009-04-22 01:14:43 +02:00
}
2009-04-22 00:19:45 +02:00
}
2009-02-25 06:44:52 +01:00
}
}
2009-07-20 11:09:53 +02:00
/**
* This is a private MSSQL - only function which returns
* specific details about a column ' s constraints ( if any )
* @ param string $tableName Name of table the column exists in
* @ param string $columnName Name of column to check for
2009-04-06 00:23:58 +02:00
*/
2010-02-01 22:45:01 +01:00
protected function ColumnConstraints ( $tableName , $columnName ) {
2009-07-20 11:09:53 +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 ;
}
2009-07-28 00:39:57 +02:00
/**
* Return the name of the default constraint applied to $tableName . $colName .
* Will return null if no such constraint exists
*/
2010-02-01 22:45:01 +01:00
protected function defaultConstraintName ( $tableName , $colName ) {
2009-07-28 00:39:57 +02:00
return $this -> query ( " SELECT s.name --default name
FROM sys . sysobjects s
join sys . syscolumns c ON s . parent_obj = c . id
WHERE s . xtype = 'd'
and c . cdefault = s . id
and parent_obj = OBJECT_ID ( '$tableName' )
and c . name = '$colName' " )->value();
}
2009-04-06 00:23:58 +02:00
2009-07-20 11:09:53 +02:00
/**
2009-04-06 00:23:58 +02:00
* Get the actual enum fields from the constraint value :
*/
2010-02-01 22:45:01 +01:00
protected 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
*/
2010-02-01 22:45:01 +01:00
protected 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 );
2010-02-02 00:24:02 +01:00
// drop the index if it exists
2009-04-01 05:37:39 +02:00
$alterCol = '' ;
2010-02-02 00:24:02 +01:00
$indexName = isset ( $indexList [ $colName ][ 'indexname' ]) ? $indexList [ $colName ][ 'indexname' ] : null ;
if ( $indexName && $colName != 'ID' ) {
$alterCol = " \n DROP INDEX \" $indexName\ " ON \ " $tableName\ " ; " ;
2009-04-01 05:37:39 +02:00
}
2009-12-07 06:38:26 +01:00
2009-04-22 00:19:45 +02:00
$prefix = " ALTER TABLE \" " . $tableName . " \" " ;
2009-07-28 00:39:57 +02:00
// Remove the old default prior to adjusting the column.
if ( $defaultConstraintName = $this -> defaultConstraintName ( $tableName , $colName )) {
$alterCol .= " ; \n $prefix DROP CONSTRAINT \" $defaultConstraintName\ " " ;
}
2009-02-25 06:44:52 +01:00
if ( isset ( $matches [ 1 ])) {
2009-07-28 23:15:27 +02:00
//We will prevent any changes being made to the ID column. Primary key indexes will have a fit if we do anything here.
if ( $colName != 'ID' ){
$alterCol .= " ; \n $prefix ALTER COLUMN \" $colName\ " $matches [ 1 ] " ;
// SET null / not null
if ( ! empty ( $matches [ 2 ])) $alterCol .= " ; \n $prefix ALTER COLUMN \" $colName\ " $matches [ 1 ] $matches [ 2 ] " ;
// Add a default back
if ( ! empty ( $matches [ 3 ])) $alterCol .= " ; \n $prefix ADD $matches[3] FOR \" $colName\ " " ;
// SET check constraint (The constraint HAS to be dropped)
if ( ! empty ( $matches [ 4 ])) {
$constraint = $this -> ColumnConstraints ( $tableName , $colName );
if ( $constraint )
$alterCol .= " ; \n $prefix DROP CONSTRAINT { $constraint [ 'CONSTRAINT_NAME' ] } " ;
//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-04-01 05:37:39 +02:00
2009-07-28 23:15: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 ;
}
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....
2010-02-02 00:24:02 +01:00
$fieldRecords = $this -> query ( " SELECT ordinal_position, column_name, data_type, column_default,
2010-06-03 02:58:31 +02:00
is_nullable , character_maximum_length , numeric_precision , numeric_scale , collation_name
2009-07-28 00:39:57 +02:00
FROM information_schema . columns WHERE table_name = '$table'
ORDER BY ordinal_position ; " );
2010-02-02 00:24:02 +01:00
// Cache the records from the query - otherwise a lack of multiple active result sets
// will cause subsequent queries to fail in this method
$fields = array ();
2009-02-25 06:44:52 +01:00
$output = array ();
2010-02-02 00:24:02 +01:00
foreach ( $fieldRecords as $record ) {
$fields [] = $record ;
}
foreach ( $fields as $field ) {
2009-07-28 00:39:57 +02:00
// Update the data_type field to be a complete column definition string for use by
2009-10-26 22:16:37 +01:00
// SS_Database::requireField()
2009-03-12 03:43:10 +01:00
switch ( $field [ 'data_type' ]){
2009-07-28 00:39:57 +02:00
case 'bigint' :
case 'numeric' :
case 'float' :
case 'bit' :
2009-07-28 05:48:52 +02:00
if ( $field [ 'data_type' ] != 'bigint' && $sizeSuffix = $field [ 'numeric_precision' ]) {
2009-07-28 23:15:27 +02:00
$field [ 'data_type' ] .= " ( $sizeSuffix ) " ;
2009-07-28 00:39:57 +02:00
}
if ( $field [ 'is_nullable' ] == 'YES' ) {
$field [ 'data_type' ] .= ' null' ;
} else {
$field [ 'data_type' ] .= ' not null' ;
}
if ( $field [ 'column_default' ]) {
$default = substr ( $field [ 'column_default' ], 2 , - 2 );
$field [ 'data_type' ] .= " default $default " ;
}
break ;
case 'decimal' :
if ( $field [ 'numeric_precision' ]) {
$sizeSuffix = $field [ 'numeric_precision' ] . ',' . $field [ 'numeric_scale' ];
$field [ 'data_type' ] .= " ( $sizeSuffix ) " ;
}
if ( $field [ 'is_nullable' ] == 'YES' ) {
$field [ 'data_type' ] .= ' null' ;
} else {
$field [ 'data_type' ] .= ' not null' ;
}
if ( $field [ 'column_default' ]) {
$default = substr ( $field [ 'column_default' ], 2 , - 2 );
$field [ 'data_type' ] .= " default $default " ;
}
break ;
2010-06-03 02:58:31 +02:00
case 'nvarchar' :
2009-03-12 03:43:10 +01:00
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-07-28 00:39:57 +02:00
break ;
2009-03-12 03:43:10 +01:00
}
2009-07-28 00:39:57 +02:00
2009-02-25 06:44:52 +01:00
default :
2009-07-28 00:39:57 +02:00
$sizeSuffix = $field [ 'character_maximum_length' ];
if ( $sizeSuffix == '-1' ) $sizeSuffix = 'max' ;
if ( $sizeSuffix ) {
$field [ 'data_type' ] .= " ( $sizeSuffix ) " ;
}
if ( $field [ 'is_nullable' ] == 'YES' ) {
$field [ 'data_type' ] .= ' null' ;
} else {
$field [ 'data_type' ] .= ' not null' ;
}
if ( $field [ 'column_default' ]) {
$default = substr ( $field [ 'column_default' ], 2 , - 2 );
$field [ 'data_type' ] .= " default ' $default ' " ;
}
2009-02-25 06:44:52 +01:00
}
2009-07-28 00:39:57 +02:00
$output [ $field [ 'column_name' ]] = $field ;
2009-02-25 06:44:52 +01:00
}
return $output ;
}
/**
* Create an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
2009-10-27 00:44:08 +01:00
* @ param string $indexSpec The specification of the index , see SS_Database :: requireIndex () for more details .
2009-02-25 06:44:52 +01:00
*/
public function createIndex ( $tableName , $indexName , $indexSpec ) {
$this -> query ( $this -> getIndexSqlDefinition ( $tableName , $indexName , $indexSpec ));
}
2009-10-22 08:51:35 +02:00
/**
2009-02-25 06:44:52 +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
* 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 ;
}
2010-06-03 02:58:33 +02:00
/**
* Return SQL for dropping and recreating an index
*/
2009-02-25 06:44:52 +01:00
protected function getIndexSqlDefinition ( $tableName , $indexName , $indexSpec ) {
2010-06-03 02:58:33 +02:00
$index = 'ix_' . $tableName . '_' . $indexName ;
$drop = " IF EXISTS (SELECT name FROM sys.indexes WHERE name = ' $index ') DROP INDEX $index ON \" " . $tableName . " \" ; " ;
2009-10-22 08:51:35 +02:00
if ( ! is_array ( $indexSpec )) {
2009-02-25 06:44:52 +01:00
$indexSpec = trim ( $indexSpec , '()' );
$bits = explode ( ',' , $indexSpec );
$indexes = " \" " . implode ( " \" , \" " , $bits ) . " \" " ;
2009-10-22 08:51:35 +02:00
return " $drop CREATE INDEX $index ON \" " . $tableName . " \" ( " . $indexes . " ); " ;
2009-02-25 06:44:52 +01:00
} else {
//create a type-specific index
2009-10-22 08:51:35 +02:00
if ( $indexSpec [ 'type' ] == 'fulltext' ) {
2010-02-03 09:11:31 +01:00
if ( $this -> fullTextEnabled ()) {
2009-05-08 05:37:31 +02:00
//Enable full text search.
$this -> createFullTextCatalog ();
2009-12-08 21:53:15 +01:00
2009-05-08 05:37:31 +02:00
$primary_key = $this -> getPrimaryKey ( $tableName );
2009-10-22 08:51:35 +02:00
2010-06-03 02:58:32 +02:00
return " CREATE FULLTEXT INDEX ON \" $tableName\ " ({ $indexSpec [ 'value' ]}) KEY INDEX $primary_key WITH CHANGE_TRACKING AUTO ; " ;
2009-05-08 05:37:31 +02:00
}
2009-10-22 08:51:35 +02:00
}
2009-03-17 04:58:58 +01:00
2009-10-22 08:51:35 +02:00
if ( $indexSpec [ 'type' ] == 'unique' ) {
2010-06-03 02:58:33 +02:00
if ( ! is_array ( $indexSpec [ 'value' ])) $columns = preg_split ( '/ *, */' , trim ( $indexSpec [ 'value' ]));
else $columns = $indexSpec [ 'value' ];
$SQL_columnList = '"' . implode ( '", "' , $columns ) . '"' ;
return " $drop CREATE UNIQUE INDEX $index ON \" " . $tableName . " \" ( $SQL_columnList ); " ;
2009-03-17 04:58:58 +01:00
}
2009-02-25 06:44:52 +01:00
}
}
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 .
2009-10-27 00:44:08 +01:00
* @ param string $indexSpec The specification of the index , see SS_Database :: requireIndex () for more details .
2009-02-25 06:44:52 +01:00
*/
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-07-28 23:15:27 +02: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:
2010-02-03 09:11:31 +01:00
if ( $this -> fullTextEnabled ()) {
2009-05-08 05:37:31 +02:00
$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 {
2010-03-11 09:47:04 +01:00
return $this -> lastAffectedRows ;
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2010-03-11 09:41:37 +01:00
2009-02-25 06:44:52 +01:00
/**
* 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
*/
2010-02-03 08:50:29 +01:00
public function boolean ( $values ) {
2009-02-25 06:44:52 +01:00
//Annoyingly, we need to do a good ol' fashioned switch here:
( $values [ 'default' ]) ? $default = '1' : $default = '0' ;
2010-02-03 08:50:29 +01:00
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
*/
2010-02-03 08:50:29 +01:00
public function date ( $values ) {
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
*/
2010-02-03 08:50:29 +01:00
public function decimal ( $values ) {
2009-02-25 06:44:52 +01:00
// Avoid empty strings being put in the db
if ( $values [ 'precision' ] == '' ) {
$precision = 1 ;
} else {
$precision = $values [ 'precision' ];
}
2010-02-03 08:27:50 +01:00
$defaultValue = '0' ;
if ( isset ( $values [ 'default' ]) && is_numeric ( $values [ 'default' ])) {
$defaultValue = $values [ 'default' ];
}
2010-02-03 08:50:29 +01:00
return 'decimal(' . $precision . ') not null default ' . $defaultValue ;
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
*/
2010-02-03 08:50:29 +01:00
public function enum ( $values ) {
2009-07-28 00:39:57 +02:00
// 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
$maxLength = max ( array_map ( 'strlen' , $values [ 'enums' ]));
2010-02-03 08:50:29 +01:00
return " varchar( $maxLength ) not null default ' " . $values [ 'default' ]
. " ' check( \" " . $values [ 'name' ] . " \" in (' " . implode ( " ',' " , $values [ 'enums' ])
. " ')) " ;
2009-02-25 06:44:52 +01:00
}
2010-02-07 02:59:13 +01:00
/**
* @ todo Make this work like { @ link MySQLDatabase :: set ()}
*/
public function set ( $values ) {
return $this -> enum ( $values );
}
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
*/
2010-02-03 08:50:29 +01:00
public function float ( $values ) {
2010-04-13 07:38:01 +02:00
return 'float not null default ' . $values [ 'default' ];
2009-02-25 06:44:52 +01:00
}
/**
* Return a int type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
2010-02-03 08:50:29 +01:00
public function int ( $values ) {
2009-02-25 06:44:52 +01:00
//We'll be using an 8 digit precision to keep it in line with the serial8 datatype for ID columns
2010-02-03 08:50:29 +01:00
return 'numeric(8) not null default ' . ( int ) $values [ 'default' ];
2009-02-25 06:44:52 +01:00
}
/**
* 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
*/
2010-02-03 08:50:29 +01:00
public function ss_datetime ( $values ) {
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
*/
2010-02-03 08:50:29 +01:00
public function text ( $values ) {
2010-06-03 02:58:31 +02:00
$collation = self :: $collation ? " COLLATE " . self :: $collation : " " ;
return " nvarchar(max) $collation 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
*/
2010-02-03 08:50:29 +01:00
public function varchar ( $values ) {
2010-06-03 02:58:31 +02:00
$collation = self :: $collation ? " COLLATE " . self :: $collation : " " ;
return " nvarchar( " . $values [ 'precision' ] . " ) $collation 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
*/
2010-02-03 08:50:29 +01:00
public function year ( $values ) {
return 'numeric(4)' ;
2009-02-25 06:44:52 +01:00
}
2010-05-19 12:48:19 +02:00
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 )
2009-07-28 05:48:52 +02:00
return 'bigint not null' ;
2009-03-12 03:43:10 +01:00
else {
if ( $hasAutoIncPK )
2009-07-28 05:48:52 +02:00
return 'bigint identity(1,1)' ;
else return 'bigint not null' ;
2009-03-12 03:43:10 +01:00
}
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 (){
2010-05-19 13:06:35 +02:00
return " SELECT \" name \" FROM \" sys \" . \" tables \" ; " ;
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 ) {
2010-06-03 02:58:32 +02:00
$SQL_tableName = Convert :: raw2sql ( $tableName );
$value = DB :: query ( " SELECT table_name FROM information_schema.tables WHERE table_name = ' $SQL_tableName ' " ) -> value ();
return ( bool ) $value ;
2009-02-25 06:44:52 +01:00
}
/**
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
/**
2010-02-03 08:53:08 +01:00
* SQL Server uses CURRENT_TIMESTAMP for the current date / time .
2009-03-11 22:47:06 +01:00
*/
2010-02-03 08:53:08 +01:00
function now () {
2009-03-11 22:47:06 +01:00
return 'CURRENT_TIMESTAMP' ;
}
2009-09-17 09:07:02 +02:00
/**
* Returns the database - specific version of the random () function
*/
function random (){
return 'RAND()' ;
2010-04-07 04:46:28 +02:00
}
/**
* This is a lookup table for data types .
*
* For instance , MSSQL uses 'BIGINT' , while MySQL uses 'UNSIGNED'
* and PostgreSQL uses 'INT' .
*/
function dbDataType ( $type ){
$values = array (
'unsigned integer' => 'BIGINT'
);
if ( isset ( $values [ $type ])) return $values [ $type ];
else return '' ;
2009-09-17 09:07:02 +02:00
}
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
2010-05-19 13:06:35 +02:00
/**
2009-03-17 20:27:15 +01:00
* This changes the index name depending on database requirements .
2010-05-19 13:06:35 +02:00
* MSSQL requires underscores to be replaced with commas .
2009-03-17 20:27:15 +01:00
*/
2010-05-19 13:06:35 +02:00
function modifyIndex ( $index ) {
2009-03-17 20:27:15 +01:00
return str_replace ( '_' , ',' , $index );
}
2009-03-23 01:51:28 +01:00
/**
2009-07-20 11:09:53 +02:00
* The core search engine configuration .
* @ todo There is no result relevancy or ordering as it currently stands .
2009-03-23 01:51:28 +01:00
*
2009-07-20 11:09:53 +02:00
* @ param string $keywords Keywords as a space separated string
* @ return object DataObjectSet of result pages
2009-03-23 01:51:28 +01:00
*/
2009-06-05 06:37:45 +02:00
public function searchEngine ( $classesToSearch , $keywords , $start , $pageLength , $sortBy = " Relevance DESC " , $extraFilter = " " , $booleanSearch = false , $alternativeFileFilter = " " , $invertedMatch = false ) {
2010-05-19 14:11:33 +02:00
$results = new DataObjectSet ();
2010-02-03 09:11:31 +01:00
if ( ! $this -> fullTextEnabled ()) {
2010-05-19 14:11:33 +02:00
return $results ;
2009-10-27 08:08:51 +01:00
}
$keywords = Convert :: raw2sql ( trim ( $keywords ));
$htmlEntityKeywords = htmlentities ( $keywords );
$keywordList = explode ( ' ' , $keywords );
if ( $keywordList ) {
foreach ( $keywordList as $index => $keyword ) {
$keywordList [ $index ] = " \" { $keyword } \" " ;
2009-07-07 07:38:16 +02:00
}
2009-10-27 08:08:51 +01:00
$keywords = implode ( ' AND ' , $keywordList );
}
$htmlEntityKeywordList = explode ( ' ' , $htmlEntityKeywords );
if ( $htmlEntityKeywordList ) {
foreach ( $htmlEntityKeywordList as $index => $keyword ) {
$htmlEntityKeywordList [ $index ] = " \" { $keyword } \" " ;
2009-07-07 07:38:16 +02:00
}
2009-10-27 08:08:51 +01:00
$htmlEntityKeywords = implode ( ' AND ' , $htmlEntityKeywordList );
}
//Get a list of all the tables and columns we'll be searching on:
2010-05-19 14:11:33 +02:00
$result = DB :: query ( 'EXEC sp_help_fulltext_columns' );
$tables = array ();
2009-10-27 08:08:51 +01:00
foreach ( $result as $row ){
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 .= ')' ;
2009-06-23 02:20:39 +02:00
}
2009-10-27 08:08:51 +01:00
$tables [] = $thisSql ;
2009-05-08 05:37:31 +02:00
}
2009-03-23 01:51:28 +01:00
}
2010-05-19 14:11:33 +02:00
$query = implode ( ' UNION ' , $tables );
$result = DB :: query ( $query );
2009-10-27 08:08:51 +01:00
$totalCount = 0 ;
2010-05-19 14:11:33 +02:00
foreach ( $result as $row ) {
$record = DataObject :: get_by_id ( $row [ 'Source' ], $row [ 'ID' ]);
if ( $record -> canView ()) {
$results -> push ( $record );
$totalCount ++ ;
}
2009-10-27 08:08:51 +01:00
}
2010-05-19 14:11:33 +02:00
$results -> setPageLimits ( $start , $pageLength , $totalCount );
return $results ;
2009-03-23 01:51:28 +01:00
}
2010-05-19 14:11:33 +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
2009-12-07 06:38:26 +01:00
/**
* Check if a fulltext index exists on a particular table name .
2010-02-02 00:24:02 +01:00
* @ return boolean TRUE index exists | FALSE index does not exist | NULL no support
2009-12-07 06:38:26 +01:00
*/
function fulltextIndexExists ( $tableName ) {
2010-02-02 00:24:02 +01:00
// Special case for no full text index support
2010-02-03 09:11:31 +01:00
if ( ! $this -> fullTextEnabled ()) return null ;
2009-12-07 06:38:26 +01:00
return ( bool ) $this -> query ( "
SELECT 1 FROM sys . fulltext_indexes i
JOIN sys . objects o ON i . object_id = o . object_id
WHERE o . name = '$tableName'
" )->value();
}
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
2009-07-09 05:59:44 +02:00
if ( ! in_array ( $word , self :: $noiseWords )) {
2009-07-09 03:11:02 +02:00
$goodWords [] = $word ;
}
}
return join ( ' ' , $goodWords );
}
2009-10-01 23:11:18 +02:00
/*
* Does this database support transactions ?
*/
public function supportsTransactions (){
return $this -> supportsTransactions ;
}
2009-10-12 00:04:24 +02:00
/*
* This is a quick lookup to discover if the database supports particular extensions
* Currently , MSSQL supports no extensions
*/
public function supportsExtensions ( $extensions = Array ( 'partitions' , 'tablespaces' , 'clustering' )){
if ( isset ( $extensions [ 'partitions' ]))
return false ;
elseif ( isset ( $extensions [ 'tablespaces' ]))
return false ;
elseif ( isset ( $extensions [ 'clustering' ]))
return false ;
else
return false ;
}
2009-10-01 23:11:18 +02:00
/*
* Start a prepared transaction
* See http :// developer . postgresql . org / pgdocs / postgres / sql - set - transaction . html for details on transaction isolation options
*/
public function startTransaction ( $transaction_mode = false , $session_characteristics = false ){
//Transactions not set up for MSSQL yet
}
/*
* Create a savepoint that you can jump back to if you encounter problems
*/
public function transactionSavepoint ( $savepoint ){
//Transactions not set up for MSSQL yet
}
/*
* Rollback or revert to a savepoint if your queries encounter problems
* If you encounter a problem at any point during a transaction , you may
* need to rollback that particular query , or return to a savepoint
*/
public function transactionRollback ( $savepoint = false ){
//Transactions not set up for MSSQL yet
}
/*
* Commit everything inside this transaction so far
*/
public function endTransaction (){
//Transactions not set up for MSSQL yet
}
2010-02-03 06:01:37 +01:00
/**
2010-05-18 12:47:05 +02:00
* Function to return an SQL datetime expression for MSSQL
2010-02-03 06:01:37 +01: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 = " CURRENT_TIMESTAMP " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
2010-02-04 05:53:25 +01:00
$date = " ' $date .000' " ;
2010-02-03 06:01:37 +01:00
}
2010-05-19 12:25:46 +02:00
if ( $format == '%U' ) return " DATEDIFF(s, '19700101 12:00:00:000', $date ) " ;
2010-02-03 06:01:37 +01:00
$trans = array (
'Y' => 'yy' ,
'm' => 'mm' ,
'd' => 'dd' ,
'H' => 'hh' ,
'i' => 'mi' ,
's' => 'ss' ,
);
$strings = array ();
$buffer = $format ;
while ( strlen ( $buffer )) {
if ( substr ( $buffer , 0 , 1 ) == '%' ) {
$f = substr ( $buffer , 1 , 1 );
$flen = $f == 'Y' ? 4 : 2 ;
$strings [] = " RIGHT('0' + CAST(DATEPART( { $trans [ $f ] } , $date ) AS VARCHAR), $flen ) " ;
$buffer = substr ( $buffer , 2 );
} else {
$pos = strpos ( $buffer , '%' );
if ( $pos === false ) {
$strings [] = $buffer ;
$buffer = '' ;
} else {
$strings [] = " ' " . substr ( $buffer , 0 , $pos ) . " ' " ;
$buffer = substr ( $buffer , $pos );
}
}
}
return '(' . implode ( ' + ' , $strings ) . ')' ;
}
/**
2010-05-18 12:47:05 +02:00
* Function to return an SQL datetime expression for MSSQL .
2010-02-03 06:01:37 +01: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 ) {
$trans = array (
'year' => 'yy' ,
'month' => 'mm' ,
'day' => 'dd' ,
'hour' => 'hh' ,
'minute' => 'mi' ,
'second' => 'ss' ,
);
$singularinterval = preg_replace ( '/(year|month|day|hour|minute|second)s/i' , '$1' , $interval );
if (
! ( $params = preg_match ( '/([-+]\d+) (\w+)/i' , $singularinterval , $matches )) ||
! isset ( $trans [ strtolower ( $matches [ 2 ])])
) user_error ( 'datetimeIntervalClause(): invalid interval ' . $interval , E_USER_WARNING );
if ( preg_match ( '/^now$/i' , $date )) {
$date = " CURRENT_TIMESTAMP " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
$date = " ' $date ' " ;
}
2010-05-19 12:09:51 +02:00
return " CONVERT(VARCHAR, DATEADD( " . $trans [ strtolower ( $matches [ 2 ])] . " , " . ( int ) $matches [ 1 ] . " , $date ), 120) " ;
2010-02-03 06:01:37 +01:00
}
/**
2010-05-18 12:47:05 +02:00
* Function to return an SQL datetime expression for MSSQL .
2010-02-03 06:01:37 +01: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 = " CURRENT_TIMESTAMP " ;
} 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 = " CURRENT_TIMESTAMP " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date2 )) {
$date2 = " ' $date2 ' " ;
}
return " DATEDIFF(s, $date2 , $date1 ) " ;
}
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
*/
2009-10-26 21:59:29 +01:00
class MSSQLQuery extends SS_Query {
2010-05-19 13:37:43 +02:00
2009-02-25 06:44:52 +01:00
/**
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 ;
2010-05-19 13:37:43 +02:00
2009-02-25 06:44:52 +01:00
/**
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 ;
2010-05-19 13:37:43 +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 ;
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-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
}
2010-05-19 13:37:43 +02:00
2009-02-25 06:44:52 +01:00
public function __destroy () {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
mssql_free_result ( $this -> handle );
} else {
2010-03-08 21:15:27 +01:00
sqlsrv_free_stmt ( $this -> handle );
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2009-10-27 00:44:08 +01:00
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 {
2010-05-19 13:37:43 +02:00
user_error ( " MSSQLQuery::seek() sqlsrv doesn't support seek. " , E_USER_WARNING );
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2010-05-19 13:37:43 +02:00
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 {
2010-05-19 14:13:33 +02:00
// WARNING: This will only work if the cursor type is NOT forward only!
if ( function_exists ( 'sqlsrv_num_rows' )) {
return sqlsrv_num_rows ( $this -> handle );
}
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2010-05-19 13:37:43 +02:00
2009-02-25 06:44:52 +01:00
public function nextRecord () {
// Coalesce rather than replace common fields.
2010-05-19 13:37:43 +02:00
$output = array ();
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 )) {
foreach ( $data as $columnIdx => $value ) {
$columnName = mssql_field_name ( $this -> handle , $columnIdx );
2009-11-13 04:46:43 +01:00
// There are many places in the framework that expect the ID to be a string, not a double
// Do not set this to an integer, or it will cause failures in many tests that expect a string
if ( $columnName == 'ID' ) $value = ( string ) $value ;
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 [ $columnName ])) {
$output [ $columnName ] = $value ;
}
2009-02-25 06:44:52 +01:00
}
2010-05-19 13:37:43 +02:00
2009-05-28 04:07:11 +02:00
return $output ;
2009-02-25 06:44:52 +01:00
}
} else {
2009-09-14 03:44:58 +02:00
if ( $this -> handle && $data = sqlsrv_fetch_array ( $this -> handle , SQLSRV_FETCH_NUMERIC )) {
2009-05-28 04:07:11 +02:00
$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 ;
}
}
2010-05-19 13:37:43 +02:00
2009-05-28 04:07:11 +02:00
return $output ;
} else {
2010-05-19 13:37:43 +02:00
// Free the handle if there are no more results - sqlsrv crashes if there are too many handles
2009-09-14 03:44:58 +02:00
if ( $this -> handle ) {
sqlsrv_free_stmt ( $this -> handle );
2010-05-19 13:37:43 +02:00
$this -> handle = null ;
2009-09-14 03:44:58 +02:00
}
2009-05-28 04:07:11 +02:00
}
2009-02-25 06:44:52 +01:00
}
2010-05-19 13:37:43 +02:00
return false ;
2009-02-25 06:44:52 +01:00
}
2010-05-19 13:37:43 +02:00
2009-07-16 01:39:35 +02:00
}