2009-02-25 06:44:52 +01:00
< ? php
2009-04-22 00:19:45 +02:00
/**
2009-10-22 06:01:17 +02:00
* Microsoft SQL Server 2008 connector class .
2009-07-16 01:39:35 +02:00
*
2009-10-22 06:01:17 +02:00
* Connecting using Windows :
2009-10-22 06:02:40 +02:00
* If you 've got your website running on Windows, it' s highly recommended you
2009-10-22 06:01:17 +02:00
* use " sqlsrv " , a SQL Server driver for PHP by Microsoft .
* @ see http :// www . microsoft . com / downloads / details . aspx ? displaylang = en & FamilyID = ccdf728b - 1 ea0 - 48 a8 - a84a - 5052214 caad9
*
* Connecting using a UNIX platform :
* On other platforms such as Mac OS X and Linux , you ' ll have to use FreeTDS .
* PHP also needs to be built with mssql enabled . This is easy if you use XAMPP ,
* as it ' s already included .
* If using MacPorts , you can use " port install php5-mssql " if you ' ve already got
* the php5 port installed .
*
* @ 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 ;
2009-05-28 04:07:11 +02:00
/**
* 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 .
*/
2010-02-01 22:45:01 +01:00
protected $lastAffectedRows ;
2010-02-02 03:08:02 +01: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-01 22:45:01 +01:00
protected $supportsTransactions = false ;
2009-10-01 23:11:18 +02:00
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 ) {
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 {
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' ],
);
}
$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
/**
* Checks whether the current database as fulltext
* support enabled or not by looking into the
* system database table .
*
* @ return boolean
*/
public function fullTextEnabled () {
2010-02-03 09:13:41 +01:00
return ( boolean ) DB :: query ( " SELECT is_fulltext_enabled FROM sys.databases WHERE name = ' $this->database ' " ) -> value ();
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 .
* 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 (){
2010-02-03 09:11:31 +01:00
if ( $this -> fullTextEnabled ()) {
2009-05-08 05:37:31 +02:00
$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-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 .
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 .
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
}
2009-02-25 06:44:52 +01:00
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
}
/**
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 " ;
if ( $alteredFields ) 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
// fulltext indexes need to be dropped if alterting a table
2010-02-02 00:24:02 +01:00
if ( $this -> fulltextIndexExists ( $tableName ) === true ) {
2009-12-07 06:38:26 +01:00
$alterCol .= " \n DROP FULLTEXT INDEX ON \" $tableName\ " ; " ;
}
2009-07-28 00:39:57 +02: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 ;
}
/**
* 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....
2010-02-02 00:24:02 +01:00
$fieldRecords = $this -> query ( " SELECT ordinal_position, column_name, data_type, column_default,
2009-07-28 00:39:57 +02:00
is_nullable , character_maximum_length , numeric_precision , numeric_scale
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 ;
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 ;
}
protected function getIndexSqlDefinition ( $tableName , $indexName , $indexSpec ) {
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
$index = 'ix_' . $tableName . '_' . $indexName ;
2009-02-25 06:44:52 +01:00
2009-10-22 08:51:35 +02:00
$drop = " IF EXISTS (SELECT name FROM sys.indexes WHERE name = ' $index ') DROP INDEX $index ON \" " . $tableName . " \" ; " ;
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
$drop = '' ;
2010-02-02 00:24:02 +01:00
if ( $this -> fulltextIndexExists ( $tableName ) === true ) {
2009-12-08 21:53:15 +01:00
$drop = " DROP FULLTEXT INDEX ON \" $tableName\ " ; " ;
}
2009-10-22 08:51:35 +02:00
return $drop . " 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' ) {
return 'CREATE UNIQUE INDEX ix_' . $tableName . '_' . $indexName . " ON \" " . $tableName . " \" ( \" " . $indexSpec [ 'value' ] . " \" ); " ;
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 {
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
*/
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
}
/**
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 ) {
return 'float' ;
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 ) {
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
*/
2010-02-03 08:50:29 +01:00
public function varchar ( $values ) {
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
*/
2010-02-03 08:50:29 +01:00
public function year ( $values ) {
return 'numeric(4)' ;
2009-02-25 06:44:52 +01:00
}
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 )
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 (){
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
/**
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()' ;
}
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
/**
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 ) {
2009-10-27 08:08:51 +01:00
$searchResults = new DataObjectSet ();
2010-02-03 09:11:31 +01:00
if ( ! $this -> fullTextEnabled ()) {
2009-10-27 08:08:51 +01:00
return $searchResults ;
}
$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:
$result = DB :: query ( 'EXEC sp_help_fulltext_columns' );
$tables = array ();
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-06-05 05:44:28 +02:00
2009-03-23 01:51:28 +01:00
}
2009-10-27 08:08:51 +01: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!
$query = implode ( ' UNION ' , $tables );
$result = DB :: query ( $query );
foreach ( $result as $row ){
$row_result = DataObject :: get_by_id ( $row [ 'Source' ], $row [ 'ID' ]);
$searchResults -> push ( $row_result );
}
$searchResults -> setPageLimits ( $start , $pageLength , $totalCount );
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
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
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* 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 )) {
$date = " ' $date ' " ;
}
if ( $format == '%U' ) return " DATEDIFF(s, '19700101', $date ) " ;
$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 ) . ')' ;
}
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* 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 ' " ;
}
return " LEFT(CONVERT(VARCHAR, DATEADD( " . $trans [ strtolower ( $matches [ 2 ])] . " , " . ( int ) $matches [ 1 ] . " , $date ), 121), 19) " ;
}
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* 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 {
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 ;
/**
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-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
}
public function __destroy () {
2009-05-28 04:07:11 +02:00
if ( $this -> mssql ) {
mssql_free_result ( $this -> handle );
} else {
2009-09-14 03:44:58 +02:00
if ( $this -> handle ) 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 {
user_error ( " MSSQLQuery::seek() sqlserv doesn't support seek. " , E_USER_WARNING );
}
2009-02-25 06:44:52 +01:00
}
2009-10-27 00:44:08 +01: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
2009-10-27 00:44:08 +01:00
* This is ( usually ) called from SS_Query :: rewind (), which in turn seems to be called when a foreach ...
2009-04-07 01:08:08 +02:00
* is started on a recordset
2009-06-02 23:01:15 +02:00
*
2009-10-27 00:44:08 +01:00
* If you are using sqlsrv 1.0 , this 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
*
2009-10-27 00:44:08 +01:00
* If you are using sqlsrv 1.1 or greater , then disregard the above , because sqlsrv_num_rows () was
* added in version 1.1 of the driver .
*
* @ return boolean | int
2009-04-07 01:08:08 +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 {
2009-10-27 00:44:08 +01:00
if ( function_exists ( 'sqlsrv_num_rows' )) {
return sqlsrv_num_rows ( $this -> handle );
}
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 );
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
}
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-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
$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 {
2009-09-14 03:44:58 +02:00
// Free the handle if there are no more results - sqlserv crashes if there are too many handles
if ( $this -> handle ) {
sqlsrv_free_stmt ( $this -> handle );
$this -> handle = false ;
}
2009-05-28 04:07:11 +02:00
return false ;
}
2009-02-25 06:44:52 +01:00
}
}
2009-07-16 01:39:35 +02:00
}