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