2008-05-30 11:40:08 +00:00
< ? php
/**
2011-05-23 03:19:13 +00:00
* MS SQL Server layer for DBO
2008-05-30 11:40:08 +00:00
*
2017-06-10 21:33:55 +00:00
* CakePHP ( tm ) : Rapid Development Framework ( https :// cakephp . org )
2017-06-10 22:10:52 +00:00
* Copyright ( c ) Cake Software Foundation , Inc . ( https :// cakefoundation . org )
2008-05-30 11:40:08 +00:00
*
* Licensed under The MIT License
2013-02-08 12:22:51 +00:00
* For full copyright and license information , please see the LICENSE . txt
2008-05-30 11:40:08 +00:00
* Redistributions of files must retain the above copyright notice .
*
2017-06-10 22:10:52 +00:00
* @ copyright Copyright ( c ) Cake Software Foundation , Inc . ( https :// cakefoundation . org )
2017-06-10 21:33:55 +00:00
* @ link https :// cakephp . org CakePHP ( tm ) Project
2011-07-26 06:16:14 +00:00
* @ package Cake . Model . Datasource . Database
2008-10-30 17:30:26 +00:00
* @ since CakePHP ( tm ) v 0.10 . 5.1790
2017-06-10 22:23:14 +00:00
* @ license https :// opensource . org / licenses / mit - license . php MIT License
2008-05-30 11:40:08 +00:00
*/
2009-07-24 19:18:37 +00:00
2011-04-26 13:17:51 +00:00
App :: uses ( 'DboSource' , 'Model/Datasource' );
2008-05-30 11:40:08 +00:00
/**
2013-03-05 07:05:14 +00:00
* Dbo layer for Microsoft ' s official SQLServer driver
2008-05-30 11:40:08 +00:00
*
2013-02-05 17:32:40 +00:00
* A Dbo layer for MS SQL Server 2005 and higher . Requires the
* `pdo_sqlsrv` extension to be enabled .
2013-03-05 07:05:14 +00:00
*
2013-02-05 17:32:40 +00:00
* @ link http :// www . php . net / manual / en / ref . pdo - sqlsrv . php
2008-05-30 11:40:08 +00:00
*
2011-07-26 06:16:14 +00:00
* @ package Cake . Model . Datasource . Database
2008-05-30 11:40:08 +00:00
*/
2011-05-23 03:19:13 +00:00
class Sqlserver extends DboSource {
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Driver description
*
* @ var string
*/
2011-05-23 03:19:13 +00:00
public $description = " SQL Server DBO Driver " ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Starting quote character for quoted identifiers
*
* @ var string
*/
2010-04-04 07:14:00 +00:00
public $startQuote = " [ " ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Ending quote character for quoted identifiers
*
* @ var string
*/
2010-04-04 07:14:00 +00:00
public $endQuote = " ] " ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2012-12-22 22:48:15 +00:00
* Creates a map between field aliases and numeric indexes . Workaround for the
2008-05-30 11:40:08 +00:00
* SQL Server driver ' s 30 - character column name limitation .
*
* @ var array
*/
2011-04-29 02:20:11 +00:00
protected $_fieldMappings = array ();
2009-07-24 19:18:37 +00:00
2011-05-23 04:22:08 +00:00
/**
* Storing the last affected value
*
* @ var mixed
*/
protected $_lastAffected = false ;
2008-05-30 11:40:08 +00:00
/**
* Base configuration settings for MS SQL driver
*
* @ var array
*/
2010-04-04 06:36:12 +00:00
protected $_baseConfig = array (
2011-06-24 06:01:17 +00:00
'host' => 'localhost\SQLEXPRESS' ,
2011-04-28 00:41:29 +00:00
'login' => '' ,
2008-05-30 11:40:08 +00:00
'password' => '' ,
2012-02-23 02:08:17 +00:00
'database' => 'cake' ,
2012-02-23 01:20:34 +00:00
'schema' => '' ,
2013-11-24 06:04:17 +00:00
'flags' => array ()
2008-05-30 11:40:08 +00:00
);
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* MS SQL column definition
*
* @ var array
2017-03-09 15:45:35 +00:00
* @ link https :// msdn . microsoft . com / en - us / library / ms187752 . aspx SQL Server Data Types
2008-05-30 11:40:08 +00:00
*/
2010-04-04 07:14:00 +00:00
public $columns = array (
2008-05-30 11:40:08 +00:00
'primary_key' => array ( 'name' => 'IDENTITY (1, 1) NOT NULL' ),
2012-08-30 15:35:36 +00:00
'string' => array ( 'name' => 'nvarchar' , 'limit' => '255' ),
'text' => array ( 'name' => 'nvarchar' , 'limit' => 'MAX' ),
'integer' => array ( 'name' => 'int' , 'formatter' => 'intval' ),
2017-03-12 02:41:22 +00:00
'smallinteger' => array ( 'name' => 'smallint' , 'formatter' => 'intval' ),
'tinyinteger' => array ( 'name' => 'tinyint' , 'formatter' => 'intval' ),
2012-08-30 15:35:36 +00:00
'biginteger' => array ( 'name' => 'bigint' ),
2013-09-28 14:48:37 +00:00
'numeric' => array ( 'name' => 'decimal' , 'formatter' => 'floatval' ),
'decimal' => array ( 'name' => 'decimal' , 'formatter' => 'floatval' ),
'float' => array ( 'name' => 'float' , 'formatter' => 'floatval' ),
'real' => array ( 'name' => 'float' , 'formatter' => 'floatval' ),
2012-08-30 15:35:36 +00:00
'datetime' => array ( 'name' => 'datetime' , 'format' => 'Y-m-d H:i:s' , 'formatter' => 'date' ),
2008-05-30 11:40:08 +00:00
'timestamp' => array ( 'name' => 'timestamp' , 'format' => 'Y-m-d H:i:s' , 'formatter' => 'date' ),
2012-08-30 15:35:36 +00:00
'time' => array ( 'name' => 'datetime' , 'format' => 'H:i:s' , 'formatter' => 'date' ),
'date' => array ( 'name' => 'datetime' , 'format' => 'Y-m-d' , 'formatter' => 'date' ),
'binary' => array ( 'name' => 'varbinary' ),
'boolean' => array ( 'name' => 'bit' )
2008-05-30 11:40:08 +00:00
);
2009-07-24 19:18:37 +00:00
2011-06-24 06:01:17 +00:00
/**
* Magic column name used to provide pagination support for SQLServer 2008
* which lacks proper limit / offset support .
2014-02-07 14:45:00 +00:00
*
* @ var string
2011-06-24 06:01:17 +00:00
*/
2011-06-21 20:17:49 +00:00
const ROW_COUNTER = '_cake_page_rownum_' ;
2008-05-30 11:40:08 +00:00
/**
* Connects to the database using options in the given configuration array .
*
2017-08-16 18:23:42 +00:00
* Please note that the PDO :: ATTR_PERSISTENT attribute is not supported by
* the SQL Server PHP PDO drivers . As a result you cannot use the
* persistent config option when connecting to a SQL Server ( for more
* information see : https :// github . com / Microsoft / msphpsql / issues / 65 ) .
*
2014-07-03 13:36:42 +00:00
* @ return bool True if the database could be connected , else false
2017-08-16 18:23:42 +00:00
* @ throws InvalidArgumentException if an unsupported setting is in the database config
2011-07-31 20:55:52 +00:00
* @ throws MissingConnectionException
2008-05-30 11:40:08 +00:00
*/
2011-05-22 02:18:57 +00:00
public function connect () {
2008-05-30 11:40:08 +00:00
$config = $this -> config ;
$this -> connected = false ;
2013-07-15 04:18:09 +00:00
2017-08-16 18:23:42 +00:00
if ( isset ( $config [ 'persistent' ]) && $config [ 'persistent' ]) {
throw new InvalidArgumentException ( 'Config setting "persistent" cannot be set to true, as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT' );
}
2013-11-24 06:04:17 +00:00
$flags = $config [ 'flags' ] + array (
2013-07-15 04:18:09 +00:00
PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION
);
if ( ! empty ( $config [ 'encoding' ])) {
$flags [ PDO :: SQLSRV_ATTR_ENCODING ] = $config [ 'encoding' ];
}
2011-04-28 00:41:29 +00:00
try {
$this -> _connection = new PDO (
" sqlsrv:server= { $config [ 'host' ] } ;Database= { $config [ 'database' ] } " ,
$config [ 'login' ],
$config [ 'password' ],
$flags
);
2008-05-30 11:40:08 +00:00
$this -> connected = true ;
2013-03-07 22:45:42 +00:00
if ( ! empty ( $config [ 'settings' ])) {
foreach ( $config [ 'settings' ] as $key => $value ) {
$this -> _execute ( " SET $key $value " );
}
}
2011-04-28 00:41:29 +00:00
} catch ( PDOException $e ) {
2012-09-13 02:31:07 +00:00
throw new MissingConnectionException ( array (
'class' => get_class ( $this ),
'message' => $e -> getMessage ()
));
2008-05-30 11:40:08 +00:00
}
2011-04-28 00:41:29 +00:00
2008-05-30 11:40:08 +00:00
return $this -> connected ;
}
2009-07-24 19:18:37 +00:00
2009-10-20 20:11:31 +00:00
/**
2011-04-28 00:41:29 +00:00
* Check that PDO SQL Server is installed / loaded
2009-10-20 20:11:31 +00:00
*
2014-07-03 13:36:42 +00:00
* @ return bool
2009-11-14 12:19:25 +00:00
*/
2011-04-28 00:41:29 +00:00
public function enabled () {
return in_array ( 'sqlsrv' , PDO :: getAvailableDrivers ());
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of sources ( tables ) in the database .
*
2014-06-06 18:06:32 +00:00
* @ param mixed $data The names
2011-12-02 05:58:09 +00:00
* @ return array Array of table names in the database
2008-05-30 11:40:08 +00:00
*/
2011-07-30 23:17:20 +00:00
public function listSources ( $data = null ) {
2008-06-26 21:13:10 +00:00
$cache = parent :: listSources ();
2011-04-28 00:53:42 +00:00
if ( $cache !== null ) {
2008-05-30 11:40:08 +00:00
return $cache ;
}
2012-02-04 02:53:32 +00:00
$result = $this -> _execute ( " SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " );
2008-05-30 11:40:08 +00:00
2011-04-28 00:53:42 +00:00
if ( ! $result ) {
$result -> closeCursor ();
2008-05-30 11:40:08 +00:00
return array ();
2013-07-02 23:14:41 +00:00
}
$tables = array ();
2011-04-28 00:53:42 +00:00
2013-07-02 23:14:41 +00:00
while ( $line = $result -> fetch ( PDO :: FETCH_NUM )) {
$tables [] = $line [ 0 ];
2011-05-18 18:12:36 +00:00
}
2013-07-02 23:14:41 +00:00
$result -> closeCursor ();
parent :: listSources ( $tables );
return $tables ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of the fields in given table name .
*
2011-10-15 01:25:14 +00:00
* @ param Model | string $model Model object to describe , or a string table name .
2008-05-30 11:40:08 +00:00
* @ return array Fields in table . Keys are name and type
2011-07-31 20:55:52 +00:00
* @ throws CakeException
2008-05-30 11:40:08 +00:00
*/
2011-10-15 01:25:14 +00:00
public function describe ( $model ) {
2014-04-02 19:59:04 +00:00
$table = $this -> fullTableName ( $model , false , false );
$fulltable = $this -> fullTableName ( $model , false , true );
$cache = parent :: describe ( $fulltable );
2012-09-21 22:30:43 +00:00
if ( $cache ) {
2008-05-30 11:40:08 +00:00
return $cache ;
}
2014-04-02 19:59:04 +00:00
2011-06-21 21:00:28 +00:00
$fields = array ();
2014-05-06 10:43:36 +00:00
$schema = is_object ( $model ) ? $model -> schemaName : false ;
2014-04-02 19:59:04 +00:00
2011-06-22 15:09:25 +00:00
$cols = $this -> _execute (
2011-08-16 03:55:08 +00:00
" SELECT
2011-06-22 15:09:25 +00:00
COLUMN_NAME as Field ,
2011-08-16 03:55:08 +00:00
DATA_TYPE as Type ,
2014-04-02 19:59:04 +00:00
COL_LENGTH ( '" . ($schema ? $fulltable : $table) . "' , COLUMN_NAME ) as Length ,
2011-08-16 03:55:08 +00:00
IS_NULLABLE As [ Null ],
COLUMN_DEFAULT as [ Default ],
2014-04-02 19:59:04 +00:00
COLUMNPROPERTY ( OBJECT_ID ( '" . ($schema ? $fulltable : $table) . "' ), COLUMN_NAME , 'IsIdentity' ) as [ Key ],
2011-08-16 03:55:08 +00:00
NUMERIC_SCALE as Size
FROM INFORMATION_SCHEMA . COLUMNS
2014-03-06 03:18:31 +00:00
WHERE TABLE_NAME = '" . $table . "' " . ( $schema ? " AND TABLE_SCHEMA = '" . $schema . "' " : '')
2011-06-22 15:09:25 +00:00
);
2014-04-02 19:59:04 +00:00
2011-04-28 01:27:53 +00:00
if ( ! $cols ) {
2011-10-15 01:25:14 +00:00
throw new CakeException ( __d ( 'cake_dev' , 'Could not describe table for %s' , $table ));
2011-04-28 01:27:53 +00:00
}
2008-05-30 11:40:08 +00:00
2012-01-07 05:34:02 +00:00
while ( $column = $cols -> fetch ( PDO :: FETCH_OBJ )) {
2011-04-28 01:27:53 +00:00
$field = $column -> Field ;
2008-05-30 11:40:08 +00:00
$fields [ $field ] = array (
2011-06-22 15:59:51 +00:00
'type' => $this -> column ( $column ),
2011-04-28 01:27:53 +00:00
'null' => ( $column -> Null === 'YES' ? true : false ),
2013-03-20 01:02:27 +00:00
'default' => $column -> Default ,
2011-06-22 15:59:51 +00:00
'length' => $this -> length ( $column ),
2011-04-28 01:27:53 +00:00
'key' => ( $column -> Key == '1' ) ? 'primary' : false
2008-05-30 11:40:08 +00:00
);
2011-04-28 01:27:53 +00:00
2008-06-26 14:45:32 +00:00
if ( $fields [ $field ][ 'default' ] === 'null' ) {
2008-05-30 11:40:08 +00:00
$fields [ $field ][ 'default' ] = null ;
2013-03-20 01:02:27 +00:00
}
if ( $fields [ $field ][ 'default' ] !== null ) {
$fields [ $field ][ 'default' ] = preg_replace (
" /^[(] { 1,2}'?([^')]*)?'?[)] { 1,2} $ / " ,
" $ 1 " ,
$fields [ $field ][ 'default' ]
);
2008-06-26 14:45:32 +00:00
$this -> value ( $fields [ $field ][ 'default' ], $fields [ $field ][ 'type' ]);
2008-05-30 11:40:08 +00:00
}
2008-06-26 14:45:32 +00:00
2013-02-12 02:38:08 +00:00
if ( $fields [ $field ][ 'key' ] !== false && $fields [ $field ][ 'type' ] === 'integer' ) {
2008-05-30 11:40:08 +00:00
$fields [ $field ][ 'length' ] = 11 ;
2011-04-28 01:27:53 +00:00
} elseif ( $fields [ $field ][ 'key' ] === false ) {
2008-05-30 11:40:08 +00:00
unset ( $fields [ $field ][ 'key' ]);
}
if ( in_array ( $fields [ $field ][ 'type' ], array ( 'date' , 'time' , 'datetime' , 'timestamp' ))) {
$fields [ $field ][ 'length' ] = null ;
}
2013-02-12 02:38:08 +00:00
if ( $fields [ $field ][ 'type' ] === 'float' && ! empty ( $column -> Size )) {
2011-06-20 23:13:26 +00:00
$fields [ $field ][ 'length' ] = $fields [ $field ][ 'length' ] . ',' . $column -> Size ;
}
2008-05-30 11:40:08 +00:00
}
2011-08-20 05:39:30 +00:00
$this -> _cacheDescription ( $table , $fields );
2011-04-28 01:27:53 +00:00
$cols -> closeCursor ();
2008-05-30 11:40:08 +00:00
return $fields ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Generates the fields list of an SQL query .
*
2014-06-06 18:06:32 +00:00
* @ param Model $model The model to get fields for .
2011-12-02 05:58:09 +00:00
* @ param string $alias Alias table name
2014-06-06 18:06:32 +00:00
* @ param array $fields The fields so far .
2014-07-03 13:36:42 +00:00
* @ param bool $quote Whether or not to quote identfiers .
2008-05-30 11:40:08 +00:00
* @ return array
*/
2012-02-23 14:10:29 +00:00
public function fields ( Model $model , $alias = null , $fields = array (), $quote = true ) {
2008-05-30 11:40:08 +00:00
if ( empty ( $alias )) {
$alias = $model -> alias ;
}
$fields = parent :: fields ( $model , $alias , $fields , false );
$count = count ( $fields );
2009-08-02 06:47:28 +00:00
if ( $count >= 1 && strpos ( $fields [ 0 ], 'COUNT(*)' ) === false ) {
$result = array ();
2008-05-30 11:40:08 +00:00
for ( $i = 0 ; $i < $count ; $i ++ ) {
$prepend = '' ;
2013-10-30 03:01:20 +00:00
if ( strpos ( $fields [ $i ], 'DISTINCT' ) !== false && strpos ( $fields [ $i ], 'COUNT' ) === false ) {
2008-05-30 11:40:08 +00:00
$prepend = 'DISTINCT ' ;
$fields [ $i ] = trim ( str_replace ( 'DISTINCT' , '' , $fields [ $i ]));
}
if ( ! preg_match ( '/\s+AS\s+/i' , $fields [ $i ])) {
2013-02-12 02:38:08 +00:00
if ( substr ( $fields [ $i ], - 1 ) === '*' ) {
2009-08-02 06:47:28 +00:00
if ( strpos ( $fields [ $i ], '.' ) !== false && $fields [ $i ] != $alias . '.*' ) {
$build = explode ( '.' , $fields [ $i ]);
$AssociatedModel = $model -> { $build [ 0 ]};
} else {
$AssociatedModel = $model ;
}
$_fields = $this -> fields ( $AssociatedModel , $AssociatedModel -> alias , array_keys ( $AssociatedModel -> schema ()));
$result = array_merge ( $result , $_fields );
continue ;
}
2008-05-30 11:40:08 +00:00
if ( strpos ( $fields [ $i ], '.' ) === false ) {
2011-06-21 23:28:33 +00:00
$this -> _fieldMappings [ $alias . '__' . $fields [ $i ]] = $alias . '.' . $fields [ $i ];
2012-11-21 14:39:03 +00:00
$fieldName = $this -> name ( $alias . '.' . $fields [ $i ]);
2011-06-21 23:28:33 +00:00
$fieldAlias = $this -> name ( $alias . '__' . $fields [ $i ]);
2008-05-30 11:40:08 +00:00
} else {
$build = explode ( '.' , $fields [ $i ]);
2012-12-27 01:55:07 +00:00
$build [ 0 ] = trim ( $build [ 0 ], '[]' );
$build [ 1 ] = trim ( $build [ 1 ], '[]' );
$name = $build [ 0 ] . '.' . $build [ 1 ];
$alias = $build [ 0 ] . '__' . $build [ 1 ];
$this -> _fieldMappings [ $alias ] = $name ;
$fieldName = $this -> name ( $name );
$fieldAlias = $this -> name ( $alias );
2008-05-30 11:40:08 +00:00
}
2013-02-12 02:38:08 +00:00
if ( $model -> getColumnType ( $fields [ $i ]) === 'datetime' ) {
2008-05-30 11:40:08 +00:00
$fieldName = " CONVERT(VARCHAR(20), { $fieldName } , 20) " ;
}
2012-03-04 19:18:04 +00:00
$fields [ $i ] = " { $fieldName } AS { $fieldAlias } " ;
2008-05-30 11:40:08 +00:00
}
2009-08-02 06:47:28 +00:00
$result [] = $prepend . $fields [ $i ];
2008-05-30 11:40:08 +00:00
}
2009-08-02 06:47:28 +00:00
return $result ;
2008-05-30 11:40:08 +00:00
}
2013-07-02 23:14:41 +00:00
return $fields ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Generates and executes an SQL INSERT statement for given model , fields , and values .
* Removes Identity ( primary key ) column from update data before returning to parent , if
* value is empty .
*
2014-06-06 18:06:32 +00:00
* @ param Model $model The model to insert into .
* @ param array $fields The fields to set .
* @ param array $values The values to set .
2008-05-30 11:40:08 +00:00
* @ return array
*/
2011-07-30 23:17:20 +00:00
public function create ( Model $model , $fields = null , $values = null ) {
2008-05-30 11:40:08 +00:00
if ( ! empty ( $values )) {
$fields = array_combine ( $fields , $values );
}
2011-05-18 18:59:17 +00:00
$primaryKey = $this -> _getPrimaryKey ( $model );
2008-05-30 11:40:08 +00:00
2009-04-29 16:26:03 +00:00
if ( array_key_exists ( $primaryKey , $fields )) {
if ( empty ( $fields [ $primaryKey ])) {
unset ( $fields [ $primaryKey ]);
2008-05-30 11:40:08 +00:00
} else {
2009-04-29 16:26:03 +00:00
$this -> _execute ( 'SET IDENTITY_INSERT ' . $this -> fullTableName ( $model ) . ' ON' );
2008-05-30 11:40:08 +00:00
}
}
$result = parent :: create ( $model , array_keys ( $fields ), array_values ( $fields ));
2009-04-29 16:26:03 +00:00
if ( array_key_exists ( $primaryKey , $fields ) && ! empty ( $fields [ $primaryKey ])) {
$this -> _execute ( 'SET IDENTITY_INSERT ' . $this -> fullTableName ( $model ) . ' OFF' );
2008-05-30 11:40:08 +00:00
}
return $result ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Generates and executes an SQL UPDATE statement for given model , fields , and values .
* Removes Identity ( primary key ) column from update data before returning to parent .
*
2014-06-06 18:06:32 +00:00
* @ param Model $model The model to update .
* @ param array $fields The fields to set .
* @ param array $values The values to set .
* @ param mixed $conditions The conditions to use .
2008-05-30 11:40:08 +00:00
* @ return array
*/
2011-07-30 23:17:20 +00:00
public function update ( Model $model , $fields = array (), $values = null , $conditions = null ) {
2008-05-30 11:40:08 +00:00
if ( ! empty ( $values )) {
$fields = array_combine ( $fields , $values );
}
if ( isset ( $fields [ $model -> primaryKey ])) {
unset ( $fields [ $model -> primaryKey ]);
}
2009-08-02 06:47:28 +00:00
if ( empty ( $fields )) {
return true ;
}
2008-05-30 11:40:08 +00:00
return parent :: update ( $model , array_keys ( $fields ), array_values ( $fields ), $conditions );
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns a limit statement in the correct format for the particular database .
*
2014-07-03 13:36:42 +00:00
* @ param int $limit Limit of results returned
* @ param int $offset Offset from which to start results
2008-05-30 11:40:08 +00:00
* @ return string SQL limit / offset statement
*/
2011-05-22 02:18:57 +00:00
public function limit ( $limit , $offset = null ) {
2008-05-30 11:40:08 +00:00
if ( $limit ) {
$rt = '' ;
if ( ! strpos ( strtolower ( $limit ), 'top' ) || strpos ( strtolower ( $limit ), 'top' ) === 0 ) {
$rt = ' TOP' ;
}
2013-05-03 03:29:10 +00:00
$rt .= sprintf ( ' %u' , $limit );
2008-05-30 11:40:08 +00:00
if ( is_int ( $offset ) && $offset > 0 ) {
2013-05-03 03:29:10 +00:00
$rt = sprintf ( ' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY' , $offset , $limit );
2008-05-30 11:40:08 +00:00
}
return $rt ;
}
return null ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Converts database - layer column types to basic types
*
2011-06-22 15:59:51 +00:00
* @ param mixed $real Either the string value of the fields type .
* or the Result object from Sqlserver :: describe ()
2008-05-30 11:40:08 +00:00
* @ return string Abstract column type ( i . e . " string " )
*/
2011-05-22 02:18:57 +00:00
public function column ( $real ) {
2009-08-02 06:47:28 +00:00
$limit = null ;
2011-06-22 15:59:51 +00:00
$col = $real ;
if ( is_object ( $real ) && isset ( $real -> Field )) {
$limit = $real -> Length ;
$col = $real -> Type ;
2008-05-30 11:40:08 +00:00
}
2013-02-12 02:38:08 +00:00
if ( $col === 'datetime2' ) {
2011-06-22 17:15:15 +00:00
return 'datetime' ;
}
2008-05-30 11:40:08 +00:00
if ( in_array ( $col , array ( 'date' , 'time' , 'datetime' , 'timestamp' ))) {
return $col ;
}
2013-02-12 02:38:08 +00:00
if ( $col === 'bit' ) {
2008-05-30 11:40:08 +00:00
return 'boolean' ;
}
2012-08-30 15:35:36 +00:00
if ( strpos ( $col , 'bigint' ) !== false ) {
return 'biginteger' ;
}
2017-03-05 17:25:14 +00:00
if ( strpos ( $col , 'smallint' ) !== false ) {
2017-03-12 02:41:22 +00:00
return 'smallinteger' ;
2017-03-05 17:25:14 +00:00
}
if ( strpos ( $col , 'tinyint' ) !== false ) {
2017-03-12 02:41:22 +00:00
return 'tinyinteger' ;
2017-03-05 17:25:14 +00:00
}
2008-05-30 11:40:08 +00:00
if ( strpos ( $col , 'int' ) !== false ) {
return 'integer' ;
}
2011-06-22 16:54:35 +00:00
if ( strpos ( $col , 'char' ) !== false && $limit == - 1 ) {
2011-06-22 15:59:51 +00:00
return 'text' ;
}
2008-05-30 11:40:08 +00:00
if ( strpos ( $col , 'char' ) !== false ) {
return 'string' ;
}
if ( strpos ( $col , 'text' ) !== false ) {
return 'text' ;
}
2013-02-12 02:38:08 +00:00
if ( strpos ( $col , 'binary' ) !== false || $col === 'image' ) {
2008-05-30 11:40:08 +00:00
return 'binary' ;
}
2013-09-28 14:48:37 +00:00
if ( in_array ( $col , array ( 'float' , 'real' ))) {
2008-05-30 11:40:08 +00:00
return 'float' ;
}
2013-09-28 14:48:37 +00:00
if ( in_array ( $col , array ( 'decimal' , 'numeric' ))) {
return 'decimal' ;
}
2008-05-30 11:40:08 +00:00
return 'text' ;
}
2009-07-24 19:18:37 +00:00
2011-06-22 15:59:51 +00:00
/**
* Handle SQLServer specific length properties .
* SQLServer handles text types as nvarchar / varchar with a length of - 1.
*
* @ param mixed $length Either the length as a string , or a Column descriptor object .
* @ return mixed null | integer with length of column .
*/
public function length ( $length ) {
if ( is_object ( $length ) && isset ( $length -> Length )) {
2011-06-22 17:15:15 +00:00
if ( $length -> Length == - 1 && strpos ( $length -> Type , 'char' ) !== false ) {
2011-06-22 15:59:51 +00:00
return null ;
}
2011-06-22 18:35:02 +00:00
if ( in_array ( $length -> Type , array ( 'nchar' , 'nvarchar' ))) {
return floor ( $length -> Length / 2 );
}
2014-12-29 21:47:50 +00:00
if ( $length -> Type === 'text' ) {
return null ;
}
2011-06-22 15:59:51 +00:00
return $length -> Length ;
}
return parent :: length ( $length );
}
2008-05-30 11:40:08 +00:00
/**
2011-04-29 01:46:56 +00:00
* Builds a map of the columns contained in a result
2008-05-30 11:40:08 +00:00
*
2014-06-06 18:06:32 +00:00
* @ param PDOStatement $results The result to modify .
2011-07-30 22:38:57 +00:00
* @ return void
2008-05-30 11:40:08 +00:00
*/
2011-05-22 02:18:57 +00:00
public function resultSet ( $results ) {
2008-05-30 11:40:08 +00:00
$this -> map = array ();
2011-04-29 01:46:56 +00:00
$numFields = $results -> columnCount ();
2008-05-30 11:40:08 +00:00
$index = 0 ;
2011-04-29 01:46:56 +00:00
while ( $numFields -- > 0 ) {
$column = $results -> getColumnMeta ( $index );
2011-05-18 18:12:36 +00:00
$name = $column [ 'name' ];
2008-05-30 11:40:08 +00:00
2011-05-18 18:12:36 +00:00
if ( strpos ( $name , '__' )) {
if ( isset ( $this -> _fieldMappings [ $name ]) && strpos ( $this -> _fieldMappings [ $name ], '.' )) {
$map = explode ( '.' , $this -> _fieldMappings [ $name ]);
} elseif ( isset ( $this -> _fieldMappings [ $name ])) {
$map = array ( 0 , $this -> _fieldMappings [ $name ]);
2008-05-30 11:40:08 +00:00
} else {
2011-05-18 18:12:36 +00:00
$map = array ( 0 , $name );
2008-05-30 11:40:08 +00:00
}
} else {
2011-05-18 18:12:36 +00:00
$map = array ( 0 , $name );
2008-05-30 11:40:08 +00:00
}
2013-02-12 02:38:08 +00:00
$map [] = ( $column [ 'sqlsrv:decl_type' ] === 'bit' ) ? 'boolean' : $column [ 'native_type' ];
2011-05-18 18:12:36 +00:00
$this -> map [ $index ++ ] = $map ;
2008-05-30 11:40:08 +00:00
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Builds final SQL statement
*
* @ param string $type Query type
* @ param array $data Query data
* @ return string
*/
2011-05-22 02:18:57 +00:00
public function renderStatement ( $type , $data ) {
2008-06-26 14:45:32 +00:00
switch ( strtolower ( $type )) {
case 'select' :
extract ( $data );
$fields = trim ( $fields );
2008-05-30 11:40:08 +00:00
2017-02-03 05:35:15 +00:00
$having = ! empty ( $having ) ? " $having " : '' ;
$lock = ! empty ( $lock ) ? " $lock " : '' ;
2008-06-26 14:45:32 +00:00
if ( strpos ( $limit , 'TOP' ) !== false && strpos ( $fields , 'DISTINCT ' ) === 0 ) {
$limit = 'DISTINCT ' . trim ( $limit );
$fields = substr ( $fields , 9 );
}
2008-05-30 11:40:08 +00:00
2011-06-23 00:01:36 +00:00
// hack order as SQLServer requires an order if there is a limit.
if ( $limit && ! $order ) {
$order = 'ORDER BY (SELECT NULL)' ;
}
// For older versions use the subquery version of pagination.
2012-04-01 01:58:33 +00:00
if ( version_compare ( $this -> getVersion (), '11' , '<' ) && preg_match ( '/FETCH\sFIRST\s+([0-9]+)/i' , $limit , $offset )) {
2011-06-23 00:01:36 +00:00
preg_match ( '/OFFSET\s*(\d+)\s*.*?(\d+)\s*ROWS/' , $limit , $limitOffset );
2014-09-10 14:29:23 +00:00
$limit = 'TOP ' . ( int ) $limitOffset [ 2 ];
$page = ( int )( $limitOffset [ 1 ] / $limitOffset [ 2 ]);
$offset = ( int )( $limitOffset [ 2 ] * $page );
2011-06-21 20:00:17 +00:00
2015-07-21 08:22:53 +00:00
$rowCounter = static :: ROW_COUNTER ;
2013-10-30 02:47:50 +00:00
$sql = " SELECT { $limit } * FROM (
2011-06-21 20:17:49 +00:00
SELECT { $fields }, ROW_NUMBER () OVER ({ $order }) AS { $rowCounter }
2017-02-03 05:35:15 +00:00
FROM { $table } { $alias }{ $lock } { $joins } { $conditions } { $group }{ $having }
2011-06-21 20:17:49 +00:00
) AS _cake_paging_
2011-06-23 00:01:36 +00:00
WHERE _cake_paging_ . { $rowCounter } > { $offset }
2011-06-21 20:17:49 +00:00
ORDER BY _cake_paging_ . { $rowCounter }
2011-06-21 20:00:17 +00:00
" ;
2013-10-30 02:47:50 +00:00
return trim ( $sql );
2013-09-11 21:15:05 +00:00
}
if ( strpos ( $limit , 'FETCH' ) !== false ) {
2017-02-03 05:35:15 +00:00
return trim ( " SELECT { $fields } FROM { $table } { $alias } { $lock } { $joins } { $conditions } { $group } { $having } { $order } { $limit } " );
2008-06-26 14:45:32 +00:00
}
2017-02-03 05:35:15 +00:00
return trim ( " SELECT { $limit } { $fields } FROM { $table } { $alias } { $lock } { $joins } { $conditions } { $group } { $having } { $order } " );
2008-06-26 14:45:32 +00:00
case " schema " :
extract ( $data );
foreach ( $indexes as $i => $index ) {
if ( preg_match ( '/PRIMARY KEY/' , $index )) {
unset ( $indexes [ $i ]);
break ;
}
}
foreach ( array ( 'columns' , 'indexes' ) as $var ) {
if ( is_array ( ${$var} )) {
2009-11-19 22:13:35 +00:00
${$var} = " \t " . implode ( " , \n \t " , array_filter ( ${$var} ));
2008-06-26 14:45:32 +00:00
}
}
2013-10-30 02:47:50 +00:00
return trim ( " CREATE TABLE { $table } ( \n { $columns } ); \n { $indexes } " );
2008-06-26 14:45:32 +00:00
default :
return parent :: renderStatement ( $type , $data );
2008-05-30 11:40:08 +00:00
}
}
2009-07-24 19:18:37 +00:00
2011-06-23 22:46:03 +00:00
/**
* Returns a quoted and escaped string of $data for use in an SQL statement .
*
* @ param string $data String to be prepared for use in an SQL statement
* @ param string $column The column into which this data will be inserted
2015-01-05 17:04:24 +00:00
* @ param bool $null Column allows NULL values
2011-06-23 22:46:03 +00:00
* @ return string Quoted and escaped data
*/
2015-01-05 17:04:24 +00:00
public function value ( $data , $column = null , $null = true ) {
2013-09-09 13:33:54 +00:00
if ( $data === null || is_array ( $data ) || is_object ( $data )) {
2015-01-05 17:04:24 +00:00
return parent :: value ( $data , $column , $null );
2013-09-11 21:15:05 +00:00
}
if ( in_array ( $data , array ( '{$__cakeID__$}' , '{$__cakeForeignKey__$}' ), true )) {
2011-06-23 23:13:35 +00:00
return $data ;
2011-06-23 22:46:03 +00:00
}
if ( empty ( $column )) {
$column = $this -> introspectType ( $data );
}
switch ( $column ) {
case 'string' :
case 'text' :
return 'N' . $this -> _connection -> quote ( $data , PDO :: PARAM_STR );
default :
2015-01-05 17:04:24 +00:00
return parent :: value ( $data , $column , $null );
2011-06-23 22:46:03 +00:00
}
}
2011-12-06 20:52:48 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of all result rows for a given SQL query .
* Returns false if no rows matched .
*
2014-06-06 18:06:32 +00:00
* @ param Model $model The model to read from
* @ param array $queryData The query data
2014-07-03 13:36:42 +00:00
* @ param int $recursive How many layers to go .
2013-10-29 16:32:20 +00:00
* @ return array | false Array of resultset rows , or false if no rows matched
2008-05-30 11:40:08 +00:00
*/
2011-07-30 23:17:20 +00:00
public function read ( Model $model , $queryData = array (), $recursive = null ) {
2008-05-30 11:40:08 +00:00
$results = parent :: read ( $model , $queryData , $recursive );
2011-04-29 02:20:11 +00:00
$this -> _fieldMappings = array ();
2008-05-30 11:40:08 +00:00
return $results ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2011-06-21 20:17:49 +00:00
* Fetches the next row from the current result set .
* Eats the magic ROW_COUNTER variable .
2008-05-30 11:40:08 +00:00
*
2011-04-29 01:46:56 +00:00
* @ return mixed
2008-05-30 11:40:08 +00:00
*/
2011-05-22 02:18:57 +00:00
public function fetchResult () {
2011-11-25 18:05:32 +00:00
if ( $row = $this -> _result -> fetch ( PDO :: FETCH_NUM )) {
2008-05-30 11:40:08 +00:00
$resultRow = array ();
2011-04-29 01:46:56 +00:00
foreach ( $this -> map as $col => $meta ) {
list ( $table , $column , $type ) = $meta ;
2015-07-21 08:22:53 +00:00
if ( $table === 0 && $column === static :: ROW_COUNTER ) {
2011-06-21 20:17:49 +00:00
continue ;
}
2011-04-29 01:46:56 +00:00
$resultRow [ $table ][ $column ] = $row [ $col ];
2013-08-16 18:12:49 +00:00
if ( $type === 'boolean' && $row [ $col ] !== null ) {
2011-04-29 01:46:56 +00:00
$resultRow [ $table ][ $column ] = $this -> boolean ( $resultRow [ $table ][ $column ]);
}
2008-05-30 11:40:08 +00:00
}
return $resultRow ;
}
2011-04-29 01:46:56 +00:00
$this -> _result -> closeCursor ();
return false ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2009-04-29 16:26:03 +00:00
/**
* Inserts multiple values into a table
*
2014-06-06 18:06:32 +00:00
* @ param string $table The table to insert into .
* @ param string $fields The fields to set .
* @ param array $values The values to set .
2011-07-30 22:38:57 +00:00
* @ return void
2009-04-29 16:26:03 +00:00
*/
2011-05-18 18:59:17 +00:00
public function insertMulti ( $table , $fields , $values ) {
$primaryKey = $this -> _getPrimaryKey ( $table );
2012-09-21 22:30:43 +00:00
$hasPrimaryKey = $primaryKey && (
2009-04-29 16:26:03 +00:00
( is_array ( $fields ) && in_array ( $primaryKey , $fields )
|| ( is_string ( $fields ) && strpos ( $fields , $this -> startQuote . $primaryKey . $this -> endQuote ) !== false ))
);
if ( $hasPrimaryKey ) {
$this -> _execute ( 'SET IDENTITY_INSERT ' . $this -> fullTableName ( $table ) . ' ON' );
}
2011-05-22 04:49:49 +00:00
2012-01-07 05:07:50 +00:00
parent :: insertMulti ( $table , $fields , $values );
2011-05-22 04:49:49 +00:00
2009-04-29 16:26:03 +00:00
if ( $hasPrimaryKey ) {
$this -> _execute ( 'SET IDENTITY_INSERT ' . $this -> fullTableName ( $table ) . ' OFF' );
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Generate a database - native column schema string
*
2012-07-18 01:55:29 +00:00
* @ param array $column An array structured like the
2012-04-02 02:10:05 +00:00
* following : array ( 'name' => 'value' , 'type' => 'value' [, options ]),
2009-08-02 06:47:28 +00:00
* where options can be 'default' , 'length' , or 'key' .
2008-05-30 11:40:08 +00:00
* @ return string
*/
2011-05-22 02:18:57 +00:00
public function buildColumn ( $column ) {
2012-04-02 02:10:05 +00:00
$result = parent :: buildColumn ( $column );
2012-08-30 15:35:36 +00:00
$result = preg_replace ( '/(bigint|int|integer)\([0-9]+\)/i' , '$1' , $result );
2012-04-02 02:10:05 +00:00
$result = preg_replace ( '/(bit)\([0-9]+\)/i' , '$1' , $result );
2009-08-02 06:47:28 +00:00
if ( strpos ( $result , 'DEFAULT NULL' ) !== false ) {
2011-05-22 01:50:25 +00:00
if ( isset ( $column [ 'default' ]) && $column [ 'default' ] === '' ) {
$result = str_replace ( 'DEFAULT NULL' , " DEFAULT '' " , $result );
} else {
$result = str_replace ( 'DEFAULT NULL' , 'NULL' , $result );
}
2014-04-29 12:19:33 +00:00
} elseif ( array_keys ( $column ) === array ( 'type' , 'name' )) {
2009-08-02 06:47:28 +00:00
$result .= ' NULL' ;
2011-06-23 23:13:35 +00:00
} elseif ( strpos ( $result , " DEFAULT N' " )) {
$result = str_replace ( " DEFAULT N' " , " DEFAULT ' " , $result );
2008-05-30 11:40:08 +00:00
}
return $result ;
}
2009-07-24 19:18:37 +00:00
2008-06-26 14:45:32 +00:00
/**
* Format indexes for create table
*
2014-06-06 18:06:32 +00:00
* @ param array $indexes The indexes to build
* @ param string $table The table to make indexes for .
2008-06-26 14:45:32 +00:00
* @ return string
*/
2011-05-22 02:18:57 +00:00
public function buildIndex ( $indexes , $table = null ) {
2008-06-26 14:45:32 +00:00
$join = array ();
foreach ( $indexes as $name => $value ) {
2013-02-12 02:38:08 +00:00
if ( $name === 'PRIMARY' ) {
2009-09-03 01:07:58 +00:00
$join [] = 'PRIMARY KEY (' . $this -> name ( $value [ 'column' ]) . ')' ;
2012-02-23 13:38:02 +00:00
} elseif ( isset ( $value [ 'unique' ]) && $value [ 'unique' ]) {
2008-06-26 14:45:32 +00:00
$out = " ALTER TABLE { $table } ADD CONSTRAINT { $name } UNIQUE " ;
if ( is_array ( $value [ 'column' ])) {
2009-11-19 22:13:35 +00:00
$value [ 'column' ] = implode ( ', ' , array_map ( array ( & $this , 'name' ), $value [ 'column' ]));
2008-06-26 14:45:32 +00:00
} else {
$value [ 'column' ] = $this -> name ( $value [ 'column' ]);
}
$out .= " ( { $value [ 'column' ] } ); " ;
2009-09-03 01:07:58 +00:00
$join [] = $out ;
2008-06-26 14:45:32 +00:00
}
}
return $join ;
}
2009-07-24 19:18:37 +00:00
2009-04-29 16:26:03 +00:00
/**
* Makes sure it will return the primary key
*
2012-05-13 00:43:31 +00:00
* @ param Model | string $model Model instance of table name
2009-04-29 16:26:03 +00:00
* @ return string
*/
2011-05-22 02:18:57 +00:00
protected function _getPrimaryKey ( $model ) {
2011-05-18 18:59:17 +00:00
$schema = $this -> describe ( $model );
2009-04-29 16:26:03 +00:00
foreach ( $schema as $field => $props ) {
2013-02-12 02:38:08 +00:00
if ( isset ( $props [ 'key' ]) && $props [ 'key' ] === 'primary' ) {
2009-04-29 16:26:03 +00:00
return $field ;
}
}
return null ;
}
2011-05-22 02:40:30 +00:00
2011-05-23 04:22:08 +00:00
/**
* Returns number of affected rows in previous database operation . If no previous operation exists ,
* this returns false .
*
2014-06-06 18:06:32 +00:00
* @ param mixed $source Unused
2014-07-03 13:36:42 +00:00
* @ return int Number of affected rows
2011-05-23 04:22:08 +00:00
*/
2011-07-30 23:17:20 +00:00
public function lastAffected ( $source = null ) {
2011-05-23 04:22:08 +00:00
$affected = parent :: lastAffected ();
if ( $affected === null && $this -> _lastAffected !== false ) {
return $this -> _lastAffected ;
}
return $affected ;
}
2011-12-06 20:52:48 +00:00
2011-05-22 02:40:30 +00:00
/**
* Executes given SQL statement .
*
* @ param string $sql SQL statement
2011-05-22 04:49:49 +00:00
* @ param array $params list of params to be bound to query ( supported only in select )
2011-05-22 02:40:30 +00:00
* @ param array $prepareOptions Options to be used in the prepare statement
2011-12-02 05:58:09 +00:00
* @ return mixed PDOStatement if query executes with no problem , true as the result of a successful , false on error
* query returning no rows , such as a CREATE statement , false otherwise
2012-03-04 19:18:04 +00:00
* @ throws PDOException
2011-05-22 02:40:30 +00:00
*/
protected function _execute ( $sql , $params = array (), $prepareOptions = array ()) {
2011-05-23 04:22:08 +00:00
$this -> _lastAffected = false ;
2014-04-11 21:25:07 +00:00
$sql = trim ( $sql );
2012-09-14 17:42:25 +00:00
if ( strncasecmp ( $sql , 'SELECT' , 6 ) === 0 || preg_match ( '/^EXEC(?:UTE)?\s/mi' , $sql ) > 0 ) {
2011-05-22 04:49:49 +00:00
$prepareOptions += array ( PDO :: ATTR_CURSOR => PDO :: CURSOR_SCROLL );
return parent :: _execute ( $sql , $params , $prepareOptions );
}
2011-05-23 02:16:22 +00:00
try {
2011-05-23 04:22:08 +00:00
$this -> _lastAffected = $this -> _connection -> exec ( $sql );
if ( $this -> _lastAffected === false ) {
$this -> _results = null ;
$error = $this -> _connection -> errorInfo ();
$this -> error = $error [ 2 ];
return false ;
}
2011-05-23 02:16:22 +00:00
return true ;
} catch ( PDOException $e ) {
2011-09-29 01:53:06 +00:00
if ( isset ( $query -> queryString )) {
$e -> queryString = $query -> queryString ;
} else {
$e -> queryString = $sql ;
}
throw $e ;
2011-05-23 02:16:22 +00:00
}
2011-05-22 02:40:30 +00:00
}
2011-06-21 00:19:06 +00:00
/**
2012-12-20 12:47:03 +00:00
* Generate a " drop table " statement for the given table
2011-06-21 00:19:06 +00:00
*
2012-12-20 12:47:03 +00:00
* @ param type $table Name of the table to drop
* @ return string Drop table SQL statement
2011-06-21 00:19:06 +00:00
*/
2012-12-20 12:47:03 +00:00
protected function _dropTable ( $table ) {
return " IF OBJECT_ID(' " . $this -> fullTableName ( $table , false ) . " ', 'U') IS NOT NULL DROP TABLE " . $this -> fullTableName ( $table ) . " ; " ;
2011-06-21 00:19:06 +00:00
}
2011-11-05 10:57:08 +00:00
/**
* Gets the schema name
*
* @ return string The schema name
*/
public function getSchemaName () {
2012-02-23 01:20:34 +00:00
return $this -> config [ 'schema' ];
2011-11-05 10:57:08 +00:00
}
2017-02-03 05:35:15 +00:00
/**
* Returns a locking hint for the given mode .
2017-03-02 11:34:28 +00:00
*
* Currently , this method only returns WITH ( UPDLOCK ) when the mode is set to true .
2017-02-03 05:35:15 +00:00
*
* @ param mixed $mode Lock mode
2017-03-02 11:34:28 +00:00
* @ return string | null WITH ( UPDLOCK ) clause or null
2017-02-03 05:35:15 +00:00
*/
public function getLockingHint ( $mode ) {
if ( $mode !== true ) {
return null ;
}
return ' WITH (UPDLOCK)' ;
}
2008-05-30 11:40:08 +00:00
}