├── .gitignore ├── contributors.txt ├── composer.json ├── LICENSE ├── README.md └── Model └── Datasource └── Sybase.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | Chris Pierce | @chrislpierce 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpierce/cakephp-sybasedb", 3 | "description": "CakePHP plugin to handle PDO MSSQL Connections using dblib", 4 | "type": "cakephp-plugin", 5 | "keywords": [ 6 | "cakephp", 7 | "MSSQL", 8 | "Sybase", 9 | "PDO", 10 | "dblib" 11 | ], 12 | "homepage": "http://github.com/cpierce/microsoft-sql-pdo-for-cakephp-2.x", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Chris Pierce", 17 | "email": "cpierce@csdurant.com", 18 | "homepage": "http://www.cpierce.org", 19 | "role": "Maintainer" 20 | } 21 | ], 22 | "require": { 23 | "composer/installers": "*", 24 | "php": ">=5.6" 25 | }, 26 | "support": { 27 | "email": "cpierce@csdurant.com", 28 | "issues": "https://github.com/cpierce/microsoft-sql-pdo-for-cakephp-2.x/issues", 29 | "source": "https://github.com/cpierce/microsoft-sql-pdo-for-cakephp-2.x" 30 | }, 31 | "extra": { 32 | "installer-name": "SybaseDB" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Pierce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SybaseDB 1.0 # 2 | 3 | Creates a PDO connection to MSSql server through Sybase dblib as a plugin for CakePHP 2.x 4 | 5 | ### Background ### 6 | 7 | To many people on the Internet were looking for a simple way to connect to a MSSQL db from a linux installed cakePHP app. I was one 8 | of them so after hours of research the birth of this plugin was planned. A few hours later it was completed. 9 | 10 | ### Prerequisites? ### 11 | 12 | * php5-sybase package installed on your linux system (apt-get install php5-sybase) 13 | * enable php Sybase module in php.ini file (extension=pdo_dblib.so) 14 | * CakePHP 2.x app installed and baked on your server 15 | 16 | ### How to use the plugin ### 17 | 18 | Update your `composer.json` file using the following: 19 | 20 | ``` 21 | composer require cpierce/cakephp-sybasedb 22 | ``` 23 | 24 | Enable the Plugin in your `app/Config/bootstrap.php` file: 25 | 26 | 'SybaseDB.Sybase', 39 | 'host' => 'someplace\SQLEXPRESS', 40 | 'login' => 'somelogin', 41 | 'password' => 'Ur_P4ssw0rd#', 42 | 'database' => 'some_database', 43 | ); 44 | 45 | } 46 | 47 | 48 | You could also use another connection instead of `$default` and then specifying `$useDbConfig` in your model. 49 | 50 | From here you can now access the data the normal cakePHP way: 51 | 52 | Model->find('first'); 54 | 55 | 56 | 57 | ### Credit where it is due ### 58 | 59 | Some of the code used was taken from the CakePHP 1.x SQLServer DBO for Microsoft Windows and altered to fit the PDO DBlib. Special thanks to the people who have used this and helped me test it (Especially Jeffery Bell and Marcel Wedel) 60 | 61 | #### Change Log #### 62 | Version 1.0 63 | - Updated for better and easier use of composer. 64 | 65 | Version 0.3b 66 | * Fixed a problem with long table cell names (over 30 characters). 67 | 68 | Version 0.2b 69 | * New mapping protocol thanks to version of SQL working in system now 70 | * Tested (save, delete, find('count', 'list', 'all', 'first', 'contain'), paginate, hasMany, belongsTo, etc. 71 | * Must specify $primaryKey for non "id" primary keys 72 | -------------------------------------------------------------------------------- /Model/Datasource/Sybase.php: -------------------------------------------------------------------------------- 1 | '', 74 | 'login' => '', 75 | 'password' => '', 76 | 'database' => 'cake', 77 | 'schema' => '', 78 | ); 79 | 80 | /** 81 | * MS SQL column definition 82 | * 83 | * @var array 84 | */ 85 | public $columns = array( 86 | 'primary_key' => array('name' => 'IDENTITY (1, 1) NOT NULL'), 87 | 'string' => array('name' => 'nvarchar', 'limit' => '255'), 88 | 'text' => array('name' => 'nvarchar', 'limit' => 'MAX'), 89 | 'integer' => array('name' => 'int', 'formatter' => 'intval'), 90 | 'biginteger' => array('name' => 'bigint'), 91 | 'numeric' => array('name' => 'decimal', 'formatter' => 'floatval'), 92 | 'decimal' => array('name' => 'decimal', 'formatter' => 'floatval'), 93 | 'float' => array('name' => 'float', 'formatter' => 'floatval'), 94 | 'real' => array('name' => 'float', 'formatter' => 'floatval'), 95 | 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), 96 | 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), 97 | 'time' => array('name' => 'datetime', 'format' => 'H:i:s', 'formatter' => 'date'), 98 | 'date' => array('name' => 'datetime', 'format' => 'Y-m-d', 'formatter' => 'date'), 99 | 'binary' => array('name' => 'varbinary'), 100 | 'boolean' => array('name' => 'bit') 101 | ); 102 | 103 | /** 104 | * Magic column name used to provide pagination support for SQLServer 2008 105 | * which lacks proper limit/offset support. 106 | * 107 | * @var string 108 | */ 109 | const ROW_COUNTER = '_cake_page_rownum_'; 110 | 111 | /** 112 | * Connects to the database using options in the given configuration array. 113 | * 114 | * @return bool True if the database could be connected, else false 115 | * 116 | * @throws MissingConnectionException 117 | */ 118 | public function connect() { 119 | $config = $this->config; 120 | $this->connected = false; 121 | 122 | try { 123 | $this->_connection = new PDO( 124 | "dblib:host={$config['host']};dbname={$config['database']}", 125 | $config['login'], 126 | $config['password'] 127 | ); 128 | $this->connected = true; 129 | if (!empty($config['settings'])) { 130 | foreach ($config['settings'] as $key => $value) { 131 | $this->_execute("SET $key $value"); 132 | } 133 | } 134 | $this->setVersion(); 135 | } catch (PDOException $e) { 136 | throw new MissingConnectionException(array( 137 | 'class' => get_class($this), 138 | 'message' => $e->getMessage() 139 | )); 140 | } 141 | return $this->connected; 142 | } 143 | 144 | /** 145 | * Check that PDO SQL Server is installed/loaded 146 | * 147 | * @param void 148 | * 149 | * @return bool 150 | */ 151 | public function enabled() { 152 | return in_array('dblib', PDO::getAvailableDrivers()); 153 | } 154 | 155 | /** 156 | * Returns an array of sources (tables) in the database. 157 | * 158 | * @param mixed $data The names 159 | * 160 | * @return array Array of table names in the database 161 | */ 162 | public function listSources($data = null) { 163 | $cache = parent::listSources(); 164 | if ($cache !== null) { 165 | return $cache; 166 | } 167 | $result = $this->_execute('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES'); 168 | 169 | if (!$result) { 170 | $result->closeCursor(); 171 | return array(); 172 | } 173 | $tables = array(); 174 | 175 | while ($line = $result->fetch(PDO::FETCH_NUM)) { 176 | $tables[] = $line[0]; 177 | } 178 | 179 | $result->closeCursor(); 180 | parent::listSources($tables); 181 | return $tables; 182 | } 183 | 184 | /** 185 | * Returns an array of the fields in given table name. 186 | * 187 | * @param Model|string $model Model object to describe, or a string table name. 188 | * 189 | * @return array Fields in table. Keys are name and type 190 | * 191 | * @throws CakeException 192 | */ 193 | public function describe($model) { 194 | $table = $this->fullTableName($model, false, false); 195 | $fulltable = $this->fullTableName($model, false, true); 196 | $cache = parent::describe($fulltable); 197 | if ($cache) { 198 | return $cache; 199 | } 200 | 201 | $fields = array(); 202 | $schema = is_object($model) ? $model->schemaName : false; 203 | $cols = $this->_execute( 204 | "SELECT 205 | COLUMN_NAME as Field, 206 | DATA_TYPE as Type, 207 | COL_LENGTH('" . ($schema ? $fulltable : $table) . "', COLUMN_NAME) as Length, 208 | IS_NULLABLE As [Null], 209 | COLUMN_DEFAULT as [Default], 210 | COLUMNPROPERTY(OBJECT_ID('" . ($schema ? $fulltable : $table) . "'), COLUMN_NAME, 'IsIdentity') as [Key], 211 | NUMERIC_SCALE as Size 212 | FROM INFORMATION_SCHEMA.COLUMNS 213 | WHERE TABLE_NAME = '" . $table . "'" . ($schema ? " AND TABLE_SCHEMA = '" . $schema . "'" : '') 214 | ); 215 | 216 | if (!$cols) { 217 | throw new CakeException(__d('cake_dev', 'Could not describe table for %s', $table)); 218 | } 219 | 220 | while ($column = $cols->fetch(PDO::FETCH_OBJ)) { 221 | $field = $column->Field; 222 | $fields[$field] = array( 223 | 'type' => $this->column($column), 224 | 'null' => ($column->Null === 'YES' ? true : false), 225 | 'default' => $column->Default, 226 | 'length' => $this->length($column), 227 | 'key' => ($column->Key == '1') ? 'primary' : false 228 | ); 229 | 230 | if ($fields[$field]['default'] === 'null') { 231 | $fields[$field]['default'] = null; 232 | } 233 | if ($fields[$field]['default'] !== null) { 234 | $fields[$field]['default'] = preg_replace( 235 | "/^[(]{1,2}'?([^')]*)?'?[)]{1,2}$/", 236 | "$1", 237 | $fields[$field]['default'] 238 | ); 239 | $this->value($fields[$field]['default'], $fields[$field]['type']); 240 | } 241 | 242 | if ($fields[$field]['key'] !== false && $fields[$field]['type'] === 'integer') { 243 | $fields[$field]['length'] = 11; 244 | } elseif ($fields[$field]['key'] === false) { 245 | unset($fields[$field]['key']); 246 | } 247 | if (in_array($fields[$field]['type'], array('date', 'time', 'datetime', 'timestamp'))) { 248 | $fields[$field]['length'] = null; 249 | } 250 | if ($fields[$field]['type'] === 'float' && !empty($column->Size)) { 251 | $fields[$field]['length'] = $fields[$field]['length'] . ',' . $column->Size; 252 | } 253 | } 254 | $this->_cacheDescription($table, $fields); 255 | $cols->closeCursor(); 256 | return $fields; 257 | } 258 | 259 | /** 260 | * Generates the fields list of an SQL query. 261 | * 262 | * @param Model $model The model to get fields for. 263 | * @param string $alias Alias table name 264 | * @param array $fields The fields so far. 265 | * @param bool $quote Whether or not to quote identfiers. 266 | * 267 | * @return array 268 | */ 269 | public function fields(Model $model, $alias = null, $fields = array(), $quote = true) { 270 | if (empty($alias)) { 271 | $alias = $model->alias; 272 | } 273 | $fields = parent::fields($model, $alias, $fields, false); 274 | $count = count($fields); 275 | 276 | if ($count >= 1 && strpos($fields[0], 'COUNT(*)') === false) { 277 | $result = array(); 278 | for ($i = 0; $i < $count; $i++) { 279 | $prepend = ''; 280 | 281 | if (strpos($fields[$i], 'DISTINCT') !== false && strpos($fields[$i], 'COUNT') === false) { 282 | $prepend = 'DISTINCT '; 283 | $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); 284 | } 285 | 286 | if (!preg_match('/\s+AS\s+/i', $fields[$i])) { 287 | if (substr($fields[$i], -1) === '*') { 288 | if (strpos($fields[$i], '.') !== false && $fields[$i] != $alias . '.*') { 289 | $build = explode('.', $fields[$i]); 290 | $AssociatedModel = $model->{$build[0]}; 291 | } else { 292 | $AssociatedModel = $model; 293 | } 294 | 295 | $_fields = $this->fields($AssociatedModel, $AssociatedModel->alias, array_keys($AssociatedModel->schema())); 296 | $result = array_merge($result, $_fields); 297 | continue; 298 | } 299 | 300 | if (strpos($fields[$i], '.') === false) { 301 | $this->_fieldMappings[$alias . '__' . $fields[$i]] = $alias . '.' . $fields[$i]; 302 | $fieldName = $this->name($alias . '.' . $fields[$i]); 303 | $fieldAlias = $this->name($alias . '__' . $fields[$i]); 304 | } else { 305 | $build = explode('.', $fields[$i]); 306 | $build[0] = trim($build[0], '[]'); 307 | $build[1] = trim($build[1], '[]'); 308 | $name = $build[0] . '.' . $build[1]; 309 | $alias = $build[0] . '__' . $build[1]; 310 | 311 | $this->_fieldMappings[$alias] = $name; 312 | $fieldName = $this->name($name); 313 | $fieldAlias = $this->name($alias); 314 | } 315 | if ($model->getColumnType($fields[$i]) === 'datetime') { 316 | $fieldName = "CONVERT(VARCHAR(20), {$fieldName}, 20)"; 317 | } 318 | $fields[$i] = "{$fieldName} AS {$fieldAlias}"; 319 | } 320 | $result[] = $prepend . $fields[$i]; 321 | } 322 | return $result; 323 | } 324 | return $fields; 325 | } 326 | 327 | /** 328 | * Generates and executes an SQL INSERT statement for given model, fields, and values. 329 | * Removes Identity (primary key) column from update data before returning to parent, if 330 | * value is empty. 331 | * 332 | * @param Model $model The model to insert into. 333 | * @param array $fields The fields to set. 334 | * @param array $values The values to set. 335 | * 336 | * @return array 337 | */ 338 | public function create(Model $model, $fields = null, $values = null) { 339 | if (!empty($values)) { 340 | $fields = array_combine($fields, $values); 341 | } 342 | $primaryKey = $this->_getPrimaryKey($model); 343 | 344 | if (array_key_exists($primaryKey, $fields)) { 345 | if (empty($fields[$primaryKey])) { 346 | unset($fields[$primaryKey]); 347 | } else { 348 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' ON'); 349 | } 350 | } 351 | $result = parent::create($model, array_keys($fields), array_values($fields)); 352 | if (array_key_exists($primaryKey, $fields) && !empty($fields[$primaryKey])) { 353 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' OFF'); 354 | } 355 | return $result; 356 | } 357 | 358 | /** 359 | * Generates and executes an SQL UPDATE statement for given model, fields, and values. 360 | * Removes Identity (primary key) column from update data before returning to parent. 361 | * 362 | * @param Model $model The model to update. 363 | * @param array $fields The fields to set. 364 | * @param array $values The values to set. 365 | * @param mixed $conditions The conditions to use. 366 | * 367 | * @return array 368 | */ 369 | public function update(Model $model, $fields = array(), $values = null, $conditions = null) { 370 | if (!empty($values)) { 371 | $fields = array_combine($fields, $values); 372 | } 373 | if (isset($fields[$model->primaryKey])) { 374 | unset($fields[$model->primaryKey]); 375 | } 376 | if (empty($fields)) { 377 | return true; 378 | } 379 | return parent::update($model, array_keys($fields), array_values($fields), $conditions); 380 | } 381 | 382 | /** 383 | * Returns a limit statement in the correct format for the particular database. 384 | * 385 | * @param int $limit Limit of results returned 386 | * @param int $offset Offset from which to start results 387 | * 388 | * @return string SQL limit/offset statement 389 | */ 390 | public function limit($limit, $offset = null) { 391 | if ($limit) { 392 | $rt = ''; 393 | if (!strpos(strtolower($limit), 'top') || strpos(strtolower($limit), 'top') === 0) { 394 | $rt = ' TOP'; 395 | } 396 | $rt .= sprintf(' %u', $limit); 397 | if (is_int($offset) && $offset > 0) { 398 | $rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $offset, $limit); 399 | } 400 | return $rt; 401 | } 402 | return null; 403 | } 404 | 405 | /** 406 | * Converts database-layer column types to basic types 407 | * 408 | * @param mixed $real Either the string value of the fields type. 409 | * or the Result object from Sqlserver::describe() 410 | * 411 | * @return string Abstract column type (i.e. "string") 412 | */ 413 | public function column($real) { 414 | $limit = null; 415 | $col = $real; 416 | if (is_object($real) && isset($real->Field)) { 417 | $limit = $real->Length; 418 | $col = $real->Type; 419 | } 420 | 421 | if ($col === 'datetime2') { 422 | return 'datetime'; 423 | } 424 | if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) { 425 | return $col; 426 | } 427 | if ($col === 'bit') { 428 | return 'boolean'; 429 | } 430 | if (strpos($col, 'bigint') !== false) { 431 | return 'biginteger'; 432 | } 433 | if (strpos($col, 'int') !== false) { 434 | return 'integer'; 435 | } 436 | if (strpos($col, 'char') !== false && $limit == -1) { 437 | return 'text'; 438 | } 439 | if (strpos($col, 'char') !== false) { 440 | return 'string'; 441 | } 442 | if (strpos($col, 'text') !== false) { 443 | return 'text'; 444 | } 445 | if (strpos($col, 'binary') !== false || $col === 'image') { 446 | return 'binary'; 447 | } 448 | if (in_array($col, array('float', 'real'))) { 449 | return 'float'; 450 | } 451 | if (in_array($col, array('decimal', 'numeric'))) { 452 | return 'decimal'; 453 | } 454 | return 'text'; 455 | } 456 | 457 | /** 458 | * Handle SQLServer specific length properties. 459 | * 460 | * SQLServer handles text types as nvarchar/varchar with a length of -1. 461 | * 462 | * @param mixed $length Either the length as a string, or a Column descriptor object. 463 | * 464 | * @return mixed null|integer with length of column. 465 | */ 466 | public function length($length) { 467 | if (is_object($length) && isset($length->Length)) { 468 | if ($length->Length == -1 && strpos($length->Type, 'char') !== false) { 469 | return null; 470 | } 471 | if (in_array($length->Type, array('nchar', 'nvarchar'))) { 472 | return floor($length->Length / 2); 473 | } 474 | return $length->Length; 475 | } 476 | return parent::length($length); 477 | } 478 | 479 | /** 480 | * Builds a map of the columns contained in a result 481 | * 482 | * @param PDOStatement $results The result to modify. 483 | * 484 | * @return void 485 | */ 486 | public function resultSet($results) { 487 | $this->map = array(); 488 | $numFields = $results->columnCount(); 489 | $index = 0; 490 | 491 | while ($numFields-- > 0) { 492 | $column = $results->getColumnMeta($index); 493 | $name = $column['name']; 494 | 495 | if (strpos($name, '__')) { 496 | if (isset($this->_fieldMappings[$name]) && strpos($this->_fieldMappings[$name], '.')) { 497 | $map = explode('.', $this->_fieldMappings[$name]); 498 | } elseif (isset($this->_fieldMappings[$name])) { 499 | $map = array(0, $this->_fieldMappings[$name]); 500 | } else { 501 | $map = array(0, $name); 502 | } 503 | } else { 504 | $map = array(0, $name); 505 | } 506 | $map[] = $column['native_type']; 507 | $this->map[$index++] = $map; 508 | } 509 | } 510 | 511 | /** 512 | * Builds final SQL statement 513 | * 514 | * @param string $type Query type 515 | * @param array $data Query data 516 | * 517 | * @return string 518 | */ 519 | public function renderStatement($type, $data) { 520 | switch (strtolower($type)) { 521 | case 'select': 522 | extract($data); 523 | $fields = trim($fields); 524 | 525 | if (strpos($limit, 'TOP') !== false && strpos($fields, 'DISTINCT ') === 0) { 526 | $limit = 'DISTINCT ' . trim($limit); 527 | $fields = substr($fields, 9); 528 | } 529 | 530 | // hack order as SQLServer requires an order if there is a limit. 531 | if ($limit && !$order) { 532 | $order = 'ORDER BY (SELECT NULL)'; 533 | } 534 | 535 | // For older versions use the subquery version of pagination. 536 | if (version_compare($this->getVersion(), '11', '<') && preg_match('/FETCH\sFIRST\s+([0-9]+)/i', $limit, $offset)) { 537 | preg_match('/OFFSET\s*(\d+)\s*.*?(\d+)\s*ROWS/', $limit, $limitOffset); 538 | 539 | $limit = 'TOP ' . (int)$limitOffset[2]; 540 | $page = (int)($limitOffset[1] / $limitOffset[2]); 541 | $offset = (int)($limitOffset[2] * $page); 542 | 543 | $rowCounter = self::ROW_COUNTER; 544 | $sql = "SELECT {$limit} * FROM ( 545 | SELECT {$fields}, ROW_NUMBER() OVER ({$order}) AS {$rowCounter} 546 | FROM {$table} {$alias} {$joins} {$conditions} {$group} 547 | ) AS _cake_paging_ 548 | WHERE _cake_paging_.{$rowCounter} > {$offset} 549 | ORDER BY _cake_paging_.{$rowCounter} 550 | "; 551 | return trim($sql); 552 | } 553 | if (strpos($limit, 'FETCH') !== false) { 554 | return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}"); 555 | } 556 | return trim("SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}"); 557 | case 'schema': 558 | extract($data); 559 | 560 | foreach ($indexes as $i => $index) { 561 | if (preg_match('/PRIMARY KEY/', $index)) { 562 | unset($indexes[$i]); 563 | break; 564 | } 565 | } 566 | 567 | foreach (array('columns', 'indexes') as $var) { 568 | if (is_array(${$var})) { 569 | ${$var} = "\t" . implode(",\n\t", array_filter(${$var})); 570 | } 571 | } 572 | return trim("CREATE TABLE {$table} (\n{$columns});\n{$indexes}"); 573 | default: 574 | return parent::renderStatement($type, $data); 575 | } 576 | } 577 | 578 | /** 579 | * Returns a quoted and escaped string of $data for use in an SQL statement. 580 | * 581 | * @param string $data String to be prepared for use in an SQL statement 582 | * @param string $column The column into which this data will be inserted 583 | * @param bool $null Column allows NULL values 584 | * 585 | * @return string Quoted and escaped data 586 | */ 587 | public function value($data, $column = null, $null = true) { 588 | if ($data === null || is_array($data) || is_object($data)) { 589 | return parent::value($data, $column); 590 | } 591 | if (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) { 592 | return $data; 593 | } 594 | 595 | if (empty($column)) { 596 | $column = $this->introspectType($data); 597 | } 598 | 599 | switch ($column) { 600 | case 'string': 601 | case 'text': 602 | return 'N' . $this->_connection->quote($data, PDO::PARAM_STR); 603 | default: 604 | return parent::value($data, $column, $null); 605 | } 606 | } 607 | 608 | /** 609 | * Returns an array of all result rows for a given SQL query. 610 | * 611 | * Returns false if no rows matched. 612 | * 613 | * @param Model $model The model to read from 614 | * @param array $queryData The query data 615 | * @param int $recursive How many layers to go. 616 | * 617 | * @return array|false Array of resultset rows, or false if no rows matched 618 | */ 619 | public function read(Model $model, $queryData = array(), $recursive = null) { 620 | $results = parent::read($model, $queryData, $recursive); 621 | $this->_fieldMappings = array(); 622 | return $results; 623 | } 624 | 625 | /** 626 | * Fetches the next row from the current result set. 627 | * Eats the magic ROW_COUNTER variable. 628 | * 629 | * @return mixed 630 | */ 631 | public function fetchResult() { 632 | if ($row = $this->_result->fetch(PDO::FETCH_NUM)) { 633 | $resultRow = array(); 634 | foreach ($this->map as $col => $meta) { 635 | list($table, $column, $type) = $meta; 636 | if ($table === 0 && $column === self::ROW_COUNTER) { 637 | continue; 638 | } 639 | // cleans the information up a bit by trimming off trailing and leading spaces 640 | $resultRow[$table][$column] = trim($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 | * 658 | * @return void 659 | */ 660 | public function insertMulti($table, $fields, $values) { 661 | $primaryKey = $this->_getPrimaryKey($table); 662 | $hasPrimaryKey = $primaryKey && ( 663 | (is_array($fields) && in_array($primaryKey, $fields) 664 | || (is_string($fields) && strpos($fields, $this->startQuote . $primaryKey . $this->endQuote) !== false)) 665 | ); 666 | 667 | if ($hasPrimaryKey) { 668 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' ON'); 669 | } 670 | 671 | parent::insertMulti($table, $fields, $values); 672 | 673 | if ($hasPrimaryKey) { 674 | $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' OFF'); 675 | } 676 | } 677 | 678 | /** 679 | * Generate a database-native column schema string 680 | * 681 | * @param array $column An array structured like the 682 | * following: array('name'=>'value', 'type'=>'value'[, options]), 683 | * where options can be 'default', 'length', or 'key'. 684 | * 685 | * @return string 686 | */ 687 | public function buildColumn($column) { 688 | $result = parent::buildColumn($column); 689 | $result = preg_replace('/(bigint|int|integer)\([0-9]+\)/i', '$1', $result); 690 | $result = preg_replace('/(bit)\([0-9]+\)/i', '$1', $result); 691 | if (strpos($result, 'DEFAULT NULL') !== false) { 692 | if (isset($column['default']) && $column['default'] === '') { 693 | $result = str_replace('DEFAULT NULL', "DEFAULT ''", $result); 694 | } else { 695 | $result = str_replace('DEFAULT NULL', 'NULL', $result); 696 | } 697 | } elseif (array_keys($column) === array('type', 'name')) { 698 | $result .= ' NULL'; 699 | } elseif (strpos($result, "DEFAULT N'")) { 700 | $result = str_replace("DEFAULT N'", "DEFAULT '", $result); 701 | } 702 | return $result; 703 | } 704 | 705 | /** 706 | * Format indexes for create table 707 | * 708 | * @param array $indexes The indexes to build 709 | * @param string $table The table to make indexes for. 710 | * 711 | * @return string 712 | */ 713 | public function buildIndex($indexes, $table = null) { 714 | $join = array(); 715 | 716 | foreach ($indexes as $name => $value) { 717 | if ($name === 'PRIMARY') { 718 | $join[] = 'PRIMARY KEY (' . $this->name($value['column']) . ')'; 719 | } elseif (isset($value['unique']) && $value['unique']) { 720 | $out = "ALTER TABLE {$table} ADD CONSTRAINT {$name} UNIQUE"; 721 | 722 | if (is_array($value['column'])) { 723 | $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column'])); 724 | } else { 725 | $value['column'] = $this->name($value['column']); 726 | } 727 | $out .= "({$value['column']});"; 728 | $join[] = $out; 729 | } 730 | } 731 | return $join; 732 | } 733 | 734 | /** 735 | * Makes sure it will return the primary key 736 | * 737 | * @param Model|string $model Model instance of table name 738 | * 739 | * @return string 740 | */ 741 | protected function _getPrimaryKey($model) { 742 | $schema = $this->describe($model); 743 | foreach ($schema as $field => $props) { 744 | if (isset($props['key']) && $props['key'] === 'primary') { 745 | return $field; 746 | } 747 | } 748 | return null; 749 | } 750 | 751 | /** 752 | * Returns number of affected rows in previous database operation. If no previous operation exists, 753 | * this returns false. 754 | * 755 | * @param mixed $source Unused 756 | * 757 | * @return int Number of affected rows 758 | */ 759 | public function lastAffected($source = null) { 760 | $affected = parent::lastAffected(); 761 | if ($affected === null && $this->_lastAffected !== false) { 762 | return $this->_lastAffected; 763 | } 764 | return $affected; 765 | } 766 | 767 | /** 768 | * Executes given SQL statement. 769 | * 770 | * @param string $sql SQL statement 771 | * @param array $params list of params to be bound to query (supported only in select) 772 | * @param array $prepareOptions Options to be used in the prepare statement 773 | * 774 | * @return mixed PDOStatement if query executes with no problem, true as the result of a successful, false on error 775 | * query returning no rows, such as a CREATE statement, false otherwise 776 | * 777 | * @throws PDOException 778 | */ 779 | protected function _execute($sql, $params = array(), $prepareOptions = array()) { 780 | $this->_lastAffected = false; 781 | $sql = trim($sql); 782 | if (strncasecmp($sql, 'SELECT', 6) === 0 || preg_match('/^EXEC(?:UTE)?\s/mi', $sql) > 0) { 783 | $prepareOptions += array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL); 784 | 785 | return parent::_execute($sql, $params, $prepareOptions); 786 | } 787 | try { 788 | $this->_lastAffected = $this->_connection->exec($sql); 789 | if ($this->_lastAffected === false) { 790 | $this->_results = null; 791 | $error = $this->_connection->errorInfo(); 792 | $this->error = $error[2]; 793 | return false; 794 | } 795 | return true; 796 | } catch (PDOException $e) { 797 | if (isset($query->queryString)) { 798 | $e->queryString = $query->queryString; 799 | } else { 800 | $e->queryString = $sql; 801 | } 802 | throw $e; 803 | } 804 | } 805 | 806 | /** 807 | * Generate a "drop table" statement for the given table 808 | * 809 | * @param type $table Name of the table to drop 810 | * 811 | * @return string Drop table SQL statement 812 | */ 813 | protected function _dropTable($table) { 814 | return "IF OBJECT_ID('" . $this->fullTableName($table, false) . "', 'U') IS NOT NULL DROP TABLE " . $this->fullTableName($table) . ";"; 815 | } 816 | 817 | /** 818 | * Gets the schema name 819 | * 820 | * @return string The schema name 821 | */ 822 | public function getSchemaName() { 823 | return $this->config['schema']; 824 | } 825 | 826 | /** 827 | * Gets the SQL Version Number and overrides the parent getVersion(); 828 | * 829 | * @param void 830 | * 831 | * @return void 832 | */ 833 | protected function setVersion() { 834 | $version = $this->_execute('SELECT SERVERPROPERTY(\'productversion\') AS version'); 835 | $ver = $version->fetch(PDO::FETCH_NUM); 836 | $this->_serverVersion = $ver[0]; 837 | } 838 | 839 | /** 840 | * Get Version Method 841 | * 842 | * @param void 843 | * 844 | * @return string The SQL Version 845 | */ 846 | public function getVersion() { 847 | return $this->_serverVersion; 848 | } 849 | 850 | } 851 | --------------------------------------------------------------------------------