├── .phpunit-watcher.yml ├── .styleci.yml ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── UPGRADE.md ├── composer-require-checker.json ├── composer.json ├── config └── params.php ├── infection.json.dist ├── psalm.xml ├── rector.php ├── src ├── Cache │ └── SchemaCache.php ├── Command │ ├── AbstractCommand.php │ ├── CommandInterface.php │ ├── Param.php │ ├── ParamBuilder.php │ └── ParamInterface.php ├── Connection │ ├── AbstractConnection.php │ ├── AbstractDsn.php │ ├── AbstractDsnSocket.php │ ├── ConnectionInterface.php │ ├── DsnInterface.php │ └── ServerInfoInterface.php ├── Constant │ ├── ColumnType.php │ ├── DataType.php │ ├── GettypeResult.php │ ├── IndexType.php │ ├── PhpType.php │ ├── PseudoType.php │ └── ReferentialAction.php ├── Constraint │ ├── CheckConstraint.php │ ├── Constraint.php │ ├── ConstraintSchemaInterface.php │ ├── DefaultValueConstraint.php │ ├── ForeignKeyConstraint.php │ └── IndexConstraint.php ├── Debug │ ├── CommandInterfaceProxy.php │ ├── ConnectionInterfaceProxy.php │ ├── DatabaseCollector.php │ └── TransactionInterfaceDecorator.php ├── Driver │ ├── DriverInterface.php │ └── Pdo │ │ ├── AbstractPdoCommand.php │ │ ├── AbstractPdoConnection.php │ │ ├── AbstractPdoDriver.php │ │ ├── AbstractPdoSchema.php │ │ ├── AbstractPdoTransaction.php │ │ ├── LogType.php │ │ ├── PdoCommandInterface.php │ │ ├── PdoConnectionInterface.php │ │ ├── PdoDataReader.php │ │ ├── PdoDriverInterface.php │ │ └── PdoServerInfo.php ├── Exception │ ├── ConvertException.php │ ├── Exception.php │ ├── IntegrityException.php │ ├── InvalidArgumentException.php │ ├── InvalidCallException.php │ ├── InvalidConfigException.php │ ├── InvalidParamException.php │ ├── NotSupportedException.php │ ├── PsrInvalidArgumentException.php │ ├── StaleObjectException.php │ ├── UnknownMethodException.php │ └── UnknownPropertyException.php ├── Expression │ ├── AbstractArrayExpressionBuilder.php │ ├── AbstractStructuredExpressionBuilder.php │ ├── ArrayExpression.php │ ├── ArrayExpressionBuilder.php │ ├── Expression.php │ ├── ExpressionBuilder.php │ ├── ExpressionBuilderInterface.php │ ├── ExpressionInterface.php │ ├── JsonExpression.php │ ├── JsonExpressionBuilder.php │ ├── StructuredExpression.php │ └── StructuredExpressionBuilder.php ├── Helper │ ├── DbArrayHelper.php │ ├── DbStringHelper.php │ └── DbUuidHelper.php ├── Profiler │ ├── Context │ │ ├── AbstractContext.php │ │ ├── CommandContext.php │ │ └── ConnectionContext.php │ ├── ContextInterface.php │ ├── ProfilerAwareInterface.php │ ├── ProfilerAwareTrait.php │ └── ProfilerInterface.php ├── Query │ ├── BatchQueryResult.php │ ├── BatchQueryResultInterface.php │ ├── DataReaderInterface.php │ ├── Query.php │ ├── QueryExpressionBuilder.php │ ├── QueryFunctionsInterface.php │ ├── QueryInterface.php │ └── QueryPartsInterface.php ├── QueryBuilder │ ├── AbstractColumnDefinitionBuilder.php │ ├── AbstractDDLQueryBuilder.php │ ├── AbstractDMLQueryBuilder.php │ ├── AbstractDQLQueryBuilder.php │ ├── AbstractQueryBuilder.php │ ├── ColumnDefinitionBuilderInterface.php │ ├── Condition │ │ ├── AbstractConjunctionCondition.php │ │ ├── AbstractOverlapsCondition.php │ │ ├── AndCondition.php │ │ ├── ArrayOverlapsCondition.php │ │ ├── BetweenColumnsCondition.php │ │ ├── BetweenCondition.php │ │ ├── Builder │ │ │ ├── AbstractOverlapsConditionBuilder.php │ │ │ ├── BetweenColumnsConditionBuilder.php │ │ │ ├── BetweenConditionBuilder.php │ │ │ ├── ConjunctionConditionBuilder.php │ │ │ ├── ExistsConditionBuilder.php │ │ │ ├── HashConditionBuilder.php │ │ │ ├── InConditionBuilder.php │ │ │ ├── LikeConditionBuilder.php │ │ │ ├── NotConditionBuilder.php │ │ │ └── SimpleConditionBuilder.php │ │ ├── ExistsCondition.php │ │ ├── HashCondition.php │ │ ├── InCondition.php │ │ ├── Interface │ │ │ ├── BetweenColumnsConditionInterface.php │ │ │ ├── BetweenConditionInterface.php │ │ │ ├── ConditionInterface.php │ │ │ ├── ConjunctionConditionInterface.php │ │ │ ├── ExistConditionInterface.php │ │ │ ├── HashConditionInterface.php │ │ │ ├── InConditionInterface.php │ │ │ ├── LikeConditionInterface.php │ │ │ ├── NotConditionInterface.php │ │ │ ├── OverlapsConditionInterface.php │ │ │ └── SimpleConditionInterface.php │ │ ├── JsonOverlapsCondition.php │ │ ├── LikeCondition.php │ │ ├── NotCondition.php │ │ ├── OrCondition.php │ │ └── SimpleCondition.php │ ├── DDLQueryBuilderInterface.php │ ├── DMLQueryBuilderInterface.php │ ├── DQLQueryBuilderInterface.php │ └── QueryBuilderInterface.php ├── Schema │ ├── AbstractSchema.php │ ├── AbstractTableSchema.php │ ├── Column │ │ ├── AbstractArrayColumn.php │ │ ├── AbstractColumn.php │ │ ├── AbstractColumnFactory.php │ │ ├── AbstractJsonColumn.php │ │ ├── AbstractStructuredColumn.php │ │ ├── ArrayColumn.php │ │ ├── ArrayLazyColumn.php │ │ ├── BigIntColumn.php │ │ ├── BinaryColumn.php │ │ ├── BitColumn.php │ │ ├── BooleanColumn.php │ │ ├── ColumnBuilder.php │ │ ├── ColumnFactoryInterface.php │ │ ├── ColumnInterface.php │ │ ├── DateTimeColumn.php │ │ ├── DoubleColumn.php │ │ ├── IntegerColumn.php │ │ ├── JsonColumn.php │ │ ├── JsonLazyColumn.php │ │ ├── StringColumn.php │ │ ├── StructuredColumn.php │ │ └── StructuredLazyColumn.php │ ├── Data │ │ ├── AbstractLazyArray.php │ │ ├── AbstractStructuredLazyArray.php │ │ ├── JsonLazyArray.php │ │ ├── LazyArray.php │ │ ├── LazyArrayInterface.php │ │ ├── LazyArrayTrait.php │ │ └── StructuredLazyArray.php │ ├── Quoter.php │ ├── QuoterInterface.php │ ├── SchemaInterface.php │ └── TableSchemaInterface.php ├── Syntax │ ├── AbstractSqlParser.php │ └── ColumnDefinitionParser.php └── Transaction │ └── TransactionInterface.php └── tests ├── AbstractColumnBuilderTest.php ├── AbstractColumnDefinitionParserTest.php ├── AbstractColumnFactoryTest.php ├── AbstractColumnTest.php ├── AbstractCommandTest.php ├── AbstractConnectionTest.php ├── AbstractPdoConnectionTest.php ├── AbstractQueryBuilderTest.php ├── AbstractQueryGetTableAliasTest.php ├── AbstractQueryTest.php ├── AbstractQuoterTest.php ├── AbstractSchemaTest.php ├── AbstractSqlParserTest.php ├── AbstractTableSchemaTest.php ├── Common ├── CommonBatchQueryResultTest.php ├── CommonColumnTest.php ├── CommonCommandTest.php ├── CommonConnectionTest.php ├── CommonPdoCommandTest.php ├── CommonPdoConnectionTest.php ├── CommonQueryBuilderTest.php ├── CommonQueryTest.php └── CommonSchemaTest.php ├── Db ├── Cache │ └── SchemaCacheTest.php ├── Command │ ├── CommandTest.php │ └── ParamBuilderTest.php ├── Connection │ ├── ConnectionTest.php │ ├── DsnSocketTest.php │ └── DsnTest.php ├── Constraint │ ├── CheckConstraintTest.php │ ├── ConstraintTest.php │ ├── DefaultValueConstraintTest.php │ ├── ForeignKeyConstraintTest.php │ └── IndexConstraintTest.php ├── Driver │ └── Pdo │ │ ├── PdoConnectionTest.php │ │ ├── PdoDriverTest.php │ │ └── PdoServerInfoTest.php ├── Exception │ ├── ConvertExceptionTest.php │ └── ExceptionTest.php ├── Expression │ ├── ArrayExpressionBuilderTest.php │ ├── ArrayExpressionTest.php │ ├── JsonExpressionBuilderTest.php │ ├── JsonExpressionTest.php │ ├── StructuredExpressionBuilderTest.php │ └── StructuredExpressionTest.php ├── Helper │ ├── DbArrayHelperTest.php │ ├── DbStringHelperTest.php │ └── DbUuidHelperTest.php ├── Query │ ├── QueryGetTableAliasTest.php │ └── QueryTest.php ├── QueryBuilder │ ├── ColumnDefinitionBuilderTest.php │ ├── Condition │ │ ├── AndConditionTest.php │ │ ├── BetweenColumnsConditionTest.php │ │ ├── BetweenConditionTest.php │ │ ├── Builder │ │ │ ├── BetweenColumnsConditionBuilderTest.php │ │ │ └── LikeConditionBuilderTest.php │ │ ├── ExistsConditionTest.php │ │ ├── HashConditionTest.php │ │ ├── InConditionTest.php │ │ ├── LikeConditionTest.php │ │ ├── NotConditionTest.php │ │ └── SimpleConditionTest.php │ └── QueryBuilderTest.php ├── Schema │ ├── Column │ │ ├── ColumnBuilderTest.php │ │ ├── ColumnFactoryTest.php │ │ └── ColumnTest.php │ ├── Data │ │ ├── LazyArrayJsonTest.php │ │ ├── LazyArrayStructuredTest.php │ │ └── LazyArrayTest.php │ ├── QuoterTest.php │ ├── SchemaTest.php │ └── TableSchemaTest.php └── Syntax │ ├── ColumnDefinitionParserTest.php │ └── SqlParserTest.php ├── Provider ├── ColumnBuilderProvider.php ├── ColumnDefinitionParserProvider.php ├── ColumnFactoryProvider.php ├── ColumnProvider.php ├── CommandPDOProvider.php ├── CommandProvider.php ├── DbArrayHelperProvider.php ├── QueryBuilderProvider.php ├── QueryProvider.php ├── QuoterProvider.php ├── SchemaProvider.php └── SqlParserProvider.php └── Support ├── AnyCaseValue.php ├── AnyValue.php ├── Assert.php ├── CompareValue.php ├── DbHelper.php ├── Fixture └── db.sql ├── IntEnum.php ├── IsOneOfAssert.php ├── JsonSerializableObject.php ├── StringEnum.php ├── Stringable.php ├── Stub ├── Column.php ├── ColumnDefinitionBuilder.php ├── ColumnFactory.php ├── Command.php ├── Connection.php ├── DDLQueryBuilder.php ├── DMLQueryBuilder.php ├── DQLQueryBuilder.php ├── Dsn.php ├── DsnSocket.php ├── ExpressionBuilder.php ├── PdoDriver.php ├── QueryBuilder.php ├── Schema.php ├── SqlParser.php ├── TableSchema.php └── Transaction.php ├── TestTrait.php ├── TraversableObject.php └── string.txt /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /composer-require-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist" : [ 3 | "Yiisoft\\Yii\\Debug\\Collector\\CollectorTrait", 4 | "Yiisoft\\Yii\\Debug\\Collector\\SummaryCollectorInterface" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'collectors' => [ 12 | DatabaseCollector::class, 13 | ], 14 | 'trackedServices' => [ 15 | ConnectionInterface::class => [ConnectionInterfaceProxy::class, DatabaseCollector::class], 16 | ], 17 | ], 18 | ]; 19 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php:\/\/stderr", 9 | "stryker": { 10 | "report": "master" 11 | } 12 | }, 13 | "mutators": { 14 | "@default": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 14 | __DIR__ . '/src', 15 | __DIR__ . '/tests', 16 | ]); 17 | 18 | // register a single rule 19 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); 20 | 21 | // define sets of rules 22 | $rectorConfig->sets([ 23 | LevelSetList::UP_TO_PHP_81, 24 | ]); 25 | 26 | $rectorConfig->skip([ 27 | GetDebugTypeRector::class => [ 28 | __DIR__ . '/tests/AbstractColumnTest.php', 29 | ], 30 | ReadOnlyPropertyRector::class, 31 | NullToStrictStringFuncCallArgRector::class, 32 | ]); 33 | }; 34 | -------------------------------------------------------------------------------- /src/Command/Param.php: -------------------------------------------------------------------------------- 1 | type; 35 | } 36 | 37 | public function getValue(): mixed 38 | { 39 | return $this->value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Command/ParamBuilder.php: -------------------------------------------------------------------------------- 1 | driver:" . "unix_socket=$this->unixSocket"; 33 | 34 | if ($this->databaseName !== null && $this->databaseName !== '') { 35 | $dsn .= ';' . "dbname=$this->databaseName"; 36 | } 37 | 38 | $parts = []; 39 | 40 | foreach ($this->options as $key => $value) { 41 | $parts[] = "$key=$value"; 42 | } 43 | 44 | if (!empty($parts)) { 45 | $dsn .= ';' . implode(';', $parts); 46 | } 47 | 48 | return $dsn; 49 | } 50 | 51 | /** 52 | * @return string The Data Source Name, or DSN, has the information required to connect to the database. 53 | */ 54 | public function __toString(): string 55 | { 56 | return $this->asString(); 57 | } 58 | 59 | /** 60 | * @return string|null The database name to connect to. 61 | */ 62 | public function getDatabaseName(): string|null 63 | { 64 | return $this->databaseName; 65 | } 66 | 67 | /** 68 | * @return string The database driver to use. 69 | */ 70 | public function getDriver(): string 71 | { 72 | return $this->driver; 73 | } 74 | 75 | /** 76 | * @return string The unix socket to connect to. 77 | */ 78 | public function getUnixSocket(): string 79 | { 80 | return $this->unixSocket; 81 | } 82 | 83 | /** 84 | * @return array The options to use. 85 | */ 86 | public function getOptions(): array 87 | { 88 | return $this->options; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Connection/DsnInterface.php: -------------------------------------------------------------------------------- 1 | 'utf8mb4']); 24 | * $pdoDriver = new PDODriver($dsn->asString(), 'username', 'password'); 25 | * $connection = new Connection($pdoDriver, $schemaCache); 26 | * ``` 27 | * 28 | * Will result in the DSN string `mysql:host=127.0.0.1;dbname=yiitest;port=3306;charset=utf8mb4`. 29 | * 30 | * Or unix socket: 31 | * 32 | * ```php 33 | * $dsn = new DsnSocket('mysql', '/var/run/mysqld/mysqld.sock', 'yiitest', '', ['charset' => 'utf8mb4']); 34 | * $pdoDriver = new PDODriver($dsn->asString(), 'username', 'password'); 35 | * $connection = new Connection($pdoDriver, $schemaCache); 36 | * ``` 37 | * 38 | * Will result in the DSN string `mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=yiitest;charset=utf8mb4`. 39 | */ 40 | public function asString(): string; 41 | } 42 | -------------------------------------------------------------------------------- /src/Connection/ServerInfoInterface.php: -------------------------------------------------------------------------------- 1 | expression; 26 | } 27 | 28 | /** 29 | * Set the SQL of the `CHECK` constraint. 30 | * 31 | * @param string $value The SQL of the `CHECK` constraint. 32 | */ 33 | public function expression(string $value): self 34 | { 35 | $this->expression = $value; 36 | return $this; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Constraint/Constraint.php: -------------------------------------------------------------------------------- 1 | columnNames; 27 | } 28 | 29 | /** 30 | * @return object|string|null The constraint name. 31 | */ 32 | public function getName(): object|string|null 33 | { 34 | return $this->name; 35 | } 36 | 37 | /** 38 | * Set the list of column names the constraint belongs to. 39 | * 40 | * @param array|string|null $value The list of column names the constraint belongs to. 41 | */ 42 | public function columnNames(array|string|null $value): static 43 | { 44 | $this->columnNames = $value; 45 | return $this; 46 | } 47 | 48 | /** 49 | * Set the constraint name. 50 | * 51 | * @param object|string|null $value The constraint name. 52 | */ 53 | public function name(object|string|null $value): static 54 | { 55 | $this->name = $value; 56 | return $this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Constraint/DefaultValueConstraint.php: -------------------------------------------------------------------------------- 1 | value; 29 | } 30 | 31 | /** 32 | * Set the default value as returned by the DBMS. 33 | * 34 | * @param mixed $value The default value as returned by the DBMS. 35 | */ 36 | public function value(mixed $value): self 37 | { 38 | $this->value = $value; 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Constraint/IndexConstraint.php: -------------------------------------------------------------------------------- 1 | isUnique; 26 | } 27 | 28 | /** 29 | * @return bool Whether the index was created for a primary key. 30 | */ 31 | public function isPrimary(): bool 32 | { 33 | return $this->isPrimary; 34 | } 35 | 36 | /** 37 | * Set whether the index is unique. 38 | * 39 | * @param bool $value Whether the index is unique. 40 | */ 41 | public function unique(bool $value): self 42 | { 43 | $this->isUnique = $value; 44 | return $this; 45 | } 46 | 47 | /** 48 | * Set whether the index was created for a primary key. 49 | * 50 | * @param bool $value whether the index was created for a primary key. 51 | */ 52 | public function primary(bool $value): self 53 | { 54 | $this->isPrimary = $value; 55 | return $this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Debug/TransactionInterfaceDecorator.php: -------------------------------------------------------------------------------- 1 | collector->collectTransactionStart($isolationLevel, $callStack['file'] . ':' . $callStack['line']); 25 | 26 | $this->decorated->begin($isolationLevel); 27 | } 28 | 29 | /** 30 | * @psalm-suppress PossiblyUndefinedArrayOffset 31 | */ 32 | public function commit(): void 33 | { 34 | [$callStack] = debug_backtrace(); 35 | 36 | $this->decorated->commit(); 37 | 38 | $this->collector->collectTransactionCommit($callStack['file'] . ':' . $callStack['line']); 39 | } 40 | 41 | public function getLevel(): int 42 | { 43 | return $this->decorated->getLevel(); 44 | } 45 | 46 | public function isActive(): bool 47 | { 48 | return $this->decorated->isActive(); 49 | } 50 | 51 | /** 52 | * @psalm-suppress PossiblyUndefinedArrayOffset 53 | */ 54 | public function rollBack(): void 55 | { 56 | [$callStack] = debug_backtrace(); 57 | 58 | $this->decorated->rollBack(); 59 | 60 | $this->collector->collectTransactionRollback($callStack['file'] . ':' . $callStack['line']); 61 | } 62 | 63 | public function setIsolationLevel(string $level): void 64 | { 65 | $this->decorated->{__FUNCTION__}(...func_get_args()); 66 | } 67 | 68 | public function createSavepoint(string $name): void 69 | { 70 | $this->decorated->{__FUNCTION__}(...func_get_args()); 71 | } 72 | 73 | public function rollBackSavepoint(string $name): void 74 | { 75 | $this->decorated->{__FUNCTION__}(...func_get_args()); 76 | } 77 | 78 | public function releaseSavepoint(string $name): void 79 | { 80 | $this->decorated->{__FUNCTION__}(...func_get_args()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Driver/DriverInterface.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 32 | } 33 | 34 | public function createConnection(): PDO 35 | { 36 | return new PDO($this->dsn, $this->username, $this->password, $this->attributes); 37 | } 38 | 39 | public function charset(string|null $charset): void 40 | { 41 | $this->charset = $charset; 42 | } 43 | 44 | public function getCharset(): string|null 45 | { 46 | return $this->charset; 47 | } 48 | 49 | public function getDsn(): string 50 | { 51 | return $this->dsn; 52 | } 53 | 54 | public function getPassword(): string 55 | { 56 | return $this->password; 57 | } 58 | 59 | public function getUsername(): string 60 | { 61 | return $this->username; 62 | } 63 | 64 | public function password(#[\SensitiveParameter] string $password): void 65 | { 66 | $this->password = $password; 67 | } 68 | 69 | public function username(string $username): void 70 | { 71 | $this->username = $username; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Driver/Pdo/AbstractPdoSchema.php: -------------------------------------------------------------------------------- 1 | db instanceof PdoConnectionInterface) { 28 | $cacheKey = [$this->db->getDriver()->getDsn(), $this->db->getDriver()->getUsername()]; 29 | } else { 30 | throw new NotSupportedException('Only PDO connections are supported.'); 31 | } 32 | 33 | return $cacheKey; 34 | } 35 | 36 | protected function getCacheKey(string $name): array 37 | { 38 | return [static::class, ...$this->generateCacheKey(), $this->db->getQuoter()->getRawTableName($name)]; 39 | } 40 | 41 | protected function getCacheTag(): string 42 | { 43 | return md5(serialize([static::class, ...$this->generateCacheKey()])); 44 | } 45 | 46 | protected function getResultColumnCacheKey(array $metadata): string 47 | { 48 | return md5(serialize([static::class . '::getResultColumn', ...$this->generateCacheKey(), ...$metadata])); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Driver/Pdo/LogType.php: -------------------------------------------------------------------------------- 1 | version === null) { 27 | /** @var string */ 28 | $this->version = $this->db->getActivePdo()->getAttribute(PDO::ATTR_SERVER_VERSION) ?? ''; 29 | } 30 | 31 | return $this->version; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Exception/ConvertException.php: -------------------------------------------------------------------------------- 1 | e->getMessage() . PHP_EOL . 'The SQL being executed was: ' . $this->rawSql; 33 | 34 | $errorInfo = $this->e instanceof PDOException ? $this->e->errorInfo : null; 35 | 36 | return match ( 37 | str_contains($message, self::MSG_INTEGRITY_EXCEPTION_1) || 38 | str_contains($message, self::MGS_INTEGRITY_EXCEPTION_2) || 39 | str_contains($message, self::MSG_INTEGRITY_EXCEPTION_3) 40 | ) { 41 | true => new IntegrityException($message, $errorInfo, $this->e), 42 | default => new Exception($message, $errorInfo, $this->e), 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | errorInfo, true); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Exception/IntegrityException.php: -------------------------------------------------------------------------------- 1 | queryBuilder->bindParam($param, $params); 31 | } 32 | 33 | protected function buildSubquery(QueryInterface $query, ArrayExpression $expression, array &$params): string 34 | { 35 | [$sql, $params] = $this->queryBuilder->build($query, $params); 36 | 37 | return "($sql)"; 38 | } 39 | 40 | protected function buildValue(iterable $value, ArrayExpression $expression, array &$params): string 41 | { 42 | if (!is_array($value)) { 43 | $value = iterator_to_array($value, false); 44 | } 45 | 46 | return $this->buildStringValue(json_encode($value, JSON_THROW_ON_ERROR), $expression, $params); 47 | } 48 | 49 | protected function getLazyArrayValue(LazyArrayInterface $value): array|string 50 | { 51 | return match ($value::class) { 52 | LazyArray::class, JsonLazyArray::class, StructuredLazyArray::class => $value->getRawValue(), 53 | default => $value->getValue(), 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Expression/Expression.php: -------------------------------------------------------------------------------- 1 | select($expression)->scalar(); // SELECT NOW(); 21 | * echo $now; // prints the current date 22 | * ``` 23 | * 24 | * Expression objects are mainly created for passing raw SQL expressions to methods of 25 | * {@see \Yiisoft\Db\Query\QueryInterface} and related classes. 26 | * 27 | * @psalm-import-type ParamsType from ConnectionInterface 28 | */ 29 | final class Expression implements ExpressionInterface, Stringable 30 | { 31 | /** 32 | * @psalm-param ParamsType $params 33 | */ 34 | public function __construct(private string $expression, private array $params = []) 35 | { 36 | } 37 | 38 | /** 39 | * @return string The expression. 40 | */ 41 | public function __toString(): string 42 | { 43 | return $this->expression; 44 | } 45 | 46 | /** 47 | * @return array List of parameters to bind to this expression. The keys are placeholders appearing in 48 | * {@see expression} and the values are the corresponding parameter values. 49 | * 50 | * @psalm-return ParamsType 51 | */ 52 | public function getParams(): array 53 | { 54 | return $this->params; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Expression/ExpressionBuilderInterface.php: -------------------------------------------------------------------------------- 1 | `, `<`), combining expressions with logical 12 | * operators (such as `AND`, `OR`), and building sub-queries. 13 | * 14 | * The interface provides a consistent way for developers to build expressions for various types of 15 | * database queries, without having to worry about the specific syntax of the underlying database. 16 | * 17 | * @see ExpressionInterface 18 | */ 19 | interface ExpressionBuilderInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Expression/ExpressionInterface.php: -------------------------------------------------------------------------------- 1 | 1, 'b' => 2]); // will be encoded to '{"a": 1, "b": 2}' 14 | * ``` 15 | */ 16 | final class JsonExpression implements ExpressionInterface 17 | { 18 | /** 19 | * @param mixed $value The JSON content. It can be represented as 20 | * - an `array` of values; 21 | * - an instance which implements {@see Traversable} or {@see JsonSerializable} and represents an array of values; 22 | * - an instance of {@see QueryInterface} that represents an SQL sub-query; 23 | * - a valid JSON encoded array as a `string`, e.g. `'[1,2,3]'` or `'{"a":1,"b":2}'`; 24 | * - any other value compatible with {@see \json_encode()} input requirements. 25 | * @param string|null $type Type of database column, value should be cast to. Defaults to `null`, meaning no explicit 26 | * casting will be performed. This property is used only for DBMSs that support different types of JSON. 27 | * For example, PostgresSQL has `json` and `jsonb` types. 28 | */ 29 | public function __construct(private readonly mixed $value, private readonly string|null $type = null) 30 | { 31 | } 32 | 33 | /** 34 | * The JSON content. It can be represented as 35 | * - an `array` of values; 36 | * - an instance which implements {@see Traversable} or {@see JsonSerializable} and represents an array of values; 37 | * - an instance of {@see QueryInterface} that represents an SQL sub-query; 38 | * - a valid JSON encoded array as a `string`, e.g. `[1,2,3]` or `'{"a":1,"b":2}'`; 39 | * - any other value compatible with {@see \json_encode()} input requirements. 40 | */ 41 | public function getValue(): mixed 42 | { 43 | return $this->value; 44 | } 45 | 46 | /** 47 | * Type of JSON, expression should be cast to. Defaults to `null`, meaning no explicit casting will be performed. 48 | * This property will be encountered only for DBMSs that support different types of JSON. 49 | * For example, PostgresSQL has `json` and `jsonb` types. 50 | */ 51 | public function getType(): string|null 52 | { 53 | return $this->type; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Expression/StructuredExpressionBuilder.php: -------------------------------------------------------------------------------- 1 | queryBuilder->bindParam($param, $params); 30 | } 31 | 32 | protected function buildSubquery(QueryInterface $query, StructuredExpression $expression, array &$params): string 33 | { 34 | [$sql, $params] = $this->queryBuilder->build($query, $params); 35 | 36 | return "($sql)"; 37 | } 38 | 39 | protected function buildValue(array|object $value, StructuredExpression $expression, array &$params): string 40 | { 41 | $value = $this->prepareValues($value, $expression); 42 | $param = new Param(json_encode(array_values($value), JSON_THROW_ON_ERROR), DataType::STRING); 43 | 44 | return $this->queryBuilder->bindParam($param, $params); 45 | } 46 | 47 | protected function getLazyArrayValue(LazyArrayInterface $value): array|string 48 | { 49 | return match ($value::class) { 50 | LazyArray::class, JsonLazyArray::class, StructuredLazyArray::class => $value->getRawValue(), 51 | default => $value->getValue(), 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Helper/DbStringHelper.php: -------------------------------------------------------------------------------- 1 | exception = $e; 24 | return $this; 25 | } 26 | 27 | public function asArray(): array 28 | { 29 | return [ 30 | self::METHOD => $this->method, 31 | self::EXCEPTION => $this->exception, 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Profiler/Context/CommandContext.php: -------------------------------------------------------------------------------- 1 | method); 20 | } 21 | 22 | public function getType(): string 23 | { 24 | return 'command'; 25 | } 26 | 27 | public function asArray(): array 28 | { 29 | return parent::asArray() + [ 30 | self::LOG_CONTEXT => $this->logContext, 31 | self::SQL => $this->sql, 32 | self::PARAMS => $this->params, 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Profiler/Context/ConnectionContext.php: -------------------------------------------------------------------------------- 1 | profiler = $profiler; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Profiler/ProfilerInterface.php: -------------------------------------------------------------------------------- 1 | createCommand('SELECT * FROM post'); 20 | * $reader = $command->query(); 21 | * 22 | * foreach ($reader as $row) { 23 | * $rows[] = $row; 24 | * } 25 | * 26 | * Note: That since DataReader is a forward-only stream, you can only traverse it once. Doing it the second time will 27 | * throw an exception. 28 | * 29 | * @extends Iterator 30 | * 31 | * @psalm-import-type IndexBy from QueryInterface 32 | * @psalm-import-type ResultCallbackOne from QueryInterface 33 | */ 34 | interface DataReaderInterface extends Iterator, Countable 35 | { 36 | /** 37 | * Returns the index of the current row or null if {@see indexBy} property is specified and there is no row 38 | * at the current position. 39 | * 40 | * This method is required by the interface {@see Iterator}. 41 | */ 42 | public function key(): int|string|null; 43 | 44 | /** 45 | * Returns the current row or false if there is no row at the current position. 46 | * 47 | * This method is required by the interface {@see Iterator}. 48 | */ 49 | public function current(): array|object|false; 50 | 51 | /** 52 | * Sets `indexBy` property. 53 | * 54 | * @param Closure|string|null $indexBy The name of the column by which the query results should be indexed by. 55 | * This can also be a `Closure` instance (for example, anonymous function) that returns the index value based 56 | * on the given row data. 57 | * 58 | * The signature of the callable should be: 59 | * 60 | * ```php 61 | * function (array $row): array-key 62 | * { 63 | * // return the index value corresponding to $row 64 | * } 65 | * ``` 66 | * 67 | * @psalm-param IndexBy|null $indexBy 68 | */ 69 | public function indexBy(Closure|string|null $indexBy): static; 70 | 71 | /** 72 | * Sets the callback, to be called on all rows of the query result before returning them. 73 | * 74 | * For example: 75 | * 76 | * ```php 77 | * function (array $rows): array { 78 | * foreach ($rows as &$row) { 79 | * $row['name'] = strtoupper($row['name']); 80 | * } 81 | * return $rows; 82 | * } 83 | * ``` 84 | * 85 | * @psalm-param ResultCallbackOne|null $resultCallback 86 | */ 87 | public function resultCallback(Closure|null $resultCallback): static; 88 | } 89 | -------------------------------------------------------------------------------- /src/Query/QueryExpressionBuilder.php: -------------------------------------------------------------------------------- 1 | queryBuilder->build($expression, $params); 33 | return "($sql)"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/QueryBuilder/ColumnDefinitionBuilderInterface.php: -------------------------------------------------------------------------------- 1 | expressions; 21 | } 22 | 23 | public static function fromArrayDefinition(string $operator, array $operands): self 24 | { 25 | return new static($operands); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/AndCondition.php: -------------------------------------------------------------------------------- 1 | column; 27 | } 28 | 29 | public function getIntervalEnd(): mixed 30 | { 31 | return $this->intervalEnd; 32 | } 33 | 34 | public function getIntervalStart(): mixed 35 | { 36 | return $this->intervalStart; 37 | } 38 | 39 | public function getOperator(): string 40 | { 41 | return $this->operator; 42 | } 43 | 44 | /** 45 | * Creates a condition based on the given operator and operands. 46 | * 47 | * @throws InvalidArgumentException If the number of operands isn't 3. 48 | */ 49 | public static function fromArrayDefinition(string $operator, array $operands): self 50 | { 51 | if (!isset($operands[0], $operands[1], $operands[2])) { 52 | throw new InvalidArgumentException("Operator '$operator' requires three operands."); 53 | } 54 | 55 | return new self(self::validateColumn($operator, $operands[0]), $operator, $operands[1], $operands[2]); 56 | } 57 | 58 | /** 59 | * Validates the given column to be string or `ExpressionInterface`. 60 | * 61 | * @throws InvalidArgumentException If the column isn't a string or `ExpressionInterface`. 62 | */ 63 | private static function validateColumn(string $operator, mixed $column): string|ExpressionInterface 64 | { 65 | if (is_string($column) || $column instanceof ExpressionInterface) { 66 | return $column; 67 | } 68 | 69 | throw new InvalidArgumentException( 70 | "Operator '$operator' requires column to be string or ExpressionInterface." 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/AbstractOverlapsConditionBuilder.php: -------------------------------------------------------------------------------- 1 | queryBuilder->buildExpression($column); 24 | } 25 | 26 | return $this->queryBuilder->getQuoter()->quoteColumnName($column); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/BetweenConditionBuilder.php: -------------------------------------------------------------------------------- 1 | getOperator(); 38 | $column = $expression->getColumn(); 39 | $column = $column instanceof ExpressionInterface ? $this->queryBuilder->buildExpression($column) : $column; 40 | 41 | if (!str_contains($column, '(')) { 42 | $column = $this->queryBuilder->getQuoter()->quoteColumnName($column); 43 | } 44 | 45 | $phName1 = $this->createPlaceholder($expression->getIntervalStart(), $params); 46 | $phName2 = $this->createPlaceholder($expression->getIntervalEnd(), $params); 47 | 48 | return "$column $operator $phName1 AND $phName2"; 49 | } 50 | 51 | /** 52 | * Attaches `$value` to `$params` array and return placeholder. 53 | * 54 | * @throws Exception 55 | * @throws InvalidArgumentException 56 | * @throws InvalidConfigException 57 | * @throws NotSupportedException 58 | */ 59 | protected function createPlaceholder(mixed $value, array &$params): string 60 | { 61 | if ($value instanceof ExpressionInterface) { 62 | return $this->queryBuilder->buildExpression($value, $params); 63 | } 64 | 65 | return $this->queryBuilder->bindParam($value, $params); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/ExistsConditionBuilder.php: -------------------------------------------------------------------------------- 1 | getOperator(); 35 | $query = $expression->getQuery(); 36 | $sql = $this->queryBuilder->buildExpression($query, $params); 37 | return "$operator $sql"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/HashConditionBuilder.php: -------------------------------------------------------------------------------- 1 | getHash() ?? []; 43 | $parts = []; 44 | 45 | /** 46 | * @psalm-var array $hash 47 | */ 48 | foreach ($hash as $column => $value) { 49 | if (is_iterable($value) || $value instanceof QueryInterface) { 50 | /** IN condition */ 51 | $parts[] = $this->queryBuilder->buildCondition(new InCondition($column, 'IN', $value), $params); 52 | } else { 53 | if (!str_contains($column, '(')) { 54 | $column = $this->queryBuilder->getQuoter()->quoteColumnName($column); 55 | } 56 | 57 | if ($value === null) { 58 | $parts[] = "$column IS NULL"; 59 | } elseif ($value instanceof ExpressionInterface) { 60 | $parts[] = "$column=" . $this->queryBuilder->buildExpression($value, $params); 61 | } else { 62 | $phName = $this->queryBuilder->bindParam($value, $params); 63 | $parts[] = "$column=$phName"; 64 | } 65 | } 66 | } 67 | 68 | return (count($parts) === 1) ? $parts[0] : ('(' . implode(') AND (', $parts) . ')'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/NotConditionBuilder.php: -------------------------------------------------------------------------------- 1 | getCondition(); 35 | 36 | if ($operand === '') { 37 | return ''; 38 | } 39 | 40 | $expressionValue = $this->queryBuilder->buildCondition($operand, $params); 41 | 42 | return "{$this->getNegationOperator()} ($expressionValue)"; 43 | } 44 | 45 | protected function getNegationOperator(): string 46 | { 47 | return 'NOT'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Builder/SimpleConditionBuilder.php: -------------------------------------------------------------------------------- 1 | getOperator(); 38 | $column = $expression->getColumn(); 39 | /** @psalm-var mixed $value */ 40 | $value = $expression->getValue(); 41 | 42 | if ($column instanceof ExpressionInterface) { 43 | $column = $this->queryBuilder->buildExpression($column, $params); 44 | } elseif (!str_contains($column, '(')) { 45 | $column = $this->queryBuilder->getQuoter()->quoteColumnName($column); 46 | } 47 | 48 | if ($value === null) { 49 | return "$column $operator NULL"; 50 | } 51 | 52 | if ($value instanceof ExpressionInterface) { 53 | return "$column $operator {$this->queryBuilder->buildExpression($value, $params)}"; 54 | } 55 | 56 | $phName = $this->queryBuilder->bindParam($value, $params); 57 | 58 | return "$column $operator $phName"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/ExistsCondition.php: -------------------------------------------------------------------------------- 1 | operator; 23 | } 24 | 25 | public function getQuery(): QueryInterface 26 | { 27 | return $this->query; 28 | } 29 | 30 | /** 31 | * Creates a condition based on the given operator and operands. 32 | * 33 | * @throws InvalidArgumentException If the number of operands isn't 1, and the first operand isn't a query object. 34 | */ 35 | public static function fromArrayDefinition(string $operator, array $operands): self 36 | { 37 | if (isset($operands[0]) && $operands[0] instanceof QueryInterface) { 38 | return new self($operator, $operands[0]); 39 | } 40 | 41 | throw new InvalidArgumentException('Sub query for EXISTS operator must be a Query object.'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/HashCondition.php: -------------------------------------------------------------------------------- 1 | hash; 21 | } 22 | 23 | /** 24 | * Creates a condition based on the given operator and operands. 25 | */ 26 | public static function fromArrayDefinition(string $operator, array $operands): self 27 | { 28 | return new self($operands); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Interface/BetweenColumnsConditionInterface.php: -------------------------------------------------------------------------------- 1 | ` or `<=`. 39 | */ 40 | public function getOperator(): string; 41 | 42 | /** 43 | * @return array|ExpressionInterface|int|Iterator|string|null The value to the right of {@see operator}. 44 | */ 45 | public function getValue(): array|int|string|Iterator|ExpressionInterface|null; 46 | 47 | /** 48 | * @return bool|null Whether the comparison is case-sensitive. `null` means using the default behavior. 49 | */ 50 | public function getCaseSensitive(): ?bool; 51 | } 52 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/Interface/NotConditionInterface.php: -------------------------------------------------------------------------------- 1 | ` or `<=`. 21 | */ 22 | public function getOperator(): string; 23 | 24 | /** 25 | * @return mixed The value to the right of {@see operator}. 26 | */ 27 | public function getValue(): mixed; 28 | } 29 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/JsonOverlapsCondition.php: -------------------------------------------------------------------------------- 1 | condition; 28 | } 29 | 30 | /** 31 | * Creates a condition based on the given operator and operands. 32 | * 33 | * @throws InvalidArgumentException If the number of operands isn't 1. 34 | */ 35 | public static function fromArrayDefinition(string $operator, array $operands): self 36 | { 37 | return new self(self::validateCondition($operator, $operands)); 38 | } 39 | 40 | /** 41 | * Validate the given condition have at least 1 condition and to be `array`, `string`, `null` or `ExpressionInterface`. 42 | * 43 | * @throws InvalidArgumentException If the number of operands isn't 1. 44 | */ 45 | private static function validateCondition(string $operator, array $condition): ExpressionInterface|array|null|string 46 | { 47 | if (count($condition) !== 1) { 48 | throw new InvalidArgumentException("Operator '$operator' requires exactly one operand."); 49 | } 50 | 51 | /** @psalm-var mixed $firstValue */ 52 | $firstValue = array_shift($condition); 53 | 54 | if ( 55 | is_array($firstValue) || 56 | $firstValue instanceof ExpressionInterface || 57 | is_string($firstValue) || 58 | $firstValue === null 59 | ) { 60 | return $firstValue; 61 | } 62 | 63 | throw new InvalidArgumentException( 64 | "Operator '$operator' requires condition to be array, string, null or ExpressionInterface." 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/QueryBuilder/Condition/OrCondition.php: -------------------------------------------------------------------------------- 1 | column; 26 | } 27 | 28 | public function getOperator(): string 29 | { 30 | return $this->operator; 31 | } 32 | 33 | public function getValue(): mixed 34 | { 35 | return $this->value; 36 | } 37 | 38 | /** 39 | * Creates a condition based on the given operator and operands. 40 | * 41 | * @throws InvalidArgumentException If the number of operands isn't 2. 42 | */ 43 | public static function fromArrayDefinition(string $operator, array $operands): self 44 | { 45 | if (isset($operands[0]) && array_key_exists(1, $operands)) { 46 | return new self(self::validateColumn($operator, $operands[0]), $operator, $operands[1]); 47 | } 48 | 49 | throw new InvalidArgumentException("Operator '$operator' requires two operands."); 50 | } 51 | 52 | /** 53 | * Validate the given column to be `string` or `ExpressionInterface`. 54 | * 55 | * @throws InvalidArgumentException If the column isn't a `string` or `ExpressionInterface`. 56 | */ 57 | private static function validateColumn(string $operator, mixed $column): string|ExpressionInterface 58 | { 59 | if (is_string($column) || $column instanceof ExpressionInterface) { 60 | return $column; 61 | } 62 | 63 | throw new InvalidArgumentException( 64 | "Operator '$operator' requires column to be string or ExpressionInterface." 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Schema/Column/AbstractArrayColumn.php: -------------------------------------------------------------------------------- 1 | column = $column; 41 | return $this; 42 | } 43 | 44 | /** 45 | * @return ColumnInterface|null The column of an array item. 46 | * @psalm-mutation-free 47 | */ 48 | public function getColumn(): ColumnInterface|null 49 | { 50 | return $this->column; 51 | } 52 | 53 | /** 54 | * Set dimension of an array, must be greater than `0`. 55 | * 56 | * @psalm-param positive-int $dimension 57 | */ 58 | public function dimension(int $dimension): static 59 | { 60 | $this->dimension = $dimension; 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return int The dimension of the array. 66 | * 67 | * @psalm-return positive-int 68 | * @psalm-mutation-free 69 | */ 70 | public function getDimension(): int 71 | { 72 | return $this->dimension; 73 | } 74 | 75 | /** @psalm-mutation-free */ 76 | public function getPhpType(): string 77 | { 78 | return PhpType::ARRAY; 79 | } 80 | 81 | /** 82 | * @param iterable|LazyArrayInterface|QueryInterface|string|null $value 83 | * @psalm-suppress MoreSpecificImplementedParamType 84 | */ 85 | public function dbTypecast(mixed $value): ExpressionInterface|null 86 | { 87 | if ($value === null || $value instanceof ExpressionInterface) { 88 | return $value; 89 | } 90 | 91 | return new ArrayExpression($value, $this); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Schema/Column/AbstractJsonColumn.php: -------------------------------------------------------------------------------- 1 | getDbType()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Schema/Column/AbstractStructuredColumn.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | protected array $columns = []; 27 | 28 | /** 29 | * Set columns of the structured type. 30 | * 31 | * @param ColumnInterface[] $columns The metadata of the structured type columns. 32 | * @psalm-param array $columns 33 | */ 34 | public function columns(array $columns): static 35 | { 36 | $this->columns = $columns; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Get the metadata of the structured type columns. 42 | * 43 | * @return ColumnInterface[] 44 | * @psalm-return array 45 | * @psalm-mutation-free 46 | */ 47 | public function getColumns(): array 48 | { 49 | return $this->columns; 50 | } 51 | 52 | /** @psalm-mutation-free */ 53 | public function getPhpType(): string 54 | { 55 | return PhpType::ARRAY; 56 | } 57 | 58 | /** 59 | * @param array|object|string|null $value 60 | * @psalm-suppress MoreSpecificImplementedParamType 61 | */ 62 | public function dbTypecast(mixed $value): ExpressionInterface|null 63 | { 64 | if ($value === null || $value instanceof ExpressionInterface) { 65 | return $value; 66 | } 67 | 68 | return new StructuredExpression($value, $this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Schema/Column/ArrayColumn.php: -------------------------------------------------------------------------------- 1 | getColumn(), $this->getDimension()))->getValue(); 26 | } 27 | 28 | return $value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Schema/Column/ArrayLazyColumn.php: -------------------------------------------------------------------------------- 1 | getColumn(), $this->getDimension()); 26 | } 27 | 28 | return $value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Schema/Column/BigIntColumn.php: -------------------------------------------------------------------------------- 1 | $this->dbTypecastString($value), 36 | GettypeResult::NULL => null, 37 | GettypeResult::INTEGER => $value, 38 | GettypeResult::DOUBLE => $this->dbTypecastString((string) $value), 39 | GettypeResult::BOOLEAN => $value ? 1 : 0, 40 | GettypeResult::OBJECT => match (true) { 41 | $value instanceof ExpressionInterface => $value, 42 | $value instanceof BackedEnum => is_int($value->value) 43 | ? $value->value 44 | : $this->dbTypecastString($value->value), 45 | $value instanceof DateTimeInterface => $value->getTimestamp(), 46 | $value instanceof Stringable => $this->dbTypecastString((string) $value), 47 | default => $this->throwWrongTypeException($value::class), 48 | }, 49 | default => $this->throwWrongTypeException(gettype($value)), 50 | }; 51 | } 52 | 53 | /** @psalm-mutation-free */ 54 | public function getPhpType(): string 55 | { 56 | return PhpType::STRING; 57 | } 58 | 59 | public function phpTypecast(mixed $value): string|null 60 | { 61 | if ($value === null) { 62 | return null; 63 | } 64 | 65 | return (string) $value; 66 | } 67 | 68 | protected function dbTypecastString(string $value): int|string|null 69 | { 70 | if ($value === '') { 71 | return null; 72 | } 73 | 74 | return PHP_INT_MAX >= $value && $value >= PHP_INT_MIN ? (int) $value : $value; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Schema/Column/BinaryColumn.php: -------------------------------------------------------------------------------- 1 | new Param($value, PDO::PARAM_LOB), 28 | GettypeResult::RESOURCE => $value, 29 | GettypeResult::NULL => null, 30 | GettypeResult::INTEGER => (string) $value, 31 | GettypeResult::DOUBLE => (string) $value, 32 | GettypeResult::BOOLEAN => $value ? '1' : '0', 33 | GettypeResult::OBJECT => match (true) { 34 | $value instanceof ExpressionInterface => $value, 35 | $value instanceof BackedEnum => (string) $value->value, 36 | $value instanceof Stringable => (string) $value, 37 | default => $this->throwWrongTypeException($value::class), 38 | }, 39 | default => $this->throwWrongTypeException(gettype($value)), 40 | }; 41 | } 42 | 43 | public function phpTypecast(mixed $value): mixed 44 | { 45 | return $value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Schema/Column/BitColumn.php: -------------------------------------------------------------------------------- 1 | null, 26 | default => $value instanceof ExpressionInterface ? $value : (int) $value, 27 | }; 28 | } 29 | 30 | /** @psalm-mutation-free */ 31 | public function getPhpType(): string 32 | { 33 | return PhpType::INT; 34 | } 35 | 36 | public function phpTypecast(mixed $value): int|null 37 | { 38 | if ($value === null) { 39 | return null; 40 | } 41 | 42 | return (int) $value; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Schema/Column/BooleanColumn.php: -------------------------------------------------------------------------------- 1 | true, 22 | false => false, 23 | null, '' => null, 24 | default => $value instanceof ExpressionInterface ? $value : (bool) $value, 25 | }; 26 | } 27 | 28 | /** @psalm-mutation-free */ 29 | public function getPhpType(): string 30 | { 31 | return PhpType::BOOL; 32 | } 33 | 34 | public function phpTypecast(mixed $value): bool|null 35 | { 36 | if ($value === null) { 37 | return null; 38 | } 39 | 40 | return $value && $value !== "\0"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Schema/Column/DoubleColumn.php: -------------------------------------------------------------------------------- 1 | $value, 30 | GettypeResult::INTEGER => $value, 31 | GettypeResult::NULL => null, 32 | GettypeResult::STRING => $value === '' ? null : (float) $value, 33 | GettypeResult::BOOLEAN => $value ? 1.0 : 0.0, 34 | GettypeResult::OBJECT => match (true) { 35 | $value instanceof ExpressionInterface => $value, 36 | $value instanceof BackedEnum => $value->value === '' 37 | ? null 38 | : (is_int($value->value) ? $value->value : (float) $value->value), 39 | $value instanceof DateTimeInterface => (float) $value->format('U.u'), 40 | $value instanceof Stringable => ($val = (string) $value) === '' ? null : (float) $val, 41 | default => $this->throwWrongTypeException($value::class), 42 | }, 43 | default => $this->throwWrongTypeException(gettype($value)), 44 | }; 45 | } 46 | 47 | /** @psalm-mutation-free */ 48 | public function getPhpType(): string 49 | { 50 | return PhpType::FLOAT; 51 | } 52 | 53 | public function phpTypecast(mixed $value): float|null 54 | { 55 | if ($value === null) { 56 | return null; 57 | } 58 | 59 | return (float) $value; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Schema/Column/IntegerColumn.php: -------------------------------------------------------------------------------- 1 | $value, 29 | GettypeResult::NULL => null, 30 | GettypeResult::STRING => $value === '' ? null : (int) $value, 31 | GettypeResult::DOUBLE => (int) $value, 32 | GettypeResult::BOOLEAN => $value ? 1 : 0, 33 | GettypeResult::OBJECT => match (true) { 34 | $value instanceof ExpressionInterface => $value, 35 | $value instanceof BackedEnum => $value->value === '' ? null : (int) $value->value, 36 | $value instanceof DateTimeInterface => $value->getTimestamp(), 37 | $value instanceof Stringable => ($val = (string) $value) === '' ? null : (int) $val, 38 | default => $this->throwWrongTypeException($value::class), 39 | }, 40 | default => $this->throwWrongTypeException(gettype($value)), 41 | }; 42 | } 43 | 44 | /** @psalm-mutation-free */ 45 | public function getPhpType(): string 46 | { 47 | return PhpType::INT; 48 | } 49 | 50 | public function phpTypecast(mixed $value): int|null 51 | { 52 | if ($value === null) { 53 | return null; 54 | } 55 | 56 | return (int) $value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Schema/Column/JsonColumn.php: -------------------------------------------------------------------------------- 1 | new JsonLazyArray($value), 26 | default => json_decode($value, true, 512, JSON_THROW_ON_ERROR), 27 | }; 28 | } 29 | 30 | return $value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Schema/Column/StringColumn.php: -------------------------------------------------------------------------------- 1 | $value, 27 | GettypeResult::RESOURCE => $value, 28 | GettypeResult::NULL => null, 29 | GettypeResult::INTEGER => (string) $value, 30 | GettypeResult::DOUBLE => (string) $value, 31 | GettypeResult::BOOLEAN => $value ? '1' : '0', 32 | GettypeResult::OBJECT => match (true) { 33 | $value instanceof ExpressionInterface => $value, 34 | $value instanceof BackedEnum => (string) $value->value, 35 | $value instanceof Stringable => (string) $value, 36 | default => $this->throwWrongTypeException($value::class), 37 | }, 38 | default => $this->throwWrongTypeException(gettype($value)), 39 | }; 40 | } 41 | 42 | /** @psalm-mutation-free */ 43 | public function getPhpType(): string 44 | { 45 | return PhpType::STRING; 46 | } 47 | 48 | public function phpTypecast(mixed $value): string|null 49 | { 50 | /** @var string|null $value */ 51 | return $value; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Schema/Column/StructuredColumn.php: -------------------------------------------------------------------------------- 1 | getColumns()))->getValue(); 26 | } 27 | 28 | return $value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Schema/Column/StructuredLazyColumn.php: -------------------------------------------------------------------------------- 1 | getColumns()); 26 | } 27 | 28 | return $value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Schema/Data/JsonLazyArray.php: -------------------------------------------------------------------------------- 1 | 24 | * @template-implements IteratorAggregate 25 | */ 26 | final class JsonLazyArray implements LazyArrayInterface, ArrayAccess, Countable, JsonSerializable, IteratorAggregate 27 | { 28 | use LazyArrayTrait; 29 | 30 | protected array|string $value; 31 | 32 | /** 33 | * @param string $value The string retrieved value from the database that can be parsed into an array. 34 | */ 35 | public function __construct( 36 | string $value 37 | ) { 38 | $this->value = $value; 39 | } 40 | 41 | /** 42 | * Prepares the value to be used as an array or throws an exception if it's impossible. 43 | * 44 | * @psalm-assert array $this->value 45 | */ 46 | protected function prepareValue(): void 47 | { 48 | if (is_string($this->value)) { 49 | $value = json_decode($this->value, true, 512, JSON_THROW_ON_ERROR); 50 | 51 | if (!is_array($value)) { 52 | throw new InvalidArgumentException('JSON value must be a valid string array representation.'); 53 | } 54 | 55 | $this->value = $value; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Schema/Data/LazyArray.php: -------------------------------------------------------------------------------- 1 | value; 27 | } 28 | 29 | /** 30 | * Returns parsed and typecasted value. 31 | */ 32 | public function getValue(): array 33 | { 34 | $this->prepareValue(); 35 | 36 | return $this->value; 37 | } 38 | 39 | public function jsonSerialize(): array 40 | { 41 | return $this->getValue(); 42 | } 43 | 44 | /** 45 | * @param int|string $offset The offset to check. 46 | */ 47 | public function offsetExists(mixed $offset): bool 48 | { 49 | $this->prepareValue(); 50 | 51 | return isset($this->value[$offset]); 52 | } 53 | 54 | /** 55 | * @param int|string $offset The offset to retrieve. 56 | */ 57 | public function offsetGet(mixed $offset): mixed 58 | { 59 | $this->prepareValue(); 60 | 61 | return $this->value[$offset]; 62 | } 63 | 64 | /** 65 | * @param int|string $offset The offset to assign the value to. 66 | */ 67 | public function offsetSet(mixed $offset, mixed $value): void 68 | { 69 | $this->prepareValue(); 70 | 71 | $this->value[$offset] = $value; 72 | } 73 | 74 | /** 75 | * @param int|string $offset The offset to unset. 76 | */ 77 | public function offsetUnset(mixed $offset): void 78 | { 79 | $this->prepareValue(); 80 | 81 | unset($this->value[$offset]); 82 | } 83 | 84 | public function count(): int 85 | { 86 | $this->prepareValue(); 87 | 88 | return count($this->value); 89 | } 90 | 91 | public function getIterator(): ArrayIterator 92 | { 93 | $this->prepareValue(); 94 | 95 | return new ArrayIterator($this->value); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Schema/Data/StructuredLazyArray.php: -------------------------------------------------------------------------------- 1 | getColumnBuilderClass(); 34 | 35 | $column = $columnBuilderClass::$buildingMethod(...$args); 36 | 37 | $this->assertInstanceOf($expectedInstanceOf, $column); 38 | $this->assertSame($expectedType, $column->getType()); 39 | 40 | $columnMethodResults = array_merge( 41 | ColumnBuilderProvider::DEFAULT_COLUMN_METHOD_RESULTS, 42 | $expectedMethodResults, 43 | ); 44 | 45 | foreach ($columnMethodResults as $method => $result) { 46 | $this->assertSame($result, $column->$method()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/AbstractColumnDefinitionParserTest.php: -------------------------------------------------------------------------------- 1 | createColumnDefinitionParser(); 29 | 30 | $this->assertSame($expected, $parser->parse($definition)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/AbstractColumnTest.php: -------------------------------------------------------------------------------- 1 | assertSame($type, $column->getType()); 24 | $this->assertSame($phpType, $column->getPhpType()); 25 | } 26 | 27 | #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')] 28 | public function testDbTypecastColumns(ColumnInterface $column, array $values) 29 | { 30 | // Set the timezone for testing purposes, could be any timezone except UTC 31 | $oldDatetime = date_default_timezone_get(); 32 | date_default_timezone_set('America/New_York'); 33 | 34 | foreach ($values as [$expected, $value]) { 35 | if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { 36 | $this->assertEquals($expected, $column->dbTypecast($value)); 37 | } else { 38 | $this->assertSame($expected, $column->dbTypecast($value)); 39 | } 40 | } 41 | 42 | date_default_timezone_set($oldDatetime); 43 | } 44 | 45 | #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')] 46 | public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value) 47 | { 48 | $type = is_object($value) ? $value::class : gettype($value); 49 | 50 | $this->expectException(InvalidArgumentException::class); 51 | $this->expectExceptionMessage("Wrong $type value for {$column->getType()} column."); 52 | 53 | $column->dbTypecast($value); 54 | } 55 | 56 | #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')] 57 | public function testPhpTypecastColumns(ColumnInterface $column, array $values) 58 | { 59 | foreach ($values as [$expected, $value]) { 60 | if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { 61 | $this->assertEquals($expected, $column->phpTypecast($value)); 62 | } else { 63 | $this->assertSame($expected, $column->phpTypecast($value)); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/AbstractSchemaTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 23 | 24 | $schema = $db->getSchema(); 25 | 26 | $this->assertNull($schema->getDefaultSchema()); 27 | } 28 | 29 | public function testGetDataType(): void 30 | { 31 | $values = [ 32 | [null, DataType::NULL], 33 | ['', DataType::STRING], 34 | ['hello', DataType::STRING], 35 | [0, DataType::INTEGER], 36 | [1, DataType::INTEGER], 37 | [1337, DataType::INTEGER], 38 | [true, DataType::BOOLEAN], 39 | [false, DataType::BOOLEAN], 40 | [$fp = fopen(__FILE__, 'rb'), DataType::LOB], 41 | ]; 42 | 43 | $db = $this->getConnection(); 44 | 45 | $schema = $db->getSchema(); 46 | 47 | foreach ($values as $value) { 48 | $this->assertSame( 49 | $value[1], 50 | $schema->getDataType($value[0]), 51 | 'type for value ' . print_r($value[0], true) . ' does not match.', 52 | ); 53 | } 54 | 55 | fclose($fp); 56 | } 57 | 58 | public function testRefresh(): void 59 | { 60 | $db = $this->getConnection(); 61 | 62 | $schema = $db->getSchema(); 63 | $schema->refresh(); 64 | 65 | $this->assertSame([], Assert::getInaccessibleProperty($schema, 'tableMetadata')); 66 | $this->assertSame([], Assert::getInaccessibleProperty($schema, 'tableNames')); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/AbstractSqlParserTest.php: -------------------------------------------------------------------------------- 1 | createSqlParser($sql); 21 | 22 | $this->assertSame($expectedPlaceholder, $parser->getNextPlaceholder($position)); 23 | $this->assertSame($expectedPosition, $position); 24 | } 25 | 26 | /** @dataProvider \Yiisoft\Db\Tests\Provider\SqlParserProvider::getAllPlaceholders */ 27 | public function testGetAllPlaceholders(string $sql, array $expectedPlaceholders, array $expectedPositions): void 28 | { 29 | $parser = $this->createSqlParser($sql); 30 | 31 | $placeholders = []; 32 | $positions = []; 33 | 34 | while (null !== $placeholder = $parser->getNextPlaceholder($position)) { 35 | $placeholders[] = $placeholder; 36 | $positions[] = $position; 37 | } 38 | 39 | $this->assertSame($expectedPlaceholders, $placeholders); 40 | $this->assertSame($expectedPositions, $positions); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Common/CommonConnectionTest.php: -------------------------------------------------------------------------------- 1 | getConnection(true); 21 | 22 | $this->expectException(Exception::class); 23 | 24 | $db->transaction( 25 | static function () use ($db) { 26 | $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute(); 27 | 28 | throw new Exception('Exception in transaction shortcut'); 29 | } 30 | ); 31 | $profilesCount = $db->createCommand( 32 | <<queryScalar(); 36 | 37 | $this->assertSame(0, $profilesCount, 'profile should not be inserted in transaction shortcut'); 38 | 39 | $db->close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Db/Command/ParamBuilderTest.php: -------------------------------------------------------------------------------- 1 | 1, 'name' => 'test', 'expression' => new Expression('NOW()')]; 21 | $expression = new Expression('id = :id AND name = :name AND expression = :expression', $params); 22 | $paramBuilder = new ParamBuilder(); 23 | 24 | $this->assertSame(':pv3', $paramBuilder->build($expression, $params)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Db/Connection/ConnectionTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 26 | 27 | $this->assertNull($db->getTableSchema('non_existing_table')); 28 | } 29 | 30 | public function testSerialized(): void 31 | { 32 | $this->expectException(NotSupportedException::class); 33 | $this->expectExceptionMessage( 34 | 'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.' 35 | ); 36 | 37 | parent::testSerialized(); 38 | } 39 | 40 | public function testConstructColumnFactory(): void 41 | { 42 | $columnFactory = new ColumnFactory(); 43 | 44 | $db = new Connection($this->getDriver(), DbHelper::getSchemaCache(), $columnFactory); 45 | 46 | $this->assertSame($columnFactory, $db->getColumnFactory()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Db/Connection/DsnTest.php: -------------------------------------------------------------------------------- 1 | assertSame('mysql', $dsn->getDriver()); 22 | $this->assertSame('localhost', $dsn->getHost()); 23 | $this->assertSame('yiitest', $dsn->getDatabaseName()); 24 | $this->assertNull($dsn->getPort()); 25 | $this->assertSame([], $dsn->getOptions()); 26 | } 27 | 28 | public function testGetDatabaseName(): void 29 | { 30 | $dsn = new Dsn('mysql', 'localhost', 'yiitest', '3306', ['charset' => 'utf8']); 31 | 32 | $this->assertSame('yiitest', $dsn->getDatabaseName()); 33 | } 34 | 35 | public function testGetDriver(): void 36 | { 37 | $dsn = new Dsn('mysql', 'localhost', 'yiitest', '3306', ['charset' => 'utf8']); 38 | 39 | $this->assertSame('mysql', $dsn->getDriver()); 40 | } 41 | 42 | public function testGetDsn(): void 43 | { 44 | $dsn = new Dsn('mysql', 'localhost', 'yiitest', '3306', ['charset' => 'utf8']); 45 | 46 | $this->assertSame('mysql:host=localhost;dbname=yiitest;port=3306;charset=utf8', $dsn->asString()); 47 | $this->assertSame('mysql:host=localhost;dbname=yiitest;port=3306;charset=utf8', $dsn->__toString()); 48 | } 49 | 50 | public function testGetDsnWithoutDatabaseName(): void 51 | { 52 | $dsn = new Dsn('mysql', 'localhost', '', '3306', ['charset' => 'utf8']); 53 | 54 | $this->assertSame('mysql:host=localhost;port=3306;charset=utf8', $dsn->asString()); 55 | $this->assertSame('mysql:host=localhost;port=3306;charset=utf8', $dsn->__toString()); 56 | $this->assertEmpty($dsn->getDatabaseName()); 57 | 58 | $dsn = new Dsn('mysql', 'localhost', null, '3306', ['charset' => 'utf8']); 59 | 60 | $this->assertSame('mysql:host=localhost;port=3306;charset=utf8', $dsn->asString()); 61 | $this->assertSame('mysql:host=localhost;port=3306;charset=utf8', $dsn->__toString()); 62 | $this->assertNull($dsn->getDatabaseName()); 63 | } 64 | 65 | public function testGetHost(): void 66 | { 67 | $dsn = new Dsn('mysql', 'localhost', 'yiitest', '3306', ['charset' => 'utf8']); 68 | 69 | $this->assertSame('localhost', $dsn->getHost()); 70 | } 71 | 72 | public function testGetPort(): void 73 | { 74 | $dsn = new Dsn('mysql', 'localhost', 'yiitest', '3306', ['charset' => 'utf8']); 75 | 76 | $this->assertSame('3306', $dsn->getPort()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/Db/Constraint/CheckConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertSame('', $checkConstraint->getExpression()); 22 | 23 | $checkConstraint = $checkConstraint->expression('expression'); 24 | 25 | $this->assertSame('expression', $checkConstraint->getExpression()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Db/Constraint/ConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertNull($constraint->getColumnNames()); 23 | 24 | $constraint->columnNames('columnNames'); 25 | 26 | $this->assertSame('columnNames', $constraint->getColumnNames()); 27 | 28 | $constraint->columnNames(['columnNames']); 29 | 30 | $this->assertSame(['columnNames'], $constraint->getColumnNames()); 31 | } 32 | 33 | public function testGetName(): void 34 | { 35 | $constraint = new Constraint(); 36 | 37 | $this->assertNull($constraint->getName()); 38 | 39 | $constraint->name('name'); 40 | 41 | $this->assertSame('name', $constraint->getName()); 42 | 43 | $constraint->name(new stdClass()); 44 | 45 | $this->assertInstanceOf(stdClass::class, $constraint->getName()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Db/Constraint/DefaultValueConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertNull($defaultValueConstraint->getValue()); 23 | 24 | $defaultValueConstraint = $defaultValueConstraint->value('value'); 25 | 26 | $this->assertSame('value', $defaultValueConstraint->getValue()); 27 | 28 | $defaultValueConstraint = $defaultValueConstraint->value(new stdClass()); 29 | 30 | $this->assertInstanceOf(stdClass::class, $defaultValueConstraint->getValue()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Db/Constraint/ForeignKeyConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertNull($foreignKeyConstraint->getForeignSchemaName()); 22 | 23 | $foreignKeyConstraint = $foreignKeyConstraint->foreignSchemaName('foreignSchemaName'); 24 | 25 | $this->assertSame('foreignSchemaName', $foreignKeyConstraint->getForeignSchemaName()); 26 | } 27 | 28 | public function testGetForeignTableName(): void 29 | { 30 | $foreignKeyConstraint = new ForeignKeyConstraint(); 31 | 32 | $this->assertNull($foreignKeyConstraint->getForeignTableName()); 33 | 34 | $foreignKeyConstraint = $foreignKeyConstraint->foreignTableName('foreignTableName'); 35 | 36 | $this->assertSame('foreignTableName', $foreignKeyConstraint->getForeignTableName()); 37 | } 38 | 39 | public function testGetForeignColumnNames(): void 40 | { 41 | $foreignKeyConstraint = new ForeignKeyConstraint(); 42 | 43 | $this->assertSame([], $foreignKeyConstraint->getForeignColumnNames()); 44 | 45 | $foreignKeyConstraint = $foreignKeyConstraint->foreignColumnNames(['foreignColumnNames']); 46 | 47 | $this->assertSame(['foreignColumnNames'], $foreignKeyConstraint->getForeignColumnNames()); 48 | } 49 | 50 | public function testGetOnUpdate(): void 51 | { 52 | $foreignKeyConstraint = new ForeignKeyConstraint(); 53 | 54 | $this->assertNull($foreignKeyConstraint->getOnUpdate()); 55 | 56 | $foreignKeyConstraint = $foreignKeyConstraint->onUpdate('onUpdate'); 57 | 58 | $this->assertSame('onUpdate', $foreignKeyConstraint->getOnUpdate()); 59 | } 60 | 61 | public function testGetOnDelete(): void 62 | { 63 | $foreignKeyConstraint = new ForeignKeyConstraint(); 64 | 65 | $this->assertNull($foreignKeyConstraint->getOnDelete()); 66 | 67 | $foreignKeyConstraint = $foreignKeyConstraint->onDelete('onDelete'); 68 | 69 | $this->assertSame('onDelete', $foreignKeyConstraint->getOnDelete()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Db/Constraint/IndexConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertFalse($indexConstraint->isUnique()); 22 | 23 | $indexConstraint = $indexConstraint->unique(true); 24 | 25 | $this->assertTrue($indexConstraint->isUnique()); 26 | } 27 | 28 | public function testIsPrimary(): void 29 | { 30 | $indexConstraint = new IndexConstraint(); 31 | 32 | $this->assertFalse($indexConstraint->isPrimary()); 33 | 34 | $indexConstraint = $indexConstraint->primary(true); 35 | 36 | $this->assertTrue($indexConstraint->isPrimary()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Db/Driver/Pdo/PdoConnectionTest.php: -------------------------------------------------------------------------------- 1 | setDsn(''); 24 | $db = $this->getConnection(); 25 | 26 | $this->expectException(InvalidConfigException::class); 27 | $this->expectExceptionMessage('Connection::dsn cannot be empty.'); 28 | 29 | $db->open(); 30 | } 31 | 32 | public function testGetLastInsertID(): void 33 | { 34 | $db = $this->getConnection(); 35 | 36 | $this->expectException(InvalidCallException::class); 37 | $this->expectExceptionMessage('DB Connection is not active.'); 38 | 39 | $db->getLastInsertId(); 40 | } 41 | 42 | public function testQuoteValueString(): void 43 | { 44 | $db = $this->getConnection(); 45 | 46 | $string = 'test string'; 47 | 48 | $this->assertStringContainsString($string, $db->quoteValue($string)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Db/Driver/Pdo/PdoDriverTest.php: -------------------------------------------------------------------------------- 1 | attributes([PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); 24 | 25 | $this->assertSame( 26 | [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION], 27 | Assert::getInaccessibleProperty($pdoDriver, 'attributes'), 28 | ); 29 | } 30 | 31 | public function testGetDriverName(): void 32 | { 33 | $dsn = 'sqlite::memory:'; 34 | $pdoDriver = new PDODriver($dsn); 35 | 36 | $this->assertSame('db', $pdoDriver->getDriverName()); 37 | } 38 | 39 | public function testSetCharSet(): void 40 | { 41 | $dsn = 'sqlite::memory:'; 42 | $pdoDriver = new PDODriver($dsn); 43 | $pdoDriver->charset('utf8'); 44 | 45 | $this->assertSame('utf8', $pdoDriver->getCharSet()); 46 | } 47 | 48 | public function testGetPassword(): void 49 | { 50 | $dsn = 'sqlite::memory:'; 51 | $pdoDriver = new PDODriver($dsn); 52 | $pdoDriver->password('password'); 53 | 54 | $this->assertSame('password', $pdoDriver->getPassword()); 55 | } 56 | 57 | public function testGetUsername(): void 58 | { 59 | $dsn = 'sqlite::memory:'; 60 | $pdoDriver = new PDODriver($dsn); 61 | $pdoDriver->username('username'); 62 | 63 | $this->assertSame('username', $pdoDriver->getUsername()); 64 | } 65 | 66 | public function testSensitiveParameter(): void 67 | { 68 | if (PHP_VERSION_ID < 80200) { 69 | $this->markTestSkipped('SensitiveParameterValue is not available in PHP < 8.2'); 70 | } 71 | $dsn = 'sqlite::memory:'; 72 | try { 73 | new PDODriver($dsn, password: null); 74 | } catch (\TypeError $e) { 75 | $this->assertTrue($e->getTrace()[0]['args'][2] instanceof \SensitiveParameterValue); 76 | } 77 | $pdoDriver = new PDODriver($dsn); 78 | try { 79 | $pdoDriver->password(null); 80 | } catch (\TypeError $e) { 81 | $this->assertTrue($e->getTrace()[0]['args'][0] instanceof \SensitiveParameterValue); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Db/Driver/Pdo/PdoServerInfoTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 18 | $serverInfo = $db->getServerInfo(); 19 | 20 | $this->expectException(NotSupportedException::class); 21 | $this->expectExceptionMessage('Yiisoft\Db\Driver\Pdo\PdoServerInfo::getTimezone is not supported by this DBMS.'); 22 | 23 | $serverInfo->getTimezone(); 24 | } 25 | 26 | public function testGetVersion(): void 27 | { 28 | $db = $this->getConnection(); 29 | $serverInfo = $db->getServerInfo(); 30 | 31 | $this->assertIsString($serverInfo->getVersion()); 32 | 33 | $db->close(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Db/Exception/ConvertExceptionTest.php: -------------------------------------------------------------------------------- 1 | run(); 24 | 25 | $this->assertSame($e, $exception->getPrevious()); 26 | $this->assertSame('test' . PHP_EOL . 'The SQL being executed was: ' . $rawSql, $exception->getMessage()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Db/Exception/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('test', $exception->getMessage()); 22 | } 23 | 24 | public function testExceptionStringable(): void 25 | { 26 | $exception = new Exception('test'); 27 | 28 | $this->assertStringContainsString( 29 | 'Yiisoft\Db\Exception\Exception: test in', 30 | (string) $exception, 31 | ); 32 | } 33 | 34 | public function testExceptionWithPrevious(): void 35 | { 36 | $previous = new \Exception('previous'); 37 | $exception = new Exception('test', [], $previous); 38 | 39 | $this->assertSame('test', $exception->getMessage()); 40 | $this->assertSame($previous, $exception->getPrevious()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Db/Expression/ArrayExpressionBuilderTest.php: -------------------------------------------------------------------------------- 1 | 1, 'b' => null], '{"a":1,"b":null}'], 35 | ['[1,2,3]', '[1,2,3]'], 36 | ['{"a":1,"b":null}', '{"a":1,"b":null}'], 37 | ]; 38 | } 39 | 40 | #[DataProvider('buildProvider')] 41 | public function testBuild(iterable|LazyArrayInterface|string $value, string $expected): void 42 | { 43 | $db = $this->getConnection(); 44 | $qb = $db->getQueryBuilder(); 45 | 46 | $params = []; 47 | $builder = new ArrayExpressionBuilder($qb); 48 | $expression = new ArrayExpression($value); 49 | 50 | $this->assertSame(':qp0', $builder->build($expression, $params)); 51 | $this->assertEquals([':qp0' => new Param($expected, DataType::STRING)], $params); 52 | } 53 | 54 | public function testBuildNull(): void 55 | { 56 | $db = $this->getConnection(); 57 | $qb = $db->getQueryBuilder(); 58 | 59 | $params = []; 60 | $builder = new ArrayExpressionBuilder($qb); 61 | $expression = new ArrayExpression(null); 62 | 63 | $this->assertSame('NULL', $builder->build($expression, $params)); 64 | $this->assertSame([], $params); 65 | } 66 | 67 | public function testBuildQueryExpression(): void 68 | { 69 | $db = $this->getConnection(); 70 | $qb = $db->getQueryBuilder(); 71 | 72 | $params = []; 73 | $builder = new ArrayExpressionBuilder($qb); 74 | $expression = new ArrayExpression((new Query($db))->select('json_field')->from('json_table')); 75 | 76 | $this->assertSame('(SELECT [json_field] FROM [json_table])', $builder->build($expression, $params)); 77 | $this->assertSame([], $params); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Db/Expression/ArrayExpressionTest.php: -------------------------------------------------------------------------------- 1 | assertSame($value, $expression->getValue()); 45 | $this->assertSame($type, $expression->getType()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Db/Expression/JsonExpressionTest.php: -------------------------------------------------------------------------------- 1 | assertSame($value, $expression->getValue()); 45 | $this->assertSame($type, $expression->getType()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Db/Expression/StructuredExpressionTest.php: -------------------------------------------------------------------------------- 1 | ColumnBuilder::money(10, 2), 28 | 'currency' => ColumnBuilder::char(3), 29 | ]); 30 | 31 | return [ 32 | [[5, 'USD'], null], 33 | [['value' => 5, 'currency' => 'USD'], null], 34 | [new ArrayIterator([5, 'USD']), $column], 35 | [new Query(self::getDb()), 'currency_money_structured'], 36 | [new JsonLazyArray('[5,"USD"]'), null], 37 | [(object) ['value' => 5, 'currency' => 'USD'], null], 38 | ]; 39 | } 40 | 41 | #[DataProvider('constructProvider')] 42 | public function testConstruct( 43 | array|object|string $value, 44 | AbstractStructuredColumn|string|null $type = null 45 | ): void { 46 | $expression = new StructuredExpression($value, $type); 47 | 48 | $this->assertSame($value, $expression->getValue()); 49 | $this->assertSame($type, $expression->getType()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Db/Helper/DbStringHelperTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl')); 18 | $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1')); 19 | $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1')); 20 | $this->assertTrue(DbStringHelper::isReadQuery('SELECT * FROM tbl WHERE id=1 LIMIT 1 OFFSET 1')); 21 | } 22 | 23 | public static function pascalCaseToIdProvider(): array 24 | { 25 | return [ 26 | ['photo\\album_controller', 'Photo\\AlbumController',], 27 | ['photo\\album\\controller', 'Photo\\Album\\Controller',], 28 | ['post_tag', 'PostTag',], 29 | ['post_tag', 'postTag'], 30 | ['foo_ybar', 'fooYBar',], 31 | ]; 32 | } 33 | 34 | public function testNormalizeFloat() 35 | { 36 | $this->assertSame('123', DbStringHelper::normalizeFloat(123)); 37 | $this->assertSame('-123', DbStringHelper::normalizeFloat(-123)); 38 | $this->assertSame('-2.5479E-70', DbStringHelper::normalizeFloat(-2.5479E-70)); 39 | $this->assertSame('2.5479E-70', DbStringHelper::normalizeFloat(2.5479E-70)); 40 | $this->assertSame('123.42', DbStringHelper::normalizeFloat(123.42)); 41 | $this->assertSame('-123.42', DbStringHelper::normalizeFloat(-123.42)); 42 | $this->assertSame('123.42', DbStringHelper::normalizeFloat('123.42')); 43 | $this->assertSame('-123.42', DbStringHelper::normalizeFloat('-123.42')); 44 | $this->assertSame('123.42', DbStringHelper::normalizeFloat('123,42')); 45 | $this->assertSame('-123.42', DbStringHelper::normalizeFloat('-123,42')); 46 | $this->assertSame('123123123.123', DbStringHelper::normalizeFloat('123.123.123,123')); 47 | $this->assertSame('123123123.123', DbStringHelper::normalizeFloat('123,123,123.123')); 48 | $this->assertSame('123123123.123', DbStringHelper::normalizeFloat('123 123 123,123')); 49 | $this->assertSame('123123123.123', DbStringHelper::normalizeFloat('123 123 123.123')); 50 | $this->assertSame('-123123123.123', DbStringHelper::normalizeFloat('-123 123 123.123')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Db/Query/QueryGetTableAliasTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 28 | $qb = $db->getQueryBuilder(); 29 | 30 | $cdb = new ColumnDefinitionBuilder($qb); 31 | 32 | $column = ColumnBuilder::integer(); 33 | 34 | $this->assertEquals($cdb->build($column), $cdb->buildAlter($column)); 35 | } 36 | 37 | public function testBuildEmptyDefaultForUuid(): void 38 | { 39 | $db = $this->getConnection(); 40 | $qb = $db->getQueryBuilder(); 41 | 42 | $cdb = new class ($qb) extends AbstractColumnDefinitionBuilder { 43 | protected function getDbType(ColumnInterface $column): string 44 | { 45 | return 'uuid'; 46 | } 47 | }; 48 | 49 | $column = ColumnBuilder::uuidPrimaryKey(); 50 | 51 | $this->assertSame('uuid PRIMARY KEY', $cdb->build($column)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/AndConditionTest.php: -------------------------------------------------------------------------------- 1 | 1, 'b' => 2]); 20 | 21 | $this->assertSame(['a' => 1, 'b' => 2], $andCondition->getExpressions()); 22 | } 23 | 24 | public function testGetOperator(): void 25 | { 26 | $andCondition = new AndCondition(['a' => 1, 'b' => 2]); 27 | 28 | $this->assertSame('AND', $andCondition->getOperator()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/BetweenConditionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('date', $betweenCondition->getColumn()); 23 | $this->assertSame('BETWEEN', $betweenCondition->getOperator()); 24 | $this->assertSame(1, $betweenCondition->getIntervalStart()); 25 | $this->assertSame(2, $betweenCondition->getIntervalEnd()); 26 | } 27 | 28 | public function testFromArrayDefinition(): void 29 | { 30 | $betweenCondition = BetweenCondition::fromArrayDefinition('BETWEEN', ['date', 1, 2]); 31 | 32 | $this->assertSame('date', $betweenCondition->getColumn()); 33 | $this->assertSame('BETWEEN', $betweenCondition->getOperator()); 34 | $this->assertSame(1, $betweenCondition->getIntervalStart()); 35 | $this->assertSame(2, $betweenCondition->getIntervalEnd()); 36 | } 37 | 38 | public function testFromArrayDefinitionExceptionWithoutOperands(): void 39 | { 40 | $this->expectException(InvalidArgumentException::class); 41 | $this->expectExceptionMessage("Operator 'between' requires three operands."); 42 | 43 | BetweenCondition::fromArrayDefinition('between', []); 44 | } 45 | 46 | public function testFromArrayDefinitionExceptionColumns(): void 47 | { 48 | $this->expectException(InvalidArgumentException::class); 49 | $this->expectExceptionMessage("Operator 'between' requires column to be string or ExpressionInterface."); 50 | 51 | BetweenCondition::fromArrayDefinition('between', [1, 'min_value', 'max_value']); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/Builder/BetweenColumnsConditionBuilderTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 24 | 25 | $betweenColumnsCondition = new BetweenColumnsCondition(42, 'BETWEEN', '1', '100'); 26 | $params = []; 27 | 28 | $this->assertSame( 29 | ':qp0 BETWEEN [1] AND [100]', 30 | (new BetweenColumnsConditionBuilder($db->getQueryBuilder()))->build($betweenColumnsCondition, $params) 31 | ); 32 | 33 | $this->assertEquals([':qp0' => 42], $params); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/Builder/LikeConditionBuilderTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 25 | 26 | $likeCondition = new LikeCondition('column', 'invalid', 'value'); 27 | 28 | $this->expectException(InvalidArgumentException::class); 29 | $this->expectExceptionMessage('Invalid operator in like condition: "INVALID"'); 30 | 31 | (new LikeConditionBuilder($db->getQueryBuilder()))->build($likeCondition); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/ExistsConditionTest.php: -------------------------------------------------------------------------------- 1 | createMock(ConnectionInterface::class))) 23 | ->select('id') 24 | ->from('users') 25 | ->where(['active' => 1]); 26 | $existCondition = new ExistsCondition('EXISTS', $query); 27 | 28 | $this->assertSame('EXISTS', $existCondition->getOperator()); 29 | $this->assertSame($query, $existCondition->getQuery()); 30 | } 31 | 32 | public function testFromArrayDefinition(): void 33 | { 34 | $query = (new Query($this->createMock(ConnectionInterface::class))) 35 | ->select('id') 36 | ->from('users') 37 | ->where(['active' => 1]); 38 | $existCondition = ExistsCondition::fromArrayDefinition('EXISTS', [$query]); 39 | 40 | $this->assertSame('EXISTS', $existCondition->getOperator()); 41 | $this->assertSame($query, $existCondition->getQuery()); 42 | } 43 | 44 | public function testFromArrayDefinitionExceptionQuery(): void 45 | { 46 | $this->expectException(InvalidArgumentException::class); 47 | $this->expectExceptionMessage('Sub query for EXISTS operator must be a Query object.'); 48 | 49 | ExistsCondition::fromArrayDefinition('EXISTS', []); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/HashConditionTest.php: -------------------------------------------------------------------------------- 1 | false, 'active' => true]); 20 | 21 | $this->assertSame(['expired' => false, 'active' => true], $hashCondition->getHash()); 22 | } 23 | 24 | public function testFromArrayDefinition(): void 25 | { 26 | $hashCondition = HashCondition::fromArrayDefinition('AND', ['expired' => false, 'active' => true]); 27 | 28 | $this->assertSame(['expired' => false, 'active' => true], $hashCondition->getHash()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/InConditionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('id', $inCondition->getColumn()); 23 | $this->assertSame('IN', $inCondition->getOperator()); 24 | $this->assertSame([1, 2, 3], $inCondition->getValues()); 25 | } 26 | 27 | public function testFromArrayDefinition(): void 28 | { 29 | $inCondition = InCondition::fromArrayDefinition('IN', ['id', [1, 2, 3]]); 30 | 31 | $this->assertSame('id', $inCondition->getColumn()); 32 | $this->assertSame('IN', $inCondition->getOperator()); 33 | $this->assertSame([1, 2, 3], $inCondition->getValues()); 34 | } 35 | 36 | public function testFromArrayDefinitionException(): void 37 | { 38 | $this->expectException(InvalidArgumentException::class); 39 | $this->expectExceptionMessage("Operator 'IN' requires two operands."); 40 | 41 | InCondition::fromArrayDefinition('IN', []); 42 | } 43 | 44 | public function testFromArrayDefinitionExceptionColumn(): void 45 | { 46 | $this->expectException(InvalidArgumentException::class); 47 | $this->expectExceptionMessage("Operator 'IN' requires column to be string, array or Iterator."); 48 | 49 | InCondition::fromArrayDefinition('IN', [1, [1, 2, 3]]); 50 | } 51 | 52 | public function testFromArrayDefinitionExceptionValues(): void 53 | { 54 | $this->expectException(InvalidArgumentException::class); 55 | $this->expectExceptionMessage( 56 | "Operator 'IN' requires values to be array, Iterator, int or QueryInterface." 57 | ); 58 | 59 | InCondition::fromArrayDefinition('IN', ['id', false]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/NotConditionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('id = 1', $notCondition->getCondition()); 23 | } 24 | 25 | public function testFromArrayDefinition(): void 26 | { 27 | $notCondition = NotCondition::fromArrayDefinition('NOT', ['id = 1']); 28 | 29 | $this->assertSame('id = 1', $notCondition->getCondition()); 30 | } 31 | 32 | public function testFromArrayDefinitionException(): void 33 | { 34 | $this->expectException(InvalidArgumentException::class); 35 | $this->expectExceptionMessage("Operator 'NOT' requires exactly one operand."); 36 | 37 | NotCondition::fromArrayDefinition('NOT', []); 38 | } 39 | 40 | public function testFromArrayDefinitionExceptionCondition(): void 41 | { 42 | $this->expectException(InvalidArgumentException::class); 43 | $this->expectExceptionMessage( 44 | "Operator 'NOT' requires condition to be array, string, null or ExpressionInterface." 45 | ); 46 | 47 | NotCondition::fromArrayDefinition('NOT', [false]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Db/QueryBuilder/Condition/SimpleConditionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('id', $simpleCondition->getColumn()); 23 | $this->assertSame('=', $simpleCondition->getOperator()); 24 | $this->assertSame(1, $simpleCondition->getValue()); 25 | } 26 | 27 | public function testFromArrayDefinition(): void 28 | { 29 | $simpleCondition = SimpleCondition::fromArrayDefinition('=', ['id', 1]); 30 | 31 | $this->assertSame('id', $simpleCondition->getColumn()); 32 | $this->assertSame('=', $simpleCondition->getOperator()); 33 | $this->assertSame(1, $simpleCondition->getValue()); 34 | } 35 | 36 | public function testFromArrayDefinitionColumnException(): void 37 | { 38 | $this->expectException(InvalidArgumentException::class); 39 | $this->expectExceptionMessage("Operator '=' requires two operands."); 40 | 41 | SimpleCondition::fromArrayDefinition('=', []); 42 | } 43 | 44 | public function testFromArrayDefinitionValueException(): void 45 | { 46 | $this->expectException(InvalidArgumentException::class); 47 | $this->expectExceptionMessage("Operator 'IN' requires two operands."); 48 | 49 | SimpleCondition::fromArrayDefinition('IN', ['column']); 50 | } 51 | 52 | public function testFromArrayDefinitionExceptionColumn(): void 53 | { 54 | $this->expectException(InvalidArgumentException::class); 55 | $this->expectExceptionMessage( 56 | "Operator '=' requires column to be string or ExpressionInterface." 57 | ); 58 | 59 | SimpleCondition::fromArrayDefinition('=', [1, 1]); 60 | } 61 | 62 | public function testNullSecondOperand(): void 63 | { 64 | $condition = SimpleCondition::fromArrayDefinition('=', ['id', null]); 65 | 66 | $this->assertNull($condition->getValue()); 67 | 68 | $condition2 = new SimpleCondition('name', 'IS NOT', null); 69 | 70 | $this->assertSame('IS NOT', $condition2->getOperator()); 71 | $this->assertNull($condition2->getValue()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Db/Schema/Column/ColumnBuilderTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidArgumentException::class); 21 | $this->expectExceptionMessage('Structured value must be a valid string representation.'); 22 | 23 | $lazyArray->getValue(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Db/Schema/Data/LazyArrayTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidArgumentException::class); 21 | $this->expectExceptionMessage('Array value must be a valid string representation.'); 22 | 23 | $lazyArray->getValue(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Db/Schema/TableSchemaTest.php: -------------------------------------------------------------------------------- 1 | '']], 13 | ['int', ['type' => 'int']], 14 | ['int(10)', ['type' => 'int', 'size' => 10]], 15 | ['int UNSIGNED', ['type' => 'int', 'unsigned' => true]], 16 | ['int UNIQUE', ['type' => 'int', 'unique' => true]], 17 | ['int(10) UNSIGNED', ['type' => 'int', 'size' => 10, 'unsigned' => true]], 18 | ['int(10) UNSIGNED NOT NULL', ['type' => 'int', 'size' => 10, 'unsigned' => true, 'notNull' => true]], 19 | ['int(10) NOT NULL', ['type' => 'int', 'size' => 10, 'notNull' => true]], 20 | ['text NOT NULL', ['type' => 'text', 'notNull' => true]], 21 | ['text NULL', ['type' => 'text', 'notNull' => false]], 22 | ['text COLLATE utf8mb4', ['type' => 'text', 'extra' => 'COLLATE utf8mb4']], 23 | ['text DEFAULT NULL', ['type' => 'text', 'defaultValueRaw' => 'NULL']], 24 | ["text DEFAULT 'value'", ['type' => 'text', 'defaultValueRaw' => "'value'"]], 25 | ['varchar(36) DEFAULT uuid()', ['type' => 'varchar', 'size' => 36, 'defaultValueRaw' => 'uuid()']], 26 | ['varchar(36) DEFAULT uuid()::varchar(36)', ['type' => 'varchar', 'size' => 36, 'defaultValueRaw' => 'uuid()::varchar(36)']], 27 | ['int DEFAULT (1 + 2)', ['type' => 'int', 'defaultValueRaw' => '(1 + 2)']], 28 | ["int COMMENT '''Quoted'' comment'", ['type' => 'int', 'comment' => "'Quoted' comment"]], 29 | ['int CHECK (value > (1 + 5))', ['type' => 'int', 'check' => 'value > (1 + 5)']], 30 | ["enum('a','b','c')", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c']]], 31 | ["enum('a','b','c') NOT NULL", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c'], 'notNull' => true]], 32 | ['int[]', ['type' => 'int', 'dimension' => 1]], 33 | ['string(126)[][]', ['type' => 'string', 'size' => 126, 'dimension' => 2]], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Provider/CommandPDOProvider.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 | } 32 | 33 | public static function bindParamsNonWhere(): array 34 | { 35 | return [ 36 | [ 37 | <<value = strtolower($v); 21 | } 22 | } else { 23 | $this->value = strtolower($value); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Support/AnyValue.php: -------------------------------------------------------------------------------- 1 | allowedValues as $value) { 31 | $this->allowedValues[] = VarDumper::create($value)->asString(); 32 | } 33 | 34 | $expectedAsString = implode(', ', $allowedValues); 35 | 36 | return "is one of $expectedAsString"; 37 | } 38 | 39 | protected function matches($other): bool 40 | { 41 | return in_array($other, $this->allowedValues); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Support/JsonSerializableObject.php: -------------------------------------------------------------------------------- 1 | data; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Support/StringEnum.php: -------------------------------------------------------------------------------- 1 | value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Support/Stub/Column.php: -------------------------------------------------------------------------------- 1 | isType($dbType) ? $dbType : ColumnType::STRING; 19 | } 20 | 21 | protected function isDbType(string $dbType): bool 22 | { 23 | return match ($dbType) { 24 | 'string', 'array' => false, 25 | 'varchar' => true, 26 | default => $this->isType($dbType), 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Support/Stub/Command.php: -------------------------------------------------------------------------------- 1 | db->getQueryBuilder(); 21 | } 22 | 23 | protected function internalExecute(): void 24 | { 25 | throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Support/Stub/Connection.php: -------------------------------------------------------------------------------- 1 | setSql($sql); 25 | } 26 | 27 | if ($this->logger !== null) { 28 | $command->setLogger($this->logger); 29 | } 30 | 31 | if ($this->profiler !== null) { 32 | $command->setProfiler($this->profiler); 33 | } 34 | 35 | return $command->bindValues($params); 36 | } 37 | 38 | public function createTransaction(): TransactionInterface 39 | { 40 | return new Transaction($this); 41 | } 42 | 43 | public function getColumnFactory(): ColumnFactoryInterface 44 | { 45 | return $this->columnFactory ??= new ColumnFactory(); 46 | } 47 | 48 | public function getQueryBuilder(): QueryBuilderInterface 49 | { 50 | return $this->queryBuilder ??= new QueryBuilder($this); 51 | } 52 | 53 | public function getQuoter(): QuoterInterface 54 | { 55 | return $this->quoter ??= new Quoter(['[', ']'], ['[', ']'], $this->getTablePrefix()); 56 | } 57 | 58 | public function getSchema(): SchemaInterface 59 | { 60 | return $this->schema ??= new Schema($this, $this->schemaCache); 61 | } 62 | 63 | protected function initConnection(): void 64 | { 65 | if ($this->getEmulatePrepare() !== null) { 66 | $this->driver->attributes([PDO::ATTR_EMULATE_PREPARES => $this->getEmulatePrepare()]); 67 | } 68 | 69 | $this->pdo = $this->driver->createConnection(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Support/Stub/DDLQueryBuilder.php: -------------------------------------------------------------------------------- 1 | getQuoter(); 15 | $schema = $db->getSchema(); 16 | 17 | parent::__construct( 18 | $db, 19 | new DDLQueryBuilder($this, $quoter, $schema), 20 | new DMLQueryBuilder($this, $quoter, $schema), 21 | new DQLQueryBuilder($this, $quoter), 22 | new ColumnDefinitionBuilder($this), 23 | ); 24 | } 25 | 26 | protected function createSqlParser(string $sql): SqlParser 27 | { 28 | return new SqlParser($sql); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Support/Stub/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->position && $this->skipToAfterChar("\n") 26 | : null, 27 | '/' => $this->sql[$this->position] === '*' 28 | ? ++$this->position && $this->skipToAfterString('*/') 29 | : null, 30 | default => null, 31 | }; 32 | 33 | if ($result !== null) { 34 | $position = $pos; 35 | 36 | return $result; 37 | } 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Support/Stub/TableSchema.php: -------------------------------------------------------------------------------- 1 | getDriver(), DbHelper::getSchemaCache()); 18 | 19 | if ($fixture) { 20 | DbHelper::loadFixture($db, __DIR__ . '/Fixture/db.sql'); 21 | } 22 | 23 | return $db; 24 | } 25 | 26 | protected static function getDb(): PdoConnectionInterface 27 | { 28 | return new Stub\Connection(new PdoDriver('sqlite::memory:'), DbHelper::getSchemaCache()); 29 | } 30 | 31 | protected function getDriver(): PdoDriverInterface 32 | { 33 | return new PdoDriver($this->dsn); 34 | } 35 | 36 | protected function getDriverName(): string 37 | { 38 | return 'db'; 39 | } 40 | 41 | protected function setDsn(string $dsn): void 42 | { 43 | $this->dsn = $dsn; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Support/TraversableObject.php: -------------------------------------------------------------------------------- 1 | data[$this->position]; 36 | } 37 | 38 | public function next(): void 39 | { 40 | $this->position++; 41 | } 42 | 43 | public function key(): int 44 | { 45 | return $this->position; 46 | } 47 | 48 | public function valid(): bool 49 | { 50 | return array_key_exists($this->position, $this->data); 51 | } 52 | 53 | public function rewind(): void 54 | { 55 | $this->position = 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Support/string.txt: -------------------------------------------------------------------------------- 1 | string --------------------------------------------------------------------------------