Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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