pictcode / lib / Cake / Model / Datasource / Database / Sqlserver.php @ 22fd3a57
履歴 | 表示 | アノテート | ダウンロード (23.596 KB)
| 1 | 635eef61 | spyder1211 | <?php
 | 
      
|---|---|---|---|
| 2 | /**
 | 
      ||
| 3 |  * MS SQL Server layer for DBO
 | 
      ||
| 4 |  *
 | 
      ||
| 5 |  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 | 
      ||
| 6 |  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 | 
      ||
| 7 |  *
 | 
      ||
| 8 |  * Licensed under The MIT License
 | 
      ||
| 9 |  * For full copyright and license information, please see the LICENSE.txt
 | 
      ||
| 10 |  * Redistributions of files must retain the above copyright notice.
 | 
      ||
| 11 |  *
 | 
      ||
| 12 |  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 | 
      ||
| 13 |  * @link          http://cakephp.org CakePHP(tm) Project
 | 
      ||
| 14 |  * @package       Cake.Model.Datasource.Database
 | 
      ||
| 15 |  * @since         CakePHP(tm) v 0.10.5.1790
 | 
      ||
| 16 |  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 | 
      ||
| 17 |  */
 | 
      ||
| 18 | |||
| 19 | App::uses('DboSource', 'Model/Datasource');  | 
      ||
| 20 | |||
| 21 | /**
 | 
      ||
| 22 |  * Dbo layer for Microsoft's official SQLServer driver
 | 
      ||
| 23 |  *
 | 
      ||
| 24 |  * A Dbo layer for MS SQL Server 2005 and higher. Requires the
 | 
      ||
| 25 |  * `pdo_sqlsrv` extension to be enabled.
 | 
      ||
| 26 |  *
 | 
      ||
| 27 |  * @link http://www.php.net/manual/en/ref.pdo-sqlsrv.php
 | 
      ||
| 28 |  *
 | 
      ||
| 29 |  * @package       Cake.Model.Datasource.Database
 | 
      ||
| 30 |  */
 | 
      ||
| 31 | class Sqlserver extends DboSource {  | 
      ||
| 32 | |||
| 33 | /**
 | 
      ||
| 34 |  * Driver description
 | 
      ||
| 35 |  *
 | 
      ||
| 36 |  * @var string
 | 
      ||
| 37 |  */
 | 
      ||
| 38 | public $description = "SQL Server DBO Driver";  | 
      ||
| 39 | |||
| 40 | /**
 | 
      ||
| 41 |  * Starting quote character for quoted identifiers
 | 
      ||
| 42 |  *
 | 
      ||
| 43 |  * @var string
 | 
      ||
| 44 |  */
 | 
      ||
| 45 | public $startQuote = "[";  | 
      ||
| 46 | |||
| 47 | /**
 | 
      ||
| 48 |  * Ending quote character for quoted identifiers
 | 
      ||
| 49 |  *
 | 
      ||
| 50 |  * @var string
 | 
      ||
| 51 |  */
 | 
      ||
| 52 | public $endQuote = "]";  | 
      ||
| 53 | |||
| 54 | /**
 | 
      ||
| 55 |  * Creates a map between field aliases and numeric indexes. Workaround for the
 | 
      ||
| 56 |  * SQL Server driver's 30-character column name limitation.
 | 
      ||
| 57 |  *
 | 
      ||
| 58 |  * @var array
 | 
      ||
| 59 |  */
 | 
      ||
| 60 | protected $_fieldMappings = array();  | 
      ||
| 61 | |||
| 62 | /**
 | 
      ||
| 63 |  * Storing the last affected value
 | 
      ||
| 64 |  *
 | 
      ||
| 65 |  * @var mixed
 | 
      ||
| 66 |  */
 | 
      ||
| 67 | protected $_lastAffected = false;  | 
      ||
| 68 | |||
| 69 | /**
 | 
      ||
| 70 |  * Base configuration settings for MS SQL driver
 | 
      ||
| 71 |  *
 | 
      ||
| 72 |  * @var array
 | 
      ||
| 73 |  */
 | 
      ||
| 74 | protected $_baseConfig = array(  | 
      ||
| 75 | 'persistent' => true,  | 
      ||
| 76 | 'host' => 'localhost\SQLEXPRESS',  | 
      ||
| 77 | 'login' => '',  | 
      ||
| 78 | 'password' => '',  | 
      ||
| 79 | 'database' => 'cake',  | 
      ||
| 80 | 'schema' => '',  | 
      ||
| 81 | 'flags' => array()  | 
      ||
| 82 | );  | 
      ||
| 83 | |||
| 84 | /**
 | 
      ||
| 85 |  * MS SQL column definition
 | 
      ||
| 86 |  *
 | 
      ||
| 87 |  * @var array
 | 
      ||
| 88 |  */
 | 
      ||
| 89 | public $columns = array(  | 
      ||
| 90 | 'primary_key' => array('name' => 'IDENTITY (1, 1) NOT NULL'),  | 
      ||
| 91 | 'string' => array('name' => 'nvarchar', 'limit' => '255'),  | 
      ||
| 92 | 'text' => array('name' => 'nvarchar', 'limit' => 'MAX'),  | 
      ||
| 93 | 'integer' => array('name' => 'int', 'formatter' => 'intval'),  | 
      ||
| 94 | 'biginteger' => array('name' => 'bigint'),  | 
      ||
| 95 | 'numeric' => array('name' => 'decimal', 'formatter' => 'floatval'),  | 
      ||
| 96 | 'decimal' => array('name' => 'decimal', 'formatter' => 'floatval'),  | 
      ||
| 97 | 'float' => array('name' => 'float', 'formatter' => 'floatval'),  | 
      ||
| 98 | 'real' => array('name' => 'float', 'formatter' => 'floatval'),  | 
      ||
| 99 | 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),  | 
      ||
| 100 | 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),  | 
      ||
| 101 | 'time' => array('name' => 'datetime', 'format' => 'H:i:s', 'formatter' => 'date'),  | 
      ||
| 102 | 'date' => array('name' => 'datetime', 'format' => 'Y-m-d', 'formatter' => 'date'),  | 
      ||
| 103 | 'binary' => array('name' => 'varbinary'),  | 
      ||
| 104 | 'boolean' => array('name' => 'bit')  | 
      ||
| 105 | );  | 
      ||
| 106 | |||
| 107 | /**
 | 
      ||
| 108 |  * Magic column name used to provide pagination support for SQLServer 2008
 | 
      ||
| 109 |  * which lacks proper limit/offset support.
 | 
      ||
| 110 |  *
 | 
      ||
| 111 |  * @var string
 | 
      ||
| 112 |  */
 | 
      ||
| 113 | const ROW_COUNTER = '_cake_page_rownum_';  | 
      ||
| 114 | |||
| 115 | /**
 | 
      ||
| 116 |  * Connects to the database using options in the given configuration array.
 | 
      ||
| 117 |  *
 | 
      ||
| 118 |  * @return bool True if the database could be connected, else false
 | 
      ||
| 119 |  * @throws MissingConnectionException
 | 
      ||
| 120 |  */
 | 
      ||
| 121 | public function connect() {  | 
      ||
| 122 | $config = $this->config;  | 
      ||
| 123 | $this->connected = false;  | 
      ||
| 124 | |||
| 125 | $flags = $config['flags'] + array(  | 
      ||
| 126 | PDO::ATTR_PERSISTENT => $config['persistent'],  | 
      ||
| 127 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION  | 
      ||
| 128 | );  | 
      ||
| 129 | |||
| 130 | if (!empty($config['encoding'])) {  | 
      ||
| 131 | $flags[PDO::SQLSRV_ATTR_ENCODING] = $config['encoding'];  | 
      ||
| 132 | }  | 
      ||
| 133 | |||
| 134 |                 try {
 | 
      ||
| 135 | $this->_connection = new PDO(  | 
      ||
| 136 |                                 "sqlsrv:server={$config['host']};Database={$config['database']}",
 | 
      ||
| 137 | $config['login'],  | 
      ||
| 138 | $config['password'],  | 
      ||
| 139 |                                 $flags
 | 
      ||
| 140 | );  | 
      ||
| 141 | $this->connected = true;  | 
      ||
| 142 | if (!empty($config['settings'])) {  | 
      ||
| 143 | foreach ($config['settings'] as $key => $value) {  | 
      ||
| 144 | $this->_execute("SET $key $value");  | 
      ||
| 145 | }  | 
      ||
| 146 | }  | 
      ||
| 147 | } catch (PDOException $e) {  | 
      ||
| 148 | throw new MissingConnectionException(array(  | 
      ||
| 149 | 'class' => get_class($this),  | 
      ||
| 150 | 'message' => $e->getMessage()  | 
      ||
| 151 | ));  | 
      ||
| 152 | }  | 
      ||
| 153 | |||
| 154 | return $this->connected;  | 
      ||
| 155 | }  | 
      ||
| 156 | |||
| 157 | /**
 | 
      ||
| 158 |  * Check that PDO SQL Server is installed/loaded
 | 
      ||
| 159 |  *
 | 
      ||
| 160 |  * @return bool
 | 
      ||
| 161 |  */
 | 
      ||
| 162 | public function enabled() {  | 
      ||
| 163 | return in_array('sqlsrv', PDO::getAvailableDrivers());  | 
      ||
| 164 | }  | 
      ||
| 165 | |||
| 166 | /**
 | 
      ||
| 167 |  * Returns an array of sources (tables) in the database.
 | 
      ||
| 168 |  *
 | 
      ||
| 169 |  * @param mixed $data The names
 | 
      ||
| 170 |  * @return array Array of table names in the database
 | 
      ||
| 171 |  */
 | 
      ||
| 172 | public function listSources($data = null) {  | 
      ||
| 173 | $cache = parent::listSources();  | 
      ||
| 174 | if ($cache !== null) {  | 
      ||
| 175 | return $cache;  | 
      ||
| 176 | }  | 
      ||
| 177 | $result = $this->_execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES");  | 
      ||
| 178 | |||
| 179 | if (!$result) {  | 
      ||
| 180 |                         $result->closeCursor();
 | 
      ||
| 181 | return array();  | 
      ||
| 182 | }  | 
      ||
| 183 | $tables = array();  | 
      ||
| 184 | |||
| 185 | while ($line = $result->fetch(PDO::FETCH_NUM)) {  | 
      ||
| 186 | $tables[] = $line[0];  | 
      ||
| 187 | }  | 
      ||
| 188 | |||
| 189 |                 $result->closeCursor();
 | 
      ||
| 190 | parent::listSources($tables);  | 
      ||
| 191 | return $tables;  | 
      ||
| 192 | }  | 
      ||
| 193 | |||
| 194 | /**
 | 
      ||
| 195 |  * Returns an array of the fields in given table name.
 | 
      ||
| 196 |  *
 | 
      ||
| 197 |  * @param Model|string $model Model object to describe, or a string table name.
 | 
      ||
| 198 |  * @return array Fields in table. Keys are name and type
 | 
      ||
| 199 |  * @throws CakeException
 | 
      ||
| 200 |  */
 | 
      ||
| 201 | public function describe($model) {  | 
      ||
| 202 | $table = $this->fullTableName($model, false, false);  | 
      ||
| 203 | $fulltable = $this->fullTableName($model, false, true);  | 
      ||
| 204 | |||
| 205 | $cache = parent::describe($fulltable);  | 
      ||
| 206 | if ($cache) {  | 
      ||
| 207 | return $cache;  | 
      ||
| 208 | }  | 
      ||
| 209 | |||
| 210 | $fields = array();  | 
      ||
| 211 | $schema = is_object($model) ? $model->schemaName : false;  | 
      ||
| 212 | |||
| 213 | $cols = $this->_execute(  | 
      ||
| 214 |                         "SELECT
 | 
      ||
| 215 |                                 COLUMN_NAME as Field,
 | 
      ||
| 216 |                                 DATA_TYPE as Type,
 | 
      ||
| 217 | COL_LENGTH('" . ($schema ? $fulltable : $table) . "', COLUMN_NAME) as Length,  | 
      ||
| 218 |                                 IS_NULLABLE As [Null],
 | 
      ||
| 219 |                                 COLUMN_DEFAULT as [Default],
 | 
      ||
| 220 | COLUMNPROPERTY(OBJECT_ID('" . ($schema ? $fulltable : $table) . "'), COLUMN_NAME, 'IsIdentity') as [Key],  | 
      ||
| 221 |                                 NUMERIC_SCALE as Size
 | 
      ||
| 222 |                         FROM INFORMATION_SCHEMA.COLUMNS
 | 
      ||
| 223 | WHERE TABLE_NAME = '" . $table . "'" . ($schema ? " AND TABLE_SCHEMA = '" . $schema . "'" : '')  | 
      ||
| 224 | );  | 
      ||
| 225 | |||
| 226 | if (!$cols) {  | 
      ||
| 227 | throw new CakeException(__d('cake_dev', 'Could not describe table for %s', $table));  | 
      ||
| 228 | }  | 
      ||
| 229 | |||
| 230 | while ($column = $cols->fetch(PDO::FETCH_OBJ)) {  | 
      ||
| 231 | $field = $column->Field;  | 
      ||
| 232 | $fields[$field] = array(  | 
      ||
| 233 | 'type' => $this->column($column),  | 
      ||
| 234 | 'null' => ($column->Null === 'YES' ? true : false),  | 
      ||
| 235 | 'default' => $column->Default,  | 
      ||
| 236 | 'length' => $this->length($column),  | 
      ||
| 237 | 'key' => ($column->Key == '1') ? 'primary' : false  | 
      ||
| 238 | );  | 
      ||
| 239 | |||
| 240 | if ($fields[$field]['default'] === 'null') {  | 
      ||
| 241 | $fields[$field]['default'] = null;  | 
      ||
| 242 | }  | 
      ||
| 243 | if ($fields[$field]['default'] !== null) {  | 
      ||
| 244 | $fields[$field]['default'] = preg_replace(  | 
      ||
| 245 |                                         "/^[(]{1,2}'?([^')]*)?'?[)]{1,2}$/",
 | 
      ||
| 246 |                                         "$1",
 | 
      ||
| 247 | $fields[$field]['default']  | 
      ||
| 248 | );  | 
      ||
| 249 | $this->value($fields[$field]['default'], $fields[$field]['type']);  | 
      ||
| 250 | }  | 
      ||
| 251 | |||
| 252 | if ($fields[$field]['key'] !== false && $fields[$field]['type'] === 'integer') {  | 
      ||
| 253 | $fields[$field]['length'] = 11;  | 
      ||
| 254 | } elseif ($fields[$field]['key'] === false) {  | 
      ||
| 255 | unset($fields[$field]['key']);  | 
      ||
| 256 | }  | 
      ||
| 257 | if (in_array($fields[$field]['type'], array('date', 'time', 'datetime', 'timestamp'))) {  | 
      ||
| 258 | $fields[$field]['length'] = null;  | 
      ||
| 259 | }  | 
      ||
| 260 | if ($fields[$field]['type'] === 'float' && !empty($column->Size)) {  | 
      ||
| 261 | $fields[$field]['length'] = $fields[$field]['length'] . ',' . $column->Size;  | 
      ||
| 262 | }  | 
      ||
| 263 | }  | 
      ||
| 264 | $this->_cacheDescription($table, $fields);  | 
      ||
| 265 |                 $cols->closeCursor();
 | 
      ||
| 266 | return $fields;  | 
      ||
| 267 | }  | 
      ||
| 268 | |||
| 269 | /**
 | 
      ||
| 270 |  * Generates the fields list of an SQL query.
 | 
      ||
| 271 |  *
 | 
      ||
| 272 |  * @param Model $model The model to get fields for.
 | 
      ||
| 273 |  * @param string $alias Alias table name
 | 
      ||
| 274 |  * @param array $fields The fields so far.
 | 
      ||
| 275 |  * @param bool $quote Whether or not to quote identfiers.
 | 
      ||
| 276 |  * @return array
 | 
      ||
| 277 |  */
 | 
      ||
| 278 | public function fields(Model $model, $alias = null, $fields = array(), $quote = true) {  | 
      ||
| 279 | if (empty($alias)) {  | 
      ||
| 280 | $alias = $model->alias;  | 
      ||
| 281 | }  | 
      ||
| 282 | $fields = parent::fields($model, $alias, $fields, false);  | 
      ||
| 283 | $count = count($fields);  | 
      ||
| 284 | |||
| 285 | if ($count >= 1 && strpos($fields[0], 'COUNT(*)') === false) {  | 
      ||
| 286 | $result = array();  | 
      ||
| 287 | for ($i = 0; $i < $count; $i++) {  | 
      ||
| 288 | $prepend = '';  | 
      ||
| 289 | |||
| 290 | if (strpos($fields[$i], 'DISTINCT') !== false && strpos($fields[$i], 'COUNT') === false) {  | 
      ||
| 291 | $prepend = 'DISTINCT ';  | 
      ||
| 292 | $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));  | 
      ||
| 293 | }  | 
      ||
| 294 | |||
| 295 | if (!preg_match('/\s+AS\s+/i', $fields[$i])) {  | 
      ||
| 296 | if (substr($fields[$i], -1) === '*') {  | 
      ||
| 297 | if (strpos($fields[$i], '.') !== false && $fields[$i] != $alias . '.*') {  | 
      ||
| 298 | $build = explode('.', $fields[$i]);  | 
      ||
| 299 | $AssociatedModel = $model->{$build[0]};  | 
      ||
| 300 |                                                 } else {
 | 
      ||
| 301 | $AssociatedModel = $model;  | 
      ||
| 302 | }  | 
      ||
| 303 | |||
| 304 | $_fields = $this->fields($AssociatedModel, $AssociatedModel->alias, array_keys($AssociatedModel->schema()));  | 
      ||
| 305 | $result = array_merge($result, $_fields);  | 
      ||
| 306 |                                                 continue;
 | 
      ||
| 307 | }  | 
      ||
| 308 | |||
| 309 | if (strpos($fields[$i], '.') === false) {  | 
      ||
| 310 | $this->_fieldMappings[$alias . '__' . $fields[$i]] = $alias . '.' . $fields[$i];  | 
      ||
| 311 | $fieldName = $this->name($alias . '.' . $fields[$i]);  | 
      ||
| 312 | $fieldAlias = $this->name($alias . '__' . $fields[$i]);  | 
      ||
| 313 |                                         } else {
 | 
      ||
| 314 | $build = explode('.', $fields[$i]);  | 
      ||
| 315 | $build[0] = trim($build[0], '[]');  | 
      ||
| 316 | $build[1] = trim($build[1], '[]');  | 
      ||
| 317 | $name = $build[0] . '.' . $build[1];  | 
      ||
| 318 | $alias = $build[0] . '__' . $build[1];  | 
      ||
| 319 | |||
| 320 | $this->_fieldMappings[$alias] = $name;  | 
      ||
| 321 | $fieldName = $this->name($name);  | 
      ||
| 322 | $fieldAlias = $this->name($alias);  | 
      ||
| 323 | }  | 
      ||
| 324 | if ($model->getColumnType($fields[$i]) === 'datetime') {  | 
      ||
| 325 | $fieldName = "CONVERT(VARCHAR(20), {$fieldName}, 20)";  | 
      ||
| 326 | }  | 
      ||
| 327 | $fields[$i] = "{$fieldName} AS {$fieldAlias}";  | 
      ||
| 328 | }  | 
      ||
| 329 | $result[] = $prepend . $fields[$i];  | 
      ||
| 330 | }  | 
      ||
| 331 | return $result;  | 
      ||
| 332 | }  | 
      ||
| 333 | return $fields;  | 
      ||
| 334 | }  | 
      ||
| 335 | |||
| 336 | /**
 | 
      ||
| 337 |  * Generates and executes an SQL INSERT statement for given model, fields, and values.
 | 
      ||
| 338 |  * Removes Identity (primary key) column from update data before returning to parent, if
 | 
      ||
| 339 |  * value is empty.
 | 
      ||
| 340 |  *
 | 
      ||
| 341 |  * @param Model $model The model to insert into.
 | 
      ||
| 342 |  * @param array $fields The fields to set.
 | 
      ||
| 343 |  * @param array $values The values to set.
 | 
      ||
| 344 |  * @return array
 | 
      ||
| 345 |  */
 | 
      ||
| 346 | public function create(Model $model, $fields = null, $values = null) {  | 
      ||
| 347 | if (!empty($values)) {  | 
      ||
| 348 | $fields = array_combine($fields, $values);  | 
      ||
| 349 | }  | 
      ||
| 350 | $primaryKey = $this->_getPrimaryKey($model);  | 
      ||
| 351 | |||
| 352 | if (array_key_exists($primaryKey, $fields)) {  | 
      ||
| 353 | if (empty($fields[$primaryKey])) {  | 
      ||
| 354 | unset($fields[$primaryKey]);  | 
      ||
| 355 |                         } else {
 | 
      ||
| 356 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' ON');  | 
      ||
| 357 | }  | 
      ||
| 358 | }  | 
      ||
| 359 | $result = parent::create($model, array_keys($fields), array_values($fields));  | 
      ||
| 360 | if (array_key_exists($primaryKey, $fields) && !empty($fields[$primaryKey])) {  | 
      ||
| 361 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' OFF');  | 
      ||
| 362 | }  | 
      ||
| 363 | return $result;  | 
      ||
| 364 | }  | 
      ||
| 365 | |||
| 366 | /**
 | 
      ||
| 367 |  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
 | 
      ||
| 368 |  * Removes Identity (primary key) column from update data before returning to parent.
 | 
      ||
| 369 |  *
 | 
      ||
| 370 |  * @param Model $model The model to update.
 | 
      ||
| 371 |  * @param array $fields The fields to set.
 | 
      ||
| 372 |  * @param array $values The values to set.
 | 
      ||
| 373 |  * @param mixed $conditions The conditions to use.
 | 
      ||
| 374 |  * @return array
 | 
      ||
| 375 |  */
 | 
      ||
| 376 | public function update(Model $model, $fields = array(), $values = null, $conditions = null) {  | 
      ||
| 377 | if (!empty($values)) {  | 
      ||
| 378 | $fields = array_combine($fields, $values);  | 
      ||
| 379 | }  | 
      ||
| 380 | if (isset($fields[$model->primaryKey])) {  | 
      ||
| 381 | unset($fields[$model->primaryKey]);  | 
      ||
| 382 | }  | 
      ||
| 383 | if (empty($fields)) {  | 
      ||
| 384 | return true;  | 
      ||
| 385 | }  | 
      ||
| 386 | return parent::update($model, array_keys($fields), array_values($fields), $conditions);  | 
      ||
| 387 | }  | 
      ||
| 388 | |||
| 389 | /**
 | 
      ||
| 390 |  * Returns a limit statement in the correct format for the particular database.
 | 
      ||
| 391 |  *
 | 
      ||
| 392 |  * @param int $limit Limit of results returned
 | 
      ||
| 393 |  * @param int $offset Offset from which to start results
 | 
      ||
| 394 |  * @return string SQL limit/offset statement
 | 
      ||
| 395 |  */
 | 
      ||
| 396 | public function limit($limit, $offset = null) {  | 
      ||
| 397 | if ($limit) {  | 
      ||
| 398 | $rt = '';  | 
      ||
| 399 | if (!strpos(strtolower($limit), 'top') || strpos(strtolower($limit), 'top') === 0) {  | 
      ||
| 400 | $rt = ' TOP';  | 
      ||
| 401 | }  | 
      ||
| 402 | $rt .= sprintf(' %u', $limit);  | 
      ||
| 403 | if (is_int($offset) && $offset > 0) {  | 
      ||
| 404 | $rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $offset, $limit);  | 
      ||
| 405 | }  | 
      ||
| 406 | return $rt;  | 
      ||
| 407 | }  | 
      ||
| 408 | return null;  | 
      ||
| 409 | }  | 
      ||
| 410 | |||
| 411 | /**
 | 
      ||
| 412 |  * Converts database-layer column types to basic types
 | 
      ||
| 413 |  *
 | 
      ||
| 414 |  * @param mixed $real Either the string value of the fields type.
 | 
      ||
| 415 |  *    or the Result object from Sqlserver::describe()
 | 
      ||
| 416 |  * @return string Abstract column type (i.e. "string")
 | 
      ||
| 417 |  */
 | 
      ||
| 418 | public function column($real) {  | 
      ||
| 419 | $limit = null;  | 
      ||
| 420 | $col = $real;  | 
      ||
| 421 | if (is_object($real) && isset($real->Field)) {  | 
      ||
| 422 | $limit = $real->Length;  | 
      ||
| 423 | $col = $real->Type;  | 
      ||
| 424 | }  | 
      ||
| 425 | |||
| 426 | if ($col === 'datetime2') {  | 
      ||
| 427 | return 'datetime';  | 
      ||
| 428 | }  | 
      ||
| 429 | if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {  | 
      ||
| 430 | return $col;  | 
      ||
| 431 | }  | 
      ||
| 432 | if ($col === 'bit') {  | 
      ||
| 433 | return 'boolean';  | 
      ||
| 434 | }  | 
      ||
| 435 | if (strpos($col, 'bigint') !== false) {  | 
      ||
| 436 | return 'biginteger';  | 
      ||
| 437 | }  | 
      ||
| 438 | if (strpos($col, 'int') !== false) {  | 
      ||
| 439 | return 'integer';  | 
      ||
| 440 | }  | 
      ||
| 441 | if (strpos($col, 'char') !== false && $limit == -1) {  | 
      ||
| 442 | return 'text';  | 
      ||
| 443 | }  | 
      ||
| 444 | if (strpos($col, 'char') !== false) {  | 
      ||
| 445 | return 'string';  | 
      ||
| 446 | }  | 
      ||
| 447 | if (strpos($col, 'text') !== false) {  | 
      ||
| 448 | return 'text';  | 
      ||
| 449 | }  | 
      ||
| 450 | if (strpos($col, 'binary') !== false || $col === 'image') {  | 
      ||
| 451 | return 'binary';  | 
      ||
| 452 | }  | 
      ||
| 453 | if (in_array($col, array('float', 'real'))) {  | 
      ||
| 454 | return 'float';  | 
      ||
| 455 | }  | 
      ||
| 456 | if (in_array($col, array('decimal', 'numeric'))) {  | 
      ||
| 457 | return 'decimal';  | 
      ||
| 458 | }  | 
      ||
| 459 | return 'text';  | 
      ||
| 460 | }  | 
      ||
| 461 | |||
| 462 | /**
 | 
      ||
| 463 |  * Handle SQLServer specific length properties.
 | 
      ||
| 464 |  * SQLServer handles text types as nvarchar/varchar with a length of -1.
 | 
      ||
| 465 |  *
 | 
      ||
| 466 |  * @param mixed $length Either the length as a string, or a Column descriptor object.
 | 
      ||
| 467 |  * @return mixed null|integer with length of column.
 | 
      ||
| 468 |  */
 | 
      ||
| 469 | public function length($length) {  | 
      ||
| 470 | if (is_object($length) && isset($length->Length)) {  | 
      ||
| 471 | if ($length->Length == -1 && strpos($length->Type, 'char') !== false) {  | 
      ||
| 472 | return null;  | 
      ||
| 473 | }  | 
      ||
| 474 | if (in_array($length->Type, array('nchar', 'nvarchar'))) {  | 
      ||
| 475 | return floor($length->Length / 2);  | 
      ||
| 476 | }  | 
      ||
| 477 | if ($length->Type === 'text') {  | 
      ||
| 478 | return null;  | 
      ||
| 479 | }  | 
      ||
| 480 | return $length->Length;  | 
      ||
| 481 | }  | 
      ||
| 482 | return parent::length($length);  | 
      ||
| 483 | }  | 
      ||
| 484 | |||
| 485 | /**
 | 
      ||
| 486 |  * Builds a map of the columns contained in a result
 | 
      ||
| 487 |  *
 | 
      ||
| 488 |  * @param PDOStatement $results The result to modify.
 | 
      ||
| 489 |  * @return void
 | 
      ||
| 490 |  */
 | 
      ||
| 491 | public function resultSet($results) {  | 
      ||
| 492 | $this->map = array();  | 
      ||
| 493 | $numFields = $results->columnCount();  | 
      ||
| 494 | $index = 0;  | 
      ||
| 495 | |||
| 496 | while ($numFields-- > 0) {  | 
      ||
| 497 | $column = $results->getColumnMeta($index);  | 
      ||
| 498 | $name = $column['name'];  | 
      ||
| 499 | |||
| 500 | if (strpos($name, '__')) {  | 
      ||
| 501 | if (isset($this->_fieldMappings[$name]) && strpos($this->_fieldMappings[$name], '.')) {  | 
      ||
| 502 | $map = explode('.', $this->_fieldMappings[$name]);  | 
      ||
| 503 | } elseif (isset($this->_fieldMappings[$name])) {  | 
      ||
| 504 | $map = array(0, $this->_fieldMappings[$name]);  | 
      ||
| 505 |                                 } else {
 | 
      ||
| 506 | $map = array(0, $name);  | 
      ||
| 507 | }  | 
      ||
| 508 |                         } else {
 | 
      ||
| 509 | $map = array(0, $name);  | 
      ||
| 510 | }  | 
      ||
| 511 | $map[] = ($column['sqlsrv:decl_type'] === 'bit') ? 'boolean' : $column['native_type'];  | 
      ||
| 512 | $this->map[$index++] = $map;  | 
      ||
| 513 | }  | 
      ||
| 514 | }  | 
      ||
| 515 | |||
| 516 | /**
 | 
      ||
| 517 |  * Builds final SQL statement
 | 
      ||
| 518 |  *
 | 
      ||
| 519 |  * @param string $type Query type
 | 
      ||
| 520 |  * @param array $data Query data
 | 
      ||
| 521 |  * @return string
 | 
      ||
| 522 |  */
 | 
      ||
| 523 | public function renderStatement($type, $data) {  | 
      ||
| 524 | switch (strtolower($type)) {  | 
      ||
| 525 | case 'select':  | 
      ||
| 526 | extract($data);  | 
      ||
| 527 | $fields = trim($fields);  | 
      ||
| 528 | |||
| 529 | if (strpos($limit, 'TOP') !== false && strpos($fields, 'DISTINCT ') === 0) {  | 
      ||
| 530 | $limit = 'DISTINCT ' . trim($limit);  | 
      ||
| 531 | $fields = substr($fields, 9);  | 
      ||
| 532 | }  | 
      ||
| 533 | |||
| 534 |                                 // hack order as SQLServer requires an order if there is a limit.
 | 
      ||
| 535 | if ($limit && !$order) {  | 
      ||
| 536 | $order = 'ORDER BY (SELECT NULL)';  | 
      ||
| 537 | }  | 
      ||
| 538 | |||
| 539 |                                 // For older versions use the subquery version of pagination.
 | 
      ||
| 540 | if (version_compare($this->getVersion(), '11', '<') && preg_match('/FETCH\sFIRST\s+([0-9]+)/i', $limit, $offset)) {  | 
      ||
| 541 | preg_match('/OFFSET\s*(\d+)\s*.*?(\d+)\s*ROWS/', $limit, $limitOffset);  | 
      ||
| 542 | |||
| 543 | $limit = 'TOP ' . (int)$limitOffset[2];  | 
      ||
| 544 | $page = (int)($limitOffset[1] / $limitOffset[2]);  | 
      ||
| 545 | $offset = (int)($limitOffset[2] * $page);  | 
      ||
| 546 | |||
| 547 | $rowCounter = static::ROW_COUNTER;  | 
      ||
| 548 | $sql = "SELECT {$limit} * FROM (  | 
      ||
| 549 |                                                         SELECT {$fields}, ROW_NUMBER() OVER ({$order}) AS {$rowCounter}
 | 
      ||
| 550 |                                                         FROM {$table} {$alias} {$joins} {$conditions} {$group}
 | 
      ||
| 551 |                                                 ) AS _cake_paging_
 | 
      ||
| 552 |                                                 WHERE _cake_paging_.{$rowCounter} > {$offset}
 | 
      ||
| 553 |                                                 ORDER BY _cake_paging_.{$rowCounter}
 | 
      ||
| 554 |                                         ";
 | 
      ||
| 555 | return trim($sql);  | 
      ||
| 556 | }  | 
      ||
| 557 | if (strpos($limit, 'FETCH') !== false) {  | 
      ||
| 558 | return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}");  | 
      ||
| 559 | }  | 
      ||
| 560 | return trim("SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}");  | 
      ||
| 561 | case "schema":  | 
      ||
| 562 | extract($data);  | 
      ||
| 563 | |||
| 564 | foreach ($indexes as $i => $index) {  | 
      ||
| 565 | if (preg_match('/PRIMARY KEY/', $index)) {  | 
      ||
| 566 | unset($indexes[$i]);  | 
      ||
| 567 |                                                 break;
 | 
      ||
| 568 | }  | 
      ||
| 569 | }  | 
      ||
| 570 | |||
| 571 | foreach (array('columns', 'indexes') as $var) {  | 
      ||
| 572 | if (is_array(${$var})) {  | 
      ||
| 573 |                                                 ${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
 | 
      ||
| 574 | }  | 
      ||
| 575 | }  | 
      ||
| 576 | return trim("CREATE TABLE {$table} (\n{$columns});\n{$indexes}");  | 
      ||
| 577 |                         default:
 | 
      ||
| 578 | return parent::renderStatement($type, $data);  | 
      ||
| 579 | }  | 
      ||
| 580 | }  | 
      ||
| 581 | |||
| 582 | /**
 | 
      ||
| 583 |  * Returns a quoted and escaped string of $data for use in an SQL statement.
 | 
      ||
| 584 |  *
 | 
      ||
| 585 |  * @param string $data String to be prepared for use in an SQL statement
 | 
      ||
| 586 |  * @param string $column The column into which this data will be inserted
 | 
      ||
| 587 |  * @param bool $null Column allows NULL values
 | 
      ||
| 588 |  * @return string Quoted and escaped data
 | 
      ||
| 589 |  */
 | 
      ||
| 590 | public function value($data, $column = null, $null = true) {  | 
      ||
| 591 | if ($data === null || is_array($data) || is_object($data)) {  | 
      ||
| 592 | return parent::value($data, $column, $null);  | 
      ||
| 593 | }  | 
      ||
| 594 | if (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {  | 
      ||
| 595 | return $data;  | 
      ||
| 596 | }  | 
      ||
| 597 | |||
| 598 | if (empty($column)) {  | 
      ||
| 599 | $column = $this->introspectType($data);  | 
      ||
| 600 | }  | 
      ||
| 601 | |||
| 602 | switch ($column) {  | 
      ||
| 603 | case 'string':  | 
      ||
| 604 | case 'text':  | 
      ||
| 605 | return 'N' . $this->_connection->quote($data, PDO::PARAM_STR);  | 
      ||
| 606 |                         default:
 | 
      ||
| 607 | return parent::value($data, $column, $null);  | 
      ||
| 608 | }  | 
      ||
| 609 | }  | 
      ||
| 610 | |||
| 611 | /**
 | 
      ||
| 612 |  * Returns an array of all result rows for a given SQL query.
 | 
      ||
| 613 |  * Returns false if no rows matched.
 | 
      ||
| 614 |  *
 | 
      ||
| 615 |  * @param Model $model The model to read from
 | 
      ||
| 616 |  * @param array $queryData The query data
 | 
      ||
| 617 |  * @param int $recursive How many layers to go.
 | 
      ||
| 618 |  * @return array|false Array of resultset rows, or false if no rows matched
 | 
      ||
| 619 |  */
 | 
      ||
| 620 | public function read(Model $model, $queryData = array(), $recursive = null) {  | 
      ||
| 621 | $results = parent::read($model, $queryData, $recursive);  | 
      ||
| 622 | $this->_fieldMappings = array();  | 
      ||
| 623 | return $results;  | 
      ||
| 624 | }  | 
      ||
| 625 | |||
| 626 | /**
 | 
      ||
| 627 |  * Fetches the next row from the current result set.
 | 
      ||
| 628 |  * Eats the magic ROW_COUNTER variable.
 | 
      ||
| 629 |  *
 | 
      ||
| 630 |  * @return mixed
 | 
      ||
| 631 |  */
 | 
      ||
| 632 | public function fetchResult() {  | 
      ||
| 633 | if ($row = $this->_result->fetch(PDO::FETCH_NUM)) {  | 
      ||
| 634 | $resultRow = array();  | 
      ||
| 635 | foreach ($this->map as $col => $meta) {  | 
      ||
| 636 | list($table, $column, $type) = $meta;  | 
      ||
| 637 | if ($table === 0 && $column === static::ROW_COUNTER) {  | 
      ||
| 638 |                                         continue;
 | 
      ||
| 639 | }  | 
      ||
| 640 | $resultRow[$table][$column] = $row[$col];  | 
      ||
| 641 | if ($type === 'boolean' && $row[$col] !== null) {  | 
      ||
| 642 | $resultRow[$table][$column] = $this->boolean($resultRow[$table][$column]);  | 
      ||
| 643 | }  | 
      ||
| 644 | }  | 
      ||
| 645 | return $resultRow;  | 
      ||
| 646 | }  | 
      ||
| 647 |                 $this->_result->closeCursor();
 | 
      ||
| 648 | return false;  | 
      ||
| 649 | }  | 
      ||
| 650 | |||
| 651 | /**
 | 
      ||
| 652 |  * Inserts multiple values into a table
 | 
      ||
| 653 |  *
 | 
      ||
| 654 |  * @param string $table The table to insert into.
 | 
      ||
| 655 |  * @param string $fields The fields to set.
 | 
      ||
| 656 |  * @param array $values The values to set.
 | 
      ||
| 657 |  * @return void
 | 
      ||
| 658 |  */
 | 
      ||
| 659 | public function insertMulti($table, $fields, $values) {  | 
      ||
| 660 | $primaryKey = $this->_getPrimaryKey($table);  | 
      ||
| 661 | $hasPrimaryKey = $primaryKey && (  | 
      ||
| 662 | (is_array($fields) && in_array($primaryKey, $fields)  | 
      ||
| 663 | || (is_string($fields) && strpos($fields, $this->startQuote . $primaryKey . $this->endQuote) !== false))  | 
      ||
| 664 | );  | 
      ||
| 665 | |||
| 666 | if ($hasPrimaryKey) {  | 
      ||
| 667 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' ON');  | 
      ||
| 668 | }  | 
      ||
| 669 | |||
| 670 | parent::insertMulti($table, $fields, $values);  | 
      ||
| 671 | |||
| 672 | if ($hasPrimaryKey) {  | 
      ||
| 673 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' OFF');  | 
      ||
| 674 | }  | 
      ||
| 675 | }  | 
      ||
| 676 | |||
| 677 | /**
 | 
      ||
| 678 |  * Generate a database-native column schema string
 | 
      ||
| 679 |  *
 | 
      ||
| 680 |  * @param array $column An array structured like the
 | 
      ||
| 681 |  *   following: array('name'=>'value', 'type'=>'value'[, options]),
 | 
      ||
| 682 |  *   where options can be 'default', 'length', or 'key'.
 | 
      ||
| 683 |  * @return string
 | 
      ||
| 684 |  */
 | 
      ||
| 685 | public function buildColumn($column) {  | 
      ||
| 686 | $result = parent::buildColumn($column);  | 
      ||
| 687 | $result = preg_replace('/(bigint|int|integer)\([0-9]+\)/i', '$1', $result);  | 
      ||
| 688 | $result = preg_replace('/(bit)\([0-9]+\)/i', '$1', $result);  | 
      ||
| 689 | if (strpos($result, 'DEFAULT NULL') !== false) {  | 
      ||
| 690 | if (isset($column['default']) && $column['default'] === '') {  | 
      ||
| 691 | $result = str_replace('DEFAULT NULL', "DEFAULT ''", $result);  | 
      ||
| 692 |                         } else {
 | 
      ||
| 693 | $result = str_replace('DEFAULT NULL', 'NULL', $result);  | 
      ||
| 694 | }  | 
      ||
| 695 | } elseif (array_keys($column) === array('type', 'name')) {  | 
      ||
| 696 | $result .= ' NULL';  | 
      ||
| 697 | } elseif (strpos($result, "DEFAULT N'")) {  | 
      ||
| 698 | $result = str_replace("DEFAULT N'", "DEFAULT '", $result);  | 
      ||
| 699 | }  | 
      ||
| 700 | return $result;  | 
      ||
| 701 | }  | 
      ||
| 702 | |||
| 703 | /**
 | 
      ||
| 704 |  * Format indexes for create table
 | 
      ||
| 705 |  *
 | 
      ||
| 706 |  * @param array $indexes The indexes to build
 | 
      ||
| 707 |  * @param string $table The table to make indexes for.
 | 
      ||
| 708 |  * @return string
 | 
      ||
| 709 |  */
 | 
      ||
| 710 | public function buildIndex($indexes, $table = null) {  | 
      ||
| 711 | $join = array();  | 
      ||
| 712 | |||
| 713 | foreach ($indexes as $name => $value) {  | 
      ||
| 714 | if ($name === 'PRIMARY') {  | 
      ||
| 715 | $join[] = 'PRIMARY KEY (' . $this->name($value['column']) . ')';  | 
      ||
| 716 | } elseif (isset($value['unique']) && $value['unique']) {  | 
      ||
| 717 | $out = "ALTER TABLE {$table} ADD CONSTRAINT {$name} UNIQUE";  | 
      ||
| 718 | |||
| 719 | if (is_array($value['column'])) {  | 
      ||
| 720 | $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column']));  | 
      ||
| 721 |                                 } else {
 | 
      ||
| 722 | $value['column'] = $this->name($value['column']);  | 
      ||
| 723 | }  | 
      ||
| 724 | $out .= "({$value['column']});";  | 
      ||
| 725 | $join[] = $out;  | 
      ||
| 726 | }  | 
      ||
| 727 | }  | 
      ||
| 728 | return $join;  | 
      ||
| 729 | }  | 
      ||
| 730 | |||
| 731 | /**
 | 
      ||
| 732 |  * Makes sure it will return the primary key
 | 
      ||
| 733 |  *
 | 
      ||
| 734 |  * @param Model|string $model Model instance of table name
 | 
      ||
| 735 |  * @return string
 | 
      ||
| 736 |  */
 | 
      ||
| 737 | protected function _getPrimaryKey($model) {  | 
      ||
| 738 | $schema = $this->describe($model);  | 
      ||
| 739 | foreach ($schema as $field => $props) {  | 
      ||
| 740 | if (isset($props['key']) && $props['key'] === 'primary') {  | 
      ||
| 741 | return $field;  | 
      ||
| 742 | }  | 
      ||
| 743 | }  | 
      ||
| 744 | return null;  | 
      ||
| 745 | }  | 
      ||
| 746 | |||
| 747 | /**
 | 
      ||
| 748 |  * Returns number of affected rows in previous database operation. If no previous operation exists,
 | 
      ||
| 749 |  * this returns false.
 | 
      ||
| 750 |  *
 | 
      ||
| 751 |  * @param mixed $source Unused
 | 
      ||
| 752 |  * @return int Number of affected rows
 | 
      ||
| 753 |  */
 | 
      ||
| 754 | public function lastAffected($source = null) {  | 
      ||
| 755 | $affected = parent::lastAffected();  | 
      ||
| 756 | if ($affected === null && $this->_lastAffected !== false) {  | 
      ||
| 757 | return $this->_lastAffected;  | 
      ||
| 758 | }  | 
      ||
| 759 | return $affected;  | 
      ||
| 760 | }  | 
      ||
| 761 | |||
| 762 | /**
 | 
      ||
| 763 |  * Executes given SQL statement.
 | 
      ||
| 764 |  *
 | 
      ||
| 765 |  * @param string $sql SQL statement
 | 
      ||
| 766 |  * @param array $params list of params to be bound to query (supported only in select)
 | 
      ||
| 767 |  * @param array $prepareOptions Options to be used in the prepare statement
 | 
      ||
| 768 |  * @return mixed PDOStatement if query executes with no problem, true as the result of a successful, false on error
 | 
      ||
| 769 |  * query returning no rows, such as a CREATE statement, false otherwise
 | 
      ||
| 770 |  * @throws PDOException
 | 
      ||
| 771 |  */
 | 
      ||
| 772 | protected function _execute($sql, $params = array(), $prepareOptions = array()) {  | 
      ||
| 773 | $this->_lastAffected = false;  | 
      ||
| 774 | $sql = trim($sql);  | 
      ||
| 775 | if (strncasecmp($sql, 'SELECT', 6) === 0 || preg_match('/^EXEC(?:UTE)?\s/mi', $sql) > 0) {  | 
      ||
| 776 | $prepareOptions += array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL);  | 
      ||
| 777 | return parent::_execute($sql, $params, $prepareOptions);  | 
      ||
| 778 | }  | 
      ||
| 779 |                 try {
 | 
      ||
| 780 | $this->_lastAffected = $this->_connection->exec($sql);  | 
      ||
| 781 | if ($this->_lastAffected === false) {  | 
      ||
| 782 | $this->_results = null;  | 
      ||
| 783 | $error = $this->_connection->errorInfo();  | 
      ||
| 784 | $this->error = $error[2];  | 
      ||
| 785 | return false;  | 
      ||
| 786 | }  | 
      ||
| 787 | return true;  | 
      ||
| 788 | } catch (PDOException $e) {  | 
      ||
| 789 | if (isset($query->queryString)) {  | 
      ||
| 790 | $e->queryString = $query->queryString;  | 
      ||
| 791 |                         } else {
 | 
      ||
| 792 | $e->queryString = $sql;  | 
      ||
| 793 | }  | 
      ||
| 794 | throw $e;  | 
      ||
| 795 | }  | 
      ||
| 796 | }  | 
      ||
| 797 | |||
| 798 | /**
 | 
      ||
| 799 |  * Generate a "drop table" statement for the given table
 | 
      ||
| 800 |  *
 | 
      ||
| 801 |  * @param type $table Name of the table to drop
 | 
      ||
| 802 |  * @return string Drop table SQL statement
 | 
      ||
| 803 |  */
 | 
      ||
| 804 | protected function _dropTable($table) {  | 
      ||
| 805 | return "IF OBJECT_ID('" . $this->fullTableName($table, false) . "', 'U') IS NOT NULL DROP TABLE " . $this->fullTableName($table) . ";";  | 
      ||
| 806 | }  | 
      ||
| 807 | |||
| 808 | /**
 | 
      ||
| 809 |  * Gets the schema name
 | 
      ||
| 810 |  *
 | 
      ||
| 811 |  * @return string The schema name
 | 
      ||
| 812 |  */
 | 
      ||
| 813 | public function getSchemaName() {  | 
      ||
| 814 | return $this->config['schema'];  | 
      ||
| 815 | }  | 
      ||
| 816 | |||
| 817 | }  |