├── .phpunit-watcher.yml
├── .styleci.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── docker-compose.yml
├── infection.json.dist
├── psalm.xml
├── rector.php
├── src
├── Builder
│ ├── InConditionBuilder.php
│ └── LikeConditionBuilder.php
├── Column
│ ├── BinaryColumn.php
│ ├── ColumnBuilder.php
│ ├── ColumnDefinitionBuilder.php
│ ├── ColumnFactory.php
│ └── DateTimeColumn.php
├── Command.php
├── Connection.php
├── DDLQueryBuilder.php
├── DMLQueryBuilder.php
├── DQLQueryBuilder.php
├── Driver.php
├── Dsn.php
├── IndexMethod.php
├── IndexType.php
├── QueryBuilder.php
├── Quoter.php
├── Schema.php
├── SqlParser.php
├── TableSchema.php
└── Transaction.php
└── tests
├── .env
├── Builder
└── InconditionBuilderTest.php
├── ColumnBuilderTest.php
├── ColumnFactoryTest.php
├── ColumnTest.php
├── CommandTest.php
├── ConnectionTest.php
├── DsnTest.php
├── PdoCommandTest.php
├── PdoConnectionTest.php
├── Provider
├── ColumnBuilderProvider.php
├── ColumnFactoryProvider.php
├── ColumnProvider.php
├── CommandPDOProvider.php
├── CommandProvider.php
├── QueryBuilderProvider.php
├── QuoterProvider.php
├── SchemaProvider.php
├── SqlParserProvider.php
└── Type
│ ├── BinaryProvider.php
│ ├── BitProvider.php
│ ├── CharProvider.php
│ ├── DateProvider.php
│ ├── GeometryProvider.php
│ ├── VarBinaryProvider.php
│ └── VarCharProvider.php
├── QueryBuilderTest.php
├── QueryGetTableAliasTest.php
├── QueryTest.php
├── QuoterTest.php
├── SchemaTest.php
├── SqlParserTest.php
├── Support
├── Fixture
│ ├── Type
│ │ ├── bigint.sql
│ │ ├── binary.sql
│ │ ├── bit.sql
│ │ ├── char.sql
│ │ ├── date.sql
│ │ ├── decimal.sql
│ │ ├── float.sql
│ │ ├── geometry.sql
│ │ ├── image.sql
│ │ ├── int.sql
│ │ ├── json.sql
│ │ ├── money.sql
│ │ ├── ntext.sql
│ │ ├── numeric.sql
│ │ ├── real.sql
│ │ ├── rowversion.sql
│ │ ├── smallint.sql
│ │ ├── smallmoney.sql
│ │ ├── text.sql
│ │ ├── tinyint.sql
│ │ ├── uniqueidentifier.sql
│ │ ├── varbinary.sql
│ │ └── varchar.sql
│ └── mssql.sql
└── TestTrait.php
├── Type
├── BigIntTest.php
├── BinaryTest.php
├── BitTest.php
├── CharTest.php
├── DateTest.php
├── DecimalTest.php
├── FloatTest.php
├── GeometryTest.php
├── ImageTest.php
├── IntTest.php
├── JsonTest.php
├── MoneyTest.php
├── NTextTest.php
├── NumericTest.php
├── RealTest.php
├── RowversionTest.php
├── SmallIntTest.php
├── SmallMoneyTest.php
├── TextTest.php
├── TinyIntTest.php
├── UniqueidentifierTest.php
├── VarBinaryTest.php
└── VarCharTest.php
└── bootstrap.php
/.phpunit-watcher.yml:
--------------------------------------------------------------------------------
1 | watch:
2 | directories:
3 | - src
4 | - tests
5 | fileMask: '*.php'
6 | notifications:
7 | passingTests: false
8 | failingTests: false
9 | phpunit:
10 | binaryPath: vendor/bin/phpunit
11 | timeout: 180
12 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr12
2 | risky: true
3 |
4 | version: 8.1
5 |
6 | finder:
7 | exclude:
8 | - docs
9 | - vendor
10 |
11 | enabled:
12 | - alpha_ordered_traits
13 | - array_indentation
14 | - array_push
15 | - combine_consecutive_issets
16 | - combine_consecutive_unsets
17 | - combine_nested_dirname
18 | - declare_strict_types
19 | - dir_constant
20 | - fully_qualified_strict_types
21 | - function_to_constant
22 | - hash_to_slash_comment
23 | - is_null
24 | - logical_operators
25 | - magic_constant_casing
26 | - magic_method_casing
27 | - method_separation
28 | - modernize_types_casting
29 | - native_function_casing
30 | - native_function_type_declaration_casing
31 | - no_alias_functions
32 | - no_empty_comment
33 | - no_empty_phpdoc
34 | - no_empty_statement
35 | - no_extra_block_blank_lines
36 | - no_short_bool_cast
37 | - no_superfluous_elseif
38 | - no_unneeded_control_parentheses
39 | - no_unneeded_curly_braces
40 | - no_unneeded_final_method
41 | - no_unset_cast
42 | - no_unused_imports
43 | - no_unused_lambda_imports
44 | - no_useless_else
45 | - no_useless_return
46 | - normalize_index_brace
47 | - php_unit_dedicate_assert
48 | - php_unit_dedicate_assert_internal_type
49 | - php_unit_expectation
50 | - php_unit_mock
51 | - php_unit_mock_short_will_return
52 | - php_unit_namespaced
53 | - php_unit_no_expectation_annotation
54 | - phpdoc_no_empty_return
55 | - phpdoc_no_useless_inheritdoc
56 | - phpdoc_order
57 | - phpdoc_property
58 | - phpdoc_scalar
59 | - phpdoc_singular_inheritdoc
60 | - phpdoc_trim
61 | - phpdoc_trim_consecutive_blank_line_separation
62 | - phpdoc_type_to_var
63 | - phpdoc_types
64 | - phpdoc_types_order
65 | - print_to_echo
66 | - regular_callable_call
67 | - return_assignment
68 | - self_accessor
69 | - self_static_accessor
70 | - set_type_to_cast
71 | - short_array_syntax
72 | - short_list_syntax
73 | - simplified_if_return
74 | - single_quote
75 | - standardize_not_equals
76 | - ternary_to_null_coalescing
77 | - trailing_comma_in_multiline_array
78 | - unalign_double_arrow
79 | - unalign_equals
80 | - empty_loop_body_braces
81 | - integer_literal_case
82 | - union_type_without_spaces
83 |
84 | disabled:
85 | - function_declaration
86 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # MSSQL Server driver for Yii Database Change Log
2 |
3 | ## 2.0.0 under development
4 |
5 | - New #277: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
6 | for type casting performance. Related with yiisoft/db#752 (@Tigrov)
7 | - Enh #293, #357: Implement and use `SqlParser` class (@Tigrov)
8 | - Chg #306: Remove parameter `$withColumn` from `Quoter::getTableNameParts()` method (@Tigrov)
9 | - Chg #308: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov)
10 | - Enh #312: Refactor `bit` type (@Tigrov)
11 | - Enh #315: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov)
12 | - Enh #317: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov)
13 | - New #316, #327: Implement `ColumnFactory` class (@Tigrov)
14 | - Enh #319: Separate column type constants (@Tigrov)
15 | - New #320: Realize `ColumnBuilder` class (@Tigrov)
16 | - Enh #321: Update according changes in `ColumnSchemaInterface` (@Tigrov)
17 | - New #322, #330, #340: Add `ColumnDefinitionBuilder` class (@Tigrov)
18 | - Enh #323: Refactor `Dsn` class (@Tigrov)
19 | - Enh #324: Use constructor to create columns and initialize properties (@Tigrov)
20 | - Enh #327: Refactor `Schema::findColumns()` method (@Tigrov)
21 | - Enh #328: Refactor `Schema::normalizeDefaultValue()` method and move it to `ColumnFactory` class (@Tigrov)
22 | - Enh #331: Refactor according to changes #902 in `yiisoft/db` package (@Tigrov)
23 | - Chg #333: Update `QueryBuilder` constructor (@Tigrov)
24 | - Enh #332: Use `ColumnDefinitionBuilder` to generate table column SQL representation (@Tigrov)
25 | - Enh #335: Remove `ColumnInterface` (@Tigrov)
26 | - Enh #337: Rename `ColumnSchemaInterface` to `ColumnInterface` (@Tigrov)
27 | - Enh #338: Replace `DbArrayHelper::getColumn()` with `array_column()` (@Tigrov)
28 | - New #339: Add `IndexType` and `IndexMethod` classes (@Tigrov)
29 | - Bug #343: Explicitly mark nullable parameters (@vjik)
30 | - New #342: Support JSON type (@Tigrov)
31 | - Chg #344: Change supported PHP versions to `8.1 - 8.4` (@Tigrov)
32 | - Chg #344: Change return type of `Command::insertWithReturningPks()` method to `array|false` (@Tigrov)
33 | - New #345: Add parameters `$ifExists` and `$cascade` to `CommandInterface::dropTable()` and
34 | `DDLQueryBuilderInterface::dropTable()` methods (@vjik)
35 | - Chg #348: Remove usage of `hasLimit()` and `hasOffset()` methods of `DQLQueryBuilder` class (@Tigrov)
36 | - Enh #350: Refactor according changes in `db` package (@Tigrov)
37 | - New #349: Add `caseSensitive` option to like condition (@vjik)
38 | - Enh #352: Remove `getCacheKey()` and `getCacheTag()` methods from `Schema` class (@Tigrov)
39 | - Enh #355, #356: Use `DbArrayHelper::arrange()` instead of `DbArrayHelper::index()` method (@Tigrov)
40 | - New #353: Realize `Schema::loadResultColumn()` method (@Tigrov)
41 | - Enh #300: Remove realization of `Connection::createBatchQueryResult()` method (@Tigrov)
42 | - Bug #360: Fix columns order in composite primary key (@Tigrov)
43 | - New #358: Use `DateTimeColumn` class for datetime column types (@Tigrov)
44 | - New #361: Implement `DMLQueryBuilder::upsertWithReturningPks()` method (@Tigrov)
45 | - Enh #361: Refactor `DMLQueryBuilder::insertWithReturningPks()` method (@Tigrov)
46 | - Chg #363: Add alias in `DQLQueryBuilder::selectExists()` method for consistency with other DBMS (@Tigrov)
47 |
48 | ## 1.2.0 March 21, 2024
49 |
50 | - Enh #286: Change property `Schema::$typeMap` to constant `Schema::TYPE_MAP` (@Tigrov)
51 | - Enh #291: Resolve deprecated methods (@Tigrov)
52 | - Enh #292: Minor refactoring of `Command` and `Quoter` (@Tigrov)
53 | - Bug #287: Fix `DMLQueryBuilder::insertWithReturningPks()` and `Command::insertWithReturningPks()` methods (@Tigrov)
54 |
55 | ## 1.1.0 November 12, 2023
56 |
57 | - Enh #283: Move methods from `Command` to `AbstractPdoCommand` class (@Tigrov)
58 | - Bug #275: Refactor `DMLQueryBuilder`, related with yiisoft/db#746 (@Tigrov)
59 | - Bug #278: Remove `RECURSIVE` expression from CTE queries (@Tigrov)
60 | - Bug #280: Fix type boolean (@terabytesoftw)
61 | - Bug #282: Fix `DDLQueryBuilder::alterColumn()` for columns with default null (@Tigrov)
62 |
63 | ## 1.0.1 July 24, 2023
64 |
65 | - Enh #271: Typecast refactoring (@Tigrov)
66 |
67 | ## 1.0.0 April 12, 2023
68 |
69 | - Initial release.
70 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software ()
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Yii Database MSSQL Server driver
9 |
10 |
11 |
12 | [](https://packagist.org/packages/yiisoft/db-mssql)
13 | [](https://packagist.org/packages/yiisoft/db-mssql)
14 | [](https://codecov.io/gh/yiisoft/db-mssql)
15 | [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/db-mssql/master)
16 | [](https://github.com/yiisoft/db-mssql/actions?query=workflow%3A%22static+analysis%22)
17 | [](https://shepherd.dev/github/yiisoft/db-mssql)
18 | [](https://shepherd.dev/github/yiisoft/db-mssql)
19 |
20 | MSSQL Server driver for [Yii Database](https://github.com/yiisoft/db) is a package for working with [MSSQL] databases in PHP.
21 |
22 | The package provides a set of classes for connecting to a [MSSQL] database, creating and executing commands, and working with data.
23 | It also includes a set of tools for building and executing queries, including support for parameter binding, as well
24 | as tools for working with transactions.
25 |
26 | To use it, you will need to have the PHP [MSSQL extension] installed and enabled on your server. You will also need to
27 | have access to a SQL Server database and the necessary credentials to connect to it.
28 |
29 | [MSSQL]: https://www.microsoft.com/sql-server
30 | [MSSQL extension]: https://pecl.php.net/package/sqlsrv
31 |
32 | ## Support version
33 |
34 | | PHP | MSSQL Version | CI-Actions |
35 | |---------------|---------------|------------|
36 | | **8.1 - 8.4** | **2017 - 2022**| [](https://github.com/yiisoft/db-mssql/actions/workflows/build.yml)
37 |
38 | ## Installation
39 |
40 | The package could be installed with [Composer](https://getcomposer.org):
41 |
42 | ```shell
43 | composer require yiisoft/db-mssql
44 | ```
45 |
46 | ## Documentation
47 |
48 | To configure connection to MSSQL database check [Connecting MSSQL](https://github.com/yiisoft/db/blob/master/docs/guide/en/connection/mssql.md).
49 |
50 | [Check the documentation docs](https://github.com/yiisoft/db/blob/master/docs/guide/en/README.md) to learn about usage.
51 |
52 | - [Internals](docs/internals.md)
53 |
54 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.
55 | You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
56 |
57 | ## License
58 |
59 | The Yii Database MSSQL Server driver is free software. It is released under the terms of the BSD License.
60 | Please see [`LICENSE`](./LICENSE.md) for more information.
61 |
62 | Maintained by [Yii Software](https://www.yiiframework.com/).
63 |
64 | ## Support the project
65 |
66 | [](https://opencollective.com/yiisoft)
67 |
68 | ## Follow updates
69 |
70 | [](https://www.yiiframework.com/)
71 | [](https://twitter.com/yiiframework)
72 | [](https://t.me/yii3en)
73 | [](https://www.facebook.com/groups/yiitalk)
74 | [](https://yiiframework.com/go/slack)
75 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/db-mssql",
3 | "description": "MSSQL Server driver for Yii Database",
4 | "keywords": [
5 | "yii",
6 | "mssql",
7 | "sql server",
8 | "database",
9 | "sql",
10 | "dbal",
11 | "query-builder"
12 | ],
13 | "type": "library",
14 | "license": "BSD-3-Clause",
15 | "support": {
16 | "issues": "https://github.com/yiisoft/db-mssql/issues?state=open",
17 | "source": "https://github.com/yiisoft/db-mssql",
18 | "forum": "https://www.yiiframework.com/forum/",
19 | "wiki": "https://www.yiiframework.com/wiki/",
20 | "irc": "ircs://irc.libera.chat:6697/yii",
21 | "chat": "https://t.me/yii3en"
22 | },
23 | "funding": [
24 | {
25 | "type": "opencollective",
26 | "url": "https://opencollective.com/yiisoft"
27 | },
28 | {
29 | "type": "github",
30 | "url": "https://github.com/sponsors/yiisoft"
31 | }
32 | ],
33 | "require": {
34 | "php": "8.1 - 8.4",
35 | "ext-pdo": "*",
36 | "yiisoft/db": "dev-master"
37 | },
38 | "require-dev": {
39 | "maglnet/composer-require-checker": "^4.7.1",
40 | "phpunit/phpunit": "^10.5.45",
41 | "rector/rector": "^2.0.10",
42 | "roave/infection-static-analysis-plugin": "^1.35",
43 | "spatie/phpunit-watcher": "^1.24",
44 | "vimeo/psalm": "^5.26.1 || ^6.8.8",
45 | "vlucas/phpdotenv": "^5.6.1",
46 | "yiisoft/aliases": "^2.0",
47 | "yiisoft/cache-file": "^3.2",
48 | "yiisoft/var-dumper": "^1.7"
49 | },
50 | "autoload": {
51 | "psr-4": {
52 | "Yiisoft\\Db\\Mssql\\": "src"
53 | }
54 | },
55 | "autoload-dev": {
56 | "psr-4": {
57 | "Yiisoft\\Db\\Mssql\\Tests\\": "tests",
58 | "Yiisoft\\Db\\Tests\\": "vendor/yiisoft/db/tests"
59 | },
60 | "files": ["tests/bootstrap.php"]
61 | },
62 | "config": {
63 | "sort-packages": true,
64 | "allow-plugins": {
65 | "infection/extension-installer": true,
66 | "composer/package-versions-deprecated": true
67 | }
68 | },
69 | "scripts": {
70 | "test": "phpunit --testdox --no-interaction",
71 | "test-watch": "phpunit-watcher watch"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | mssql:
5 | image: mcr.microsoft.com/mssql/server:2022-latest
6 | environment:
7 | - SA_PASSWORD=YourStrong!Passw0rd
8 | - ACCEPT_EULA=Y
9 | ports:
10 | # : < MSSQL Port running inside container>
11 | - "1433:1433"
12 | expose:
13 | # Opens port 1433 on the container
14 | - '1433'
15 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "timeout": 45,
8 | "logs": {
9 | "text": "php:\/\/stderr",
10 | "stryker": {
11 | "report": "master"
12 | }
13 | },
14 | "mutators": {
15 | "@default": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | paths([
13 | __DIR__ . '/src',
14 | /**
15 | * Disabled ./tests directory due to different branches with main package when testing
16 | */
17 | // __DIR__ . '/tests',
18 | ]);
19 |
20 | // register a single rule
21 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
22 |
23 | // define sets of rules
24 | $rectorConfig->sets([
25 | LevelSetList::UP_TO_PHP_81,
26 | ]);
27 |
28 | $rectorConfig->skip([
29 | NullToStrictStringFuncCallArgRector::class,
30 | ReadOnlyPropertyRector::class,
31 | ]);
32 | };
33 |
--------------------------------------------------------------------------------
/src/Builder/InConditionBuilder.php:
--------------------------------------------------------------------------------
1 | $column) {
62 | if ($column instanceof ExpressionInterface) {
63 | $quotedColumns[$i] = $columns[$i] = $this->queryBuilder->buildExpression($column);
64 | continue;
65 | }
66 |
67 | $quotedColumns[$i] = !str_contains($column, '(')
68 | ? $this->queryBuilder->getQuoter()->quoteColumnName($column) : $column;
69 | }
70 |
71 | $vss = [];
72 |
73 | /** @psalm-var string[][] $values */
74 | foreach ($values as $value) {
75 | $vs = [];
76 | foreach ($columns as $i => $column) {
77 | if (isset($value[$column])) {
78 | $phName = $this->queryBuilder->bindParam($value[$column], $params);
79 | $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
80 | } else {
81 | $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
82 | }
83 | }
84 | $vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
85 | }
86 |
87 | return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Builder/LikeConditionBuilder.php:
--------------------------------------------------------------------------------
1 | '[%]',
21 | '_' => '[_]',
22 | '[' => '[[]',
23 | ']' => '[]]',
24 | '\\' => '[\\]',
25 | ];
26 |
27 | public function build(LikeConditionInterface $expression, array &$params = []): string
28 | {
29 | if ($expression->getCaseSensitive() === true) {
30 | throw new NotSupportedException('MSSQL doesn\'t support case-sensitive "LIKE" conditions.');
31 | }
32 | return parent::build($expression, $params);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Column/BinaryColumn.php:
--------------------------------------------------------------------------------
1 | getDbType() === 'varbinary') {
19 | if ($value instanceof ParamInterface && is_string($value->getValue())) {
20 | /** @psalm-var string */
21 | $value = $value->getValue();
22 | }
23 |
24 | if (is_string($value)) {
25 | return new Expression('CONVERT(VARBINARY(MAX), ' . ('0x' . bin2hex($value)) . ')');
26 | }
27 | }
28 |
29 | return parent::dbTypecast($value);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Column/ColumnBuilder.php:
--------------------------------------------------------------------------------
1 | buildType($column)
42 | . $this->buildAutoIncrement($column)
43 | . $this->buildPrimaryKey($column)
44 | . $this->buildUnique($column)
45 | . $this->buildNotNull($column)
46 | . $this->buildDefault($column)
47 | . $this->buildCheck($column)
48 | . $this->buildReferences($column)
49 | . $this->buildExtra($column);
50 | }
51 |
52 | public function buildAlter(ColumnInterface $column): string
53 | {
54 | return $this->buildType($column)
55 | . $this->buildNotNull($column)
56 | . $this->buildExtra($column);
57 | }
58 |
59 | protected function buildCheck(ColumnInterface $column): string
60 | {
61 | $check = $column->getCheck();
62 |
63 | if (empty($check)) {
64 | $name = $column->getName();
65 |
66 | if (empty($name)) {
67 | return '';
68 | }
69 |
70 | return match ($column->getType()) {
71 | ColumnType::ARRAY, ColumnType::STRUCTURED, ColumnType::JSON =>
72 | ' CHECK (isjson(' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ') > 0)',
73 | default => '',
74 | };
75 | }
76 |
77 | return " CHECK ($check)";
78 | }
79 |
80 | protected function buildOnDelete(string $onDelete): string
81 | {
82 | if (strtoupper($onDelete) === ReferentialAction::RESTRICT) {
83 | return '';
84 | }
85 |
86 | return " ON DELETE $onDelete";
87 | }
88 |
89 | protected function buildOnUpdate(string $onUpdate): string
90 | {
91 | if (strtoupper($onUpdate) === ReferentialAction::RESTRICT) {
92 | return '';
93 | }
94 |
95 | return " ON UPDATE $onUpdate";
96 | }
97 |
98 | protected function getDbType(ColumnInterface $column): string
99 | {
100 | $size = $column->getSize();
101 |
102 | /** @psalm-suppress DocblockTypeContradiction */
103 | $dbType = $column->getDbType() ?? match ($column->getType()) {
104 | ColumnType::BOOLEAN => 'bit',
105 | ColumnType::BIT => match (true) {
106 | $size === null => 'bigint',
107 | $size === 1 => 'bit',
108 | $size <= 8 => 'tinyint',
109 | $size <= 16 => 'smallint',
110 | $size <= 32 => 'int',
111 | $size <= 64 => 'bigint',
112 | default => 'varbinary(' . ceil($size / 8) . ')',
113 | },
114 | ColumnType::TINYINT => 'tinyint',
115 | ColumnType::SMALLINT => 'smallint',
116 | ColumnType::INTEGER => 'int',
117 | ColumnType::BIGINT => 'bigint',
118 | ColumnType::FLOAT => 'real',
119 | ColumnType::DOUBLE => 'float(53)',
120 | ColumnType::DECIMAL => 'decimal',
121 | ColumnType::MONEY => 'money',
122 | ColumnType::CHAR => 'nchar',
123 | ColumnType::STRING => 'nvarchar(' . (($size ?? '255') ?: 'max') . ')',
124 | ColumnType::TEXT => 'nvarchar(max)',
125 | ColumnType::BINARY => 'varbinary(max)',
126 | ColumnType::UUID => 'uniqueidentifier',
127 | ColumnType::TIMESTAMP => 'datetime2',
128 | ColumnType::DATETIME => 'datetime2',
129 | ColumnType::DATETIMETZ => 'datetimeoffset',
130 | ColumnType::TIME => 'time',
131 | ColumnType::TIMETZ => 'time',
132 | ColumnType::DATE => 'date',
133 | ColumnType::ARRAY => 'nvarchar(max)',
134 | ColumnType::STRUCTURED => 'nvarchar(max)',
135 | ColumnType::JSON => 'nvarchar(max)',
136 | default => 'nvarchar',
137 | };
138 |
139 | return match ($dbType) {
140 | 'timestamp' => 'datetime2',
141 | 'varchar' => 'varchar(' . ($size ?: 'max') . ')',
142 | 'nvarchar' => 'nvarchar(' . ($size ?: 'max') . ')',
143 | 'varbinary' => 'varbinary(' . ($size ?: 'max') . ')',
144 | default => $dbType,
145 | };
146 | }
147 |
148 | protected function getDefaultUuidExpression(): string
149 | {
150 | return 'newid()';
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Column/ColumnFactory.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | protected const TYPE_MAP = [
26 | /** Exact numbers */
27 | 'bit' => ColumnType::BOOLEAN,
28 | 'tinyint' => ColumnType::TINYINT,
29 | 'smallint' => ColumnType::SMALLINT,
30 | 'int' => ColumnType::INTEGER,
31 | 'bigint' => ColumnType::BIGINT,
32 | 'numeric' => ColumnType::DECIMAL,
33 | 'decimal' => ColumnType::DECIMAL,
34 | 'smallmoney' => ColumnType::MONEY,
35 | 'money' => ColumnType::MONEY,
36 |
37 | /** Approximate numbers */
38 | 'float' => ColumnType::FLOAT,
39 | 'real' => ColumnType::FLOAT,
40 | 'double' => ColumnType::DOUBLE,
41 |
42 | /** Date and time */
43 | 'smalldatetime' => ColumnType::DATETIME,
44 | 'datetime' => ColumnType::DATETIME,
45 | 'datetime2' => ColumnType::DATETIME,
46 | 'datetimeoffset' => ColumnType::DATETIMETZ,
47 | 'time' => ColumnType::TIME,
48 | 'date' => ColumnType::DATE,
49 |
50 | /** Character strings */
51 | 'char' => ColumnType::CHAR,
52 | 'varchar' => ColumnType::STRING,
53 | 'text' => ColumnType::TEXT,
54 |
55 | /** Unicode character strings */
56 | 'nchar' => ColumnType::CHAR,
57 | 'nvarchar' => ColumnType::STRING,
58 | 'ntext' => ColumnType::TEXT,
59 |
60 | /** Binary strings */
61 | 'binary' => ColumnType::BINARY,
62 | 'varbinary' => ColumnType::BINARY,
63 | 'image' => ColumnType::BINARY,
64 |
65 | /**
66 | * Other data types 'cursor' type can't be used with tables
67 | */
68 | 'timestamp' => ColumnType::BINARY,
69 | 'hierarchyid' => ColumnType::STRING,
70 | 'uniqueidentifier' => ColumnType::UUID,
71 | 'sql_variant' => ColumnType::STRING,
72 | 'xml' => ColumnType::STRING,
73 | 'table' => ColumnType::STRING,
74 | ];
75 |
76 | public function fromPseudoType(string $pseudoType, array $info = []): ColumnInterface
77 | {
78 | if ($pseudoType === PseudoType::UUID_PK_SEQ && !isset($info['defaultValue'])) {
79 | $info['defaultValue'] = new Expression('newsequentialid()');
80 | }
81 |
82 | return parent::fromPseudoType($pseudoType, $info);
83 | }
84 |
85 | protected function getColumnClass(string $type, array $info = []): string
86 | {
87 | return match ($type) {
88 | ColumnType::BINARY => BinaryColumn::class,
89 | ColumnType::TIMESTAMP => DateTimeColumn::class,
90 | ColumnType::DATETIME => DateTimeColumn::class,
91 | ColumnType::DATETIMETZ => DateTimeColumn::class,
92 | ColumnType::TIME => DateTimeColumn::class,
93 | ColumnType::TIMETZ => DateTimeColumn::class,
94 | ColumnType::DATE => DateTimeColumn::class,
95 | default => parent::getColumnClass($type, $info),
96 | };
97 | }
98 |
99 | protected function getType(string $dbType, array $info = []): string
100 | {
101 | if (isset($info['check'], $info['name']) && $info['check'] === "(isjson([{$info['name']}])>(0))") {
102 | return ColumnType::JSON;
103 | }
104 |
105 | return parent::getType($dbType, $info);
106 | }
107 |
108 | protected function normalizeNotNullDefaultValue(string $defaultValue, ColumnInterface $column): mixed
109 | {
110 | if ($defaultValue[0] === '(' && $defaultValue[-1] === ')') {
111 | $defaultValue = substr($defaultValue, 1, -1);
112 | }
113 |
114 | if (str_starts_with($defaultValue, '0x')) {
115 | return hex2bin(substr($defaultValue, 2));
116 | }
117 |
118 | return parent::normalizeNotNullDefaultValue($defaultValue, $column);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Column/DateTimeColumn.php:
--------------------------------------------------------------------------------
1 | getDbType()) {
12 | 'smalldatetime' => '',
13 | 'datetime' => '.v',
14 | default => parent::getMillisecondsFormat(),
15 | };
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Command.php:
--------------------------------------------------------------------------------
1 | setSql($sql)->queryColumn();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Connection.php:
--------------------------------------------------------------------------------
1 | setSql($sql);
29 | }
30 |
31 | if ($this->logger !== null) {
32 | $command->setLogger($this->logger);
33 | }
34 |
35 | if ($this->profiler !== null) {
36 | $command->setProfiler($this->profiler);
37 | }
38 |
39 | return $command->bindValues($params);
40 | }
41 |
42 | public function createTransaction(): TransactionInterface
43 | {
44 | return new Transaction($this);
45 | }
46 |
47 | public function getColumnFactory(): ColumnFactoryInterface
48 | {
49 | return $this->columnFactory ??= new ColumnFactory();
50 | }
51 |
52 | public function getQueryBuilder(): QueryBuilderInterface
53 | {
54 | return $this->queryBuilder ??= new QueryBuilder($this);
55 | }
56 |
57 | public function getQuoter(): QuoterInterface
58 | {
59 | return $this->quoter ??= new Quoter(['[', ']'], ['[', ']'], $this->getTablePrefix());
60 | }
61 |
62 | public function getSchema(): SchemaInterface
63 | {
64 | return $this->schema ??= new Schema($this, $this->schemaCache);
65 | }
66 |
67 | /**
68 | * Initializes the DB connection.
69 | *
70 | * This method is invoked right after the DB connection is established.
71 | */
72 | protected function initConnection(): void
73 | {
74 | $this->pdo = $this->driver->createConnection();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/DMLQueryBuilder.php:
--------------------------------------------------------------------------------
1 | schema->getTableSchema($table);
36 | $primaryKeys = $tableSchema?->getPrimaryKey();
37 |
38 | if (empty($primaryKeys)) {
39 | return $this->insert($table, $columns, $params);
40 | }
41 |
42 | /** @var TableSchema $tableSchema */
43 | [$declareSql, $outputSql, $selectSql] = $this->prepareReturningParts($tableSchema, $primaryKeys);
44 | [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params);
45 |
46 | return $declareSql
47 | . 'INSERT INTO ' . $this->quoter->quoteTableName($table)
48 | . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
49 | . $outputSql
50 | . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' ' . $values) . ';'
51 | . $selectSql;
52 | }
53 |
54 | /**
55 | * @throws InvalidArgumentException
56 | */
57 | public function resetSequence(string $table, int|string|null $value = null): string
58 | {
59 | $tableSchema = $this->schema->getTableSchema($table);
60 |
61 | if ($tableSchema === null) {
62 | throw new InvalidArgumentException("Table not found: '$table'.");
63 | }
64 |
65 | $sequenceName = $tableSchema->getSequenceName();
66 |
67 | if ($sequenceName === null) {
68 | throw new InvalidArgumentException("There is not sequence associated with table '$table'.'");
69 | }
70 |
71 | $tableName = $this->quoter->quoteTableName($table);
72 |
73 | if ($value === null) {
74 | return "DBCC CHECKIDENT ('$tableName', RESEED, 0) WITH NO_INFOMSGS;DBCC CHECKIDENT ('$tableName', RESEED)";
75 | }
76 |
77 | return "DBCC CHECKIDENT ('$tableName', RESEED, $value)";
78 | }
79 |
80 | /**
81 | * @throws Exception
82 | * @throws InvalidArgumentException
83 | * @throws InvalidConfigException
84 | * @throws JsonException
85 | * @throws NotSupportedException
86 | */
87 | public function upsert(
88 | string $table,
89 | array|QueryInterface $insertColumns,
90 | array|bool $updateColumns = true,
91 | array &$params = [],
92 | ): string {
93 | /** @psalm-var Constraint[] $constraints */
94 | $constraints = [];
95 |
96 | [$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns(
97 | $table,
98 | $insertColumns,
99 | $updateColumns,
100 | $constraints
101 | );
102 |
103 | if (empty($uniqueNames)) {
104 | return $this->insert($table, $insertColumns, $params);
105 | }
106 |
107 | $onCondition = ['or'];
108 | $quotedTableName = $this->quoter->quoteTableName($table);
109 |
110 | foreach ($constraints as $constraint) {
111 | $constraintCondition = ['and'];
112 | $columnNames = (array) $constraint->getColumnNames();
113 |
114 | /** @psalm-var string[] $columnNames */
115 | foreach ($columnNames as $name) {
116 | $quotedName = $this->quoter->quoteColumnName($name);
117 | $constraintCondition[] = "$quotedTableName.$quotedName=[EXCLUDED].$quotedName";
118 | }
119 |
120 | $onCondition[] = $constraintCondition;
121 | }
122 |
123 | $on = $this->queryBuilder->buildCondition($onCondition, $params);
124 |
125 | [, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params);
126 |
127 | $mergeSql = 'MERGE ' . $quotedTableName . ' WITH (HOLDLOCK) USING ('
128 | . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : $values)
129 | . ') AS [EXCLUDED] (' . implode(', ', $insertNames) . ') ' . "ON ($on)";
130 |
131 | $insertValues = [];
132 |
133 | foreach ($insertNames as $quotedName) {
134 | $insertValues[] = '[EXCLUDED].' . $quotedName;
135 | }
136 |
137 | $insertSql = 'INSERT (' . implode(', ', $insertNames) . ') VALUES (' . implode(', ', $insertValues) . ')';
138 |
139 | if ($updateColumns === false || $updateNames === []) {
140 | /** there are no columns to update */
141 | return "$mergeSql WHEN NOT MATCHED THEN $insertSql;";
142 | }
143 |
144 | if ($updateColumns === true) {
145 | $updateColumns = [];
146 |
147 | /** @psalm-var string[] $updateNames */
148 | foreach ($updateNames as $quotedName) {
149 | $updateColumns[$quotedName] = new Expression('[EXCLUDED].' . $quotedName);
150 | }
151 | }
152 |
153 | [$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params);
154 |
155 | return "$mergeSql WHEN MATCHED THEN UPDATE SET " . implode(', ', $updates)
156 | . " WHEN NOT MATCHED THEN $insertSql;";
157 | }
158 |
159 | public function upsertWithReturningPks(
160 | string $table,
161 | array|QueryInterface $insertColumns,
162 | array|bool $updateColumns = true,
163 | array &$params = [],
164 | ): string {
165 | [$uniqueNames] = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns);
166 |
167 | if (empty($uniqueNames)) {
168 | return $this->insertWithReturningPks($table, $insertColumns, $params);
169 | }
170 |
171 | $upsertSql = $this->upsert($table, $insertColumns, $updateColumns, $params);
172 |
173 | $tableSchema = $this->schema->getTableSchema($table);
174 | $primaryKeys = $tableSchema?->getPrimaryKey();
175 |
176 | if (empty($primaryKeys)) {
177 | return $upsertSql;
178 | }
179 |
180 | /** @var TableSchema $tableSchema */
181 | [$declareSql, $outputSql, $selectSql] = $this->prepareReturningParts($tableSchema, $primaryKeys);
182 |
183 | return $declareSql
184 | . rtrim($upsertSql, ';')
185 | . $outputSql . ';'
186 | . $selectSql;
187 | }
188 |
189 | /**
190 | * Prepares SQL parts for a returning query.
191 | *
192 | * @param TableSchema $tableSchema The table schema.
193 | * @param string[] $returnColumns The columns to return.
194 | *
195 | * @return string[] List of Declare SQL, output SQL and select SQL.
196 | */
197 | private function prepareReturningParts(
198 | TableSchema $tableSchema,
199 | array $returnColumns,
200 | ): array {
201 | $createdCols = [];
202 | $insertedCols = [];
203 | $columns = array_intersect_key($tableSchema->getColumns(), array_fill_keys($returnColumns, true));
204 |
205 | $columnDefinitionBuilder = $this->queryBuilder->getColumnDefinitionBuilder();
206 |
207 | foreach ($columns as $name => $column) {
208 | if ($column->getDbType() === 'timestamp') {
209 | $dbType = $column->isNotNull() ? 'binary(8)' : 'varbinary(8)';
210 | } else {
211 | $dbType = $columnDefinitionBuilder->buildType($column);
212 | }
213 |
214 | $quotedName = $this->quoter->quoteColumnName($name);
215 | $createdCols[] = $quotedName . ' ' . $dbType . ($column->isNotNull() ? '' : ' NULL');
216 | $insertedCols[] = 'INSERTED.' . $quotedName;
217 | }
218 |
219 | return [
220 | 'SET NOCOUNT ON;DECLARE @temporary_inserted TABLE (' . implode(', ', $createdCols) . ');',
221 | ' OUTPUT ' . implode(',', $insertedCols) . ' INTO @temporary_inserted',
222 | 'SELECT * FROM @temporary_inserted;',
223 | ];
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/DQLQueryBuilder.php:
--------------------------------------------------------------------------------
1 | buildOrderBy($orderBy, $params);
33 |
34 | return $orderByString === '' ? $sql : $sql . $this->separator . $orderByString;
35 | }
36 |
37 | return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset, $params);
38 | }
39 |
40 | public function selectExists(string $rawSql): string
41 | {
42 | return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END AS [0]';
43 | }
44 |
45 | protected function defaultExpressionBuilders(): array
46 | {
47 | return [
48 | ...parent::defaultExpressionBuilders(),
49 | InCondition::class => InConditionBuilder::class,
50 | LikeCondition::class => LikeConditionBuilder::class,
51 | ];
52 | }
53 |
54 | /**
55 | * Builds the `ORDER BY`/`LIMIT`/`OFFSET` clauses for SQL SERVER 2012 or newer.
56 | *
57 | * @param string $sql The existing SQL (without `ORDER BY`/`LIMIT`/`OFFSET`).
58 | * @param array $orderBy The order by columns. See {@see Query::orderBy} for more details on how to specify
59 | * this parameter.
60 | * @param ExpressionInterface|int|null $limit The limit number. See {@see Query::limit} for more details.
61 | * @param ExpressionInterface|int|null $offset The offset number. See {@see Query::offset} for more details.
62 | * @param array $params The binding parameters to populate.
63 | *
64 | * @throws Exception
65 | * @throws InvalidArgumentException
66 | *
67 | * @return string The SQL completed with `ORDER BY`/`LIMIT`/`OFFSET` (if any).
68 | */
69 | protected function newBuildOrderByAndLimit(
70 | string $sql,
71 | array $orderBy,
72 | ExpressionInterface|int|null $limit,
73 | ExpressionInterface|int|null $offset,
74 | array &$params = []
75 | ): string {
76 | $orderByString = $this->buildOrderBy($orderBy, $params);
77 |
78 | if ($orderByString === '') {
79 | /** `ORDER BY` clause is required when `FETCH` and `OFFSET` are in the SQL */
80 | $orderByString = 'ORDER BY (SELECT NULL)';
81 | }
82 |
83 | $sql .= $this->separator . $orderByString;
84 |
85 | /**
86 | * @link https://technet.microsoft.com/en-us/library/gg699618.aspx
87 | */
88 | $offsetString = !empty($offset)
89 | ? ($offset instanceof ExpressionInterface ? $this->buildExpression($offset) : (string) $offset)
90 | : '0';
91 | $sql .= $this->separator . 'OFFSET ' . $offsetString . ' ROWS';
92 |
93 | if ($limit !== null) {
94 | $sql .= $this->separator . 'FETCH NEXT '
95 | . ($limit instanceof ExpressionInterface ? $this->buildExpression($limit) : (string) $limit)
96 | . ' ROWS ONLY';
97 | }
98 |
99 | return $sql;
100 | }
101 |
102 | /**
103 | * Extracts table alias if there is one or returns `false`.
104 | *
105 | * @psalm-return string[]|bool
106 | */
107 | protected function extractAlias(string $table): array|bool
108 | {
109 | if (preg_match('/^\[.*]$/', $table)) {
110 | return false;
111 | }
112 |
113 | return parent::extractAlias($table);
114 | }
115 |
116 | public function buildWithQueries(array $withs, array &$params): string
117 | {
118 | /** @psalm-var array{query:string|Query, alias:ExpressionInterface|string, recursive:bool}[] $withs */
119 | foreach ($withs as &$with) {
120 | $with['recursive'] = false;
121 | }
122 |
123 | return parent::buildWithQueries($withs, $params);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Driver.php:
--------------------------------------------------------------------------------
1 | attributes += [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
20 | return parent::createConnection();
21 | }
22 |
23 | public function getDriverName(): string
24 | {
25 | return 'sqlsrv';
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Dsn.php:
--------------------------------------------------------------------------------
1 | $options
18 | */
19 | public function __construct(
20 | string $driver = 'sqlsrv',
21 | string $host = '127.0.0.1',
22 | string|null $databaseName = null,
23 | string|null $port = '1433',
24 | array $options = []
25 | ) {
26 | parent::__construct($driver, $host, $databaseName, $port, $options);
27 | }
28 |
29 | /**
30 | * @return string the Data Source Name, or DSN, has the information required to connect to the database.
31 | * Please refer to the [PHP manual](https://php.net/manual/en/pdo.construct.php) on the format of the DSN string.
32 | *
33 | * The `driver` array key is used as the driver prefix of the DSN, all further key-value pairs are rendered as
34 | * `key=value` and concatenated by `;`. For example:
35 | *
36 | * ```php
37 | * $dsn = new Dsn('sqlsrv', 'localhost', 'yiitest', '1433');
38 | * $driver = new Driver($dsn->asString(), 'username', 'password');
39 | * $db = new Connection($driver, $schemaCache);
40 | * ```
41 | *
42 | * Will result in the DSN string `sqlsrv:Server=localhost,1433;Database=yiitest`.
43 | */
44 | public function asString(): string
45 | {
46 | $driver = $this->getDriver();
47 | $host = $this->getHost();
48 | $port = $this->getPort();
49 | $databaseName = $this->getDatabaseName();
50 | $options = $this->getOptions();
51 |
52 | $dsn = "$driver:Server=$host";
53 |
54 | if (!empty($port)) {
55 | $dsn .= ",$port";
56 | }
57 |
58 | if (!empty($databaseName)) {
59 | $dsn .= ";Database=$databaseName";
60 | }
61 |
62 | if (!empty($options)) {
63 | foreach ($options as $key => $value) {
64 | $dsn .= ";$key=$value";
65 | }
66 | }
67 |
68 | return $dsn;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/IndexMethod.php:
--------------------------------------------------------------------------------
1 | getQuoter();
23 | $schema = $db->getSchema();
24 |
25 | parent::__construct(
26 | $db,
27 | new DDLQueryBuilder($this, $quoter, $schema),
28 | new DMLQueryBuilder($this, $quoter, $schema),
29 | new DQLQueryBuilder($this, $quoter),
30 | new ColumnDefinitionBuilder($this),
31 | );
32 | }
33 |
34 | protected function createSqlParser(string $sql): SqlParser
35 | {
36 | return new SqlParser($sql);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Quoter.php:
--------------------------------------------------------------------------------
1 | 0) {
31 | $parts = array_slice($matches[0], -4, 4);
32 |
33 | return array_map($this->unquoteSimpleTableName(...), $parts);
34 | }
35 |
36 | return [$this->unquoteSimpleTableName($name)];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/SqlParser.php:
--------------------------------------------------------------------------------
1 | length - 1;
15 |
16 | while ($this->position < $length) {
17 | $pos = $this->position++;
18 |
19 | match ($this->sql[$pos]) {
20 | ':' => ($word = $this->parseWord()) === ''
21 | ? $this->skipChars(':')
22 | : $result = ':' . $word,
23 | '"', "'" => $this->skipQuotedWithoutEscape($this->sql[$pos]),
24 | '[' => $this->sql[$this->position] === '['
25 | ? $this->skipToAfterString(']]')
26 | : $this->skipQuotedWithoutEscape(']'),
27 | '-' => $this->sql[$this->position] === '-'
28 | ? ++$this->position && $this->skipToAfterChar("\n")
29 | : null,
30 | '/' => $this->sql[$this->position] === '*'
31 | ? ++$this->position && $this->skipToAfterString('*/')
32 | : null,
33 | default => null,
34 | };
35 |
36 | if ($result !== null) {
37 | $position = $pos;
38 |
39 | return $result;
40 | }
41 | }
42 |
43 | return null;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/TableSchema.php:
--------------------------------------------------------------------------------
1 | db->createCommand("SAVE TRANSACTION $name")->execute();
27 | }
28 |
29 | /**
30 | * Releases an existing savepoint.
31 | *
32 | * @param string $name the savepoint name.
33 | */
34 | public function releaseSavepoint(string $name): void
35 | {
36 | // does nothing as MSSQL doesn't support this
37 | }
38 |
39 | /**
40 | * Rolls back to a before created savepoint.
41 | *
42 | * @param string $name the savepoint name.
43 | *
44 | * @throws Exception|InvalidConfigException|Throwable
45 | */
46 | public function rollBackSavepoint(string $name): void
47 | {
48 | $this->db->createCommand("ROLLBACK TRANSACTION $name")->execute();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/.env:
--------------------------------------------------------------------------------
1 | ENVIRONMENT=local
2 | YII_MSSQL_DATABASE=tempdb
3 | YII_MSSQL_HOST=mssql
4 | YII_MSSQL_PORT=1433
5 | YII_MSSQL_USER=SA
6 | YII_MSSQL_PASSWORD=YourStrong!Passw0rd
7 |
--------------------------------------------------------------------------------
/tests/Builder/InconditionBuilderTest.php:
--------------------------------------------------------------------------------
1 | getConnection();
34 | $inCondition = new InCondition(
35 | ['id'],
36 | 'in',
37 | (new Query($db))->select('id')->from('users')->where(['active' => 1]),
38 | );
39 |
40 | $this->expectException(NotSupportedException::class);
41 | $this->expectExceptionMessage(
42 | 'Yiisoft\Db\Mssql\Builder\InConditionBuilder::buildSubqueryInCondition is not supported by MSSQL.'
43 | );
44 |
45 | (new InConditionBuilder($db->getQueryBuilder()))->build($inCondition);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/ColumnBuilderTest.php:
--------------------------------------------------------------------------------
1 | createCommand()->insert(
40 | 'type',
41 | [
42 | 'int_col' => 1,
43 | 'char_col' => str_repeat('x', 100),
44 | 'char_col3' => null,
45 | 'float_col' => 1.234,
46 | 'blob_col' => "\x10\x11\x12",
47 | 'datetime_col' => '2023-07-11 14:50:00.123',
48 | 'bool_col' => false,
49 | 'json_col' => [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]],
50 | ]
51 | )->execute();
52 | }
53 |
54 | private function assertTypecastedValues(array $result, bool $allTypecasted = false): void
55 | {
56 | $this->assertSame(1, $result['int_col']);
57 | $this->assertSame(str_repeat('x', 100), $result['char_col']);
58 | $this->assertNull($result['char_col3']);
59 | $this->assertSame(1.234, $result['float_col']);
60 | $this->assertSame("\x10\x11\x12", $result['blob_col']);
61 | $this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:00.123', new DateTimeZone('UTC')), $result['datetime_col']);
62 | $this->assertFalse($result['bool_col']);
63 |
64 | if ($allTypecasted) {
65 | $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $result['json_col']);
66 | } else {
67 | $this->assertSame('[{"a":1,"b":null,"c":[1,3,5]}]', $result['json_col']);
68 | }
69 | }
70 |
71 | public function testQueryWithTypecasting(): void
72 | {
73 | $db = $this->getConnection(true);
74 |
75 | $this->insertTypeValues($db);
76 |
77 | $query = (new Query($db))->from('type')->withTypecasting();
78 |
79 | $result = $query->one();
80 |
81 | $this->assertTypecastedValues($result);
82 |
83 | $result = $query->all();
84 |
85 | $this->assertTypecastedValues($result[0]);
86 |
87 | $db->close();
88 | }
89 |
90 | public function testCommandWithPhpTypecasting(): void
91 | {
92 | $db = $this->getConnection(true);
93 |
94 | $this->insertTypeValues($db);
95 |
96 | $command = $db->createCommand('SELECT * FROM type')->withPhpTypecasting();
97 |
98 | $result = $command->queryOne();
99 |
100 | $this->assertTypecastedValues($result);
101 |
102 | $result = $command->queryAll();
103 |
104 | $this->assertTypecastedValues($result[0]);
105 |
106 | $db->close();
107 | }
108 |
109 | public function testSelectWithPhpTypecasting(): void
110 | {
111 | $db = $this->getConnection();
112 |
113 | $sql = "SELECT null AS [null], 1 AS [1], 2.5 AS [2.5], 'string' AS [string]";
114 |
115 | $expected = [
116 | 'null' => null,
117 | 1 => 1,
118 | '2.5' => 2.5,
119 | 'string' => 'string',
120 | ];
121 |
122 | $result = $db->createCommand($sql)
123 | ->withPhpTypecasting()
124 | ->queryOne();
125 |
126 | $this->assertSame($expected, $result);
127 |
128 | $result = $db->createCommand($sql)
129 | ->withPhpTypecasting()
130 | ->queryAll();
131 |
132 | $this->assertSame([$expected], $result);
133 |
134 | $result = $db->createCommand('SELECT 2.5')
135 | ->withPhpTypecasting()
136 | ->queryScalar();
137 |
138 | $this->assertSame(2.5, $result);
139 |
140 | $result = $db->createCommand('SELECT 2.5 UNION SELECT 3.3')
141 | ->withPhpTypecasting()
142 | ->queryColumn();
143 |
144 | $this->assertSame([2.5, 3.3], $result);
145 |
146 | $db->close();
147 | }
148 |
149 | public function testPhpTypeCast(): void
150 | {
151 | $db = $this->getConnection(true);
152 | $schema = $db->getSchema();
153 | $columns = $schema->getTableSchema('type')->getColumns();
154 |
155 | $this->insertTypeValues($db);
156 |
157 | $query = (new Query($db))->from('type')->one();
158 |
159 | $result = [];
160 |
161 | foreach ($columns as $columnName => $column) {
162 | $result[$columnName] = $column->phpTypecast($query[$columnName]);
163 | }
164 |
165 | $this->assertTypecastedValues($result, true);
166 |
167 | $db->close();
168 | }
169 |
170 | public function testColumnInstance()
171 | {
172 | $db = $this->getConnection(true);
173 | $schema = $db->getSchema();
174 | $tableSchema = $schema->getTableSchema('type');
175 |
176 | $this->assertInstanceOf(IntegerColumn::class, $tableSchema->getColumn('int_col'));
177 | $this->assertInstanceOf(StringColumn::class, $tableSchema->getColumn('char_col'));
178 | $this->assertInstanceOf(DoubleColumn::class, $tableSchema->getColumn('float_col'));
179 | $this->assertInstanceOf(BinaryColumn::class, $tableSchema->getColumn('blob_col'));
180 | $this->assertInstanceOf(BooleanColumn::class, $tableSchema->getColumn('bool_col'));
181 | }
182 |
183 | #[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')]
184 | public function testPredefinedType(string $className, string $type, string $phpType)
185 | {
186 | parent::testPredefinedType($className, $type, $phpType);
187 | }
188 |
189 | #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')]
190 | public function testDbTypecastColumns(ColumnInterface $column, array $values)
191 | {
192 | parent::testDbTypecastColumns($column, $values);
193 | }
194 |
195 | #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')]
196 | public function testPhpTypecastColumns(ColumnInterface $column, array $values)
197 | {
198 | parent::testPhpTypecastColumns($column, $values);
199 | }
200 |
201 | public function testBinaryColumn()
202 | {
203 | $binaryCol = new BinaryColumn();
204 | $binaryCol->dbType('varbinary');
205 |
206 | $this->assertEquals(
207 | new Expression('CONVERT(VARBINARY(MAX), 0x101112)'),
208 | $binaryCol->dbTypecast("\x10\x11\x12"),
209 | );
210 | $this->assertEquals(
211 | new Expression('CONVERT(VARBINARY(MAX), 0x101112)'),
212 | $binaryCol->dbTypecast(new Param("\x10\x11\x12", PDO::PARAM_LOB)),
213 | );
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/tests/ConnectionTest.php:
--------------------------------------------------------------------------------
1 | getConnection(true);
38 |
39 | $transaction = $db->beginTransaction(TransactionInterface::READ_UNCOMMITTED);
40 | $transaction->commit();
41 |
42 | $transaction = $db->beginTransaction(TransactionInterface::READ_COMMITTED);
43 | $transaction->commit();
44 |
45 | $transaction = $db->beginTransaction(TransactionInterface::REPEATABLE_READ);
46 | $transaction->commit();
47 |
48 | $transaction = $db->beginTransaction(TransactionInterface::SERIALIZABLE);
49 | $transaction->commit();
50 |
51 | /* should not be any exception so far */
52 | $this->assertTrue(true);
53 | }
54 |
55 | /**
56 | * @throws Exception
57 | * @throws InvalidConfigException
58 | * @throws Throwable
59 | */
60 | public function testTransactionShortcutCustom(): void
61 | {
62 | $db = $this->getConnection();
63 |
64 | $result = $db->transaction(
65 | static function (PdoConnectionInterface $db): bool {
66 | $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
67 | return true;
68 | },
69 | TransactionInterface::READ_UNCOMMITTED,
70 | );
71 |
72 | $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
73 |
74 | $profilesCount = $db->createCommand(
75 | <<queryScalar();
79 |
80 | $this->assertSame('1', $profilesCount, 'profile should be inserted in transaction shortcut');
81 | }
82 |
83 | /**
84 | * @throws Exception
85 | * @throws InvalidConfigException
86 | */
87 | public function testSettingDefaultAttributes(): void
88 | {
89 | $db = $this->getConnection();
90 |
91 | $this->assertSame(PDO::ERRMODE_EXCEPTION, $db->getActivePDO()?->getAttribute(PDO::ATTR_ERRMODE));
92 | }
93 |
94 | public function testGetColumnFactory(): void
95 | {
96 | $db = $this->getConnection();
97 |
98 | $this->assertInstanceOf(ColumnFactory::class, $db->getColumnFactory());
99 |
100 | $db->close();
101 | }
102 |
103 | public function testUserDefinedColumnFactory(): void
104 | {
105 | $columnFactory = new ColumnFactory();
106 |
107 | $db = new Connection($this->getDriver(), DbHelper::getSchemaCache(), $columnFactory);
108 |
109 | $this->assertSame($columnFactory, $db->getColumnFactory());
110 |
111 | $db->close();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/DsnTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
20 | 'sqlsrv:Server=127.0.0.1,1433;Database=yiitest',
21 | (new Dsn('sqlsrv', '127.0.0.1', 'yiitest', '1433'))->asString(),
22 | );
23 | }
24 |
25 | public function testAsStringWithDatabaseName(): void
26 | {
27 | $this->assertSame('sqlsrv:Server=127.0.0.1,1433', (new Dsn('sqlsrv', '127.0.0.1'))->asString());
28 | }
29 |
30 | public function testAsStringWithDatabaseNameWithEmptyString(): void
31 | {
32 | $this->assertSame('sqlsrv:Server=127.0.0.1,1433', (new Dsn('sqlsrv', '127.0.0.1', ''))->asString());
33 | }
34 |
35 | public function testAsStringWithDatabaseNameWithNull(): void
36 | {
37 | $this->assertSame('sqlsrv:Server=127.0.0.1,1433', (new Dsn('sqlsrv', '127.0.0.1', null))->asString());
38 | }
39 |
40 | public function testAsStringWithEmptyPort(): void
41 | {
42 | $this->assertSame(
43 | 'sqlsrv:Server=127.0.0.1;Database=yiitest',
44 | (new Dsn('sqlsrv', '127.0.0.1', 'yiitest', ''))->asString(),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/PdoCommandTest.php:
--------------------------------------------------------------------------------
1 | getConnection();
33 |
34 | // One sequence, two tables
35 | $tableName1 = 'seqtable1';
36 | $tableName2 = 'seqtable2';
37 | $sequenceName = 'sequence1';
38 |
39 | $command = $db->createCommand();
40 |
41 | if ($db->getSchema()->getTableSchema($tableName1) !== null) {
42 | $command->dropTable($tableName1)->execute();
43 | }
44 |
45 | if ($db->getSchema()->getTableSchema($tableName2) !== null) {
46 | $command->dropTable($tableName2)->execute();
47 | }
48 |
49 | $command->setSql(
50 | <<execute();
54 |
55 | $command->setSql(
56 | <<execute();
60 | $command->setSql(
61 | <<execute();
65 | $command->setSql(
66 | <<execute();
70 | $command->insert(
71 | $tableName1,
72 | ['seqnum' => new Expression("NEXT VALUE FOR $sequenceName"), 'SomeNumber' => 20],
73 | )->execute();
74 | $command->insert(
75 | $tableName1,
76 | ['seqnum' => new Expression("NEXT VALUE FOR $sequenceName"), 'SomeNumber' => 40],
77 | )->execute();
78 | $command->insert(
79 | $tableName1,
80 | ['seqnum' => new Expression("NEXT VALUE FOR $sequenceName"), 'SomeNumber' => 60],
81 | )->execute();
82 | $command->insert($tableName2, ['SomeValue' => 20])->execute();
83 |
84 | // Return the last inserted ID for the table with a sequence
85 | $this->assertSame('3', $db->getLastInsertId($sequenceName));
86 |
87 | // Return the last inserted ID for the table with an identity column
88 | $this->assertSame('1', $db->getLastInsertId());
89 |
90 | // Return empty sting
91 | $this->assertEmpty($db->getLastInsertId($tableName1));
92 |
93 | // Return empty sting
94 | $this->assertEmpty($db->getLastInsertId($tableName2));
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Provider/ColumnBuilderProvider.php:
--------------------------------------------------------------------------------
1 | '1',
23 | 'email' => 'user1@example.com',
24 | 'name' => 'user1',
25 | 'address' => 'address1',
26 | 'status' => '1',
27 | 'profile_id' => '1',
28 | ],
29 | ],
30 | [
31 | 'id',
32 | ':id',
33 | 1,
34 | PDO::PARAM_INT,
35 | 0,
36 | null,
37 | [
38 | 'id' => '1',
39 | 'email' => 'user1@example.com',
40 | 'name' => 'user1',
41 | 'address' => 'address1',
42 | 'status' => '1',
43 | 'profile_id' => '1',
44 | ],
45 | ],
46 | [
47 | 'status',
48 | ':status',
49 | 2,
50 | PDO::PARAM_STR,
51 | 0,
52 | PDO::SQLSRV_ENCODING_UTF8,
53 | [
54 | 'id' => '3',
55 | 'email' => 'user3@example.com',
56 | 'name' => 'user3',
57 | 'address' => 'address3',
58 | 'status' => '2',
59 | 'profile_id' => '2',
60 | ],
61 | ],
62 | ];
63 | }
64 |
65 | public static function bindParamsNonWhere(): array
66 | {
67 | return[
68 | [
69 | << 'string', 'integer' => 1234], JSON_THROW_ON_ERROR),
50 | json_encode(['string' => 'string', 'integer' => 1234], JSON_THROW_ON_ERROR),
51 | ],
52 | [
53 | serialize(['string' => 'string', 'integer' => 1234]),
54 | new Param(serialize(['string' => 'string', 'integer' => 1234]), PDO::PARAM_LOB),
55 | ],
56 | [
57 | 'simple string',
58 | 'simple string',
59 | ],
60 | ];
61 | }
62 |
63 | public static function rawSql(): array
64 | {
65 | $rawSql = parent::rawSql();
66 |
67 | foreach ($rawSql as &$values) {
68 | $values[2] = strtr($values[2], [
69 | 'FALSE' => '0',
70 | 'TRUE' => '1',
71 | ]);
72 | }
73 |
74 | return $rawSql;
75 | }
76 |
77 | public static function createIndex(): array
78 | {
79 | return [
80 | ...parent::createIndex(),
81 | [['col1' => ColumnBuilder::integer()], ['col1'], IndexType::CLUSTERED, null],
82 | [['col1' => ColumnBuilder::integer()], ['col1'], IndexType::NONCLUSTERED, null],
83 | [['col1' => ColumnBuilder::integer()], ['col1'], IndexType::UNIQUE, null],
84 | [['col1' => ColumnBuilder::integer()], ['col1'], IndexType::UNIQUE_CLUSTERED, null],
85 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'geometry'], ['col1'], IndexType::SPATIAL, IndexMethod::GEOMETRY_GRID . ' WITH(BOUNDING_BOX = (0, 0, 100, 100))'],
86 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'geometry'], ['col1'], IndexType::SPATIAL, IndexMethod::GEOMETRY_AUTO_GRID . ' WITH(BOUNDING_BOX = (0, 0, 100, 100))'],
87 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'geography'], ['col1'], IndexType::SPATIAL, null],
88 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'geography'], ['col1'], IndexType::SPATIAL, IndexMethod::GEOGRAPHY_GRID],
89 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'geography'], ['col1'], IndexType::SPATIAL, IndexMethod::GEOGRAPHY_AUTO_GRID],
90 | [['col1' => ColumnBuilder::integer()], ['col1'], IndexType::COLUMNSTORE, null],
91 | [['id' => ColumnBuilder::primaryKey(), 'col1' => 'xml'], ['col1'], IndexType::PRIMARY_XML, null],
92 | ];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Provider/QuoterProvider.php:
--------------------------------------------------------------------------------
1 | getConnection(true);
33 |
34 | $selectExpression = 'CONCAT(customer.name, \' in \', p.description) name';
35 |
36 | $result = (new Query($db))
37 | ->select([$selectExpression])
38 | ->from('customer')
39 | ->innerJoin('profile p', '[[customer]].[[profile_id]] = [[p]].[[id]]')
40 | ->indexBy('id')
41 | ->column();
42 |
43 | $this->assertSame([1 => 'user1 in profile customer 1', 3 => 'user3 in profile customer 3'], $result);
44 | }
45 |
46 | /**
47 | * @throws Exception
48 | * @throws InvalidConfigException
49 | * @throws Throwable
50 | */
51 | public function testUnion(): void
52 | {
53 | $db = $this->getConnection();
54 |
55 | $subQueryFromItem = (new Query($db))->select(['id', 'name'])->from('item')->limit(2);
56 | $subQueryFromCategory = (new Query($db))->select(['id', 'name'])->from(['category'])->limit(2);
57 | $subQueryUnion = (new Query($db))->select(['id', 'name'])->from($subQueryFromCategory);
58 |
59 | $query = (new Query($db))->select(['id', 'name'])->from($subQueryFromItem)->union($subQueryUnion);
60 | $data = $query->all();
61 |
62 | $this->assertCount(4, $data);
63 | }
64 |
65 | #[DataProvider('dataLikeCaseSensitive')]
66 | public function testLikeCaseSensitive(mixed $expected, string $value): void
67 | {
68 | $db = $this->getConnection(true);
69 |
70 | $query = (new Query($db))
71 | ->select('name')
72 | ->from('customer')
73 | ->where(['like', 'name', $value, 'caseSensitive' => true]);
74 |
75 | $this->expectException(NotSupportedException::class);
76 | $this->expectExceptionMessage('MSSQL doesn\'t support case-sensitive "LIKE" conditions.');
77 | $query->scalar();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/QuoterTest.php:
--------------------------------------------------------------------------------
1 | getConnection(true);
49 | $schema = $db->getSchema();
50 | $tUpsert = $schema->getTableSchema('T_upsert');
51 |
52 | $this->assertInstanceOf(TableSchemaInterface::class, $tUpsert);
53 | $this->assertContains([0 => 'email', 1 => 'recovery_email'], $schema->findUniqueIndexes($tUpsert));
54 | }
55 |
56 | public function testGetDefaultSchema(): void
57 | {
58 | $db = $this->getConnection();
59 |
60 | $schema = $db->getSchema();
61 |
62 | $this->assertSame('dbo', $schema->getDefaultSchema());
63 | }
64 |
65 | /**
66 | * @throws NotSupportedException
67 | */
68 | public function testGetSchemaNames(): void
69 | {
70 | $db = $this->getConnection(true);
71 |
72 | $schema = $db->getSchema();
73 | $schemas = $schema->getSchemaNames();
74 |
75 | $this->assertSame(['dbo', 'guest'], $schemas);
76 | }
77 |
78 | public function testGetViewNames(): void
79 | {
80 | $db = $this->getConnection(true);
81 |
82 | $schema = $db->getSchema();
83 |
84 | $this->assertSame(['animal_view'], $schema->getViewNames());
85 | $this->assertSame(['animal_view'], $schema->getViewNames('dbo'));
86 | $this->assertSame(['animal_view'], $schema->getViewNames('dbo', true));
87 | }
88 |
89 | /**
90 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\SchemaProvider::constraints
91 | *
92 | * @throws Exception
93 | */
94 | public function testTableSchemaConstraints(string $tableName, string $type, mixed $expected): void
95 | {
96 | parent::testTableSchemaConstraints($tableName, $type, $expected);
97 | }
98 |
99 | /**
100 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\SchemaProvider::constraints
101 | *
102 | * @throws Exception
103 | */
104 | public function testTableSchemaConstraintsWithPdoLowercase(string $tableName, string $type, mixed $expected): void
105 | {
106 | parent::testTableSchemaConstraintsWithPdoLowercase($tableName, $type, $expected);
107 | }
108 |
109 | /**
110 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\SchemaProvider::constraints
111 | *
112 | * @throws Exception
113 | */
114 | public function testTableSchemaConstraintsWithPdoUppercase(string $tableName, string $type, mixed $expected): void
115 | {
116 | parent::testTableSchemaConstraintsWithPdoUppercase($tableName, $type, $expected);
117 | }
118 |
119 | /**
120 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\SchemaProvider::tableSchemaWithDbSchemes
121 | */
122 | public function testTableSchemaWithDbSchemes(string $tableName, string $expectedTableName): void
123 | {
124 | $db = $this->getConnection();
125 |
126 | $commandMock = $this->createMock(CommandInterface::class);
127 | $commandMock->method('queryAll')->willReturn([]);
128 | $mockDb = $this->createMock(PdoConnectionInterface::class);
129 | $mockDb->method('getQuoter')->willReturn($db->getQuoter());
130 | $mockDb
131 | ->method('createCommand')
132 | ->with(
133 | self::callback(static fn ($sql) => true),
134 | self::callback(
135 | function ($params) use ($expectedTableName) {
136 | $this->assertEquals($expectedTableName, $params[':fullName']);
137 |
138 | return true;
139 | }
140 | )
141 | )
142 | ->willReturn($commandMock);
143 | $schema = new Schema($mockDb, DbHelper::getSchemaCache());
144 | $schema->getTablePrimaryKey($tableName);
145 | }
146 |
147 | public function withIndexDataProvider(): array
148 | {
149 | return [
150 | ...parent::withIndexDataProvider(),
151 | [
152 | 'indexType' => SchemaInterface::INDEX_CLUSTERED,
153 | 'indexMethod' => null,
154 | 'columnType' => 'varchar(16)',
155 | ],
156 | [
157 | 'indexType' => SchemaInterface::INDEX_NONCLUSTERED,
158 | 'indexMethod' => null,
159 | 'columnType' => 'varchar(16)',
160 | ],
161 | ];
162 | }
163 |
164 | public function testNotConnectionPDO(): void
165 | {
166 | $db = $this->createMock(ConnectionInterface::class);
167 | $schema = new Schema($db, DbHelper::getSchemaCache());
168 |
169 | $this->expectException(NotSupportedException::class);
170 | $this->expectExceptionMessage('Only PDO connections are supported.');
171 |
172 | $schema->refresh();
173 | }
174 |
175 | public function testNegativeDefaultValues(): void
176 | {
177 | $db = $this->getConnection(true);
178 |
179 | $schema = $db->getSchema();
180 | $table = $schema->getTableSchema('negative_default_values');
181 |
182 | $this->assertNotNull($table);
183 | $this->assertSame(-123, $table->getColumn('smallint_col')?->getDefaultValue());
184 | $this->assertSame(-123, $table->getColumn('int_col')?->getDefaultValue());
185 | $this->assertSame(-123, $table->getColumn('bigint_col')?->getDefaultValue());
186 | $this->assertSame(-12345.6789, $table->getColumn('float_col')?->getDefaultValue());
187 | $this->assertEquals(-33.22, $table->getColumn('numeric_col')?->getDefaultValue());
188 |
189 | $db->close();
190 | }
191 |
192 | #[DataProviderExternal(SchemaProvider::class, 'resultColumns')]
193 | public function testGetResultColumn(ColumnInterface|null $expected, array $info): void
194 | {
195 | parent::testGetResultColumn($expected, $info);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/SqlParserTest.php:
--------------------------------------------------------------------------------
1 | getDriver(), DbHelper::getSchemaCache());
26 |
27 | if ($fixture) {
28 | DbHelper::loadFixture($db, __DIR__ . "/Fixture/$this->fixture");
29 | }
30 |
31 | return $db;
32 | }
33 |
34 | protected static function getDb(): Connection
35 | {
36 | $dsn = (new Dsn(
37 | host: self::getHost(),
38 | databaseName: self::getDatabaseName(),
39 | port: self::getPort(),
40 | options: ['Encrypt' => 'no']
41 | ))->asString();
42 |
43 | return new Connection(
44 | new Driver($dsn, self::getUsername(), self::getPassword()),
45 | DbHelper::getSchemaCache(),
46 | );
47 | }
48 |
49 | protected function getDsn(): string
50 | {
51 | if ($this->dsn === '') {
52 | $this->dsn = (new Dsn(
53 | host: self::getHost(),
54 | databaseName: self::getDatabaseName(),
55 | port: self::getPort(),
56 | options: ['Encrypt' => 'no']
57 | ))->asString();
58 | }
59 |
60 | return $this->dsn;
61 | }
62 |
63 | protected function getDriverName(): string
64 | {
65 | return 'sqlsrv';
66 | }
67 |
68 | protected function setDsn(string $dsn): void
69 | {
70 | $this->dsn = $dsn;
71 | }
72 |
73 | protected function setFixture(string $fixture): void
74 | {
75 | $this->fixture = $fixture;
76 | }
77 |
78 | protected function getDriver(): Driver
79 | {
80 | return new Driver($this->getDsn(), self::getUsername(), self::getPassword());
81 | }
82 |
83 | private static function getDatabaseName(): string
84 | {
85 | return getenv('YII_MSSQL_DATABASE') ?: 'yiitest';
86 | }
87 |
88 | private static function getHost(): string
89 | {
90 | return getenv('YII_MSSQL_HOST') ?: '127.0.0.1';
91 | }
92 |
93 | private static function getPort(): string
94 | {
95 | return getenv('YII_MSSQL_PORT') ?: '1433';
96 | }
97 |
98 | private static function getUsername(): string
99 | {
100 | return getenv('YII_MSSQL_USER') ?: 'SA';
101 | }
102 |
103 | private static function getPassword(): string
104 | {
105 | return getenv('YII_MSSQL_PASSWORD') ?: 'YourStrong!Passw0rd';
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/Type/BigIntTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
37 |
38 | $tableSchema = $db->getTableSchema('bigint_default');
39 |
40 | $this->assertSame('bigint', $tableSchema?->getColumn('Mybigint')->getDbType());
41 | $this->assertSame('int', $tableSchema?->getColumn('Mybigint')->getPhpType());
42 | $this->assertSame(9_223_372_036_854_775_807, $tableSchema?->getColumn('Mybigint')->getDefaultValue());
43 |
44 | $db->createCommand()->dropTable('bigint_default')->execute();
45 | }
46 |
47 | /**
48 | * @throws Exception
49 | * @throws InvalidConfigException
50 | * @throws InvalidArgumentException
51 | * @throws NotSupportedException
52 | * @throws Throwable
53 | */
54 | public function testCreateTableWithInsert(): void
55 | {
56 | $db = $this->buildTable();
57 |
58 | $command = $db->createCommand();
59 | $command->insert('bigint_default', [])->execute();
60 |
61 | $this->assertSame(
62 | $this->getColumns(),
63 | $command->setSql(
64 | <<queryOne(),
68 | );
69 |
70 | $db->createCommand()->dropTable('bigint_default')->execute();
71 | }
72 |
73 | /**
74 | * @throws Exception
75 | * @throws InvalidConfigException
76 | * @throws InvalidArgumentException
77 | * @throws NotSupportedException
78 | * @throws Throwable
79 | */
80 | public function testDefaultValue(): void
81 | {
82 | $this->setFixture('Type/bigint.sql');
83 |
84 | $db = $this->getConnection(true);
85 | $tableSchema = $db->getTableSchema('bigint_default');
86 |
87 | $this->assertSame('bigint', $tableSchema?->getColumn('Mybigint')->getDbType());
88 | $this->assertSame('int', $tableSchema?->getColumn('Mybigint')->getPhpType());
89 | $this->assertSame(9_223_372_036_854_775_807, $tableSchema?->getColumn('Mybigint')->getDefaultValue());
90 |
91 | $db->createCommand()->dropTable('bigint_default')->execute();
92 | }
93 |
94 | /**
95 | * @throws Exception
96 | * @throws InvalidConfigException
97 | * @throws InvalidArgumentException
98 | * @throws NotSupportedException
99 | * @throws Throwable
100 | */
101 | public function testDefaultValueWithInsert(): void
102 | {
103 | $this->setFixture('Type/bigint.sql');
104 |
105 | $db = $this->getConnection(true);
106 | $command = $db->createCommand();
107 | $command->insert('bigint_default', [])->execute();
108 |
109 | $this->assertSame(
110 | $this->getColumns(),
111 | $command->setSql(
112 | <<queryOne(),
116 | );
117 |
118 | $db->createCommand()->dropTable('bigint_default')->execute();
119 | }
120 |
121 | /**
122 | * Max value is `9223372036854775807`, but when the value is greater than `9223372036854775807` it is out of range
123 | * and save as `9223372036854775807`.
124 | *
125 | * @throws Exception
126 | * @throws InvalidConfigException
127 | * @throws InvalidArgumentException
128 | * @throws NotSupportedException
129 | * @throws Throwable
130 | */
131 | public function testMaxValue(): void
132 | {
133 | $this->setFixture('Type/bigint.sql');
134 |
135 | $db = $this->getConnection(true);
136 | $command = $db->createCommand();
137 | $command->insert('bigint', ['Mybigint1' => '9223372036854775807', 'Mybigint2' => '0'])->execute();
138 |
139 | $this->assertSame(
140 | [
141 | 'id' => '1',
142 | 'Mybigint1' => '9223372036854775807',
143 | 'Mybigint2' => '0',
144 | ],
145 | $command->setSql(
146 | <<queryOne()
150 | );
151 |
152 | $command->insert('bigint', ['Mybigint1' => '9223372036854775808', 'Mybigint2' => null])->execute();
153 |
154 | $this->assertSame(
155 | [
156 | 'id' => '2',
157 | 'Mybigint1' => '9223372036854775807',
158 | 'Mybigint2' => null,
159 | ],
160 | $command->setSql(
161 | <<queryOne()
165 | );
166 |
167 | $db->createCommand()->dropTable('bigint')->execute();
168 | }
169 |
170 | /**
171 | * Min value is `-9223372036854775808`, but when the value is less than `-9223372036854775808` it is out of range
172 | * and save as `-9223372036854775808`.
173 | *
174 | * @throws Exception
175 | * @throws InvalidConfigException
176 | * @throws InvalidArgumentException
177 | * @throws NotSupportedException
178 | * @throws Throwable
179 | */
180 | public function testMinValue(): void
181 | {
182 | $this->setFixture('Type/bigint.sql');
183 |
184 | $db = $this->getConnection(true);
185 | $command = $db->createCommand();
186 | $command->insert('bigint', ['Mybigint1' => '-9223372036854775808', 'Mybigint2' => '0'])->execute();
187 |
188 | $this->assertSame(
189 | [
190 | 'id' => '1',
191 | 'Mybigint1' => '-9223372036854775808',
192 | 'Mybigint2' => '0',
193 | ],
194 | $command->setSql(
195 | <<queryOne()
199 | );
200 |
201 | $command->insert('bigint', ['Mybigint1' => '-9223372036854775809', 'Mybigint2' => null])->execute();
202 |
203 | $this->assertSame(
204 | [
205 | 'id' => '2',
206 | 'Mybigint1' => '-9223372036854775808',
207 | 'Mybigint2' => null,
208 | ],
209 | $command->setSql(
210 | <<queryOne()
214 | );
215 |
216 | $db->createCommand()->dropTable('bigint')->execute();
217 | }
218 |
219 | private function buildTable(): ConnectionInterface
220 | {
221 | $db = $this->getConnection();
222 |
223 | $command = $db->createCommand();
224 |
225 | if ($db->getSchema()->getTableSchema('bigint_default') !== null) {
226 | $command->dropTable('bigint_default')->execute();
227 | }
228 |
229 | $command->createTable(
230 | 'bigint_default',
231 | [
232 | 'id' => 'INT IDENTITY NOT NULL',
233 | 'Mybigint' => 'BIGINT DEFAULT 9223372036854775807', // Max value
234 | ],
235 | )->execute();
236 |
237 | return $db;
238 | }
239 |
240 | private function getColumns(): array
241 | {
242 | return [
243 | 'id' => '1',
244 | 'Mybigint' => '9223372036854775807',
245 | ];
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/tests/Type/BinaryTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
45 |
46 | $tableSchema = $db->getTableSchema('binary_default');
47 |
48 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
49 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
50 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
51 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
52 |
53 | $db->createCommand()->dropTable('binary_default')->execute();
54 | }
55 |
56 | /**
57 | * @throws Exception
58 | * @throws InvalidConfigException
59 | * @throws InvalidArgumentException
60 | * @throws NotSupportedException
61 | * @throws Throwable
62 | */
63 | public function testCreateTableWithInsert(): void
64 | {
65 | $db = $this->buildTable();
66 |
67 | $command = $db->createCommand();
68 | $command->insert('binary_default', [])->execute();
69 |
70 | $this->assertSame(
71 | $this->getColumns(),
72 | $command->setSql(
73 | <<queryOne(),
77 | );
78 |
79 | $db->createCommand()->dropTable('binary_default')->execute();
80 | }
81 |
82 | /**
83 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\Type\BinaryProvider::columns
84 | *
85 | * @throws Exception
86 | * @throws InvalidConfigException
87 | * @throws InvalidArgumentException
88 | * @throws NotSupportedException
89 | * @throws Throwable
90 | */
91 | public function testDefaultValue(
92 | string $column,
93 | string $dbType,
94 | string $phpType,
95 | int $size,
96 | Expression $defaultValue
97 | ): void {
98 | $this->setFixture('Type/binary.sql');
99 |
100 | $db = $this->getConnection(true);
101 | $tableSchema = $db->getTableSchema('binary_default');
102 |
103 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
104 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
105 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
106 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
107 |
108 | $db->createCommand()->dropTable('binary_default')->execute();
109 | }
110 |
111 | /**
112 | * @throws Exception
113 | * @throws InvalidConfigException
114 | * @throws InvalidArgumentException
115 | * @throws NotSupportedException
116 | * @throws Throwable
117 | */
118 | public function testDefaultValueWithInsert(): void
119 | {
120 | $this->setFixture('Type/binary.sql');
121 |
122 | $db = $this->getConnection(true);
123 | $command = $db->createCommand();
124 | $command->insert('binary_default', [])->execute();
125 |
126 | $this->assertSame(
127 | $this->getColumns(),
128 | $command->setSql(
129 | <<queryOne(),
133 | );
134 |
135 | $db->createCommand()->dropTable('binary_default')->execute();
136 | }
137 |
138 | /**
139 | * When the value is greater than the maximum value, the value is truncated.
140 | *
141 | * @throws Exception
142 | * @throws InvalidConfigException
143 | * @throws InvalidArgumentException
144 | * @throws NotSupportedException
145 | * @throws Throwable
146 | */
147 | public function testMaxValue(): void
148 | {
149 | $this->setFixture('Type/binary.sql');
150 |
151 | $db = $this->getConnection(true);
152 | $command = $db->createCommand();
153 | $command->insert('binary', [
154 | 'Mybinary1' => new Expression('CONVERT(binary(10), \'binary_default_value\')'),
155 | 'Mybinary3' => new Expression('CONVERT(binary(1), \'bb\')'),
156 | ])->execute();
157 |
158 | $this->assertSame(
159 | [
160 | 'id' => '1',
161 | 'Mybinary1' => '0x62696E6172795F646566',
162 | 'Mybinary2' => null,
163 | 'Mybinary3' => 'b',
164 | 'Mybinary4' => null,
165 | ],
166 | $command->setSql(
167 | <<queryOne()
171 | );
172 |
173 | $db->createCommand()->dropTable('binary')->execute();
174 | }
175 |
176 | /**
177 | * @throws Exception
178 | * @throws InvalidConfigException
179 | * @throws InvalidArgumentException
180 | * @throws NotSupportedException
181 | * @throws Throwable
182 | */
183 | public function testValue(): void
184 | {
185 | $this->setFixture('Type/binary.sql');
186 |
187 | $db = $this->getConnection(true);
188 | $command = $db->createCommand();
189 | $command->insert('binary', [
190 | 'Mybinary1' => new Expression('CONVERT(binary(10), \'binary\')'),
191 | 'Mybinary2' => new Expression('CONVERT(binary(10), null)'),
192 | 'Mybinary3' => new Expression('CONVERT(binary(1), \'b\')'),
193 | 'Mybinary4' => new Expression('CONVERT(binary(1), null)'),
194 | ])->execute();
195 |
196 | $this->assertSame(
197 | [
198 | 'id' => '1',
199 | 'Mybinary1' => '0x62696E61727900000000',
200 | 'Mybinary2' => null,
201 | 'Mybinary3' => 'b',
202 | 'Mybinary4' => null,
203 | ],
204 | $command->setSql(
205 | <<queryOne()
209 | );
210 |
211 | $db->createCommand()->dropTable('binary')->execute();
212 | }
213 |
214 | private function buildTable(): ConnectionInterface
215 | {
216 | $db = $this->getConnection();
217 |
218 | $command = $db->createCommand();
219 |
220 | if ($db->getSchema()->getTableSchema('binary_default') !== null) {
221 | $command->dropTable('binary_default')->execute();
222 | }
223 |
224 | $command->createTable(
225 | 'binary_default',
226 | [
227 | 'id' => 'INT IDENTITY NOT NULL',
228 | 'Mybinary1' => 'BINARY(10) DEFAULT CONVERT(binary(10), \'binary\')',
229 | 'Mybinary2' => 'BINARY(1) DEFAULT CONVERT(binary(1), \'b\')',
230 | ],
231 | )->execute();
232 |
233 | return $db;
234 | }
235 |
236 | private function getColumns(): array
237 | {
238 | return [
239 | 'id' => '1',
240 | 'Mybinary1' => '0x62696E61727900000000',
241 | 'Mybinary2' => 'b',
242 | ];
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/tests/Type/CharTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
44 |
45 | $tableSchema = $db->getTableSchema('char_default');
46 |
47 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
48 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
49 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
50 | $this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
51 |
52 | $db->createCommand()->dropTable('char_default')->execute();
53 | }
54 |
55 | /**
56 | * @throws Exception
57 | * @throws InvalidConfigException
58 | * @throws InvalidArgumentException
59 | * @throws NotSupportedException
60 | * @throws Throwable
61 | */
62 | public function testCreateTableWithInsert(): void
63 | {
64 | $db = $this->buildTable();
65 |
66 | $command = $db->createCommand();
67 | $command->insert('char_default', [])->execute();
68 |
69 | $this->assertSame(
70 | $this->getColumns(),
71 | $command->setSql(
72 | <<queryOne(),
76 | );
77 |
78 | $db->createCommand()->dropTable('char_default')->execute();
79 | }
80 |
81 | /**
82 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\Type\CharProvider::columns
83 | *
84 | * @throws Exception
85 | * @throws InvalidConfigException
86 | * @throws InvalidArgumentException
87 | * @throws NotSupportedException
88 | * @throws Throwable
89 | */
90 | public function testDefaultValue(
91 | string $column,
92 | string $dbType,
93 | string $phpType,
94 | int $size,
95 | string $defaultValue
96 | ): void {
97 | $this->setFixture('Type/char.sql');
98 |
99 | $db = $this->getConnection(true);
100 | $tableSchema = $db->getTableSchema('char_default');
101 |
102 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
103 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
104 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
105 | $this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
106 |
107 | $db->createCommand()->dropTable('char_default')->execute();
108 | }
109 |
110 | /**
111 | * @throws Exception
112 | * @throws InvalidConfigException
113 | * @throws InvalidArgumentException
114 | * @throws NotSupportedException
115 | * @throws Throwable
116 | */
117 | public function testDefaultValueWithInsert(): void
118 | {
119 | $this->setFixture('Type/char.sql');
120 |
121 | $db = $this->getConnection(true);
122 | $command = $db->createCommand();
123 | $command->insert('char_default', [])->execute();
124 |
125 | $this->assertSame(
126 | $this->getColumns(),
127 | $command->setSql(
128 | <<queryOne(),
132 | );
133 |
134 | $db->createCommand()->dropTable('char_default')->execute();
135 | }
136 |
137 | /**
138 | * @throws Exception
139 | * @throws InvalidConfigException
140 | * @throws InvalidArgumentException
141 | * @throws NotSupportedException
142 | * @throws Throwable
143 | */
144 | public function testValue(): void
145 | {
146 | $this->setFixture('Type/char.sql');
147 |
148 | $db = $this->getConnection(true);
149 | $command = $db->createCommand();
150 | $command->insert(
151 | 'char',
152 | [
153 | 'Mychar1' => '0123456789',
154 | 'Mychar2' => null,
155 | 'Mychar3' => 'b',
156 | 'Mychar4' => null,
157 | ],
158 | )->execute();
159 |
160 | $this->assertSame(
161 | [
162 | 'id' => '1',
163 | 'Mychar1' => '0123456789',
164 | 'Mychar2' => null,
165 | 'Mychar3' => 'b',
166 | 'Mychar4' => null,
167 | ],
168 | $command->setSql(
169 | <<queryOne()
173 | );
174 |
175 | $db->createCommand()->dropTable('char')->execute();
176 | }
177 |
178 | /**
179 | * @throws Exception
180 | * @throws InvalidConfigException
181 | * @throws InvalidArgumentException
182 | * @throws NotSupportedException
183 | * @throws Throwable
184 | */
185 | public function testValueException(): void
186 | {
187 | $this->setFixture('Type/char.sql');
188 |
189 | $db = $this->getConnection(true);
190 | $command = $db->createCommand();
191 |
192 | $this->expectException(Exception::class);
193 | $this->expectExceptionMessage(
194 | '[SQL Server]String or binary data would be truncated'
195 | );
196 |
197 | $command->insert('char', ['Mychar1' => '01234567891'])->execute();
198 | }
199 |
200 | private function buildTable(): ConnectionInterface
201 | {
202 | $db = $this->getConnection();
203 |
204 | $command = $db->createCommand();
205 |
206 | if ($db->getSchema()->getTableSchema('char_default') !== null) {
207 | $command->dropTable('char_default')->execute();
208 | }
209 |
210 | $command->createTable(
211 | 'char_default',
212 | [
213 | 'id' => 'INT IDENTITY NOT NULL',
214 | 'Mychar1' => 'CHAR(10) DEFAULT \'char\'', // Max value
215 | 'Mychar2' => 'CHAR(1) DEFAULT \'c\'', // Max value
216 | ],
217 | )->execute();
218 |
219 | return $db;
220 | }
221 |
222 | private function getColumns(): array
223 | {
224 | return [
225 | 'id' => '1',
226 | 'Mychar1' => 'char ',
227 | 'Mychar2' => 'c',
228 | ];
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/tests/Type/DateTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
46 |
47 | $tableSchema = $db->getTableSchema('date_default');
48 |
49 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
50 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
51 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
52 |
53 | $db->createCommand()->insert('date_default', [])->execute();
54 | }
55 |
56 | /**
57 | * @throws Exception
58 | * @throws InvalidConfigException
59 | * @throws InvalidArgumentException
60 | * @throws NotSupportedException
61 | * @throws Throwable
62 | */
63 | public function testCreateTableWithInsert(): void
64 | {
65 | $db = $this->buildTable();
66 |
67 | $command = $db->createCommand();
68 | $command->insert('date_default', [])->execute();
69 |
70 | $this->assertSame(
71 | $this->getColumns(),
72 | $command->setSql(
73 | <<queryOne(),
77 | );
78 |
79 | $db->createCommand()->dropTable('date_default')->execute();
80 | }
81 |
82 | /**
83 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\Type\DateProvider::columns
84 | *
85 | * @throws Exception
86 | * @throws InvalidConfigException
87 | * @throws InvalidArgumentException
88 | * @throws NotSupportedException
89 | * @throws Throwable
90 | */
91 | public function testDefaultValue(
92 | string $column,
93 | string $dbType,
94 | string $phpType,
95 | DateTimeImmutable $defaultValue
96 | ): void {
97 | $this->setFixture('Type/date.sql');
98 |
99 | $db = $this->getConnection(true);
100 | $tableSchema = $db->getTableSchema('date_default');
101 |
102 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
103 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
104 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
105 |
106 | $db->createCommand()->dropTable('date_default')->execute();
107 | }
108 |
109 | /**
110 | * @throws Exception
111 | * @throws InvalidConfigException
112 | * @throws InvalidArgumentException
113 | * @throws NotSupportedException
114 | * @throws Throwable
115 | */
116 | public function testDefaultValueWithInsert(): void
117 | {
118 | $this->setFixture('Type/date.sql');
119 |
120 | $db = $this->getConnection(true);
121 | $command = $db->createCommand();
122 | $command->insert('date_default', [])->execute();
123 |
124 | $this->assertSame(
125 | $this->getColumns(),
126 | $command->setSql(
127 | <<queryOne(),
131 | );
132 |
133 | $db->createCommand()->dropTable('date_default')->execute();
134 | }
135 |
136 | /**
137 | * @throws Exception
138 | * @throws InvalidConfigException
139 | * @throws InvalidArgumentException
140 | * @throws NotSupportedException
141 | * @throws Throwable
142 | */
143 | public function testValue(): void
144 | {
145 | $this->setFixture('Type/date.sql');
146 |
147 | $db = $this->getConnection(true);
148 | $command = $db->createCommand()->withDbTypecasting(false);
149 | $command->insert('date', [
150 | 'Mydate1' => '2007-05-08',
151 | 'Mydate2' => null,
152 | 'Mydatetime1' => '2007-05-08 12:35:29.123',
153 | 'Mydatetime2' => null,
154 | 'Mydatetimeoffset1' => '2007-05-08 12:35:29.1234567 +12:15',
155 | 'Mydatetimeoffset2' => null,
156 | 'Mytime1' => '12:35:29.1234567',
157 | 'Mytime2' => null,
158 | ])->execute();
159 |
160 | $this->assertSame(
161 | [
162 | 'id' => '1',
163 | 'Mydate1' => '2007-05-08',
164 | 'Mydate2' => null,
165 | 'Mydatetime1' => '2007-05-08 12:35:29.123',
166 | 'Mydatetime2' => null,
167 | 'Mydatetimeoffset1' => '2007-05-08 12:35:29.1234567 +12:15',
168 | 'Mydatetimeoffset2' => null,
169 | 'Mytime1' => '12:35:29.1234567',
170 | 'Mytime2' => null,
171 | ],
172 | $command->setSql(
173 | <<queryOne()
177 | );
178 |
179 | $db->createCommand()->dropTable('date')->execute();
180 | }
181 |
182 | /**
183 | * @throws Exception
184 | * @throws InvalidConfigException
185 | * @throws InvalidArgumentException
186 | * @throws NotSupportedException
187 | * @throws Throwable
188 | */
189 | public function testValueException(): void
190 | {
191 | $this->setFixture('Type/date.sql');
192 |
193 | $db = $this->getConnection(true);
194 |
195 | $this->expectException(Exception::class);
196 | $this->expectExceptionMessage(
197 | '[SQL Server]Conversion failed when converting date and/or time from character string.'
198 | );
199 |
200 | $db->createCommand()->insert('date', ['Mydate1' => '0000-00-00'])->execute();
201 | }
202 |
203 | private function buildTable(): ConnectionInterface
204 | {
205 | $db = $this->getConnection();
206 |
207 | $command = $db->createCommand();
208 |
209 | if ($db->getSchema()->getTableSchema('date_default') !== null) {
210 | $command->dropTable('date_default')->execute();
211 | }
212 |
213 | $command->createTable(
214 | 'date_default',
215 | [
216 | 'id' => 'INT IDENTITY NOT NULL',
217 | 'Mydate' => 'DATE DEFAULT \'2007-05-08\'',
218 | 'Mydatetime' => 'DATETIME DEFAULT \'2007-05-08 12:35:29.123\'',
219 | 'Mydatetime2' => ColumnBuilder::datetime(7)->defaultValue(new Expression("'2007-05-08 12:35:29.1234567'")),
220 | 'Mydatetimeoffset' => ColumnBuilder::datetimeWithTimezone(7)->defaultValue(new Expression("'2007-05-08 12:35:29.1234567 +12:15'")),
221 | 'Mytime' => ColumnBuilder::time(7)->defaultValue(new Expression("'12:35:29.1234567'")),
222 | ],
223 | )->execute();
224 |
225 | return $db;
226 | }
227 |
228 | private function getColumns(): array
229 | {
230 | return [
231 | 'id' => '1',
232 | 'Mydate' => '2007-05-08',
233 | 'Mydatetime' => '2007-05-08 12:35:29.123',
234 | 'Mydatetime2' => '2007-05-08 12:35:29.1234567',
235 | 'Mydatetimeoffset' => '2007-05-08 12:35:29.1234567 +12:15',
236 | 'Mytime' => '12:35:29.1234567',
237 | ];
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/tests/Type/GeometryTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
44 |
45 | $tableSchema = $db->getTableSchema('geometry_default');
46 |
47 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
48 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
49 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
50 |
51 | $db->createCommand()->dropTable('geometry_default')->execute();
52 | }
53 |
54 | /**
55 | * @throws Exception
56 | * @throws InvalidConfigException
57 | * @throws InvalidArgumentException
58 | * @throws NotSupportedException
59 | * @throws Throwable
60 | */
61 | public function testCreateTableWithInsert(): void
62 | {
63 | $db = $this->buildTable();
64 |
65 | $command = $db->createCommand();
66 | $command->insert('geometry_default', [])->execute();
67 |
68 | $this->assertSame(
69 | $this->getColumns(),
70 | $command->setSql(
71 | <<queryOne(),
75 | );
76 |
77 | $db->createCommand()->dropTable('geometry_default')->execute();
78 | }
79 |
80 | /**
81 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\Type\GeometryProvider::columns
82 | *
83 | * @throws Exception
84 | * @throws InvalidConfigException
85 | * @throws InvalidArgumentException
86 | * @throws NotSupportedException
87 | * @throws Throwable
88 | */
89 | public function testDefaultValue(
90 | string $column,
91 | string $dbType,
92 | string $phpType,
93 | Expression|null $defaultValue
94 | ): void {
95 | $this->setFixture('Type/geometry.sql');
96 |
97 | $db = $this->getConnection(true);
98 | $tableSchema = $db->getTableSchema('geometry_default');
99 |
100 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
101 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
102 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
103 |
104 | $db->createCommand()->dropTable('geometry_default')->execute();
105 | }
106 |
107 | /**
108 | * @throws Exception
109 | * @throws InvalidConfigException
110 | * @throws InvalidArgumentException
111 | * @throws NotSupportedException
112 | * @throws Throwable
113 | */
114 | public function testDefaultValueWithInsert(): void
115 | {
116 | $this->setFixture('Type/geometry.sql');
117 |
118 | $db = $this->getConnection(true);
119 | $command = $db->createCommand();
120 | $command->insert('geometry_default', [])->execute();
121 |
122 | $this->assertSame(
123 | $this->getColumns(),
124 | $command->setSql(
125 | <<queryOne(),
129 | );
130 |
131 | $db->createCommand()->dropTable('geometry_default')->execute();
132 | }
133 |
134 | /**
135 | * @throws Exception
136 | * @throws InvalidConfigException
137 | * @throws InvalidArgumentException
138 | * @throws NotSupportedException
139 | * @throws Throwable
140 | */
141 | public function testValue(): void
142 | {
143 | $this->setFixture('Type/geometry.sql');
144 |
145 | $db = $this->getConnection(true);
146 | $command = $db->createCommand();
147 | $command->insert(
148 | 'geometry',
149 | [
150 | 'Mygeometry1' => new Expression('geometry::STGeomFromText(\'LINESTRING(100 100,20 180,180 180)\', 0)'),
151 | ],
152 | )->execute();
153 |
154 | $this->assertSame(
155 | [
156 | 'id' => '1',
157 | 'Mygeometry1' => 'LINESTRING (100 100, 20 180, 180 180)',
158 | 'Mygeometry2' => 'LINESTRING (100 100, 20 180, 180 180)',
159 | ],
160 | $command->setSql(
161 | <<queryOne()
165 | );
166 |
167 | $db->createCommand()->dropTable('geometry')->execute();
168 | }
169 |
170 | private function buildTable(): ConnectionInterface
171 | {
172 | $db = $this->getConnection();
173 |
174 | $command = $db->createCommand();
175 |
176 | if ($db->getSchema()->getTableSchema('geometry_default') !== null) {
177 | $command->dropTable('geometry_default')->execute();
178 | }
179 |
180 | $command->createTable(
181 | 'geometry_default',
182 | [
183 | 'id' => 'INT IDENTITY NOT NULL',
184 | 'Mygeometry1' => 'GEOMETRY DEFAULT [geometry]::STGeomFromText(\'POINT(0 0)\',(0))',
185 | 'Mygeometry2' => 'GEOMETRY',
186 | ],
187 | )->execute();
188 |
189 | return $db;
190 | }
191 |
192 | private function getColumns(): array
193 | {
194 | return [
195 | 'id' => '1',
196 | 'Mygeometry1' => 'POINT (0 0)',
197 | 'Mygeometry2' => null,
198 | ];
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/tests/Type/ImageTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
38 |
39 | $tableSchema = $db->getTableSchema('image_default');
40 |
41 | $this->assertSame('image', $tableSchema?->getColumn('Myimage')->getDbType());
42 | $this->assertSame('mixed', $tableSchema?->getColumn('Myimage')->getPhpType());
43 | $this->assertSame('image', $tableSchema?->getColumn('Myimage')->getDefaultValue());
44 |
45 | $db->createCommand()->dropTable('image_default')->execute();
46 | }
47 |
48 | /**
49 | * @throws Exception
50 | * @throws InvalidConfigException
51 | * @throws InvalidArgumentException
52 | * @throws NotSupportedException
53 | * @throws Throwable
54 | */
55 | public function testCreateTableWithInsert(): void
56 | {
57 | $db = $this->buildTable();
58 |
59 | $command = $db->createCommand();
60 | $command->insert('image_default', [])->execute();
61 |
62 | $this->assertSame(
63 | $this->getColumns(),
64 | $command->setSql(
65 | <<queryOne(),
69 | );
70 |
71 | $db->createCommand()->dropTable('image_default')->execute();
72 | }
73 |
74 | /**
75 | * @throws Exception
76 | * @throws InvalidConfigException
77 | * @throws InvalidArgumentException
78 | * @throws NotSupportedException
79 | * @throws Throwable
80 | */
81 | public function testDefaultValue(): void
82 | {
83 | $this->setFixture('Type/image.sql');
84 |
85 | $db = $this->getConnection(true);
86 | $tableSchema = $db->getTableSchema('image_default');
87 |
88 | $this->assertSame('image', $tableSchema?->getColumn('Myimage')->getDbType());
89 | $this->assertSame('mixed', $tableSchema?->getColumn('Myimage')->getPhpType());
90 | $this->assertSame('image', $tableSchema?->getColumn('Myimage')->getDefaultValue());
91 |
92 | $db->createCommand()->dropTable('image_default')->execute();
93 | }
94 |
95 | /**
96 | * @throws Exception
97 | * @throws InvalidConfigException
98 | * @throws InvalidArgumentException
99 | * @throws NotSupportedException
100 | * @throws Throwable
101 | */
102 | public function testDefaultValueWithInsert(): void
103 | {
104 | $this->setFixture('Type/image.sql');
105 |
106 | $db = $this->getConnection(true);
107 | $command = $db->createCommand();
108 | $command->insert('image_default', [])->execute();
109 |
110 | $this->assertSame(
111 | $this->getColumns(),
112 | $command->setSql(
113 | <<queryOne(),
117 | );
118 |
119 | $db->createCommand()->dropTable('image_default')->execute();
120 | }
121 |
122 | /**
123 | * @throws Exception
124 | * @throws InvalidConfigException
125 | * @throws InvalidArgumentException
126 | * @throws NotSupportedException
127 | * @throws Throwable
128 | */
129 | public function testValue(): void
130 | {
131 | $this->setFixture('Type/image.sql');
132 |
133 | $db = $this->getConnection(true);
134 | $command = $db->createCommand();
135 | $command->insert(
136 | 'image',
137 | ['Myimage1' => new Expression('CONVERT(image, 0x30313233343536373839)'), 'Myimage2' => null]
138 | )->execute();
139 |
140 | $this->assertSame(
141 | [
142 | 'id' => '1',
143 | 'Myimage1' => '0123456789',
144 | 'Myimage2' => null,
145 | ],
146 | $command->setSql(
147 | <<queryOne()
151 | );
152 |
153 | $db->createCommand()->dropTable('image')->execute();
154 | }
155 |
156 | private function buildTable(): ConnectionInterface
157 | {
158 | $db = $this->getConnection();
159 |
160 | $command = $db->createCommand();
161 |
162 | if ($db->getSchema()->getTableSchema('image_default') !== null) {
163 | $command->dropTable('image_default')->execute();
164 | }
165 |
166 | $command->createTable(
167 | 'image_default',
168 | [
169 | 'id' => 'INT IDENTITY NOT NULL',
170 | 'Myimage' => 'IMAGE DEFAULT \'image\'',
171 | ],
172 | )->execute();
173 |
174 | return $db;
175 | }
176 |
177 | private function getColumns(): array
178 | {
179 | return [
180 | 'id' => '1',
181 | 'Myimage' => 'image',
182 | ];
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/tests/Type/NTextTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
37 |
38 | $tableSchema = $db->getTableSchema('ntext_default');
39 |
40 | $this->assertSame('ntext', $tableSchema?->getColumn('Myntext')->getDbType());
41 | $this->assertSame('string', $tableSchema?->getColumn('Myntext')->getPhpType());
42 | $this->assertSame('ntext', $tableSchema?->getColumn('Myntext')->getDefaultValue());
43 |
44 | $db->createCommand()->dropTable('ntext_default')->execute();
45 | }
46 |
47 | /**
48 | * @throws Exception
49 | * @throws InvalidConfigException
50 | * @throws InvalidArgumentException
51 | * @throws NotSupportedException
52 | * @throws Throwable
53 | */
54 | public function testCreateTableWithInsert(): void
55 | {
56 | $db = $this->buildTable();
57 |
58 | $command = $db->createCommand();
59 | $command->insert('ntext_default', [])->execute();
60 |
61 | $this->assertSame(
62 | $this->getColumns(),
63 | $command->setSql(
64 | <<queryOne(),
68 | );
69 |
70 | $db->createCommand()->dropTable('ntext_default')->execute();
71 | }
72 |
73 | /**
74 | * @throws Exception
75 | * @throws InvalidConfigException
76 | * @throws InvalidArgumentException
77 | * @throws NotSupportedException
78 | * @throws Throwable
79 | */
80 | public function testDefaultValue(): void
81 | {
82 | $this->setFixture('Type/ntext.sql');
83 |
84 | $db = $this->getConnection(true);
85 | $tableSchema = $db->getTableSchema('ntext_default');
86 |
87 | $this->assertSame('ntext', $tableSchema?->getColumn('Myntext')->getDbType());
88 | $this->assertSame('string', $tableSchema?->getColumn('Myntext')->getPhpType());
89 | $this->assertSame('ntext', $tableSchema?->getColumn('Myntext')->getDefaultValue());
90 |
91 | $db->createCommand()->dropTable('ntext_default')->execute();
92 | }
93 |
94 | /**
95 | * @throws Exception
96 | * @throws InvalidConfigException
97 | * @throws InvalidArgumentException
98 | * @throws NotSupportedException
99 | * @throws Throwable
100 | */
101 | public function testDefaultValueWithInsert(): void
102 | {
103 | $this->setFixture('Type/ntext.sql');
104 |
105 | $db = $this->getConnection(true);
106 | $command = $db->createCommand();
107 | $command->insert('ntext_default', [])->execute();
108 |
109 | $this->assertSame(
110 | $this->getColumns(),
111 | $command->setSql(
112 | <<queryOne(),
116 | );
117 |
118 | $db->createCommand()->dropTable('ntext_default')->execute();
119 | }
120 |
121 | /**
122 | * @throws Exception
123 | * @throws InvalidConfigException
124 | * @throws InvalidArgumentException
125 | * @throws NotSupportedException
126 | * @throws Throwable
127 | */
128 | public function testValue(): void
129 | {
130 | $this->setFixture('Type/ntext.sql');
131 |
132 | $db = $this->getConnection(true);
133 | $command = $db->createCommand();
134 | $command->insert('ntext', ['Myntext1' => '0123456789', 'Myntext2' => null])->execute();
135 |
136 | $this->assertSame(
137 | [
138 | 'id' => '1',
139 | 'Myntext1' => '0123456789',
140 | 'Myntext2' => null,
141 | ],
142 | $command->setSql(
143 | <<queryOne()
147 | );
148 |
149 | $db->createCommand()->dropTable('ntext')->execute();
150 | }
151 |
152 | private function buildTable(): ConnectionInterface
153 | {
154 | $db = $this->getConnection();
155 |
156 | $command = $db->createCommand();
157 |
158 | if ($db->getSchema()->getTableSchema('ntext_default') !== null) {
159 | $command->dropTable('ntext_default')->execute();
160 | }
161 |
162 | $command->createTable(
163 | 'ntext_default',
164 | [
165 | 'id' => 'INT IDENTITY NOT NULL',
166 | 'Myntext' => 'NTEXT DEFAULT \'ntext\'',
167 | ],
168 | )->execute();
169 |
170 | return $db;
171 | }
172 |
173 | private function getColumns(): array
174 | {
175 | return [
176 | 'id' => '1',
177 | 'Myntext' => 'ntext',
178 | ];
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/tests/Type/RowversionTest.php:
--------------------------------------------------------------------------------
1 | setFixture('Type/rowversion.sql');
36 |
37 | $db = $this->getConnection(true);
38 | $tableSchema = $db->getTableSchema('rowversion');
39 |
40 | $this->assertSame('timestamp', $tableSchema?->getColumn('Myrowversion')->getDbType());
41 | $this->assertSame('mixed', $tableSchema?->getColumn('Myrowversion')->getPhpType());
42 | $this->assertNull($tableSchema?->getColumn('Myrowversion')->getDefaultValue());
43 |
44 | $command = $db->createCommand();
45 | $command->insert('rowversion', [])->execute();
46 |
47 | $this->assertIsNumeric(
48 | $command->setSql(
49 | <<queryScalar()
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Type/TextTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
37 |
38 | $tableSchema = $db->getTableSchema('text_default');
39 |
40 | $this->assertSame('text', $tableSchema?->getColumn('Mytext')->getDbType());
41 | $this->assertSame('string', $tableSchema?->getColumn('Mytext')->getPhpType());
42 | $this->assertSame('text', $tableSchema?->getColumn('Mytext')->getDefaultValue());
43 |
44 | $db->createCommand()->dropTable('text_default')->execute();
45 | }
46 |
47 | /**
48 | * @throws Exception
49 | * @throws InvalidConfigException
50 | * @throws InvalidArgumentException
51 | * @throws NotSupportedException
52 | * @throws Throwable
53 | */
54 | public function testCreateTableWithInsert(): void
55 | {
56 | $db = $this->buildTable();
57 |
58 | $command = $db->createCommand();
59 | $command->insert('text_default', [])->execute();
60 |
61 | $this->assertSame(
62 | $this->getColumns(),
63 | $command->setSql(
64 | <<queryOne(),
68 | );
69 |
70 | $db->createCommand()->dropTable('text_default')->execute();
71 | }
72 |
73 | /**
74 | * @throws Exception
75 | * @throws InvalidConfigException
76 | * @throws InvalidArgumentException
77 | * @throws NotSupportedException
78 | * @throws Throwable
79 | */
80 | public function testDefaultValue(): void
81 | {
82 | $this->setFixture('Type/text.sql');
83 |
84 | $db = $this->getConnection(true);
85 | $tableSchema = $db->getTableSchema('text_default');
86 |
87 | $this->assertSame('text', $tableSchema?->getColumn('Mytext')->getDbType());
88 | $this->assertSame('string', $tableSchema?->getColumn('Mytext')->getPhpType());
89 | $this->assertSame('text', $tableSchema?->getColumn('Mytext')->getDefaultValue());
90 |
91 | $db->createCommand()->dropTable('text_default')->execute();
92 | }
93 |
94 | /**
95 | * @throws Exception
96 | * @throws InvalidConfigException
97 | * @throws InvalidArgumentException
98 | * @throws NotSupportedException
99 | * @throws Throwable
100 | */
101 | public function testDefaultValueWithInsert(): void
102 | {
103 | $this->setFixture('Type/text.sql');
104 |
105 | $db = $this->getConnection(true);
106 | $command = $db->createCommand();
107 | $command->insert('text_default', [])->execute();
108 |
109 | $this->assertSame(
110 | $this->getColumns(),
111 | $command->setSql(
112 | <<queryOne(),
116 | );
117 |
118 | $db->createCommand()->dropTable('text_default')->execute();
119 | }
120 |
121 | /**
122 | * @throws Exception
123 | * @throws InvalidConfigException
124 | * @throws InvalidArgumentException
125 | * @throws NotSupportedException
126 | * @throws Throwable
127 | */
128 | public function testValue(): void
129 | {
130 | $this->setFixture('Type/text.sql');
131 |
132 | $db = $this->getConnection(true);
133 | $command = $db->createCommand();
134 | $command->insert('text', ['Mytext1' => '0123456789', 'Mytext2' => null])->execute();
135 |
136 | $this->assertSame(
137 | [
138 | 'id' => '1',
139 | 'Mytext1' => '0123456789',
140 | 'Mytext2' => null,
141 | ],
142 | $command->setSql(
143 | <<queryOne()
147 | );
148 |
149 | $db->createCommand()->dropTable('text')->execute();
150 | }
151 |
152 | private function buildTable(): ConnectionInterface
153 | {
154 | $db = $this->getConnection();
155 |
156 | $command = $db->createCommand();
157 |
158 | if ($db->getSchema()->getTableSchema('text_default') !== null) {
159 | $command->dropTable('text_default')->execute();
160 | }
161 |
162 | $command->createTable(
163 | 'text_default',
164 | [
165 | 'id' => 'INT IDENTITY NOT NULL',
166 | 'Mytext' => 'TEXT DEFAULT \'text\'',
167 | ],
168 | )->execute();
169 |
170 | return $db;
171 | }
172 |
173 | private function getColumns(): array
174 | {
175 | return [
176 | 'id' => '1',
177 | 'Mytext' => 'text',
178 | ];
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/tests/Type/UniqueidentifierTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
37 |
38 | $tableSchema = $db->getTableSchema('uniqueidentifier_default');
39 |
40 | $this->assertSame('uniqueidentifier', $tableSchema?->getColumn('Myuniqueidentifier')->getDbType());
41 | $this->assertSame('string', $tableSchema?->getColumn('Myuniqueidentifier')->getPhpType());
42 | $this->assertSame(
43 | '12345678-1234-1234-1234-123456789012',
44 | $tableSchema?->getColumn('Myuniqueidentifier')->getDefaultValue(),
45 | );
46 |
47 | $db->createCommand()->dropTable('uniqueidentifier_default')->execute();
48 | }
49 |
50 | /**
51 | * @throws Exception
52 | * @throws InvalidConfigException
53 | * @throws InvalidArgumentException
54 | * @throws NotSupportedException
55 | * @throws Throwable
56 | */
57 | public function testCreateTableWithInsert(): void
58 | {
59 | $db = $this->buildTable();
60 |
61 | $command = $db->createCommand();
62 | $command->insert('uniqueidentifier_default', [])->execute();
63 |
64 | $this->assertSame(
65 | $this->getColumns(),
66 | $command->setSql(
67 | <<queryOne(),
71 | );
72 |
73 | $db->createCommand()->dropTable('uniqueidentifier_default')->execute();
74 | }
75 |
76 | /**
77 | * @throws Exception
78 | * @throws InvalidConfigException
79 | * @throws InvalidArgumentException
80 | * @throws NotSupportedException
81 | * @throws Throwable
82 | */
83 | public function testDefaultValue(): void
84 | {
85 | $this->setFixture('Type/uniqueidentifier.sql');
86 |
87 | $db = $this->getConnection(true);
88 | $tableSchema = $db->getTableSchema('uniqueidentifier_default');
89 |
90 | $this->assertSame('uniqueidentifier', $tableSchema?->getColumn('Myuniqueidentifier')->getDbType());
91 | $this->assertSame('string', $tableSchema?->getColumn('Myuniqueidentifier')->getPhpType());
92 | $this->assertSame(
93 | '12345678-1234-1234-1234-123456789012',
94 | $tableSchema?->getColumn('Myuniqueidentifier')->getDefaultValue(),
95 | );
96 |
97 | $db->createCommand()->dropTable('uniqueidentifier_default')->execute();
98 | }
99 |
100 | /**
101 | * @throws Exception
102 | * @throws InvalidConfigException
103 | * @throws InvalidArgumentException
104 | * @throws NotSupportedException
105 | * @throws Throwable
106 | */
107 | public function testDefaultValueWithInsert(): void
108 | {
109 | $this->setFixture('Type/uniqueidentifier.sql');
110 |
111 | $db = $this->getConnection(true);
112 | $command = $db->createCommand();
113 | $command->insert('uniqueidentifier_default', [])->execute();
114 |
115 | $this->assertSame(
116 | $this->getColumns(),
117 | $command->setSql(
118 | <<queryOne(),
122 | );
123 |
124 | $db->createCommand()->dropTable('uniqueidentifier_default')->execute();
125 | }
126 |
127 | /**
128 | * Max value is 36 characters.
129 | *
130 | * @throws Exception
131 | * @throws InvalidConfigException
132 | * @throws InvalidArgumentException
133 | * @throws NotSupportedException
134 | * @throws Throwable
135 | */
136 | public function testValue(): void
137 | {
138 | $this->setFixture('Type/uniqueidentifier.sql');
139 |
140 | $db = $this->getConnection(true);
141 | $command = $db->createCommand();
142 | $command->insert(
143 | 'uniqueidentifier',
144 | ['Myuniqueidentifier1' => '12345678-1234-1234-1234-123456789012', 'Myuniqueidentifier2' => null],
145 | )->execute();
146 |
147 | $this->assertSame(
148 | [
149 | 'id' => '1',
150 | 'Myuniqueidentifier1' => '12345678-1234-1234-1234-123456789012',
151 | 'Myuniqueidentifier2' => null,
152 | ],
153 | $command->setSql(
154 | <<queryOne()
158 | );
159 |
160 | $db->createCommand()->dropTable('uniqueidentifier')->execute();
161 | }
162 |
163 | /**
164 | * @throws Exception
165 | * @throws InvalidConfigException
166 | * @throws InvalidArgumentException
167 | * @throws NotSupportedException
168 | * @throws Throwable
169 | */
170 | public function testValueException(): void
171 | {
172 | $this->setFixture('Type/uniqueidentifier.sql');
173 |
174 | $db = $this->getConnection(true);
175 |
176 | $this->expectException(Exception::class);
177 | $this->expectExceptionMessage(
178 | '[SQL Server]Conversion failed when converting from a character string to uniqueidentifier.'
179 | );
180 |
181 | $command = $db->createCommand();
182 | $command->insert('uniqueidentifier', ['Myuniqueidentifier1' => '1'])->execute();
183 | }
184 |
185 | /**
186 | * When you insert a value that is longer than 36 characters, the value is truncated to 36 characters.
187 | *
188 | * @throws Exception
189 | * @throws InvalidConfigException
190 | * @throws InvalidArgumentException
191 | * @throws NotSupportedException
192 | * @throws Throwable
193 | */
194 | public function testValueLength(): void
195 | {
196 | $this->setFixture('Type/uniqueidentifier.sql');
197 |
198 | $db = $this->getConnection(true);
199 | $command = $db->createCommand();
200 | $command->insert(
201 | 'uniqueidentifier',
202 | ['Myuniqueidentifier1' => '12345678-1234-1234-1234-1234567890123', 'Myuniqueidentifier2' => null],
203 | )->execute();
204 |
205 | $this->assertSame(
206 | [
207 | 'id' => '1',
208 | 'Myuniqueidentifier1' => '12345678-1234-1234-1234-123456789012',
209 | 'Myuniqueidentifier2' => null,
210 | ],
211 | $command->setSql(
212 | <<queryOne()
216 | );
217 |
218 | $db->createCommand()->dropTable('uniqueidentifier')->execute();
219 | }
220 |
221 | private function buildTable(): ConnectionInterface
222 | {
223 | $db = $this->getConnection();
224 |
225 | $command = $db->createCommand();
226 |
227 | if ($db->getSchema()->getTableSchema('uniqueidentifier_default') !== null) {
228 | $command->dropTable('uniqueidentifier_default')->execute();
229 | }
230 |
231 | $command->createTable(
232 | 'uniqueidentifier_default',
233 | [
234 | 'id' => 'INT IDENTITY NOT NULL',
235 | 'Myuniqueidentifier' => 'UNIQUEIDENTIFIER DEFAULT \'12345678-1234-1234-1234-123456789012\'',
236 | ],
237 | )->execute();
238 |
239 | return $db;
240 | }
241 |
242 | private function getColumns(): array
243 | {
244 | return [
245 | 'id' => '1',
246 | 'Myuniqueidentifier' => '12345678-1234-1234-1234-123456789012',
247 | ];
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/tests/Type/VarCharTest.php:
--------------------------------------------------------------------------------
1 | buildTable();
45 |
46 | $tableSchema = $db->getTableSchema('varchar_default');
47 |
48 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
49 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
50 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
51 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
52 |
53 | $db->createCommand()->dropTable('varchar_default')->execute();
54 | }
55 |
56 | /**
57 | * @throws Exception
58 | * @throws InvalidConfigException
59 | * @throws InvalidArgumentException
60 | * @throws NotSupportedException
61 | * @throws Throwable
62 | */
63 | public function testCreateTableWithInsert(): void
64 | {
65 | $db = $this->buildTable();
66 |
67 | $command = $db->createCommand();
68 | $command->insert('varchar_default', [])->execute();
69 |
70 | $this->assertSame(
71 | $this->getColumns(),
72 | $command->setSql(
73 | <<queryOne(),
77 | );
78 |
79 | $db->createCommand()->dropTable('varchar_default')->execute();
80 | }
81 |
82 | /**
83 | * @dataProvider \Yiisoft\Db\Mssql\Tests\Provider\Type\VarCharProvider::columns
84 | *
85 | * @throws Exception
86 | * @throws InvalidConfigException
87 | * @throws InvalidArgumentException
88 | * @throws NotSupportedException
89 | * @throws Throwable
90 | */
91 | public function testDefaultValue(
92 | string $column,
93 | string $dbType,
94 | string $phpType,
95 | int $size,
96 | string|Expression $defaultValue
97 | ): void {
98 | $this->setFixture('Type/varchar.sql');
99 |
100 | $db = $this->getConnection(true);
101 | $tableSchema = $db->getTableSchema('varchar_default');
102 |
103 | $this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
104 | $this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
105 | $this->assertSame($size, $tableSchema?->getColumn($column)->getSize());
106 | $this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
107 |
108 | $db->createCommand()->dropTable('varchar_default')->execute();
109 | }
110 |
111 | /**
112 | * @throws Exception
113 | * @throws InvalidConfigException
114 | * @throws InvalidArgumentException
115 | * @throws NotSupportedException
116 | * @throws Throwable
117 | */
118 | public function testDefaultValueWithInsert(): void
119 | {
120 | $this->setFixture('Type/varchar.sql');
121 |
122 | $db = $this->getConnection(true);
123 | $command = $db->createCommand();
124 | $command->insert('varchar_default', [])->execute();
125 |
126 | $this->assertSame(
127 | $this->getColumns(),
128 | $command->setSql(
129 | <<queryOne(),
133 | );
134 |
135 | $db->createCommand()->dropTable('varchar_default')->execute();
136 | }
137 |
138 | /**
139 | * @throws Exception
140 | * @throws InvalidConfigException
141 | * @throws InvalidArgumentException
142 | * @throws NotSupportedException
143 | * @throws Throwable
144 | */
145 | public function testValue(): void
146 | {
147 | $this->setFixture('Type/varchar.sql');
148 |
149 | $db = $this->getConnection(true);
150 | $command = $db->createCommand();
151 | $command->insert(
152 | 'varchar',
153 | [
154 | 'Myvarchar1' => '0123456789',
155 | 'Myvarchar2' => null,
156 | 'Myvarchar3' => str_repeat('b', 100),
157 | 'Myvarchar4' => null,
158 | ],
159 | )->execute();
160 |
161 | $this->assertSame(
162 | [
163 | 'id' => '1',
164 | 'Myvarchar1' => '0123456789',
165 | 'Myvarchar2' => null,
166 | 'Myvarchar3' => str_repeat('b', 100),
167 | 'Myvarchar4' => null,
168 | ],
169 | $command->setSql(
170 | <<queryOne()
174 | );
175 |
176 | $db->createCommand()->dropTable('varchar')->execute();
177 | }
178 |
179 | /**
180 | * @throws Exception
181 | * @throws InvalidConfigException
182 | * @throws InvalidArgumentException
183 | * @throws NotSupportedException
184 | * @throws Throwable
185 | */
186 | public function testValueException(): void
187 | {
188 | $this->setFixture('Type/varchar.sql');
189 |
190 | $db = $this->getConnection(true);
191 | $command = $db->createCommand();
192 |
193 | $this->expectException(Exception::class);
194 | $this->expectExceptionMessage(
195 | '[SQL Server]String or binary data would be truncated'
196 | );
197 |
198 | $command->insert('varchar', ['Myvarchar1' => '01234567891'])->execute();
199 | }
200 |
201 | private function buildTable(): ConnectionInterface
202 | {
203 | $db = $this->getConnection();
204 |
205 | $command = $db->createCommand();
206 |
207 | if ($db->getSchema()->getTableSchema('varchar_default') !== null) {
208 | $command->dropTable('varchar_default')->execute();
209 | }
210 |
211 | $command->createTable(
212 | 'varchar_default',
213 | [
214 | 'id' => 'INT IDENTITY NOT NULL',
215 | 'Myvarchar1' => 'VARCHAR(10) DEFAULT \'varchar\'',
216 | 'Myvarchar2' => 'VARCHAR(100) DEFAULT \'v\'',
217 | 'Myvarchar3' => 'VARCHAR(20) DEFAULT TRY_CONVERT(varchar(20), year(getdate()))',
218 | ],
219 | )->execute();
220 |
221 | return $db;
222 | }
223 |
224 | private function getColumns(): array
225 | {
226 | return [
227 | 'id' => '1',
228 | 'Myvarchar1' => 'varchar',
229 | 'Myvarchar2' => 'v',
230 | 'Myvarchar3' => date('Y'),
231 | ];
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | load();
8 | }
9 |
--------------------------------------------------------------------------------