MsSqlPlatform.php 23 KB
Newer Older
1
<?php
2

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */
20

21
namespace Doctrine\DBAL\Platforms;
22

23 24
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\DBALException;
25 26
use Doctrine\DBAL\Schema\Index,
    Doctrine\DBAL\Schema\Table;
27 28 29 30 31 32 33 34

/**
 * The MsSqlPlatform provides the behavior, features and SQL dialect of the
 * MySQL database platform.
 *
 * @since 2.0
 * @author Roman Borschel <roman@code-factory.org>
 * @author Jonathan H. Wage <jonwage@gmail.com>
35
 * @author Benjamin Eberlei <kontakt@beberlei.de>
36
 * @todo Rename: MsSQLPlatform
37
 */
38
class MsSqlPlatform extends AbstractPlatform
39
{
40 41 42
    /**
     * {@inheritDoc}
     */
43
    public function getDateDiffExpression($date1, $date2)
44 45 46
    {
        return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')';
    }
47

48 49 50 51
    public function getDateAddDaysExpression($date, $days)
    {
        return 'DATEADD(day, ' . $days . ', ' . $date . ')';
    }
52

53 54 55 56
    public function getDateSubDaysExpression($date, $days)
    {
        return 'DATEADD(day, -1 * ' . $days . ', ' . $date . ')';
    }
57

58 59 60 61
    public function getDateAddMonthExpression($date, $months)
    {
        return 'DATEADD(month, ' . $months . ', ' . $date . ')';
    }
62

63 64 65 66
    public function getDateSubMonthExpression($date, $months)
    {
        return 'DATEADD(month, -1 * ' . $months . ', ' . $date . ')';
    }
67

68
    /**
69 70 71
     * Whether the platform prefers identity columns for ID generation.
     * MsSql prefers "autoincrement" identity columns since sequences can only
     * be emulated with a table.
72
     *
73
     * @return boolean
74 75
     * @override
     */
76
    public function prefersIdentityColumns()
77
    {
78 79
        return true;
    }
80

81 82 83 84 85 86 87 88 89 90 91 92 93
    /**
     * Whether the platform supports identity columns.
     * MsSql supports this through AUTO_INCREMENT columns.
     *
     * @return boolean
     * @override
     */
    public function supportsIdentityColumns()
    {
        return true;
    }

    /**
94
     * Whether the platform supports releasing savepoints.
95 96 97
     *
     * @return boolean
     */
98
    public function supportsReleaseSavepoints()
99 100 101
    {
        return false;
    }
102

103 104 105 106 107 108 109 110 111 112 113
    /**
     * create a new database
     *
     * @param string $name name of the database that should be created
     * @return string
     * @override
     */
    public function getCreateDatabaseSQL($name)
    {
        return 'CREATE DATABASE ' . $name;
    }
114

115 116 117 118 119 120 121 122 123
    /**
     * drop an existing database
     *
     * @param string $name name of the database that should be dropped
     * @return string
     * @override
     */
    public function getDropDatabaseSQL($name)
    {
124 125 126 127 128 129
        return 'DROP DATABASE ' . $name;
    }

    /**
     * @override
     */
130
    public function supportsCreateDropDatabase()
131 132
    {
        return false;
133 134 135 136 137 138 139 140
    }

    /**
     * @override
     */
    public function getDropForeignKeySQL($foreignKey, $table)
    {
        if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
141
            $foreignKey = $foreignKey->getQuotedName($this);
142 143
        }

144
        if ($table instanceof \Doctrine\DBAL\Schema\Table) {
145
            $table = $table->getQuotedName($this);
146 147 148
        }

        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
149
    }
150 151

    /**
152 153
     * @override
     */
154
    public function getDropIndexSQL($index, $table=null)
155
    {
156 157
        if ($index instanceof \Doctrine\DBAL\Schema\Index) {
            $index_ = $index;
158
            $index = $index->getQuotedName($this);
159
        } else if (!is_string($index)) {
160 161 162
            throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
        }

163 164 165 166
        if (!isset($table)) {
            return 'DROP INDEX ' . $index;
        } else {
            if ($table instanceof \Doctrine\DBAL\Schema\Table) {
167
                $table = $table->getQuotedName($this);
168 169 170
            }

            return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
171
						ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . "
Benjamin Eberlei's avatar
Benjamin Eberlei committed
172
					ELSE
173
						DROP INDEX " . $index . " ON " . $table;
174
        }
175
    }
176 177 178 179 180

    /**
     * @override
     */
    protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
181
    {
182 183 184 185 186 187 188 189
        // @todo does other code breaks because of this?
        // foce primary keys to be not null
        foreach ($columns as &$column) {
            if (isset($column['primary']) && $column['primary']) {
                $column['notnull'] = true;
            }
        }

190
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
191 192

        if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) {
193 194 195 196
            foreach ($options['uniqueConstraints'] as $name => $definition) {
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
            }
        }
197 198

        if (isset($options['primary']) && !empty($options['primary'])) {
199 200 201 202 203 204
            $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
        }

        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;

        $check = $this->getCheckDeclarationSQL($columns);
205
        if (!empty($check)) {
206 207 208 209 210
            $query .= ', ' . $check;
        }
        $query .= ')';

        $sql[] = $query;
211 212

        if (isset($options['indexes']) && !empty($options['indexes'])) {
213 214 215 216 217 218 219 220 221 222 223 224 225
            foreach ($options['indexes'] AS $index) {
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
            }
        }

        if (isset($options['foreignKeys'])) {
            foreach ((array) $options['foreignKeys'] AS $definition) {
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
            }
        }

        return $sql;
    }
226 227

    /**
228 229
     * @override
     */
230
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
231 232
    {
        $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index);
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

        $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);

        return $constraint;
    }

    /**
     * @override
     */
    public function getCreateIndexSQL(Index $index, $table)
    {
        $constraint = parent::getCreateIndexSQL($index, $table);

        if ($index->isUnique()) {
            $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
        }

        return $constraint;
    }

    /**
254
     * Extend unique key constraint with required filters
255 256 257 258 259 260 261 262
     *
     * @param string $sql
     * @param Index $index
     * @return string
     */
    private function _appendUniqueConstraintDefinition($sql, Index $index)
    {
        $fields = array();
263 264 265 266
        foreach ($index->getColumns() as $field => $definition) {
            if (!is_array($definition)) {
                $field = $definition;
            }
267 268

            $fields[] = $field . ' IS NOT NULL';
269
        }
270 271 272

        return $sql . ' WHERE ' . implode(' AND ', $fields);
    }
273

274
    /**
275
     * @override
276
     */
277
    public function getAlterTableSQL(TableDiff $diff)
278
    {
279
        $queryParts = array();
280
        $sql = array();
281

282
        if ($diff->newName !== false) {
283
            $queryParts[] = 'RENAME TO ' . $diff->newName;
284 285
        }

286
        foreach ($diff->addedColumns AS $fieldName => $column) {
287
            $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
288 289
        }

290
        foreach ($diff->removedColumns AS $column) {
291
            $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
292 293
        }

294 295 296
        foreach ($diff->changedColumns AS $columnDiff) {
            /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */
            $column = $columnDiff->column;
297 298
            $queryParts[] = 'ALTER COLUMN ' .
                    $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
299 300
        }

301
        foreach ($diff->renamedColumns AS $oldColumnName => $column) {
302 303 304
            $sql[] = "sp_RENAME '". $diff->name. ".". $oldColumnName . "' , '".$column->getQuotedName($this)."', 'COLUMN'";
            $queryParts[] = 'ALTER COLUMN ' .
                    $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
305 306
        }

307 308 309
        foreach ($queryParts as $query) {
            $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
        }
310

311
        $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
312

313
        return $sql;
314
    }
315

316 317 318
    /**
     * @override
     */
319
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
320
    {
321
        return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
322 323
    }

324 325 326
    /**
     * @override
     */
327
    public function getShowDatabasesSQL()
328
    {
329
        return 'SHOW DATABASES';
330 331 332 333 334
    }

    /**
     * @override
     */
335
    public function getListTablesSQL()
336
    {
337 338
        // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
        return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' ORDER BY name";
339 340 341 342 343
    }

    /**
     * @override
     */
344
    public function getListTableColumnsSQL($table, $database = null)
345
    {
346
        return "exec sp_columns @table_name = '" . $table . "'";
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    }

    /**
     * @override
     */
    public function getListTableForeignKeysSQL($table, $database = null)
    {
        return "SELECT f.name AS ForeignKey,
                SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
                OBJECT_NAME (f.parent_object_id) AS TableName,
                COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
                SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
                OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
                COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
                f.delete_referential_action_desc,
                f.update_referential_action_desc
                FROM sys.foreign_keys AS f
                INNER JOIN sys.foreign_key_columns AS fc
                INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
                ON f.OBJECT_ID = fc.constraint_object_id
                WHERE OBJECT_NAME (f.parent_object_id) = '" . $table . "'";
    }

    /**
     * @override
     */
373
    public function getListTableIndexesSQL($table, $currentDatabase = null)
374 375
    {
        return "exec sp_helpindex '" . $table . "'";
376
    }
377 378

    /**
379 380
     * @override
     */
381
    public function getCreateViewSQL($name, $sql)
382 383 384
    {
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
    }
385 386

    /**
387 388
     * @override
     */
389
    public function getListViewsSQL($database)
390 391 392 393
    {
        return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name";
    }

394
    /**
395 396 397 398
     * @override
     */
    public function getDropViewSQL($name)
    {
399
        return 'DROP VIEW ' . $name;
400
    }
401 402

    /**
403
     * Returns the regular expression operator.
404
     *
405
     * @return string
406 407
     * @override
     */
408
    public function getRegexpExpression()
409
    {
410
        return 'RLIKE';
411 412 413 414 415 416 417 418 419 420
    }

    /**
     * Returns global unique identifier
     *
     * @return string to get global unique identifier
     * @override
     */
    public function getGuidExpression()
    {
421
        return 'UUID()';
422
    }
423 424 425 426

    /**
     * @override
     */
427
    public function getLocateExpression($str, $substr, $startPos = false)
428
    {
429 430 431
        if ($startPos == false) {
            return 'CHARINDEX(' . $substr . ', ' . $str . ')';
        } else {
432
            return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')';
433
        }
434
    }
435

436 437 438
    /**
     * @override
     */
439
    public function getModExpression($expression1, $expression2)
440
    {
441
        return $expression1 . ' % ' . $expression2;
442
    }
443

444 445 446
    /**
     * @override
     */
447
    public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
448
    {
449
        $trimFn = '';
450

451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
        if (!$char) {
            if ($pos == self::TRIM_LEADING) {
                $trimFn = 'LTRIM';
            } else if ($pos == self::TRIM_TRAILING) {
                $trimFn = 'RTRIM';
            } else {
                return 'LTRIM(RTRIM(' . $str . '))';
            }

            return $trimFn . '(' . $str . ')';
        } else {
            /** Original query used to get those expressions
              declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x';
              declare @pat varchar(10) = '%[^' + @trim_char + ']%';
              select @c as string
              , @trim_char as trim_char
              , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading
              , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing
              , reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both;
             */
            $pattern = "'%[^' + $char + ']%'";

            if ($pos == self::TRIM_LEADING) {
                return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)';
            } else if ($pos == self::TRIM_TRAILING) {
                return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))';
            } else {
                return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))';
            }
        }
481
    }
482

483 484 485 486
    /**
     * @override
     */
    public function getConcatExpression()
487
    {
488 489
        $args = func_get_args();
        return '(' . implode(' + ', $args) . ')';
490
    }
491 492

    public function getListDatabasesSQL()
493 494 495
    {
        return 'SELECT * FROM SYS.DATABASES';
    }
496

497
    /**
498 499
     * @override
     */
500
    public function getSubstringExpression($value, $from, $len = null)
501
    {
502
        if (!is_null($len)) {
503 504 505
            return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $len . ')';
        }
        return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)';
506
    }
507

508 509 510
    /**
     * @override
     */
511
    public function getLengthExpression($column)
512
    {
513
        return 'LEN(' . $column . ')';
514 515
    }

516 517 518
    /**
     * @override
     */
519
    public function getSetTransactionIsolationSQL($level)
romanb's avatar
romanb committed
520
    {
521
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
romanb's avatar
romanb committed
522
    }
523

524 525
    /**
     * @override
526
     */
527
    public function getIntegerTypeDeclarationSQL(array $field)
528
    {
529
        return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
530 531
    }

532
    /**
533
     * @override
534
     */
535
    public function getBigIntTypeDeclarationSQL(array $field)
536
    {
537
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
538 539
    }

540 541
    /**
     * @override
542
     */
543
    public function getSmallIntTypeDeclarationSQL(array $field)
544
    {
545
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
546 547
    }

548
    /** @override */
549
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
550
    {
551
        return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)');
552
    }
553

554
    /** @override */
555
    public function getClobTypeDeclarationSQL(array $field)
556 557 558
    {
        return 'TEXT';
    }
559

560 561
    /**
     * @override
562
     */
563
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
564 565
    {
        $autoinc = '';
566
        if (!empty($columnDef['autoincrement'])) {
567
            $autoinc = ' IDENTITY';
568 569 570 571 572
        }
        $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : '';

        return $unsigned . $autoinc;
    }
573

574 575 576
    /**
     * @override
     */
577
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
578
    {
579 580
        // 6 - microseconds precision length
        return 'DATETIME2(6)';
581 582
    }

583 584 585
    /**
     * @override
     */
586
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
587
    {
588 589
        return 'DATE';
    }
590 591 592 593

    /**
     * @override
     */
594
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
595
    {
596
        return 'TIME(0)';
597
    }
598

599 600 601
    /**
     * @override
     */
602
    public function getBooleanTypeDeclarationSQL(array $field)
603 604 605 606
    {
        return 'BIT';
    }

607 608 609 610 611 612 613 614 615
    /**
     * Adds an adapter-specific LIMIT clause to the SELECT statement.
     *
     * @param string $query
     * @param mixed $limit
     * @param mixed $offset
     * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
     * @return string
     */
616
    protected function doModifyLimitQuery($query, $limit, $offset = null)
617 618 619 620 621 622
    {
        if ($limit > 0) {
            $count = intval($limit);
            $offset = intval($offset);

            if ($offset < 0) {
623
                throw new DBALException("LIMIT argument offset=$offset is not valid");
624 625
            }

626 627 628 629
            if ($offset == 0) {
                $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $query);
            } else {
                $orderby = stristr($query, 'ORDER BY');
630

631 632 633 634
                if (!$orderby) {
                    $over = 'ORDER BY (SELECT 0)';
                } else {
                    $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
635 636
                }

637 638
                // Remove ORDER BY clause from $query
                $query = preg_replace('/\s+ORDER BY(.*)/', '', $query);
AaronDDM's avatar
AaronDDM committed
639
                $query = preg_replace('/^SELECT\s/', '', $query);
640

641 642
                $start = $offset + 1;
                $end = $offset + $count;
643

AaronDDM's avatar
AaronDDM committed
644
                $query = "SELECT * FROM (SELECT ROW_NUMBER() OVER ($over) AS \"doctrine_rownum\", $query) AS doctrine_tbl WHERE \"doctrine_rownum\" BETWEEN $start AND $end";
645 646 647 648 649
            }
        }

        return $query;
    }
650 651

    /**
652
     * @override
653
     */
654
    public function convertBooleans($item)
655
    {
656 657 658
        if (is_array($item)) {
            foreach ($item as $key => $value) {
                if (is_bool($value) || is_numeric($item)) {
659
                    $item[$key] = ($value) ? 1 : 0;
660 661 662
                }
            }
        } else {
663
            if (is_bool($item) || is_numeric($item)) {
664
                $item = ($item) ? 1 : 0;
665
            }
666 667
        }
        return $item;
668
    }
669 670

    /**
671
     * @override
672
     */
673
    public function getCreateTemporaryTableSnippetSQL()
674
    {
675
        return "CREATE TABLE";
676
    }
677

678 679 680 681 682 683 684 685
    /**
     * @override
     */
    public function getTemporaryTableName($tableName)
    {
        return '#' . $tableName;
    }

686
    /**
687 688 689 690 691 692
     * @override
     */
    public function getDateTimeFormatString()
    {
        return 'Y-m-d H:i:s.u';
    }
693

694 695 696 697 698 699 700
    /**
     * @override
     */
    public function getDateTimeTzFormatString()
    {
        return $this->getDateTimeFormatString();
    }
701

702
    /**
703 704
     * Get the platform name for this instance
     *
705 706
     * @return string
     */
707
    public function getName()
708
    {
709
        return 'mssql';
710
    }
711

Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
712 713 714
    /**
     * @override
     */
715 716
    protected function initializeDoctrineTypeMappings()
    {
717
        $this->doctrineTypeMapping = array(
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
            'bigint' => 'bigint',
            'numeric' => 'decimal',
            'bit' => 'boolean',
            'smallint' => 'smallint',
            'decimal' => 'decimal',
            'smallmoney' => 'integer',
            'int' => 'integer',
            'tinyint' => 'smallint',
            'money' => 'integer',
            'float' => 'float',
            'real' => 'float',
            'double' => 'float',
            'double precision' => 'float',
            'date' => 'date',
            'datetimeoffset' => 'datetimetz',
            'datetime2' => 'datetime',
            'smalldatetime' => 'datetime',
            'datetime' => 'datetime',
            'time' => 'time',
            'char' => 'string',
            'varchar' => 'string',
            'text' => 'text',
            'nchar' => 'string',
            'nvarchar' => 'string',
            'ntext' => 'text',
            'binary' => 'text',
            'varbinary' => 'text',
            'image' => 'text',
746
        );
747
    }
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779

    /**
     * Generate SQL to create a new savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function createSavePoint($savepoint)
    {
        return 'SAVE TRANSACTION ' . $savepoint;
    }

    /**
     * Generate SQL to release a savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function releaseSavePoint($savepoint)
    {
        return '';
    }

    /**
     * Generate SQL to rollback a savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function rollbackSavePoint($savepoint)
    {
        return 'ROLLBACK TRANSACTION ' . $savepoint;
780
    }
781 782

    /**
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
783 784
     * @override
     */
785
    public function appendLockHint($fromClause, $lockMode)
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
786
    {
787 788
        // @todo coorect
        if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
789 790 791
            return $fromClause . ' WITH (tablockx)';
        } else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
            return $fromClause . ' WITH (tablockx)';
792 793
        } else {
            return $fromClause;
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
794 795 796 797 798 799 800 801 802 803
        }
    }

    /**
     * @override
     */
    public function getForUpdateSQL()
    {
        return ' ';
    }
804

805 806 807 808
    protected function getReservedKeywordsClass()
    {
        return 'Doctrine\DBAL\Platforms\Keywords\MsSQLKeywords';
    }
809 810 811 812 813 814 815 816 817 818 819 820 821 822

    /**
     * Quotes a string so that it can be safely used as a table or column name,
     * even if it is a reserved word of the platform.
     *
     * NOTE: Just because you CAN use quoted identifiers doesn't mean
     * you SHOULD use them.  In general, they end up causing way more
     * problems than they solve.
     *
     * @param string $str           identifier name to be quoted
     * @return string               quoted identifier string
     */
    public function quoteIdentifier($str)
    {
823
        return "[" . str_replace("]", "][", $str) . "]";
824
    }
825

826 827 828 829
    public function getTruncateTableSQL($tableName, $cascade = false)
    {
        return 'TRUNCATE TABLE '.$tableName;
    }
830 831 832 833 834 835 836 837

    /**
     * Gets the SQL Snippet used to declare a BLOB column type.
     */
    public function getBlobTypeDeclarationSQL(array $field)
    {
        return 'VARBINARY(MAX)';
    }
838
}