pictcode / lib / Cake / Model / Datasource / Database / Sqlserver.php @ 0b1b8047
履歴 | 表示 | アノテート | ダウンロード (23.596 KB)
| 1 | <?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 | } |