summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src')
-rw-r--r--vendor/doctrine/dbal/src/ArrayParameterType.php39
-rw-r--r--vendor/doctrine/dbal/src/ArrayParameters/Exception.php12
-rw-r--r--vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingNamedParameter.php21
-rw-r--r--vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingPositionalParameter.php25
-rw-r--r--vendor/doctrine/dbal/src/Cache/ArrayResult.php101
-rw-r--r--vendor/doctrine/dbal/src/Cache/CacheException.php12
-rw-r--r--vendor/doctrine/dbal/src/Cache/Exception/NoCacheKey.php16
-rw-r--r--vendor/doctrine/dbal/src/Cache/Exception/NoResultDriverConfigured.php16
-rw-r--r--vendor/doctrine/dbal/src/Cache/QueryCacheProfile.php91
-rw-r--r--vendor/doctrine/dbal/src/ColumnCase.php21
-rw-r--r--vendor/doctrine/dbal/src/Configuration.php156
-rw-r--r--vendor/doctrine/dbal/src/Connection.php1372
-rw-r--r--vendor/doctrine/dbal/src/Connection/StaticServerVersionProvider.php19
-rw-r--r--vendor/doctrine/dbal/src/ConnectionException.php10
-rw-r--r--vendor/doctrine/dbal/src/Connections/PrimaryReadReplicaConnection.php327
-rw-r--r--vendor/doctrine/dbal/src/Driver.php48
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php25
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/IBMDB2/ExceptionConverter.php47
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php94
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php52
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php82
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php49
-rw-r--r--vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php85
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractException.php36
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php86
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php38
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php112
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php33
-rw-r--r--vendor/doctrine/dbal/src/Driver/Connection.php93
-rw-r--r--vendor/doctrine/dbal/src/Driver/Exception.php25
-rw-r--r--vendor/doctrine/dbal/src/Driver/Exception/IdentityColumnsNotSupported.php21
-rw-r--r--vendor/doctrine/dbal/src/Driver/Exception/NoIdentityValue.php21
-rw-r--r--vendor/doctrine/dbal/src/Driver/FetchUtils.php69
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php131
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php80
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php41
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionError.php29
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionFailed.php28
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/Factory.php35
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/PrepareFailed.php25
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/StatementError.php34
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Result.php106
-rw-r--r--vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php156
-rw-r--r--vendor/doctrine/dbal/src/Driver/Middleware.php12
-rw-r--r--vendor/doctrine/dbal/src/Driver/Middleware/AbstractConnectionMiddleware.php69
-rw-r--r--vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php39
-rw-r--r--vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php68
-rw-r--r--vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php26
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php112
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php117
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php30
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php35
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php22
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/HostRequired.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidCharset.php41
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php24
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php24
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Exception/StatementError.php30
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php14
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Charset.php32
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php28
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Result.php164
-rw-r--r--vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php154
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Connection.php119
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php58
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Driver.php60
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php26
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Exception/Error.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Exception/InvalidConfiguration.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Exception/NonTerminatedStringLiteral.php27
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Exception/UnknownParameterIndex.php24
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/ExecutionMode.php30
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php40
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Result.php128
-rw-r--r--vendor/doctrine/dbal/src/Driver/OCI8/Statement.php103
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/Connection.php133
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/Exception.php30
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php76
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php61
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php113
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/Result.php110
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php29
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php108
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php46
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php55
-rw-r--r--vendor/doctrine/dbal/src/Driver/PDO/Statement.php80
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php129
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php49
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php88
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php31
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php29
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Result.php240
-rw-r--r--vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php91
-rw-r--r--vendor/doctrine/dbal/src/Driver/Result.php93
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php108
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php73
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLSrv/Exception/Error.php44
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLSrv/Result.php104
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php140
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php109
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php48
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php20
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLite3/Result.php88
-rw-r--r--vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php61
-rw-r--r--vendor/doctrine/dbal/src/Driver/Statement.php39
-rw-r--r--vendor/doctrine/dbal/src/DriverManager.php191
-rw-r--r--vendor/doctrine/dbal/src/Exception.php12
-rw-r--r--vendor/doctrine/dbal/src/Exception/CommitFailedRollbackOnly.php16
-rw-r--r--vendor/doctrine/dbal/src/Exception/ConnectionException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/ConnectionLost.php10
-rw-r--r--vendor/doctrine/dbal/src/Exception/ConstraintViolationException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/DatabaseDoesNotExist.php10
-rw-r--r--vendor/doctrine/dbal/src/Exception/DatabaseObjectExistsException.php18
-rw-r--r--vendor/doctrine/dbal/src/Exception/DatabaseObjectNotFoundException.php18
-rw-r--r--vendor/doctrine/dbal/src/Exception/DatabaseRequired.php23
-rw-r--r--vendor/doctrine/dbal/src/Exception/DeadlockException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/DriverException.php51
-rw-r--r--vendor/doctrine/dbal/src/Exception/DriverRequired.php30
-rw-r--r--vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidArgumentException.php16
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidColumnDeclaration.php19
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidColumnType.php13
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnLengthRequired.php34
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnPrecisionRequired.php20
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnScaleRequired.php20
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidDriverClass.php24
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidFieldNameException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/InvalidWrapperClass.php24
-rw-r--r--vendor/doctrine/dbal/src/Exception/LockWaitTimeoutException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/MalformedDsnException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/NoActiveTransaction.php16
-rw-r--r--vendor/doctrine/dbal/src/Exception/NoKeyValue.php27
-rw-r--r--vendor/doctrine/dbal/src/Exception/NonUniqueFieldNameException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/NotNullConstraintViolationException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/ReadOnlyException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/RetryableException.php16
-rw-r--r--vendor/doctrine/dbal/src/Exception/SavepointsNotSupported.php16
-rw-r--r--vendor/doctrine/dbal/src/Exception/SchemaDoesNotExist.php10
-rw-r--r--vendor/doctrine/dbal/src/Exception/ServerException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/SyntaxErrorException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/TableExistsException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/TableNotFoundException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/UniqueConstraintViolationException.php14
-rw-r--r--vendor/doctrine/dbal/src/Exception/UnknownDriver.php24
-rw-r--r--vendor/doctrine/dbal/src/ExpandArrayParameters.php128
-rw-r--r--vendor/doctrine/dbal/src/LockMode.php16
-rw-r--r--vendor/doctrine/dbal/src/Logging/Connection.php69
-rw-r--r--vendor/doctrine/dbal/src/Logging/Driver.php50
-rw-r--r--vendor/doctrine/dbal/src/Logging/Middleware.php21
-rw-r--r--vendor/doctrine/dbal/src/Logging/Statement.php48
-rw-r--r--vendor/doctrine/dbal/src/ParameterType.php46
-rw-r--r--vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php842
-rw-r--r--vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php2219
-rw-r--r--vendor/doctrine/dbal/src/Platforms/DB2Platform.php593
-rw-r--r--vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php17
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php28
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php414
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php42
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php264
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php59
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php257
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php133
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php119
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php207
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php141
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php38
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php165
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php29
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php37
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php29
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php37
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php93
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php23
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php25
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php49
-rw-r--r--vendor/doctrine/dbal/src/Platforms/OraclePlatform.php784
-rw-r--r--vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php784
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php50
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php84
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php1223
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php52
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php994
-rw-r--r--vendor/doctrine/dbal/src/Platforms/TrimMode.php13
-rw-r--r--vendor/doctrine/dbal/src/Portability/Connection.php41
-rw-r--r--vendor/doctrine/dbal/src/Portability/Converter.php280
-rw-r--r--vendor/doctrine/dbal/src/Portability/Driver.php69
-rw-r--r--vendor/doctrine/dbal/src/Portability/Middleware.php25
-rw-r--r--vendor/doctrine/dbal/src/Portability/OptimizeFlags.php42
-rw-r--r--vendor/doctrine/dbal/src/Portability/Result.php68
-rw-r--r--vendor/doctrine/dbal/src/Portability/Statement.php31
-rw-r--r--vendor/doctrine/dbal/src/Query.php44
-rw-r--r--vendor/doctrine/dbal/src/Query/Exception/NonUniqueAlias.php27
-rw-r--r--vendor/doctrine/dbal/src/Query/Exception/UnknownAlias.php27
-rw-r--r--vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php98
-rw-r--r--vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php244
-rw-r--r--vendor/doctrine/dbal/src/Query/ForUpdate.php21
-rw-r--r--vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php18
-rw-r--r--vendor/doctrine/dbal/src/Query/From.php15
-rw-r--r--vendor/doctrine/dbal/src/Query/Join.php32
-rw-r--r--vendor/doctrine/dbal/src/Query/Limit.php29
-rw-r--r--vendor/doctrine/dbal/src/Query/QueryBuilder.php1479
-rw-r--r--vendor/doctrine/dbal/src/Query/QueryException.php12
-rw-r--r--vendor/doctrine/dbal/src/Query/QueryType.php14
-rw-r--r--vendor/doctrine/dbal/src/Query/SelectQuery.php78
-rw-r--r--vendor/doctrine/dbal/src/Result.php270
-rw-r--r--vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php73
-rw-r--r--vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php94
-rw-r--r--vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php54
-rw-r--r--vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php14
-rw-r--r--vendor/doctrine/dbal/src/SQL/Parser.php129
-rw-r--r--vendor/doctrine/dbal/src/SQL/Parser/Exception.php11
-rw-r--r--vendor/doctrine/dbal/src/SQL/Parser/Exception/RegularExpressionError.php19
-rw-r--r--vendor/doctrine/dbal/src/SQL/Parser/Visitor.php28
-rw-r--r--vendor/doctrine/dbal/src/Schema/AbstractAsset.php157
-rw-r--r--vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php864
-rw-r--r--vendor/doctrine/dbal/src/Schema/Column.php252
-rw-r--r--vendor/doctrine/dbal/src/Schema/ColumnDiff.php106
-rw-r--r--vendor/doctrine/dbal/src/Schema/Comparator.php417
-rw-r--r--vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php371
-rw-r--r--vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php20
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php21
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php21
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php19
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php21
-rw-r--r--vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php21
-rw-r--r--vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php291
-rw-r--r--vendor/doctrine/dbal/src/Schema/Identifier.php29
-rw-r--r--vendor/doctrine/dbal/src/Schema/Index.php314
-rw-r--r--vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php548
-rw-r--r--vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php475
-rw-r--r--vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php572
-rw-r--r--vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php498
-rw-r--r--vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php620
-rw-r--r--vendor/doctrine/dbal/src/Schema/Schema.php374
-rw-r--r--vendor/doctrine/dbal/src/Schema/SchemaConfig.php61
-rw-r--r--vendor/doctrine/dbal/src/Schema/SchemaDiff.php109
-rw-r--r--vendor/doctrine/dbal/src/Schema/SchemaException.php12
-rw-r--r--vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php17
-rw-r--r--vendor/doctrine/dbal/src/Schema/Sequence.php98
-rw-r--r--vendor/doctrine/dbal/src/Schema/Table.php753
-rw-r--r--vendor/doctrine/dbal/src/Schema/TableDiff.php164
-rw-r--r--vendor/doctrine/dbal/src/Schema/UniqueConstraint.php152
-rw-r--r--vendor/doctrine/dbal/src/Schema/View.php21
-rw-r--r--vendor/doctrine/dbal/src/ServerVersionProvider.php13
-rw-r--r--vendor/doctrine/dbal/src/Statement.php143
-rw-r--r--vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php119
-rw-r--r--vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php11
-rw-r--r--vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider.php15
-rw-r--r--vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider/SingleConnectionProvider.php34
-rw-r--r--vendor/doctrine/dbal/src/Tools/DsnParser.php217
-rw-r--r--vendor/doctrine/dbal/src/TransactionIsolationLevel.php13
-rw-r--r--vendor/doctrine/dbal/src/Types/AsciiStringType.php24
-rw-r--r--vendor/doctrine/dbal/src/Types/BigIntType.php61
-rw-r--r--vendor/doctrine/dbal/src/Types/BinaryType.php49
-rw-r--r--vendor/doctrine/dbal/src/Types/BlobType.php56
-rw-r--r--vendor/doctrine/dbal/src/Types/BooleanType.php44
-rw-r--r--vendor/doctrine/dbal/src/Types/ConversionException.php16
-rw-r--r--vendor/doctrine/dbal/src/Types/DateImmutableType.php74
-rw-r--r--vendor/doctrine/dbal/src/Types/DateIntervalType.php84
-rw-r--r--vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php80
-rw-r--r--vendor/doctrine/dbal/src/Types/DateTimeType.php80
-rw-r--r--vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php74
-rw-r--r--vendor/doctrine/dbal/src/Types/DateTimeTzType.php87
-rw-r--r--vendor/doctrine/dbal/src/Types/DateType.php69
-rw-r--r--vendor/doctrine/dbal/src/Types/DecimalType.php35
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/InvalidFormat.php39
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/InvalidType.php53
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/SerializationFailed.php29
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/TypeAlreadyRegistered.php25
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/TypeNotFound.php18
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/TypeNotRegistered.php25
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/TypesAlreadyExists.php18
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/TypesException.php11
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/UnknownColumnType.php29
-rw-r--r--vendor/doctrine/dbal/src/Types/Exception/ValueNotConvertible.php44
-rw-r--r--vendor/doctrine/dbal/src/Types/FloatType.php30
-rw-r--r--vendor/doctrine/dbal/src/Types/GuidType.php21
-rw-r--r--vendor/doctrine/dbal/src/Types/IntegerType.php39
-rw-r--r--vendor/doctrine/dbal/src/Types/JsonType.php69
-rw-r--r--vendor/doctrine/dbal/src/Types/PhpDateMappingType.php14
-rw-r--r--vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php14
-rw-r--r--vendor/doctrine/dbal/src/Types/PhpIntegerMappingType.php14
-rw-r--r--vendor/doctrine/dbal/src/Types/PhpTimeMappingType.php14
-rw-r--r--vendor/doctrine/dbal/src/Types/SimpleArrayType.php51
-rw-r--r--vendor/doctrine/dbal/src/Types/SmallIntType.php39
-rw-r--r--vendor/doctrine/dbal/src/Types/StringType.php21
-rw-r--r--vendor/doctrine/dbal/src/Types/TextType.php29
-rw-r--r--vendor/doctrine/dbal/src/Types/TimeImmutableType.php74
-rw-r--r--vendor/doctrine/dbal/src/Types/TimeType.php69
-rw-r--r--vendor/doctrine/dbal/src/Types/Type.php221
-rw-r--r--vendor/doctrine/dbal/src/Types/TypeRegistry.php131
-rw-r--r--vendor/doctrine/dbal/src/Types/Types.php40
-rw-r--r--vendor/doctrine/dbal/src/Types/VarDateTimeImmutableType.php63
-rw-r--r--vendor/doctrine/dbal/src/Types/VarDateTimeType.php42
318 files changed, 33112 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/ArrayParameterType.php b/vendor/doctrine/dbal/src/ArrayParameterType.php
new file mode 100644
index 0000000..851d47d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ArrayParameterType.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7enum ArrayParameterType
8{
9 /**
10 * Represents an array of ints to be expanded by Doctrine SQL parsing.
11 */
12 case INTEGER;
13
14 /**
15 * Represents an array of strings to be expanded by Doctrine SQL parsing.
16 */
17 case STRING;
18
19 /**
20 * Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
21 */
22 case ASCII;
23
24 /**
25 * Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
26 */
27 case BINARY;
28
29 /** @internal */
30 public static function toElementParameterType(self $type): ParameterType
31 {
32 return match ($type) {
33 self::INTEGER => ParameterType::INTEGER,
34 self::STRING => ParameterType::STRING,
35 self::ASCII => ParameterType::ASCII,
36 self::BINARY => ParameterType::BINARY,
37 };
38 }
39}
diff --git a/vendor/doctrine/dbal/src/ArrayParameters/Exception.php b/vendor/doctrine/dbal/src/ArrayParameters/Exception.php
new file mode 100644
index 0000000..e5a580b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ArrayParameters/Exception.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\ArrayParameters;
6
7use Throwable;
8
9/** @internal */
10interface Exception extends Throwable
11{
12}
diff --git a/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingNamedParameter.php b/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingNamedParameter.php
new file mode 100644
index 0000000..7ed18dc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingNamedParameter.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\ArrayParameters\Exception;
6
7use Doctrine\DBAL\ArrayParameters\Exception;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13class MissingNamedParameter extends LogicException implements Exception
14{
15 public static function new(string $name): self
16 {
17 return new self(
18 sprintf('Named parameter "%s" does not have a bound value.', $name),
19 );
20 }
21}
diff --git a/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingPositionalParameter.php b/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingPositionalParameter.php
new file mode 100644
index 0000000..91bdbe7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ArrayParameters/Exception/MissingPositionalParameter.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\ArrayParameters\Exception;
6
7use Doctrine\DBAL\ArrayParameters\Exception;
8use LogicException;
9
10use function sprintf;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17class MissingPositionalParameter extends LogicException implements Exception
18{
19 public static function new(int $index): self
20 {
21 return new self(
22 sprintf('Positional parameter at index %d does not have a bound value.', $index),
23 );
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Cache/ArrayResult.php b/vendor/doctrine/dbal/src/Cache/ArrayResult.php
new file mode 100644
index 0000000..65b8652
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Cache/ArrayResult.php
@@ -0,0 +1,101 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Cache;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\Result;
9
10use function array_values;
11use function count;
12use function reset;
13
14/** @internal The class is internal to the caching layer implementation. */
15final class ArrayResult implements Result
16{
17 private readonly int $columnCount;
18 private int $num = 0;
19
20 /** @param list<array<string, mixed>> $data */
21 public function __construct(private array $data)
22 {
23 $this->columnCount = $data === [] ? 0 : count($data[0]);
24 }
25
26 public function fetchNumeric(): array|false
27 {
28 $row = $this->fetch();
29
30 if ($row === false) {
31 return false;
32 }
33
34 return array_values($row);
35 }
36
37 public function fetchAssociative(): array|false
38 {
39 return $this->fetch();
40 }
41
42 public function fetchOne(): mixed
43 {
44 $row = $this->fetch();
45
46 if ($row === false) {
47 return false;
48 }
49
50 return reset($row);
51 }
52
53 /**
54 * {@inheritDoc}
55 */
56 public function fetchAllNumeric(): array
57 {
58 return FetchUtils::fetchAllNumeric($this);
59 }
60
61 /**
62 * {@inheritDoc}
63 */
64 public function fetchAllAssociative(): array
65 {
66 return FetchUtils::fetchAllAssociative($this);
67 }
68
69 /**
70 * {@inheritDoc}
71 */
72 public function fetchFirstColumn(): array
73 {
74 return FetchUtils::fetchFirstColumn($this);
75 }
76
77 public function rowCount(): int
78 {
79 return count($this->data);
80 }
81
82 public function columnCount(): int
83 {
84 return $this->columnCount;
85 }
86
87 public function free(): void
88 {
89 $this->data = [];
90 }
91
92 /** @return array<string, mixed>|false */
93 private function fetch(): array|false
94 {
95 if (! isset($this->data[$this->num])) {
96 return false;
97 }
98
99 return $this->data[$this->num++];
100 }
101}
diff --git a/vendor/doctrine/dbal/src/Cache/CacheException.php b/vendor/doctrine/dbal/src/Cache/CacheException.php
new file mode 100644
index 0000000..a6913ed
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Cache/CacheException.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Cache;
6
7use Doctrine\DBAL\Exception;
8
9/** @psalm-immutable */
10class CacheException extends \Exception implements Exception
11{
12}
diff --git a/vendor/doctrine/dbal/src/Cache/Exception/NoCacheKey.php b/vendor/doctrine/dbal/src/Cache/Exception/NoCacheKey.php
new file mode 100644
index 0000000..9fc58b7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Cache/Exception/NoCacheKey.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Cache\Exception;
6
7use Doctrine\DBAL\Cache\CacheException;
8
9/** @psalm-immutable */
10final class NoCacheKey extends CacheException
11{
12 public static function new(): self
13 {
14 return new self('No cache key was set.');
15 }
16}
diff --git a/vendor/doctrine/dbal/src/Cache/Exception/NoResultDriverConfigured.php b/vendor/doctrine/dbal/src/Cache/Exception/NoResultDriverConfigured.php
new file mode 100644
index 0000000..14e41f7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Cache/Exception/NoResultDriverConfigured.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Cache\Exception;
6
7use Doctrine\DBAL\Cache\CacheException;
8
9/** @psalm-immutable */
10final class NoResultDriverConfigured extends CacheException
11{
12 public static function new(): self
13 {
14 return new self('Trying to cache a query but no result driver is configured.');
15 }
16}
diff --git a/vendor/doctrine/dbal/src/Cache/QueryCacheProfile.php b/vendor/doctrine/dbal/src/Cache/QueryCacheProfile.php
new file mode 100644
index 0000000..b085f6c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Cache/QueryCacheProfile.php
@@ -0,0 +1,91 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Cache;
6
7use Doctrine\DBAL\Cache\Exception\NoCacheKey;
8use Doctrine\DBAL\Connection;
9use Psr\Cache\CacheItemPoolInterface;
10
11use function hash;
12use function serialize;
13use function sha1;
14
15/**
16 * Query Cache Profile handles the data relevant for query caching.
17 *
18 * It is a value object, setter methods return NEW instances.
19 *
20 * @psalm-import-type WrapperParameterType from Connection
21 */
22class QueryCacheProfile
23{
24 public function __construct(
25 private readonly int $lifetime = 0,
26 private readonly ?string $cacheKey = null,
27 private readonly ?CacheItemPoolInterface $resultCache = null,
28 ) {
29 }
30
31 public function getResultCache(): ?CacheItemPoolInterface
32 {
33 return $this->resultCache;
34 }
35
36 public function getLifetime(): int
37 {
38 return $this->lifetime;
39 }
40
41 /** @throws CacheException */
42 public function getCacheKey(): string
43 {
44 if ($this->cacheKey === null) {
45 throw NoCacheKey::new();
46 }
47
48 return $this->cacheKey;
49 }
50
51 /**
52 * Generates the real cache key from query, params, types and connection parameters.
53 *
54 * @param list<mixed>|array<string, mixed> $params
55 * @param array<string, mixed> $connectionParams
56 * @psalm-param array<int, WrapperParameterType>|array<string, WrapperParameterType> $types
57 *
58 * @return array{string, string}
59 */
60 public function generateCacheKeys(string $sql, array $params, array $types, array $connectionParams = []): array
61 {
62 if (isset($connectionParams['password'])) {
63 unset($connectionParams['password']);
64 }
65
66 $realCacheKey = 'query=' . $sql .
67 '&params=' . serialize($params) .
68 '&types=' . serialize($types) .
69 '&connectionParams=' . hash('sha256', serialize($connectionParams));
70
71 // should the key be automatically generated using the inputs or is the cache key set?
72 $cacheKey = $this->cacheKey ?? sha1($realCacheKey);
73
74 return [$cacheKey, $realCacheKey];
75 }
76
77 public function setResultCache(CacheItemPoolInterface $cache): QueryCacheProfile
78 {
79 return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache);
80 }
81
82 public function setCacheKey(?string $cacheKey): self
83 {
84 return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCache);
85 }
86
87 public function setLifetime(int $lifetime): self
88 {
89 return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCache);
90 }
91}
diff --git a/vendor/doctrine/dbal/src/ColumnCase.php b/vendor/doctrine/dbal/src/ColumnCase.php
new file mode 100644
index 0000000..687a04f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ColumnCase.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7/**
8 * Contains portable column case conversions.
9 */
10enum ColumnCase
11{
12 /**
13 * Convert column names to upper case.
14 */
15 case UPPER;
16
17 /**
18 * Convert column names to lower case.
19 */
20 case LOWER;
21}
diff --git a/vendor/doctrine/dbal/src/Configuration.php b/vendor/doctrine/dbal/src/Configuration.php
new file mode 100644
index 0000000..9aa001d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Configuration.php
@@ -0,0 +1,156 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\Driver\Middleware;
8use Doctrine\DBAL\Exception\InvalidArgumentException;
9use Doctrine\DBAL\Schema\SchemaManagerFactory;
10use Psr\Cache\CacheItemPoolInterface;
11
12/**
13 * Configuration container for the Doctrine DBAL.
14 */
15class Configuration
16{
17 /** @var Middleware[] */
18 private array $middlewares = [];
19
20 /**
21 * The cache driver implementation that is used for query result caching.
22 */
23 private ?CacheItemPoolInterface $resultCache = null;
24
25 /**
26 * The callable to use to filter schema assets.
27 *
28 * @var callable
29 */
30 protected $schemaAssetsFilter;
31
32 /**
33 * The default auto-commit mode for connections.
34 */
35 protected bool $autoCommit = true;
36
37 private ?SchemaManagerFactory $schemaManagerFactory = null;
38
39 public function __construct()
40 {
41 $this->schemaAssetsFilter = static function (): bool {
42 return true;
43 };
44 }
45
46 /**
47 * Gets the cache driver implementation that is used for query result caching.
48 */
49 public function getResultCache(): ?CacheItemPoolInterface
50 {
51 return $this->resultCache;
52 }
53
54 /**
55 * Sets the cache driver implementation that is used for query result caching.
56 */
57 public function setResultCache(CacheItemPoolInterface $cache): void
58 {
59 $this->resultCache = $cache;
60 }
61
62 /**
63 * Sets the callable to use to filter schema assets.
64 */
65 public function setSchemaAssetsFilter(callable $schemaAssetsFilter): void
66 {
67 $this->schemaAssetsFilter = $schemaAssetsFilter;
68 }
69
70 /**
71 * Returns the callable to use to filter schema assets.
72 */
73 public function getSchemaAssetsFilter(): callable
74 {
75 return $this->schemaAssetsFilter;
76 }
77
78 /**
79 * Sets the default auto-commit mode for connections.
80 *
81 * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
82 * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
83 * the method commit or the method rollback. By default, new connections are in auto-commit mode.
84 *
85 * @see getAutoCommit
86 *
87 * @param bool $autoCommit True to enable auto-commit mode; false to disable it
88 */
89 public function setAutoCommit(bool $autoCommit): void
90 {
91 $this->autoCommit = $autoCommit;
92 }
93
94 /**
95 * Returns the default auto-commit mode for connections.
96 *
97 * @see setAutoCommit
98 *
99 * @return bool True if auto-commit mode is enabled by default for connections, false otherwise.
100 */
101 public function getAutoCommit(): bool
102 {
103 return $this->autoCommit;
104 }
105
106 /**
107 * @param Middleware[] $middlewares
108 *
109 * @return $this
110 */
111 public function setMiddlewares(array $middlewares): self
112 {
113 $this->middlewares = $middlewares;
114
115 return $this;
116 }
117
118 /** @return Middleware[] */
119 public function getMiddlewares(): array
120 {
121 return $this->middlewares;
122 }
123
124 public function getSchemaManagerFactory(): ?SchemaManagerFactory
125 {
126 return $this->schemaManagerFactory;
127 }
128
129 /** @return $this */
130 public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFactory): self
131 {
132 $this->schemaManagerFactory = $schemaManagerFactory;
133
134 return $this;
135 }
136
137 /** @return true */
138 public function getDisableTypeComments(): bool
139 {
140 return true;
141 }
142
143 /**
144 * @param true $disableTypeComments
145 *
146 * @return $this
147 */
148 public function setDisableTypeComments(bool $disableTypeComments): self
149 {
150 if (! $disableTypeComments) {
151 throw new InvalidArgumentException('Column comments cannot be enabled anymore.');
152 }
153
154 return $this;
155 }
156}
diff --git a/vendor/doctrine/dbal/src/Connection.php b/vendor/doctrine/dbal/src/Connection.php
new file mode 100644
index 0000000..184b01b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Connection.php
@@ -0,0 +1,1372 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Closure;
8use Doctrine\DBAL\Cache\ArrayResult;
9use Doctrine\DBAL\Cache\CacheException;
10use Doctrine\DBAL\Cache\Exception\NoResultDriverConfigured;
11use Doctrine\DBAL\Cache\QueryCacheProfile;
12use Doctrine\DBAL\Connection\StaticServerVersionProvider;
13use Doctrine\DBAL\Driver\API\ExceptionConverter;
14use Doctrine\DBAL\Driver\Connection as DriverConnection;
15use Doctrine\DBAL\Driver\Statement as DriverStatement;
16use Doctrine\DBAL\Exception\CommitFailedRollbackOnly;
17use Doctrine\DBAL\Exception\ConnectionLost;
18use Doctrine\DBAL\Exception\DriverException;
19use Doctrine\DBAL\Exception\NoActiveTransaction;
20use Doctrine\DBAL\Exception\SavepointsNotSupported;
21use Doctrine\DBAL\Platforms\AbstractPlatform;
22use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
23use Doctrine\DBAL\Query\QueryBuilder;
24use Doctrine\DBAL\Schema\AbstractSchemaManager;
25use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
26use Doctrine\DBAL\Schema\SchemaManagerFactory;
27use Doctrine\DBAL\SQL\Parser;
28use Doctrine\DBAL\Types\Type;
29use Doctrine\Deprecations\Deprecation;
30use InvalidArgumentException;
31use SensitiveParameter;
32use Throwable;
33use Traversable;
34
35use function array_key_exists;
36use function array_merge;
37use function assert;
38use function count;
39use function implode;
40use function is_array;
41use function is_int;
42use function is_string;
43use function key;
44use function sprintf;
45
46/**
47 * A database abstraction-level connection that implements features like transaction isolation levels,
48 * configuration, emulated transaction nesting, lazy connecting and more.
49 *
50 * @psalm-import-type Params from DriverManager
51 * @psalm-type WrapperParameterType = string|Type|ParameterType|ArrayParameterType
52 * @psalm-type WrapperParameterTypeArray = array<int<0, max>, WrapperParameterType>|array<string, WrapperParameterType>
53 * @psalm-consistent-constructor
54 */
55class Connection implements ServerVersionProvider
56{
57 /**
58 * The wrapped driver connection.
59 */
60 protected ?DriverConnection $_conn = null;
61
62 protected Configuration $_config;
63
64 /**
65 * The current auto-commit mode of this connection.
66 */
67 private bool $autoCommit = true;
68
69 /**
70 * The transaction nesting level.
71 */
72 private int $transactionNestingLevel = 0;
73
74 /**
75 * The currently active transaction isolation level or NULL before it has been determined.
76 */
77 private ?TransactionIsolationLevel $transactionIsolationLevel = null;
78
79 /**
80 * The parameters used during creation of the Connection instance.
81 *
82 * @var array<string,mixed>
83 * @psalm-var Params
84 */
85 private array $params;
86
87 /**
88 * The database platform object used by the connection or NULL before it's initialized.
89 */
90 private ?AbstractPlatform $platform = null;
91
92 private ?ExceptionConverter $exceptionConverter = null;
93 private ?Parser $parser = null;
94
95 /**
96 * Flag that indicates whether the current transaction is marked for rollback only.
97 */
98 private bool $isRollbackOnly = false;
99
100 private SchemaManagerFactory $schemaManagerFactory;
101
102 /**
103 * Initializes a new instance of the Connection class.
104 *
105 * @internal The connection can be only instantiated by the driver manager.
106 *
107 * @param array<string, mixed> $params The connection parameters.
108 * @param Driver $driver The driver to use.
109 * @param Configuration|null $config The configuration, optional.
110 * @psalm-param Params $params
111 */
112 public function __construct(
113 #[SensitiveParameter]
114 array $params,
115 protected Driver $driver,
116 ?Configuration $config = null,
117 ) {
118 $this->_config = $config ?? new Configuration();
119 $this->params = $params;
120 $this->autoCommit = $this->_config->getAutoCommit();
121
122 $this->schemaManagerFactory = $this->_config->getSchemaManagerFactory()
123 ?? new DefaultSchemaManagerFactory();
124 }
125
126 /**
127 * Gets the parameters used during instantiation.
128 *
129 * @internal
130 *
131 * @return array<string,mixed>
132 * @psalm-return Params
133 */
134 public function getParams(): array
135 {
136 return $this->params;
137 }
138
139 /**
140 * Gets the name of the currently selected database.
141 *
142 * @return string|null The name of the database or NULL if a database is not selected.
143 * The platforms which don't support the concept of a database (e.g. embedded databases)
144 * must always return a string as an indicator of an implicitly selected database.
145 *
146 * @throws Exception
147 */
148 public function getDatabase(): ?string
149 {
150 $platform = $this->getDatabasePlatform();
151 $query = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression());
152 $database = $this->fetchOne($query);
153
154 assert(is_string($database) || $database === null);
155
156 return $database;
157 }
158
159 /**
160 * Gets the DBAL driver instance.
161 */
162 public function getDriver(): Driver
163 {
164 return $this->driver;
165 }
166
167 /**
168 * Gets the Configuration used by the Connection.
169 */
170 public function getConfiguration(): Configuration
171 {
172 return $this->_config;
173 }
174
175 /**
176 * Gets the DatabasePlatform for the connection.
177 *
178 * @throws Exception
179 */
180 public function getDatabasePlatform(): AbstractPlatform
181 {
182 if ($this->platform === null) {
183 $versionProvider = $this;
184
185 if (isset($this->params['serverVersion'])) {
186 $versionProvider = new StaticServerVersionProvider($this->params['serverVersion']);
187 } elseif (isset($this->params['primary']['serverVersion'])) {
188 $versionProvider = new StaticServerVersionProvider($this->params['primary']['serverVersion']);
189 }
190
191 $this->platform = $this->driver->getDatabasePlatform($versionProvider);
192 }
193
194 return $this->platform;
195 }
196
197 /**
198 * Creates an expression builder for the connection.
199 */
200 public function createExpressionBuilder(): ExpressionBuilder
201 {
202 return new ExpressionBuilder($this);
203 }
204
205 /**
206 * Establishes the connection with the database and returns the underlying connection.
207 *
208 * @throws Exception
209 */
210 protected function connect(): DriverConnection
211 {
212 if ($this->_conn !== null) {
213 return $this->_conn;
214 }
215
216 try {
217 $connection = $this->_conn = $this->driver->connect($this->params);
218 } catch (Driver\Exception $e) {
219 throw $this->convertException($e);
220 }
221
222 if ($this->autoCommit === false) {
223 $this->beginTransaction();
224 }
225
226 return $connection;
227 }
228
229 /**
230 * {@inheritDoc}
231 *
232 * @throws Exception
233 */
234 public function getServerVersion(): string
235 {
236 return $this->connect()->getServerVersion();
237 }
238
239 /**
240 * Returns the current auto-commit mode for this connection.
241 *
242 * @see setAutoCommit
243 *
244 * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
245 */
246 public function isAutoCommit(): bool
247 {
248 return $this->autoCommit;
249 }
250
251 /**
252 * Sets auto-commit mode for this connection.
253 *
254 * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
255 * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
256 * the method commit or the method rollback. By default, new connections are in auto-commit mode.
257 *
258 * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
259 * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
260 *
261 * @see isAutoCommit
262 *
263 * @throws ConnectionException
264 * @throws DriverException
265 */
266 public function setAutoCommit(bool $autoCommit): void
267 {
268 // Mode not changed, no-op.
269 if ($autoCommit === $this->autoCommit) {
270 return;
271 }
272
273 $this->autoCommit = $autoCommit;
274
275 // Commit all currently active transactions if any when switching auto-commit mode.
276 if ($this->_conn === null || $this->transactionNestingLevel === 0) {
277 return;
278 }
279
280 $this->commitAll();
281 }
282
283 /**
284 * Prepares and executes an SQL query and returns the first row of the result
285 * as an associative array.
286 *
287 * @param list<mixed>|array<string, mixed> $params
288 * @psalm-param WrapperParameterTypeArray $types
289 *
290 * @return array<string, mixed>|false False is returned if no rows are found.
291 *
292 * @throws Exception
293 */
294 public function fetchAssociative(string $query, array $params = [], array $types = []): array|false
295 {
296 return $this->executeQuery($query, $params, $types)->fetchAssociative();
297 }
298
299 /**
300 * Prepares and executes an SQL query and returns the first row of the result
301 * as a numerically indexed array.
302 *
303 * @param list<mixed>|array<string, mixed> $params
304 * @psalm-param WrapperParameterTypeArray $types
305 *
306 * @return list<mixed>|false False is returned if no rows are found.
307 *
308 * @throws Exception
309 */
310 public function fetchNumeric(string $query, array $params = [], array $types = []): array|false
311 {
312 return $this->executeQuery($query, $params, $types)->fetchNumeric();
313 }
314
315 /**
316 * Prepares and executes an SQL query and returns the value of a single column
317 * of the first row of the result.
318 *
319 * @param list<mixed>|array<string, mixed> $params
320 * @psalm-param WrapperParameterTypeArray $types
321 *
322 * @return mixed|false False is returned if no rows are found.
323 *
324 * @throws Exception
325 */
326 public function fetchOne(string $query, array $params = [], array $types = []): mixed
327 {
328 return $this->executeQuery($query, $params, $types)->fetchOne();
329 }
330
331 /**
332 * Whether an actual connection to the database is established.
333 */
334 public function isConnected(): bool
335 {
336 return $this->_conn !== null;
337 }
338
339 /**
340 * Checks whether a transaction is currently active.
341 *
342 * @return bool TRUE if a transaction is currently active, FALSE otherwise.
343 */
344 public function isTransactionActive(): bool
345 {
346 return $this->transactionNestingLevel > 0;
347 }
348
349 /**
350 * Adds condition based on the criteria to the query components
351 *
352 * @param array<string, mixed> $criteria Map of key columns to their values
353 *
354 * @return array{list<string>, list<mixed>, list<string>}
355 */
356 private function getCriteriaCondition(array $criteria): array
357 {
358 $columns = $values = $conditions = [];
359
360 foreach ($criteria as $columnName => $value) {
361 if ($value === null) {
362 $conditions[] = $columnName . ' IS NULL';
363 continue;
364 }
365
366 $columns[] = $columnName;
367 $values[] = $value;
368 $conditions[] = $columnName . ' = ?';
369 }
370
371 return [$columns, $values, $conditions];
372 }
373
374 /**
375 * Executes an SQL DELETE statement on a table.
376 *
377 * Table expression and columns are not escaped and are not safe for user-input.
378 *
379 * @param array<string, mixed> $criteria
380 * @param array<int<0,max>, string|ParameterType|Type>|array<string, string|ParameterType|Type> $types
381 *
382 * @return int|numeric-string The number of affected rows.
383 *
384 * @throws Exception
385 */
386 public function delete(string $table, array $criteria = [], array $types = []): int|string
387 {
388 [$columns, $values, $conditions] = $this->getCriteriaCondition($criteria);
389
390 $sql = 'DELETE FROM ' . $table;
391
392 if ($conditions !== []) {
393 $sql .= ' WHERE ' . implode(' AND ', $conditions);
394 }
395
396 return $this->executeStatement(
397 $sql,
398 $values,
399 is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types,
400 );
401 }
402
403 /**
404 * Closes the connection.
405 */
406 public function close(): void
407 {
408 $this->_conn = null;
409 $this->transactionNestingLevel = 0;
410 }
411
412 /**
413 * Sets the transaction isolation level.
414 *
415 * @param TransactionIsolationLevel $level The level to set.
416 *
417 * @throws Exception
418 */
419 public function setTransactionIsolation(TransactionIsolationLevel $level): void
420 {
421 $this->transactionIsolationLevel = $level;
422
423 $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
424 }
425
426 /**
427 * Gets the currently active transaction isolation level.
428 *
429 * @return TransactionIsolationLevel The current transaction isolation level.
430 *
431 * @throws Exception
432 */
433 public function getTransactionIsolation(): TransactionIsolationLevel
434 {
435 return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
436 }
437
438 /**
439 * Executes an SQL UPDATE statement on a table.
440 *
441 * Table expression and columns are not escaped and are not safe for user-input.
442 *
443 * @param array<string, mixed> $data
444 * @param array<string, mixed> $criteria
445 * @param array<int<0,max>, string|ParameterType|Type>|array<string, string|ParameterType|Type> $types
446 *
447 * @return int|numeric-string The number of affected rows.
448 *
449 * @throws Exception
450 */
451 public function update(string $table, array $data, array $criteria = [], array $types = []): int|string
452 {
453 $columns = $values = $conditions = $set = [];
454
455 foreach ($data as $columnName => $value) {
456 $columns[] = $columnName;
457 $values[] = $value;
458 $set[] = $columnName . ' = ?';
459 }
460
461 [$criteriaColumns, $criteriaValues, $criteriaConditions] = $this->getCriteriaCondition($criteria);
462
463 $columns = array_merge($columns, $criteriaColumns);
464 $values = array_merge($values, $criteriaValues);
465 $conditions = array_merge($conditions, $criteriaConditions);
466
467 if (is_string(key($types))) {
468 $types = $this->extractTypeValues($columns, $types);
469 }
470
471 $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set);
472
473 if ($conditions !== []) {
474 $sql .= ' WHERE ' . implode(' AND ', $conditions);
475 }
476
477 return $this->executeStatement($sql, $values, $types);
478 }
479
480 /**
481 * Inserts a table row with specified data.
482 *
483 * Table expression and columns are not escaped and are not safe for user-input.
484 *
485 * @param array<string, mixed> $data
486 * @param array<int<0,max>, string|ParameterType|Type>|array<string, string|ParameterType|Type> $types
487 *
488 * @return int|numeric-string The number of affected rows.
489 *
490 * @throws Exception
491 */
492 public function insert(string $table, array $data, array $types = []): int|string
493 {
494 if (count($data) === 0) {
495 return $this->executeStatement('INSERT INTO ' . $table . ' () VALUES ()');
496 }
497
498 $columns = [];
499 $values = [];
500 $set = [];
501
502 foreach ($data as $columnName => $value) {
503 $columns[] = $columnName;
504 $values[] = $value;
505 $set[] = '?';
506 }
507
508 return $this->executeStatement(
509 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' .
510 ' VALUES (' . implode(', ', $set) . ')',
511 $values,
512 is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types,
513 );
514 }
515
516 /**
517 * Extract ordered type list from an ordered column list and type map.
518 *
519 * @param array<int, string> $columns
520 * @param array<int, string|ParameterType|Type>|array<string, string|ParameterType|Type> $types
521 *
522 * @return array<int<0, max>, string|ParameterType|Type>
523 */
524 private function extractTypeValues(array $columns, array $types): array
525 {
526 $typeValues = [];
527
528 foreach ($columns as $columnName) {
529 $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
530 }
531
532 return $typeValues;
533 }
534
535 /**
536 * Quotes a string so it can be safely used as a table or column name, even if
537 * it is a reserved name.
538 *
539 * Delimiting style depends on the underlying database platform that is being used.
540 *
541 * NOTE: Just because you CAN use quoted identifiers does not mean
542 * you SHOULD use them. In general, they end up causing way more
543 * problems than they solve.
544 *
545 * @param string $identifier The identifier to be quoted.
546 *
547 * @return string The quoted identifier.
548 */
549 public function quoteIdentifier(string $identifier): string
550 {
551 return $this->getDatabasePlatform()->quoteIdentifier($identifier);
552 }
553
554 /**
555 * The usage of this method is discouraged. Use prepared statements
556 * or {@see AbstractPlatform::quoteStringLiteral()} instead.
557 */
558 public function quote(string $value): string
559 {
560 return $this->connect()->quote($value);
561 }
562
563 /**
564 * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
565 *
566 * @param list<mixed>|array<string, mixed> $params
567 * @psalm-param WrapperParameterTypeArray $types
568 *
569 * @return list<list<mixed>>
570 *
571 * @throws Exception
572 */
573 public function fetchAllNumeric(string $query, array $params = [], array $types = []): array
574 {
575 return $this->executeQuery($query, $params, $types)->fetchAllNumeric();
576 }
577
578 /**
579 * Prepares and executes an SQL query and returns the result as an array of associative arrays.
580 *
581 * @param list<mixed>|array<string, mixed> $params
582 * @psalm-param WrapperParameterTypeArray $types
583 *
584 * @return list<array<string,mixed>>
585 *
586 * @throws Exception
587 */
588 public function fetchAllAssociative(string $query, array $params = [], array $types = []): array
589 {
590 return $this->executeQuery($query, $params, $types)->fetchAllAssociative();
591 }
592
593 /**
594 * Prepares and executes an SQL query and returns the result as an associative array with the keys
595 * mapped to the first column and the values mapped to the second column.
596 *
597 * @param list<mixed>|array<string, mixed> $params
598 * @psalm-param WrapperParameterTypeArray $types
599 *
600 * @return array<mixed,mixed>
601 *
602 * @throws Exception
603 */
604 public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array
605 {
606 return $this->executeQuery($query, $params, $types)->fetchAllKeyValue();
607 }
608
609 /**
610 * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
611 * to the first column and the values being an associative array representing the rest of the columns
612 * and their values.
613 *
614 * @param list<mixed>|array<string, mixed> $params
615 * @psalm-param WrapperParameterTypeArray $types
616 *
617 * @return array<mixed,array<string,mixed>>
618 *
619 * @throws Exception
620 */
621 public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array
622 {
623 return $this->executeQuery($query, $params, $types)->fetchAllAssociativeIndexed();
624 }
625
626 /**
627 * Prepares and executes an SQL query and returns the result as an array of the first column values.
628 *
629 * @param list<mixed>|array<string, mixed> $params
630 * @psalm-param WrapperParameterTypeArray $types
631 *
632 * @return list<mixed>
633 *
634 * @throws Exception
635 */
636 public function fetchFirstColumn(string $query, array $params = [], array $types = []): array
637 {
638 return $this->executeQuery($query, $params, $types)->fetchFirstColumn();
639 }
640
641 /**
642 * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays.
643 *
644 * @param list<mixed>|array<string, mixed> $params
645 * @psalm-param WrapperParameterTypeArray $types
646 *
647 * @return Traversable<int,list<mixed>>
648 *
649 * @throws Exception
650 */
651 public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable
652 {
653 return $this->executeQuery($query, $params, $types)->iterateNumeric();
654 }
655
656 /**
657 * Prepares and executes an SQL query and returns the result as an iterator over rows represented
658 * as associative arrays.
659 *
660 * @param list<mixed>|array<string, mixed> $params
661 * @psalm-param WrapperParameterTypeArray $types
662 *
663 * @return Traversable<int,array<string,mixed>>
664 *
665 * @throws Exception
666 */
667 public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable
668 {
669 return $this->executeQuery($query, $params, $types)->iterateAssociative();
670 }
671
672 /**
673 * Prepares and executes an SQL query and returns the result as an iterator with the keys
674 * mapped to the first column and the values mapped to the second column.
675 *
676 * @param list<mixed>|array<string, mixed> $params
677 * @psalm-param WrapperParameterTypeArray $types
678 *
679 * @return Traversable<mixed,mixed>
680 *
681 * @throws Exception
682 */
683 public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable
684 {
685 return $this->executeQuery($query, $params, $types)->iterateKeyValue();
686 }
687
688 /**
689 * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped
690 * to the first column and the values being an associative array representing the rest of the columns
691 * and their values.
692 *
693 * @param list<mixed>|array<string, mixed> $params
694 * @psalm-param WrapperParameterTypeArray $types
695 *
696 * @return Traversable<mixed,array<string,mixed>>
697 *
698 * @throws Exception
699 */
700 public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable
701 {
702 return $this->executeQuery($query, $params, $types)->iterateAssociativeIndexed();
703 }
704
705 /**
706 * Prepares and executes an SQL query and returns the result as an iterator over the first column values.
707 *
708 * @param list<mixed>|array<string, mixed> $params
709 * @psalm-param WrapperParameterTypeArray $types
710 *
711 * @return Traversable<int,mixed>
712 *
713 * @throws Exception
714 */
715 public function iterateColumn(string $query, array $params = [], array $types = []): Traversable
716 {
717 return $this->executeQuery($query, $params, $types)->iterateColumn();
718 }
719
720 /**
721 * Prepares an SQL statement.
722 *
723 * @param string $sql The SQL statement to prepare.
724 *
725 * @throws Exception
726 */
727 public function prepare(string $sql): Statement
728 {
729 $connection = $this->connect();
730
731 try {
732 $statement = $connection->prepare($sql);
733 } catch (Driver\Exception $e) {
734 throw $this->convertExceptionDuringQuery($e, $sql);
735 }
736
737 return new Statement($this, $statement, $sql);
738 }
739
740 /**
741 * Executes an, optionally parameterized, SQL query.
742 *
743 * If the query is parametrized, a prepared statement is used.
744 *
745 * @param list<mixed>|array<string, mixed> $params
746 * @psalm-param WrapperParameterTypeArray $types
747 *
748 * @throws Exception
749 */
750 public function executeQuery(
751 string $sql,
752 array $params = [],
753 array $types = [],
754 ?QueryCacheProfile $qcp = null,
755 ): Result {
756 if ($qcp !== null) {
757 return $this->executeCacheQuery($sql, $params, $types, $qcp);
758 }
759
760 $connection = $this->connect();
761
762 try {
763 if (count($params) > 0) {
764 [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types);
765
766 $stmt = $connection->prepare($sql);
767
768 $this->bindParameters($stmt, $params, $types);
769
770 $result = $stmt->execute();
771 } else {
772 $result = $connection->query($sql);
773 }
774
775 return new Result($result, $this);
776 } catch (Driver\Exception $e) {
777 throw $this->convertExceptionDuringQuery($e, $sql, $params, $types);
778 }
779 }
780
781 /**
782 * Executes a caching query.
783 *
784 * @param list<mixed>|array<string, mixed> $params
785 * @psalm-param WrapperParameterTypeArray $types
786 *
787 * @throws CacheException
788 * @throws Exception
789 */
790 public function executeCacheQuery(string $sql, array $params, array $types, QueryCacheProfile $qcp): Result
791 {
792 $resultCache = $qcp->getResultCache() ?? $this->_config->getResultCache();
793
794 if ($resultCache === null) {
795 throw NoResultDriverConfigured::new();
796 }
797
798 $connectionParams = $this->params;
799 unset($connectionParams['password']);
800
801 [$cacheKey, $realKey] = $qcp->generateCacheKeys($sql, $params, $types, $connectionParams);
802
803 $item = $resultCache->getItem($cacheKey);
804
805 if ($item->isHit()) {
806 $value = $item->get();
807 if (! is_array($value)) {
808 $value = [];
809 }
810
811 if (isset($value[$realKey])) {
812 return new Result(new ArrayResult($value[$realKey]), $this);
813 }
814 } else {
815 $value = [];
816 }
817
818 $data = $this->fetchAllAssociative($sql, $params, $types);
819
820 $value[$realKey] = $data;
821
822 $item->set($value);
823
824 $lifetime = $qcp->getLifetime();
825 if ($lifetime > 0) {
826 $item->expiresAfter($lifetime);
827 }
828
829 $resultCache->save($item);
830
831 return new Result(new ArrayResult($data), $this);
832 }
833
834 /**
835 * Executes an SQL statement with the given parameters and returns the number of affected rows.
836 *
837 * Could be used for:
838 * - DML statements: INSERT, UPDATE, DELETE, etc.
839 * - DDL statements: CREATE, DROP, ALTER, etc.
840 * - DCL statements: GRANT, REVOKE, etc.
841 * - Session control statements: ALTER SESSION, SET, DECLARE, etc.
842 * - Other statements that don't yield a row set.
843 *
844 * This method supports PDO binding types as well as DBAL mapping types.
845 *
846 * @param list<mixed>|array<string, mixed> $params
847 * @psalm-param WrapperParameterTypeArray $types
848 *
849 * @return int|numeric-string
850 *
851 * @throws Exception
852 */
853 public function executeStatement(string $sql, array $params = [], array $types = []): int|string
854 {
855 $connection = $this->connect();
856
857 try {
858 if (count($params) > 0) {
859 [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types);
860
861 $stmt = $connection->prepare($sql);
862
863 $this->bindParameters($stmt, $params, $types);
864
865 return $stmt->execute()
866 ->rowCount();
867 }
868
869 return $connection->exec($sql);
870 } catch (Driver\Exception $e) {
871 throw $this->convertExceptionDuringQuery($e, $sql, $params, $types);
872 }
873 }
874
875 /**
876 * Returns the current transaction nesting level.
877 *
878 * @return int The nesting level. A value of 0 means there's no active transaction.
879 */
880 public function getTransactionNestingLevel(): int
881 {
882 return $this->transactionNestingLevel;
883 }
884
885 /**
886 * Returns the ID of the last inserted row.
887 *
888 * If the underlying driver does not support identity columns, an exception is thrown.
889 *
890 * @throws Exception
891 */
892 public function lastInsertId(): int|string
893 {
894 try {
895 return $this->connect()->lastInsertId();
896 } catch (Driver\Exception $e) {
897 throw $this->convertException($e);
898 }
899 }
900
901 /**
902 * Executes a function in a transaction.
903 *
904 * The function gets passed this Connection instance as an (optional) parameter.
905 *
906 * If an exception occurs during execution of the function or transaction commit,
907 * the transaction is rolled back and the exception re-thrown.
908 *
909 * @param Closure(self):T $func The function to execute transactionally.
910 *
911 * @return T The value returned by $func
912 *
913 * @throws Throwable
914 *
915 * @template T
916 */
917 public function transactional(Closure $func): mixed
918 {
919 $this->beginTransaction();
920 try {
921 $res = $func($this);
922 $this->commit();
923
924 return $res;
925 } catch (Throwable $e) {
926 $this->rollBack();
927
928 throw $e;
929 }
930 }
931
932 /**
933 * Sets if nested transactions should use savepoints.
934 *
935 * @deprecated No replacement planned
936 *
937 * @throws Exception
938 */
939 public function setNestTransactionsWithSavepoints(bool $nestTransactionsWithSavepoints): void
940 {
941 if (! $nestTransactionsWithSavepoints) {
942 throw new InvalidArgumentException(sprintf(
943 'Calling %s with false to enable nesting transactions without savepoints is no longer supported.',
944 __METHOD__,
945 ));
946 }
947
948 Deprecation::trigger(
949 'doctrine/dbal',
950 'https://github.com/doctrine/dbal/pull/5383',
951 '%s is deprecated and will be removed in 5.0',
952 __METHOD__,
953 );
954 }
955
956 /**
957 * Gets if nested transactions should use savepoints.
958 *
959 * @deprecated No replacement planned
960 */
961 public function getNestTransactionsWithSavepoints(): bool
962 {
963 Deprecation::trigger(
964 'doctrine/dbal',
965 'https://github.com/doctrine/dbal/pull/5383',
966 '%s is deprecated and will be removed in 5.0',
967 __METHOD__,
968 );
969
970 return true;
971 }
972
973 /**
974 * Returns the savepoint name to use for nested transactions.
975 */
976 protected function _getNestedTransactionSavePointName(): string
977 {
978 return 'DOCTRINE_' . $this->transactionNestingLevel;
979 }
980
981 /** @throws Exception */
982 public function beginTransaction(): void
983 {
984 $connection = $this->connect();
985
986 ++$this->transactionNestingLevel;
987
988 if ($this->transactionNestingLevel === 1) {
989 $connection->beginTransaction();
990 } else {
991 $this->createSavepoint($this->_getNestedTransactionSavePointName());
992 }
993 }
994
995 /** @throws Exception */
996 public function commit(): void
997 {
998 if ($this->transactionNestingLevel === 0) {
999 throw NoActiveTransaction::new();
1000 }
1001
1002 if ($this->isRollbackOnly) {
1003 throw CommitFailedRollbackOnly::new();
1004 }
1005
1006 $connection = $this->connect();
1007
1008 if ($this->transactionNestingLevel === 1) {
1009 try {
1010 $connection->commit();
1011 } catch (Driver\Exception $e) {
1012 throw $this->convertException($e);
1013 }
1014 } else {
1015 $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1016 }
1017
1018 --$this->transactionNestingLevel;
1019
1020 if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1021 return;
1022 }
1023
1024 $this->beginTransaction();
1025 }
1026
1027 /**
1028 * Commits all current nesting transactions.
1029 *
1030 * @throws Exception
1031 */
1032 private function commitAll(): void
1033 {
1034 while ($this->transactionNestingLevel !== 0) {
1035 if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
1036 // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1037 // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1038 $this->commit();
1039
1040 return;
1041 }
1042
1043 $this->commit();
1044 }
1045 }
1046
1047 /** @throws Exception */
1048 public function rollBack(): void
1049 {
1050 if ($this->transactionNestingLevel === 0) {
1051 throw NoActiveTransaction::new();
1052 }
1053
1054 $connection = $this->connect();
1055
1056 if ($this->transactionNestingLevel === 1) {
1057 $this->transactionNestingLevel = 0;
1058
1059 try {
1060 $connection->rollBack();
1061 } catch (Driver\Exception $e) {
1062 throw $this->convertException($e);
1063 } finally {
1064 $this->isRollbackOnly = false;
1065
1066 if ($this->autoCommit === false) {
1067 $this->beginTransaction();
1068 }
1069 }
1070 } else {
1071 $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1072 --$this->transactionNestingLevel;
1073 }
1074 }
1075
1076 /**
1077 * Creates a new savepoint.
1078 *
1079 * @param string $savepoint The name of the savepoint to create.
1080 *
1081 * @throws Exception
1082 */
1083 public function createSavepoint(string $savepoint): void
1084 {
1085 $platform = $this->getDatabasePlatform();
1086
1087 if (! $platform->supportsSavepoints()) {
1088 throw SavepointsNotSupported::new();
1089 }
1090
1091 $this->executeStatement($platform->createSavePoint($savepoint));
1092 }
1093
1094 /**
1095 * Releases the given savepoint.
1096 *
1097 * @param string $savepoint The name of the savepoint to release.
1098 *
1099 * @throws Exception
1100 */
1101 public function releaseSavepoint(string $savepoint): void
1102 {
1103 $platform = $this->getDatabasePlatform();
1104
1105 if (! $platform->supportsSavepoints()) {
1106 throw SavepointsNotSupported::new();
1107 }
1108
1109 if (! $platform->supportsReleaseSavepoints()) {
1110 return;
1111 }
1112
1113 $this->executeStatement($platform->releaseSavePoint($savepoint));
1114 }
1115
1116 /**
1117 * Rolls back to the given savepoint.
1118 *
1119 * @param string $savepoint The name of the savepoint to rollback to.
1120 *
1121 * @throws Exception
1122 */
1123 public function rollbackSavepoint(string $savepoint): void
1124 {
1125 $platform = $this->getDatabasePlatform();
1126
1127 if (! $platform->supportsSavepoints()) {
1128 throw SavepointsNotSupported::new();
1129 }
1130
1131 $this->executeStatement($platform->rollbackSavePoint($savepoint));
1132 }
1133
1134 /**
1135 * Provides access to the native database connection.
1136 *
1137 * @return resource|object
1138 *
1139 * @throws Exception
1140 */
1141 public function getNativeConnection()
1142 {
1143 return $this->connect()->getNativeConnection();
1144 }
1145
1146 /**
1147 * Creates a SchemaManager that can be used to inspect or change the
1148 * database schema through the connection.
1149 *
1150 * @throws Exception
1151 */
1152 public function createSchemaManager(): AbstractSchemaManager
1153 {
1154 return $this->schemaManagerFactory->createSchemaManager($this);
1155 }
1156
1157 /**
1158 * Marks the current transaction so that the only possible
1159 * outcome for the transaction to be rolled back.
1160 *
1161 * @throws ConnectionException If no transaction is active.
1162 */
1163 public function setRollbackOnly(): void
1164 {
1165 if ($this->transactionNestingLevel === 0) {
1166 throw NoActiveTransaction::new();
1167 }
1168
1169 $this->isRollbackOnly = true;
1170 }
1171
1172 /**
1173 * Checks whether the current transaction is marked for rollback only.
1174 *
1175 * @throws ConnectionException If no transaction is active.
1176 */
1177 public function isRollbackOnly(): bool
1178 {
1179 if ($this->transactionNestingLevel === 0) {
1180 throw NoActiveTransaction::new();
1181 }
1182
1183 return $this->isRollbackOnly;
1184 }
1185
1186 /**
1187 * Converts a given value to its database representation according to the conversion
1188 * rules of a specific DBAL mapping type.
1189 *
1190 * @param mixed $value The value to convert.
1191 * @param string $type The name of the DBAL mapping type.
1192 *
1193 * @return mixed The converted value.
1194 *
1195 * @throws Exception
1196 */
1197 public function convertToDatabaseValue(mixed $value, string $type): mixed
1198 {
1199 return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1200 }
1201
1202 /**
1203 * Converts a given value to its PHP representation according to the conversion
1204 * rules of a specific DBAL mapping type.
1205 *
1206 * @param mixed $value The value to convert.
1207 * @param string $type The name of the DBAL mapping type.
1208 *
1209 * @return mixed The converted type.
1210 *
1211 * @throws Exception
1212 */
1213 public function convertToPHPValue(mixed $value, string $type): mixed
1214 {
1215 return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1216 }
1217
1218 /**
1219 * Binds a set of parameters, some or all of which are typed with a PDO binding type
1220 * or DBAL mapping type, to a given statement.
1221 *
1222 * @param list<mixed>|array<string, mixed> $params
1223 * @param array<int, string|ParameterType|Type>|array<string, string|ParameterType|Type> $types
1224 *
1225 * @throws Exception
1226 */
1227 private function bindParameters(DriverStatement $stmt, array $params, array $types): void
1228 {
1229 // Check whether parameters are positional or named. Mixing is not allowed.
1230 if (is_int(key($params))) {
1231 $bindIndex = 1;
1232
1233 foreach ($params as $key => $value) {
1234 if (array_key_exists($key, $types)) {
1235 $type = $types[$key];
1236 [$value, $bindingType] = $this->getBindingInfo($value, $type);
1237 } else {
1238 $bindingType = ParameterType::STRING;
1239 }
1240
1241 $stmt->bindValue($bindIndex, $value, $bindingType);
1242
1243 ++$bindIndex;
1244 }
1245 } else {
1246 // Named parameters
1247 foreach ($params as $name => $value) {
1248 if (array_key_exists($name, $types)) {
1249 $type = $types[$name];
1250 [$value, $bindingType] = $this->getBindingInfo($value, $type);
1251 } else {
1252 $bindingType = ParameterType::STRING;
1253 }
1254
1255 $stmt->bindValue($name, $value, $bindingType);
1256 }
1257 }
1258 }
1259
1260 /**
1261 * Gets the binding type of a given type.
1262 *
1263 * @param mixed $value The value to bind.
1264 * @param string|ParameterType|Type $type The type to bind.
1265 *
1266 * @return array{mixed, ParameterType} [0] => the (escaped) value, [1] => the binding type.
1267 *
1268 * @throws Exception
1269 */
1270 private function getBindingInfo(mixed $value, string|ParameterType|Type $type): array
1271 {
1272 if (is_string($type)) {
1273 $type = Type::getType($type);
1274 }
1275
1276 if ($type instanceof Type) {
1277 $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1278 $bindingType = $type->getBindingType();
1279 } else {
1280 $bindingType = $type;
1281 }
1282
1283 return [$value, $bindingType];
1284 }
1285
1286 /**
1287 * Creates a new instance of a SQL query builder.
1288 */
1289 public function createQueryBuilder(): QueryBuilder
1290 {
1291 return new Query\QueryBuilder($this);
1292 }
1293
1294 /**
1295 * @internal
1296 *
1297 * @param list<mixed>|array<string,mixed> $params
1298 * @psalm-param WrapperParameterTypeArray $types
1299 */
1300 final public function convertExceptionDuringQuery(
1301 Driver\Exception $e,
1302 string $sql,
1303 array $params = [],
1304 array $types = [],
1305 ): DriverException {
1306 return $this->handleDriverException($e, new Query($sql, $params, $types));
1307 }
1308
1309 /** @internal */
1310 final public function convertException(Driver\Exception $e): DriverException
1311 {
1312 return $this->handleDriverException($e, null);
1313 }
1314
1315 /**
1316 * @param list<mixed>|array<string, mixed> $params
1317 * @psalm-param WrapperParameterTypeArray $types
1318 *
1319 * @return array{
1320 * string,
1321 * list<mixed>|array<string, mixed>,
1322 * array<int<0, max>, string|ParameterType|Type>|array<string, string|ParameterType|Type>
1323 * }
1324 */
1325 private function expandArrayParameters(string $sql, array $params, array $types): array
1326 {
1327 $needsConversion = false;
1328 $nonArrayTypes = [];
1329
1330 if (is_string(key($params))) {
1331 $needsConversion = true;
1332 } else {
1333 foreach ($types as $key => $type) {
1334 if ($type instanceof ArrayParameterType) {
1335 $needsConversion = true;
1336 break;
1337 }
1338
1339 $nonArrayTypes[$key] = $type;
1340 }
1341 }
1342
1343 if (! $needsConversion) {
1344 return [$sql, $params, $nonArrayTypes];
1345 }
1346
1347 $this->parser ??= $this->getDatabasePlatform()->createSQLParser();
1348 $visitor = new ExpandArrayParameters($params, $types);
1349
1350 $this->parser->parse($sql, $visitor);
1351
1352 return [
1353 $visitor->getSQL(),
1354 $visitor->getParameters(),
1355 $visitor->getTypes(),
1356 ];
1357 }
1358
1359 private function handleDriverException(
1360 Driver\Exception $driverException,
1361 ?Query $query,
1362 ): DriverException {
1363 $this->exceptionConverter ??= $this->driver->getExceptionConverter();
1364 $exception = $this->exceptionConverter->convert($driverException, $query);
1365
1366 if ($exception instanceof ConnectionLost) {
1367 $this->close();
1368 }
1369
1370 return $exception;
1371 }
1372}
diff --git a/vendor/doctrine/dbal/src/Connection/StaticServerVersionProvider.php b/vendor/doctrine/dbal/src/Connection/StaticServerVersionProvider.php
new file mode 100644
index 0000000..aebe086
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Connection/StaticServerVersionProvider.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Connection;
6
7use Doctrine\DBAL\ServerVersionProvider;
8
9class StaticServerVersionProvider implements ServerVersionProvider
10{
11 public function __construct(private readonly string $version)
12 {
13 }
14
15 public function getServerVersion(): string
16 {
17 return $this->version;
18 }
19}
diff --git a/vendor/doctrine/dbal/src/ConnectionException.php b/vendor/doctrine/dbal/src/ConnectionException.php
new file mode 100644
index 0000000..de96248
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ConnectionException.php
@@ -0,0 +1,10 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7/** @psalm-immutable */
8class ConnectionException extends \Exception implements Exception
9{
10}
diff --git a/vendor/doctrine/dbal/src/Connections/PrimaryReadReplicaConnection.php b/vendor/doctrine/dbal/src/Connections/PrimaryReadReplicaConnection.php
new file mode 100644
index 0000000..1d9c1a9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Connections/PrimaryReadReplicaConnection.php
@@ -0,0 +1,327 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Connections;
6
7use Doctrine\DBAL\Configuration;
8use Doctrine\DBAL\Connection;
9use Doctrine\DBAL\Driver;
10use Doctrine\DBAL\Driver\Connection as DriverConnection;
11use Doctrine\DBAL\Driver\Exception as DriverException;
12use Doctrine\DBAL\DriverManager;
13use Doctrine\DBAL\Exception;
14use Doctrine\DBAL\Statement;
15use InvalidArgumentException;
16use SensitiveParameter;
17
18use function array_rand;
19use function assert;
20use function count;
21
22/**
23 * Primary-Replica Connection
24 *
25 * Connection can be used with primary-replica setups.
26 *
27 * Important for the understanding of this connection should be how and when
28 * it picks the replica or primary.
29 *
30 * 1. Replica if primary was never picked before and ONLY if 'getWrappedConnection'
31 * or 'executeQuery' is used.
32 * 2. Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint',
33 * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called.
34 * 3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards.
35 * 4. One replica connection is randomly picked ONCE during a request.
36 *
37 * ATTENTION: You can write to the replica with this connection if you execute a write query without
38 * opening up a transaction. For example:
39 *
40 * $conn = DriverManager::getConnection(...);
41 * $conn->executeQuery("DELETE FROM table");
42 *
43 * Be aware that Connection#executeQuery is a method specifically for READ
44 * operations only.
45 *
46 * Use Connection#executeStatement for any SQL statement that changes/updates
47 * state in the database (UPDATE, INSERT, DELETE or DDL statements).
48 *
49 * This connection is limited to replica operations using the
50 * Connection#executeQuery operation only, because it wouldn't be compatible
51 * with the ORM or SchemaManager code otherwise. Both use all the other
52 * operations in a context where writes could happen to a replica, which makes
53 * this restricted approach necessary.
54 *
55 * You can manually connect to the primary at any time by calling:
56 *
57 * $conn->ensureConnectedToPrimary();
58 *
59 * Instantiation through the DriverManager looks like:
60 *
61 * @psalm-import-type Params from DriverManager
62 * @psalm-import-type OverrideParams from DriverManager
63 * @example
64 *
65 * $conn = DriverManager::getConnection(array(
66 * 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection',
67 * 'driver' => 'pdo_mysql',
68 * 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
69 * 'replica' => array(
70 * array('user' => 'replica1', 'password' => '', 'host' => '', 'dbname' => ''),
71 * array('user' => 'replica2', 'password' => '', 'host' => '', 'dbname' => ''),
72 * )
73 * ));
74 *
75 * You can also pass 'driverOptions' and any other documented option to each of this drivers
76 * to pass additional information.
77 */
78class PrimaryReadReplicaConnection extends Connection
79{
80 /**
81 * Primary and Replica connection (one of the randomly picked replicas).
82 *
83 * @var array<string, DriverConnection|null>
84 */
85 protected array $connections = ['primary' => null, 'replica' => null];
86
87 /**
88 * You can keep the replica connection and then switch back to it
89 * during the request if you know what you are doing.
90 */
91 protected bool $keepReplica = false;
92
93 /**
94 * Creates Primary Replica Connection.
95 *
96 * @internal The connection can be only instantiated by the driver manager.
97 *
98 * @param array<string, mixed> $params
99 * @psalm-param Params $params
100 */
101 public function __construct(array $params, Driver $driver, ?Configuration $config = null)
102 {
103 if (! isset($params['replica'], $params['primary'])) {
104 throw new InvalidArgumentException('primary or replica configuration missing');
105 }
106
107 if (count($params['replica']) === 0) {
108 throw new InvalidArgumentException('You have to configure at least one replica.');
109 }
110
111 if (isset($params['driver'])) {
112 $params['primary']['driver'] = $params['driver'];
113
114 foreach ($params['replica'] as $replicaKey => $replica) {
115 $params['replica'][$replicaKey]['driver'] = $params['driver'];
116 }
117 }
118
119 $this->keepReplica = ! empty($params['keepReplica']);
120
121 parent::__construct($params, $driver, $config);
122 }
123
124 /**
125 * Checks if the connection is currently towards the primary or not.
126 */
127 public function isConnectedToPrimary(): bool
128 {
129 return $this->_conn !== null && $this->_conn === $this->connections['primary'];
130 }
131
132 public function connect(?string $connectionName = null): DriverConnection
133 {
134 if ($connectionName !== null) {
135 throw new InvalidArgumentException(
136 'Passing a connection name as first argument is not supported anymore.'
137 . ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.',
138 );
139 }
140
141 return $this->performConnect();
142 }
143
144 protected function performConnect(?string $connectionName = null): DriverConnection
145 {
146 $requestedConnectionChange = ($connectionName !== null);
147 $connectionName ??= 'replica';
148
149 if ($connectionName !== 'replica' && $connectionName !== 'primary') {
150 throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.');
151 }
152
153 // If we have a connection open, and this is not an explicit connection
154 // change request, then abort right here, because we are already done.
155 // This prevents writes to the replica in case of "keepReplica" option enabled.
156 if ($this->_conn !== null && ! $requestedConnectionChange) {
157 return $this->_conn;
158 }
159
160 $forcePrimaryAsReplica = false;
161
162 if ($this->getTransactionNestingLevel() > 0) {
163 $connectionName = 'primary';
164 $forcePrimaryAsReplica = true;
165 }
166
167 if (isset($this->connections[$connectionName])) {
168 $this->_conn = $this->connections[$connectionName];
169
170 if ($forcePrimaryAsReplica && ! $this->keepReplica) {
171 $this->connections['replica'] = $this->_conn;
172 }
173
174 return $this->_conn;
175 }
176
177 if ($connectionName === 'primary') {
178 $this->connections['primary'] = $this->_conn = $this->connectTo($connectionName);
179
180 // Set replica connection to primary to avoid invalid reads
181 if (! $this->keepReplica) {
182 $this->connections['replica'] = $this->connections['primary'];
183 }
184 } else {
185 $this->connections['replica'] = $this->_conn = $this->connectTo($connectionName);
186 }
187
188 return $this->_conn;
189 }
190
191 /**
192 * Connects to the primary node of the database cluster.
193 *
194 * All following statements after this will be executed against the primary node.
195 */
196 public function ensureConnectedToPrimary(): void
197 {
198 $this->performConnect('primary');
199 }
200
201 /**
202 * Connects to a replica node of the database cluster.
203 *
204 * All following statements after this will be executed against the replica node,
205 * unless the keepReplica option is set to false and a primary connection
206 * was already opened.
207 */
208 public function ensureConnectedToReplica(): void
209 {
210 $this->performConnect('replica');
211 }
212
213 /**
214 * Connects to a specific connection.
215 *
216 * @throws Exception
217 */
218 protected function connectTo(string $connectionName): DriverConnection
219 {
220 $params = $this->getParams();
221 assert(isset($params['primary']));
222
223 if ($connectionName === 'primary') {
224 $connectionParams = $params['primary'];
225 } else {
226 assert(isset($params['replica']));
227 $connectionParams = $this->chooseReplicaConnectionParameters($params['primary'], $params['replica']);
228 }
229
230 try {
231 return $this->driver->connect($connectionParams);
232 } catch (DriverException $e) {
233 throw $this->convertException($e);
234 }
235 }
236
237 /**
238 * @param OverrideParams $primary
239 * @param array<OverrideParams> $replicas
240 *
241 * @return array<string, mixed>
242 * @psalm-return OverrideParams
243 */
244 protected function chooseReplicaConnectionParameters(
245 #[SensitiveParameter]
246 array $primary,
247 #[SensitiveParameter]
248 array $replicas,
249 ): array {
250 $params = $replicas[array_rand($replicas)];
251
252 if (! isset($params['charset']) && isset($primary['charset'])) {
253 $params['charset'] = $primary['charset'];
254 }
255
256 return $params;
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 public function executeStatement(string $sql, array $params = [], array $types = []): int|string
263 {
264 $this->ensureConnectedToPrimary();
265
266 return parent::executeStatement($sql, $params, $types);
267 }
268
269 public function beginTransaction(): void
270 {
271 $this->ensureConnectedToPrimary();
272
273 parent::beginTransaction();
274 }
275
276 public function commit(): void
277 {
278 $this->ensureConnectedToPrimary();
279
280 parent::commit();
281 }
282
283 public function rollBack(): void
284 {
285 $this->ensureConnectedToPrimary();
286
287 parent::rollBack();
288 }
289
290 public function close(): void
291 {
292 unset($this->connections['primary'], $this->connections['replica']);
293
294 parent::close();
295
296 $this->_conn = null;
297 $this->connections = ['primary' => null, 'replica' => null];
298 }
299
300 public function createSavepoint(string $savepoint): void
301 {
302 $this->ensureConnectedToPrimary();
303
304 parent::createSavepoint($savepoint);
305 }
306
307 public function releaseSavepoint(string $savepoint): void
308 {
309 $this->ensureConnectedToPrimary();
310
311 parent::releaseSavepoint($savepoint);
312 }
313
314 public function rollbackSavepoint(string $savepoint): void
315 {
316 $this->ensureConnectedToPrimary();
317
318 parent::rollbackSavepoint($savepoint);
319 }
320
321 public function prepare(string $sql): Statement
322 {
323 $this->ensureConnectedToPrimary();
324
325 return parent::prepare($sql);
326 }
327}
diff --git a/vendor/doctrine/dbal/src/Driver.php b/vendor/doctrine/dbal/src/Driver.php
new file mode 100644
index 0000000..ffdc83c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter;
8use Doctrine\DBAL\Driver\Connection as DriverConnection;
9use Doctrine\DBAL\Driver\Exception;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use SensitiveParameter;
12
13/**
14 * Driver interface.
15 * Interface that all DBAL drivers must implement.
16 *
17 * @psalm-import-type Params from DriverManager
18 */
19interface Driver
20{
21 /**
22 * Attempts to create a connection with the database.
23 *
24 * @param array<string, mixed> $params All connection parameters.
25 * @psalm-param Params $params All connection parameters.
26 *
27 * @return DriverConnection The database connection.
28 *
29 * @throws Exception
30 */
31 public function connect(
32 #[SensitiveParameter]
33 array $params,
34 ): DriverConnection;
35
36 /**
37 * Gets the DatabasePlatform instance that provides all the metadata about
38 * the platform this driver connects to.
39 *
40 * @return AbstractPlatform The database platform.
41 */
42 public function getDatabasePlatform(ServerVersionProvider $versionProvider): AbstractPlatform;
43
44 /**
45 * Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions.
46 */
47 public function getExceptionConverter(): ExceptionConverter;
48}
diff --git a/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php
new file mode 100644
index 0000000..a7bf271
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/ExceptionConverter.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Exception\DriverException;
9use Doctrine\DBAL\Query;
10
11interface ExceptionConverter
12{
13 /**
14 * Converts a given driver-level exception into a DBAL-level driver exception.
15 *
16 * Implementors should use the vendor-specific error code and SQLSTATE of the exception
17 * and instantiate the most appropriate specialized {@see DriverException} subclass.
18 *
19 * @param Exception $exception The driver exception to convert.
20 * @param Query|null $query The SQL query that triggered the exception, if any.
21 *
22 * @return DriverException An instance of {@see DriverException} or one of its subclasses.
23 */
24 public function convert(Exception $exception, ?Query $query): DriverException;
25}
diff --git a/vendor/doctrine/dbal/src/Driver/API/IBMDB2/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/IBMDB2/ExceptionConverter.php
new file mode 100644
index 0000000..bcd5554
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/IBMDB2/ExceptionConverter.php
@@ -0,0 +1,47 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\IBMDB2;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\DriverException;
11use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
12use Doctrine\DBAL\Exception\InvalidFieldNameException;
13use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
14use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
15use Doctrine\DBAL\Exception\SyntaxErrorException;
16use Doctrine\DBAL\Exception\TableExistsException;
17use Doctrine\DBAL\Exception\TableNotFoundException;
18use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
19use Doctrine\DBAL\Query;
20
21/**
22 * @internal
23 *
24 * @link https://www.ibm.com/docs/en/db2/11.5?topic=messages-sql
25 */
26final class ExceptionConverter implements ExceptionConverterInterface
27{
28 public function convert(Exception $exception, ?Query $query): DriverException
29 {
30 return match ($exception->getCode()) {
31 -104 => new SyntaxErrorException($exception, $query),
32 -203 => new NonUniqueFieldNameException($exception, $query),
33 -204 => new TableNotFoundException($exception, $query),
34 -206 => new InvalidFieldNameException($exception, $query),
35 -407 => new NotNullConstraintViolationException($exception, $query),
36 -530,
37 -531,
38 -532,
39 -20356 => new ForeignKeyConstraintViolationException($exception, $query),
40 -601 => new TableExistsException($exception, $query),
41 -803 => new UniqueConstraintViolationException($exception, $query),
42 -1336,
43 -30082 => new ConnectionException($exception, $query),
44 default => new DriverException($exception, $query),
45 };
46 }
47}
diff --git a/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php
new file mode 100644
index 0000000..ad0f0e1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php
@@ -0,0 +1,94 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\MySQL;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\ConnectionLost;
11use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
12use Doctrine\DBAL\Exception\DeadlockException;
13use Doctrine\DBAL\Exception\DriverException;
14use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
15use Doctrine\DBAL\Exception\InvalidFieldNameException;
16use Doctrine\DBAL\Exception\LockWaitTimeoutException;
17use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
18use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
19use Doctrine\DBAL\Exception\SyntaxErrorException;
20use Doctrine\DBAL\Exception\TableExistsException;
21use Doctrine\DBAL\Exception\TableNotFoundException;
22use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
23use Doctrine\DBAL\Query;
24
25/** @internal */
26final class ExceptionConverter implements ExceptionConverterInterface
27{
28 /**
29 * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html
30 * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
31 */
32 public function convert(Exception $exception, ?Query $query): DriverException
33 {
34 return match ($exception->getCode()) {
35 1008 => new DatabaseDoesNotExist($exception, $query),
36 1213 => new DeadlockException($exception, $query),
37 1205 => new LockWaitTimeoutException($exception, $query),
38 1050 => new TableExistsException($exception, $query),
39 1051,
40 1146 => new TableNotFoundException($exception, $query),
41 1216,
42 1217,
43 1451,
44 1452,
45 1701 => new ForeignKeyConstraintViolationException($exception, $query),
46 1062,
47 1557,
48 1569,
49 1586 => new UniqueConstraintViolationException($exception, $query),
50 1054,
51 1166,
52 1611 => new InvalidFieldNameException($exception, $query),
53 1052,
54 1060,
55 1110 => new NonUniqueFieldNameException($exception, $query),
56 1064,
57 1149,
58 1287,
59 1341,
60 1342,
61 1343,
62 1344,
63 1382,
64 1479,
65 1541,
66 1554,
67 1626 => new SyntaxErrorException($exception, $query),
68 1044,
69 1045,
70 1046,
71 1049,
72 1095,
73 1142,
74 1143,
75 1227,
76 1370,
77 1429,
78 2002,
79 2005,
80 2054 => new ConnectionException($exception, $query),
81 2006,
82 4031 => new ConnectionLost($exception, $query),
83 1048,
84 1121,
85 1138,
86 1171,
87 1252,
88 1263,
89 1364,
90 1566 => new NotNullConstraintViolationException($exception, $query),
91 default => new DriverException($exception, $query),
92 };
93 }
94}
diff --git a/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php
new file mode 100644
index 0000000..1c0dc79
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/OCI/ExceptionConverter.php
@@ -0,0 +1,52 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\OCI;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
11use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
12use Doctrine\DBAL\Exception\DriverException;
13use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
14use Doctrine\DBAL\Exception\InvalidFieldNameException;
15use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
16use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
17use Doctrine\DBAL\Exception\SyntaxErrorException;
18use Doctrine\DBAL\Exception\TableExistsException;
19use Doctrine\DBAL\Exception\TableNotFoundException;
20use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
21use Doctrine\DBAL\Query;
22
23/** @internal */
24final class ExceptionConverter implements ExceptionConverterInterface
25{
26 /** @link http://www.dba-oracle.com/t_error_code_list.htm */
27 public function convert(Exception $exception, ?Query $query): DriverException
28 {
29 return match ($exception->getCode()) {
30 1,
31 2299,
32 38911 => new UniqueConstraintViolationException($exception, $query),
33 904 => new InvalidFieldNameException($exception, $query),
34 918,
35 960 => new NonUniqueFieldNameException($exception, $query),
36 923 => new SyntaxErrorException($exception, $query),
37 942 => new TableNotFoundException($exception, $query),
38 955 => new TableExistsException($exception, $query),
39 1017,
40 12545 => new ConnectionException($exception, $query),
41 1400 => new NotNullConstraintViolationException($exception, $query),
42 1918 => new DatabaseDoesNotExist($exception, $query),
43 2289,
44 2443,
45 4080 => new DatabaseObjectNotFoundException($exception, $query),
46 2266,
47 2291,
48 2292 => new ForeignKeyConstraintViolationException($exception, $query),
49 default => new DriverException($exception, $query),
50 };
51 }
52}
diff --git a/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php
new file mode 100644
index 0000000..54e4966
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php
@@ -0,0 +1,82 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\PostgreSQL;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
11use Doctrine\DBAL\Exception\DeadlockException;
12use Doctrine\DBAL\Exception\DriverException;
13use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
14use Doctrine\DBAL\Exception\InvalidFieldNameException;
15use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
16use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
17use Doctrine\DBAL\Exception\SchemaDoesNotExist;
18use Doctrine\DBAL\Exception\SyntaxErrorException;
19use Doctrine\DBAL\Exception\TableExistsException;
20use Doctrine\DBAL\Exception\TableNotFoundException;
21use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
22use Doctrine\DBAL\Query;
23
24use function str_contains;
25
26/** @internal */
27final class ExceptionConverter implements ExceptionConverterInterface
28{
29 /** @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html */
30 public function convert(Exception $exception, ?Query $query): DriverException
31 {
32 switch ($exception->getSQLState()) {
33 case '40001':
34 case '40P01':
35 return new DeadlockException($exception, $query);
36
37 case '0A000':
38 // Foreign key constraint violations during a TRUNCATE operation
39 // are considered "feature not supported" in PostgreSQL.
40 if (str_contains($exception->getMessage(), 'truncate')) {
41 return new ForeignKeyConstraintViolationException($exception, $query);
42 }
43
44 break;
45
46 case '23502':
47 return new NotNullConstraintViolationException($exception, $query);
48
49 case '23503':
50 return new ForeignKeyConstraintViolationException($exception, $query);
51
52 case '23505':
53 return new UniqueConstraintViolationException($exception, $query);
54
55 case '3D000':
56 return new DatabaseDoesNotExist($exception, $query);
57
58 case '3F000':
59 return new SchemaDoesNotExist($exception, $query);
60
61 case '42601':
62 return new SyntaxErrorException($exception, $query);
63
64 case '42702':
65 return new NonUniqueFieldNameException($exception, $query);
66
67 case '42703':
68 return new InvalidFieldNameException($exception, $query);
69
70 case '42P01':
71 return new TableNotFoundException($exception, $query);
72
73 case '42P07':
74 return new TableExistsException($exception, $query);
75
76 case '08006':
77 return new ConnectionException($exception, $query);
78 }
79
80 return new DriverException($exception, $query);
81 }
82}
diff --git a/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php
new file mode 100644
index 0000000..561e58b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/SQLSrv/ExceptionConverter.php
@@ -0,0 +1,49 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\SQLSrv;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
11use Doctrine\DBAL\Exception\DriverException;
12use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
13use Doctrine\DBAL\Exception\InvalidFieldNameException;
14use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
15use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
16use Doctrine\DBAL\Exception\SyntaxErrorException;
17use Doctrine\DBAL\Exception\TableExistsException;
18use Doctrine\DBAL\Exception\TableNotFoundException;
19use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
20use Doctrine\DBAL\Query;
21
22/**
23 * @internal
24 *
25 * @link https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors
26 */
27final class ExceptionConverter implements ExceptionConverterInterface
28{
29 public function convert(Exception $exception, ?Query $query): DriverException
30 {
31 return match ($exception->getCode()) {
32 102 => new SyntaxErrorException($exception, $query),
33 207 => new InvalidFieldNameException($exception, $query),
34 208 => new TableNotFoundException($exception, $query),
35 209 => new NonUniqueFieldNameException($exception, $query),
36 515 => new NotNullConstraintViolationException($exception, $query),
37 547,
38 4712 => new ForeignKeyConstraintViolationException($exception, $query),
39 2601,
40 2627 => new UniqueConstraintViolationException($exception, $query),
41 2714 => new TableExistsException($exception, $query),
42 3701,
43 15151 => new DatabaseObjectNotFoundException($exception, $query),
44 11001,
45 18456 => new ConnectionException($exception, $query),
46 default => new DriverException($exception, $query),
47 };
48 }
49}
diff --git a/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php b/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php
new file mode 100644
index 0000000..5885195
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/API/SQLite/ExceptionConverter.php
@@ -0,0 +1,85 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\API\SQLite;
6
7use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Exception\ConnectionException;
10use Doctrine\DBAL\Exception\DriverException;
11use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
12use Doctrine\DBAL\Exception\InvalidFieldNameException;
13use Doctrine\DBAL\Exception\LockWaitTimeoutException;
14use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
15use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
16use Doctrine\DBAL\Exception\ReadOnlyException;
17use Doctrine\DBAL\Exception\SyntaxErrorException;
18use Doctrine\DBAL\Exception\TableExistsException;
19use Doctrine\DBAL\Exception\TableNotFoundException;
20use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
21use Doctrine\DBAL\Query;
22
23use function str_contains;
24
25/** @internal */
26final class ExceptionConverter implements ExceptionConverterInterface
27{
28 /** @link http://www.sqlite.org/c3ref/c_abort.html */
29 public function convert(Exception $exception, ?Query $query): DriverException
30 {
31 if (str_contains($exception->getMessage(), 'database is locked')) {
32 return new LockWaitTimeoutException($exception, $query);
33 }
34
35 if (
36 str_contains($exception->getMessage(), 'must be unique') ||
37 str_contains($exception->getMessage(), 'is not unique') ||
38 str_contains($exception->getMessage(), 'are not unique') ||
39 str_contains($exception->getMessage(), 'UNIQUE constraint failed')
40 ) {
41 return new UniqueConstraintViolationException($exception, $query);
42 }
43
44 if (
45 str_contains($exception->getMessage(), 'may not be NULL') ||
46 str_contains($exception->getMessage(), 'NOT NULL constraint failed')
47 ) {
48 return new NotNullConstraintViolationException($exception, $query);
49 }
50
51 if (str_contains($exception->getMessage(), 'no such table:')) {
52 return new TableNotFoundException($exception, $query);
53 }
54
55 if (str_contains($exception->getMessage(), 'already exists')) {
56 return new TableExistsException($exception, $query);
57 }
58
59 if (str_contains($exception->getMessage(), 'has no column named')) {
60 return new InvalidFieldNameException($exception, $query);
61 }
62
63 if (str_contains($exception->getMessage(), 'ambiguous column name')) {
64 return new NonUniqueFieldNameException($exception, $query);
65 }
66
67 if (str_contains($exception->getMessage(), 'syntax error')) {
68 return new SyntaxErrorException($exception, $query);
69 }
70
71 if (str_contains($exception->getMessage(), 'attempt to write a readonly database')) {
72 return new ReadOnlyException($exception, $query);
73 }
74
75 if (str_contains($exception->getMessage(), 'unable to open database file')) {
76 return new ConnectionException($exception, $query);
77 }
78
79 if (str_contains($exception->getMessage(), 'FOREIGN KEY constraint failed')) {
80 return new ForeignKeyConstraintViolationException($exception, $query);
81 }
82
83 return new DriverException($exception, $query);
84 }
85}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php b/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php
new file mode 100644
index 0000000..9955a38
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractDB2Driver.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
9use Doctrine\DBAL\Driver\API\IBMDB2\ExceptionConverter;
10use Doctrine\DBAL\Platforms\DB2Platform;
11use Doctrine\DBAL\ServerVersionProvider;
12
13/**
14 * Abstract base implementation of the {@see Driver} interface for IBM DB2 based drivers.
15 */
16abstract class AbstractDB2Driver implements Driver
17{
18 public function getDatabasePlatform(ServerVersionProvider $versionProvider): DB2Platform
19 {
20 return new DB2Platform();
21 }
22
23 public function getExceptionConverter(): ExceptionConverterInterface
24 {
25 return new ExceptionConverter();
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractException.php b/vendor/doctrine/dbal/src/Driver/AbstractException.php
new file mode 100644
index 0000000..367fc49
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractException.php
@@ -0,0 +1,36 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Exception as BaseException;
8use Throwable;
9
10/**
11 * Abstract base implementation of the {@see DriverException} interface.
12 *
13 * @psalm-immutable
14 */
15abstract class AbstractException extends BaseException implements Exception
16{
17 /**
18 * @param string $message The driver error message.
19 * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any.
20 * @param int $code The driver specific error code if any.
21 * @param Throwable|null $previous The previous throwable used for the exception chaining.
22 */
23 public function __construct(
24 string $message,
25 private readonly ?string $sqlState = null,
26 int $code = 0,
27 ?Throwable $previous = null,
28 ) {
29 parent::__construct($message, $code, $previous);
30 }
31
32 public function getSQLState(): ?string
33 {
34 return $this->sqlState;
35 }
36}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php
new file mode 100644
index 0000000..40d6507
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractMySQLDriver.php
@@ -0,0 +1,86 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
9use Doctrine\DBAL\Driver\API\MySQL\ExceptionConverter;
10use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
11use Doctrine\DBAL\Platforms\Exception\InvalidPlatformVersion;
12use Doctrine\DBAL\Platforms\MariaDB1052Platform;
13use Doctrine\DBAL\Platforms\MariaDB1060Platform;
14use Doctrine\DBAL\Platforms\MariaDBPlatform;
15use Doctrine\DBAL\Platforms\MySQL80Platform;
16use Doctrine\DBAL\Platforms\MySQLPlatform;
17use Doctrine\DBAL\ServerVersionProvider;
18
19use function preg_match;
20use function stripos;
21use function version_compare;
22
23/**
24 * Abstract base implementation of the {@see Driver} interface for MySQL based drivers.
25 */
26abstract class AbstractMySQLDriver implements Driver
27{
28 /**
29 * {@inheritDoc}
30 *
31 * @throws InvalidPlatformVersion
32 */
33 public function getDatabasePlatform(ServerVersionProvider $versionProvider): AbstractMySQLPlatform
34 {
35 $version = $versionProvider->getServerVersion();
36 if (stripos($version, 'mariadb') !== false) {
37 $mariaDbVersion = $this->getMariaDbMysqlVersionNumber($version);
38 if (version_compare($mariaDbVersion, '10.6.0', '>=')) {
39 return new MariaDB1060Platform();
40 }
41
42 if (version_compare($mariaDbVersion, '10.5.2', '>=')) {
43 return new MariaDB1052Platform();
44 }
45
46 return new MariaDBPlatform();
47 }
48
49 if (version_compare($version, '8.0.0', '>=')) {
50 return new MySQL80Platform();
51 }
52
53 return new MySQLPlatform();
54 }
55
56 public function getExceptionConverter(): ExceptionConverterInterface
57 {
58 return new ExceptionConverter();
59 }
60
61 /**
62 * Detect MariaDB server version, including hack for some mariadb distributions
63 * that starts with the prefix '5.5.5-'
64 *
65 * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial'
66 *
67 * @throws InvalidPlatformVersion
68 */
69 private function getMariaDbMysqlVersionNumber(string $versionString): string
70 {
71 if (
72 preg_match(
73 '/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
74 $versionString,
75 $versionParts,
76 ) === 0
77 ) {
78 throw InvalidPlatformVersion::new(
79 $versionString,
80 '^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>',
81 );
82 }
83
84 return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
85 }
86}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php
new file mode 100644
index 0000000..cf56cfa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver.php
@@ -0,0 +1,38 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString;
9use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
10use Doctrine\DBAL\Driver\API\OCI\ExceptionConverter;
11use Doctrine\DBAL\Platforms\OraclePlatform;
12use Doctrine\DBAL\ServerVersionProvider;
13
14/**
15 * Abstract base implementation of the {@see Driver} interface for Oracle based drivers.
16 */
17abstract class AbstractOracleDriver implements Driver
18{
19 public function getDatabasePlatform(ServerVersionProvider $versionProvider): OraclePlatform
20 {
21 return new OraclePlatform();
22 }
23
24 public function getExceptionConverter(): ExceptionConverterInterface
25 {
26 return new ExceptionConverter();
27 }
28
29 /**
30 * Returns an appropriate Easy Connect String for the given parameters.
31 *
32 * @param array<string, mixed> $params The connection parameters to return the Easy Connect String for.
33 */
34 protected function getEasyConnectString(array $params): string
35 {
36 return (string) EasyConnectString::fromConnectionParameters($params);
37 }
38}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php
new file mode 100644
index 0000000..a777817
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php
@@ -0,0 +1,112 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\AbstractOracleDriver;
6
7use function implode;
8use function is_array;
9use function sprintf;
10
11/**
12 * Represents an Oracle Easy Connect string
13 *
14 * @link https://docs.oracle.com/database/121/NETAG/naming.htm
15 */
16final class EasyConnectString
17{
18 private function __construct(private readonly string $string)
19 {
20 }
21
22 public function __toString(): string
23 {
24 return $this->string;
25 }
26
27 /**
28 * Creates the object from an array representation
29 *
30 * @param mixed[] $params
31 */
32 public static function fromArray(array $params): self
33 {
34 return new self(self::renderParams($params));
35 }
36
37 /**
38 * Creates the object from the given DBAL connection parameters.
39 *
40 * @param mixed[] $params
41 */
42 public static function fromConnectionParameters(array $params): self
43 {
44 if (isset($params['connectstring'])) {
45 return new self($params['connectstring']);
46 }
47
48 if (! isset($params['host'])) {
49 return new self($params['dbname'] ?? '');
50 }
51
52 $connectData = [];
53
54 if (isset($params['servicename']) || isset($params['dbname'])) {
55 $serviceKey = 'SID';
56
57 if (isset($params['service'])) {
58 $serviceKey = 'SERVICE_NAME';
59 }
60
61 $serviceName = $params['servicename'] ?? $params['dbname'];
62
63 $connectData[$serviceKey] = $serviceName;
64 }
65
66 if (isset($params['instancename'])) {
67 $connectData['INSTANCE_NAME'] = $params['instancename'];
68 }
69
70 if (! empty($params['pooled'])) {
71 $connectData['SERVER'] = 'POOLED';
72 }
73
74 return self::fromArray([
75 'DESCRIPTION' => [
76 'ADDRESS' => [
77 'PROTOCOL' => 'TCP',
78 'HOST' => $params['host'],
79 'PORT' => $params['port'] ?? 1521,
80 ],
81 'CONNECT_DATA' => $connectData,
82 ],
83 ]);
84 }
85
86 /** @param mixed[] $params */
87 private static function renderParams(array $params): string
88 {
89 $chunks = [];
90
91 foreach ($params as $key => $value) {
92 $string = self::renderValue($value);
93
94 if ($string === '') {
95 continue;
96 }
97
98 $chunks[] = sprintf('(%s=%s)', $key, $string);
99 }
100
101 return implode('', $chunks);
102 }
103
104 private static function renderValue(mixed $value): string
105 {
106 if (is_array($value)) {
107 return self::renderParams($value);
108 }
109
110 return (string) $value;
111 }
112}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php
new file mode 100644
index 0000000..2efcddc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractPostgreSQLDriver.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
9use Doctrine\DBAL\Driver\API\PostgreSQL\ExceptionConverter;
10use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
11use Doctrine\DBAL\ServerVersionProvider;
12
13/**
14 * Abstract base implementation of the {@see Driver} interface for PostgreSQL based drivers.
15 */
16abstract class AbstractPostgreSQLDriver implements Driver
17{
18 public function getDatabasePlatform(ServerVersionProvider $versionProvider): PostgreSQLPlatform
19 {
20 return new PostgreSQLPlatform();
21 }
22
23 public function getExceptionConverter(): ExceptionConverterInterface
24 {
25 return new ExceptionConverter();
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php
new file mode 100644
index 0000000..8c2d012
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
9use Doctrine\DBAL\Driver\API\SQLSrv\ExceptionConverter;
10use Doctrine\DBAL\Platforms\SQLServerPlatform;
11use Doctrine\DBAL\ServerVersionProvider;
12
13/**
14 * Abstract base implementation of the {@see Driver} interface for Microsoft SQL Server based drivers.
15 */
16abstract class AbstractSQLServerDriver implements Driver
17{
18 public function getDatabasePlatform(ServerVersionProvider $versionProvider): SQLServerPlatform
19 {
20 return new SQLServerPlatform();
21 }
22
23 public function getExceptionConverter(): ExceptionConverterInterface
24 {
25 return new ExceptionConverter();
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php
new file mode 100644
index 0000000..ea8dcc4
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class PortWithoutHost extends AbstractException
15{
16 public static function new(): self
17 {
18 return new self('Connection port specified without the host');
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver.php b/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver.php
new file mode 100644
index 0000000..74a2b12
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
9use Doctrine\DBAL\Driver\API\SQLite\ExceptionConverter;
10use Doctrine\DBAL\Platforms\SQLitePlatform;
11use Doctrine\DBAL\ServerVersionProvider;
12
13/**
14 * Abstract base implementation of the {@see Driver} interface for SQLite based drivers.
15 */
16abstract class AbstractSQLiteDriver implements Driver
17{
18 public function getDatabasePlatform(ServerVersionProvider $versionProvider): SQLitePlatform
19 {
20 return new SQLitePlatform();
21 }
22
23 public function getExceptionConverter(): ExceptionConverterInterface
24 {
25 return new ExceptionConverter();
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php b/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php
new file mode 100644
index 0000000..a7375cc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php
@@ -0,0 +1,33 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\Connection;
9use Doctrine\DBAL\Driver\Middleware;
10use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
11use SensitiveParameter;
12
13final class EnableForeignKeys implements Middleware
14{
15 public function wrap(Driver $driver): Driver
16 {
17 return new class ($driver) extends AbstractDriverMiddleware {
18 /**
19 * {@inheritDoc}
20 */
21 public function connect(
22 #[SensitiveParameter]
23 array $params,
24 ): Connection {
25 $connection = parent::connect($params);
26
27 $connection->exec('PRAGMA foreign_keys=ON');
28
29 return $connection;
30 }
31 };
32 }
33}
diff --git a/vendor/doctrine/dbal/src/Driver/Connection.php b/vendor/doctrine/dbal/src/Driver/Connection.php
new file mode 100644
index 0000000..68852e9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Connection.php
@@ -0,0 +1,93 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\ServerVersionProvider;
8
9/**
10 * Connection interface.
11 * Driver connections must implement this interface.
12 */
13interface Connection extends ServerVersionProvider
14{
15 /**
16 * Prepares a statement for execution and returns a Statement object.
17 *
18 * @throws Exception
19 */
20 public function prepare(string $sql): Statement;
21
22 /**
23 * Executes an SQL statement, returning a result set as a Statement object.
24 *
25 * @throws Exception
26 */
27 public function query(string $sql): Result;
28
29 /**
30 * Quotes a string for use in a query.
31 *
32 * The usage of this method is discouraged. Use prepared statements
33 * or {@see AbstractPlatform::quoteStringLiteral()} instead.
34 */
35 public function quote(string $value): string;
36
37 /**
38 * Executes an SQL statement and return the number of affected rows.
39 * If the number of affected rows is greater than the maximum int value (PHP_INT_MAX),
40 * the number of affected rows may be returned as a string.
41 *
42 * @return int|numeric-string
43 *
44 * @throws Exception
45 */
46 public function exec(string $sql): int|string;
47
48 /**
49 * Returns the ID of the last inserted row.
50 *
51 * This method returns an integer or a string representing the value of the auto-increment column
52 * from the last row inserted into the database, if any, or throws an exception if a value cannot be returned,
53 * in particular when:
54 *
55 * - the driver does not support identity columns;
56 * - the last statement dit not return an identity (caution: see note below).
57 *
58 * Note: if the last statement was not an INSERT to an autoincrement column, this method MAY return an ID from a
59 * previous statement. DO NOT RELY ON THIS BEHAVIOR which is driver-dependent: always call this method right after
60 * executing an INSERT statement.
61 *
62 * @throws Exception
63 */
64 public function lastInsertId(): int|string;
65
66 /**
67 * Initiates a transaction.
68 *
69 * @throws Exception
70 */
71 public function beginTransaction(): void;
72
73 /**
74 * Commits a transaction.
75 *
76 * @throws Exception
77 */
78 public function commit(): void;
79
80 /**
81 * Rolls back the current transaction, as initiated by beginTransaction().
82 *
83 * @throws Exception
84 */
85 public function rollBack(): void;
86
87 /**
88 * Provides access to the native database connection.
89 *
90 * @return resource|object
91 */
92 public function getNativeConnection();
93}
diff --git a/vendor/doctrine/dbal/src/Driver/Exception.php b/vendor/doctrine/dbal/src/Driver/Exception.php
new file mode 100644
index 0000000..34d9a51
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Exception.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Throwable;
8
9/**
10 * Contract for a driver exception.
11 *
12 * Driver exceptions provide the SQLSTATE of the driver
13 * and the driver specific error code at the time the error occurred.
14 *
15 * @psalm-immutable
16 */
17interface Exception extends Throwable
18{
19 /**
20 * Returns the SQLSTATE the driver was in at the time the error occurred.
21 *
22 * Returns null if the driver does not provide a SQLSTATE for the error occurred.
23 */
24 public function getSQLState(): ?string;
25}
diff --git a/vendor/doctrine/dbal/src/Driver/Exception/IdentityColumnsNotSupported.php b/vendor/doctrine/dbal/src/Driver/Exception/IdentityColumnsNotSupported.php
new file mode 100644
index 0000000..c904954
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Exception/IdentityColumnsNotSupported.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use Throwable;
9
10/**
11 * @internal
12 *
13 * @psalm-immutable
14 */
15final class IdentityColumnsNotSupported extends AbstractException
16{
17 public static function new(?Throwable $previous = null): self
18 {
19 return new self('The driver does not support identity columns.', null, 0, $previous);
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Driver/Exception/NoIdentityValue.php b/vendor/doctrine/dbal/src/Driver/Exception/NoIdentityValue.php
new file mode 100644
index 0000000..decbbd5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Exception/NoIdentityValue.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use Throwable;
9
10/**
11 * @internal
12 *
13 * @psalm-immutable
14 */
15final class NoIdentityValue extends AbstractException
16{
17 public static function new(?Throwable $previous = null): self
18 {
19 return new self('No identity value was generated by the last statement.', null, 0, $previous);
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Driver/FetchUtils.php b/vendor/doctrine/dbal/src/Driver/FetchUtils.php
new file mode 100644
index 0000000..eda5211
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/FetchUtils.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7/** @internal */
8final class FetchUtils
9{
10 /** @throws Exception */
11 public static function fetchOne(Result $result): mixed
12 {
13 $row = $result->fetchNumeric();
14
15 if ($row === false) {
16 return false;
17 }
18
19 return $row[0];
20 }
21
22 /**
23 * @return list<list<mixed>>
24 *
25 * @throws Exception
26 */
27 public static function fetchAllNumeric(Result $result): array
28 {
29 $rows = [];
30
31 while (($row = $result->fetchNumeric()) !== false) {
32 $rows[] = $row;
33 }
34
35 return $rows;
36 }
37
38 /**
39 * @return list<array<string,mixed>>
40 *
41 * @throws Exception
42 */
43 public static function fetchAllAssociative(Result $result): array
44 {
45 $rows = [];
46
47 while (($row = $result->fetchAssociative()) !== false) {
48 $rows[] = $row;
49 }
50
51 return $rows;
52 }
53
54 /**
55 * @return list<mixed>
56 *
57 * @throws Exception
58 */
59 public static function fetchFirstColumn(Result $result): array
60 {
61 $rows = [];
62
63 while (($row = $result->fetchOne()) !== false) {
64 $rows[] = $row;
65 }
66
67 return $rows;
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php
new file mode 100644
index 0000000..2c8783b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Connection.php
@@ -0,0 +1,131 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
9use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError;
10use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed;
11use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
12use stdClass;
13
14use function assert;
15use function db2_autocommit;
16use function db2_commit;
17use function db2_escape_string;
18use function db2_exec;
19use function db2_last_insert_id;
20use function db2_num_rows;
21use function db2_prepare;
22use function db2_rollback;
23use function db2_server_info;
24use function error_get_last;
25
26use const DB2_AUTOCOMMIT_OFF;
27use const DB2_AUTOCOMMIT_ON;
28
29final class Connection implements ConnectionInterface
30{
31 /**
32 * @internal The connection can be only instantiated by its driver.
33 *
34 * @param resource $connection
35 */
36 public function __construct(private readonly mixed $connection)
37 {
38 }
39
40 public function getServerVersion(): string
41 {
42 $serverInfo = db2_server_info($this->connection);
43 assert($serverInfo instanceof stdClass);
44
45 return $serverInfo->DBMS_VER;
46 }
47
48 public function prepare(string $sql): Statement
49 {
50 $stmt = @db2_prepare($this->connection, $sql);
51
52 if ($stmt === false) {
53 throw PrepareFailed::new(error_get_last());
54 }
55
56 return new Statement($stmt);
57 }
58
59 public function query(string $sql): Result
60 {
61 return $this->prepare($sql)->execute();
62 }
63
64 public function quote(string $value): string
65 {
66 return "'" . db2_escape_string($value) . "'";
67 }
68
69 public function exec(string $sql): int|string
70 {
71 $stmt = @db2_exec($this->connection, $sql);
72
73 if ($stmt === false) {
74 throw StatementError::new();
75 }
76
77 $numRows = db2_num_rows($stmt);
78
79 if ($numRows === false) {
80 throw StatementError::new();
81 }
82
83 return $numRows;
84 }
85
86 public function lastInsertId(): string
87 {
88 $lastInsertId = db2_last_insert_id($this->connection);
89
90 if ($lastInsertId === null) {
91 throw NoIdentityValue::new();
92 }
93
94 return $lastInsertId;
95 }
96
97 public function beginTransaction(): void
98 {
99 if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF) !== true) {
100 throw ConnectionError::new($this->connection);
101 }
102 }
103
104 public function commit(): void
105 {
106 if (! db2_commit($this->connection)) {
107 throw ConnectionError::new($this->connection);
108 }
109
110 if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON) !== true) {
111 throw ConnectionError::new($this->connection);
112 }
113 }
114
115 public function rollBack(): void
116 {
117 if (! db2_rollback($this->connection)) {
118 throw ConnectionError::new($this->connection);
119 }
120
121 if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON) !== true) {
122 throw ConnectionError::new($this->connection);
123 }
124 }
125
126 /** @return resource */
127 public function getNativeConnection()
128 {
129 return $this->connection;
130 }
131}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php
new file mode 100644
index 0000000..a1e5948
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/DataSourceName.php
@@ -0,0 +1,80 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2;
6
7use SensitiveParameter;
8
9use function implode;
10use function sprintf;
11use function str_contains;
12
13/**
14 * IBM DB2 DSN
15 */
16final class DataSourceName
17{
18 private function __construct(
19 #[SensitiveParameter]
20 private readonly string $string,
21 ) {
22 }
23
24 public function toString(): string
25 {
26 return $this->string;
27 }
28
29 /**
30 * Creates the object from an array representation
31 *
32 * @param array<string,mixed> $params
33 */
34 public static function fromArray(
35 #[SensitiveParameter]
36 array $params,
37 ): self {
38 $chunks = [];
39
40 foreach ($params as $key => $value) {
41 $chunks[] = sprintf('%s=%s', $key, $value);
42 }
43
44 return new self(implode(';', $chunks));
45 }
46
47 /**
48 * Creates the object from the given DBAL connection parameters.
49 *
50 * @param array<string,mixed> $params
51 */
52 public static function fromConnectionParameters(#[SensitiveParameter]
53 array $params,): self
54 {
55 if (isset($params['dbname']) && str_contains($params['dbname'], '=')) {
56 return new self($params['dbname']);
57 }
58
59 $dsnParams = [];
60
61 foreach (
62 [
63 'host' => 'HOSTNAME',
64 'port' => 'PORT',
65 'protocol' => 'PROTOCOL',
66 'dbname' => 'DATABASE',
67 'user' => 'UID',
68 'password' => 'PWD',
69 ] as $dbalParam => $dsnParam
70 ) {
71 if (! isset($params[$dbalParam])) {
72 continue;
73 }
74
75 $dsnParams[$dsnParam] = $params[$dbalParam];
76 }
77
78 return self::fromArray($dsnParams);
79 }
80}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php
new file mode 100644
index 0000000..f2f4ed7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Driver.php
@@ -0,0 +1,41 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2;
6
7use Doctrine\DBAL\Driver\AbstractDB2Driver;
8use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed;
9use SensitiveParameter;
10
11use function db2_connect;
12use function db2_pconnect;
13
14final class Driver extends AbstractDB2Driver
15{
16 /**
17 * {@inheritDoc}
18 */
19 public function connect(
20 #[SensitiveParameter]
21 array $params,
22 ): Connection {
23 $dataSourceName = DataSourceName::fromConnectionParameters($params)->toString();
24
25 $username = $params['user'] ?? '';
26 $password = $params['password'] ?? '';
27 $driverOptions = $params['driverOptions'] ?? [];
28
29 if (! empty($params['persistent'])) {
30 $connection = db2_pconnect($dataSourceName, $username, $password, $driverOptions);
31 } else {
32 $connection = db2_connect($dataSourceName, $username, $password, $driverOptions);
33 }
34
35 if ($connection === false) {
36 throw ConnectionFailed::new();
37 }
38
39 return new Connection($connection);
40 }
41}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php
new file mode 100644
index 0000000..c584fb8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class CannotCopyStreamToStream extends AbstractException
15{
16 /** @psalm-param array{message: string, ...}|null $error */
17 public static function new(?array $error): self
18 {
19 $message = 'Could not copy source stream to temporary file';
20
21 if ($error !== null) {
22 $message .= ': ' . $error['message'];
23 }
24
25 return new self($message);
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php
new file mode 100644
index 0000000..d7646a0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class CannotCreateTemporaryFile extends AbstractException
15{
16 /** @psalm-param array{message: string, ...}|null $error */
17 public static function new(?array $error): self
18 {
19 $message = 'Could not create temporary file';
20
21 if ($error !== null) {
22 $message .= ': ' . $error['message'];
23 }
24
25 return new self($message);
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionError.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionError.php
new file mode 100644
index 0000000..b7bd8be
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionError.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function db2_conn_error;
10use function db2_conn_errormsg;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class ConnectionError extends AbstractException
18{
19 /** @param resource $connection */
20 public static function new($connection): self
21 {
22 $message = db2_conn_errormsg($connection);
23 $sqlState = db2_conn_error($connection);
24
25 return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
26 return new self($message, $sqlState, $code);
27 });
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionFailed.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionFailed.php
new file mode 100644
index 0000000..9dd0443
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/ConnectionFailed.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function db2_conn_error;
10use function db2_conn_errormsg;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class ConnectionFailed extends AbstractException
18{
19 public static function new(): self
20 {
21 $message = db2_conn_errormsg();
22 $sqlState = db2_conn_error();
23
24 return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
25 return new self($message, $sqlState, $code);
26 });
27 }
28}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/Factory.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/Factory.php
new file mode 100644
index 0000000..91b9b43
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/Factory.php
@@ -0,0 +1,35 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function preg_match;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class Factory
17{
18 /**
19 * @param callable(int): T $constructor
20 *
21 * @return T
22 *
23 * @template T of AbstractException
24 */
25 public static function create(string $message, callable $constructor): AbstractException
26 {
27 $code = 0;
28
29 if (preg_match('/ SQL(\d+)N /', $message, $matches) === 1) {
30 $code = -(int) $matches[1];
31 }
32
33 return $constructor($code);
34 }
35}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/PrepareFailed.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/PrepareFailed.php
new file mode 100644
index 0000000..5344b65
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/PrepareFailed.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class PrepareFailed extends AbstractException
15{
16 /** @psalm-param array{message: string, ...}|null $error */
17 public static function new(?array $error): self
18 {
19 if ($error === null) {
20 return new self('Unknown error');
21 }
22
23 return new self($error['message']);
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/StatementError.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/StatementError.php
new file mode 100644
index 0000000..6bf8511
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Exception/StatementError.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function db2_stmt_error;
10use function db2_stmt_errormsg;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class StatementError extends AbstractException
18{
19 /** @param resource|null $statement */
20 public static function new($statement = null): self
21 {
22 if ($statement !== null) {
23 $message = db2_stmt_errormsg($statement);
24 $sqlState = db2_stmt_error($statement);
25 } else {
26 $message = db2_stmt_errormsg();
27 $sqlState = db2_stmt_error();
28 }
29
30 return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
31 return new self($message, $sqlState, $code);
32 });
33 }
34}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Result.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Result.php
new file mode 100644
index 0000000..461f44a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Result.php
@@ -0,0 +1,106 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
9use Doctrine\DBAL\Driver\Result as ResultInterface;
10
11use function db2_fetch_array;
12use function db2_fetch_assoc;
13use function db2_free_result;
14use function db2_num_fields;
15use function db2_num_rows;
16use function db2_stmt_error;
17
18final class Result implements ResultInterface
19{
20 /**
21 * @internal The result can be only instantiated by its driver connection or statement.
22 *
23 * @param resource $statement
24 */
25 public function __construct(private readonly mixed $statement)
26 {
27 }
28
29 public function fetchNumeric(): array|false
30 {
31 $row = @db2_fetch_array($this->statement);
32
33 if ($row === false && db2_stmt_error($this->statement) !== '02000') {
34 throw StatementError::new($this->statement);
35 }
36
37 return $row;
38 }
39
40 public function fetchAssociative(): array|false
41 {
42 $row = @db2_fetch_assoc($this->statement);
43
44 if ($row === false && db2_stmt_error($this->statement) !== '02000') {
45 throw StatementError::new($this->statement);
46 }
47
48 return $row;
49 }
50
51 public function fetchOne(): mixed
52 {
53 return FetchUtils::fetchOne($this);
54 }
55
56 /**
57 * {@inheritDoc}
58 */
59 public function fetchAllNumeric(): array
60 {
61 return FetchUtils::fetchAllNumeric($this);
62 }
63
64 /**
65 * {@inheritDoc}
66 */
67 public function fetchAllAssociative(): array
68 {
69 return FetchUtils::fetchAllAssociative($this);
70 }
71
72 /**
73 * {@inheritDoc}
74 */
75 public function fetchFirstColumn(): array
76 {
77 return FetchUtils::fetchFirstColumn($this);
78 }
79
80 public function rowCount(): int
81 {
82 $numRows = @db2_num_rows($this->statement);
83
84 if ($numRows === false) {
85 throw StatementError::new($this->statement);
86 }
87
88 return $numRows;
89 }
90
91 public function columnCount(): int
92 {
93 $count = db2_num_fields($this->statement);
94
95 if ($count !== false) {
96 return $count;
97 }
98
99 return 0;
100 }
101
102 public function free(): void
103 {
104 db2_free_result($this->statement);
105 }
106}
diff --git a/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php b/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php
new file mode 100644
index 0000000..96852da
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/IBMDB2/Statement.php
@@ -0,0 +1,156 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\IBMDB2;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream;
9use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile;
10use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
11use Doctrine\DBAL\Driver\Statement as StatementInterface;
12use Doctrine\DBAL\ParameterType;
13
14use function assert;
15use function db2_bind_param;
16use function db2_execute;
17use function error_get_last;
18use function fclose;
19use function is_int;
20use function is_resource;
21use function stream_copy_to_stream;
22use function stream_get_meta_data;
23use function tmpfile;
24
25use const DB2_BINARY;
26use const DB2_CHAR;
27use const DB2_LONG;
28use const DB2_PARAM_FILE;
29use const DB2_PARAM_IN;
30
31final class Statement implements StatementInterface
32{
33 /** @var mixed[] */
34 private array $parameters = [];
35
36 /**
37 * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
38 * and the temporary file handle bound to the underlying statement
39 *
40 * @var array<int,string|resource|null>
41 */
42 private array $lobs = [];
43
44 /**
45 * @internal The statement can be only instantiated by its driver connection.
46 *
47 * @param resource $stmt
48 */
49 public function __construct(private readonly mixed $stmt)
50 {
51 }
52
53 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
54 {
55 assert(is_int($param));
56
57 switch ($type) {
58 case ParameterType::INTEGER:
59 $this->bind($param, $value, DB2_PARAM_IN, DB2_LONG);
60 break;
61
62 case ParameterType::LARGE_OBJECT:
63 $this->lobs[$param] = &$value;
64 break;
65
66 default:
67 $this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR);
68 break;
69 }
70 }
71
72 /** @throws Exception */
73 private function bind(int $position, mixed &$variable, int $parameterType, int $dataType): void
74 {
75 $this->parameters[$position] =& $variable;
76
77 if (! db2_bind_param($this->stmt, $position, '', $parameterType, $dataType)) {
78 throw StatementError::new($this->stmt);
79 }
80 }
81
82 public function execute(): Result
83 {
84 $handles = $this->bindLobs();
85
86 $result = @db2_execute($this->stmt, $this->parameters);
87
88 foreach ($handles as $handle) {
89 fclose($handle);
90 }
91
92 $this->lobs = [];
93
94 if ($result === false) {
95 throw StatementError::new($this->stmt);
96 }
97
98 return new Result($this->stmt);
99 }
100
101 /**
102 * @return list<resource>
103 *
104 * @throws Exception
105 */
106 private function bindLobs(): array
107 {
108 $handles = [];
109
110 foreach ($this->lobs as $param => $value) {
111 if (is_resource($value)) {
112 $handle = $handles[] = $this->createTemporaryFile();
113 $path = stream_get_meta_data($handle)['uri'];
114
115 $this->copyStreamToStream($value, $handle);
116
117 $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY);
118 } else {
119 $this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR);
120 }
121
122 unset($value);
123 }
124
125 return $handles;
126 }
127
128 /**
129 * @return resource
130 *
131 * @throws Exception
132 */
133 private function createTemporaryFile()
134 {
135 $handle = @tmpfile();
136
137 if ($handle === false) {
138 throw CannotCreateTemporaryFile::new(error_get_last());
139 }
140
141 return $handle;
142 }
143
144 /**
145 * @param resource $source
146 * @param resource $target
147 *
148 * @throws Exception
149 */
150 private function copyStreamToStream($source, $target): void
151 {
152 if (@stream_copy_to_stream($source, $target) === false) {
153 throw CannotCopyStreamToStream::new(error_get_last());
154 }
155 }
156}
diff --git a/vendor/doctrine/dbal/src/Driver/Middleware.php b/vendor/doctrine/dbal/src/Driver/Middleware.php
new file mode 100644
index 0000000..4629d9a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Middleware.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\Driver;
8
9interface Middleware
10{
11 public function wrap(Driver $driver): Driver;
12}
diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractConnectionMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractConnectionMiddleware.php
new file mode 100644
index 0000000..bdf9b2f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractConnectionMiddleware.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Middleware;
6
7use Doctrine\DBAL\Driver\Connection;
8use Doctrine\DBAL\Driver\Result;
9use Doctrine\DBAL\Driver\Statement;
10
11abstract class AbstractConnectionMiddleware implements Connection
12{
13 public function __construct(private readonly Connection $wrappedConnection)
14 {
15 }
16
17 public function prepare(string $sql): Statement
18 {
19 return $this->wrappedConnection->prepare($sql);
20 }
21
22 public function query(string $sql): Result
23 {
24 return $this->wrappedConnection->query($sql);
25 }
26
27 public function quote(string $value): string
28 {
29 return $this->wrappedConnection->quote($value);
30 }
31
32 public function exec(string $sql): int|string
33 {
34 return $this->wrappedConnection->exec($sql);
35 }
36
37 public function lastInsertId(): int|string
38 {
39 return $this->wrappedConnection->lastInsertId();
40 }
41
42 public function beginTransaction(): void
43 {
44 $this->wrappedConnection->beginTransaction();
45 }
46
47 public function commit(): void
48 {
49 $this->wrappedConnection->commit();
50 }
51
52 public function rollBack(): void
53 {
54 $this->wrappedConnection->rollBack();
55 }
56
57 public function getServerVersion(): string
58 {
59 return $this->wrappedConnection->getServerVersion();
60 }
61
62 /**
63 * {@inheritDoc}
64 */
65 public function getNativeConnection()
66 {
67 return $this->wrappedConnection->getNativeConnection();
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php
new file mode 100644
index 0000000..482f134
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractDriverMiddleware.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Middleware;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\API\ExceptionConverter;
9use Doctrine\DBAL\Driver\Connection as DriverConnection;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use Doctrine\DBAL\ServerVersionProvider;
12use SensitiveParameter;
13
14abstract class AbstractDriverMiddleware implements Driver
15{
16 public function __construct(private readonly Driver $wrappedDriver)
17 {
18 }
19
20 /**
21 * {@inheritDoc}
22 */
23 public function connect(
24 #[SensitiveParameter]
25 array $params,
26 ): DriverConnection {
27 return $this->wrappedDriver->connect($params);
28 }
29
30 public function getDatabasePlatform(ServerVersionProvider $versionProvider): AbstractPlatform
31 {
32 return $this->wrappedDriver->getDatabasePlatform($versionProvider);
33 }
34
35 public function getExceptionConverter(): ExceptionConverter
36 {
37 return $this->wrappedDriver->getExceptionConverter();
38 }
39}
diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php
new file mode 100644
index 0000000..7da2f99
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractResultMiddleware.php
@@ -0,0 +1,68 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Middleware;
6
7use Doctrine\DBAL\Driver\Result;
8
9abstract class AbstractResultMiddleware implements Result
10{
11 public function __construct(private readonly Result $wrappedResult)
12 {
13 }
14
15 public function fetchNumeric(): array|false
16 {
17 return $this->wrappedResult->fetchNumeric();
18 }
19
20 public function fetchAssociative(): array|false
21 {
22 return $this->wrappedResult->fetchAssociative();
23 }
24
25 public function fetchOne(): mixed
26 {
27 return $this->wrappedResult->fetchOne();
28 }
29
30 /**
31 * {@inheritDoc}
32 */
33 public function fetchAllNumeric(): array
34 {
35 return $this->wrappedResult->fetchAllNumeric();
36 }
37
38 /**
39 * {@inheritDoc}
40 */
41 public function fetchAllAssociative(): array
42 {
43 return $this->wrappedResult->fetchAllAssociative();
44 }
45
46 /**
47 * {@inheritDoc}
48 */
49 public function fetchFirstColumn(): array
50 {
51 return $this->wrappedResult->fetchFirstColumn();
52 }
53
54 public function rowCount(): int|string
55 {
56 return $this->wrappedResult->rowCount();
57 }
58
59 public function columnCount(): int
60 {
61 return $this->wrappedResult->columnCount();
62 }
63
64 public function free(): void
65 {
66 $this->wrappedResult->free();
67 }
68}
diff --git a/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php
new file mode 100644
index 0000000..6eaad50
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Middleware/AbstractStatementMiddleware.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Middleware;
6
7use Doctrine\DBAL\Driver\Result;
8use Doctrine\DBAL\Driver\Statement;
9use Doctrine\DBAL\ParameterType;
10
11abstract class AbstractStatementMiddleware implements Statement
12{
13 public function __construct(private readonly Statement $wrappedStatement)
14 {
15 }
16
17 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
18 {
19 $this->wrappedStatement->bindValue($param, $value, $type);
20 }
21
22 public function execute(): Result
23 {
24 return $this->wrappedStatement->execute();
25 }
26}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php
new file mode 100644
index 0000000..cc00fb6
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Connection.php
@@ -0,0 +1,112 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
10use mysqli;
11use mysqli_sql_exception;
12
13final class Connection implements ConnectionInterface
14{
15 /**
16 * Name of the option to set connection flags
17 */
18 public const OPTION_FLAGS = 'flags';
19
20 /** @internal The connection can be only instantiated by its driver. */
21 public function __construct(private readonly mysqli $connection)
22 {
23 }
24
25 public function getServerVersion(): string
26 {
27 return $this->connection->get_server_info();
28 }
29
30 public function prepare(string $sql): Statement
31 {
32 try {
33 $stmt = $this->connection->prepare($sql);
34 } catch (mysqli_sql_exception $e) {
35 throw ConnectionError::upcast($e);
36 }
37
38 if ($stmt === false) {
39 throw ConnectionError::new($this->connection);
40 }
41
42 return new Statement($stmt);
43 }
44
45 public function query(string $sql): Result
46 {
47 return $this->prepare($sql)->execute();
48 }
49
50 public function quote(string $value): string
51 {
52 return "'" . $this->connection->escape_string($value) . "'";
53 }
54
55 public function exec(string $sql): int|string
56 {
57 try {
58 $result = $this->connection->query($sql);
59 } catch (mysqli_sql_exception $e) {
60 throw ConnectionError::upcast($e);
61 }
62
63 if ($result === false) {
64 throw ConnectionError::new($this->connection);
65 }
66
67 return $this->connection->affected_rows;
68 }
69
70 public function lastInsertId(): int|string
71 {
72 $lastInsertId = $this->connection->insert_id;
73
74 if ($lastInsertId === 0) {
75 throw Exception\NoIdentityValue::new();
76 }
77
78 return $this->connection->insert_id;
79 }
80
81 public function beginTransaction(): void
82 {
83 $this->connection->begin_transaction();
84 }
85
86 public function commit(): void
87 {
88 try {
89 if (! $this->connection->commit()) {
90 throw ConnectionError::new($this->connection);
91 }
92 } catch (mysqli_sql_exception $e) {
93 throw ConnectionError::upcast($e);
94 }
95 }
96
97 public function rollBack(): void
98 {
99 try {
100 if (! $this->connection->rollback()) {
101 throw ConnectionError::new($this->connection);
102 }
103 } catch (mysqli_sql_exception $e) {
104 throw ConnectionError::upcast($e);
105 }
106 }
107
108 public function getNativeConnection(): mysqli
109 {
110 return $this->connection;
111 }
112}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php
new file mode 100644
index 0000000..9855e56
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Driver.php
@@ -0,0 +1,117 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli;
6
7use Doctrine\DBAL\Driver\AbstractMySQLDriver;
8use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
9use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired;
10use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset;
11use Doctrine\DBAL\Driver\Mysqli\Initializer\Options;
12use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure;
13use Generator;
14use mysqli;
15use mysqli_sql_exception;
16use SensitiveParameter;
17
18final class Driver extends AbstractMySQLDriver
19{
20 /**
21 * {@inheritDoc}
22 */
23 public function connect(
24 #[SensitiveParameter]
25 array $params,
26 ): Connection {
27 if (! empty($params['persistent'])) {
28 if (! isset($params['host'])) {
29 throw HostRequired::forPersistentConnection();
30 }
31
32 $host = 'p:' . $params['host'];
33 } else {
34 $host = $params['host'] ?? '';
35 }
36
37 $connection = new mysqli();
38
39 foreach ($this->compilePreInitializers($params) as $initializer) {
40 $initializer->initialize($connection);
41 }
42
43 try {
44 $success = @$connection->real_connect(
45 $host,
46 $params['user'] ?? '',
47 $params['password'] ?? '',
48 $params['dbname'] ?? '',
49 $params['port'] ?? 0,
50 $params['unix_socket'] ?? '',
51 $params['driverOptions'][Connection::OPTION_FLAGS] ?? 0,
52 );
53 } catch (mysqli_sql_exception $e) {
54 throw ConnectionFailed::upcast($e);
55 }
56
57 if (! $success) {
58 throw ConnectionFailed::new($connection);
59 }
60
61 foreach ($this->compilePostInitializers($params) as $initializer) {
62 $initializer->initialize($connection);
63 }
64
65 return new Connection($connection);
66 }
67
68 /**
69 * @param array<string, mixed> $params
70 *
71 * @return Generator<int, Initializer>
72 */
73 private function compilePreInitializers(
74 #[SensitiveParameter]
75 array $params,
76 ): Generator {
77 unset($params['driverOptions'][Connection::OPTION_FLAGS]);
78
79 if (isset($params['driverOptions']) && $params['driverOptions'] !== []) {
80 yield new Options($params['driverOptions']);
81 }
82
83 if (
84 ! isset($params['ssl_key']) &&
85 ! isset($params['ssl_cert']) &&
86 ! isset($params['ssl_ca']) &&
87 ! isset($params['ssl_capath']) &&
88 ! isset($params['ssl_cipher'])
89 ) {
90 return;
91 }
92
93 yield new Secure(
94 $params['ssl_key'] ?? '',
95 $params['ssl_cert'] ?? '',
96 $params['ssl_ca'] ?? '',
97 $params['ssl_capath'] ?? '',
98 $params['ssl_cipher'] ?? '',
99 );
100 }
101
102 /**
103 * @param array<string, mixed> $params
104 *
105 * @return Generator<int, Initializer>
106 */
107 private function compilePostInitializers(
108 #[SensitiveParameter]
109 array $params,
110 ): Generator {
111 if (! isset($params['charset'])) {
112 return;
113 }
114
115 yield new Charset($params['charset']);
116 }
117}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php
new file mode 100644
index 0000000..d2477fd
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionError.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use mysqli;
9use mysqli_sql_exception;
10use ReflectionProperty;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class ConnectionError extends AbstractException
18{
19 public static function new(mysqli $connection): self
20 {
21 return new self($connection->error, $connection->sqlstate, $connection->errno);
22 }
23
24 public static function upcast(mysqli_sql_exception $exception): self
25 {
26 $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
27
28 return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception);
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php
new file mode 100644
index 0000000..cb3bc64
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php
@@ -0,0 +1,35 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use mysqli;
9use mysqli_sql_exception;
10use ReflectionProperty;
11
12use function assert;
13
14/**
15 * @internal
16 *
17 * @psalm-immutable
18 */
19final class ConnectionFailed extends AbstractException
20{
21 public static function new(mysqli $connection): self
22 {
23 $error = $connection->connect_error;
24 assert($error !== null);
25
26 return new self($error, 'HY000', $connection->connect_errno);
27 }
28
29 public static function upcast(mysqli_sql_exception $exception): self
30 {
31 $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
32
33 return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception);
34 }
35}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php
new file mode 100644
index 0000000..6f26dbe
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php
@@ -0,0 +1,22 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class FailedReadingStreamOffset extends AbstractException
17{
18 public static function new(int $parameter): self
19 {
20 return new self(sprintf('Failed reading the stream resource for parameter #%d.', $parameter));
21 }
22}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/HostRequired.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/HostRequired.php
new file mode 100644
index 0000000..d3359fc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/HostRequired.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class HostRequired extends AbstractException
15{
16 public static function forPersistentConnection(): self
17 {
18 return new self('The "host" parameter is required for a persistent connection');
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidCharset.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidCharset.php
new file mode 100644
index 0000000..778ea64
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidCharset.php
@@ -0,0 +1,41 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use mysqli;
9use mysqli_sql_exception;
10use ReflectionProperty;
11
12use function sprintf;
13
14/**
15 * @internal
16 *
17 * @psalm-immutable
18 */
19final class InvalidCharset extends AbstractException
20{
21 public static function fromCharset(mysqli $connection, string $charset): self
22 {
23 return new self(
24 sprintf('Failed to set charset "%s": %s', $charset, $connection->error),
25 $connection->sqlstate,
26 $connection->errno,
27 );
28 }
29
30 public static function upcast(mysqli_sql_exception $exception, string $charset): self
31 {
32 $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
33
34 return new self(
35 sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()),
36 $p->getValue($exception),
37 $exception->getCode(),
38 $exception,
39 );
40 }
41}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php
new file mode 100644
index 0000000..654bb87
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/InvalidOption.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class InvalidOption extends AbstractException
17{
18 public static function fromOption(int $option, mixed $value): self
19 {
20 return new self(
21 sprintf('Failed to set option %d with value "%s"', $option, $value),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php
new file mode 100644
index 0000000..566d636
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class NonStreamResourceUsedAsLargeObject extends AbstractException
17{
18 public static function new(int $parameter): self
19 {
20 return new self(
21 sprintf('The resource passed as a LARGE_OBJECT parameter #%d must be of type "stream"', $parameter),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/StatementError.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/StatementError.php
new file mode 100644
index 0000000..991384c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Exception/StatementError.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use mysqli_sql_exception;
9use mysqli_stmt;
10use ReflectionProperty;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class StatementError extends AbstractException
18{
19 public static function new(mysqli_stmt $statement): self
20 {
21 return new self($statement->error, $statement->sqlstate, $statement->errno);
22 }
23
24 public static function upcast(mysqli_sql_exception $exception): self
25 {
26 $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
27
28 return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception);
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php
new file mode 100644
index 0000000..efab67e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli;
6
7use Doctrine\DBAL\Driver\Exception;
8use mysqli;
9
10interface Initializer
11{
12 /** @throws Exception */
13 public function initialize(mysqli $connection): void;
14}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Charset.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Charset.php
new file mode 100644
index 0000000..d02c768
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Charset.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
6
7use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset;
8use Doctrine\DBAL\Driver\Mysqli\Initializer;
9use mysqli;
10use mysqli_sql_exception;
11
12final class Charset implements Initializer
13{
14 public function __construct(private readonly string $charset)
15 {
16 }
17
18 public function initialize(mysqli $connection): void
19 {
20 try {
21 $success = $connection->set_charset($this->charset);
22 } catch (mysqli_sql_exception $e) {
23 throw InvalidCharset::upcast($e, $this->charset);
24 }
25
26 if ($success) {
27 return;
28 }
29
30 throw InvalidCharset::fromCharset($connection, $this->charset);
31 }
32}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php
new file mode 100644
index 0000000..3223951
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Options.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
6
7use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption;
8use Doctrine\DBAL\Driver\Mysqli\Initializer;
9use mysqli;
10
11use function mysqli_options;
12
13final class Options implements Initializer
14{
15 /** @param array<int,mixed> $options */
16 public function __construct(private readonly array $options)
17 {
18 }
19
20 public function initialize(mysqli $connection): void
21 {
22 foreach ($this->options as $option => $value) {
23 if (! mysqli_options($connection, $option, $value)) {
24 throw InvalidOption::fromOption($option, $value);
25 }
26 }
27 }
28}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php
new file mode 100644
index 0000000..fa819b5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Initializer/Secure.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
6
7use Doctrine\DBAL\Driver\Mysqli\Initializer;
8use mysqli;
9use SensitiveParameter;
10
11final class Secure implements Initializer
12{
13 public function __construct(
14 #[SensitiveParameter]
15 private readonly string $key,
16 private readonly string $cert,
17 private readonly string $ca,
18 private readonly string $capath,
19 private readonly string $cipher,
20 ) {
21 }
22
23 public function initialize(mysqli $connection): void
24 {
25 $connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher);
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php
new file mode 100644
index 0000000..8d3c47a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Result.php
@@ -0,0 +1,164 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Driver\FetchUtils;
9use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
10use Doctrine\DBAL\Driver\Result as ResultInterface;
11use mysqli_sql_exception;
12use mysqli_stmt;
13
14use function array_column;
15use function array_combine;
16use function array_fill;
17use function count;
18
19final class Result implements ResultInterface
20{
21 /**
22 * Whether the statement result has columns. The property should be used only after the result metadata
23 * has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined.
24 */
25 private readonly bool $hasColumns;
26
27 /**
28 * Mapping of statement result column indexes to their names. The property should be used only
29 * if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined.
30 *
31 * @var array<int,string>
32 */
33 private readonly array $columnNames;
34
35 /** @var mixed[] */
36 private array $boundValues = [];
37
38 /**
39 * @internal The result can be only instantiated by its driver connection or statement.
40 *
41 * @throws Exception
42 */
43 public function __construct(private readonly mysqli_stmt $statement)
44 {
45 $meta = $statement->result_metadata();
46 $this->hasColumns = $meta !== false;
47 $this->columnNames = $meta !== false ? array_column($meta->fetch_fields(), 'name') : [];
48
49 if ($meta === false) {
50 return;
51 }
52
53 $meta->free();
54
55 // Store result of every execution which has it. Otherwise it will be impossible
56 // to execute a new statement in case if the previous one has non-fetched rows
57 // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
58 $this->statement->store_result();
59
60 // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
61 // it will have to allocate as much memory as it may be needed for the given column type
62 // (e.g. for a LONGBLOB column it's 4 gigabytes)
63 // @link https://bugs.php.net/bug.php?id=51386#1270673122
64 //
65 // Make sure that the values are bound after each execution. Otherwise, if free() has been
66 // previously called on the result, the values are unbound making the statement unusable.
67 //
68 // It's also important that row values are bound after _each_ call to store_result(). Otherwise,
69 // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
70 // to the length of the ones fetched during the previous execution.
71 $this->boundValues = array_fill(0, count($this->columnNames), null);
72
73 // The following is necessary as PHP cannot handle references to properties properly
74 $refs = &$this->boundValues;
75
76 if (! $this->statement->bind_result(...$refs)) {
77 throw StatementError::new($this->statement);
78 }
79 }
80
81 public function fetchNumeric(): array|false
82 {
83 try {
84 $ret = $this->statement->fetch();
85 } catch (mysqli_sql_exception $e) {
86 throw StatementError::upcast($e);
87 }
88
89 if ($ret === false) {
90 throw StatementError::new($this->statement);
91 }
92
93 if ($ret === null) {
94 return false;
95 }
96
97 $values = [];
98
99 foreach ($this->boundValues as $v) {
100 $values[] = $v;
101 }
102
103 return $values;
104 }
105
106 public function fetchAssociative(): array|false
107 {
108 $values = $this->fetchNumeric();
109
110 if ($values === false) {
111 return false;
112 }
113
114 return array_combine($this->columnNames, $values);
115 }
116
117 public function fetchOne(): mixed
118 {
119 return FetchUtils::fetchOne($this);
120 }
121
122 /**
123 * {@inheritDoc}
124 */
125 public function fetchAllNumeric(): array
126 {
127 return FetchUtils::fetchAllNumeric($this);
128 }
129
130 /**
131 * {@inheritDoc}
132 */
133 public function fetchAllAssociative(): array
134 {
135 return FetchUtils::fetchAllAssociative($this);
136 }
137
138 /**
139 * {@inheritDoc}
140 */
141 public function fetchFirstColumn(): array
142 {
143 return FetchUtils::fetchFirstColumn($this);
144 }
145
146 public function rowCount(): int|string
147 {
148 if ($this->hasColumns) {
149 return $this->statement->num_rows;
150 }
151
152 return $this->statement->affected_rows;
153 }
154
155 public function columnCount(): int
156 {
157 return $this->statement->field_count;
158 }
159
160 public function free(): void
161 {
162 $this->statement->free_result();
163 }
164}
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php
new file mode 100644
index 0000000..8436fad
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php
@@ -0,0 +1,154 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\Mysqli;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
9use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject;
10use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
11use Doctrine\DBAL\Driver\Statement as StatementInterface;
12use Doctrine\DBAL\ParameterType;
13use mysqli_sql_exception;
14use mysqli_stmt;
15
16use function array_fill;
17use function assert;
18use function count;
19use function feof;
20use function fread;
21use function get_resource_type;
22use function is_int;
23use function is_resource;
24use function str_repeat;
25
26final class Statement implements StatementInterface
27{
28 private const PARAMETER_TYPE_STRING = 's';
29 private const PARAMETER_TYPE_INTEGER = 'i';
30 private const PARAMETER_TYPE_BINARY = 'b';
31
32 /** @var mixed[] */
33 private array $boundValues;
34
35 private string $types;
36
37 /**
38 * Contains ref values for bindValue().
39 *
40 * @var mixed[]
41 */
42 private array $values = [];
43
44 /** @internal The statement can be only instantiated by its driver connection. */
45 public function __construct(private readonly mysqli_stmt $stmt)
46 {
47 $paramCount = $this->stmt->param_count;
48 $this->types = str_repeat(self::PARAMETER_TYPE_STRING, $paramCount);
49 $this->boundValues = array_fill(1, $paramCount, null);
50 }
51
52 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
53 {
54 assert(is_int($param));
55
56 $this->types[$param - 1] = $this->convertParameterType($type);
57 $this->values[$param] = $value;
58 $this->boundValues[$param] =& $this->values[$param];
59 }
60
61 public function execute(): Result
62 {
63 if (count($this->boundValues) > 0) {
64 $this->bindParameters();
65 }
66
67 try {
68 if (! $this->stmt->execute()) {
69 throw StatementError::new($this->stmt);
70 }
71 } catch (mysqli_sql_exception $e) {
72 throw StatementError::upcast($e);
73 }
74
75 return new Result($this->stmt);
76 }
77
78 /**
79 * Binds parameters with known types previously bound to the statement
80 *
81 * @throws Exception
82 */
83 private function bindParameters(): void
84 {
85 $streams = $values = [];
86 $types = $this->types;
87
88 foreach ($this->boundValues as $parameter => $value) {
89 assert(is_int($parameter));
90 if (! isset($types[$parameter - 1])) {
91 $types[$parameter - 1] = self::PARAMETER_TYPE_STRING;
92 }
93
94 if ($types[$parameter - 1] === self::PARAMETER_TYPE_BINARY) {
95 if (is_resource($value)) {
96 if (get_resource_type($value) !== 'stream') {
97 throw NonStreamResourceUsedAsLargeObject::new($parameter);
98 }
99
100 $streams[$parameter] = $value;
101 $values[$parameter] = null;
102 continue;
103 }
104
105 $types[$parameter - 1] = self::PARAMETER_TYPE_STRING;
106 }
107
108 $values[$parameter] = $value;
109 }
110
111 if (! $this->stmt->bind_param($types, ...$values)) {
112 throw StatementError::new($this->stmt);
113 }
114
115 $this->sendLongData($streams);
116 }
117
118 /**
119 * Handle $this->_longData after regular query parameters have been bound
120 *
121 * @param array<int, resource> $streams
122 *
123 * @throws Exception
124 */
125 private function sendLongData(array $streams): void
126 {
127 foreach ($streams as $paramNr => $stream) {
128 while (! feof($stream)) {
129 $chunk = fread($stream, 8192);
130
131 if ($chunk === false) {
132 throw FailedReadingStreamOffset::new($paramNr);
133 }
134
135 if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) {
136 throw StatementError::new($this->stmt);
137 }
138 }
139 }
140 }
141
142 private function convertParameterType(ParameterType $type): string
143 {
144 return match ($type) {
145 ParameterType::NULL,
146 ParameterType::STRING,
147 ParameterType::ASCII,
148 ParameterType::BINARY => self::PARAMETER_TYPE_STRING,
149 ParameterType::INTEGER,
150 ParameterType::BOOLEAN => self::PARAMETER_TYPE_INTEGER,
151 ParameterType::LARGE_OBJECT => self::PARAMETER_TYPE_BINARY,
152 };
153 }
154}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php b/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php
new file mode 100644
index 0000000..3652ca0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Connection.php
@@ -0,0 +1,119 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception;
9use Doctrine\DBAL\Driver\Exception\IdentityColumnsNotSupported;
10use Doctrine\DBAL\Driver\OCI8\Exception\Error;
11use Doctrine\DBAL\SQL\Parser;
12
13use function addcslashes;
14use function assert;
15use function is_resource;
16use function oci_commit;
17use function oci_parse;
18use function oci_rollback;
19use function oci_server_version;
20use function preg_match;
21use function str_replace;
22
23final class Connection implements ConnectionInterface
24{
25 private readonly Parser $parser;
26 private readonly ExecutionMode $executionMode;
27
28 /**
29 * @internal The connection can be only instantiated by its driver.
30 *
31 * @param resource $connection
32 */
33 public function __construct(private readonly mixed $connection)
34 {
35 $this->parser = new Parser(false);
36 $this->executionMode = new ExecutionMode();
37 }
38
39 public function getServerVersion(): string
40 {
41 $version = oci_server_version($this->connection);
42 assert($version !== false);
43
44 $result = preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches);
45 assert($result === 1);
46
47 return $matches[1];
48 }
49
50 /** @throws Parser\Exception */
51 public function prepare(string $sql): Statement
52 {
53 $visitor = new ConvertPositionalToNamedPlaceholders();
54
55 $this->parser->parse($sql, $visitor);
56
57 $statement = oci_parse($this->connection, $visitor->getSQL());
58 assert(is_resource($statement));
59
60 return new Statement($this->connection, $statement, $visitor->getParameterMap(), $this->executionMode);
61 }
62
63 /**
64 * @throws Exception
65 * @throws Parser\Exception
66 */
67 public function query(string $sql): Result
68 {
69 return $this->prepare($sql)->execute();
70 }
71
72 public function quote(string $value): string
73 {
74 return "'" . addcslashes(str_replace("'", "''", $value), "\000\n\r\\\032") . "'";
75 }
76
77 /**
78 * @throws Exception
79 * @throws Parser\Exception
80 */
81 public function exec(string $sql): int|string
82 {
83 return $this->prepare($sql)->execute()->rowCount();
84 }
85
86 public function lastInsertId(): int|string
87 {
88 throw IdentityColumnsNotSupported::new();
89 }
90
91 public function beginTransaction(): void
92 {
93 $this->executionMode->disableAutoCommit();
94 }
95
96 public function commit(): void
97 {
98 if (! oci_commit($this->connection)) {
99 throw Error::new($this->connection);
100 }
101
102 $this->executionMode->enableAutoCommit();
103 }
104
105 public function rollBack(): void
106 {
107 if (! oci_rollback($this->connection)) {
108 throw Error::new($this->connection);
109 }
110
111 $this->executionMode->enableAutoCommit();
112 }
113
114 /** @return resource */
115 public function getNativeConnection()
116 {
117 return $this->connection;
118 }
119}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php b/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php
new file mode 100644
index 0000000..5898a2c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php
@@ -0,0 +1,58 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7use Doctrine\DBAL\SQL\Parser\Visitor;
8
9use function count;
10use function implode;
11
12/**
13 * Converts positional (?) into named placeholders (:param<num>).
14 *
15 * Oracle does not support positional parameters, hence this method converts all
16 * positional parameters into artificially named parameters.
17 *
18 * @internal This class is not covered by the backward compatibility promise
19 */
20final class ConvertPositionalToNamedPlaceholders implements Visitor
21{
22 /** @var list<string> */
23 private array $buffer = [];
24
25 /** @var array<int,string> */
26 private array $parameterMap = [];
27
28 public function acceptOther(string $sql): void
29 {
30 $this->buffer[] = $sql;
31 }
32
33 public function acceptPositionalParameter(string $sql): void
34 {
35 $position = count($this->parameterMap) + 1;
36 $param = ':param' . $position;
37
38 $this->parameterMap[$position] = $param;
39
40 $this->buffer[] = $param;
41 }
42
43 public function acceptNamedParameter(string $sql): void
44 {
45 $this->buffer[] = $sql;
46 }
47
48 public function getSQL(): string
49 {
50 return implode('', $this->buffer);
51 }
52
53 /** @return array<int,string> */
54 public function getParameterMap(): array
55 {
56 return $this->parameterMap;
57 }
58}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php b/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php
new file mode 100644
index 0000000..1b0afbf
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Driver.php
@@ -0,0 +1,60 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7use Doctrine\DBAL\Driver\AbstractOracleDriver;
8use Doctrine\DBAL\Driver\OCI8\Exception\ConnectionFailed;
9use Doctrine\DBAL\Driver\OCI8\Exception\InvalidConfiguration;
10use SensitiveParameter;
11
12use function oci_connect;
13use function oci_new_connect;
14use function oci_pconnect;
15
16use const OCI_NO_AUTO_COMMIT;
17
18/**
19 * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions.
20 */
21final class Driver extends AbstractOracleDriver
22{
23 /**
24 * {@inheritDoc}
25 */
26 public function connect(
27 #[SensitiveParameter]
28 array $params,
29 ): Connection {
30 $username = $params['user'] ?? '';
31 $password = $params['password'] ?? '';
32 $charset = $params['charset'] ?? '';
33 $sessionMode = $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT;
34
35 $connectionString = $this->getEasyConnectString($params);
36
37 /** @psalm-suppress RiskyTruthyFalsyComparison */
38 $persistent = ! empty($params['persistent']);
39 /** @psalm-suppress RiskyTruthyFalsyComparison */
40 $exclusive = ! empty($params['driverOptions']['exclusive']);
41
42 if ($persistent && $exclusive) {
43 throw InvalidConfiguration::forPersistentAndExclusive();
44 }
45
46 if ($persistent) {
47 $connection = @oci_pconnect($username, $password, $connectionString, $charset, $sessionMode);
48 } elseif ($exclusive) {
49 $connection = @oci_new_connect($username, $password, $connectionString, $charset, $sessionMode);
50 } else {
51 $connection = @oci_connect($username, $password, $connectionString, $charset, $sessionMode);
52 }
53
54 if ($connection === false) {
55 throw ConnectionFailed::new();
56 }
57
58 return new Connection($connection);
59 }
60}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php
new file mode 100644
index 0000000..cefe9a3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/ConnectionFailed.php
@@ -0,0 +1,26 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function assert;
10use function oci_error;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class ConnectionFailed extends AbstractException
18{
19 public static function new(): self
20 {
21 $error = oci_error();
22 assert($error !== false);
23
24 return new self($error['message'], null, $error['code']);
25 }
26}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/Error.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/Error.php
new file mode 100644
index 0000000..6abdf23
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/Error.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function assert;
10use function oci_error;
11
12/**
13 * @internal
14 *
15 * @psalm-immutable
16 */
17final class Error extends AbstractException
18{
19 /** @param resource $resource */
20 public static function new($resource): self
21 {
22 $error = oci_error($resource);
23 assert($error !== false);
24
25 return new self($error['message'], null, $error['code']);
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/InvalidConfiguration.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/InvalidConfiguration.php
new file mode 100644
index 0000000..e9d2d0e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/InvalidConfiguration.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class InvalidConfiguration extends AbstractException
15{
16 public static function forPersistentAndExclusive(): self
17 {
18 return new self('The "persistent" parameter and the "exclusive" driver option are mutually exclusive');
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/NonTerminatedStringLiteral.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/NonTerminatedStringLiteral.php
new file mode 100644
index 0000000..776728f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/NonTerminatedStringLiteral.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class NonTerminatedStringLiteral extends AbstractException
17{
18 public static function new(int $offset): self
19 {
20 return new self(
21 sprintf(
22 'The statement contains non-terminated string literal starting at offset %d.',
23 $offset,
24 ),
25 );
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Exception/UnknownParameterIndex.php b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/UnknownParameterIndex.php
new file mode 100644
index 0000000..2cd3fe7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Exception/UnknownParameterIndex.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class UnknownParameterIndex extends AbstractException
17{
18 public static function new(int $index): self
19 {
20 return new self(
21 sprintf('Could not find variable mapping with index %d, in the SQL statement', $index),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/ExecutionMode.php b/vendor/doctrine/dbal/src/Driver/OCI8/ExecutionMode.php
new file mode 100644
index 0000000..8efb936
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/ExecutionMode.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7/**
8 * Encapsulates the execution mode that is shared between the connection and its statements.
9 *
10 * @internal This class is not covered by the backward compatibility promise
11 */
12final class ExecutionMode
13{
14 private bool $isAutoCommitEnabled = true;
15
16 public function enableAutoCommit(): void
17 {
18 $this->isAutoCommitEnabled = true;
19 }
20
21 public function disableAutoCommit(): void
22 {
23 $this->isAutoCommitEnabled = false;
24 }
25
26 public function isAutoCommitEnabled(): bool
27 {
28 return $this->isAutoCommitEnabled;
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php b/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php
new file mode 100644
index 0000000..b825a1a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Middleware/InitializeSession.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8\Middleware;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Driver\Connection;
9use Doctrine\DBAL\Driver\Middleware;
10use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
11use SensitiveParameter;
12
13final class InitializeSession implements Middleware
14{
15 public function wrap(Driver $driver): Driver
16 {
17 return new class ($driver) extends AbstractDriverMiddleware {
18 /**
19 * {@inheritDoc}
20 */
21 public function connect(
22 #[SensitiveParameter]
23 array $params,
24 ): Connection {
25 $connection = parent::connect($params);
26
27 $connection->exec(
28 'ALTER SESSION SET'
29 . " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
30 . " NLS_TIME_FORMAT = 'HH24:MI:SS'"
31 . " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
32 . " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'"
33 . " NLS_NUMERIC_CHARACTERS = '.,'",
34 );
35
36 return $connection;
37 }
38 };
39 }
40}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Result.php b/vendor/doctrine/dbal/src/Driver/OCI8/Result.php
new file mode 100644
index 0000000..609e651
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Result.php
@@ -0,0 +1,128 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Driver\FetchUtils;
9use Doctrine\DBAL\Driver\OCI8\Exception\Error;
10use Doctrine\DBAL\Driver\Result as ResultInterface;
11
12use function oci_cancel;
13use function oci_error;
14use function oci_fetch_all;
15use function oci_fetch_array;
16use function oci_num_fields;
17use function oci_num_rows;
18
19use const OCI_ASSOC;
20use const OCI_FETCHSTATEMENT_BY_COLUMN;
21use const OCI_FETCHSTATEMENT_BY_ROW;
22use const OCI_NUM;
23use const OCI_RETURN_LOBS;
24use const OCI_RETURN_NULLS;
25
26final class Result implements ResultInterface
27{
28 /**
29 * @internal The result can be only instantiated by its driver connection or statement.
30 *
31 * @param resource $statement
32 */
33 public function __construct(private readonly mixed $statement)
34 {
35 }
36
37 public function fetchNumeric(): array|false
38 {
39 return $this->fetch(OCI_NUM);
40 }
41
42 public function fetchAssociative(): array|false
43 {
44 return $this->fetch(OCI_ASSOC);
45 }
46
47 public function fetchOne(): mixed
48 {
49 return FetchUtils::fetchOne($this);
50 }
51
52 /**
53 * {@inheritDoc}
54 */
55 public function fetchAllNumeric(): array
56 {
57 return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
58 }
59
60 /**
61 * {@inheritDoc}
62 */
63 public function fetchAllAssociative(): array
64 {
65 return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
66 }
67
68 /**
69 * {@inheritDoc}
70 */
71 public function fetchFirstColumn(): array
72 {
73 return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
74 }
75
76 public function rowCount(): int
77 {
78 $count = oci_num_rows($this->statement);
79
80 if ($count !== false) {
81 return $count;
82 }
83
84 return 0;
85 }
86
87 public function columnCount(): int
88 {
89 $count = oci_num_fields($this->statement);
90
91 if ($count !== false) {
92 return $count;
93 }
94
95 return 0;
96 }
97
98 public function free(): void
99 {
100 oci_cancel($this->statement);
101 }
102
103 /** @throws Exception */
104 private function fetch(int $mode): mixed
105 {
106 $result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
107
108 if ($result === false && oci_error($this->statement) !== false) {
109 throw Error::new($this->statement);
110 }
111
112 return $result;
113 }
114
115 /** @return array<mixed> */
116 private function fetchAll(int $mode, int $fetchStructure): array
117 {
118 oci_fetch_all(
119 $this->statement,
120 $result,
121 0,
122 -1,
123 $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS,
124 );
125
126 return $result;
127 }
128}
diff --git a/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php b/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php
new file mode 100644
index 0000000..408f0dd
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/OCI8/Statement.php
@@ -0,0 +1,103 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\OCI8;
6
7use Doctrine\DBAL\Driver\OCI8\Exception\Error;
8use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex;
9use Doctrine\DBAL\Driver\Statement as StatementInterface;
10use Doctrine\DBAL\ParameterType;
11
12use function is_int;
13use function oci_bind_by_name;
14use function oci_execute;
15use function oci_new_descriptor;
16
17use const OCI_B_BIN;
18use const OCI_B_BLOB;
19use const OCI_COMMIT_ON_SUCCESS;
20use const OCI_D_LOB;
21use const OCI_NO_AUTO_COMMIT;
22use const OCI_TEMP_BLOB;
23use const SQLT_CHR;
24
25final class Statement implements StatementInterface
26{
27 /**
28 * @internal The statement can be only instantiated by its driver connection.
29 *
30 * @param resource $connection
31 * @param resource $statement
32 * @param array<int,string> $parameterMap
33 */
34 public function __construct(
35 private readonly mixed $connection,
36 private readonly mixed $statement,
37 private readonly array $parameterMap,
38 private readonly ExecutionMode $executionMode,
39 ) {
40 }
41
42 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
43 {
44 if (is_int($param)) {
45 if (! isset($this->parameterMap[$param])) {
46 throw UnknownParameterIndex::new($param);
47 }
48
49 $param = $this->parameterMap[$param];
50 }
51
52 if ($type === ParameterType::LARGE_OBJECT) {
53 if ($value !== null) {
54 $lob = oci_new_descriptor($this->connection, OCI_D_LOB);
55 $lob->writeTemporary($value, OCI_TEMP_BLOB);
56
57 $value =& $lob;
58 } else {
59 $type = ParameterType::STRING;
60 }
61 }
62
63 if (
64 ! @oci_bind_by_name(
65 $this->statement,
66 $param,
67 $value,
68 -1,
69 $this->convertParameterType($type),
70 )
71 ) {
72 throw Error::new($this->statement);
73 }
74 }
75
76 /**
77 * Converts DBAL parameter type to oci8 parameter type
78 */
79 private function convertParameterType(ParameterType $type): int
80 {
81 return match ($type) {
82 ParameterType::BINARY => OCI_B_BIN,
83 ParameterType::LARGE_OBJECT => OCI_B_BLOB,
84 default => SQLT_CHR,
85 };
86 }
87
88 public function execute(): Result
89 {
90 if ($this->executionMode->isAutoCommitEnabled()) {
91 $mode = OCI_COMMIT_ON_SUCCESS;
92 } else {
93 $mode = OCI_NO_AUTO_COMMIT;
94 }
95
96 $ret = @oci_execute($this->statement, $mode);
97 if (! $ret) {
98 throw Error::new($this->statement);
99 }
100
101 return new Result($this->statement);
102 }
103}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Connection.php b/vendor/doctrine/dbal/src/Driver/PDO/Connection.php
new file mode 100644
index 0000000..b1faca1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/Connection.php
@@ -0,0 +1,133 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\IdentityColumnsNotSupported;
9use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
10use PDO;
11use PDOException;
12use PDOStatement;
13
14use function assert;
15
16final class Connection implements ConnectionInterface
17{
18 /** @internal The connection can be only instantiated by its driver. */
19 public function __construct(private readonly PDO $connection)
20 {
21 $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
22 }
23
24 public function exec(string $sql): int
25 {
26 try {
27 $result = $this->connection->exec($sql);
28
29 assert($result !== false);
30
31 return $result;
32 } catch (PDOException $exception) {
33 throw Exception::new($exception);
34 }
35 }
36
37 public function getServerVersion(): string
38 {
39 return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
40 }
41
42 public function prepare(string $sql): Statement
43 {
44 try {
45 $stmt = $this->connection->prepare($sql);
46 assert($stmt instanceof PDOStatement);
47
48 return new Statement($stmt);
49 } catch (PDOException $exception) {
50 throw Exception::new($exception);
51 }
52 }
53
54 public function query(string $sql): Result
55 {
56 try {
57 $stmt = $this->connection->query($sql);
58 assert($stmt instanceof PDOStatement);
59
60 return new Result($stmt);
61 } catch (PDOException $exception) {
62 throw Exception::new($exception);
63 }
64 }
65
66 public function quote(string $value): string
67 {
68 return $this->connection->quote($value);
69 }
70
71 public function lastInsertId(): int|string
72 {
73 try {
74 $value = $this->connection->lastInsertId();
75 } catch (PDOException $exception) {
76 assert($exception->errorInfo !== null);
77 [$sqlState] = $exception->errorInfo;
78
79 // if the PDO driver does not support this capability, PDO::lastInsertId() triggers an IM001 SQLSTATE
80 // see https://www.php.net/manual/en/pdo.lastinsertid.php
81 if ($sqlState === 'IM001') {
82 throw IdentityColumnsNotSupported::new();
83 }
84
85 // PDO PGSQL throws a 'lastval is not yet defined in this session' error when no identity value is
86 // available, with SQLSTATE 55000 'Object Not In Prerequisite State'
87 if ($sqlState === '55000' && $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) === 'pgsql') {
88 throw NoIdentityValue::new($exception);
89 }
90
91 throw Exception::new($exception);
92 }
93
94 // pdo_mysql & pdo_sqlite return '0', pdo_sqlsrv returns '' or false depending on the PHP version
95 if ($value === '0' || $value === '' || $value === false) {
96 throw NoIdentityValue::new();
97 }
98
99 return $value;
100 }
101
102 public function beginTransaction(): void
103 {
104 try {
105 $this->connection->beginTransaction();
106 } catch (PDOException $exception) {
107 throw Exception::new($exception);
108 }
109 }
110
111 public function commit(): void
112 {
113 try {
114 $this->connection->commit();
115 } catch (PDOException $exception) {
116 throw Exception::new($exception);
117 }
118 }
119
120 public function rollBack(): void
121 {
122 try {
123 $this->connection->rollBack();
124 } catch (PDOException $exception) {
125 throw Exception::new($exception);
126 }
127 }
128
129 public function getNativeConnection(): PDO
130 {
131 return $this->connection;
132 }
133}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Exception.php b/vendor/doctrine/dbal/src/Driver/PDO/Exception.php
new file mode 100644
index 0000000..fbb8125
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/Exception.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use PDOException;
9
10/**
11 * @internal
12 *
13 * @psalm-immutable
14 */
15final class Exception extends AbstractException
16{
17 public static function new(PDOException $exception): self
18 {
19 if ($exception->errorInfo !== null) {
20 [$sqlState, $code] = $exception->errorInfo;
21
22 $code ??= 0;
23 } else {
24 $code = $exception->getCode();
25 $sqlState = null;
26 }
27
28 return new self($exception->getMessage(), $sqlState, $code, $exception);
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php
new file mode 100644
index 0000000..963fef0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/MySQL/Driver.php
@@ -0,0 +1,76 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\MySQL;
6
7use Doctrine\DBAL\Driver\AbstractMySQLDriver;
8use Doctrine\DBAL\Driver\PDO\Connection;
9use Doctrine\DBAL\Driver\PDO\Exception;
10use PDO;
11use PDOException;
12use SensitiveParameter;
13
14final class Driver extends AbstractMySQLDriver
15{
16 /**
17 * {@inheritDoc}
18 */
19 public function connect(
20 #[SensitiveParameter]
21 array $params,
22 ): Connection {
23 $driverOptions = $params['driverOptions'] ?? [];
24
25 if (! empty($params['persistent'])) {
26 $driverOptions[PDO::ATTR_PERSISTENT] = true;
27 }
28
29 $safeParams = $params;
30 unset($safeParams['password']);
31
32 try {
33 $pdo = new PDO(
34 $this->constructPdoDsn($safeParams),
35 $params['user'] ?? '',
36 $params['password'] ?? '',
37 $driverOptions,
38 );
39 } catch (PDOException $exception) {
40 throw Exception::new($exception);
41 }
42
43 return new Connection($pdo);
44 }
45
46 /**
47 * Constructs the MySQL PDO DSN.
48 *
49 * @param mixed[] $params
50 */
51 private function constructPdoDsn(array $params): string
52 {
53 $dsn = 'mysql:';
54 if (isset($params['host']) && $params['host'] !== '') {
55 $dsn .= 'host=' . $params['host'] . ';';
56 }
57
58 if (isset($params['port'])) {
59 $dsn .= 'port=' . $params['port'] . ';';
60 }
61
62 if (isset($params['dbname'])) {
63 $dsn .= 'dbname=' . $params['dbname'] . ';';
64 }
65
66 if (isset($params['unix_socket'])) {
67 $dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
68 }
69
70 if (isset($params['charset'])) {
71 $dsn .= 'charset=' . $params['charset'] . ';';
72 }
73
74 return $dsn;
75 }
76}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php
new file mode 100644
index 0000000..4c02de1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/OCI/Driver.php
@@ -0,0 +1,61 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\OCI;
6
7use Doctrine\DBAL\Driver\AbstractOracleDriver;
8use Doctrine\DBAL\Driver\PDO\Connection;
9use Doctrine\DBAL\Driver\PDO\Exception;
10use PDO;
11use PDOException;
12use SensitiveParameter;
13
14final class Driver extends AbstractOracleDriver
15{
16 /**
17 * {@inheritDoc}
18 */
19 public function connect(
20 #[SensitiveParameter]
21 array $params,
22 ): Connection {
23 $driverOptions = $params['driverOptions'] ?? [];
24
25 if (! empty($params['persistent'])) {
26 $driverOptions[PDO::ATTR_PERSISTENT] = true;
27 }
28
29 $safeParams = $params;
30 unset($safeParams['password']);
31
32 try {
33 $pdo = new PDO(
34 $this->constructPdoDsn($params),
35 $params['user'] ?? '',
36 $params['password'] ?? '',
37 $driverOptions,
38 );
39 } catch (PDOException $exception) {
40 throw Exception::new($exception);
41 }
42
43 return new Connection($pdo);
44 }
45
46 /**
47 * Constructs the Oracle PDO DSN.
48 *
49 * @param mixed[] $params
50 */
51 private function constructPdoDsn(array $params): string
52 {
53 $dsn = 'oci:dbname=' . $this->getEasyConnectString($params);
54
55 if (isset($params['charset'])) {
56 $dsn .= ';charset=' . $params['charset'];
57 }
58
59 return $dsn;
60 }
61}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php
new file mode 100644
index 0000000..a267e84
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/PgSQL/Driver.php
@@ -0,0 +1,113 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\PgSQL;
6
7use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
8use Doctrine\DBAL\Driver\PDO\Connection;
9use Doctrine\DBAL\Driver\PDO\Exception;
10use PDO;
11use PDOException;
12use SensitiveParameter;
13
14final class Driver extends AbstractPostgreSQLDriver
15{
16 /**
17 * {@inheritDoc}
18 */
19 public function connect(
20 #[SensitiveParameter]
21 array $params,
22 ): Connection {
23 $driverOptions = $params['driverOptions'] ?? [];
24
25 if (! empty($params['persistent'])) {
26 $driverOptions[PDO::ATTR_PERSISTENT] = true;
27 }
28
29 $safeParams = $params;
30 unset($safeParams['password']);
31
32 try {
33 $pdo = new PDO(
34 $this->constructPdoDsn($safeParams),
35 $params['user'] ?? '',
36 $params['password'] ?? '',
37 $driverOptions,
38 );
39 } catch (PDOException $exception) {
40 throw Exception::new($exception);
41 }
42
43 if (
44 ! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES])
45 || $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] === true
46 ) {
47 $pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true);
48 }
49
50 $connection = new Connection($pdo);
51
52 /* defining client_encoding via SET NAMES to avoid inconsistent DSN support
53 * - passing client_encoding via the 'options' param breaks pgbouncer support
54 */
55 if (isset($params['charset'])) {
56 $connection->exec('SET NAMES \'' . $params['charset'] . '\'');
57 }
58
59 return $connection;
60 }
61
62 /**
63 * Constructs the Postgres PDO DSN.
64 *
65 * @param array<string, mixed> $params
66 */
67 private function constructPdoDsn(array $params): string
68 {
69 $dsn = 'pgsql:';
70
71 if (isset($params['host']) && $params['host'] !== '') {
72 $dsn .= 'host=' . $params['host'] . ';';
73 }
74
75 if (isset($params['port']) && $params['port'] !== '') {
76 $dsn .= 'port=' . $params['port'] . ';';
77 }
78
79 if (isset($params['dbname'])) {
80 $dsn .= 'dbname=' . $params['dbname'] . ';';
81 }
82
83 if (isset($params['sslmode'])) {
84 $dsn .= 'sslmode=' . $params['sslmode'] . ';';
85 }
86
87 if (isset($params['sslrootcert'])) {
88 $dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';';
89 }
90
91 if (isset($params['sslcert'])) {
92 $dsn .= 'sslcert=' . $params['sslcert'] . ';';
93 }
94
95 if (isset($params['sslkey'])) {
96 $dsn .= 'sslkey=' . $params['sslkey'] . ';';
97 }
98
99 if (isset($params['sslcrl'])) {
100 $dsn .= 'sslcrl=' . $params['sslcrl'] . ';';
101 }
102
103 if (isset($params['application_name'])) {
104 $dsn .= 'application_name=' . $params['application_name'] . ';';
105 }
106
107 if (isset($params['gssencmode'])) {
108 $dsn .= 'gssencmode=' . $params['gssencmode'] . ';';
109 }
110
111 return $dsn;
112 }
113}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Result.php b/vendor/doctrine/dbal/src/Driver/PDO/Result.php
new file mode 100644
index 0000000..1a844d1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/Result.php
@@ -0,0 +1,110 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO;
6
7use Doctrine\DBAL\Driver\Result as ResultInterface;
8use PDO;
9use PDOException;
10use PDOStatement;
11
12final class Result implements ResultInterface
13{
14 /** @internal The result can be only instantiated by its driver connection or statement. */
15 public function __construct(private readonly PDOStatement $statement)
16 {
17 }
18
19 public function fetchNumeric(): array|false
20 {
21 return $this->fetch(PDO::FETCH_NUM);
22 }
23
24 public function fetchAssociative(): array|false
25 {
26 return $this->fetch(PDO::FETCH_ASSOC);
27 }
28
29 public function fetchOne(): mixed
30 {
31 return $this->fetch(PDO::FETCH_COLUMN);
32 }
33
34 /**
35 * {@inheritDoc}
36 */
37 public function fetchAllNumeric(): array
38 {
39 return $this->fetchAll(PDO::FETCH_NUM);
40 }
41
42 /**
43 * {@inheritDoc}
44 */
45 public function fetchAllAssociative(): array
46 {
47 return $this->fetchAll(PDO::FETCH_ASSOC);
48 }
49
50 /**
51 * {@inheritDoc}
52 */
53 public function fetchFirstColumn(): array
54 {
55 return $this->fetchAll(PDO::FETCH_COLUMN);
56 }
57
58 public function rowCount(): int
59 {
60 try {
61 return $this->statement->rowCount();
62 } catch (PDOException $exception) {
63 throw Exception::new($exception);
64 }
65 }
66
67 public function columnCount(): int
68 {
69 try {
70 return $this->statement->columnCount();
71 } catch (PDOException $exception) {
72 throw Exception::new($exception);
73 }
74 }
75
76 public function free(): void
77 {
78 $this->statement->closeCursor();
79 }
80
81 /**
82 * @psalm-param PDO::FETCH_* $mode
83 *
84 * @throws Exception
85 */
86 private function fetch(int $mode): mixed
87 {
88 try {
89 return $this->statement->fetch($mode);
90 } catch (PDOException $exception) {
91 throw Exception::new($exception);
92 }
93 }
94
95 /**
96 * @psalm-param PDO::FETCH_* $mode
97 *
98 * @return list<mixed>
99 *
100 * @throws Exception
101 */
102 private function fetchAll(int $mode): array
103 {
104 try {
105 return $this->statement->fetchAll($mode);
106 } catch (PDOException $exception) {
107 throw Exception::new($exception);
108 }
109 }
110}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php
new file mode 100644
index 0000000..78ba7f8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Connection.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
6
7use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
8use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
9use PDO;
10
11final class Connection extends AbstractConnectionMiddleware
12{
13 public function __construct(private readonly PDOConnection $connection)
14 {
15 parent::__construct($connection);
16 }
17
18 public function prepare(string $sql): Statement
19 {
20 return new Statement(
21 $this->connection->prepare($sql),
22 );
23 }
24
25 public function getNativeConnection(): PDO
26 {
27 return $this->connection->getNativeConnection();
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php
new file mode 100644
index 0000000..a3cae97
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Driver.php
@@ -0,0 +1,108 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
6
7use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
8use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
9use Doctrine\DBAL\Driver\Exception;
10use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
11use Doctrine\DBAL\Driver\PDO\Exception as PDOException;
12use PDO;
13use SensitiveParameter;
14
15use function is_int;
16use function sprintf;
17
18final class Driver extends AbstractSQLServerDriver
19{
20 /**
21 * {@inheritDoc}
22 */
23 public function connect(
24 #[SensitiveParameter]
25 array $params,
26 ): Connection {
27 $driverOptions = $dsnOptions = [];
28
29 if (isset($params['driverOptions'])) {
30 foreach ($params['driverOptions'] as $option => $value) {
31 if (is_int($option)) {
32 $driverOptions[$option] = $value;
33 } else {
34 $dsnOptions[$option] = $value;
35 }
36 }
37 }
38
39 if (! empty($params['persistent'])) {
40 $driverOptions[PDO::ATTR_PERSISTENT] = true;
41 }
42
43 $safeParams = $params;
44 unset($safeParams['password']);
45
46 try {
47 $pdo = new PDO(
48 $this->constructDsn($safeParams, $dsnOptions),
49 $params['user'] ?? '',
50 $params['password'] ?? '',
51 $driverOptions,
52 );
53 } catch (\PDOException $exception) {
54 throw PDOException::new($exception);
55 }
56
57 return new Connection(new PDOConnection($pdo));
58 }
59
60 /**
61 * Constructs the Sqlsrv PDO DSN.
62 *
63 * @param mixed[] $params
64 * @param string[] $connectionOptions
65 *
66 * @throws Exception
67 */
68 private function constructDsn(array $params, array $connectionOptions): string
69 {
70 $dsn = 'sqlsrv:server=';
71
72 if (isset($params['host'])) {
73 $dsn .= $params['host'];
74
75 if (isset($params['port'])) {
76 $dsn .= ',' . $params['port'];
77 }
78 } elseif (isset($params['port'])) {
79 throw PortWithoutHost::new();
80 }
81
82 if (isset($params['dbname'])) {
83 $connectionOptions['Database'] = $params['dbname'];
84 }
85
86 if (isset($params['MultipleActiveResultSets'])) {
87 $connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false';
88 }
89
90 return $dsn . $this->getConnectionOptionsDsn($connectionOptions);
91 }
92
93 /**
94 * Converts a connection options array to the DSN
95 *
96 * @param string[] $connectionOptions
97 */
98 private function getConnectionOptionsDsn(array $connectionOptions): string
99 {
100 $connectionOptionsDsn = '';
101
102 foreach ($connectionOptions as $paramName => $paramValue) {
103 $connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue);
104 }
105
106 return $connectionOptionsDsn;
107 }
108}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php
new file mode 100644
index 0000000..44cecc9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLSrv/Statement.php
@@ -0,0 +1,46 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
6
7use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
8use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement;
9use Doctrine\DBAL\ParameterType;
10use PDO;
11
12final class Statement extends AbstractStatementMiddleware
13{
14 /** @internal The statement can be only instantiated by its driver connection. */
15 public function __construct(private readonly PDOStatement $statement)
16 {
17 parent::__construct($statement);
18 }
19
20 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
21 {
22 switch ($type) {
23 case ParameterType::LARGE_OBJECT:
24 case ParameterType::BINARY:
25 $this->statement->bindParamWithDriverOptions(
26 $param,
27 $value,
28 $type,
29 PDO::SQLSRV_ENCODING_BINARY,
30 );
31 break;
32
33 case ParameterType::ASCII:
34 $this->statement->bindParamWithDriverOptions(
35 $param,
36 $value,
37 ParameterType::STRING,
38 PDO::SQLSRV_ENCODING_SYSTEM,
39 );
40 break;
41
42 default:
43 $this->statement->bindValue($param, $value, $type);
44 }
45 }
46}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php b/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php
new file mode 100644
index 0000000..74194a5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/SQLite/Driver.php
@@ -0,0 +1,55 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO\SQLite;
6
7use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
8use Doctrine\DBAL\Driver\PDO\Connection;
9use Doctrine\DBAL\Driver\PDO\Exception;
10use PDO;
11use PDOException;
12use SensitiveParameter;
13
14use function array_intersect_key;
15
16final class Driver extends AbstractSQLiteDriver
17{
18 /**
19 * {@inheritDoc}
20 */
21 public function connect(
22 #[SensitiveParameter]
23 array $params,
24 ): Connection {
25 try {
26 $pdo = new PDO(
27 $this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])),
28 $params['user'] ?? '',
29 $params['password'] ?? '',
30 $params['driverOptions'] ?? [],
31 );
32 } catch (PDOException $exception) {
33 throw Exception::new($exception);
34 }
35
36 return new Connection($pdo);
37 }
38
39 /**
40 * Constructs the Sqlite PDO DSN.
41 *
42 * @param array<string, mixed> $params
43 */
44 private function constructPdoDsn(array $params): string
45 {
46 $dsn = 'sqlite:';
47 if (isset($params['path'])) {
48 $dsn .= $params['path'];
49 } elseif (isset($params['memory'])) {
50 $dsn .= ':memory:';
51 }
52
53 return $dsn;
54 }
55}
diff --git a/vendor/doctrine/dbal/src/Driver/PDO/Statement.php b/vendor/doctrine/dbal/src/Driver/PDO/Statement.php
new file mode 100644
index 0000000..4f0e070
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PDO/Statement.php
@@ -0,0 +1,80 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PDO;
6
7use Doctrine\DBAL\Driver\Exception as ExceptionInterface;
8use Doctrine\DBAL\Driver\Statement as StatementInterface;
9use Doctrine\DBAL\ParameterType;
10use PDO;
11use PDOException;
12use PDOStatement;
13
14final class Statement implements StatementInterface
15{
16 /** @internal The statement can be only instantiated by its driver connection. */
17 public function __construct(private readonly PDOStatement $stmt)
18 {
19 }
20
21 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
22 {
23 $pdoType = $this->convertParamType($type);
24
25 try {
26 $this->stmt->bindValue($param, $value, $pdoType);
27 } catch (PDOException $exception) {
28 throw Exception::new($exception);
29 }
30 }
31
32 /**
33 * @internal Driver options can be only specified by a PDO-based driver.
34 *
35 * @throws ExceptionInterface
36 */
37 public function bindParamWithDriverOptions(
38 string|int $param,
39 mixed &$variable,
40 ParameterType $type,
41 mixed $driverOptions,
42 ): void {
43 $pdoType = $this->convertParamType($type);
44
45 try {
46 $this->stmt->bindParam($param, $variable, $pdoType, 0, $driverOptions);
47 } catch (PDOException $exception) {
48 throw Exception::new($exception);
49 }
50 }
51
52 public function execute(): Result
53 {
54 try {
55 $this->stmt->execute();
56 } catch (PDOException $exception) {
57 throw Exception::new($exception);
58 }
59
60 return new Result($this->stmt);
61 }
62
63 /**
64 * Converts DBAL parameter type to PDO parameter type
65 *
66 * @psalm-return PDO::PARAM_*
67 */
68 private function convertParamType(ParameterType $type): int
69 {
70 return match ($type) {
71 ParameterType::NULL => PDO::PARAM_NULL,
72 ParameterType::INTEGER => PDO::PARAM_INT,
73 ParameterType::STRING,
74 ParameterType::ASCII => PDO::PARAM_STR,
75 ParameterType::BINARY,
76 ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
77 ParameterType::BOOLEAN => PDO::PARAM_BOOL,
78 };
79 }
80}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php
new file mode 100644
index 0000000..4f280b0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Connection.php
@@ -0,0 +1,129 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
9use Doctrine\DBAL\SQL\Parser;
10use PgSql\Connection as PgSqlConnection;
11
12use function assert;
13use function pg_close;
14use function pg_escape_literal;
15use function pg_get_result;
16use function pg_last_error;
17use function pg_result_error;
18use function pg_send_prepare;
19use function pg_send_query;
20use function pg_version;
21use function uniqid;
22
23final class Connection implements ConnectionInterface
24{
25 private readonly Parser $parser;
26
27 public function __construct(private readonly PgSqlConnection $connection)
28 {
29 $this->parser = new Parser(false);
30 }
31
32 public function __destruct()
33 {
34 if (! isset($this->connection)) {
35 return;
36 }
37
38 @pg_close($this->connection);
39 }
40
41 public function prepare(string $sql): Statement
42 {
43 $visitor = new ConvertParameters();
44 $this->parser->parse($sql, $visitor);
45
46 $statementName = uniqid('dbal', true);
47 if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) {
48 throw new Exception(pg_last_error($this->connection));
49 }
50
51 $result = @pg_get_result($this->connection);
52 assert($result !== false);
53
54 if ((bool) pg_result_error($result)) {
55 throw Exception::fromResult($result);
56 }
57
58 return new Statement($this->connection, $statementName, $visitor->getParameterMap());
59 }
60
61 public function query(string $sql): Result
62 {
63 if (@pg_send_query($this->connection, $sql) !== true) {
64 throw new Exception(pg_last_error($this->connection));
65 }
66
67 $result = @pg_get_result($this->connection);
68 assert($result !== false);
69
70 if ((bool) pg_result_error($result)) {
71 throw Exception::fromResult($result);
72 }
73
74 return new Result($result);
75 }
76
77 /** {@inheritDoc} */
78 public function quote(string $value): string
79 {
80 $quotedValue = pg_escape_literal($this->connection, $value);
81 assert($quotedValue !== false);
82
83 return $quotedValue;
84 }
85
86 public function exec(string $sql): int
87 {
88 return $this->query($sql)->rowCount();
89 }
90
91 /** {@inheritDoc} */
92 public function lastInsertId(): int|string
93 {
94 try {
95 return $this->query('SELECT LASTVAL()')->fetchOne();
96 } catch (Exception $exception) {
97 if ($exception->getSQLState() === '55000') {
98 throw NoIdentityValue::new($exception);
99 }
100
101 throw $exception;
102 }
103 }
104
105 public function beginTransaction(): void
106 {
107 $this->exec('BEGIN');
108 }
109
110 public function commit(): void
111 {
112 $this->exec('COMMIT');
113 }
114
115 public function rollBack(): void
116 {
117 $this->exec('ROLLBACK');
118 }
119
120 public function getServerVersion(): string
121 {
122 return (string) pg_version($this->connection)['server'];
123 }
124
125 public function getNativeConnection(): PgSqlConnection
126 {
127 return $this->connection;
128 }
129}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php
new file mode 100644
index 0000000..795f12d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/ConvertParameters.php
@@ -0,0 +1,49 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\SQL\Parser\Visitor;
8
9use function count;
10use function implode;
11
12final class ConvertParameters implements Visitor
13{
14 /** @var list<string> */
15 private array $buffer = [];
16
17 /** @var array<array-key, int> */
18 private array $parameterMap = [];
19
20 public function acceptPositionalParameter(string $sql): void
21 {
22 $position = count($this->parameterMap) + 1;
23 $this->parameterMap[$position] = $position;
24 $this->buffer[] = '$' . $position;
25 }
26
27 public function acceptNamedParameter(string $sql): void
28 {
29 $position = count($this->parameterMap) + 1;
30 $this->parameterMap[$sql] = $position;
31 $this->buffer[] = '$' . $position;
32 }
33
34 public function acceptOther(string $sql): void
35 {
36 $this->buffer[] = $sql;
37 }
38
39 public function getSQL(): string
40 {
41 return implode('', $this->buffer);
42 }
43
44 /** @return array<array-key, int> */
45 public function getParameterMap(): array
46 {
47 return $this->parameterMap;
48 }
49}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php
new file mode 100644
index 0000000..0d5d605
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Driver.php
@@ -0,0 +1,88 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
8use ErrorException;
9use SensitiveParameter;
10
11use function addslashes;
12use function array_filter;
13use function array_keys;
14use function array_map;
15use function array_slice;
16use function array_values;
17use function func_get_args;
18use function implode;
19use function pg_connect;
20use function restore_error_handler;
21use function set_error_handler;
22use function sprintf;
23
24use const PGSQL_CONNECT_FORCE_NEW;
25
26final class Driver extends AbstractPostgreSQLDriver
27{
28 /** {@inheritDoc} */
29 public function connect(
30 #[SensitiveParameter]
31 array $params,
32 ): Connection {
33 set_error_handler(
34 static function (int $severity, string $message): never {
35 throw new ErrorException($message, 0, $severity, ...array_slice(func_get_args(), 2, 2));
36 },
37 );
38
39 try {
40 $connection = pg_connect($this->constructConnectionString($params), PGSQL_CONNECT_FORCE_NEW);
41 } catch (ErrorException $e) {
42 throw new Exception($e->getMessage(), '08006', 0, $e);
43 } finally {
44 restore_error_handler();
45 }
46
47 if ($connection === false) {
48 throw new Exception('Unable to connect to Postgres server.');
49 }
50
51 $driverConnection = new Connection($connection);
52
53 if (isset($params['application_name'])) {
54 $driverConnection->exec('SET application_name = ' . $driverConnection->quote($params['application_name']));
55 }
56
57 return $driverConnection;
58 }
59
60 /**
61 * Constructs the Postgres connection string
62 *
63 * @param array<string, mixed> $params
64 */
65 private function constructConnectionString(
66 #[SensitiveParameter]
67 array $params,
68 ): string {
69 $components = array_filter(
70 [
71 'host' => $params['host'] ?? null,
72 'port' => $params['port'] ?? null,
73 'dbname' => $params['dbname'] ?? 'postgres',
74 'user' => $params['user'] ?? null,
75 'password' => $params['password'] ?? null,
76 'sslmode' => $params['sslmode'] ?? null,
77 'gssencmode' => $params['gssencmode'] ?? null,
78 ],
79 static fn (int|string|null $value) => $value !== '' && $value !== null,
80 );
81
82 return implode(' ', array_map(
83 static fn (int|string $value, string $key) => sprintf("%s='%s'", $key, addslashes((string) $value)),
84 array_values($components),
85 array_keys($components),
86 ));
87 }
88}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php
new file mode 100644
index 0000000..3036e55
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception.php
@@ -0,0 +1,31 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\AbstractException;
8use PgSql\Result as PgSqlResult;
9
10use function pg_result_error_field;
11
12use const PGSQL_DIAG_MESSAGE_PRIMARY;
13use const PGSQL_DIAG_SQLSTATE;
14
15/**
16 * @internal
17 *
18 * @psalm-immutable
19 */
20final class Exception extends AbstractException
21{
22 public static function fromResult(PgSqlResult $result): self
23 {
24 $sqlstate = pg_result_error_field($result, PGSQL_DIAG_SQLSTATE);
25 if ($sqlstate === false) {
26 $sqlstate = null;
27 }
28
29 return new self((string) pg_result_error_field($result, PGSQL_DIAG_MESSAGE_PRIMARY), $sqlstate);
30 }
31}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php
new file mode 100644
index 0000000..350d80f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL\Exception;
6
7use Doctrine\DBAL\Driver\Exception;
8use UnexpectedValueException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class UnexpectedValue extends UnexpectedValueException implements Exception
14{
15 public static function new(string $value, string $type): self
16 {
17 return new self(sprintf(
18 'Unexpected value "%s" of type "%s" returned by Postgres',
19 $value,
20 $type,
21 ));
22 }
23
24 /** @return null */
25 public function getSQLState(): string|null
26 {
27 return null;
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php
new file mode 100644
index 0000000..549f96d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Exception/UnknownParameter.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class UnknownParameter extends AbstractException
13{
14 public static function new(string $param): self
15 {
16 return new self(
17 sprintf('Could not find parameter %s in the SQL statement', $param),
18 );
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php
new file mode 100644
index 0000000..954758c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Result.php
@@ -0,0 +1,240 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\PgSQL\Exception\UnexpectedValue;
9use Doctrine\DBAL\Driver\Result as ResultInterface;
10use PgSql\Result as PgSqlResult;
11
12use function array_keys;
13use function array_map;
14use function assert;
15use function hex2bin;
16use function pg_affected_rows;
17use function pg_fetch_all;
18use function pg_fetch_all_columns;
19use function pg_fetch_assoc;
20use function pg_fetch_row;
21use function pg_field_name;
22use function pg_field_type;
23use function pg_free_result;
24use function pg_num_fields;
25use function substr;
26
27use const PGSQL_ASSOC;
28use const PGSQL_NUM;
29use const PHP_INT_SIZE;
30
31final class Result implements ResultInterface
32{
33 private ?PgSqlResult $result;
34
35 public function __construct(PgSqlResult $result)
36 {
37 $this->result = $result;
38 }
39
40 public function __destruct()
41 {
42 if (! isset($this->result)) {
43 return;
44 }
45
46 $this->free();
47 }
48
49 /** {@inheritDoc} */
50 public function fetchNumeric(): array|false
51 {
52 if ($this->result === null) {
53 return false;
54 }
55
56 $row = pg_fetch_row($this->result);
57 if ($row === false) {
58 return false;
59 }
60
61 return $this->mapNumericRow($row, $this->fetchNumericColumnTypes());
62 }
63
64 /** {@inheritDoc} */
65 public function fetchAssociative(): array|false
66 {
67 if ($this->result === null) {
68 return false;
69 }
70
71 $row = pg_fetch_assoc($this->result);
72 if ($row === false) {
73 return false;
74 }
75
76 return $this->mapAssociativeRow($row, $this->fetchAssociativeColumnTypes());
77 }
78
79 /** {@inheritDoc} */
80 public function fetchOne(): mixed
81 {
82 return FetchUtils::fetchOne($this);
83 }
84
85 /** {@inheritDoc} */
86 public function fetchAllNumeric(): array
87 {
88 if ($this->result === null) {
89 return [];
90 }
91
92 $types = $this->fetchNumericColumnTypes();
93
94 return array_map(
95 fn (array $row) => $this->mapNumericRow($row, $types),
96 pg_fetch_all($this->result, PGSQL_NUM),
97 );
98 }
99
100 /** {@inheritDoc} */
101 public function fetchAllAssociative(): array
102 {
103 if ($this->result === null) {
104 return [];
105 }
106
107 $types = $this->fetchAssociativeColumnTypes();
108
109 return array_map(
110 fn (array $row) => $this->mapAssociativeRow($row, $types),
111 pg_fetch_all($this->result, PGSQL_ASSOC),
112 );
113 }
114
115 /** {@inheritDoc} */
116 public function fetchFirstColumn(): array
117 {
118 if ($this->result === null) {
119 return [];
120 }
121
122 $postgresType = pg_field_type($this->result, 0);
123
124 return array_map(
125 fn ($value) => $this->mapType($postgresType, $value),
126 pg_fetch_all_columns($this->result),
127 );
128 }
129
130 public function rowCount(): int
131 {
132 if ($this->result === null) {
133 return 0;
134 }
135
136 return pg_affected_rows($this->result);
137 }
138
139 public function columnCount(): int
140 {
141 if ($this->result === null) {
142 return 0;
143 }
144
145 return pg_num_fields($this->result);
146 }
147
148 public function free(): void
149 {
150 if ($this->result === null) {
151 return;
152 }
153
154 pg_free_result($this->result);
155 $this->result = null;
156 }
157
158 /** @return array<int, string> */
159 private function fetchNumericColumnTypes(): array
160 {
161 assert($this->result !== null);
162
163 $types = [];
164 $numFields = pg_num_fields($this->result);
165 for ($i = 0; $i < $numFields; ++$i) {
166 $types[$i] = pg_field_type($this->result, $i);
167 }
168
169 return $types;
170 }
171
172 /** @return array<string, string> */
173 private function fetchAssociativeColumnTypes(): array
174 {
175 assert($this->result !== null);
176
177 $types = [];
178 $numFields = pg_num_fields($this->result);
179 for ($i = 0; $i < $numFields; ++$i) {
180 $types[pg_field_name($this->result, $i)] = pg_field_type($this->result, $i);
181 }
182
183 return $types;
184 }
185
186 /**
187 * @param list<string|null> $row
188 * @param array<int, string> $types
189 *
190 * @return list<mixed>
191 */
192 private function mapNumericRow(array $row, array $types): array
193 {
194 assert($this->result !== null);
195
196 return array_map(
197 fn ($value, $field) => $this->mapType($types[$field], $value),
198 $row,
199 array_keys($row),
200 );
201 }
202
203 /**
204 * @param array<string, string|null> $row
205 * @param array<string, string> $types
206 *
207 * @return array<string, mixed>
208 */
209 private function mapAssociativeRow(array $row, array $types): array
210 {
211 assert($this->result !== null);
212
213 $mappedRow = [];
214 foreach ($row as $field => $value) {
215 $mappedRow[$field] = $this->mapType($types[$field], $value);
216 }
217
218 return $mappedRow;
219 }
220
221 private function mapType(string $postgresType, ?string $value): string|int|float|bool|null
222 {
223 if ($value === null) {
224 return null;
225 }
226
227 return match ($postgresType) {
228 'bool' => match ($value) {
229 't' => true,
230 'f' => false,
231 default => throw UnexpectedValue::new($value, $postgresType),
232 },
233 'bytea' => hex2bin(substr($value, 2)),
234 'float4', 'float8' => (float) $value,
235 'int2', 'int4' => (int) $value,
236 'int8' => PHP_INT_SIZE >= 8 ? (int) $value : $value,
237 default => $value,
238 };
239 }
240}
diff --git a/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php
new file mode 100644
index 0000000..b48ab5d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/PgSQL/Statement.php
@@ -0,0 +1,91 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\PgSQL;
6
7use Doctrine\DBAL\Driver\PgSQL\Exception\UnknownParameter;
8use Doctrine\DBAL\Driver\Statement as StatementInterface;
9use Doctrine\DBAL\ParameterType;
10use PgSql\Connection as PgSqlConnection;
11
12use function assert;
13use function is_resource;
14use function ksort;
15use function pg_escape_bytea;
16use function pg_escape_identifier;
17use function pg_get_result;
18use function pg_last_error;
19use function pg_query;
20use function pg_result_error;
21use function pg_send_execute;
22use function stream_get_contents;
23
24final class Statement implements StatementInterface
25{
26 /** @var array<int, mixed> */
27 private array $parameters = [];
28
29 /** @psalm-var array<int, ParameterType> */
30 private array $parameterTypes = [];
31
32 /** @param array<array-key, int> $parameterMap */
33 public function __construct(
34 private readonly PgSqlConnection $connection,
35 private readonly string $name,
36 private readonly array $parameterMap,
37 ) {
38 }
39
40 public function __destruct()
41 {
42 if (! isset($this->connection)) {
43 return;
44 }
45
46 @pg_query(
47 $this->connection,
48 'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name),
49 );
50 }
51
52 /** {@inheritDoc} */
53 public function bindValue(int|string $param, mixed $value, ParameterType $type = ParameterType::STRING): void
54 {
55 if (! isset($this->parameterMap[$param])) {
56 throw UnknownParameter::new((string) $param);
57 }
58
59 $this->parameters[$this->parameterMap[$param]] = $value;
60 $this->parameterTypes[$this->parameterMap[$param]] = $type;
61 }
62
63 /** {@inheritDoc} */
64 public function execute(): Result
65 {
66 ksort($this->parameters);
67
68 $escapedParameters = [];
69 foreach ($this->parameters as $parameter => $value) {
70 $escapedParameters[] = match ($this->parameterTypes[$parameter]) {
71 ParameterType::BINARY, ParameterType::LARGE_OBJECT => $value === null
72 ? null
73 : pg_escape_bytea($this->connection, is_resource($value) ? stream_get_contents($value) : $value),
74 default => $value,
75 };
76 }
77
78 if (@pg_send_execute($this->connection, $this->name, $escapedParameters) !== true) {
79 throw new Exception(pg_last_error($this->connection));
80 }
81
82 $result = @pg_get_result($this->connection);
83 assert($result !== false);
84
85 if ((bool) pg_result_error($result)) {
86 throw Exception::fromResult($result);
87 }
88
89 return new Result($result);
90 }
91}
diff --git a/vendor/doctrine/dbal/src/Driver/Result.php b/vendor/doctrine/dbal/src/Driver/Result.php
new file mode 100644
index 0000000..500cb88
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Result.php
@@ -0,0 +1,93 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7/**
8 * Driver-level statement execution result.
9 */
10interface Result
11{
12 /**
13 * Returns the next row of the result as a numeric array or FALSE if there are no more rows.
14 *
15 * @return list<mixed>|false
16 *
17 * @throws Exception
18 */
19 public function fetchNumeric(): array|false;
20
21 /**
22 * Returns the next row of the result as an associative array or FALSE if there are no more rows.
23 *
24 * @return array<string,mixed>|false
25 *
26 * @throws Exception
27 */
28 public function fetchAssociative(): array|false;
29
30 /**
31 * Returns the first value of the next row of the result or FALSE if there are no more rows.
32 *
33 * @throws Exception
34 */
35 public function fetchOne(): mixed;
36
37 /**
38 * Returns an array containing all of the result rows represented as numeric arrays.
39 *
40 * @return list<list<mixed>>
41 *
42 * @throws Exception
43 */
44 public function fetchAllNumeric(): array;
45
46 /**
47 * Returns an array containing all of the result rows represented as associative arrays.
48 *
49 * @return list<array<string,mixed>>
50 *
51 * @throws Exception
52 */
53 public function fetchAllAssociative(): array;
54
55 /**
56 * Returns an array containing the values of the first column of the result.
57 *
58 * @return list<mixed>
59 *
60 * @throws Exception
61 */
62 public function fetchFirstColumn(): array;
63
64 /**
65 * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result.
66 *
67 * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.),
68 * some database drivers may return the number of rows returned by that query. However, this behaviour
69 * is not guaranteed for all drivers and should not be relied on in portable applications.
70 *
71 * If the number of rows exceeds {@see PHP_INT_MAX}, it might be returned as string if the driver supports it.
72 *
73 * @return int|numeric-string
74 *
75 * @throws Exception
76 */
77 public function rowCount(): int|string;
78
79 /**
80 * Returns the number of columns in the result
81 *
82 * @return int The number of columns in the result. If the columns cannot be counted,
83 * this method must return 0.
84 *
85 * @throws Exception
86 */
87 public function columnCount(): int;
88
89 /**
90 * Discards the non-fetched portion of the result, enabling the originating statement to be executed again.
91 */
92 public function free(): void;
93}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php
new file mode 100644
index 0000000..71050f1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Connection.php
@@ -0,0 +1,108 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLSrv;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
9use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
10
11use function sqlsrv_begin_transaction;
12use function sqlsrv_commit;
13use function sqlsrv_query;
14use function sqlsrv_rollback;
15use function sqlsrv_rows_affected;
16use function sqlsrv_server_info;
17use function str_replace;
18
19final class Connection implements ConnectionInterface
20{
21 /**
22 * @internal The connection can be only instantiated by its driver.
23 *
24 * @param resource $connection
25 */
26 public function __construct(private readonly mixed $connection)
27 {
28 }
29
30 public function getServerVersion(): string
31 {
32 $serverInfo = sqlsrv_server_info($this->connection);
33
34 return $serverInfo['SQLServerVersion'];
35 }
36
37 public function prepare(string $sql): Statement
38 {
39 return new Statement($this->connection, $sql);
40 }
41
42 public function query(string $sql): Result
43 {
44 return $this->prepare($sql)->execute();
45 }
46
47 public function quote(string $value): string
48 {
49 return "'" . str_replace("'", "''", $value) . "'";
50 }
51
52 public function exec(string $sql): int
53 {
54 $stmt = sqlsrv_query($this->connection, $sql);
55
56 if ($stmt === false) {
57 throw Error::new();
58 }
59
60 $rowsAffected = sqlsrv_rows_affected($stmt);
61
62 if ($rowsAffected === false) {
63 throw Error::new();
64 }
65
66 return $rowsAffected;
67 }
68
69 public function lastInsertId(): int|string
70 {
71 $result = $this->query('SELECT @@IDENTITY');
72
73 $lastInsertId = $result->fetchOne();
74
75 if ($lastInsertId === null) {
76 throw NoIdentityValue::new();
77 }
78
79 return $lastInsertId;
80 }
81
82 public function beginTransaction(): void
83 {
84 if (! sqlsrv_begin_transaction($this->connection)) {
85 throw Error::new();
86 }
87 }
88
89 public function commit(): void
90 {
91 if (! sqlsrv_commit($this->connection)) {
92 throw Error::new();
93 }
94 }
95
96 public function rollBack(): void
97 {
98 if (! sqlsrv_rollback($this->connection)) {
99 throw Error::new();
100 }
101 }
102
103 /** @return resource */
104 public function getNativeConnection()
105 {
106 return $this->connection;
107 }
108}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php
new file mode 100644
index 0000000..c9c2c34
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Driver.php
@@ -0,0 +1,73 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLSrv;
6
7use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
8use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
9use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
10use SensitiveParameter;
11
12use function sqlsrv_configure;
13use function sqlsrv_connect;
14
15/**
16 * Driver for ext/sqlsrv.
17 */
18final class Driver extends AbstractSQLServerDriver
19{
20 /**
21 * {@inheritDoc}
22 */
23 public function connect(
24 #[SensitiveParameter]
25 array $params,
26 ): Connection {
27 $serverName = '';
28
29 if (isset($params['host'])) {
30 $serverName = $params['host'];
31
32 if (isset($params['port'])) {
33 $serverName .= ',' . $params['port'];
34 }
35 } elseif (isset($params['port'])) {
36 throw PortWithoutHost::new();
37 }
38
39 $driverOptions = $params['driverOptions'] ?? [];
40
41 if (isset($params['dbname'])) {
42 $driverOptions['Database'] = $params['dbname'];
43 }
44
45 if (isset($params['charset'])) {
46 $driverOptions['CharacterSet'] = $params['charset'];
47 }
48
49 if (isset($params['user'])) {
50 $driverOptions['UID'] = $params['user'];
51 }
52
53 if (isset($params['password'])) {
54 $driverOptions['PWD'] = $params['password'];
55 }
56
57 if (! isset($driverOptions['ReturnDatesAsStrings'])) {
58 $driverOptions['ReturnDatesAsStrings'] = 1;
59 }
60
61 if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) {
62 throw Error::new();
63 }
64
65 $connection = sqlsrv_connect($serverName, $driverOptions);
66
67 if ($connection === false) {
68 throw Error::new();
69 }
70
71 return new Connection($connection);
72 }
73}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Exception/Error.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Exception/Error.php
new file mode 100644
index 0000000..f39d5fc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Exception/Error.php
@@ -0,0 +1,44 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLSrv\Exception;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9use function rtrim;
10use function sqlsrv_errors;
11
12use const SQLSRV_ERR_ERRORS;
13
14/**
15 * @internal
16 *
17 * @psalm-immutable
18 */
19final class Error extends AbstractException
20{
21 public static function new(): self
22 {
23 $message = '';
24 $sqlState = null;
25 $code = 0;
26
27 foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) {
28 $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n";
29 $sqlState ??= $error['SQLSTATE'];
30
31 if ($code !== 0) {
32 continue;
33 }
34
35 $code = $error['code'];
36 }
37
38 if ($message === '') {
39 $message = 'SQL Server error occurred but no error message was retrieved from driver.';
40 }
41
42 return new self(rtrim($message), $sqlState, $code);
43 }
44}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Result.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Result.php
new file mode 100644
index 0000000..316368f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Result.php
@@ -0,0 +1,104 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLSrv;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\Result as ResultInterface;
9
10use function sqlsrv_fetch;
11use function sqlsrv_fetch_array;
12use function sqlsrv_num_fields;
13use function sqlsrv_rows_affected;
14
15use const SQLSRV_FETCH_ASSOC;
16use const SQLSRV_FETCH_NUMERIC;
17
18final class Result implements ResultInterface
19{
20 /**
21 * @internal The result can be only instantiated by its driver connection or statement.
22 *
23 * @param resource $statement
24 */
25 public function __construct(private readonly mixed $statement)
26 {
27 }
28
29 public function fetchNumeric(): array|false
30 {
31 return $this->fetch(SQLSRV_FETCH_NUMERIC);
32 }
33
34 public function fetchAssociative(): array|false
35 {
36 return $this->fetch(SQLSRV_FETCH_ASSOC);
37 }
38
39 public function fetchOne(): mixed
40 {
41 return FetchUtils::fetchOne($this);
42 }
43
44 /**
45 * {@inheritDoc}
46 */
47 public function fetchAllNumeric(): array
48 {
49 return FetchUtils::fetchAllNumeric($this);
50 }
51
52 /**
53 * {@inheritDoc}
54 */
55 public function fetchAllAssociative(): array
56 {
57 return FetchUtils::fetchAllAssociative($this);
58 }
59
60 /**
61 * {@inheritDoc}
62 */
63 public function fetchFirstColumn(): array
64 {
65 return FetchUtils::fetchFirstColumn($this);
66 }
67
68 public function rowCount(): int
69 {
70 $count = sqlsrv_rows_affected($this->statement);
71
72 if ($count !== false) {
73 return $count;
74 }
75
76 return 0;
77 }
78
79 public function columnCount(): int
80 {
81 $count = sqlsrv_num_fields($this->statement);
82
83 if ($count !== false) {
84 return $count;
85 }
86
87 return 0;
88 }
89
90 public function free(): void
91 {
92 // emulate it by fetching and discarding rows, similarly to what PDO does in this case
93 // @link http://php.net/manual/en/pdostatement.closecursor.php
94 // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
95 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
96 while (sqlsrv_fetch($this->statement)) {
97 }
98 }
99
100 private function fetch(int $fetchType): mixed
101 {
102 return sqlsrv_fetch_array($this->statement, $fetchType) ?? false;
103 }
104}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php b/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php
new file mode 100644
index 0000000..dc7827a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLSrv/Statement.php
@@ -0,0 +1,140 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLSrv;
6
7use Doctrine\DBAL\Driver\Exception;
8use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
9use Doctrine\DBAL\Driver\Statement as StatementInterface;
10use Doctrine\DBAL\ParameterType;
11
12use function assert;
13use function is_int;
14use function sqlsrv_execute;
15use function SQLSRV_PHPTYPE_STREAM;
16use function SQLSRV_PHPTYPE_STRING;
17use function sqlsrv_prepare;
18use function SQLSRV_SQLTYPE_VARBINARY;
19use function stripos;
20
21use const SQLSRV_ENC_BINARY;
22use const SQLSRV_ENC_CHAR;
23use const SQLSRV_PARAM_IN;
24
25final class Statement implements StatementInterface
26{
27 /**
28 * The SQLSRV statement resource.
29 *
30 * @var resource|null
31 */
32 private $stmt;
33
34 /**
35 * References to the variables bound as statement parameters.
36 *
37 * @var array<int, mixed>
38 */
39 private array $variables = [];
40
41 /**
42 * Bound parameter types.
43 *
44 * @var array<int, ParameterType>
45 */
46 private array $types = [];
47
48 /**
49 * Append to any INSERT query to retrieve the last insert id.
50 */
51 private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
52
53 /**
54 * @internal The statement can be only instantiated by its driver connection.
55 *
56 * @param resource $conn
57 */
58 public function __construct(
59 private readonly mixed $conn,
60 private string $sql,
61 ) {
62 if (stripos($sql, 'INSERT INTO ') !== 0) {
63 return;
64 }
65
66 $this->sql .= self::LAST_INSERT_ID_SQL;
67 }
68
69 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
70 {
71 assert(is_int($param));
72
73 $this->variables[$param] = $value;
74 $this->types[$param] = $type;
75 }
76
77 public function execute(): Result
78 {
79 $this->stmt ??= $this->prepare();
80
81 if (! sqlsrv_execute($this->stmt)) {
82 throw Error::new();
83 }
84
85 return new Result($this->stmt);
86 }
87
88 /**
89 * Prepares SQL Server statement resource
90 *
91 * @return resource
92 *
93 * @throws Exception
94 */
95 private function prepare()
96 {
97 $params = [];
98
99 foreach ($this->variables as $column => &$variable) {
100 switch ($this->types[$column]) {
101 case ParameterType::LARGE_OBJECT:
102 $params[$column - 1] = [
103 &$variable,
104 SQLSRV_PARAM_IN,
105 SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
106 SQLSRV_SQLTYPE_VARBINARY('max'),
107 ];
108 break;
109
110 case ParameterType::BINARY:
111 $params[$column - 1] = [
112 &$variable,
113 SQLSRV_PARAM_IN,
114 SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
115 ];
116 break;
117
118 case ParameterType::ASCII:
119 $params[$column - 1] = [
120 &$variable,
121 SQLSRV_PARAM_IN,
122 SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR),
123 ];
124 break;
125
126 default:
127 $params[$column - 1] =& $variable;
128 break;
129 }
130 }
131
132 $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
133
134 if ($stmt === false) {
135 throw Error::new();
136 }
137
138 return $stmt;
139 }
140}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php
new file mode 100644
index 0000000..1e9af93
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Connection.php
@@ -0,0 +1,109 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLite3;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Exception\NoIdentityValue;
9use SQLite3;
10
11use function assert;
12use function sprintf;
13
14final class Connection implements ConnectionInterface
15{
16 /** @internal The connection can be only instantiated by its driver. */
17 public function __construct(private readonly SQLite3 $connection)
18 {
19 }
20
21 public function prepare(string $sql): Statement
22 {
23 try {
24 $statement = $this->connection->prepare($sql);
25 } catch (\Exception $e) {
26 throw Exception::new($e);
27 }
28
29 assert($statement !== false);
30
31 return new Statement($this->connection, $statement);
32 }
33
34 public function query(string $sql): Result
35 {
36 try {
37 $result = $this->connection->query($sql);
38 } catch (\Exception $e) {
39 throw Exception::new($e);
40 }
41
42 assert($result !== false);
43
44 return new Result($result, $this->connection->changes());
45 }
46
47 public function quote(string $value): string
48 {
49 return sprintf('\'%s\'', SQLite3::escapeString($value));
50 }
51
52 public function exec(string $sql): int
53 {
54 try {
55 $this->connection->exec($sql);
56 } catch (\Exception $e) {
57 throw Exception::new($e);
58 }
59
60 return $this->connection->changes();
61 }
62
63 public function lastInsertId(): int
64 {
65 $value = $this->connection->lastInsertRowID();
66 if ($value === 0) {
67 throw NoIdentityValue::new();
68 }
69
70 return $value;
71 }
72
73 public function beginTransaction(): void
74 {
75 try {
76 $this->connection->exec('BEGIN');
77 } catch (\Exception $e) {
78 throw Exception::new($e);
79 }
80 }
81
82 public function commit(): void
83 {
84 try {
85 $this->connection->exec('COMMIT');
86 } catch (\Exception $e) {
87 throw Exception::new($e);
88 }
89 }
90
91 public function rollBack(): void
92 {
93 try {
94 $this->connection->exec('ROLLBACK');
95 } catch (\Exception $e) {
96 throw Exception::new($e);
97 }
98 }
99
100 public function getNativeConnection(): SQLite3
101 {
102 return $this->connection;
103 }
104
105 public function getServerVersion(): string
106 {
107 return SQLite3::version()['versionString'];
108 }
109}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php
new file mode 100644
index 0000000..e6996d3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Driver.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLite3;
6
7use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
8use SensitiveParameter;
9use SQLite3;
10
11final class Driver extends AbstractSQLiteDriver
12{
13 /**
14 * {@inheritDoc}
15 */
16 public function connect(
17 #[SensitiveParameter]
18 array $params,
19 ): Connection {
20 $isMemory = $params['memory'] ?? false;
21
22 if (isset($params['path'])) {
23 if ($isMemory) {
24 throw new Exception(
25 'Invalid connection settings: specifying both parameters "path" and "memory" is ambiguous.',
26 );
27 }
28
29 $filename = $params['path'];
30 } elseif ($isMemory) {
31 $filename = ':memory:';
32 } else {
33 throw new Exception(
34 'Invalid connection settings: specify either the "path" or the "memory" parameter for SQLite3.',
35 );
36 }
37
38 try {
39 $connection = new SQLite3($filename);
40 } catch (\Exception $e) {
41 throw Exception::new($e);
42 }
43
44 $connection->enableExceptions(true);
45
46 return new Connection($connection);
47 }
48}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php
new file mode 100644
index 0000000..d423004
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Exception.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLite3;
6
7use Doctrine\DBAL\Driver\AbstractException;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class Exception extends AbstractException
15{
16 public static function new(\Exception $exception): self
17 {
18 return new self($exception->getMessage(), null, (int) $exception->getCode(), $exception);
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php
new file mode 100644
index 0000000..61b42d3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Result.php
@@ -0,0 +1,88 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLite3;
6
7use Doctrine\DBAL\Driver\FetchUtils;
8use Doctrine\DBAL\Driver\Result as ResultInterface;
9use SQLite3Result;
10
11use const SQLITE3_ASSOC;
12use const SQLITE3_NUM;
13
14final class Result implements ResultInterface
15{
16 private ?SQLite3Result $result;
17
18 /** @internal The result can be only instantiated by its driver connection or statement. */
19 public function __construct(SQLite3Result $result, private readonly int $changes)
20 {
21 $this->result = $result;
22 }
23
24 public function fetchNumeric(): array|false
25 {
26 if ($this->result === null) {
27 return false;
28 }
29
30 return $this->result->fetchArray(SQLITE3_NUM);
31 }
32
33 public function fetchAssociative(): array|false
34 {
35 if ($this->result === null) {
36 return false;
37 }
38
39 return $this->result->fetchArray(SQLITE3_ASSOC);
40 }
41
42 public function fetchOne(): mixed
43 {
44 return FetchUtils::fetchOne($this);
45 }
46
47 /** @inheritDoc */
48 public function fetchAllNumeric(): array
49 {
50 return FetchUtils::fetchAllNumeric($this);
51 }
52
53 /** @inheritDoc */
54 public function fetchAllAssociative(): array
55 {
56 return FetchUtils::fetchAllAssociative($this);
57 }
58
59 /** @inheritDoc */
60 public function fetchFirstColumn(): array
61 {
62 return FetchUtils::fetchFirstColumn($this);
63 }
64
65 public function rowCount(): int
66 {
67 return $this->changes;
68 }
69
70 public function columnCount(): int
71 {
72 if ($this->result === null) {
73 return 0;
74 }
75
76 return $this->result->numColumns();
77 }
78
79 public function free(): void
80 {
81 if ($this->result === null) {
82 return;
83 }
84
85 $this->result->finalize();
86 $this->result = null;
87 }
88}
diff --git a/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php b/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php
new file mode 100644
index 0000000..fe22e1d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/SQLite3/Statement.php
@@ -0,0 +1,61 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver\SQLite3;
6
7use Doctrine\DBAL\Driver\Statement as StatementInterface;
8use Doctrine\DBAL\ParameterType;
9use SQLite3;
10use SQLite3Stmt;
11
12use function assert;
13
14use const SQLITE3_BLOB;
15use const SQLITE3_INTEGER;
16use const SQLITE3_NULL;
17use const SQLITE3_TEXT;
18
19final class Statement implements StatementInterface
20{
21 private const TYPE_BLOB = SQLITE3_BLOB;
22 private const TYPE_INTEGER = SQLITE3_INTEGER;
23 private const TYPE_NULL = SQLITE3_NULL;
24 private const TYPE_TEXT = SQLITE3_TEXT;
25
26 /** @internal The statement can be only instantiated by its driver connection. */
27 public function __construct(
28 private readonly SQLite3 $connection,
29 private readonly SQLite3Stmt $statement,
30 ) {
31 }
32
33 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
34 {
35 $this->statement->bindValue($param, $value, $this->convertParamType($type));
36 }
37
38 public function execute(): Result
39 {
40 try {
41 $result = $this->statement->execute();
42 } catch (\Exception $e) {
43 throw Exception::new($e);
44 }
45
46 assert($result !== false);
47
48 return new Result($result, $this->connection->changes());
49 }
50
51 /** @psalm-return self::TYPE_* */
52 private function convertParamType(ParameterType $type): int
53 {
54 return match ($type) {
55 ParameterType::NULL => self::TYPE_NULL,
56 ParameterType::INTEGER, ParameterType::BOOLEAN => self::TYPE_INTEGER,
57 ParameterType::STRING, ParameterType::ASCII => self::TYPE_TEXT,
58 ParameterType::BINARY, ParameterType::LARGE_OBJECT => self::TYPE_BLOB,
59 };
60 }
61}
diff --git a/vendor/doctrine/dbal/src/Driver/Statement.php b/vendor/doctrine/dbal/src/Driver/Statement.php
new file mode 100644
index 0000000..5f91b49
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Driver/Statement.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Driver;
6
7use Doctrine\DBAL\ParameterType;
8
9/**
10 * Driver-level statement
11 */
12interface Statement
13{
14 /**
15 * Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional
16 * placeholder in the SQL statement that was used to prepare the statement.
17 *
18 * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(),
19 * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine.
20 *
21 * @param int|string $param Parameter identifier. For a prepared statement using named placeholders,
22 * this will be a parameter name of the form :name. For a prepared statement
23 * using question mark placeholders, this will be the 1-indexed position
24 * of the parameter.
25 * @param mixed $value The value to bind to the parameter.
26 * @param ParameterType $type Explicit data type for the parameter using the {@see ParameterType}
27 * constants.
28 *
29 * @throws Exception
30 */
31 public function bindValue(int|string $param, mixed $value, ParameterType $type): void;
32
33 /**
34 * Executes a prepared statement
35 *
36 * @throws Exception
37 */
38 public function execute(): Result;
39}
diff --git a/vendor/doctrine/dbal/src/DriverManager.php b/vendor/doctrine/dbal/src/DriverManager.php
new file mode 100644
index 0000000..8b41cbb
--- /dev/null
+++ b/vendor/doctrine/dbal/src/DriverManager.php
@@ -0,0 +1,191 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\Driver\IBMDB2;
8use Doctrine\DBAL\Driver\Mysqli;
9use Doctrine\DBAL\Driver\OCI8;
10use Doctrine\DBAL\Driver\PDO;
11use Doctrine\DBAL\Driver\PgSQL;
12use Doctrine\DBAL\Driver\SQLite3;
13use Doctrine\DBAL\Driver\SQLSrv;
14use Doctrine\DBAL\Exception\DriverRequired;
15use Doctrine\DBAL\Exception\InvalidDriverClass;
16use Doctrine\DBAL\Exception\InvalidWrapperClass;
17use Doctrine\DBAL\Exception\UnknownDriver;
18use SensitiveParameter;
19
20use function array_keys;
21use function is_a;
22
23/**
24 * Factory for creating {@see Connection} instances.
25 *
26 * @psalm-type OverrideParams = array{
27 * application_name?: string,
28 * charset?: string,
29 * dbname?: string,
30 * defaultTableOptions?: array<string, mixed>,
31 * driver?: key-of<self::DRIVER_MAP>,
32 * driverClass?: class-string<Driver>,
33 * driverOptions?: array<mixed>,
34 * host?: string,
35 * memory?: bool,
36 * password?: string,
37 * path?: string,
38 * persistent?: bool,
39 * port?: int,
40 * serverVersion?: string,
41 * sessionMode?: int,
42 * user?: string,
43 * unix_socket?: string,
44 * wrapperClass?: class-string<Connection>,
45 * }
46 * @psalm-type Params = array{
47 * application_name?: string,
48 * charset?: string,
49 * dbname?: string,
50 * defaultTableOptions?: array<string, mixed>,
51 * driver?: key-of<self::DRIVER_MAP>,
52 * driverClass?: class-string<Driver>,
53 * driverOptions?: array<mixed>,
54 * host?: string,
55 * keepReplica?: bool,
56 * memory?: bool,
57 * password?: string,
58 * path?: string,
59 * persistent?: bool,
60 * port?: int,
61 * primary?: OverrideParams,
62 * replica?: array<OverrideParams>,
63 * serverVersion?: string,
64 * sessionMode?: int,
65 * user?: string,
66 * wrapperClass?: class-string<Connection>,
67 * unix_socket?: string,
68 * }
69 */
70final class DriverManager
71{
72 /**
73 * List of supported drivers and their mappings to the driver classes.
74 *
75 * To add your own driver use the 'driverClass' parameter to {@see DriverManager::getConnection()}.
76 */
77 private const DRIVER_MAP = [
78 'pdo_mysql' => PDO\MySQL\Driver::class,
79 'pdo_sqlite' => PDO\SQLite\Driver::class,
80 'pdo_pgsql' => PDO\PgSQL\Driver::class,
81 'pdo_oci' => PDO\OCI\Driver::class,
82 'oci8' => OCI8\Driver::class,
83 'ibm_db2' => IBMDB2\Driver::class,
84 'pdo_sqlsrv' => PDO\SQLSrv\Driver::class,
85 'mysqli' => Mysqli\Driver::class,
86 'pgsql' => PgSQL\Driver::class,
87 'sqlsrv' => SQLSrv\Driver::class,
88 'sqlite3' => SQLite3\Driver::class,
89 ];
90
91 /**
92 * Private constructor. This class cannot be instantiated.
93 *
94 * @codeCoverageIgnore
95 */
96 private function __construct()
97 {
98 }
99
100 /**
101 * Creates a connection object based on the specified parameters.
102 * This method returns a Doctrine\DBAL\Connection which wraps the underlying
103 * driver connection.
104 *
105 * $params must contain at least one of the following.
106 *
107 * Either 'driver' with one of the array keys of {@see DRIVER_MAP},
108 * OR 'driverClass' that contains the full class name (with namespace) of the
109 * driver class to instantiate.
110 *
111 * Other (optional) parameters:
112 *
113 * <b>user (string)</b>:
114 * The username to use when connecting.
115 *
116 * <b>password (string)</b>:
117 * The password to use when connecting.
118 *
119 * <b>driverOptions (array)</b>:
120 * Any additional driver-specific options for the driver. These are just passed
121 * through to the driver.
122 *
123 * <b>wrapperClass</b>:
124 * You may specify a custom wrapper class through the 'wrapperClass'
125 * parameter but this class MUST inherit from Doctrine\DBAL\Connection.
126 *
127 * <b>driverClass</b>:
128 * The driver class to use.
129 *
130 * @param Configuration|null $config The configuration to use.
131 * @psalm-param Params $params
132 *
133 * @psalm-return ($params is array{wrapperClass: class-string<T>} ? T : Connection)
134 *
135 * @template T of Connection
136 */
137 public static function getConnection(
138 #[SensitiveParameter]
139 array $params,
140 ?Configuration $config = null,
141 ): Connection {
142 $config ??= new Configuration();
143 $driver = self::createDriver($params['driver'] ?? null, $params['driverClass'] ?? null);
144
145 foreach ($config->getMiddlewares() as $middleware) {
146 $driver = $middleware->wrap($driver);
147 }
148
149 /** @var class-string<Connection> $wrapperClass */
150 $wrapperClass = $params['wrapperClass'] ?? Connection::class;
151 if (! is_a($wrapperClass, Connection::class, true)) {
152 throw InvalidWrapperClass::new($wrapperClass);
153 }
154
155 return new $wrapperClass($params, $driver, $config);
156 }
157
158 /**
159 * Returns the list of supported drivers.
160 *
161 * @return string[]
162 * @psalm-return list<key-of<self::DRIVER_MAP>>
163 */
164 public static function getAvailableDrivers(): array
165 {
166 return array_keys(self::DRIVER_MAP);
167 }
168
169 /**
170 * @param class-string<Driver>|null $driverClass
171 * @param key-of<self::DRIVER_MAP>|null $driver
172 */
173 private static function createDriver(?string $driver, ?string $driverClass): Driver
174 {
175 if ($driverClass === null) {
176 if ($driver === null) {
177 throw DriverRequired::new();
178 }
179
180 if (! isset(self::DRIVER_MAP[$driver])) {
181 throw UnknownDriver::new($driver, array_keys(self::DRIVER_MAP));
182 }
183
184 $driverClass = self::DRIVER_MAP[$driver];
185 } elseif (! is_a($driverClass, Driver::class, true)) {
186 throw InvalidDriverClass::new($driverClass);
187 }
188
189 return new $driverClass();
190 }
191}
diff --git a/vendor/doctrine/dbal/src/Exception.php b/vendor/doctrine/dbal/src/Exception.php
new file mode 100644
index 0000000..d942243
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Throwable;
8
9/** @psalm-immutable */
10interface Exception extends Throwable
11{
12}
diff --git a/vendor/doctrine/dbal/src/Exception/CommitFailedRollbackOnly.php b/vendor/doctrine/dbal/src/Exception/CommitFailedRollbackOnly.php
new file mode 100644
index 0000000..b49fd0c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/CommitFailedRollbackOnly.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\ConnectionException;
8
9/** @psalm-immutable */
10final class CommitFailedRollbackOnly extends ConnectionException
11{
12 public static function new(): self
13 {
14 return new self('Transaction commit failed because the transaction has been marked for rollback only.');
15 }
16}
diff --git a/vendor/doctrine/dbal/src/Exception/ConnectionException.php b/vendor/doctrine/dbal/src/Exception/ConnectionException.php
new file mode 100644
index 0000000..673475d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ConnectionException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Base class for all connection related errors detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class ConnectionException extends DriverException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/ConnectionLost.php b/vendor/doctrine/dbal/src/Exception/ConnectionLost.php
new file mode 100644
index 0000000..eda8db1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ConnectionLost.php
@@ -0,0 +1,10 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/** @psalm-immutable */
8final class ConnectionLost extends ConnectionException
9{
10}
diff --git a/vendor/doctrine/dbal/src/Exception/ConstraintViolationException.php b/vendor/doctrine/dbal/src/Exception/ConstraintViolationException.php
new file mode 100644
index 0000000..13a3250
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ConstraintViolationException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Base class for all constraint violation related errors detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class ConstraintViolationException extends ServerException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/DatabaseDoesNotExist.php b/vendor/doctrine/dbal/src/Exception/DatabaseDoesNotExist.php
new file mode 100644
index 0000000..d569d78
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DatabaseDoesNotExist.php
@@ -0,0 +1,10 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/** @psalm-immutable */
8class DatabaseDoesNotExist extends DatabaseObjectNotFoundException
9{
10}
diff --git a/vendor/doctrine/dbal/src/Exception/DatabaseObjectExistsException.php b/vendor/doctrine/dbal/src/Exception/DatabaseObjectExistsException.php
new file mode 100644
index 0000000..b336015
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DatabaseObjectExistsException.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Base class for all already existing database object related errors detected in the driver.
9 *
10 * A database object is considered any asset that can be created in a database
11 * such as schemas, tables, views, sequences, triggers, constraints, indexes,
12 * functions, stored procedures etc.
13 *
14 * @psalm-immutable
15 */
16class DatabaseObjectExistsException extends ServerException
17{
18}
diff --git a/vendor/doctrine/dbal/src/Exception/DatabaseObjectNotFoundException.php b/vendor/doctrine/dbal/src/Exception/DatabaseObjectNotFoundException.php
new file mode 100644
index 0000000..d12dfff
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DatabaseObjectNotFoundException.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Base class for all unknown database object related errors detected in the driver.
9 *
10 * A database object is considered any asset that can be created in a database
11 * such as schemas, tables, views, sequences, triggers, constraints, indexes,
12 * functions, stored procedures etc.
13 *
14 * @psalm-immutable
15 */
16class DatabaseObjectNotFoundException extends ServerException
17{
18}
diff --git a/vendor/doctrine/dbal/src/Exception/DatabaseRequired.php b/vendor/doctrine/dbal/src/Exception/DatabaseRequired.php
new file mode 100644
index 0000000..fc36a9b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DatabaseRequired.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12class DatabaseRequired extends \Exception implements Exception
13{
14 public static function new(string $methodName): self
15 {
16 return new self(
17 sprintf(
18 'A database is required for the method: %s.',
19 $methodName,
20 ),
21 );
22 }
23}
diff --git a/vendor/doctrine/dbal/src/Exception/DeadlockException.php b/vendor/doctrine/dbal/src/Exception/DeadlockException.php
new file mode 100644
index 0000000..9000387
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DeadlockException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a deadlock error of a transaction detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class DeadlockException extends ServerException implements RetryableException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/DriverException.php b/vendor/doctrine/dbal/src/Exception/DriverException.php
new file mode 100644
index 0000000..d794d77
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DriverException.php
@@ -0,0 +1,51 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Query;
10
11use function assert;
12
13/**
14 * Base class for all errors detected in the driver.
15 *
16 * @psalm-immutable
17 */
18class DriverException extends \Exception implements Exception, Driver\Exception
19{
20 /**
21 * @internal
22 *
23 * @param Driver\Exception $driverException The DBAL driver exception to chain.
24 * @param Query|null $query The SQL query that triggered the exception, if any.
25 */
26 public function __construct(
27 Driver\Exception $driverException,
28 private readonly ?Query $query,
29 ) {
30 if ($query !== null) {
31 $message = 'An exception occurred while executing a query: ' . $driverException->getMessage();
32 } else {
33 $message = 'An exception occurred in the driver: ' . $driverException->getMessage();
34 }
35
36 parent::__construct($message, $driverException->getCode(), $driverException);
37 }
38
39 public function getSQLState(): ?string
40 {
41 $previous = $this->getPrevious();
42 assert($previous instanceof Driver\Exception);
43
44 return $previous->getSQLState();
45 }
46
47 public function getQuery(): ?Query
48 {
49 return $this->query;
50 }
51}
diff --git a/vendor/doctrine/dbal/src/Exception/DriverRequired.php b/vendor/doctrine/dbal/src/Exception/DriverRequired.php
new file mode 100644
index 0000000..e37ac68
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/DriverRequired.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use function sprintf;
8
9/** @psalm-immutable */
10final class DriverRequired extends InvalidArgumentException
11{
12 /** @param string|null $url The URL that was provided in the connection parameters (if any). */
13 public static function new(?string $url = null): self
14 {
15 if ($url !== null) {
16 return new self(
17 sprintf(
18 'The options "driver" or "driverClass" are mandatory if a connection URL without scheme '
19 . 'is given to DriverManager::getConnection(). Given URL "%s".',
20 $url,
21 ),
22 );
23 }
24
25 return new self(
26 'The options "driver" or "driverClass" are mandatory if no PDO '
27 . 'instance is given to DriverManager::getConnection().',
28 );
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php b/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php
new file mode 100644
index 0000000..22531ec
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ForeignKeyConstraintViolationException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a foreign key constraint violation detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class ForeignKeyConstraintViolationException extends ConstraintViolationException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidArgumentException.php b/vendor/doctrine/dbal/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..7b7cd3d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Exception;
8
9/**
10 * Exception to be thrown when invalid arguments are passed to any DBAL API
11 *
12 * @psalm-immutable
13 */
14class InvalidArgumentException extends \InvalidArgumentException implements Exception
15{
16}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidColumnDeclaration.php b/vendor/doctrine/dbal/src/Exception/InvalidColumnDeclaration.php
new file mode 100644
index 0000000..c177611
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidColumnDeclaration.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Exception;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class InvalidColumnDeclaration extends LogicException implements Exception
14{
15 public static function fromInvalidColumnType(string $columnName, InvalidColumnType $e): self
16 {
17 return new self(sprintf('Column "%s" has invalid type', $columnName), 0, $e);
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidColumnType.php b/vendor/doctrine/dbal/src/Exception/InvalidColumnType.php
new file mode 100644
index 0000000..6c3c8ec
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidColumnType.php
@@ -0,0 +1,13 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Exception;
8use LogicException;
9
10/** @psalm-immutable */
11abstract class InvalidColumnType extends LogicException implements Exception
12{
13}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnLengthRequired.php b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnLengthRequired.php
new file mode 100644
index 0000000..1d5e795
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnLengthRequired.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception\InvalidColumnType;
6
7use Doctrine\DBAL\Exception\InvalidColumnType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10use function get_debug_type;
11use function sprintf;
12
13/**
14 * @internal
15 *
16 * @psalm-immutable
17 */
18final class ColumnLengthRequired extends InvalidColumnType
19{
20 /**
21 * @param AbstractPlatform $platform The target platform
22 * @param string $type The SQL column type
23 */
24 public static function new(AbstractPlatform $platform, string $type): self
25 {
26 return new self(
27 sprintf(
28 '%s requires the length of a %s column to be specified',
29 get_debug_type($platform),
30 $type,
31 ),
32 );
33 }
34}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnPrecisionRequired.php b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnPrecisionRequired.php
new file mode 100644
index 0000000..8898cd0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnPrecisionRequired.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception\InvalidColumnType;
6
7use Doctrine\DBAL\Exception\InvalidColumnType;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class ColumnPrecisionRequired extends InvalidColumnType
15{
16 public static function new(): self
17 {
18 return new self('Column precision is not specified');
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnScaleRequired.php b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnScaleRequired.php
new file mode 100644
index 0000000..472cbc3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidColumnType/ColumnScaleRequired.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception\InvalidColumnType;
6
7use Doctrine\DBAL\Exception\InvalidColumnType;
8
9/**
10 * @internal
11 *
12 * @psalm-immutable
13 */
14final class ColumnScaleRequired extends InvalidColumnType
15{
16 public static function new(): self
17 {
18 return new self('Column scale is not specified');
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidDriverClass.php b/vendor/doctrine/dbal/src/Exception/InvalidDriverClass.php
new file mode 100644
index 0000000..2ebfd87
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidDriverClass.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Driver;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class InvalidDriverClass extends InvalidArgumentException
13{
14 public static function new(string $driverClass): self
15 {
16 return new self(
17 sprintf(
18 'The given driver class %s has to implement the %s interface.',
19 $driverClass,
20 Driver::class,
21 ),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidFieldNameException.php b/vendor/doctrine/dbal/src/Exception/InvalidFieldNameException.php
new file mode 100644
index 0000000..e5bfcee
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidFieldNameException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for an invalid specified field name in a statement detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class InvalidFieldNameException extends ServerException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/InvalidWrapperClass.php b/vendor/doctrine/dbal/src/Exception/InvalidWrapperClass.php
new file mode 100644
index 0000000..2cbe82a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/InvalidWrapperClass.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Connection;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class InvalidWrapperClass extends InvalidArgumentException
13{
14 public static function new(string $wrapperClass): self
15 {
16 return new self(
17 sprintf(
18 'The given wrapper class %s has to be a subtype of %s.',
19 $wrapperClass,
20 Connection::class,
21 ),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Exception/LockWaitTimeoutException.php b/vendor/doctrine/dbal/src/Exception/LockWaitTimeoutException.php
new file mode 100644
index 0000000..2e81580
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/LockWaitTimeoutException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a lock wait timeout error of a transaction detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class LockWaitTimeoutException extends ServerException implements RetryableException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/MalformedDsnException.php b/vendor/doctrine/dbal/src/Exception/MalformedDsnException.php
new file mode 100644
index 0000000..eb3056c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/MalformedDsnException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/** @psalm-immutable */
8class MalformedDsnException extends InvalidArgumentException
9{
10 public static function new(): self
11 {
12 return new self('Malformed database connection URL');
13 }
14}
diff --git a/vendor/doctrine/dbal/src/Exception/NoActiveTransaction.php b/vendor/doctrine/dbal/src/Exception/NoActiveTransaction.php
new file mode 100644
index 0000000..a8d19c5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/NoActiveTransaction.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\ConnectionException;
8
9/** @psalm-immutable */
10final class NoActiveTransaction extends ConnectionException
11{
12 public static function new(): self
13 {
14 return new self('There is no active transaction.');
15 }
16}
diff --git a/vendor/doctrine/dbal/src/Exception/NoKeyValue.php b/vendor/doctrine/dbal/src/Exception/NoKeyValue.php
new file mode 100644
index 0000000..5cea11d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/NoKeyValue.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\Exception;
8
9use function sprintf;
10
11/**
12 * @internal
13 *
14 * @psalm-immutable
15 */
16final class NoKeyValue extends \Exception implements Exception
17{
18 public static function fromColumnCount(int $columnCount): self
19 {
20 return new self(
21 sprintf(
22 'Fetching as key-value pairs requires the result to contain at least 2 columns, %d given.',
23 $columnCount,
24 ),
25 );
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Exception/NonUniqueFieldNameException.php b/vendor/doctrine/dbal/src/Exception/NonUniqueFieldNameException.php
new file mode 100644
index 0000000..96a8582
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/NonUniqueFieldNameException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a non-unique/ambiguous specified field name in a statement detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class NonUniqueFieldNameException extends ServerException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/NotNullConstraintViolationException.php b/vendor/doctrine/dbal/src/Exception/NotNullConstraintViolationException.php
new file mode 100644
index 0000000..4c2a7be
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/NotNullConstraintViolationException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a NOT NULL constraint violation detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class NotNullConstraintViolationException extends ConstraintViolationException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/ReadOnlyException.php b/vendor/doctrine/dbal/src/Exception/ReadOnlyException.php
new file mode 100644
index 0000000..73fa5b1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ReadOnlyException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a write operation attempt on a read-only database element detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class ReadOnlyException extends ServerException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/RetryableException.php b/vendor/doctrine/dbal/src/Exception/RetryableException.php
new file mode 100644
index 0000000..7ed6cf2
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/RetryableException.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Throwable;
8
9/**
10 * Marker interface for all exceptions where retrying the transaction makes sense.
11 *
12 * @psalm-immutable
13 */
14interface RetryableException extends Throwable
15{
16}
diff --git a/vendor/doctrine/dbal/src/Exception/SavepointsNotSupported.php b/vendor/doctrine/dbal/src/Exception/SavepointsNotSupported.php
new file mode 100644
index 0000000..1d7260e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/SavepointsNotSupported.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use Doctrine\DBAL\ConnectionException;
8
9/** @psalm-immutable */
10final class SavepointsNotSupported extends ConnectionException
11{
12 public static function new(): self
13 {
14 return new self('Savepoints are not supported by this driver.');
15 }
16}
diff --git a/vendor/doctrine/dbal/src/Exception/SchemaDoesNotExist.php b/vendor/doctrine/dbal/src/Exception/SchemaDoesNotExist.php
new file mode 100644
index 0000000..463d95b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/SchemaDoesNotExist.php
@@ -0,0 +1,10 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/** @psalm-immutable */
8class SchemaDoesNotExist extends DatabaseObjectNotFoundException
9{
10}
diff --git a/vendor/doctrine/dbal/src/Exception/ServerException.php b/vendor/doctrine/dbal/src/Exception/ServerException.php
new file mode 100644
index 0000000..798c2b9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/ServerException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Base class for all server related errors detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class ServerException extends DriverException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/SyntaxErrorException.php b/vendor/doctrine/dbal/src/Exception/SyntaxErrorException.php
new file mode 100644
index 0000000..595f3b4
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/SyntaxErrorException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a syntax error in a statement detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class SyntaxErrorException extends ServerException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/TableExistsException.php b/vendor/doctrine/dbal/src/Exception/TableExistsException.php
new file mode 100644
index 0000000..e469e22
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/TableExistsException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for an already existing table referenced in a statement detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class TableExistsException extends DatabaseObjectExistsException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/TableNotFoundException.php b/vendor/doctrine/dbal/src/Exception/TableNotFoundException.php
new file mode 100644
index 0000000..f029590
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/TableNotFoundException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for an unknown table referenced in a statement detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class TableNotFoundException extends DatabaseObjectNotFoundException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/UniqueConstraintViolationException.php b/vendor/doctrine/dbal/src/Exception/UniqueConstraintViolationException.php
new file mode 100644
index 0000000..ab04a29
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/UniqueConstraintViolationException.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7/**
8 * Exception for a unique constraint violation detected in the driver.
9 *
10 * @psalm-immutable
11 */
12class UniqueConstraintViolationException extends ConstraintViolationException
13{
14}
diff --git a/vendor/doctrine/dbal/src/Exception/UnknownDriver.php b/vendor/doctrine/dbal/src/Exception/UnknownDriver.php
new file mode 100644
index 0000000..1fd4b3a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Exception/UnknownDriver.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Exception;
6
7use function implode;
8use function sprintf;
9
10/** @psalm-immutable */
11final class UnknownDriver extends InvalidArgumentException
12{
13 /** @param string[] $knownDrivers */
14 public static function new(string $unknownDriverName, array $knownDrivers): self
15 {
16 return new self(
17 sprintf(
18 'The given driver "%s" is unknown, Doctrine currently supports only the following drivers: %s',
19 $unknownDriverName,
20 implode(', ', $knownDrivers),
21 ),
22 );
23 }
24}
diff --git a/vendor/doctrine/dbal/src/ExpandArrayParameters.php b/vendor/doctrine/dbal/src/ExpandArrayParameters.php
new file mode 100644
index 0000000..253861c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ExpandArrayParameters.php
@@ -0,0 +1,128 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\ArrayParameters\Exception\MissingNamedParameter;
8use Doctrine\DBAL\ArrayParameters\Exception\MissingPositionalParameter;
9use Doctrine\DBAL\SQL\Parser\Visitor;
10use Doctrine\DBAL\Types\Type;
11
12use function array_fill;
13use function array_key_exists;
14use function count;
15use function implode;
16use function substr;
17
18/** @psalm-import-type WrapperParameterTypeArray from Connection */
19final class ExpandArrayParameters implements Visitor
20{
21 private int $originalParameterIndex = 0;
22
23 /** @var list<string> */
24 private array $convertedSQL = [];
25
26 /** @var list<mixed> */
27 private array $convertedParameters = [];
28
29 /** @var array<int<0, max>,string|ParameterType|Type> */
30 private array $convertedTypes = [];
31
32 /**
33 * @param array<int, mixed>|array<string, mixed> $parameters
34 * @psalm-param WrapperParameterTypeArray $types
35 */
36 public function __construct(
37 private readonly array $parameters,
38 private readonly array $types,
39 ) {
40 }
41
42 public function acceptPositionalParameter(string $sql): void
43 {
44 $index = $this->originalParameterIndex;
45
46 if (! array_key_exists($index, $this->parameters)) {
47 throw MissingPositionalParameter::new($index);
48 }
49
50 $this->acceptParameter($index, $this->parameters[$index]);
51
52 $this->originalParameterIndex++;
53 }
54
55 public function acceptNamedParameter(string $sql): void
56 {
57 $name = substr($sql, 1);
58
59 if (! array_key_exists($name, $this->parameters)) {
60 throw MissingNamedParameter::new($name);
61 }
62
63 $this->acceptParameter($name, $this->parameters[$name]);
64 }
65
66 public function acceptOther(string $sql): void
67 {
68 $this->convertedSQL[] = $sql;
69 }
70
71 public function getSQL(): string
72 {
73 return implode('', $this->convertedSQL);
74 }
75
76 /** @return list<mixed> */
77 public function getParameters(): array
78 {
79 return $this->convertedParameters;
80 }
81
82 private function acceptParameter(int|string $key, mixed $value): void
83 {
84 if (! isset($this->types[$key])) {
85 $this->convertedSQL[] = '?';
86 $this->convertedParameters[] = $value;
87
88 return;
89 }
90
91 $type = $this->types[$key];
92
93 if (! $type instanceof ArrayParameterType) {
94 $this->appendTypedParameter([$value], $type);
95
96 return;
97 }
98
99 if (count($value) === 0) {
100 $this->convertedSQL[] = 'NULL';
101
102 return;
103 }
104
105 $this->appendTypedParameter($value, ArrayParameterType::toElementParameterType($type));
106 }
107
108 /** @return array<int<0, max>,string|ParameterType|Type> */
109 public function getTypes(): array
110 {
111 return $this->convertedTypes;
112 }
113
114 /** @param list<mixed> $values */
115 private function appendTypedParameter(array $values, string|ParameterType|Type $type): void
116 {
117 $this->convertedSQL[] = implode(', ', array_fill(0, count($values), '?'));
118
119 $index = count($this->convertedParameters);
120
121 foreach ($values as $value) {
122 $this->convertedParameters[] = $value;
123 $this->convertedTypes[$index] = $type;
124
125 $index++;
126 }
127 }
128}
diff --git a/vendor/doctrine/dbal/src/LockMode.php b/vendor/doctrine/dbal/src/LockMode.php
new file mode 100644
index 0000000..035353e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/LockMode.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7/**
8 * Contains all supported lock modes.
9 */
10enum LockMode
11{
12 case NONE;
13 case OPTIMISTIC;
14 case PESSIMISTIC_READ;
15 case PESSIMISTIC_WRITE;
16}
diff --git a/vendor/doctrine/dbal/src/Logging/Connection.php b/vendor/doctrine/dbal/src/Logging/Connection.php
new file mode 100644
index 0000000..9c220de
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Logging/Connection.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Logging;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
9use Doctrine\DBAL\Driver\Result;
10use Doctrine\DBAL\Driver\Statement as DriverStatement;
11use Psr\Log\LoggerInterface;
12
13final class Connection extends AbstractConnectionMiddleware
14{
15 /** @internal This connection can be only instantiated by its driver. */
16 public function __construct(ConnectionInterface $connection, private readonly LoggerInterface $logger)
17 {
18 parent::__construct($connection);
19 }
20
21 public function __destruct()
22 {
23 $this->logger->info('Disconnecting');
24 }
25
26 public function prepare(string $sql): DriverStatement
27 {
28 return new Statement(
29 parent::prepare($sql),
30 $this->logger,
31 $sql,
32 );
33 }
34
35 public function query(string $sql): Result
36 {
37 $this->logger->debug('Executing query: {sql}', ['sql' => $sql]);
38
39 return parent::query($sql);
40 }
41
42 public function exec(string $sql): int|string
43 {
44 $this->logger->debug('Executing statement: {sql}', ['sql' => $sql]);
45
46 return parent::exec($sql);
47 }
48
49 public function beginTransaction(): void
50 {
51 $this->logger->debug('Beginning transaction');
52
53 parent::beginTransaction();
54 }
55
56 public function commit(): void
57 {
58 $this->logger->debug('Committing transaction');
59
60 parent::commit();
61 }
62
63 public function rollBack(): void
64 {
65 $this->logger->debug('Rolling back transaction');
66
67 parent::rollBack();
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Logging/Driver.php b/vendor/doctrine/dbal/src/Logging/Driver.php
new file mode 100644
index 0000000..35acd39
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Logging/Driver.php
@@ -0,0 +1,50 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Logging;
6
7use Doctrine\DBAL\Driver as DriverInterface;
8use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
9use Psr\Log\LoggerInterface;
10use SensitiveParameter;
11
12final class Driver extends AbstractDriverMiddleware
13{
14 /** @internal This driver can be only instantiated by its middleware. */
15 public function __construct(DriverInterface $driver, private readonly LoggerInterface $logger)
16 {
17 parent::__construct($driver);
18 }
19
20 /**
21 * {@inheritDoc}
22 */
23 public function connect(
24 #[SensitiveParameter]
25 array $params,
26 ): Connection {
27 $this->logger->info('Connecting with parameters {params}', ['params' => $this->maskPassword($params)]);
28
29 return new Connection(
30 parent::connect($params),
31 $this->logger,
32 );
33 }
34
35 /**
36 * @param array<string,mixed> $params Connection parameters
37 *
38 * @return array<string,mixed>
39 */
40 private function maskPassword(
41 #[SensitiveParameter]
42 array $params,
43 ): array {
44 if (isset($params['password'])) {
45 $params['password'] = '<redacted>';
46 }
47
48 return $params;
49 }
50}
diff --git a/vendor/doctrine/dbal/src/Logging/Middleware.php b/vendor/doctrine/dbal/src/Logging/Middleware.php
new file mode 100644
index 0000000..989e0ca
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Logging/Middleware.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Logging;
6
7use Doctrine\DBAL\Driver as DriverInterface;
8use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
9use Psr\Log\LoggerInterface;
10
11final class Middleware implements MiddlewareInterface
12{
13 public function __construct(private readonly LoggerInterface $logger)
14 {
15 }
16
17 public function wrap(DriverInterface $driver): DriverInterface
18 {
19 return new Driver($driver, $this->logger);
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Logging/Statement.php b/vendor/doctrine/dbal/src/Logging/Statement.php
new file mode 100644
index 0000000..ed1ca7f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Logging/Statement.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Logging;
6
7use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
8use Doctrine\DBAL\Driver\Result as ResultInterface;
9use Doctrine\DBAL\Driver\Statement as StatementInterface;
10use Doctrine\DBAL\ParameterType;
11use Psr\Log\LoggerInterface;
12
13final class Statement extends AbstractStatementMiddleware
14{
15 /** @var array<int,mixed>|array<string,mixed> */
16 private array $params = [];
17
18 /** @var array<int,ParameterType>|array<string,ParameterType> */
19 private array $types = [];
20
21 /** @internal This statement can be only instantiated by its connection. */
22 public function __construct(
23 StatementInterface $statement,
24 private readonly LoggerInterface $logger,
25 private readonly string $sql,
26 ) {
27 parent::__construct($statement);
28 }
29
30 public function bindValue(int|string $param, mixed $value, ParameterType $type): void
31 {
32 $this->params[$param] = $value;
33 $this->types[$param] = $type;
34
35 parent::bindValue($param, $value, $type);
36 }
37
38 public function execute(): ResultInterface
39 {
40 $this->logger->debug('Executing statement: {sql} (parameters: {params}, types: {types})', [
41 'sql' => $this->sql,
42 'params' => $this->params,
43 'types' => $this->types,
44 ]);
45
46 return parent::execute();
47 }
48}
diff --git a/vendor/doctrine/dbal/src/ParameterType.php b/vendor/doctrine/dbal/src/ParameterType.php
new file mode 100644
index 0000000..19f577e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ParameterType.php
@@ -0,0 +1,46 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7/**
8 * Statement parameter type.
9 */
10enum ParameterType
11{
12 /**
13 * Represents the SQL NULL data type.
14 */
15 case NULL;
16
17 /**
18 * Represents the SQL INTEGER data type.
19 */
20 case INTEGER;
21
22 /**
23 * Represents the SQL CHAR, VARCHAR, or other string data type.
24 */
25 case STRING;
26
27 /**
28 * Represents the SQL large object data type.
29 */
30 case LARGE_OBJECT;
31
32 /**
33 * Represents a boolean data type.
34 */
35 case BOOLEAN;
36
37 /**
38 * Represents a binary string data type.
39 */
40 case BINARY;
41
42 /**
43 * Represents an ASCII string data type
44 */
45 case ASCII;
46}
diff --git a/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php
new file mode 100644
index 0000000..770d863
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php
@@ -0,0 +1,842 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\Keywords\KeywordList;
10use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
11use Doctrine\DBAL\Schema\AbstractAsset;
12use Doctrine\DBAL\Schema\ForeignKeyConstraint;
13use Doctrine\DBAL\Schema\Identifier;
14use Doctrine\DBAL\Schema\Index;
15use Doctrine\DBAL\Schema\MySQLSchemaManager;
16use Doctrine\DBAL\Schema\TableDiff;
17use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
18use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
19use Doctrine\DBAL\TransactionIsolationLevel;
20use Doctrine\DBAL\Types\Types;
21
22use function array_merge;
23use function array_unique;
24use function array_values;
25use function count;
26use function implode;
27use function in_array;
28use function is_numeric;
29use function sprintf;
30use function str_replace;
31use function strtolower;
32
33/**
34 * Provides the base implementation for the lowest versions of supported MySQL-like database platforms.
35 */
36abstract class AbstractMySQLPlatform extends AbstractPlatform
37{
38 final public const LENGTH_LIMIT_TINYTEXT = 255;
39 final public const LENGTH_LIMIT_TEXT = 65535;
40 final public const LENGTH_LIMIT_MEDIUMTEXT = 16777215;
41
42 final public const LENGTH_LIMIT_TINYBLOB = 255;
43 final public const LENGTH_LIMIT_BLOB = 65535;
44 final public const LENGTH_LIMIT_MEDIUMBLOB = 16777215;
45
46 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
47 {
48 if ($limit !== null) {
49 $query .= sprintf(' LIMIT %d', $limit);
50
51 if ($offset > 0) {
52 $query .= sprintf(' OFFSET %d', $offset);
53 }
54 } elseif ($offset > 0) {
55 // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible
56 $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset);
57 }
58
59 return $query;
60 }
61
62 public function quoteSingleIdentifier(string $str): string
63 {
64 return '`' . str_replace('`', '``', $str) . '`';
65 }
66
67 public function getRegexpExpression(): string
68 {
69 return 'RLIKE';
70 }
71
72 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
73 {
74 if ($start === null) {
75 return sprintf('LOCATE(%s, %s)', $substring, $string);
76 }
77
78 return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start);
79 }
80
81 public function getConcatExpression(string ...$string): string
82 {
83 return sprintf('CONCAT(%s)', implode(', ', $string));
84 }
85
86 protected function getDateArithmeticIntervalExpression(
87 string $date,
88 string $operator,
89 string $interval,
90 DateIntervalUnit $unit,
91 ): string {
92 $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB';
93
94 return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit->value . ')';
95 }
96
97 public function getDateDiffExpression(string $date1, string $date2): string
98 {
99 return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')';
100 }
101
102 public function getCurrentDatabaseExpression(): string
103 {
104 return 'DATABASE()';
105 }
106
107 public function getLengthExpression(string $string): string
108 {
109 return 'CHAR_LENGTH(' . $string . ')';
110 }
111
112 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
113 public function getListDatabasesSQL(): string
114 {
115 return 'SHOW DATABASES';
116 }
117
118 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
119 public function getListViewsSQL(string $database): string
120 {
121 return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database);
122 }
123
124 /**
125 * {@inheritDoc}
126 */
127 public function getJsonTypeDeclarationSQL(array $column): string
128 {
129 return 'JSON';
130 }
131
132 /**
133 * Gets the SQL snippet used to declare a CLOB column type.
134 * TINYTEXT : 2 ^ 8 - 1 = 255
135 * TEXT : 2 ^ 16 - 1 = 65535
136 * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215
137 * LONGTEXT : 2 ^ 32 - 1 = 4294967295
138 *
139 * {@inheritDoc}
140 */
141 public function getClobTypeDeclarationSQL(array $column): string
142 {
143 if (! empty($column['length']) && is_numeric($column['length'])) {
144 $length = $column['length'];
145
146 if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
147 return 'TINYTEXT';
148 }
149
150 if ($length <= static::LENGTH_LIMIT_TEXT) {
151 return 'TEXT';
152 }
153
154 if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) {
155 return 'MEDIUMTEXT';
156 }
157 }
158
159 return 'LONGTEXT';
160 }
161
162 /**
163 * {@inheritDoc}
164 */
165 public function getDateTimeTypeDeclarationSQL(array $column): string
166 {
167 if (isset($column['version']) && $column['version'] === true) {
168 return 'TIMESTAMP';
169 }
170
171 return 'DATETIME';
172 }
173
174 /**
175 * {@inheritDoc}
176 */
177 public function getDateTypeDeclarationSQL(array $column): string
178 {
179 return 'DATE';
180 }
181
182 /**
183 * {@inheritDoc}
184 */
185 public function getTimeTypeDeclarationSQL(array $column): string
186 {
187 return 'TIME';
188 }
189
190 /**
191 * {@inheritDoc}
192 */
193 public function getBooleanTypeDeclarationSQL(array $column): string
194 {
195 return 'TINYINT(1)';
196 }
197
198 /**
199 * {@inheritDoc}
200 *
201 * MySQL supports this through AUTO_INCREMENT columns.
202 */
203 public function supportsIdentityColumns(): bool
204 {
205 return true;
206 }
207
208 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
209 public function supportsInlineColumnComments(): bool
210 {
211 return true;
212 }
213
214 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
215 public function supportsColumnCollation(): bool
216 {
217 return true;
218 }
219
220 /**
221 * The SQL snippet required to elucidate a column type
222 *
223 * Returns a column type SELECT snippet string
224 */
225 public function getColumnTypeSQLSnippet(string $tableAlias, string $databaseName): string
226 {
227 return $tableAlias . '.COLUMN_TYPE';
228 }
229
230 /**
231 * {@inheritDoc}
232 */
233 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
234 {
235 $queryFields = $this->getColumnDeclarationListSQL($columns);
236
237 if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
238 foreach ($options['uniqueConstraints'] as $definition) {
239 $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition);
240 }
241 }
242
243 // add all indexes
244 if (isset($options['indexes']) && ! empty($options['indexes'])) {
245 foreach ($options['indexes'] as $definition) {
246 $queryFields .= ', ' . $this->getIndexDeclarationSQL($definition);
247 }
248 }
249
250 // attach all primary keys
251 if (isset($options['primary']) && ! empty($options['primary'])) {
252 $keyColumns = array_unique(array_values($options['primary']));
253 $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
254 }
255
256 $sql = ['CREATE'];
257
258 if (! empty($options['temporary'])) {
259 $sql[] = 'TEMPORARY';
260 }
261
262 $sql[] = 'TABLE ' . $name . ' (' . $queryFields . ')';
263
264 $tableOptions = $this->buildTableOptions($options);
265
266 if ($tableOptions !== '') {
267 $sql[] = $tableOptions;
268 }
269
270 if (isset($options['partition_options'])) {
271 $sql[] = $options['partition_options'];
272 }
273
274 $sql = [implode(' ', $sql)];
275
276 if (isset($options['foreignKeys'])) {
277 foreach ($options['foreignKeys'] as $definition) {
278 $sql[] = $this->getCreateForeignKeySQL($definition, $name);
279 }
280 }
281
282 return $sql;
283 }
284
285 public function createSelectSQLBuilder(): SelectSQLBuilder
286 {
287 return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null);
288 }
289
290 /**
291 * Build SQL for table options
292 *
293 * @param mixed[] $options
294 */
295 private function buildTableOptions(array $options): string
296 {
297 if (isset($options['table_options'])) {
298 return $options['table_options'];
299 }
300
301 $tableOptions = [];
302
303 if (isset($options['charset'])) {
304 $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
305 }
306
307 if (isset($options['collation'])) {
308 $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']);
309 }
310
311 if (isset($options['engine'])) {
312 $tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
313 }
314
315 // Auto increment
316 if (isset($options['auto_increment'])) {
317 $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
318 }
319
320 // Comment
321 if (isset($options['comment'])) {
322 $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment']));
323 }
324
325 // Row format
326 if (isset($options['row_format'])) {
327 $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
328 }
329
330 return implode(' ', $tableOptions);
331 }
332
333 /**
334 * {@inheritDoc}
335 */
336 public function getAlterTableSQL(TableDiff $diff): array
337 {
338 $columnSql = [];
339 $queryParts = [];
340
341 foreach ($diff->getAddedColumns() as $column) {
342 $columnProperties = array_merge($column->toArray(), [
343 'comment' => $column->getComment(),
344 ]);
345
346 $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL(
347 $column->getQuotedName($this),
348 $columnProperties,
349 );
350 }
351
352 foreach ($diff->getDroppedColumns() as $column) {
353 $queryParts[] = 'DROP ' . $column->getQuotedName($this);
354 }
355
356 foreach ($diff->getModifiedColumns() as $columnDiff) {
357 $newColumn = $columnDiff->getNewColumn();
358
359 $newColumnProperties = array_merge($newColumn->toArray(), [
360 'comment' => $newColumn->getComment(),
361 ]);
362
363 $oldColumn = $columnDiff->getOldColumn();
364
365 $queryParts[] = 'CHANGE ' . $oldColumn->getQuotedName($this) . ' '
366 . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties);
367 }
368
369 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
370 $oldColumnName = new Identifier($oldColumnName);
371
372 $columnProperties = array_merge($column->toArray(), [
373 'comment' => $column->getComment(),
374 ]);
375
376 $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' '
377 . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties);
378 }
379
380 $addedIndexes = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes());
381 $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes());
382 $diffModified = false;
383
384 if (isset($addedIndexes['primary'])) {
385 $keyColumns = array_values(array_unique($addedIndexes['primary']->getColumns()));
386 $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
387 unset($addedIndexes['primary']);
388 $diffModified = true;
389 } elseif (isset($modifiedIndexes['primary'])) {
390 $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns());
391
392 // Necessary in case the new primary key includes a new auto_increment column
393 foreach ($modifiedIndexes['primary']->getColumns() as $columnName) {
394 if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) {
395 $keyColumns = array_values(array_unique($modifiedIndexes['primary']->getColumns()));
396 $queryParts[] = 'DROP PRIMARY KEY';
397 $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
398 unset($modifiedIndexes['primary']);
399 $diffModified = true;
400 break;
401 }
402 }
403 }
404
405 if ($diffModified) {
406 $diff = new TableDiff(
407 $diff->getOldTable(),
408 $diff->getAddedColumns(),
409 $diff->getModifiedColumns(),
410 $diff->getDroppedColumns(),
411 $diff->getRenamedColumns(),
412 array_values($addedIndexes),
413 array_values($modifiedIndexes),
414 $diff->getDroppedIndexes(),
415 $diff->getRenamedIndexes(),
416 $diff->getAddedForeignKeys(),
417 $diff->getModifiedForeignKeys(),
418 $diff->getDroppedForeignKeys(),
419 );
420 }
421
422 $sql = [];
423 $tableSql = [];
424
425 if (count($queryParts) > 0) {
426 $sql[] = 'ALTER TABLE ' . $diff->getOldTable()->getQuotedName($this) . ' '
427 . implode(', ', $queryParts);
428 }
429
430 $sql = array_merge(
431 $this->getPreAlterTableIndexForeignKeySQL($diff),
432 $sql,
433 $this->getPostAlterTableIndexForeignKeySQL($diff),
434 );
435
436 return array_merge($sql, $tableSql, $columnSql);
437 }
438
439 /**
440 * {@inheritDoc}
441 */
442 protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array
443 {
444 $sql = [];
445
446 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
447
448 foreach ($diff->getModifiedIndexes() as $changedIndex) {
449 $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex));
450 }
451
452 foreach ($diff->getDroppedIndexes() as $droppedIndex) {
453 $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex));
454
455 foreach ($diff->getAddedIndexes() as $addedIndex) {
456 if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) {
457 continue;
458 }
459
460 $indexClause = 'INDEX ' . $addedIndex->getName();
461
462 if ($addedIndex->isPrimary()) {
463 $indexClause = 'PRIMARY KEY';
464 } elseif ($addedIndex->isUnique()) {
465 $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName();
466 }
467
468 $query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', ';
469 $query .= 'ADD ' . $indexClause;
470 $query .= ' (' . implode(', ', $addedIndex->getQuotedColumns($this)) . ')';
471
472 $sql[] = $query;
473
474 $diff->unsetAddedIndex($addedIndex);
475 $diff->unsetDroppedIndex($droppedIndex);
476
477 break;
478 }
479 }
480
481 return array_merge(
482 $sql,
483 $this->getPreAlterTableAlterIndexForeignKeySQL($diff),
484 parent::getPreAlterTableIndexForeignKeySQL($diff),
485 $this->getPreAlterTableRenameIndexForeignKeySQL($diff),
486 );
487 }
488
489 /**
490 * @return list<string>
491 *
492 * @throws Exception
493 */
494 private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array
495 {
496 if (! $index->isPrimary()) {
497 return [];
498 }
499
500 $table = $diff->getOldTable();
501
502 $sql = [];
503
504 $tableNameSQL = $table->getQuotedName($this);
505
506 // Dropping primary keys requires to unset autoincrement attribute on the particular column first.
507 foreach ($index->getColumns() as $columnName) {
508 if (! $table->hasColumn($columnName)) {
509 continue;
510 }
511
512 $column = $table->getColumn($columnName);
513
514 if (! $column->getAutoincrement()) {
515 continue;
516 }
517
518 $column->setAutoincrement(false);
519
520 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' .
521 $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
522
523 // original autoincrement information might be needed later on by other parts of the table alteration
524 $column->setAutoincrement(true);
525 }
526
527 return $sql;
528 }
529
530 /**
531 * @param TableDiff $diff The table diff to gather the SQL for.
532 *
533 * @return list<string>
534 *
535 * @throws Exception
536 */
537 private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array
538 {
539 $table = $diff->getOldTable();
540
541 $primaryKey = $table->getPrimaryKey();
542
543 if ($primaryKey === null) {
544 return [];
545 }
546
547 $primaryKeyColumns = [];
548
549 foreach ($primaryKey->getColumns() as $columnName) {
550 if (! $table->hasColumn($columnName)) {
551 continue;
552 }
553
554 $primaryKeyColumns[] = $table->getColumn($columnName);
555 }
556
557 if (count($primaryKeyColumns) === 0) {
558 return [];
559 }
560
561 $sql = [];
562
563 $tableNameSQL = $table->getQuotedName($this);
564
565 foreach ($diff->getModifiedIndexes() as $changedIndex) {
566 // Changed primary key
567 if (! $changedIndex->isPrimary()) {
568 continue;
569 }
570
571 foreach ($primaryKeyColumns as $column) {
572 // Check if an autoincrement column was dropped from the primary key.
573 if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) {
574 continue;
575 }
576
577 // The autoincrement attribute needs to be removed from the dropped column
578 // before we can drop and recreate the primary key.
579 $column->setAutoincrement(false);
580
581 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' .
582 $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
583
584 // Restore the autoincrement attribute as it might be needed later on
585 // by other parts of the table alteration.
586 $column->setAutoincrement(true);
587 }
588 }
589
590 return $sql;
591 }
592
593 /**
594 * @param TableDiff $diff The table diff to gather the SQL for.
595 *
596 * @return list<string>
597 */
598 protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array
599 {
600 return [];
601 }
602
603 protected function getCreateIndexSQLFlags(Index $index): string
604 {
605 $type = '';
606 if ($index->isUnique()) {
607 $type .= 'UNIQUE ';
608 } elseif ($index->hasFlag('fulltext')) {
609 $type .= 'FULLTEXT ';
610 } elseif ($index->hasFlag('spatial')) {
611 $type .= 'SPATIAL ';
612 }
613
614 return $type;
615 }
616
617 /**
618 * {@inheritDoc}
619 */
620 public function getIntegerTypeDeclarationSQL(array $column): string
621 {
622 return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
623 }
624
625 /**
626 * {@inheritDoc}
627 */
628 public function getBigIntTypeDeclarationSQL(array $column): string
629 {
630 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
631 }
632
633 /**
634 * {@inheritDoc}
635 */
636 public function getSmallIntTypeDeclarationSQL(array $column): string
637 {
638 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
639 }
640
641 /**
642 * {@inheritDoc}
643 */
644 public function getFloatDeclarationSQL(array $column): string
645 {
646 return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column);
647 }
648
649 /**
650 * {@inheritDoc}
651 */
652 public function getDecimalTypeDeclarationSQL(array $column): string
653 {
654 return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column);
655 }
656
657 /**
658 * Get unsigned declaration for a column.
659 *
660 * @param mixed[] $columnDef
661 */
662 private function getUnsignedDeclaration(array $columnDef): string
663 {
664 return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
665 }
666
667 /**
668 * {@inheritDoc}
669 */
670 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
671 {
672 $autoinc = '';
673 if (! empty($column['autoincrement'])) {
674 $autoinc = ' AUTO_INCREMENT';
675 }
676
677 return $this->getUnsignedDeclaration($column) . $autoinc;
678 }
679
680 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
681 public function getColumnCharsetDeclarationSQL(string $charset): string
682 {
683 return 'CHARACTER SET ' . $charset;
684 }
685
686 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
687 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
688 {
689 $query = '';
690 if ($foreignKey->hasOption('match')) {
691 $query .= ' MATCH ' . $foreignKey->getOption('match');
692 }
693
694 $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
695
696 return $query;
697 }
698
699 public function getDropIndexSQL(string $name, string $table): string
700 {
701 return 'DROP INDEX ' . $name . ' ON ' . $table;
702 }
703
704 /**
705 * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19.
706 *
707 * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html
708 */
709 public function getDropUniqueConstraintSQL(string $name, string $tableName): string
710 {
711 return $this->getDropIndexSQL($name, $tableName);
712 }
713
714 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
715 {
716 return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
717 }
718
719 protected function initializeDoctrineTypeMappings(): void
720 {
721 $this->doctrineTypeMapping = [
722 'bigint' => Types::BIGINT,
723 'binary' => Types::BINARY,
724 'blob' => Types::BLOB,
725 'char' => Types::STRING,
726 'date' => Types::DATE_MUTABLE,
727 'datetime' => Types::DATETIME_MUTABLE,
728 'decimal' => Types::DECIMAL,
729 'double' => Types::FLOAT,
730 'float' => Types::FLOAT,
731 'int' => Types::INTEGER,
732 'integer' => Types::INTEGER,
733 'json' => Types::JSON,
734 'longblob' => Types::BLOB,
735 'longtext' => Types::TEXT,
736 'mediumblob' => Types::BLOB,
737 'mediumint' => Types::INTEGER,
738 'mediumtext' => Types::TEXT,
739 'numeric' => Types::DECIMAL,
740 'real' => Types::FLOAT,
741 'set' => Types::SIMPLE_ARRAY,
742 'smallint' => Types::SMALLINT,
743 'string' => Types::STRING,
744 'text' => Types::TEXT,
745 'time' => Types::TIME_MUTABLE,
746 'timestamp' => Types::DATETIME_MUTABLE,
747 'tinyblob' => Types::BLOB,
748 'tinyint' => Types::BOOLEAN,
749 'tinytext' => Types::TEXT,
750 'varbinary' => Types::BINARY,
751 'varchar' => Types::STRING,
752 'year' => Types::DATE_MUTABLE,
753 ];
754 }
755
756 protected function createReservedKeywordsList(): KeywordList
757 {
758 return new MySQLKeywords();
759 }
760
761 /**
762 * {@inheritDoc}
763 *
764 * MySQL commits a transaction implicitly when DROP TABLE is executed, however not
765 * if DROP TEMPORARY TABLE is executed.
766 */
767 public function getDropTemporaryTableSQL(string $table): string
768 {
769 return 'DROP TEMPORARY TABLE ' . $table;
770 }
771
772 /**
773 * Gets the SQL Snippet used to declare a BLOB column type.
774 * TINYBLOB : 2 ^ 8 - 1 = 255
775 * BLOB : 2 ^ 16 - 1 = 65535
776 * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215
777 * LONGBLOB : 2 ^ 32 - 1 = 4294967295
778 *
779 * {@inheritDoc}
780 */
781 public function getBlobTypeDeclarationSQL(array $column): string
782 {
783 if (! empty($column['length']) && is_numeric($column['length'])) {
784 $length = $column['length'];
785
786 if ($length <= static::LENGTH_LIMIT_TINYBLOB) {
787 return 'TINYBLOB';
788 }
789
790 if ($length <= static::LENGTH_LIMIT_BLOB) {
791 return 'BLOB';
792 }
793
794 if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) {
795 return 'MEDIUMBLOB';
796 }
797 }
798
799 return 'LONGBLOB';
800 }
801
802 public function quoteStringLiteral(string $str): string
803 {
804 // MySQL requires backslashes to be escaped as well.
805 $str = str_replace('\\', '\\\\', $str);
806
807 return parent::quoteStringLiteral($str);
808 }
809
810 public function getDefaultTransactionIsolationLevel(): TransactionIsolationLevel
811 {
812 return TransactionIsolationLevel::REPEATABLE_READ;
813 }
814
815 public function supportsColumnLengthIndexes(): bool
816 {
817 return true;
818 }
819
820 public function createSchemaManager(Connection $connection): MySQLSchemaManager
821 {
822 return new MySQLSchemaManager($connection, $this);
823 }
824
825 /**
826 * @param array<T> $assets
827 *
828 * @return array<string,T>
829 *
830 * @template T of AbstractAsset
831 */
832 private function indexAssetsByLowerCaseName(array $assets): array
833 {
834 $result = [];
835
836 foreach ($assets as $asset) {
837 $result[strtolower($asset->getName())] = $asset;
838 }
839
840 return $result;
841 }
842}
diff --git a/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php
new file mode 100644
index 0000000..0beb37a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php
@@ -0,0 +1,2219 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Exception\InvalidArgumentException;
10use Doctrine\DBAL\Exception\InvalidColumnDeclaration;
11use Doctrine\DBAL\Exception\InvalidColumnType;
12use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
13use Doctrine\DBAL\Exception\InvalidColumnType\ColumnPrecisionRequired;
14use Doctrine\DBAL\Exception\InvalidColumnType\ColumnScaleRequired;
15use Doctrine\DBAL\LockMode;
16use Doctrine\DBAL\Platforms\Exception\NoColumnsSpecifiedForTable;
17use Doctrine\DBAL\Platforms\Exception\NotSupported;
18use Doctrine\DBAL\Platforms\Keywords\KeywordList;
19use Doctrine\DBAL\Schema\AbstractSchemaManager;
20use Doctrine\DBAL\Schema\Column;
21use Doctrine\DBAL\Schema\ForeignKeyConstraint;
22use Doctrine\DBAL\Schema\Identifier;
23use Doctrine\DBAL\Schema\Index;
24use Doctrine\DBAL\Schema\SchemaDiff;
25use Doctrine\DBAL\Schema\Sequence;
26use Doctrine\DBAL\Schema\Table;
27use Doctrine\DBAL\Schema\TableDiff;
28use Doctrine\DBAL\Schema\UniqueConstraint;
29use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
30use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
31use Doctrine\DBAL\SQL\Parser;
32use Doctrine\DBAL\TransactionIsolationLevel;
33use Doctrine\DBAL\Types;
34use Doctrine\DBAL\Types\Exception\TypeNotFound;
35use Doctrine\DBAL\Types\Type;
36
37use function addcslashes;
38use function array_map;
39use function array_merge;
40use function array_unique;
41use function array_values;
42use function assert;
43use function count;
44use function explode;
45use function implode;
46use function in_array;
47use function is_array;
48use function is_bool;
49use function is_float;
50use function is_int;
51use function is_string;
52use function preg_quote;
53use function preg_replace;
54use function sprintf;
55use function str_contains;
56use function str_replace;
57use function strlen;
58use function strtolower;
59use function strtoupper;
60
61/**
62 * Base class for all DatabasePlatforms. The DatabasePlatforms are the central
63 * point of abstraction of platform-specific behaviors, features and SQL dialects.
64 * They are a passive source of information.
65 *
66 * @todo Remove any unnecessary methods.
67 */
68abstract class AbstractPlatform
69{
70 /** @deprecated */
71 public const CREATE_INDEXES = 1;
72
73 /** @deprecated */
74 public const CREATE_FOREIGNKEYS = 2;
75
76 /** @var string[]|null */
77 protected ?array $doctrineTypeMapping = null;
78
79 /**
80 * Holds the KeywordList instance for the current platform.
81 */
82 protected ?KeywordList $_keywords = null;
83
84 /**
85 * Returns the SQL snippet that declares a boolean column.
86 *
87 * @param mixed[] $column
88 */
89 abstract public function getBooleanTypeDeclarationSQL(array $column): string;
90
91 /**
92 * Returns the SQL snippet that declares a 4 byte integer column.
93 *
94 * @param mixed[] $column
95 */
96 abstract public function getIntegerTypeDeclarationSQL(array $column): string;
97
98 /**
99 * Returns the SQL snippet that declares an 8 byte integer column.
100 *
101 * @param mixed[] $column
102 */
103 abstract public function getBigIntTypeDeclarationSQL(array $column): string;
104
105 /**
106 * Returns the SQL snippet that declares a 2 byte integer column.
107 *
108 * @param mixed[] $column
109 */
110 abstract public function getSmallIntTypeDeclarationSQL(array $column): string;
111
112 /**
113 * Returns the SQL snippet that declares common properties of an integer column.
114 *
115 * @param mixed[] $column
116 */
117 abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column): string;
118
119 /**
120 * Lazy load Doctrine Type Mappings.
121 */
122 abstract protected function initializeDoctrineTypeMappings(): void;
123
124 /**
125 * Initializes Doctrine Type Mappings with the platform defaults
126 * and with all additional type mappings.
127 */
128 private function initializeAllDoctrineTypeMappings(): void
129 {
130 $this->initializeDoctrineTypeMappings();
131
132 foreach (Type::getTypesMap() as $typeName => $className) {
133 foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) {
134 $dbType = strtolower($dbType);
135 $this->doctrineTypeMapping[$dbType] = $typeName;
136 }
137 }
138 }
139
140 /**
141 * Returns the SQL snippet used to declare a column that can
142 * store characters in the ASCII character set
143 *
144 * @param array<string, mixed> $column The column definition.
145 */
146 public function getAsciiStringTypeDeclarationSQL(array $column): string
147 {
148 return $this->getStringTypeDeclarationSQL($column);
149 }
150
151 /**
152 * Returns the SQL snippet used to declare a string column type.
153 *
154 * @param array<string, mixed> $column The column definition.
155 */
156 public function getStringTypeDeclarationSQL(array $column): string
157 {
158 $length = $column['length'] ?? null;
159
160 if (empty($column['fixed'])) {
161 try {
162 return $this->getVarcharTypeDeclarationSQLSnippet($length);
163 } catch (InvalidColumnType $e) {
164 throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e);
165 }
166 }
167
168 return $this->getCharTypeDeclarationSQLSnippet($length);
169 }
170
171 /**
172 * Returns the SQL snippet used to declare a binary string column type.
173 *
174 * @param array<string, mixed> $column The column definition.
175 */
176 public function getBinaryTypeDeclarationSQL(array $column): string
177 {
178 $length = $column['length'] ?? null;
179
180 try {
181 if (empty($column['fixed'])) {
182 return $this->getVarbinaryTypeDeclarationSQLSnippet($length);
183 }
184
185 return $this->getBinaryTypeDeclarationSQLSnippet($length);
186 } catch (InvalidColumnType $e) {
187 throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e);
188 }
189 }
190
191 /**
192 * Returns the SQL snippet to declare a GUID/UUID column.
193 *
194 * By default this maps directly to a CHAR(36) and only maps to more
195 * special datatypes when the underlying databases support this datatype.
196 *
197 * @param array<string, mixed> $column The column definition.
198 */
199 public function getGuidTypeDeclarationSQL(array $column): string
200 {
201 $column['length'] = 36;
202 $column['fixed'] = true;
203
204 return $this->getStringTypeDeclarationSQL($column);
205 }
206
207 /**
208 * Returns the SQL snippet to declare a JSON column.
209 *
210 * By default this maps directly to a CLOB and only maps to more
211 * special datatypes when the underlying databases support this datatype.
212 *
213 * @param mixed[] $column
214 */
215 public function getJsonTypeDeclarationSQL(array $column): string
216 {
217 return $this->getClobTypeDeclarationSQL($column);
218 }
219
220 /**
221 * @param int|null $length The length of the column in characters
222 * or NULL if the length should be omitted.
223 */
224 protected function getCharTypeDeclarationSQLSnippet(?int $length): string
225 {
226 $sql = 'CHAR';
227
228 if ($length !== null) {
229 $sql .= sprintf('(%d)', $length);
230 }
231
232 return $sql;
233 }
234
235 /**
236 * @param int|null $length The length of the column in characters
237 * or NULL if the length should be omitted.
238 */
239 protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string
240 {
241 if ($length === null) {
242 throw ColumnLengthRequired::new($this, 'VARCHAR');
243 }
244
245 return sprintf('VARCHAR(%d)', $length);
246 }
247
248 /**
249 * Returns the SQL snippet used to declare a fixed length binary column type.
250 *
251 * @param int|null $length The length of the column in bytes
252 * or NULL if the length should be omitted.
253 */
254 protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string
255 {
256 $sql = 'BINARY';
257
258 if ($length !== null) {
259 $sql .= sprintf('(%d)', $length);
260 }
261
262 return $sql;
263 }
264
265 /**
266 * Returns the SQL snippet used to declare a variable length binary column type.
267 *
268 * @param int|null $length The length of the column in bytes
269 * or NULL if the length should be omitted.
270 */
271 protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string
272 {
273 if ($length === null) {
274 throw ColumnLengthRequired::new($this, 'VARBINARY');
275 }
276
277 return sprintf('VARBINARY(%d)', $length);
278 }
279
280 /**
281 * Returns the SQL snippet used to declare a CLOB column type.
282 *
283 * @param mixed[] $column
284 */
285 abstract public function getClobTypeDeclarationSQL(array $column): string;
286
287 /**
288 * Returns the SQL Snippet used to declare a BLOB column type.
289 *
290 * @param mixed[] $column
291 */
292 abstract public function getBlobTypeDeclarationSQL(array $column): string;
293
294 /**
295 * Registers a doctrine type to be used in conjunction with a column type of this platform.
296 *
297 * @throws Exception If the type is not found.
298 */
299 public function registerDoctrineTypeMapping(string $dbType, string $doctrineType): void
300 {
301 if ($this->doctrineTypeMapping === null) {
302 $this->initializeAllDoctrineTypeMappings();
303 }
304
305 if (! Types\Type::hasType($doctrineType)) {
306 throw TypeNotFound::new($doctrineType);
307 }
308
309 $dbType = strtolower($dbType);
310 $this->doctrineTypeMapping[$dbType] = $doctrineType;
311 }
312
313 /**
314 * Gets the Doctrine type that is mapped for the given database column type.
315 */
316 public function getDoctrineTypeMapping(string $dbType): string
317 {
318 if ($this->doctrineTypeMapping === null) {
319 $this->initializeAllDoctrineTypeMappings();
320 }
321
322 $dbType = strtolower($dbType);
323
324 if (! isset($this->doctrineTypeMapping[$dbType])) {
325 throw new InvalidArgumentException(sprintf(
326 'Unknown database type "%s" requested, %s may not support it.',
327 $dbType,
328 static::class,
329 ));
330 }
331
332 return $this->doctrineTypeMapping[$dbType];
333 }
334
335 /**
336 * Checks if a database type is currently supported by this platform.
337 */
338 public function hasDoctrineTypeMappingFor(string $dbType): bool
339 {
340 if ($this->doctrineTypeMapping === null) {
341 $this->initializeAllDoctrineTypeMappings();
342 }
343
344 $dbType = strtolower($dbType);
345
346 return isset($this->doctrineTypeMapping[$dbType]);
347 }
348
349 /**
350 * Returns the regular expression operator.
351 */
352 public function getRegexpExpression(): string
353 {
354 throw NotSupported::new(__METHOD__);
355 }
356
357 /**
358 * Returns the SQL snippet to get the length of a text column in characters.
359 *
360 * @param string $string SQL expression producing the string.
361 */
362 public function getLengthExpression(string $string): string
363 {
364 return 'LENGTH(' . $string . ')';
365 }
366
367 /**
368 * Returns the SQL snippet to get the remainder of the operation of division of dividend by divisor.
369 *
370 * @param string $dividend SQL expression producing the dividend.
371 * @param string $divisor SQL expression producing the divisor.
372 */
373 public function getModExpression(string $dividend, string $divisor): string
374 {
375 return 'MOD(' . $dividend . ', ' . $divisor . ')';
376 }
377
378 /**
379 * Returns the SQL snippet to trim a string.
380 *
381 * @param string $str The expression to apply the trim to.
382 * @param TrimMode $mode The position of the trim.
383 * @param string|null $char The char to trim, has to be quoted already. Defaults to space.
384 */
385 public function getTrimExpression(
386 string $str,
387 TrimMode $mode = TrimMode::UNSPECIFIED,
388 ?string $char = null,
389 ): string {
390 $tokens = [];
391
392 switch ($mode) {
393 case TrimMode::UNSPECIFIED:
394 break;
395
396 case TrimMode::LEADING:
397 $tokens[] = 'LEADING';
398 break;
399
400 case TrimMode::TRAILING:
401 $tokens[] = 'TRAILING';
402 break;
403
404 case TrimMode::BOTH:
405 $tokens[] = 'BOTH';
406 break;
407 }
408
409 if ($char !== null) {
410 $tokens[] = $char;
411 }
412
413 if (count($tokens) > 0) {
414 $tokens[] = 'FROM';
415 }
416
417 $tokens[] = $str;
418
419 return sprintf('TRIM(%s)', implode(' ', $tokens));
420 }
421
422 /**
423 * Returns the SQL snippet to get the position of the first occurrence of the substring in the string.
424 *
425 * @param string $string SQL expression producing the string to locate the substring in.
426 * @param string $substring SQL expression producing the substring to locate.
427 * @param string|null $start SQL expression producing the position to start at.
428 * Defaults to the beginning of the string.
429 */
430 abstract public function getLocateExpression(string $string, string $substring, ?string $start = null): string;
431
432 /**
433 * Returns an SQL snippet to get a substring inside the string.
434 *
435 * Note: Not SQL92, but common functionality.
436 *
437 * @param string $string SQL expression producing the string from which a substring should be extracted.
438 * @param string $start SQL expression producing the position to start at,
439 * @param string|null $length SQL expression producing the length of the substring portion to be returned.
440 * By default, the entire substring is returned.
441 */
442 public function getSubstringExpression(string $string, string $start, ?string $length = null): string
443 {
444 if ($length === null) {
445 return sprintf('SUBSTRING(%s FROM %s)', $string, $start);
446 }
447
448 return sprintf('SUBSTRING(%s FROM %s FOR %s)', $string, $start, $length);
449 }
450
451 /**
452 * Returns a SQL snippet to concatenate the given strings.
453 */
454 public function getConcatExpression(string ...$string): string
455 {
456 return implode(' || ', $string);
457 }
458
459 /**
460 * Returns the SQL to calculate the difference in days between the two passed dates.
461 *
462 * Computes diff = date1 - date2.
463 */
464 abstract public function getDateDiffExpression(string $date1, string $date2): string;
465
466 /**
467 * Returns the SQL to add the number of given seconds to a date.
468 *
469 * @param string $date SQL expression producing the date.
470 * @param string $seconds SQL expression producing the number of seconds.
471 */
472 public function getDateAddSecondsExpression(string $date, string $seconds): string
473 {
474 return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND);
475 }
476
477 /**
478 * Returns the SQL to subtract the number of given seconds from a date.
479 *
480 * @param string $date SQL expression producing the date.
481 * @param string $seconds SQL expression producing the number of seconds.
482 */
483 public function getDateSubSecondsExpression(string $date, string $seconds): string
484 {
485 return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND);
486 }
487
488 /**
489 * Returns the SQL to add the number of given minutes to a date.
490 *
491 * @param string $date SQL expression producing the date.
492 * @param string $minutes SQL expression producing the number of minutes.
493 */
494 public function getDateAddMinutesExpression(string $date, string $minutes): string
495 {
496 return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE);
497 }
498
499 /**
500 * Returns the SQL to subtract the number of given minutes from a date.
501 *
502 * @param string $date SQL expression producing the date.
503 * @param string $minutes SQL expression producing the number of minutes.
504 */
505 public function getDateSubMinutesExpression(string $date, string $minutes): string
506 {
507 return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE);
508 }
509
510 /**
511 * Returns the SQL to add the number of given hours to a date.
512 *
513 * @param string $date SQL expression producing the date.
514 * @param string $hours SQL expression producing the number of hours.
515 */
516 public function getDateAddHourExpression(string $date, string $hours): string
517 {
518 return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR);
519 }
520
521 /**
522 * Returns the SQL to subtract the number of given hours to a date.
523 *
524 * @param string $date SQL expression producing the date.
525 * @param string $hours SQL expression producing the number of hours.
526 */
527 public function getDateSubHourExpression(string $date, string $hours): string
528 {
529 return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR);
530 }
531
532 /**
533 * Returns the SQL to add the number of given days to a date.
534 *
535 * @param string $date SQL expression producing the date.
536 * @param string $days SQL expression producing the number of days.
537 */
538 public function getDateAddDaysExpression(string $date, string $days): string
539 {
540 return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY);
541 }
542
543 /**
544 * Returns the SQL to subtract the number of given days to a date.
545 *
546 * @param string $date SQL expression producing the date.
547 * @param string $days SQL expression producing the number of days.
548 */
549 public function getDateSubDaysExpression(string $date, string $days): string
550 {
551 return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY);
552 }
553
554 /**
555 * Returns the SQL to add the number of given weeks to a date.
556 *
557 * @param string $date SQL expression producing the date.
558 * @param string $weeks SQL expression producing the number of weeks.
559 */
560 public function getDateAddWeeksExpression(string $date, string $weeks): string
561 {
562 return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK);
563 }
564
565 /**
566 * Returns the SQL to subtract the number of given weeks from a date.
567 *
568 * @param string $date SQL expression producing the date.
569 * @param string $weeks SQL expression producing the number of weeks.
570 */
571 public function getDateSubWeeksExpression(string $date, string $weeks): string
572 {
573 return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK);
574 }
575
576 /**
577 * Returns the SQL to add the number of given months to a date.
578 *
579 * @param string $date SQL expression producing the date.
580 * @param string $months SQL expression producing the number of months.
581 */
582 public function getDateAddMonthExpression(string $date, string $months): string
583 {
584 return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH);
585 }
586
587 /**
588 * Returns the SQL to subtract the number of given months to a date.
589 *
590 * @param string $date SQL expression producing the date.
591 * @param string $months SQL expression producing the number of months.
592 */
593 public function getDateSubMonthExpression(string $date, string $months): string
594 {
595 return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH);
596 }
597
598 /**
599 * Returns the SQL to add the number of given quarters to a date.
600 *
601 * @param string $date SQL expression producing the date.
602 * @param string $quarters SQL expression producing the number of quarters.
603 */
604 public function getDateAddQuartersExpression(string $date, string $quarters): string
605 {
606 return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER);
607 }
608
609 /**
610 * Returns the SQL to subtract the number of given quarters from a date.
611 *
612 * @param string $date SQL expression producing the date.
613 * @param string $quarters SQL expression producing the number of quarters.
614 */
615 public function getDateSubQuartersExpression(string $date, string $quarters): string
616 {
617 return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER);
618 }
619
620 /**
621 * Returns the SQL to add the number of given years to a date.
622 *
623 * @param string $date SQL expression producing the date.
624 * @param string $years SQL expression producing the number of years.
625 */
626 public function getDateAddYearsExpression(string $date, string $years): string
627 {
628 return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR);
629 }
630
631 /**
632 * Returns the SQL to subtract the number of given years from a date.
633 *
634 * @param string $date SQL expression producing the date.
635 * @param string $years SQL expression producing the number of years.
636 */
637 public function getDateSubYearsExpression(string $date, string $years): string
638 {
639 return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR);
640 }
641
642 /**
643 * Returns the SQL for a date arithmetic expression.
644 *
645 * @param string $date SQL expression representing a date to perform the arithmetic operation on.
646 * @param string $operator The arithmetic operator (+ or -).
647 * @param string $interval SQL expression representing the value of the interval that shall be calculated
648 * into the date.
649 * @param DateIntervalUnit $unit The unit of the interval that shall be calculated into the date.
650 */
651 abstract protected function getDateArithmeticIntervalExpression(
652 string $date,
653 string $operator,
654 string $interval,
655 DateIntervalUnit $unit,
656 ): string;
657
658 /**
659 * Generates the SQL expression which represents the given date interval multiplied by a number
660 *
661 * @param string $interval SQL expression describing the interval value
662 * @param int $multiplier Interval multiplier
663 */
664 protected function multiplyInterval(string $interval, int $multiplier): string
665 {
666 return sprintf('(%s * %d)', $interval, $multiplier);
667 }
668
669 /**
670 * Returns the SQL bit AND comparison expression.
671 *
672 * @param string $value1 SQL expression producing the first value.
673 * @param string $value2 SQL expression producing the second value.
674 */
675 public function getBitAndComparisonExpression(string $value1, string $value2): string
676 {
677 return '(' . $value1 . ' & ' . $value2 . ')';
678 }
679
680 /**
681 * Returns the SQL bit OR comparison expression.
682 *
683 * @param string $value1 SQL expression producing the first value.
684 * @param string $value2 SQL expression producing the second value.
685 */
686 public function getBitOrComparisonExpression(string $value1, string $value2): string
687 {
688 return '(' . $value1 . ' | ' . $value2 . ')';
689 }
690
691 /**
692 * Returns the SQL expression which represents the currently selected database.
693 */
694 abstract public function getCurrentDatabaseExpression(): string;
695
696 /**
697 * Honors that some SQL vendors such as MsSql use table hints for locking instead of the
698 * ANSI SQL FOR UPDATE specification.
699 *
700 * @param string $fromClause The FROM clause to append the hint for the given lock mode to
701 */
702 public function appendLockHint(string $fromClause, LockMode $lockMode): string
703 {
704 return $fromClause;
705 }
706
707 /**
708 * Returns the SQL snippet to drop an existing table.
709 */
710 public function getDropTableSQL(string $table): string
711 {
712 return 'DROP TABLE ' . $table;
713 }
714
715 /**
716 * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction.
717 */
718 public function getDropTemporaryTableSQL(string $table): string
719 {
720 return $this->getDropTableSQL($table);
721 }
722
723 /**
724 * Returns the SQL to drop an index from a table.
725 */
726 public function getDropIndexSQL(string $name, string $table): string
727 {
728 return 'DROP INDEX ' . $name;
729 }
730
731 /**
732 * Returns the SQL to drop a constraint.
733 *
734 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
735 */
736 protected function getDropConstraintSQL(string $name, string $table): string
737 {
738 return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name;
739 }
740
741 /**
742 * Returns the SQL to drop a foreign key.
743 */
744 public function getDropForeignKeySQL(string $foreignKey, string $table): string
745 {
746 return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
747 }
748
749 /**
750 * Returns the SQL to drop a unique constraint.
751 */
752 public function getDropUniqueConstraintSQL(string $name, string $tableName): string
753 {
754 return $this->getDropConstraintSQL($name, $tableName);
755 }
756
757 /**
758 * Returns the SQL statement(s) to create a table with the specified name, columns and constraints
759 * on this platform.
760 *
761 * @return list<string> The list of SQL statements.
762 */
763 public function getCreateTableSQL(Table $table): array
764 {
765 return $this->buildCreateTableSQL($table, true);
766 }
767
768 public function createSelectSQLBuilder(): SelectSQLBuilder
769 {
770 return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', 'SKIP LOCKED');
771 }
772
773 /**
774 * @internal
775 *
776 * @return list<string>
777 */
778 final protected function getCreateTableWithoutForeignKeysSQL(Table $table): array
779 {
780 return $this->buildCreateTableSQL($table, false);
781 }
782
783 /** @return list<string> */
784 private function buildCreateTableSQL(Table $table, bool $createForeignKeys): array
785 {
786 if (count($table->getColumns()) === 0) {
787 throw NoColumnsSpecifiedForTable::new($table->getName());
788 }
789
790 $tableName = $table->getQuotedName($this);
791 $options = $table->getOptions();
792 $options['uniqueConstraints'] = [];
793 $options['indexes'] = [];
794 $options['primary'] = [];
795
796 foreach ($table->getIndexes() as $index) {
797 if (! $index->isPrimary()) {
798 $options['indexes'][$index->getQuotedName($this)] = $index;
799
800 continue;
801 }
802
803 $options['primary'] = $index->getQuotedColumns($this);
804 $options['primary_index'] = $index;
805 }
806
807 foreach ($table->getUniqueConstraints() as $uniqueConstraint) {
808 $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint;
809 }
810
811 if ($createForeignKeys) {
812 $options['foreignKeys'] = [];
813
814 foreach ($table->getForeignKeys() as $fkConstraint) {
815 $options['foreignKeys'][] = $fkConstraint;
816 }
817 }
818
819 $columnSql = [];
820 $columns = [];
821
822 foreach ($table->getColumns() as $column) {
823 $columnData = $this->columnToArray($column);
824
825 if (in_array($column->getName(), $options['primary'], true)) {
826 $columnData['primary'] = true;
827 }
828
829 $columns[] = $columnData;
830 }
831
832 $sql = $this->_getCreateTableSQL($tableName, $columns, $options);
833
834 if ($this->supportsCommentOnStatement()) {
835 if ($table->hasOption('comment')) {
836 $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment'));
837 }
838
839 foreach ($table->getColumns() as $column) {
840 $comment = $column->getComment();
841
842 if ($comment === '') {
843 continue;
844 }
845
846 $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment);
847 }
848 }
849
850 return array_merge($sql, $columnSql);
851 }
852
853 /**
854 * @param array<Table> $tables
855 *
856 * @return list<string>
857 */
858 public function getCreateTablesSQL(array $tables): array
859 {
860 $sql = [];
861
862 foreach ($tables as $table) {
863 $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table));
864 }
865
866 foreach ($tables as $table) {
867 foreach ($table->getForeignKeys() as $foreignKey) {
868 $sql[] = $this->getCreateForeignKeySQL(
869 $foreignKey,
870 $table->getQuotedName($this),
871 );
872 }
873 }
874
875 return $sql;
876 }
877
878 /**
879 * @param array<Table> $tables
880 *
881 * @return list<string>
882 */
883 public function getDropTablesSQL(array $tables): array
884 {
885 $sql = [];
886
887 foreach ($tables as $table) {
888 foreach ($table->getForeignKeys() as $foreignKey) {
889 $sql[] = $this->getDropForeignKeySQL(
890 $foreignKey->getQuotedName($this),
891 $table->getQuotedName($this),
892 );
893 }
894 }
895
896 foreach ($tables as $table) {
897 $sql[] = $this->getDropTableSQL($table->getQuotedName($this));
898 }
899
900 return $sql;
901 }
902
903 protected function getCommentOnTableSQL(string $tableName, string $comment): string
904 {
905 $tableName = new Identifier($tableName);
906
907 return sprintf(
908 'COMMENT ON TABLE %s IS %s',
909 $tableName->getQuotedName($this),
910 $this->quoteStringLiteral($comment),
911 );
912 }
913
914 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
915 public function getCommentOnColumnSQL(string $tableName, string $columnName, string $comment): string
916 {
917 $tableName = new Identifier($tableName);
918 $columnName = new Identifier($columnName);
919
920 return sprintf(
921 'COMMENT ON COLUMN %s.%s IS %s',
922 $tableName->getQuotedName($this),
923 $columnName->getQuotedName($this),
924 $this->quoteStringLiteral($comment),
925 );
926 }
927
928 /**
929 * Returns the SQL to create inline comment on a column.
930 *
931 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
932 */
933 public function getInlineColumnCommentSQL(string $comment): string
934 {
935 if (! $this->supportsInlineColumnComments()) {
936 throw NotSupported::new(__METHOD__);
937 }
938
939 return 'COMMENT ' . $this->quoteStringLiteral($comment);
940 }
941
942 /**
943 * Returns the SQL used to create a table.
944 *
945 * @param mixed[][] $columns
946 * @param mixed[] $options
947 *
948 * @return array<int, string>
949 */
950 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
951 {
952 $columnListSql = $this->getColumnDeclarationListSQL($columns);
953
954 if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
955 foreach ($options['uniqueConstraints'] as $definition) {
956 $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition);
957 }
958 }
959
960 if (isset($options['primary']) && ! empty($options['primary'])) {
961 $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
962 }
963
964 if (isset($options['indexes']) && ! empty($options['indexes'])) {
965 foreach ($options['indexes'] as $index => $definition) {
966 $columnListSql .= ', ' . $this->getIndexDeclarationSQL($definition);
967 }
968 }
969
970 $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql;
971 $check = $this->getCheckDeclarationSQL($columns);
972
973 if (! empty($check)) {
974 $query .= ', ' . $check;
975 }
976
977 $query .= ')';
978
979 $sql = [$query];
980
981 if (isset($options['foreignKeys'])) {
982 foreach ($options['foreignKeys'] as $definition) {
983 $sql[] = $this->getCreateForeignKeySQL($definition, $name);
984 }
985 }
986
987 return $sql;
988 }
989
990 public function getCreateTemporaryTableSnippetSQL(): string
991 {
992 return 'CREATE TEMPORARY TABLE';
993 }
994
995 /**
996 * Generates SQL statements that can be used to apply the diff.
997 *
998 * @return list<string>
999 */
1000 public function getAlterSchemaSQL(SchemaDiff $diff): array
1001 {
1002 $sql = [];
1003
1004 if ($this->supportsSchemas()) {
1005 foreach ($diff->getCreatedSchemas() as $schema) {
1006 $sql[] = $this->getCreateSchemaSQL($schema);
1007 }
1008 }
1009
1010 if ($this->supportsSequences()) {
1011 foreach ($diff->getAlteredSequences() as $sequence) {
1012 $sql[] = $this->getAlterSequenceSQL($sequence);
1013 }
1014
1015 foreach ($diff->getDroppedSequences() as $sequence) {
1016 $sql[] = $this->getDropSequenceSQL($sequence->getQuotedName($this));
1017 }
1018
1019 foreach ($diff->getCreatedSequences() as $sequence) {
1020 $sql[] = $this->getCreateSequenceSQL($sequence);
1021 }
1022 }
1023
1024 $sql = array_merge(
1025 $sql,
1026 $this->getCreateTablesSQL(
1027 $diff->getCreatedTables(),
1028 ),
1029 $this->getDropTablesSQL(
1030 $diff->getDroppedTables(),
1031 ),
1032 );
1033
1034 foreach ($diff->getAlteredTables() as $tableDiff) {
1035 $sql = array_merge($sql, $this->getAlterTableSQL($tableDiff));
1036 }
1037
1038 return $sql;
1039 }
1040
1041 /**
1042 * Returns the SQL to create a sequence on this platform.
1043 */
1044 public function getCreateSequenceSQL(Sequence $sequence): string
1045 {
1046 throw NotSupported::new(__METHOD__);
1047 }
1048
1049 /**
1050 * Returns the SQL to change a sequence on this platform.
1051 */
1052 public function getAlterSequenceSQL(Sequence $sequence): string
1053 {
1054 throw NotSupported::new(__METHOD__);
1055 }
1056
1057 /**
1058 * Returns the SQL snippet to drop an existing sequence.
1059 */
1060 public function getDropSequenceSQL(string $name): string
1061 {
1062 if (! $this->supportsSequences()) {
1063 throw NotSupported::new(__METHOD__);
1064 }
1065
1066 return 'DROP SEQUENCE ' . $name;
1067 }
1068
1069 /**
1070 * Returns the SQL to create an index on a table on this platform.
1071 */
1072 public function getCreateIndexSQL(Index $index, string $table): string
1073 {
1074 $name = $index->getQuotedName($this);
1075 $columns = $index->getColumns();
1076
1077 if (count($columns) === 0) {
1078 throw new InvalidArgumentException(sprintf(
1079 'Incomplete or invalid index definition %s on table %s',
1080 $name,
1081 $table,
1082 ));
1083 }
1084
1085 if ($index->isPrimary()) {
1086 return $this->getCreatePrimaryKeySQL($index, $table);
1087 }
1088
1089 $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
1090 $query .= ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index);
1091
1092 return $query;
1093 }
1094
1095 /**
1096 * Adds condition for partial index.
1097 */
1098 protected function getPartialIndexSQL(Index $index): string
1099 {
1100 if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
1101 return ' WHERE ' . $index->getOption('where');
1102 }
1103
1104 return '';
1105 }
1106
1107 /**
1108 * Adds additional flags for index generation.
1109 */
1110 protected function getCreateIndexSQLFlags(Index $index): string
1111 {
1112 return $index->isUnique() ? 'UNIQUE ' : '';
1113 }
1114
1115 /**
1116 * Returns the SQL to create an unnamed primary key constraint.
1117 */
1118 public function getCreatePrimaryKeySQL(Index $index, string $table): string
1119 {
1120 return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . implode(', ', $index->getQuotedColumns($this)) . ')';
1121 }
1122
1123 /**
1124 * Returns the SQL to create a named schema.
1125 */
1126 public function getCreateSchemaSQL(string $schemaName): string
1127 {
1128 if (! $this->supportsSchemas()) {
1129 throw NotSupported::new(__METHOD__);
1130 }
1131
1132 return 'CREATE SCHEMA ' . $schemaName;
1133 }
1134
1135 /**
1136 * Returns the SQL to create a unique constraint on a table on this platform.
1137 */
1138 public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string
1139 {
1140 return 'ALTER TABLE ' . $tableName . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this) . ' UNIQUE'
1141 . ' (' . implode(', ', $constraint->getQuotedColumns($this)) . ')';
1142 }
1143
1144 /**
1145 * Returns the SQL snippet to drop a schema.
1146 */
1147 public function getDropSchemaSQL(string $schemaName): string
1148 {
1149 if (! $this->supportsSchemas()) {
1150 throw NotSupported::new(__METHOD__);
1151 }
1152
1153 return 'DROP SCHEMA ' . $schemaName;
1154 }
1155
1156 /**
1157 * Quotes a string so that it can be safely used as a table or column name,
1158 * even if it is a reserved word of the platform. This also detects identifier
1159 * chains separated by dot and quotes them independently.
1160 *
1161 * NOTE: Just because you CAN use quoted identifiers doesn't mean
1162 * you SHOULD use them. In general, they end up causing way more
1163 * problems than they solve.
1164 *
1165 * @param string $identifier The identifier name to be quoted.
1166 *
1167 * @return string The quoted identifier string.
1168 */
1169 public function quoteIdentifier(string $identifier): string
1170 {
1171 if (str_contains($identifier, '.')) {
1172 $parts = array_map($this->quoteSingleIdentifier(...), explode('.', $identifier));
1173
1174 return implode('.', $parts);
1175 }
1176
1177 return $this->quoteSingleIdentifier($identifier);
1178 }
1179
1180 /**
1181 * Quotes a single identifier (no dot chain separation).
1182 *
1183 * @param string $str The identifier name to be quoted.
1184 *
1185 * @return string The quoted identifier string.
1186 */
1187 public function quoteSingleIdentifier(string $str): string
1188 {
1189 return '"' . str_replace('"', '""', $str) . '"';
1190 }
1191
1192 /**
1193 * Returns the SQL to create a new foreign key.
1194 *
1195 * @param ForeignKeyConstraint $foreignKey The foreign key constraint.
1196 * @param string $table The name of the table on which the foreign key is to be created.
1197 */
1198 public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, string $table): string
1199 {
1200 return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey);
1201 }
1202
1203 /**
1204 * Gets the SQL statements for altering an existing table.
1205 *
1206 * This method returns an array of SQL statements, since some platforms need several statements.
1207 *
1208 * @return list<string>
1209 */
1210 abstract public function getAlterTableSQL(TableDiff $diff): array;
1211
1212 public function getRenameTableSQL(string $oldName, string $newName): string
1213 {
1214 return sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName);
1215 }
1216
1217 /** @return list<string> */
1218 protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array
1219 {
1220 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
1221
1222 $sql = [];
1223
1224 foreach ($diff->getDroppedForeignKeys() as $foreignKey) {
1225 $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL);
1226 }
1227
1228 foreach ($diff->getModifiedForeignKeys() as $foreignKey) {
1229 $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL);
1230 }
1231
1232 foreach ($diff->getDroppedIndexes() as $index) {
1233 $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL);
1234 }
1235
1236 foreach ($diff->getModifiedIndexes() as $index) {
1237 $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL);
1238 }
1239
1240 return $sql;
1241 }
1242
1243 /** @return list<string> */
1244 protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array
1245 {
1246 $sql = [];
1247
1248 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
1249
1250 foreach ($diff->getAddedForeignKeys() as $foreignKey) {
1251 $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL);
1252 }
1253
1254 foreach ($diff->getModifiedForeignKeys() as $foreignKey) {
1255 $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL);
1256 }
1257
1258 foreach ($diff->getAddedIndexes() as $index) {
1259 $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL);
1260 }
1261
1262 foreach ($diff->getModifiedIndexes() as $index) {
1263 $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL);
1264 }
1265
1266 foreach ($diff->getRenamedIndexes() as $oldIndexName => $index) {
1267 $oldIndexName = new Identifier($oldIndexName);
1268 $sql = array_merge(
1269 $sql,
1270 $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableNameSQL),
1271 );
1272 }
1273
1274 return $sql;
1275 }
1276
1277 /**
1278 * Returns the SQL for renaming an index on a table.
1279 *
1280 * @param string $oldIndexName The name of the index to rename from.
1281 * @param Index $index The definition of the index to rename to.
1282 * @param string $tableName The table to rename the given index on.
1283 *
1284 * @return list<string> The sequence of SQL statements for renaming the given index.
1285 */
1286 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
1287 {
1288 return [
1289 $this->getDropIndexSQL($oldIndexName, $tableName),
1290 $this->getCreateIndexSQL($index, $tableName),
1291 ];
1292 }
1293
1294 /**
1295 * Gets declaration of a number of columns in bulk.
1296 *
1297 * @param mixed[][] $columns A multidimensional array.
1298 * The first dimension determines the ordinal position of the column,
1299 * while the second dimension is keyed with the name of the properties
1300 * of the column being declared as array indexes. Currently, the types
1301 * of supported column properties are as follows:
1302 *
1303 * length
1304 * Integer value that determines the maximum length of the text
1305 * column. If this argument is missing the column should be
1306 * declared to have the longest length allowed by the DBMS.
1307 * default
1308 * Text value to be used as default for this column.
1309 * notnull
1310 * Boolean flag that indicates whether this column is constrained
1311 * to not be set to null.
1312 * charset
1313 * Text value with the default CHARACTER SET for this column.
1314 * collation
1315 * Text value with the default COLLATION for this column.
1316 */
1317 public function getColumnDeclarationListSQL(array $columns): string
1318 {
1319 $declarations = [];
1320
1321 foreach ($columns as $column) {
1322 $declarations[] = $this->getColumnDeclarationSQL($column['name'], $column);
1323 }
1324
1325 return implode(', ', $declarations);
1326 }
1327
1328 /**
1329 * Obtains DBMS specific SQL code portion needed to declare a generic type
1330 * column to be used in statements like CREATE TABLE.
1331 *
1332 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1333 *
1334 * @param string $name The name the column to be declared.
1335 * @param mixed[] $column An associative array with the name of the properties
1336 * of the column being declared as array indexes. Currently, the types
1337 * of supported column properties are as follows:
1338 *
1339 * length
1340 * Integer value that determines the maximum length of the text
1341 * column. If this argument is missing the column should be
1342 * declared to have the longest length allowed by the DBMS.
1343 * default
1344 * Text value to be used as default for this column.
1345 * notnull
1346 * Boolean flag that indicates whether this column is constrained
1347 * to not be set to null.
1348 * charset
1349 * Text value with the default CHARACTER SET for this column.
1350 * collation
1351 * Text value with the default COLLATION for this column.
1352 * columnDefinition
1353 * a string that defines the complete column
1354 *
1355 * @return string DBMS specific SQL code portion that should be used to declare the column.
1356 */
1357 public function getColumnDeclarationSQL(string $name, array $column): string
1358 {
1359 if (isset($column['columnDefinition'])) {
1360 $declaration = $column['columnDefinition'];
1361 } else {
1362 $default = $this->getDefaultValueDeclarationSQL($column);
1363
1364 $charset = ! empty($column['charset']) ?
1365 ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : '';
1366
1367 $collation = ! empty($column['collation']) ?
1368 ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : '';
1369
1370 $notnull = ! empty($column['notnull']) ? ' NOT NULL' : '';
1371
1372 $typeDecl = $column['type']->getSQLDeclaration($column, $this);
1373 $declaration = $typeDecl . $charset . $default . $notnull . $collation;
1374
1375 if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') {
1376 $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']);
1377 }
1378 }
1379
1380 return $name . ' ' . $declaration;
1381 }
1382
1383 /**
1384 * Returns the SQL snippet that declares a floating point column of arbitrary precision.
1385 *
1386 * @param mixed[] $column
1387 */
1388 public function getDecimalTypeDeclarationSQL(array $column): string
1389 {
1390 if (! isset($column['precision'])) {
1391 $e = ColumnPrecisionRequired::new();
1392 } elseif (! isset($column['scale'])) {
1393 $e = ColumnScaleRequired::new();
1394 } else {
1395 $e = null;
1396 }
1397
1398 if ($e !== null) {
1399 throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e);
1400 }
1401
1402 return 'NUMERIC(' . $column['precision'] . ', ' . $column['scale'] . ')';
1403 }
1404
1405 /**
1406 * Obtains DBMS specific SQL code portion needed to set a default value
1407 * declaration to be used in statements like CREATE TABLE.
1408 *
1409 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1410 *
1411 * @param mixed[] $column The column definition array.
1412 *
1413 * @return string DBMS specific SQL code portion needed to set a default value.
1414 */
1415 public function getDefaultValueDeclarationSQL(array $column): string
1416 {
1417 if (! isset($column['default'])) {
1418 return empty($column['notnull']) ? ' DEFAULT NULL' : '';
1419 }
1420
1421 $default = $column['default'];
1422
1423 if (! isset($column['type'])) {
1424 return " DEFAULT '" . $default . "'";
1425 }
1426
1427 $type = $column['type'];
1428
1429 if ($type instanceof Types\PhpIntegerMappingType) {
1430 return ' DEFAULT ' . $default;
1431 }
1432
1433 if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) {
1434 return ' DEFAULT ' . $this->getCurrentTimestampSQL();
1435 }
1436
1437 if ($type instanceof Types\PhpTimeMappingType && $default === $this->getCurrentTimeSQL()) {
1438 return ' DEFAULT ' . $this->getCurrentTimeSQL();
1439 }
1440
1441 if ($type instanceof Types\PhpDateMappingType && $default === $this->getCurrentDateSQL()) {
1442 return ' DEFAULT ' . $this->getCurrentDateSQL();
1443 }
1444
1445 if ($type instanceof Types\BooleanType) {
1446 return ' DEFAULT ' . $this->convertBooleans($default);
1447 }
1448
1449 if (is_int($default) || is_float($default)) {
1450 return ' DEFAULT ' . $default;
1451 }
1452
1453 return ' DEFAULT ' . $this->quoteStringLiteral($default);
1454 }
1455
1456 /**
1457 * Obtains DBMS specific SQL code portion needed to set a CHECK constraint
1458 * declaration to be used in statements like CREATE TABLE.
1459 *
1460 * @param string[]|mixed[][] $definition The check definition.
1461 *
1462 * @return string DBMS specific SQL code portion needed to set a CHECK constraint.
1463 */
1464 public function getCheckDeclarationSQL(array $definition): string
1465 {
1466 $constraints = [];
1467 foreach ($definition as $def) {
1468 if (is_string($def)) {
1469 $constraints[] = 'CHECK (' . $def . ')';
1470 } else {
1471 if (isset($def['min'])) {
1472 $constraints[] = 'CHECK (' . $def['name'] . ' >= ' . $def['min'] . ')';
1473 }
1474
1475 if (! isset($def['max'])) {
1476 continue;
1477 }
1478
1479 $constraints[] = 'CHECK (' . $def['name'] . ' <= ' . $def['max'] . ')';
1480 }
1481 }
1482
1483 return implode(', ', $constraints);
1484 }
1485
1486 /**
1487 * Obtains DBMS specific SQL code portion needed to set a unique
1488 * constraint declaration to be used in statements like CREATE TABLE.
1489 *
1490 * @param UniqueConstraint $constraint The unique constraint definition.
1491 *
1492 * @return string DBMS specific SQL code portion needed to set a constraint.
1493 */
1494 public function getUniqueConstraintDeclarationSQL(UniqueConstraint $constraint): string
1495 {
1496 $columns = $constraint->getColumns();
1497
1498 if (count($columns) === 0) {
1499 throw new InvalidArgumentException('Incomplete definition. "columns" required.');
1500 }
1501
1502 $chunks = ['CONSTRAINT'];
1503
1504 if ($constraint->getName() !== '') {
1505 $chunks[] = $constraint->getQuotedName($this);
1506 }
1507
1508 $chunks[] = 'UNIQUE';
1509
1510 if ($constraint->hasFlag('clustered')) {
1511 $chunks[] = 'CLUSTERED';
1512 }
1513
1514 $chunks[] = sprintf('(%s)', implode(', ', $columns));
1515
1516 return implode(' ', $chunks);
1517 }
1518
1519 /**
1520 * Obtains DBMS specific SQL code portion needed to set an index
1521 * declaration to be used in statements like CREATE TABLE.
1522 *
1523 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1524 *
1525 * @param Index $index The index definition.
1526 *
1527 * @return string DBMS specific SQL code portion needed to set an index.
1528 */
1529 public function getIndexDeclarationSQL(Index $index): string
1530 {
1531 $columns = $index->getColumns();
1532
1533 if (count($columns) === 0) {
1534 throw new InvalidArgumentException('Incomplete definition. "columns" required.');
1535 }
1536
1537 return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $index->getQuotedName($this)
1538 . ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index);
1539 }
1540
1541 /**
1542 * Some vendors require temporary table names to be qualified specially.
1543 */
1544 public function getTemporaryTableName(string $tableName): string
1545 {
1546 return $tableName;
1547 }
1548
1549 /**
1550 * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
1551 * of a column declaration to be used in statements like CREATE TABLE.
1552 *
1553 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1554 *
1555 * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
1556 * of a column declaration.
1557 */
1558 public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey): string
1559 {
1560 $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey);
1561 $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey);
1562
1563 return $sql;
1564 }
1565
1566 /**
1567 * Returns the FOREIGN KEY query section dealing with non-standard options
1568 * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
1569 *
1570 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1571 *
1572 * @param ForeignKeyConstraint $foreignKey The foreign key definition.
1573 */
1574 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
1575 {
1576 $query = '';
1577 if ($foreignKey->hasOption('onUpdate')) {
1578 $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate'));
1579 }
1580
1581 if ($foreignKey->hasOption('onDelete')) {
1582 $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
1583 }
1584
1585 return $query;
1586 }
1587
1588 /**
1589 * Returns the given referential action in uppercase if valid, otherwise throws an exception.
1590 *
1591 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1592 *
1593 * @param string $action The foreign key referential action.
1594 */
1595 public function getForeignKeyReferentialActionSQL(string $action): string
1596 {
1597 $upper = strtoupper($action);
1598
1599 return match ($upper) {
1600 'CASCADE',
1601 'SET NULL',
1602 'NO ACTION',
1603 'RESTRICT',
1604 'SET DEFAULT' => $upper,
1605 default => throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $upper)),
1606 };
1607 }
1608
1609 /**
1610 * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
1611 * of a column declaration to be used in statements like CREATE TABLE.
1612 */
1613 public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey): string
1614 {
1615 $sql = '';
1616 if ($foreignKey->getName() !== '') {
1617 $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
1618 }
1619
1620 $sql .= 'FOREIGN KEY (';
1621
1622 if (count($foreignKey->getLocalColumns()) === 0) {
1623 throw new InvalidArgumentException('Incomplete definition. "local" required.');
1624 }
1625
1626 if (count($foreignKey->getForeignColumns()) === 0) {
1627 throw new InvalidArgumentException('Incomplete definition. "foreign" required.');
1628 }
1629
1630 if (strlen($foreignKey->getForeignTableName()) === 0) {
1631 throw new InvalidArgumentException('Incomplete definition. "foreignTable" required.');
1632 }
1633
1634 return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this))
1635 . ') REFERENCES '
1636 . $foreignKey->getQuotedForeignTableName($this) . ' ('
1637 . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')';
1638 }
1639
1640 /**
1641 * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET
1642 * of a column declaration to be used in statements like CREATE TABLE.
1643 *
1644 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1645 *
1646 * @param string $charset The name of the charset.
1647 *
1648 * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
1649 * of a column declaration.
1650 */
1651 public function getColumnCharsetDeclarationSQL(string $charset): string
1652 {
1653 return '';
1654 }
1655
1656 /**
1657 * Obtains DBMS specific SQL code portion needed to set the COLLATION
1658 * of a column declaration to be used in statements like CREATE TABLE.
1659 *
1660 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1661 *
1662 * @param string $collation The name of the collation.
1663 *
1664 * @return string DBMS specific SQL code portion needed to set the COLLATION
1665 * of a column declaration.
1666 */
1667 public function getColumnCollationDeclarationSQL(string $collation): string
1668 {
1669 return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : '';
1670 }
1671
1672 /**
1673 * Some platforms need the boolean values to be converted.
1674 *
1675 * The default conversion in this implementation converts to integers (false => 0, true => 1).
1676 *
1677 * Note: if the input is not a boolean the original input might be returned.
1678 *
1679 * There are two contexts when converting booleans: Literals and Prepared Statements.
1680 * This method should handle the literal case
1681 *
1682 * @param mixed $item A boolean or an array of them.
1683 *
1684 * @return mixed A boolean database value or an array of them.
1685 */
1686 public function convertBooleans(mixed $item): mixed
1687 {
1688 if (is_array($item)) {
1689 foreach ($item as $k => $value) {
1690 if (! is_bool($value)) {
1691 continue;
1692 }
1693
1694 $item[$k] = (int) $value;
1695 }
1696 } elseif (is_bool($item)) {
1697 $item = (int) $item;
1698 }
1699
1700 return $item;
1701 }
1702
1703 /**
1704 * Some platforms have boolean literals that needs to be correctly converted
1705 *
1706 * The default conversion tries to convert value into bool "(bool)$item"
1707 *
1708 * @param T $item
1709 *
1710 * @return (T is null ? null : bool)
1711 *
1712 * @template T
1713 */
1714 public function convertFromBoolean(mixed $item): ?bool
1715 {
1716 if ($item === null) {
1717 return null;
1718 }
1719
1720 return (bool) $item;
1721 }
1722
1723 /**
1724 * This method should handle the prepared statements case. When there is no
1725 * distinction, it's OK to use the same method.
1726 *
1727 * Note: if the input is not a boolean the original input might be returned.
1728 *
1729 * @param mixed $item A boolean or an array of them.
1730 *
1731 * @return mixed A boolean database value or an array of them.
1732 */
1733 public function convertBooleansToDatabaseValue(mixed $item): mixed
1734 {
1735 return $this->convertBooleans($item);
1736 }
1737
1738 /**
1739 * Returns the SQL specific for the platform to get the current date.
1740 */
1741 public function getCurrentDateSQL(): string
1742 {
1743 return 'CURRENT_DATE';
1744 }
1745
1746 /**
1747 * Returns the SQL specific for the platform to get the current time.
1748 */
1749 public function getCurrentTimeSQL(): string
1750 {
1751 return 'CURRENT_TIME';
1752 }
1753
1754 /**
1755 * Returns the SQL specific for the platform to get the current timestamp
1756 */
1757 public function getCurrentTimestampSQL(): string
1758 {
1759 return 'CURRENT_TIMESTAMP';
1760 }
1761
1762 /**
1763 * Returns the SQL for a given transaction isolation level Connection constant.
1764 */
1765 protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string
1766 {
1767 return match ($level) {
1768 TransactionIsolationLevel::READ_UNCOMMITTED => 'READ UNCOMMITTED',
1769 TransactionIsolationLevel::READ_COMMITTED => 'READ COMMITTED',
1770 TransactionIsolationLevel::REPEATABLE_READ => 'REPEATABLE READ',
1771 TransactionIsolationLevel::SERIALIZABLE => 'SERIALIZABLE',
1772 };
1773 }
1774
1775 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
1776 public function getListDatabasesSQL(): string
1777 {
1778 throw NotSupported::new(__METHOD__);
1779 }
1780
1781 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
1782 public function getListSequencesSQL(string $database): string
1783 {
1784 throw NotSupported::new(__METHOD__);
1785 }
1786
1787 /**
1788 * Returns the SQL to list all views of a database or user.
1789 *
1790 * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
1791 */
1792 abstract public function getListViewsSQL(string $database): string;
1793
1794 public function getCreateViewSQL(string $name, string $sql): string
1795 {
1796 return 'CREATE VIEW ' . $name . ' AS ' . $sql;
1797 }
1798
1799 public function getDropViewSQL(string $name): string
1800 {
1801 return 'DROP VIEW ' . $name;
1802 }
1803
1804 public function getSequenceNextValSQL(string $sequence): string
1805 {
1806 throw NotSupported::new(__METHOD__);
1807 }
1808
1809 /**
1810 * Returns the SQL to create a new database.
1811 *
1812 * @param string $name The name of the database that should be created.
1813 */
1814 public function getCreateDatabaseSQL(string $name): string
1815 {
1816 return 'CREATE DATABASE ' . $name;
1817 }
1818
1819 /**
1820 * Returns the SQL snippet to drop an existing database.
1821 *
1822 * @param string $name The name of the database that should be dropped.
1823 */
1824 public function getDropDatabaseSQL(string $name): string
1825 {
1826 return 'DROP DATABASE ' . $name;
1827 }
1828
1829 /**
1830 * Returns the SQL to set the transaction isolation level.
1831 */
1832 abstract public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string;
1833
1834 /**
1835 * Obtains DBMS specific SQL to be used to create datetime columns in
1836 * statements like CREATE TABLE.
1837 *
1838 * @param mixed[] $column
1839 */
1840 abstract public function getDateTimeTypeDeclarationSQL(array $column): string;
1841
1842 /**
1843 * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns.
1844 *
1845 * @param mixed[] $column
1846 */
1847 public function getDateTimeTzTypeDeclarationSQL(array $column): string
1848 {
1849 return $this->getDateTimeTypeDeclarationSQL($column);
1850 }
1851
1852 /**
1853 * Obtains DBMS specific SQL to be used to create date columns in statements
1854 * like CREATE TABLE.
1855 *
1856 * @param mixed[] $column
1857 */
1858 abstract public function getDateTypeDeclarationSQL(array $column): string;
1859
1860 /**
1861 * Obtains DBMS specific SQL to be used to create time columns in statements
1862 * like CREATE TABLE.
1863 *
1864 * @param mixed[] $column
1865 */
1866 abstract public function getTimeTypeDeclarationSQL(array $column): string;
1867
1868 /** @param mixed[] $column */
1869 public function getFloatDeclarationSQL(array $column): string
1870 {
1871 return 'DOUBLE PRECISION';
1872 }
1873
1874 /**
1875 * Gets the default transaction isolation level of the platform.
1876 *
1877 * @return TransactionIsolationLevel The default isolation level.
1878 */
1879 public function getDefaultTransactionIsolationLevel(): TransactionIsolationLevel
1880 {
1881 return TransactionIsolationLevel::READ_COMMITTED;
1882 }
1883
1884 /* supports*() methods */
1885
1886 /**
1887 * Whether the platform supports sequences.
1888 */
1889 public function supportsSequences(): bool
1890 {
1891 return false;
1892 }
1893
1894 /**
1895 * Whether the platform supports identity columns.
1896 *
1897 * Identity columns are columns that receive an auto-generated value from the
1898 * database on insert of a row.
1899 */
1900 public function supportsIdentityColumns(): bool
1901 {
1902 return false;
1903 }
1904
1905 /**
1906 * Whether the platform supports partial indexes.
1907 *
1908 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1909 */
1910 public function supportsPartialIndexes(): bool
1911 {
1912 return false;
1913 }
1914
1915 /**
1916 * Whether the platform supports indexes with column length definitions.
1917 */
1918 public function supportsColumnLengthIndexes(): bool
1919 {
1920 return false;
1921 }
1922
1923 /**
1924 * Whether the platform supports savepoints.
1925 */
1926 public function supportsSavepoints(): bool
1927 {
1928 return true;
1929 }
1930
1931 /**
1932 * Whether the platform supports releasing savepoints.
1933 */
1934 public function supportsReleaseSavepoints(): bool
1935 {
1936 return $this->supportsSavepoints();
1937 }
1938
1939 /**
1940 * Whether the platform supports database schemas.
1941 */
1942 public function supportsSchemas(): bool
1943 {
1944 return false;
1945 }
1946
1947 /**
1948 * Whether this platform support to add inline column comments as postfix.
1949 *
1950 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1951 */
1952 public function supportsInlineColumnComments(): bool
1953 {
1954 return false;
1955 }
1956
1957 /**
1958 * Whether this platform support the proprietary syntax "COMMENT ON asset".
1959 *
1960 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1961 */
1962 public function supportsCommentOnStatement(): bool
1963 {
1964 return false;
1965 }
1966
1967 /**
1968 * Does this platform support column collation?
1969 *
1970 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1971 */
1972 public function supportsColumnCollation(): bool
1973 {
1974 return false;
1975 }
1976
1977 /**
1978 * Gets the format string, as accepted by the date() function, that describes
1979 * the format of a stored datetime value of this platform.
1980 *
1981 * @return string The format string.
1982 */
1983 public function getDateTimeFormatString(): string
1984 {
1985 return 'Y-m-d H:i:s';
1986 }
1987
1988 /**
1989 * Gets the format string, as accepted by the date() function, that describes
1990 * the format of a stored datetime with timezone value of this platform.
1991 *
1992 * @return string The format string.
1993 */
1994 public function getDateTimeTzFormatString(): string
1995 {
1996 return 'Y-m-d H:i:s';
1997 }
1998
1999 /**
2000 * Gets the format string, as accepted by the date() function, that describes
2001 * the format of a stored date value of this platform.
2002 *
2003 * @return string The format string.
2004 */
2005 public function getDateFormatString(): string
2006 {
2007 return 'Y-m-d';
2008 }
2009
2010 /**
2011 * Gets the format string, as accepted by the date() function, that describes
2012 * the format of a stored time value of this platform.
2013 *
2014 * @return string The format string.
2015 */
2016 public function getTimeFormatString(): string
2017 {
2018 return 'H:i:s';
2019 }
2020
2021 /**
2022 * Adds an driver-specific LIMIT clause to the query.
2023 */
2024 final public function modifyLimitQuery(string $query, ?int $limit, int $offset = 0): string
2025 {
2026 if ($offset < 0) {
2027 throw new InvalidArgumentException(sprintf(
2028 'Offset must be a positive integer or zero, %d given.',
2029 $offset,
2030 ));
2031 }
2032
2033 return $this->doModifyLimitQuery($query, $limit, $offset);
2034 }
2035
2036 /**
2037 * Adds an platform-specific LIMIT clause to the query.
2038 */
2039 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
2040 {
2041 if ($limit !== null) {
2042 $query .= sprintf(' LIMIT %d', $limit);
2043 }
2044
2045 if ($offset > 0) {
2046 $query .= sprintf(' OFFSET %d', $offset);
2047 }
2048
2049 return $query;
2050 }
2051
2052 /**
2053 * Maximum length of any given database identifier, like tables or column names.
2054 */
2055 public function getMaxIdentifierLength(): int
2056 {
2057 return 63;
2058 }
2059
2060 /**
2061 * Returns the insert SQL for an empty insert statement.
2062 */
2063 public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string
2064 {
2065 return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)';
2066 }
2067
2068 /**
2069 * Generates a Truncate Table SQL statement for a given table.
2070 *
2071 * Cascade is not supported on many platforms but would optionally cascade the truncate by
2072 * following the foreign keys.
2073 */
2074 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
2075 {
2076 $tableIdentifier = new Identifier($tableName);
2077
2078 return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
2079 }
2080
2081 /**
2082 * This is for test reasons, many vendors have special requirements for dummy statements.
2083 */
2084 public function getDummySelectSQL(string $expression = '1'): string
2085 {
2086 return sprintf('SELECT %s', $expression);
2087 }
2088
2089 /**
2090 * Returns the SQL to create a new savepoint.
2091 */
2092 public function createSavePoint(string $savepoint): string
2093 {
2094 return 'SAVEPOINT ' . $savepoint;
2095 }
2096
2097 /**
2098 * Returns the SQL to release a savepoint.
2099 */
2100 public function releaseSavePoint(string $savepoint): string
2101 {
2102 return 'RELEASE SAVEPOINT ' . $savepoint;
2103 }
2104
2105 /**
2106 * Returns the SQL to rollback a savepoint.
2107 */
2108 public function rollbackSavePoint(string $savepoint): string
2109 {
2110 return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
2111 }
2112
2113 /**
2114 * Returns the keyword list instance of this platform.
2115 */
2116 final public function getReservedKeywordsList(): KeywordList
2117 {
2118 // Store the instance so it doesn't need to be generated on every request.
2119 return $this->_keywords ??= $this->createReservedKeywordsList();
2120 }
2121
2122 /**
2123 * Creates an instance of the reserved keyword list of this platform.
2124 */
2125 abstract protected function createReservedKeywordsList(): KeywordList;
2126
2127 /**
2128 * Quotes a literal string.
2129 * This method is NOT meant to fix SQL injections!
2130 * It is only meant to escape this platform's string literal
2131 * quote character inside the given literal string.
2132 *
2133 * @param string $str The literal string to be quoted.
2134 *
2135 * @return string The quoted literal string.
2136 */
2137 public function quoteStringLiteral(string $str): string
2138 {
2139 return "'" . str_replace("'", "''", $str) . "'";
2140 }
2141
2142 /**
2143 * Escapes metacharacters in a string intended to be used with a LIKE
2144 * operator.
2145 *
2146 * @param string $inputString a literal, unquoted string
2147 * @param string $escapeChar should be reused by the caller in the LIKE
2148 * expression.
2149 */
2150 final public function escapeStringForLike(string $inputString, string $escapeChar): string
2151 {
2152 $sql = preg_replace(
2153 '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u',
2154 addcslashes($escapeChar, '\\') . '$1',
2155 $inputString,
2156 );
2157
2158 assert(is_string($sql));
2159
2160 return $sql;
2161 }
2162
2163 /**
2164 * @return array<string,mixed> An associative array with the name of the properties
2165 * of the column being declared as array indexes.
2166 */
2167 private function columnToArray(Column $column): array
2168 {
2169 return array_merge($column->toArray(), [
2170 'name' => $column->getQuotedName($this),
2171 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false,
2172 'comment' => $column->getComment(),
2173 ]);
2174 }
2175
2176 /** @internal */
2177 public function createSQLParser(): Parser
2178 {
2179 return new Parser(false);
2180 }
2181
2182 protected function getLikeWildcardCharacters(): string
2183 {
2184 return '%_';
2185 }
2186
2187 /**
2188 * Compares the definitions of the given columns in the context of this platform.
2189 */
2190 public function columnsEqual(Column $column1, Column $column2): bool
2191 {
2192 $column1Array = $this->columnToArray($column1);
2193 $column2Array = $this->columnToArray($column2);
2194
2195 // ignore explicit columnDefinition since it's not set on the Column generated by the SchemaManager
2196 unset($column1Array['columnDefinition']);
2197 unset($column2Array['columnDefinition']);
2198
2199 if (
2200 $this->getColumnDeclarationSQL('', $column1Array)
2201 !== $this->getColumnDeclarationSQL('', $column2Array)
2202 ) {
2203 return false;
2204 }
2205
2206 // If the platform supports inline comments, all comparison is already done above
2207 if ($this->supportsInlineColumnComments()) {
2208 return true;
2209 }
2210
2211 return $column1->getComment() === $column2->getComment();
2212 }
2213
2214 /**
2215 * Creates the schema manager that can be used to inspect and change the underlying
2216 * database schema according to the dialect of the platform.
2217 */
2218 abstract public function createSchemaManager(Connection $connection): AbstractSchemaManager;
2219}
diff --git a/vendor/doctrine/dbal/src/Platforms/DB2Platform.php b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php
new file mode 100644
index 0000000..a00b24b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php
@@ -0,0 +1,593 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Platforms\Exception\NotSupported;
9use Doctrine\DBAL\Platforms\Keywords\DB2Keywords;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Schema\ColumnDiff;
12use Doctrine\DBAL\Schema\DB2SchemaManager;
13use Doctrine\DBAL\Schema\Identifier;
14use Doctrine\DBAL\Schema\Index;
15use Doctrine\DBAL\Schema\TableDiff;
16use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
17use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
18use Doctrine\DBAL\TransactionIsolationLevel;
19use Doctrine\DBAL\Types\Types;
20
21use function array_merge;
22use function count;
23use function current;
24use function explode;
25use function implode;
26use function sprintf;
27use function str_contains;
28
29/**
30 * Provides the behavior, features and SQL dialect of the IBM DB2 database platform of the oldest supported version.
31 */
32class DB2Platform extends AbstractPlatform
33{
34 /**
35 * {@inheritDoc}
36 */
37 public function getBlobTypeDeclarationSQL(array $column): string
38 {
39 // todo blob(n) with $column['length'];
40 return 'BLOB(1M)';
41 }
42
43 protected function initializeDoctrineTypeMappings(): void
44 {
45 $this->doctrineTypeMapping = [
46 'bigint' => Types::BIGINT,
47 'binary' => Types::BINARY,
48 'blob' => Types::BLOB,
49 'character' => Types::STRING,
50 'clob' => Types::TEXT,
51 'date' => Types::DATE_MUTABLE,
52 'decimal' => Types::DECIMAL,
53 'double' => Types::FLOAT,
54 'integer' => Types::INTEGER,
55 'real' => Types::FLOAT,
56 'smallint' => Types::SMALLINT,
57 'time' => Types::TIME_MUTABLE,
58 'timestamp' => Types::DATETIME_MUTABLE,
59 'varbinary' => Types::BINARY,
60 'varchar' => Types::STRING,
61 ];
62 }
63
64 protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string
65 {
66 return $this->getCharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
67 }
68
69 protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string
70 {
71 return $this->getVarcharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
72 }
73
74 /**
75 * {@inheritDoc}
76 */
77 public function getClobTypeDeclarationSQL(array $column): string
78 {
79 // todo clob(n) with $column['length'];
80 return 'CLOB(1M)';
81 }
82
83 /**
84 * {@inheritDoc}
85 */
86 public function getBooleanTypeDeclarationSQL(array $column): string
87 {
88 return 'SMALLINT';
89 }
90
91 /**
92 * {@inheritDoc}
93 */
94 public function getIntegerTypeDeclarationSQL(array $column): string
95 {
96 return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column);
97 }
98
99 /**
100 * {@inheritDoc}
101 */
102 public function getBigIntTypeDeclarationSQL(array $column): string
103 {
104 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
105 }
106
107 /**
108 * {@inheritDoc}
109 */
110 public function getSmallIntTypeDeclarationSQL(array $column): string
111 {
112 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
113 }
114
115 /**
116 * {@inheritDoc}
117 */
118 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
119 {
120 $autoinc = '';
121 if (! empty($column['autoincrement'])) {
122 $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
123 }
124
125 return $autoinc;
126 }
127
128 public function getBitAndComparisonExpression(string $value1, string $value2): string
129 {
130 return 'BITAND(' . $value1 . ', ' . $value2 . ')';
131 }
132
133 public function getBitOrComparisonExpression(string $value1, string $value2): string
134 {
135 return 'BITOR(' . $value1 . ', ' . $value2 . ')';
136 }
137
138 protected function getDateArithmeticIntervalExpression(
139 string $date,
140 string $operator,
141 string $interval,
142 DateIntervalUnit $unit,
143 ): string {
144 switch ($unit) {
145 case DateIntervalUnit::WEEK:
146 $interval = $this->multiplyInterval($interval, 7);
147 $unit = DateIntervalUnit::DAY;
148 break;
149
150 case DateIntervalUnit::QUARTER:
151 $interval = $this->multiplyInterval($interval, 3);
152 $unit = DateIntervalUnit::MONTH;
153 break;
154 }
155
156 return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit->value;
157 }
158
159 public function getDateDiffExpression(string $date1, string $date2): string
160 {
161 return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
162 }
163
164 /**
165 * {@inheritDoc}
166 */
167 public function getDateTimeTypeDeclarationSQL(array $column): string
168 {
169 if (isset($column['version']) && $column['version'] === true) {
170 return 'TIMESTAMP(0) WITH DEFAULT';
171 }
172
173 return 'TIMESTAMP(0)';
174 }
175
176 /**
177 * {@inheritDoc}
178 */
179 public function getDateTypeDeclarationSQL(array $column): string
180 {
181 return 'DATE';
182 }
183
184 /**
185 * {@inheritDoc}
186 */
187 public function getTimeTypeDeclarationSQL(array $column): string
188 {
189 return 'TIME';
190 }
191
192 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
193 {
194 $tableIdentifier = new Identifier($tableName);
195
196 return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE';
197 }
198
199 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
200 {
201 throw NotSupported::new(__METHOD__);
202 }
203
204 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
205 public function getListViewsSQL(string $database): string
206 {
207 return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS';
208 }
209
210 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
211 public function supportsCommentOnStatement(): bool
212 {
213 return true;
214 }
215
216 public function getCurrentDateSQL(): string
217 {
218 return 'CURRENT DATE';
219 }
220
221 public function getCurrentTimeSQL(): string
222 {
223 return 'CURRENT TIME';
224 }
225
226 public function getCurrentTimestampSQL(): string
227 {
228 return 'CURRENT TIMESTAMP';
229 }
230
231 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
232 public function getIndexDeclarationSQL(Index $index): string
233 {
234 // Index declaration in statements like CREATE TABLE is not supported.
235 throw NotSupported::new(__METHOD__);
236 }
237
238 /**
239 * {@inheritDoc}
240 */
241 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
242 {
243 $indexes = [];
244 if (isset($options['indexes'])) {
245 $indexes = $options['indexes'];
246 }
247
248 $options['indexes'] = [];
249
250 $sqls = parent::_getCreateTableSQL($name, $columns, $options);
251
252 foreach ($indexes as $definition) {
253 $sqls[] = $this->getCreateIndexSQL($definition, $name);
254 }
255
256 return $sqls;
257 }
258
259 /**
260 * {@inheritDoc}
261 */
262 public function getAlterTableSQL(TableDiff $diff): array
263 {
264 $sql = [];
265 $columnSql = [];
266 $commentsSQL = [];
267
268 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
269
270 $queryParts = [];
271 foreach ($diff->getAddedColumns() as $column) {
272 $columnDef = $column->toArray();
273 $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
274
275 // Adding non-nullable columns to a table requires a default value to be specified.
276 if (
277 ! empty($columnDef['notnull']) &&
278 ! isset($columnDef['default']) &&
279 empty($columnDef['autoincrement'])
280 ) {
281 $queryPart .= ' WITH DEFAULT';
282 }
283
284 $queryParts[] = $queryPart;
285
286 $comment = $column->getComment();
287
288 if ($comment === '') {
289 continue;
290 }
291
292 $commentsSQL[] = $this->getCommentOnColumnSQL(
293 $tableNameSQL,
294 $column->getQuotedName($this),
295 $comment,
296 );
297 }
298
299 foreach ($diff->getDroppedColumns() as $column) {
300 $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
301 }
302
303 foreach ($diff->getModifiedColumns() as $columnDiff) {
304 if ($columnDiff->hasCommentChanged()) {
305 $newColumn = $columnDiff->getNewColumn();
306 $commentsSQL[] = $this->getCommentOnColumnSQL(
307 $tableNameSQL,
308 $newColumn->getQuotedName($this),
309 $newColumn->getComment(),
310 );
311 }
312
313 $this->gatherAlterColumnSQL(
314 $tableNameSQL,
315 $columnDiff,
316 $sql,
317 $queryParts,
318 );
319 }
320
321 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
322 $oldColumnName = new Identifier($oldColumnName);
323
324 $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
325 ' TO ' . $column->getQuotedName($this);
326 }
327
328 if (count($queryParts) > 0) {
329 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . implode(' ', $queryParts);
330 }
331
332 // Some table alteration operations require a table reorganization.
333 if (count($diff->getDroppedColumns()) > 0 || count($diff->getModifiedColumns()) > 0) {
334 $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $tableNameSQL . "')";
335 }
336
337 $sql = array_merge(
338 $this->getPreAlterTableIndexForeignKeySQL($diff),
339 $sql,
340 $commentsSQL,
341 $this->getPostAlterTableIndexForeignKeySQL($diff),
342 );
343
344 return array_merge($sql, $columnSql);
345 }
346
347 public function getRenameTableSQL(string $oldName, string $newName): string
348 {
349 return sprintf('RENAME TABLE %s TO %s', $oldName, $newName);
350 }
351
352 /**
353 * Gathers the table alteration SQL for a given column diff.
354 *
355 * @param string $table The table to gather the SQL for.
356 * @param ColumnDiff $columnDiff The column diff to evaluate.
357 * @param list<string> $sql The sequence of table alteration statements to fill.
358 * @param list<string> $queryParts The sequence of column alteration clauses to fill.
359 */
360 private function gatherAlterColumnSQL(
361 string $table,
362 ColumnDiff $columnDiff,
363 array &$sql,
364 array &$queryParts,
365 ): void {
366 $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
367
368 if (empty($alterColumnClauses)) {
369 return;
370 }
371
372 // If we have a single column alteration, we can append the clause to the main query.
373 if (count($alterColumnClauses) === 1) {
374 $queryParts[] = current($alterColumnClauses);
375
376 return;
377 }
378
379 // We have multiple alterations for the same column,
380 // so we need to trigger a complete ALTER TABLE statement
381 // for each ALTER COLUMN clause.
382 foreach ($alterColumnClauses as $alterColumnClause) {
383 $sql[] = 'ALTER TABLE ' . $table . ' ' . $alterColumnClause;
384 }
385 }
386
387 /**
388 * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff.
389 *
390 * @return string[]
391 */
392 private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array
393 {
394 $newColumn = $columnDiff->getNewColumn()->toArray();
395
396 $alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this);
397
398 if ($newColumn['columnDefinition'] !== null) {
399 return [$alterClause . ' ' . $newColumn['columnDefinition']];
400 }
401
402 $clauses = [];
403
404 if (
405 $columnDiff->hasTypeChanged() ||
406 $columnDiff->hasLengthChanged() ||
407 $columnDiff->hasPrecisionChanged() ||
408 $columnDiff->hasScaleChanged() ||
409 $columnDiff->hasFixedChanged()
410 ) {
411 $clauses[] = $alterClause . ' SET DATA TYPE ' . $newColumn['type']->getSQLDeclaration($newColumn, $this);
412 }
413
414 if ($columnDiff->hasNotNullChanged()) {
415 $clauses[] = $newColumn['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
416 }
417
418 if ($columnDiff->hasDefaultChanged()) {
419 if (isset($newColumn['default'])) {
420 $defaultClause = $this->getDefaultValueDeclarationSQL($newColumn);
421
422 if ($defaultClause !== '') {
423 $clauses[] = $alterClause . ' SET' . $defaultClause;
424 }
425 } else {
426 $clauses[] = $alterClause . ' DROP DEFAULT';
427 }
428 }
429
430 return $clauses;
431 }
432
433 /**
434 * {@inheritDoc}
435 */
436 protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array
437 {
438 $sql = [];
439
440 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
441
442 foreach ($diff->getDroppedIndexes() as $droppedIndex) {
443 foreach ($diff->getAddedIndexes() as $addedIndex) {
444 if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) {
445 continue;
446 }
447
448 if ($droppedIndex->isPrimary()) {
449 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP PRIMARY KEY';
450 } elseif ($droppedIndex->isUnique()) {
451 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP UNIQUE ' . $droppedIndex->getQuotedName($this);
452 } else {
453 $sql[] = $this->getDropIndexSQL($droppedIndex->getQuotedName($this), $tableNameSQL);
454 }
455
456 $sql[] = $this->getCreateIndexSQL($addedIndex, $tableNameSQL);
457
458 $diff->unsetAddedIndex($addedIndex);
459 $diff->unsetDroppedIndex($droppedIndex);
460
461 break;
462 }
463 }
464
465 return array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
466 }
467
468 /**
469 * {@inheritDoc}
470 */
471 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
472 {
473 if (str_contains($tableName, '.')) {
474 [$schema] = explode('.', $tableName);
475 $oldIndexName = $schema . '.' . $oldIndexName;
476 }
477
478 return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
479 }
480
481 /**
482 * {@inheritDoc}
483 *
484 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
485 */
486 public function getDefaultValueDeclarationSQL(array $column): string
487 {
488 if (! empty($column['autoincrement'])) {
489 return '';
490 }
491
492 if (! empty($column['version'])) {
493 if ((string) $column['type'] !== 'DateTime') {
494 $column['default'] = '1';
495 }
496 }
497
498 return parent::getDefaultValueDeclarationSQL($column);
499 }
500
501 public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string
502 {
503 return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
504 }
505
506 public function getCreateTemporaryTableSnippetSQL(): string
507 {
508 return 'DECLARE GLOBAL TEMPORARY TABLE';
509 }
510
511 public function getTemporaryTableName(string $tableName): string
512 {
513 return 'SESSION.' . $tableName;
514 }
515
516 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
517 {
518 if ($offset > 0) {
519 $query .= sprintf(' OFFSET %d ROWS', $offset);
520 }
521
522 if ($limit !== null) {
523 $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit);
524 }
525
526 return $query;
527 }
528
529 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
530 {
531 if ($start === null) {
532 return sprintf('LOCATE(%s, %s)', $substring, $string);
533 }
534
535 return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start);
536 }
537
538 public function getSubstringExpression(string $string, string $start, ?string $length = null): string
539 {
540 if ($length === null) {
541 return sprintf('SUBSTR(%s, %s)', $string, $start);
542 }
543
544 return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
545 }
546
547 public function getLengthExpression(string $string): string
548 {
549 return 'LENGTH(' . $string . ', CODEUNITS32)';
550 }
551
552 public function getCurrentDatabaseExpression(): string
553 {
554 return 'CURRENT_USER';
555 }
556
557 public function supportsIdentityColumns(): bool
558 {
559 return true;
560 }
561
562 public function createSelectSQLBuilder(): SelectSQLBuilder
563 {
564 return new DefaultSelectSQLBuilder($this, 'WITH RR USE AND KEEP UPDATE LOCKS', null);
565 }
566
567 public function getDummySelectSQL(string $expression = '1'): string
568 {
569 return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression);
570 }
571
572 /**
573 * {@inheritDoc}
574 *
575 * DB2 supports savepoints, but they work semantically different than on other vendor platforms.
576 *
577 * TODO: We have to investigate how to get DB2 up and running with savepoints.
578 */
579 public function supportsSavepoints(): bool
580 {
581 return false;
582 }
583
584 protected function createReservedKeywordsList(): KeywordList
585 {
586 return new DB2Keywords();
587 }
588
589 public function createSchemaManager(Connection $connection): DB2SchemaManager
590 {
591 return new DB2SchemaManager($connection, $this);
592 }
593}
diff --git a/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php
new file mode 100644
index 0000000..ba783f3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php
@@ -0,0 +1,17 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7enum DateIntervalUnit: string
8{
9 case SECOND = 'SECOND';
10 case MINUTE = 'MINUTE';
11 case HOUR = 'HOUR';
12 case DAY = 'DAY';
13 case WEEK = 'WEEK';
14 case MONTH = 'MONTH';
15 case QUARTER = 'QUARTER';
16 case YEAR = 'YEAR';
17}
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php b/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php
new file mode 100644
index 0000000..dd70190
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class InvalidPlatformVersion extends Exception implements PlatformException
13{
14 /**
15 * Returns a new instance for an invalid specified platform version.
16 *
17 * @param string $version The invalid platform version given.
18 * @param string $expectedFormat The expected platform version format.
19 */
20 public static function new(string $version, string $expectedFormat): self
21 {
22 return new self(sprintf(
23 'Invalid platform version "%s" specified. The platform version has to be specified in the format: "%s".',
24 $version,
25 $expectedFormat,
26 ));
27 }
28}
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php b/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php
new file mode 100644
index 0000000..0690eaa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use LogicException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class NoColumnsSpecifiedForTable extends LogicException implements PlatformException
13{
14 public static function new(string $tableName): self
15 {
16 return new self(sprintf('No columns specified for table "%s".', $tableName));
17 }
18}
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php b/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php
new file mode 100644
index 0000000..4ab6fc9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use LogicException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class NotSupported extends LogicException implements PlatformException
13{
14 public static function new(string $method): self
15 {
16 return new self(sprintf('Operation "%s" is not supported by platform.', $method));
17 }
18}
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php b/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php
new file mode 100644
index 0000000..be546f5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use Doctrine\DBAL\Exception;
8
9interface PlatformException extends Exception
10{
11}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php
new file mode 100644
index 0000000..5ab6a6c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php
@@ -0,0 +1,414 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * DB2 Keywords.
9 */
10class DB2Keywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 */
15 protected function getKeywords(): array
16 {
17 return [
18 'ACTIVATE',
19 'ADD',
20 'AFTER',
21 'ALIAS',
22 'ALL',
23 'ALLOCATE',
24 'ALLOW',
25 'ALTER',
26 'AND',
27 'ANY',
28 'AS',
29 'ASENSITIVE',
30 'ASSOCIATE',
31 'ASUTIME',
32 'AT',
33 'ATTRIBUTES',
34 'AUDIT',
35 'AUTHORIZATION',
36 'AUX',
37 'AUXILIARY',
38 'BEFORE',
39 'BEGIN',
40 'BETWEEN',
41 'BINARY',
42 'BUFFERPOOL',
43 'BY',
44 'CACHE',
45 'CALL',
46 'CALLED',
47 'CAPTURE',
48 'CARDINALITY',
49 'CASCADED',
50 'CASE',
51 'CAST',
52 'CCSID',
53 'CHAR',
54 'CHARACTER',
55 'CHECK',
56 'CLONE',
57 'CLOSE',
58 'CLUSTER',
59 'COLLECTION',
60 'COLLID',
61 'COLUMN',
62 'COMMENT',
63 'COMMIT',
64 'CONCAT',
65 'CONDITION',
66 'CONNECT',
67 'CONNECTION',
68 'CONSTRAINT',
69 'CONTAINS',
70 'CONTINUE',
71 'COUNT',
72 'COUNT_BIG',
73 'CREATE',
74 'CROSS',
75 'CURRENT',
76 'CURRENT_DATE',
77 'CURRENT_LC_CTYPE',
78 'CURRENT_PATH',
79 'CURRENT_SCHEMA',
80 'CURRENT_SERVER',
81 'CURRENT_TIME',
82 'CURRENT_TIMESTAMP',
83 'CURRENT_TIMEZONE',
84 'CURRENT_USER',
85 'CURSOR',
86 'CYCLE',
87 'DATA',
88 'DATABASE',
89 'DATAPARTITIONNAME',
90 'DATAPARTITIONNUM',
91 'DATE',
92 'DAY',
93 'DAYS',
94 'DB2GENERAL',
95 'DB2GENRL',
96 'DB2SQL',
97 'DBINFO',
98 'DBPARTITIONNAME',
99 'DBPARTITIONNUM',
100 'DEALLOCATE',
101 'DECLARE',
102 'DEFAULT',
103 'DEFAULTS',
104 'DEFINITION',
105 'DELETE',
106 'DENSE_RANK',
107 'DENSERANK',
108 'DESCRIBE',
109 'DESCRIPTOR',
110 'DETERMINISTIC',
111 'DIAGNOSTICS',
112 'DISABLE',
113 'DISALLOW',
114 'DISCONNECT',
115 'DISTINCT',
116 'DO',
117 'DOCUMENT',
118 'DOUBLE',
119 'DROP',
120 'DSSIZE',
121 'DYNAMIC',
122 'EACH',
123 'EDITPROC',
124 'ELSE',
125 'ELSEIF',
126 'ENABLE',
127 'ENCODING',
128 'ENCRYPTION',
129 'END',
130 'END-EXEC',
131 'ENDING',
132 'ERASE',
133 'ESCAPE',
134 'EVERY',
135 'EXCEPT',
136 'EXCEPTION',
137 'EXCLUDING',
138 'EXCLUSIVE',
139 'EXECUTE',
140 'EXISTS',
141 'EXIT',
142 'EXPLAIN',
143 'EXTERNAL',
144 'EXTRACT',
145 'FENCED',
146 'FETCH',
147 'FIELDPROC',
148 'FILE',
149 'FINAL',
150 'FOR',
151 'FOREIGN',
152 'FREE',
153 'FROM',
154 'FULL',
155 'FUNCTION',
156 'GENERAL',
157 'GENERATED',
158 'GET',
159 'GLOBAL',
160 'GO',
161 'GOTO',
162 'GRANT',
163 'GRAPHIC',
164 'GROUP',
165 'HANDLER',
166 'HASH',
167 'HASHED_VALUE',
168 'HAVING',
169 'HINT',
170 'HOLD',
171 'HOUR',
172 'HOURS',
173 'IDENTITY',
174 'IF',
175 'IMMEDIATE',
176 'IN',
177 'INCLUDING',
178 'INCLUSIVE',
179 'INCREMENT',
180 'INDEX',
181 'INDICATOR',
182 'INF',
183 'INFINITY',
184 'INHERIT',
185 'INNER',
186 'INOUT',
187 'INSENSITIVE',
188 'INSERT',
189 'INTEGRITY',
190 'INTERSECT',
191 'INTO',
192 'IS',
193 'ISOBID',
194 'ISOLATION',
195 'ITERATE',
196 'JAR',
197 'JAVA',
198 'JOIN',
199 'KEEP',
200 'KEY',
201 'LABEL',
202 'LANGUAGE',
203 'LATERAL',
204 'LC_CTYPE',
205 'LEAVE',
206 'LEFT',
207 'LIKE',
208 'LINKTYPE',
209 'LOCAL',
210 'LOCALDATE',
211 'LOCALE',
212 'LOCALTIME',
213 'LOCALTIMESTAMP RIGHT',
214 'LOCATOR',
215 'LOCATORS',
216 'LOCK',
217 'LOCKMAX',
218 'LOCKSIZE',
219 'LONG',
220 'LOOP',
221 'MAINTAINED',
222 'MATERIALIZED',
223 'MAXVALUE',
224 'MICROSECOND',
225 'MICROSECONDS',
226 'MINUTE',
227 'MINUTES',
228 'MINVALUE',
229 'MODE',
230 'MODIFIES',
231 'MONTH',
232 'MONTHS',
233 'NAN',
234 'NEW',
235 'NEW_TABLE',
236 'NEXTVAL',
237 'NO',
238 'NOCACHE',
239 'NOCYCLE',
240 'NODENAME',
241 'NODENUMBER',
242 'NOMAXVALUE',
243 'NOMINVALUE',
244 'NONE',
245 'NOORDER',
246 'NORMALIZED',
247 'NOT',
248 'NULL',
249 'NULLS',
250 'NUMPARTS',
251 'OBID',
252 'OF',
253 'OLD',
254 'OLD_TABLE',
255 'ON',
256 'OPEN',
257 'OPTIMIZATION',
258 'OPTIMIZE',
259 'OPTION',
260 'OR',
261 'ORDER',
262 'OUT',
263 'OUTER',
264 'OVER',
265 'OVERRIDING',
266 'PACKAGE',
267 'PADDED',
268 'PAGESIZE',
269 'PARAMETER',
270 'PART',
271 'PARTITION',
272 'PARTITIONED',
273 'PARTITIONING',
274 'PARTITIONS',
275 'PASSWORD',
276 'PATH',
277 'PIECESIZE',
278 'PLAN',
279 'POSITION',
280 'PRECISION',
281 'PREPARE',
282 'PREVVAL',
283 'PRIMARY',
284 'PRIQTY',
285 'PRIVILEGES',
286 'PROCEDURE',
287 'PROGRAM',
288 'PSID',
289 'PUBLIC',
290 'QUERY',
291 'QUERYNO',
292 'RANGE',
293 'RANK',
294 'READ',
295 'READS',
296 'RECOVERY',
297 'REFERENCES',
298 'REFERENCING',
299 'REFRESH',
300 'RELEASE',
301 'RENAME',
302 'REPEAT',
303 'RESET',
304 'RESIGNAL',
305 'RESTART',
306 'RESTRICT',
307 'RESULT',
308 'RESULT_SET_LOCATOR WLM',
309 'RETURN',
310 'RETURNS',
311 'REVOKE',
312 'ROLE',
313 'ROLLBACK',
314 'ROUND_CEILING',
315 'ROUND_DOWN',
316 'ROUND_FLOOR',
317 'ROUND_HALF_DOWN',
318 'ROUND_HALF_EVEN',
319 'ROUND_HALF_UP',
320 'ROUND_UP',
321 'ROUTINE',
322 'ROW',
323 'ROW_NUMBER',
324 'ROWNUMBER',
325 'ROWS',
326 'ROWSET',
327 'RRN',
328 'RUN',
329 'SAVEPOINT',
330 'SCHEMA',
331 'SCRATCHPAD',
332 'SCROLL',
333 'SEARCH',
334 'SECOND',
335 'SECONDS',
336 'SECQTY',
337 'SECURITY',
338 'SELECT',
339 'SENSITIVE',
340 'SEQUENCE',
341 'SESSION',
342 'SESSION_USER',
343 'SET',
344 'SIGNAL',
345 'SIMPLE',
346 'SNAN',
347 'SOME',
348 'SOURCE',
349 'SPECIFIC',
350 'SQL',
351 'SQLID',
352 'STACKED',
353 'STANDARD',
354 'START',
355 'STARTING',
356 'STATEMENT',
357 'STATIC',
358 'STATMENT',
359 'STAY',
360 'STOGROUP',
361 'STORES',
362 'STYLE',
363 'SUBSTRING',
364 'SUMMARY',
365 'SYNONYM',
366 'SYSFUN',
367 'SYSIBM',
368 'SYSPROC',
369 'SYSTEM',
370 'SYSTEM_USER',
371 'TABLE',
372 'TABLESPACE',
373 'THEN',
374 'TIME',
375 'TIMESTAMP',
376 'TO',
377 'TRANSACTION',
378 'TRIGGER',
379 'TRIM',
380 'TRUNCATE',
381 'TYPE',
382 'UNDO',
383 'UNION',
384 'UNIQUE',
385 'UNTIL',
386 'UPDATE',
387 'USAGE',
388 'USER',
389 'USING',
390 'VALIDPROC',
391 'VALUE',
392 'VALUES',
393 'VARIABLE',
394 'VARIANT',
395 'VCAT',
396 'VERSION',
397 'VIEW',
398 'VOLATILE',
399 'VOLUMES',
400 'WHEN',
401 'WHENEVER',
402 'WHERE',
403 'WHILE',
404 'WITH',
405 'WITHOUT',
406 'WRITE',
407 'XMLELEMENT',
408 'XMLEXISTS',
409 'XMLNAMESPACES',
410 'YEAR',
411 'YEARS',
412 ];
413 }
414}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php b/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php
new file mode 100644
index 0000000..150ca3b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php
@@ -0,0 +1,42 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7use function array_flip;
8use function array_map;
9use function strtoupper;
10
11/**
12 * Abstract interface for a SQL reserved keyword dictionary.
13 */
14abstract class KeywordList
15{
16 /** @var string[]|null */
17 private ?array $keywords = null;
18
19 /**
20 * Checks if the given word is a keyword of this dialect/vendor platform.
21 */
22 public function isKeyword(string $word): bool
23 {
24 if ($this->keywords === null) {
25 $this->initializeKeywords();
26 }
27
28 return isset($this->keywords[strtoupper($word)]);
29 }
30
31 protected function initializeKeywords(): void
32 {
33 $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords()));
34 }
35
36 /**
37 * Returns the list of keywords.
38 *
39 * @return string[]
40 */
41 abstract protected function getKeywords(): array;
42}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php
new file mode 100644
index 0000000..6857cd3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php
@@ -0,0 +1,264 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7class MariaDBKeywords extends MySQLKeywords
8{
9 /**
10 * {@inheritDoc}
11 */
12 protected function getKeywords(): array
13 {
14 return [
15 'ACCESSIBLE',
16 'ADD',
17 'ALL',
18 'ALTER',
19 'ANALYZE',
20 'AND',
21 'AS',
22 'ASC',
23 'ASENSITIVE',
24 'BEFORE',
25 'BETWEEN',
26 'BIGINT',
27 'BINARY',
28 'BLOB',
29 'BOTH',
30 'BY',
31 'CALL',
32 'CASCADE',
33 'CASE',
34 'CHANGE',
35 'CHAR',
36 'CHARACTER',
37 'CHECK',
38 'COLLATE',
39 'COLUMN',
40 'CONDITION',
41 'CONSTRAINT',
42 'CONTINUE',
43 'CONVERT',
44 'CREATE',
45 'CROSS',
46 'CURRENT_DATE',
47 'CURRENT_TIME',
48 'CURRENT_TIMESTAMP',
49 'CURRENT_USER',
50 'CURSOR',
51 'DATABASE',
52 'DATABASES',
53 'DAY_HOUR',
54 'DAY_MICROSECOND',
55 'DAY_MINUTE',
56 'DAY_SECOND',
57 'DEC',
58 'DECIMAL',
59 'DECLARE',
60 'DEFAULT',
61 'DELAYED',
62 'DELETE',
63 'DESC',
64 'DESCRIBE',
65 'DETERMINISTIC',
66 'DISTINCT',
67 'DISTINCTROW',
68 'DIV',
69 'DOUBLE',
70 'DROP',
71 'DUAL',
72 'EACH',
73 'ELSE',
74 'ELSEIF',
75 'ENCLOSED',
76 'ESCAPED',
77 'EXCEPT',
78 'EXISTS',
79 'EXIT',
80 'EXPLAIN',
81 'FALSE',
82 'FETCH',
83 'FLOAT',
84 'FLOAT4',
85 'FLOAT8',
86 'FOR',
87 'FORCE',
88 'FOREIGN',
89 'FROM',
90 'FULLTEXT',
91 'GENERATED',
92 'GET',
93 'GENERAL',
94 'GRANT',
95 'GROUP',
96 'HAVING',
97 'HIGH_PRIORITY',
98 'HOUR_MICROSECOND',
99 'HOUR_MINUTE',
100 'HOUR_SECOND',
101 'IF',
102 'IGNORE',
103 'IGNORE_SERVER_IDS',
104 'IN',
105 'INDEX',
106 'INFILE',
107 'INNER',
108 'INOUT',
109 'INSENSITIVE',
110 'INSERT',
111 'INT',
112 'INT1',
113 'INT2',
114 'INT3',
115 'INT4',
116 'INT8',
117 'INTEGER',
118 'INTERSECT',
119 'INTERVAL',
120 'INTO',
121 'IO_AFTER_GTIDS',
122 'IO_BEFORE_GTIDS',
123 'IS',
124 'ITERATE',
125 'JOIN',
126 'KEY',
127 'KEYS',
128 'KILL',
129 'LEADING',
130 'LEAVE',
131 'LEFT',
132 'LIKE',
133 'LIMIT',
134 'LINEAR',
135 'LINES',
136 'LOAD',
137 'LOCALTIME',
138 'LOCALTIMESTAMP',
139 'LOCK',
140 'LONG',
141 'LONGBLOB',
142 'LONGTEXT',
143 'LOOP',
144 'LOW_PRIORITY',
145 'MASTER_BIND',
146 'MASTER_HEARTBEAT_PERIOD',
147 'MASTER_SSL_VERIFY_SERVER_CERT',
148 'MATCH',
149 'MAXVALUE',
150 'MEDIUMBLOB',
151 'MEDIUMINT',
152 'MEDIUMTEXT',
153 'MIDDLEINT',
154 'MINUTE_MICROSECOND',
155 'MINUTE_SECOND',
156 'MOD',
157 'MODIFIES',
158 'NATURAL',
159 'NO_WRITE_TO_BINLOG',
160 'NOT',
161 'NULL',
162 'NUMERIC',
163 'OFFSET',
164 'ON',
165 'OPTIMIZE',
166 'OPTIMIZER_COSTS',
167 'OPTION',
168 'OPTIONALLY',
169 'OR',
170 'ORDER',
171 'OUT',
172 'OUTER',
173 'OUTFILE',
174 'OVER',
175 'PARTITION',
176 'PRECISION',
177 'PRIMARY',
178 'PROCEDURE',
179 'PURGE',
180 'RANGE',
181 'READ',
182 'READ_WRITE',
183 'READS',
184 'REAL',
185 'RECURSIVE',
186 'REFERENCES',
187 'REGEXP',
188 'RELEASE',
189 'RENAME',
190 'REPEAT',
191 'REPLACE',
192 'REQUIRE',
193 'RESIGNAL',
194 'RESTRICT',
195 'RETURN',
196 'RETURNING',
197 'REVOKE',
198 'RIGHT',
199 'RLIKE',
200 'ROWS',
201 'SCHEMA',
202 'SCHEMAS',
203 'SECOND_MICROSECOND',
204 'SELECT',
205 'SENSITIVE',
206 'SEPARATOR',
207 'SET',
208 'SHOW',
209 'SIGNAL',
210 'SLOW',
211 'SMALLINT',
212 'SPATIAL',
213 'SPECIFIC',
214 'SQL',
215 'SQL_BIG_RESULT',
216 'SQL_CALC_FOUND_ROWS',
217 'SQL_SMALL_RESULT',
218 'SQLEXCEPTION',
219 'SQLSTATE',
220 'SQLWARNING',
221 'SSL',
222 'STARTING',
223 'STORED',
224 'STRAIGHT_JOIN',
225 'TABLE',
226 'TERMINATED',
227 'THEN',
228 'TINYBLOB',
229 'TINYINT',
230 'TINYTEXT',
231 'TO',
232 'TRAILING',
233 'TRIGGER',
234 'TRUE',
235 'UNDO',
236 'UNION',
237 'UNIQUE',
238 'UNLOCK',
239 'UNSIGNED',
240 'UPDATE',
241 'USAGE',
242 'USE',
243 'USING',
244 'UTC_DATE',
245 'UTC_TIME',
246 'UTC_TIMESTAMP',
247 'VALUES',
248 'VARBINARY',
249 'VARCHAR',
250 'VARCHARACTER',
251 'VARYING',
252 'VIRTUAL',
253 'WHEN',
254 'WHERE',
255 'WHILE',
256 'WINDOW',
257 'WITH',
258 'WRITE',
259 'XOR',
260 'YEAR_MONTH',
261 'ZEROFILL',
262 ];
263 }
264}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php
new file mode 100644
index 0000000..331192a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php
@@ -0,0 +1,59 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7use function array_merge;
8
9/**
10 * MySQL 8.0 reserved keywords list.
11 */
12class MySQL80Keywords extends MySQLKeywords
13{
14 /**
15 * {@inheritDoc}
16 *
17 * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
18 */
19 protected function getKeywords(): array
20 {
21 $keywords = parent::getKeywords();
22
23 $keywords = array_merge($keywords, [
24 'ADMIN',
25 'ARRAY',
26 'CUBE',
27 'CUME_DIST',
28 'DENSE_RANK',
29 'EMPTY',
30 'EXCEPT',
31 'FIRST_VALUE',
32 'FUNCTION',
33 'GROUPING',
34 'GROUPS',
35 'JSON_TABLE',
36 'LAG',
37 'LAST_VALUE',
38 'LATERAL',
39 'LEAD',
40 'MEMBER',
41 'NTH_VALUE',
42 'NTILE',
43 'OF',
44 'OVER',
45 'PERCENT_RANK',
46 'PERSIST',
47 'PERSIST_ONLY',
48 'RANK',
49 'RECURSIVE',
50 'ROW',
51 'ROWS',
52 'ROW_NUMBER',
53 'SYSTEM',
54 'WINDOW',
55 ]);
56
57 return $keywords;
58 }
59}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php
new file mode 100644
index 0000000..2fbc461
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php
@@ -0,0 +1,257 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * MySQL Keywordlist.
9 */
10class MySQLKeywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 *
15 * @link https://dev.mysql.com/doc/mysqld-version-reference/en/keywords-5-7.html
16 */
17 protected function getKeywords(): array
18 {
19 return [
20 'ACCESSIBLE',
21 'ADD',
22 'ALL',
23 'ALTER',
24 'ANALYZE',
25 'AND',
26 'AS',
27 'ASC',
28 'ASENSITIVE',
29 'BEFORE',
30 'BETWEEN',
31 'BIGINT',
32 'BINARY',
33 'BLOB',
34 'BOTH',
35 'BY',
36 'CALL',
37 'CASCADE',
38 'CASE',
39 'CHANGE',
40 'CHAR',
41 'CHARACTER',
42 'CHECK',
43 'COLLATE',
44 'COLUMN',
45 'CONDITION',
46 'CONSTRAINT',
47 'CONTINUE',
48 'CONVERT',
49 'CREATE',
50 'CROSS',
51 'CURRENT_DATE',
52 'CURRENT_TIME',
53 'CURRENT_TIMESTAMP',
54 'CURRENT_USER',
55 'CURSOR',
56 'DATABASE',
57 'DATABASES',
58 'DAY_HOUR',
59 'DAY_MICROSECOND',
60 'DAY_MINUTE',
61 'DAY_SECOND',
62 'DEC',
63 'DECIMAL',
64 'DECLARE',
65 'DEFAULT',
66 'DELAYED',
67 'DELETE',
68 'DESC',
69 'DESCRIBE',
70 'DETERMINISTIC',
71 'DISTINCT',
72 'DISTINCTROW',
73 'DIV',
74 'DOUBLE',
75 'DROP',
76 'DUAL',
77 'EACH',
78 'ELSE',
79 'ELSEIF',
80 'ENCLOSED',
81 'ESCAPED',
82 'EXISTS',
83 'EXIT',
84 'EXPLAIN',
85 'FALSE',
86 'FETCH',
87 'FLOAT',
88 'FLOAT4',
89 'FLOAT8',
90 'FOR',
91 'FORCE',
92 'FOREIGN',
93 'FROM',
94 'FULLTEXT',
95 'GENERATED',
96 'GET',
97 'GRANT',
98 'GROUP',
99 'HAVING',
100 'HIGH_PRIORITY',
101 'HOUR_MICROSECOND',
102 'HOUR_MINUTE',
103 'HOUR_SECOND',
104 'IF',
105 'IGNORE',
106 'IN',
107 'INDEX',
108 'INFILE',
109 'INNER',
110 'INOUT',
111 'INSENSITIVE',
112 'INSERT',
113 'INT',
114 'INT1',
115 'INT2',
116 'INT3',
117 'INT4',
118 'INT8',
119 'INTEGER',
120 'INTERVAL',
121 'INTO',
122 'IO_AFTER_GTIDS',
123 'IO_BEFORE_GTIDS',
124 'IS',
125 'ITERATE',
126 'JOIN',
127 'KEY',
128 'KEYS',
129 'KILL',
130 'LEADING',
131 'LEAVE',
132 'LEFT',
133 'LIKE',
134 'LIMIT',
135 'LINEAR',
136 'LINES',
137 'LOAD',
138 'LOCALTIME',
139 'LOCALTIMESTAMP',
140 'LOCK',
141 'LONG',
142 'LONGBLOB',
143 'LONGTEXT',
144 'LOOP',
145 'LOW_PRIORITY',
146 'MASTER_BIND',
147 'MASTER_SSL_VERIFY_SERVER_CERT',
148 'MATCH',
149 'MAXVALUE',
150 'MEDIUMBLOB',
151 'MEDIUMINT',
152 'MEDIUMTEXT',
153 'MIDDLEINT',
154 'MINUTE_MICROSECOND',
155 'MINUTE_SECOND',
156 'MOD',
157 'MODIFIES',
158 'NATURAL',
159 'NO_WRITE_TO_BINLOG',
160 'NOT',
161 'NULL',
162 'NUMERIC',
163 'ON',
164 'OPTIMIZE',
165 'OPTIMIZER_COSTS',
166 'OPTION',
167 'OPTIONALLY',
168 'OR',
169 'ORDER',
170 'OUT',
171 'OUTER',
172 'OUTFILE',
173 'PARTITION',
174 'PRECISION',
175 'PRIMARY',
176 'PROCEDURE',
177 'PURGE',
178 'RANGE',
179 'READ',
180 'READ_WRITE',
181 'READS',
182 'REAL',
183 'REFERENCES',
184 'REGEXP',
185 'RELEASE',
186 'RENAME',
187 'REPEAT',
188 'REPLACE',
189 'REQUIRE',
190 'RESIGNAL',
191 'RESTRICT',
192 'RETURN',
193 'REVOKE',
194 'RIGHT',
195 'RLIKE',
196 'SCHEMA',
197 'SCHEMAS',
198 'SECOND_MICROSECOND',
199 'SELECT',
200 'SENSITIVE',
201 'SEPARATOR',
202 'SET',
203 'SHOW',
204 'SIGNAL',
205 'SMALLINT',
206 'SPATIAL',
207 'SPECIFIC',
208 'SQL',
209 'SQL_BIG_RESULT',
210 'SQL_CALC_FOUND_ROWS',
211 'SQL_SMALL_RESULT',
212 'SQLEXCEPTION',
213 'SQLSTATE',
214 'SQLWARNING',
215 'SSL',
216 'STARTING',
217 'STORED',
218 'STRAIGHT_JOIN',
219 'TABLE',
220 'TERMINATED',
221 'THEN',
222 'TINYBLOB',
223 'TINYINT',
224 'TINYTEXT',
225 'TO',
226 'TRAILING',
227 'TRIGGER',
228 'TRUE',
229 'UNDO',
230 'UNION',
231 'UNIQUE',
232 'UNLOCK',
233 'UNSIGNED',
234 'UPDATE',
235 'USAGE',
236 'USE',
237 'USING',
238 'UTC_DATE',
239 'UTC_TIME',
240 'UTC_TIMESTAMP',
241 'VALUES',
242 'VARBINARY',
243 'VARCHAR',
244 'VARCHARACTER',
245 'VARYING',
246 'VIRTUAL',
247 'WHEN',
248 'WHERE',
249 'WHILE',
250 'WITH',
251 'WRITE',
252 'XOR',
253 'YEAR_MONTH',
254 'ZEROFILL',
255 ];
256 }
257}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php
new file mode 100644
index 0000000..589d092
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php
@@ -0,0 +1,133 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Oracle Keywordlist.
9 */
10class OracleKeywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 */
15 protected function getKeywords(): array
16 {
17 return [
18 'ACCESS',
19 'ADD',
20 'ALL',
21 'ALTER',
22 'AND',
23 'ANY',
24 'ARRAYLEN',
25 'AS',
26 'ASC',
27 'AUDIT',
28 'BETWEEN',
29 'BY',
30 'CHAR',
31 'CHECK',
32 'CLUSTER',
33 'COLUMN',
34 'COMMENT',
35 'COMPRESS',
36 'CONNECT',
37 'CREATE',
38 'CURRENT',
39 'DATE',
40 'DECIMAL',
41 'DEFAULT',
42 'DELETE',
43 'DESC',
44 'DISTINCT',
45 'DROP',
46 'ELSE',
47 'EXCLUSIVE',
48 'EXISTS',
49 'FILE',
50 'FLOAT',
51 'FOR',
52 'FROM',
53 'GRANT',
54 'GROUP',
55 'HAVING',
56 'IDENTIFIED',
57 'IMMEDIATE',
58 'IN',
59 'INCREMENT',
60 'INDEX',
61 'INITIAL',
62 'INSERT',
63 'INTEGER',
64 'INTERSECT',
65 'INTO',
66 'IS',
67 'LEVEL',
68 'LIKE',
69 'LOCK',
70 'LONG',
71 'MAXEXTENTS',
72 'MINUS',
73 'MODE',
74 'MODIFY',
75 'NOAUDIT',
76 'NOCOMPRESS',
77 'NOT',
78 'NOTFOUND',
79 'NOWAIT',
80 'NULL',
81 'NUMBER',
82 'OF',
83 'OFFLINE',
84 'ON',
85 'ONLINE',
86 'OPTION',
87 'OR',
88 'ORDER',
89 'PCTFREE',
90 'PRIOR',
91 'PRIVILEGES',
92 'PUBLIC',
93 'RANGE',
94 'RAW',
95 'RENAME',
96 'RESOURCE',
97 'REVOKE',
98 'ROW',
99 'ROWID',
100 'ROWLABEL',
101 'ROWNUM',
102 'ROWS',
103 'SELECT',
104 'SESSION',
105 'SET',
106 'SHARE',
107 'SIZE',
108 'SMALLINT',
109 'SQLBUF',
110 'START',
111 'SUCCESSFUL',
112 'SYNONYM',
113 'SYSDATE',
114 'TABLE',
115 'THEN',
116 'TO',
117 'TRIGGER',
118 'UID',
119 'UNION',
120 'UNIQUE',
121 'UPDATE',
122 'USER',
123 'VALIDATE',
124 'VALUES',
125 'VARCHAR',
126 'VARCHAR2',
127 'VIEW',
128 'WHENEVER',
129 'WHERE',
130 'WITH',
131 ];
132 }
133}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php
new file mode 100644
index 0000000..62ef98a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php
@@ -0,0 +1,119 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Reserved keywords list corresponding to the PostgreSQL database platform of the oldest supported version.
9 */
10class PostgreSQLKeywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 */
15 protected function getKeywords(): array
16 {
17 return [
18 'ALL',
19 'ANALYSE',
20 'ANALYZE',
21 'AND',
22 'ANY',
23 'ARRAY',
24 'AS',
25 'ASC',
26 'ASYMMETRIC',
27 'AUTHORIZATION',
28 'BINARY',
29 'BOTH',
30 'CASE',
31 'CAST',
32 'CHECK',
33 'COLLATE',
34 'COLLATION',
35 'COLUMN',
36 'CONCURRENTLY',
37 'CONSTRAINT',
38 'CREATE',
39 'CROSS',
40 'CURRENT_CATALOG',
41 'CURRENT_DATE',
42 'CURRENT_ROLE',
43 'CURRENT_SCHEMA',
44 'CURRENT_TIME',
45 'CURRENT_TIMESTAMP',
46 'CURRENT_USER',
47 'DEFAULT',
48 'DEFERRABLE',
49 'DESC',
50 'DISTINCT',
51 'DO',
52 'ELSE',
53 'END',
54 'EXCEPT',
55 'FALSE',
56 'FETCH',
57 'FOR',
58 'FOREIGN',
59 'FREEZE',
60 'FROM',
61 'FULL',
62 'GRANT',
63 'GROUP',
64 'HAVING',
65 'ILIKE',
66 'IN',
67 'INITIALLY',
68 'INNER',
69 'INTERSECT',
70 'INTO',
71 'IS',
72 'ISNULL',
73 'JOIN',
74 'LATERAL',
75 'LEADING',
76 'LEFT',
77 'LIKE',
78 'LIMIT',
79 'LOCALTIME',
80 'LOCALTIMESTAMP',
81 'NATURAL',
82 'NOT',
83 'NOTNULL',
84 'NULL',
85 'OFFSET',
86 'ON',
87 'ONLY',
88 'OR',
89 'ORDER',
90 'OUTER',
91 'OVERLAPS',
92 'PLACING',
93 'PRIMARY',
94 'REFERENCES',
95 'RETURNING',
96 'RIGHT',
97 'SELECT',
98 'SESSION_USER',
99 'SIMILAR',
100 'SOME',
101 'SYMMETRIC',
102 'TABLE',
103 'THEN',
104 'TO',
105 'TRAILING',
106 'TRUE',
107 'UNION',
108 'UNIQUE',
109 'USER',
110 'USING',
111 'VARIADIC',
112 'VERBOSE',
113 'WHEN',
114 'WHERE',
115 'WINDOW',
116 'WITH',
117 ];
118 }
119}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php
new file mode 100644
index 0000000..02d52cf
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php
@@ -0,0 +1,207 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Reserved keywords list corresponding to the Microsoft SQL Server database platform of the oldest supported version.
9 */
10class SQLServerKeywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 *
15 * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx
16 */
17 protected function getKeywords(): array
18 {
19 return [
20 'ADD',
21 'ALL',
22 'ALTER',
23 'AND',
24 'ANY',
25 'AS',
26 'ASC',
27 'AUTHORIZATION',
28 'BACKUP',
29 'BEGIN',
30 'BETWEEN',
31 'BREAK',
32 'BROWSE',
33 'BULK',
34 'BY',
35 'CASCADE',
36 'CASE',
37 'CHECK',
38 'CHECKPOINT',
39 'CLOSE',
40 'CLUSTERED',
41 'COALESCE',
42 'COLLATE',
43 'COLUMN',
44 'COMMIT',
45 'COMPUTE',
46 'CONSTRAINT',
47 'CONTAINS',
48 'CONTAINSTABLE',
49 'CONTINUE',
50 'CONVERT',
51 'CREATE',
52 'CROSS',
53 'CURRENT',
54 'CURRENT_DATE',
55 'CURRENT_TIME',
56 'CURRENT_TIMESTAMP',
57 'CURRENT_USER',
58 'CURSOR',
59 'DATABASE',
60 'DBCC',
61 'DEALLOCATE',
62 'DECLARE',
63 'DEFAULT',
64 'DELETE',
65 'DENY',
66 'DESC',
67 'DISK',
68 'DISTINCT',
69 'DISTRIBUTED',
70 'DOUBLE',
71 'DROP',
72 'DUMP',
73 'ELSE',
74 'END',
75 'ERRLVL',
76 'ESCAPE',
77 'EXCEPT',
78 'EXEC',
79 'EXECUTE',
80 'EXISTS',
81 'EXIT',
82 'EXTERNAL',
83 'FETCH',
84 'FILE',
85 'FILLFACTOR',
86 'FOR',
87 'FOREIGN',
88 'FREETEXT',
89 'FREETEXTTABLE',
90 'FROM',
91 'FULL',
92 'FUNCTION',
93 'GOTO',
94 'GRANT',
95 'GROUP',
96 'HAVING',
97 'HOLDLOCK',
98 'IDENTITY',
99 'IDENTITY_INSERT',
100 'IDENTITYCOL',
101 'IF',
102 'IN',
103 'INDEX',
104 'INNER',
105 'INSERT',
106 'INTERSECT',
107 'INTO',
108 'IS',
109 'JOIN',
110 'KEY',
111 'KILL',
112 'LEFT',
113 'LIKE',
114 'LINENO',
115 'LOAD',
116 'MERGE',
117 'NATIONAL',
118 'NOCHECK ',
119 'NONCLUSTERED',
120 'NOT',
121 'NULL',
122 'NULLIF',
123 'OF',
124 'OFF',
125 'OFFSETS',
126 'ON',
127 'OPEN',
128 'OPENDATASOURCE',
129 'OPENQUERY',
130 'OPENROWSET',
131 'OPENXML',
132 'OPTION',
133 'OR',
134 'ORDER',
135 'OUTER',
136 'OVER',
137 'PERCENT',
138 'PIVOT',
139 'PLAN',
140 'PRECISION',
141 'PRIMARY',
142 'PRINT',
143 'PROC',
144 'PROCEDURE',
145 'PUBLIC',
146 'RAISERROR',
147 'READ',
148 'READTEXT',
149 'RECONFIGURE',
150 'REFERENCES',
151 'REPLICATION',
152 'RESTORE',
153 'RESTRICT',
154 'RETURN',
155 'REVERT',
156 'REVOKE',
157 'RIGHT',
158 'ROLLBACK',
159 'ROWCOUNT',
160 'ROWGUIDCOL',
161 'RULE',
162 'SAVE',
163 'SCHEMA',
164 'SECURITYAUDIT',
165 'SELECT',
166 'SEMANTICKEYPHRASETABLE',
167 'SEMANTICSIMILARITYDETAILSTABLE',
168 'SEMANTICSIMILARITYTABLE',
169 'SESSION_USER',
170 'SET',
171 'SETUSER',
172 'SHUTDOWN',
173 'SOME',
174 'STATISTICS',
175 'SYSTEM_USER',
176 'TABLE',
177 'TABLESAMPLE',
178 'TEXTSIZE',
179 'THEN',
180 'TO',
181 'TOP',
182 'TRAN',
183 'TRANSACTION',
184 'TRIGGER',
185 'TRUNCATE',
186 'TRY_CONVERT',
187 'TSEQUAL',
188 'UNION',
189 'UNIQUE',
190 'UNPIVOT',
191 'UPDATE',
192 'UPDATETEXT',
193 'USE',
194 'USER',
195 'VALUES',
196 'VARYING',
197 'VIEW',
198 'WAITFOR',
199 'WHEN',
200 'WHERE',
201 'WHILE',
202 'WITH',
203 'WITHIN GROUP',
204 'WRITETEXT',
205 ];
206 }
207}
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php
new file mode 100644
index 0000000..e7de212
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php
@@ -0,0 +1,141 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * SQLite Keywordlist.
9 */
10class SQLiteKeywords extends KeywordList
11{
12 /**
13 * {@inheritDoc}
14 */
15 protected function getKeywords(): array
16 {
17 return [
18 'ABORT',
19 'ACTION',
20 'ADD',
21 'AFTER',
22 'ALL',
23 'ALTER',
24 'ANALYZE',
25 'AND',
26 'AS',
27 'ASC',
28 'ATTACH',
29 'AUTOINCREMENT',
30 'BEFORE',
31 'BEGIN',
32 'BETWEEN',
33 'BY',
34 'CASCADE',
35 'CASE',
36 'CAST',
37 'CHECK',
38 'COLLATE',
39 'COLUMN',
40 'COMMIT',
41 'CONFLICT',
42 'CONSTRAINT',
43 'CREATE',
44 'CROSS',
45 'CURRENT_DATE',
46 'CURRENT_TIME',
47 'CURRENT_TIMESTAMP',
48 'DATABASE',
49 'DEFAULT',
50 'DEFERRABLE',
51 'DEFERRED',
52 'DELETE',
53 'DESC',
54 'DETACH',
55 'DISTINCT',
56 'DROP',
57 'EACH',
58 'ELSE',
59 'END',
60 'ESCAPE',
61 'EXCEPT',
62 'EXCLUSIVE',
63 'EXISTS',
64 'EXPLAIN',
65 'FAIL',
66 'FOR',
67 'FOREIGN',
68 'FROM',
69 'FULL',
70 'GLOB',
71 'GROUP',
72 'HAVING',
73 'IF',
74 'IGNORE',
75 'IMMEDIATE',
76 'IN',
77 'INDEX',
78 'INDEXED',
79 'INITIALLY',
80 'INNER',
81 'INSERT',
82 'INSTEAD',
83 'INTERSECT',
84 'INTO',
85 'IS',
86 'ISNULL',
87 'JOIN',
88 'KEY',
89 'LEFT',
90 'LIKE',
91 'LIMIT',
92 'MATCH',
93 'NATURAL',
94 'NO',
95 'NOT',
96 'NOTNULL',
97 'NULL',
98 'OF',
99 'OFFSET',
100 'ON',
101 'OR',
102 'ORDER',
103 'OUTER',
104 'PLAN',
105 'PRAGMA',
106 'PRIMARY',
107 'QUERY',
108 'RAISE',
109 'REFERENCES',
110 'REGEXP',
111 'REINDEX',
112 'RELEASE',
113 'RENAME',
114 'REPLACE',
115 'RESTRICT',
116 'RIGHT',
117 'ROLLBACK',
118 'ROW',
119 'SAVEPOINT',
120 'SELECT',
121 'SET',
122 'TABLE',
123 'TEMP',
124 'TEMPORARY',
125 'THEN',
126 'TO',
127 'TRANSACTION',
128 'TRIGGER',
129 'UNION',
130 'UNIQUE',
131 'UPDATE',
132 'USING',
133 'VACUUM',
134 'VALUES',
135 'VIEW',
136 'VIRTUAL',
137 'WHEN',
138 'WHERE',
139 ];
140 }
141}
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php
new file mode 100644
index 0000000..ccccbb0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php
@@ -0,0 +1,38 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Schema\Index;
8use Doctrine\DBAL\Schema\TableDiff;
9
10/**
11 * Provides the behavior, features and SQL dialect of the MariaDB 10.5 database platform.
12 */
13class MariaDB1052Platform extends MariaDBPlatform
14{
15 /**
16 * {@inheritDoc}
17 */
18 protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array
19 {
20 return AbstractMySQLPlatform::getPreAlterTableRenameIndexForeignKeySQL($diff);
21 }
22
23 /**
24 * {@inheritDoc}
25 */
26 protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array
27 {
28 return AbstractMySQLPlatform::getPostAlterTableIndexForeignKeySQL($diff);
29 }
30
31 /**
32 * {@inheritDoc}
33 */
34 protected function getRenameIndexSQL(string $oldIndexName, Index $index, $tableName): array
35 {
36 return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
37 }
38}
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php
new file mode 100644
index 0000000..028473d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
8
9/**
10 * Provides the behavior, features and SQL dialect of the MariaDB 10.6 database platform.
11 */
12class MariaDB1060Platform extends MariaDB1052Platform
13{
14 public function createSelectSQLBuilder(): SelectSQLBuilder
15 {
16 return AbstractPlatform::createSelectSQLBuilder();
17 }
18}
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php b/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php
new file mode 100644
index 0000000..d4082ae
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php
@@ -0,0 +1,165 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MariaDBKeywords;
9use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10use Doctrine\DBAL\Schema\TableDiff;
11use Doctrine\DBAL\Types\JsonType;
12
13use function array_diff_key;
14use function array_merge;
15use function count;
16use function in_array;
17
18/**
19 * Provides the behavior, features and SQL dialect of the MariaDB database platform of the oldest supported version.
20 */
21class MariaDBPlatform extends AbstractMySQLPlatform
22{
23 /**
24 * Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT.
25 *
26 * MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column
27 * is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column
28 * as JSON where it was originally specified as such instead of LONGTEXT.
29 *
30 * The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so query that table.
31 */
32 public function getColumnTypeSQLSnippet(string $tableAlias, string $databaseName): string
33 {
34 $subQueryAlias = 'i_' . $tableAlias;
35
36 $databaseName = $this->quoteStringLiteral($databaseName);
37
38 // The check for `CONSTRAINT_SCHEMA = $databaseName` is mandatory here to prevent performance issues
39 return <<<SQL
40 IF(
41 $tableAlias.COLUMN_TYPE = 'longtext'
42 AND EXISTS(
43 SELECT * FROM information_schema.CHECK_CONSTRAINTS $subQueryAlias
44 WHERE $subQueryAlias.CONSTRAINT_SCHEMA = $databaseName
45 AND $subQueryAlias.TABLE_NAME = $tableAlias.TABLE_NAME
46 AND $subQueryAlias.CHECK_CLAUSE = CONCAT(
47 'json_valid(`',
48 $tableAlias.COLUMN_NAME,
49 '`)'
50 )
51 ),
52 'json',
53 $tableAlias.COLUMN_TYPE
54 )
55 SQL;
56 }
57
58 /**
59 * {@inheritDoc}
60 */
61 protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array
62 {
63 $sql = [];
64 $tableName = $diff->getOldTable()->getQuotedName($this);
65
66 $modifiedForeignKeys = $diff->getModifiedForeignKeys();
67
68 foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
69 if (in_array($foreignKey, $modifiedForeignKeys, true)) {
70 continue;
71 }
72
73 $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableName);
74 }
75
76 return $sql;
77 }
78
79 /**
80 * {@inheritDoc}
81 */
82 protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array
83 {
84 return array_merge(
85 parent::getPostAlterTableIndexForeignKeySQL($diff),
86 $this->getPostAlterTableRenameIndexForeignKeySQL($diff),
87 );
88 }
89
90 /** @return list<string> */
91 private function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array
92 {
93 $sql = [];
94
95 $tableName = $diff->getOldTable()->getQuotedName($this);
96
97 $modifiedForeignKeys = $diff->getModifiedForeignKeys();
98
99 foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
100 if (in_array($foreignKey, $modifiedForeignKeys, true)) {
101 continue;
102 }
103
104 $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
105 }
106
107 return $sql;
108 }
109
110 /**
111 * Returns the remaining foreign key constraints that require one of the renamed indexes.
112 *
113 * "Remaining" here refers to the diff between the foreign keys currently defined in the associated
114 * table and the foreign keys to be removed.
115 *
116 * @param TableDiff $diff The table diff to evaluate.
117 *
118 * @return ForeignKeyConstraint[]
119 */
120 private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array
121 {
122 $renamedIndexes = $diff->getRenamedIndexes();
123
124 if (count($renamedIndexes) === 0) {
125 return [];
126 }
127
128 $foreignKeys = [];
129
130 $remainingForeignKeys = array_diff_key(
131 $diff->getOldTable()->getForeignKeys(),
132 $diff->getDroppedForeignKeys(),
133 );
134
135 foreach ($remainingForeignKeys as $foreignKey) {
136 foreach ($renamedIndexes as $index) {
137 if ($foreignKey->intersectsIndexColumns($index)) {
138 $foreignKeys[] = $foreignKey;
139
140 break;
141 }
142 }
143 }
144
145 return $foreignKeys;
146 }
147
148 /** {@inheritDoc} */
149 public function getColumnDeclarationSQL(string $name, array $column): string
150 {
151 // MariaDb forces column collation to utf8mb4_bin where the column was declared as JSON so ignore
152 // collation and character set for json columns as attempting to set them can cause an error.
153 if ($this->getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) {
154 unset($column['collation']);
155 unset($column['charset']);
156 }
157
158 return parent::getColumnDeclarationSQL($name, $column);
159 }
160
161 protected function createReservedKeywordsList(): KeywordList
162 {
163 return new MariaDBKeywords();
164 }
165}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php
new file mode 100644
index 0000000..665543e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8interface CharsetMetadataProvider
9{
10 public function getDefaultCharsetCollation(string $charset): ?string;
11}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php
new file mode 100644
index 0000000..dadc841
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
6
7use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
8
9use function array_key_exists;
10
11/** @internal */
12final class CachingCharsetMetadataProvider implements CharsetMetadataProvider
13{
14 /** @var array<string,?string> */
15 private array $cache = [];
16
17 public function __construct(private readonly CharsetMetadataProvider $charsetMetadataProvider)
18 {
19 }
20
21 public function getDefaultCharsetCollation(string $charset): ?string
22 {
23 if (array_key_exists($charset, $this->cache)) {
24 return $this->cache[$charset];
25 }
26
27 return $this->cache[$charset] = $this->charsetMetadataProvider->getDefaultCharsetCollation($charset);
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php
new file mode 100644
index 0000000..65b63df
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php
@@ -0,0 +1,37 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
10
11/** @internal */
12final class ConnectionCharsetMetadataProvider implements CharsetMetadataProvider
13{
14 public function __construct(private readonly Connection $connection)
15 {
16 }
17
18 /** @throws Exception */
19 public function getDefaultCharsetCollation(string $charset): ?string
20 {
21 $collation = $this->connection->fetchOne(
22 <<<'SQL'
23 SELECT DEFAULT_COLLATE_NAME
24 FROM information_schema.CHARACTER_SETS
25 WHERE CHARACTER_SET_NAME = ?;
26 SQL
27 ,
28 [$charset],
29 );
30
31 if ($collation !== false) {
32 return $collation;
33 }
34
35 return null;
36 }
37}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php
new file mode 100644
index 0000000..d52ca74
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8interface CollationMetadataProvider
9{
10 public function getCollationCharset(string $collation): ?string;
11}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php
new file mode 100644
index 0000000..0c99aa3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
6
7use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
8
9use function array_key_exists;
10
11/** @internal */
12final class CachingCollationMetadataProvider implements CollationMetadataProvider
13{
14 /** @var array<string,?string> */
15 private array $cache = [];
16
17 public function __construct(private readonly CollationMetadataProvider $collationMetadataProvider)
18 {
19 }
20
21 public function getCollationCharset(string $collation): ?string
22 {
23 if (array_key_exists($collation, $this->cache)) {
24 return $this->cache[$collation];
25 }
26
27 return $this->cache[$collation] = $this->collationMetadataProvider->getCollationCharset($collation);
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php
new file mode 100644
index 0000000..fcd9995
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php
@@ -0,0 +1,37 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
10
11/** @internal */
12final class ConnectionCollationMetadataProvider implements CollationMetadataProvider
13{
14 public function __construct(private readonly Connection $connection)
15 {
16 }
17
18 /** @throws Exception */
19 public function getCollationCharset(string $collation): ?string
20 {
21 $charset = $this->connection->fetchOne(
22 <<<'SQL'
23SELECT CHARACTER_SET_NAME
24FROM information_schema.COLLATIONS
25WHERE COLLATION_NAME = ?;
26SQL
27 ,
28 [$collation],
29 );
30
31 if ($charset !== false) {
32 return $charset;
33 }
34
35 return null;
36 }
37}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php
new file mode 100644
index 0000000..ebe025d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php
@@ -0,0 +1,93 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use Doctrine\DBAL\Schema\TableDiff;
11
12use function array_diff_assoc;
13
14/**
15 * Compares schemas in the context of MySQL platform.
16 *
17 * In MySQL, unless specified explicitly, the column's character set and collation are inherited from its containing
18 * table. So during comparison, an omitted value and the value that matches the default value of table in the
19 * desired schema must be considered equal.
20 */
21class Comparator extends BaseComparator
22{
23 /** @internal The comparator can be only instantiated by a schema manager. */
24 public function __construct(
25 AbstractMySQLPlatform $platform,
26 private readonly CharsetMetadataProvider $charsetMetadataProvider,
27 private readonly CollationMetadataProvider $collationMetadataProvider,
28 private readonly DefaultTableOptions $defaultTableOptions,
29 ) {
30 parent::__construct($platform);
31 }
32
33 public function compareTables(Table $oldTable, Table $newTable): TableDiff
34 {
35 return parent::compareTables(
36 $this->normalizeTable($oldTable),
37 $this->normalizeTable($newTable),
38 );
39 }
40
41 private function normalizeTable(Table $table): Table
42 {
43 $charset = $table->getOption('charset');
44 $collation = $table->getOption('collation');
45
46 if ($charset === null && $collation !== null) {
47 $charset = $this->collationMetadataProvider->getCollationCharset($collation);
48 } elseif ($charset !== null && $collation === null) {
49 $collation = $this->charsetMetadataProvider->getDefaultCharsetCollation($charset);
50 } elseif ($charset === null && $collation === null) {
51 $charset = $this->defaultTableOptions->getCharset();
52 $collation = $this->defaultTableOptions->getCollation();
53 }
54
55 $tableOptions = [
56 'charset' => $charset,
57 'collation' => $collation,
58 ];
59
60 $table = clone $table;
61
62 foreach ($table->getColumns() as $column) {
63 $originalOptions = $column->getPlatformOptions();
64 $normalizedOptions = $this->normalizeOptions($originalOptions);
65
66 $overrideOptions = array_diff_assoc($normalizedOptions, $tableOptions);
67
68 if ($overrideOptions === $originalOptions) {
69 continue;
70 }
71
72 $column->setPlatformOptions($overrideOptions);
73 }
74
75 return $table;
76 }
77
78 /**
79 * @param array<string,string> $options
80 *
81 * @return array<string,string|null>
82 */
83 private function normalizeOptions(array $options): array
84 {
85 if (isset($options['charset']) && ! isset($options['collation'])) {
86 $options['collation'] = $this->charsetMetadataProvider->getDefaultCharsetCollation($options['charset']);
87 } elseif (isset($options['collation']) && ! isset($options['charset'])) {
88 $options['charset'] = $this->collationMetadataProvider->getCollationCharset($options['collation']);
89 }
90
91 return $options;
92 }
93}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php b/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php
new file mode 100644
index 0000000..ede3ba2
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php
@@ -0,0 +1,23 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8final class DefaultTableOptions
9{
10 public function __construct(private readonly string $charset, private readonly string $collation)
11 {
12 }
13
14 public function getCharset(): string
15 {
16 return $this->charset;
17 }
18
19 public function getCollation(): string
20 {
21 return $this->collation;
22 }
23}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php
new file mode 100644
index 0000000..14a81a6
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords;
9use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
10
11/**
12 * Provides the behavior, features and SQL dialect of the MySQL 8.0 database platform.
13 */
14class MySQL80Platform extends MySQLPlatform
15{
16 protected function createReservedKeywordsList(): KeywordList
17 {
18 return new MySQL80Keywords();
19 }
20
21 public function createSelectSQLBuilder(): SelectSQLBuilder
22 {
23 return AbstractPlatform::createSelectSQLBuilder();
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php
new file mode 100644
index 0000000..840ff57
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php
@@ -0,0 +1,49 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
9use Doctrine\DBAL\Schema\Index;
10use Doctrine\DBAL\Types\BlobType;
11use Doctrine\DBAL\Types\TextType;
12
13/**
14 * Provides the behavior, features and SQL dialect of the Oracle MySQL database platform
15 * of the oldest supported version.
16 */
17class MySQLPlatform extends AbstractMySQLPlatform
18{
19 /**
20 * {@inheritDoc}
21 *
22 * Oracle MySQL does not support default values on TEXT/BLOB columns until 8.0.13.
23 *
24 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
25 *
26 * @link https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-13.html#mysqld-8-0-13-data-types
27 */
28 public function getDefaultValueDeclarationSQL(array $column): string
29 {
30 if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) {
31 unset($column['default']);
32 }
33
34 return parent::getDefaultValueDeclarationSQL($column);
35 }
36
37 /**
38 * {@inheritDoc}
39 */
40 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
41 {
42 return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
43 }
44
45 protected function createReservedKeywordsList(): KeywordList
46 {
47 return new MySQLKeywords();
48 }
49}
diff --git a/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php b/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php
new file mode 100644
index 0000000..314f6ee
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php
@@ -0,0 +1,784 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
9use Doctrine\DBAL\Platforms\Keywords\KeywordList;
10use Doctrine\DBAL\Platforms\Keywords\OracleKeywords;
11use Doctrine\DBAL\Schema\ForeignKeyConstraint;
12use Doctrine\DBAL\Schema\Identifier;
13use Doctrine\DBAL\Schema\Index;
14use Doctrine\DBAL\Schema\OracleSchemaManager;
15use Doctrine\DBAL\Schema\Sequence;
16use Doctrine\DBAL\Schema\TableDiff;
17use Doctrine\DBAL\TransactionIsolationLevel;
18use Doctrine\DBAL\Types\Types;
19use InvalidArgumentException;
20
21use function array_merge;
22use function count;
23use function explode;
24use function implode;
25use function sprintf;
26use function str_contains;
27use function strlen;
28use function strtoupper;
29use function substr;
30
31/**
32 * OraclePlatform.
33 */
34class OraclePlatform extends AbstractPlatform
35{
36 public function getSubstringExpression(string $string, string $start, ?string $length = null): string
37 {
38 if ($length === null) {
39 return sprintf('SUBSTR(%s, %s)', $string, $start);
40 }
41
42 return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
43 }
44
45 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
46 {
47 if ($start === null) {
48 return sprintf('INSTR(%s, %s)', $string, $substring);
49 }
50
51 return sprintf('INSTR(%s, %s, %s)', $string, $substring, $start);
52 }
53
54 protected function getDateArithmeticIntervalExpression(
55 string $date,
56 string $operator,
57 string $interval,
58 DateIntervalUnit $unit,
59 ): string {
60 switch ($unit) {
61 case DateIntervalUnit::MONTH:
62 case DateIntervalUnit::QUARTER:
63 case DateIntervalUnit::YEAR:
64 switch ($unit) {
65 case DateIntervalUnit::QUARTER:
66 $interval = $this->multiplyInterval($interval, 3);
67 break;
68
69 case DateIntervalUnit::YEAR:
70 $interval = $this->multiplyInterval($interval, 12);
71 break;
72 }
73
74 return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
75
76 default:
77 $calculationClause = '';
78
79 switch ($unit) {
80 case DateIntervalUnit::SECOND:
81 $calculationClause = '/24/60/60';
82 break;
83
84 case DateIntervalUnit::MINUTE:
85 $calculationClause = '/24/60';
86 break;
87
88 case DateIntervalUnit::HOUR:
89 $calculationClause = '/24';
90 break;
91
92 case DateIntervalUnit::WEEK:
93 $calculationClause = '*7';
94 break;
95 }
96
97 return '(' . $date . $operator . $interval . $calculationClause . ')';
98 }
99 }
100
101 public function getDateDiffExpression(string $date1, string $date2): string
102 {
103 return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2);
104 }
105
106 public function getBitAndComparisonExpression(string $value1, string $value2): string
107 {
108 return 'BITAND(' . $value1 . ', ' . $value2 . ')';
109 }
110
111 public function getCurrentDatabaseExpression(): string
112 {
113 return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')";
114 }
115
116 public function getBitOrComparisonExpression(string $value1, string $value2): string
117 {
118 return '(' . $value1 . '-' .
119 $this->getBitAndComparisonExpression($value1, $value2)
120 . '+' . $value2 . ')';
121 }
122
123 public function getCreatePrimaryKeySQL(Index $index, string $table): string
124 {
125 return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this)
126 . ' PRIMARY KEY (' . implode(', ', $index->getQuotedColumns($this)) . ')';
127 }
128
129 /**
130 * {@inheritDoc}
131 *
132 * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
133 * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
134 * in {@see listSequences()}
135 */
136 public function getCreateSequenceSQL(Sequence $sequence): string
137 {
138 return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
139 ' START WITH ' . $sequence->getInitialValue() .
140 ' MINVALUE ' . $sequence->getInitialValue() .
141 ' INCREMENT BY ' . $sequence->getAllocationSize() .
142 $this->getSequenceCacheSQL($sequence);
143 }
144
145 public function getAlterSequenceSQL(Sequence $sequence): string
146 {
147 return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
148 ' INCREMENT BY ' . $sequence->getAllocationSize()
149 . $this->getSequenceCacheSQL($sequence);
150 }
151
152 /**
153 * Cache definition for sequences
154 */
155 private function getSequenceCacheSQL(Sequence $sequence): string
156 {
157 if ($sequence->getCache() === 0) {
158 return ' NOCACHE';
159 }
160
161 if ($sequence->getCache() === 1) {
162 return ' NOCACHE';
163 }
164
165 if ($sequence->getCache() > 1) {
166 return ' CACHE ' . $sequence->getCache();
167 }
168
169 return '';
170 }
171
172 public function getSequenceNextValSQL(string $sequence): string
173 {
174 return 'SELECT ' . $sequence . '.nextval FROM DUAL';
175 }
176
177 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
178 {
179 return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
180 }
181
182 protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string
183 {
184 return match ($level) {
185 TransactionIsolationLevel::READ_UNCOMMITTED => 'READ UNCOMMITTED',
186 TransactionIsolationLevel::READ_COMMITTED => 'READ COMMITTED',
187 TransactionIsolationLevel::REPEATABLE_READ,
188 TransactionIsolationLevel::SERIALIZABLE => 'SERIALIZABLE',
189 };
190 }
191
192 /**
193 * {@inheritDoc}
194 */
195 public function getBooleanTypeDeclarationSQL(array $column): string
196 {
197 return 'NUMBER(1)';
198 }
199
200 /**
201 * {@inheritDoc}
202 */
203 public function getIntegerTypeDeclarationSQL(array $column): string
204 {
205 return 'NUMBER(10)';
206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 public function getBigIntTypeDeclarationSQL(array $column): string
212 {
213 return 'NUMBER(20)';
214 }
215
216 /**
217 * {@inheritDoc}
218 */
219 public function getSmallIntTypeDeclarationSQL(array $column): string
220 {
221 return 'NUMBER(5)';
222 }
223
224 /**
225 * {@inheritDoc}
226 */
227 public function getDateTimeTypeDeclarationSQL(array $column): string
228 {
229 return 'TIMESTAMP(0)';
230 }
231
232 /**
233 * {@inheritDoc}
234 */
235 public function getDateTimeTzTypeDeclarationSQL(array $column): string
236 {
237 return 'TIMESTAMP(0) WITH TIME ZONE';
238 }
239
240 /**
241 * {@inheritDoc}
242 */
243 public function getDateTypeDeclarationSQL(array $column): string
244 {
245 return 'DATE';
246 }
247
248 /**
249 * {@inheritDoc}
250 */
251 public function getTimeTypeDeclarationSQL(array $column): string
252 {
253 return 'DATE';
254 }
255
256 /**
257 * {@inheritDoc}
258 */
259 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
260 {
261 return '';
262 }
263
264 protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string
265 {
266 if ($length === null) {
267 throw ColumnLengthRequired::new($this, 'VARCHAR2');
268 }
269
270 return sprintf('VARCHAR2(%d)', $length);
271 }
272
273 protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string
274 {
275 if ($length === null) {
276 throw ColumnLengthRequired::new($this, 'RAW');
277 }
278
279 return sprintf('RAW(%d)', $length);
280 }
281
282 protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string
283 {
284 return $this->getBinaryTypeDeclarationSQLSnippet($length);
285 }
286
287 /**
288 * {@inheritDoc}
289 */
290 public function getClobTypeDeclarationSQL(array $column): string
291 {
292 return 'CLOB';
293 }
294
295 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
296 public function getListDatabasesSQL(): string
297 {
298 return 'SELECT username FROM all_users';
299 }
300
301 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
302 public function getListSequencesSQL(string $database): string
303 {
304 return 'SELECT SEQUENCE_NAME, MIN_VALUE, INCREMENT_BY FROM SYS.ALL_SEQUENCES WHERE SEQUENCE_OWNER = '
305 . $this->quoteStringLiteral(
306 $this->normalizeIdentifier($database)->getName(),
307 );
308 }
309
310 /**
311 * {@inheritDoc}
312 */
313 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
314 {
315 $indexes = $options['indexes'] ?? [];
316 $options['indexes'] = [];
317 $sql = parent::_getCreateTableSQL($name, $columns, $options);
318
319 foreach ($columns as $column) {
320 if (isset($column['sequence'])) {
321 $sql[] = $this->getCreateSequenceSQL($column['sequence']);
322 }
323
324 if (
325 empty($column['autoincrement'])
326 ) {
327 continue;
328 }
329
330 $sql = array_merge($sql, $this->getCreateAutoincrementSql($column['name'], $name));
331 }
332
333 foreach ($indexes as $index) {
334 $sql[] = $this->getCreateIndexSQL($index, $name);
335 }
336
337 return $sql;
338 }
339
340 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
341 public function getListViewsSQL(string $database): string
342 {
343 return 'SELECT view_name, text FROM sys.user_views';
344 }
345
346 /** @return array<int, string> */
347 protected function getCreateAutoincrementSql(string $name, string $table, int $start = 1): array
348 {
349 $tableIdentifier = $this->normalizeIdentifier($table);
350 $quotedTableName = $tableIdentifier->getQuotedName($this);
351 $unquotedTableName = $tableIdentifier->getName();
352
353 $nameIdentifier = $this->normalizeIdentifier($name);
354 $quotedName = $nameIdentifier->getQuotedName($this);
355 $unquotedName = $nameIdentifier->getName();
356
357 $sql = [];
358
359 $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
360
361 $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
362
363 $sql[] = "DECLARE
364 constraints_Count NUMBER;
365BEGIN
366 SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count
367 FROM USER_CONSTRAINTS
368 WHERE TABLE_NAME = '" . $unquotedTableName . "'
369 AND CONSTRAINT_TYPE = 'P';
370 IF constraints_Count = 0 OR constraints_Count = '' THEN
371 EXECUTE IMMEDIATE '" . $this->getCreateIndexSQL($idx, $quotedTableName) . "';
372 END IF;
373END;";
374
375 $sequenceName = $this->getIdentitySequenceName(
376 $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
377 );
378 $sequence = new Sequence($sequenceName, $start);
379 $sql[] = $this->getCreateSequenceSQL($sequence);
380
381 $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
382 BEFORE INSERT
383 ON ' . $quotedTableName . '
384 FOR EACH ROW
385DECLARE
386 last_Sequence NUMBER;
387 last_InsertID NUMBER;
388BEGIN
389 IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN
390 SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
391 ELSE
392 SELECT NVL(Last_Number, 0) INTO last_Sequence
393 FROM User_Sequences
394 WHERE Sequence_Name = \'' . $sequence->getName() . '\';
395 SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
396 WHILE (last_InsertID > last_Sequence) LOOP
397 SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
398 END LOOP;
399 SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
400 END IF;
401END;';
402
403 return $sql;
404 }
405
406 /**
407 * @internal The method should be only used from within the OracleSchemaManager class hierarchy.
408 *
409 * Returns the SQL statements to drop the autoincrement for the given table name.
410 *
411 * @param string $table The table name to drop the autoincrement for.
412 *
413 * @return string[]
414 */
415 public function getDropAutoincrementSql(string $table): array
416 {
417 $table = $this->normalizeIdentifier($table);
418 $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
419 $identitySequenceName = $this->getIdentitySequenceName(
420 $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
421 );
422
423 return [
424 'DROP TRIGGER ' . $autoincrementIdentifierName,
425 $this->getDropSequenceSQL($identitySequenceName),
426 $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
427 ];
428 }
429
430 /**
431 * Normalizes the given identifier.
432 *
433 * Uppercases the given identifier if it is not quoted by intention
434 * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
435 *
436 * @param string $name The identifier to normalize.
437 */
438 private function normalizeIdentifier(string $name): Identifier
439 {
440 $identifier = new Identifier($name);
441
442 return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
443 }
444
445 /**
446 * Adds suffix to identifier,
447 *
448 * if the new string exceeds max identifier length,
449 * keeps $suffix, cuts from $identifier as much as the part exceeding.
450 */
451 private function addSuffix(string $identifier, string $suffix): string
452 {
453 $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix);
454 if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) {
455 $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix);
456 }
457
458 return $identifier . $suffix;
459 }
460
461 /**
462 * Returns the autoincrement primary key identifier name for the given table identifier.
463 *
464 * Quotes the autoincrement primary key identifier name
465 * if the given table name is quoted by intention.
466 */
467 private function getAutoincrementIdentifierName(Identifier $table): string
468 {
469 $identifierName = $this->addSuffix($table->getName(), '_AI_PK');
470
471 return $table->isQuoted()
472 ? $this->quoteSingleIdentifier($identifierName)
473 : $identifierName;
474 }
475
476 public function getDropForeignKeySQL(string $foreignKey, string $table): string
477 {
478 return $this->getDropConstraintSQL($foreignKey, $table);
479 }
480
481 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
482 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
483 {
484 $referentialAction = '';
485
486 if ($foreignKey->hasOption('onDelete')) {
487 $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
488 }
489
490 if ($referentialAction !== '') {
491 return ' ON DELETE ' . $referentialAction;
492 }
493
494 return '';
495 }
496
497 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
498 public function getForeignKeyReferentialActionSQL(string $action): string
499 {
500 $action = strtoupper($action);
501
502 return match ($action) {
503 'RESTRICT',
504 'NO ACTION' => '',
505 'CASCADE',
506 'SET NULL' => $action,
507 default => throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $action)),
508 };
509 }
510
511 public function getCreateDatabaseSQL(string $name): string
512 {
513 return 'CREATE USER ' . $name;
514 }
515
516 public function getDropDatabaseSQL(string $name): string
517 {
518 return 'DROP USER ' . $name . ' CASCADE';
519 }
520
521 /**
522 * {@inheritDoc}
523 */
524 public function getAlterTableSQL(TableDiff $diff): array
525 {
526 $sql = [];
527 $commentsSQL = [];
528 $columnSql = [];
529
530 $addColumnSQL = [];
531
532 $tableNameSQL = $diff->getOldTable()->getQuotedName($this);
533
534 foreach ($diff->getAddedColumns() as $column) {
535 $addColumnSQL[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
536 $comment = $column->getComment();
537
538 if ($comment === '') {
539 continue;
540 }
541
542 $commentsSQL[] = $this->getCommentOnColumnSQL(
543 $tableNameSQL,
544 $column->getQuotedName($this),
545 $comment,
546 );
547 }
548
549 if (count($addColumnSQL) > 0) {
550 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $addColumnSQL) . ')';
551 }
552
553 $modifyColumnSQL = [];
554 foreach ($diff->getModifiedColumns() as $columnDiff) {
555 $newColumn = $columnDiff->getNewColumn();
556 $oldColumn = $columnDiff->getOldColumn();
557
558 $newColumnProperties = $newColumn->toArray();
559 $oldColumnProperties = $oldColumn->toArray();
560
561 $oldSQL = $this->getColumnDeclarationSQL('', $oldColumnProperties);
562 $newSQL = $this->getColumnDeclarationSQL('', $newColumnProperties);
563
564 if ($newSQL !== $oldSQL) {
565 if (! $columnDiff->hasNotNullChanged()) {
566 unset($newColumnProperties['notnull']);
567 $newSQL = $this->getColumnDeclarationSQL('', $newColumnProperties);
568 }
569
570 $modifyColumnSQL[] = $newColumn->getQuotedName($this) . $newSQL;
571 }
572
573 if (! $columnDiff->hasCommentChanged()) {
574 continue;
575 }
576
577 $commentsSQL[] = $this->getCommentOnColumnSQL(
578 $tableNameSQL,
579 $newColumn->getQuotedName($this),
580 $newColumn->getComment(),
581 );
582 }
583
584 if (count($modifyColumnSQL) > 0) {
585 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $modifyColumnSQL) . ')';
586 }
587
588 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
589 $oldColumnName = new Identifier($oldColumnName);
590
591 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this)
592 . ' TO ' . $column->getQuotedName($this);
593 }
594
595 $dropColumnSQL = [];
596 foreach ($diff->getDroppedColumns() as $column) {
597 $dropColumnSQL[] = $column->getQuotedName($this);
598 }
599
600 if (count($dropColumnSQL) > 0) {
601 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $dropColumnSQL) . ')';
602 }
603
604 return array_merge(
605 $this->getPreAlterTableIndexForeignKeySQL($diff),
606 $sql,
607 $commentsSQL,
608 $this->getPostAlterTableIndexForeignKeySQL($diff),
609 $columnSql,
610 );
611 }
612
613 /**
614 * {@inheritDoc}
615 *
616 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
617 */
618 public function getColumnDeclarationSQL(string $name, array $column): string
619 {
620 if (isset($column['columnDefinition'])) {
621 $declaration = $column['columnDefinition'];
622 } else {
623 $default = $this->getDefaultValueDeclarationSQL($column);
624
625 $notnull = '';
626
627 if (isset($column['notnull'])) {
628 $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL';
629 }
630
631 $typeDecl = $column['type']->getSQLDeclaration($column, $this);
632 $declaration = $typeDecl . $default . $notnull;
633 }
634
635 return $name . ' ' . $declaration;
636 }
637
638 /**
639 * {@inheritDoc}
640 */
641 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
642 {
643 if (str_contains($tableName, '.')) {
644 [$schema] = explode('.', $tableName);
645 $oldIndexName = $schema . '.' . $oldIndexName;
646 }
647
648 return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
649 }
650
651 protected function getIdentitySequenceName(string $tableName): string
652 {
653 $table = new Identifier($tableName);
654
655 // No usage of column name to preserve BC compatibility with <2.5
656 $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ');
657
658 if ($table->isQuoted()) {
659 $identitySequenceName = '"' . $identitySequenceName . '"';
660 }
661
662 $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
663
664 return $identitySequenceIdentifier->getQuotedName($this);
665 }
666
667 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
668 public function supportsCommentOnStatement(): bool
669 {
670 return true;
671 }
672
673 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
674 {
675 if ($offset > 0) {
676 $query .= sprintf(' OFFSET %d ROWS', $offset);
677 }
678
679 if ($limit !== null) {
680 $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit);
681 }
682
683 return $query;
684 }
685
686 public function getCreateTemporaryTableSnippetSQL(): string
687 {
688 return 'CREATE GLOBAL TEMPORARY TABLE';
689 }
690
691 public function getDateTimeTzFormatString(): string
692 {
693 return 'Y-m-d H:i:sP';
694 }
695
696 public function getDateFormatString(): string
697 {
698 return 'Y-m-d 00:00:00';
699 }
700
701 public function getTimeFormatString(): string
702 {
703 return '1900-01-01 H:i:s';
704 }
705
706 public function getMaxIdentifierLength(): int
707 {
708 return 128;
709 }
710
711 public function supportsSequences(): bool
712 {
713 return true;
714 }
715
716 public function supportsReleaseSavepoints(): bool
717 {
718 return false;
719 }
720
721 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
722 {
723 $tableIdentifier = new Identifier($tableName);
724
725 return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
726 }
727
728 public function getDummySelectSQL(string $expression = '1'): string
729 {
730 return sprintf('SELECT %s FROM DUAL', $expression);
731 }
732
733 protected function initializeDoctrineTypeMappings(): void
734 {
735 $this->doctrineTypeMapping = [
736 'binary_double' => Types::FLOAT,
737 'binary_float' => Types::FLOAT,
738 'binary_integer' => Types::BOOLEAN,
739 'blob' => Types::BLOB,
740 'char' => Types::STRING,
741 'clob' => Types::TEXT,
742 'date' => Types::DATE_MUTABLE,
743 'float' => Types::FLOAT,
744 'integer' => Types::INTEGER,
745 'long' => Types::STRING,
746 'long raw' => Types::BLOB,
747 'nchar' => Types::STRING,
748 'nclob' => Types::TEXT,
749 'number' => Types::INTEGER,
750 'nvarchar2' => Types::STRING,
751 'pls_integer' => Types::BOOLEAN,
752 'raw' => Types::BINARY,
753 'rowid' => Types::STRING,
754 'timestamp' => Types::DATETIME_MUTABLE,
755 'timestamptz' => Types::DATETIMETZ_MUTABLE,
756 'urowid' => Types::STRING,
757 'varchar' => Types::STRING,
758 'varchar2' => Types::STRING,
759 ];
760 }
761
762 public function releaseSavePoint(string $savepoint): string
763 {
764 return '';
765 }
766
767 protected function createReservedKeywordsList(): KeywordList
768 {
769 return new OracleKeywords();
770 }
771
772 /**
773 * {@inheritDoc}
774 */
775 public function getBlobTypeDeclarationSQL(array $column): string
776 {
777 return 'BLOB';
778 }
779
780 public function createSchemaManager(Connection $connection): OracleSchemaManager
781 {
782 return new OracleSchemaManager($connection, $this);
783 }
784}
diff --git a/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php
new file mode 100644
index 0000000..166ee75
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php
@@ -0,0 +1,784 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Platforms\Keywords\KeywordList;
9use Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords;
10use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11use Doctrine\DBAL\Schema\Identifier;
12use Doctrine\DBAL\Schema\Index;
13use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;
14use Doctrine\DBAL\Schema\Sequence;
15use Doctrine\DBAL\Schema\TableDiff;
16use Doctrine\DBAL\TransactionIsolationLevel;
17use Doctrine\DBAL\Types\Types;
18use UnexpectedValueException;
19
20use function array_merge;
21use function array_unique;
22use function array_values;
23use function explode;
24use function implode;
25use function in_array;
26use function is_array;
27use function is_bool;
28use function is_numeric;
29use function is_string;
30use function sprintf;
31use function str_contains;
32use function strtolower;
33use function trim;
34
35/**
36 * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform.
37 */
38class PostgreSQLPlatform extends AbstractPlatform
39{
40 private bool $useBooleanTrueFalseStrings = true;
41
42 /** @var string[][] PostgreSQL booleans literals */
43 private array $booleanLiterals = [
44 'true' => [
45 't',
46 'true',
47 'y',
48 'yes',
49 'on',
50 '1',
51 ],
52 'false' => [
53 'f',
54 'false',
55 'n',
56 'no',
57 'off',
58 '0',
59 ],
60 ];
61
62 /**
63 * PostgreSQL has different behavior with some drivers
64 * with regard to how booleans have to be handled.
65 *
66 * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
67 */
68 public function setUseBooleanTrueFalseStrings(bool $flag): void
69 {
70 $this->useBooleanTrueFalseStrings = $flag;
71 }
72
73 public function getRegexpExpression(): string
74 {
75 return 'SIMILAR TO';
76 }
77
78 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
79 {
80 if ($start !== null) {
81 $string = $this->getSubstringExpression($string, $start);
82
83 return 'CASE WHEN (POSITION(' . $substring . ' IN ' . $string . ') = 0) THEN 0'
84 . ' ELSE (POSITION(' . $substring . ' IN ' . $string . ') + ' . $start . ' - 1) END';
85 }
86
87 return sprintf('POSITION(%s IN %s)', $substring, $string);
88 }
89
90 protected function getDateArithmeticIntervalExpression(
91 string $date,
92 string $operator,
93 string $interval,
94 DateIntervalUnit $unit,
95 ): string {
96 if ($unit === DateIntervalUnit::QUARTER) {
97 $interval = $this->multiplyInterval($interval, 3);
98 $unit = DateIntervalUnit::MONTH;
99 }
100
101 return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit->value . "')::interval)";
102 }
103
104 public function getDateDiffExpression(string $date1, string $date2): string
105 {
106 return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
107 }
108
109 public function getCurrentDatabaseExpression(): string
110 {
111 return 'CURRENT_DATABASE()';
112 }
113
114 public function supportsSequences(): bool
115 {
116 return true;
117 }
118
119 public function supportsSchemas(): bool
120 {
121 return true;
122 }
123
124 public function supportsIdentityColumns(): bool
125 {
126 return true;
127 }
128
129 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
130 public function supportsPartialIndexes(): bool
131 {
132 return true;
133 }
134
135 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
136 public function supportsCommentOnStatement(): bool
137 {
138 return true;
139 }
140
141 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
142 public function getListDatabasesSQL(): string
143 {
144 return 'SELECT datname FROM pg_database';
145 }
146
147 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
148 public function getListSequencesSQL(string $database): string
149 {
150 return 'SELECT sequence_name AS relname,
151 sequence_schema AS schemaname,
152 minimum_value AS min_value,
153 increment AS increment_by
154 FROM information_schema.sequences
155 WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . "
156 AND sequence_schema NOT LIKE 'pg\_%'
157 AND sequence_schema != 'information_schema'";
158 }
159
160 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
161 public function getListViewsSQL(string $database): string
162 {
163 return 'SELECT quote_ident(table_name) AS viewname,
164 table_schema AS schemaname,
165 view_definition AS definition
166 FROM information_schema.views
167 WHERE view_definition IS NOT NULL';
168 }
169
170 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
171 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
172 {
173 $query = '';
174
175 if ($foreignKey->hasOption('match')) {
176 $query .= ' MATCH ' . $foreignKey->getOption('match');
177 }
178
179 $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
180
181 if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
182 $query .= ' DEFERRABLE';
183 } else {
184 $query .= ' NOT DEFERRABLE';
185 }
186
187 if (
188 $foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false
189 ) {
190 $query .= ' INITIALLY DEFERRED';
191 } else {
192 $query .= ' INITIALLY IMMEDIATE';
193 }
194
195 return $query;
196 }
197
198 /**
199 * {@inheritDoc}
200 */
201 public function getAlterTableSQL(TableDiff $diff): array
202 {
203 $sql = [];
204 $commentsSQL = [];
205 $columnSql = [];
206
207 $table = $diff->getOldTable();
208
209 $tableNameSQL = $table->getQuotedName($this);
210
211 foreach ($diff->getAddedColumns() as $addedColumn) {
212 $query = 'ADD ' . $this->getColumnDeclarationSQL(
213 $addedColumn->getQuotedName($this),
214 $addedColumn->toArray(),
215 );
216
217 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
218
219 $comment = $addedColumn->getComment();
220
221 if ($comment === '') {
222 continue;
223 }
224
225 $commentsSQL[] = $this->getCommentOnColumnSQL(
226 $tableNameSQL,
227 $addedColumn->getQuotedName($this),
228 $comment,
229 );
230 }
231
232 foreach ($diff->getDroppedColumns() as $droppedColumn) {
233 $query = 'DROP ' . $droppedColumn->getQuotedName($this);
234 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
235 }
236
237 foreach ($diff->getModifiedColumns() as $columnDiff) {
238 $oldColumn = $columnDiff->getOldColumn();
239 $newColumn = $columnDiff->getNewColumn();
240
241 $oldColumnName = $oldColumn->getQuotedName($this);
242
243 if (
244 $columnDiff->hasTypeChanged()
245 || $columnDiff->hasPrecisionChanged()
246 || $columnDiff->hasScaleChanged()
247 || $columnDiff->hasFixedChanged()
248 ) {
249 $type = $newColumn->getType();
250
251 // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
252 $columnDefinition = $newColumn->toArray();
253 $columnDefinition['autoincrement'] = false;
254
255 // here was a server version check before, but DBAL API does not support this anymore.
256 $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this);
257 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
258 }
259
260 if ($columnDiff->hasDefaultChanged()) {
261 $defaultClause = $newColumn->getDefault() === null
262 ? ' DROP DEFAULT'
263 : ' SET' . $this->getDefaultValueDeclarationSQL($newColumn->toArray());
264
265 $query = 'ALTER ' . $oldColumnName . $defaultClause;
266 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
267 }
268
269 if ($columnDiff->hasNotNullChanged()) {
270 $query = 'ALTER ' . $oldColumnName . ' ' . ($newColumn->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
271 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
272 }
273
274 if ($columnDiff->hasAutoIncrementChanged()) {
275 if ($newColumn->getAutoincrement()) {
276 $query = 'ADD GENERATED BY DEFAULT AS IDENTITY';
277 } else {
278 $query = 'DROP IDENTITY';
279 }
280
281 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ALTER ' . $oldColumnName . ' ' . $query;
282 }
283
284 $newComment = $newColumn->getComment();
285 $oldComment = $columnDiff->getOldColumn()->getComment();
286
287 if ($columnDiff->hasCommentChanged() || $oldComment !== $newComment) {
288 $commentsSQL[] = $this->getCommentOnColumnSQL(
289 $tableNameSQL,
290 $newColumn->getQuotedName($this),
291 $newComment,
292 );
293 }
294
295 if (! $columnDiff->hasLengthChanged()) {
296 continue;
297 }
298
299 $query = 'ALTER ' . $oldColumnName . ' TYPE '
300 . $newColumn->getType()->getSQLDeclaration($newColumn->toArray(), $this);
301 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
302 }
303
304 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
305 $oldColumnName = new Identifier($oldColumnName);
306
307 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this)
308 . ' TO ' . $column->getQuotedName($this);
309 }
310
311 return array_merge(
312 $this->getPreAlterTableIndexForeignKeySQL($diff),
313 $sql,
314 $commentsSQL,
315 $this->getPostAlterTableIndexForeignKeySQL($diff),
316 $columnSql,
317 );
318 }
319
320 /**
321 * {@inheritDoc}
322 */
323 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
324 {
325 if (str_contains($tableName, '.')) {
326 [$schema] = explode('.', $tableName);
327 $oldIndexName = $schema . '.' . $oldIndexName;
328 }
329
330 return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
331 }
332
333 public function getCreateSequenceSQL(Sequence $sequence): string
334 {
335 return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
336 ' INCREMENT BY ' . $sequence->getAllocationSize() .
337 ' MINVALUE ' . $sequence->getInitialValue() .
338 ' START ' . $sequence->getInitialValue() .
339 $this->getSequenceCacheSQL($sequence);
340 }
341
342 public function getAlterSequenceSQL(Sequence $sequence): string
343 {
344 return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
345 ' INCREMENT BY ' . $sequence->getAllocationSize() .
346 $this->getSequenceCacheSQL($sequence);
347 }
348
349 /**
350 * Cache definition for sequences
351 */
352 private function getSequenceCacheSQL(Sequence $sequence): string
353 {
354 if ($sequence->getCache() > 1) {
355 return ' CACHE ' . $sequence->getCache();
356 }
357
358 return '';
359 }
360
361 public function getDropSequenceSQL(string $name): string
362 {
363 return parent::getDropSequenceSQL($name) . ' CASCADE';
364 }
365
366 public function getDropForeignKeySQL(string $foreignKey, string $table): string
367 {
368 return $this->getDropConstraintSQL($foreignKey, $table);
369 }
370
371 public function getDropIndexSQL(string $name, string $table): string
372 {
373 if ($name === '"primary"') {
374 $constraintName = $table . '_pkey';
375
376 return $this->getDropConstraintSQL($constraintName, $table);
377 }
378
379 return parent::getDropIndexSQL($name, $table);
380 }
381
382 /**
383 * {@inheritDoc}
384 */
385 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
386 {
387 $queryFields = $this->getColumnDeclarationListSQL($columns);
388
389 if (isset($options['primary']) && ! empty($options['primary'])) {
390 $keyColumns = array_unique(array_values($options['primary']));
391 $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
392 }
393
394 $unlogged = isset($options['unlogged']) && $options['unlogged'] === true ? ' UNLOGGED' : '';
395
396 $query = 'CREATE' . $unlogged . ' TABLE ' . $name . ' (' . $queryFields . ')';
397
398 $sql = [$query];
399
400 if (isset($options['indexes']) && ! empty($options['indexes'])) {
401 foreach ($options['indexes'] as $index) {
402 $sql[] = $this->getCreateIndexSQL($index, $name);
403 }
404 }
405
406 if (isset($options['uniqueConstraints'])) {
407 foreach ($options['uniqueConstraints'] as $uniqueConstraint) {
408 $sql[] = $this->getCreateUniqueConstraintSQL($uniqueConstraint, $name);
409 }
410 }
411
412 if (isset($options['foreignKeys'])) {
413 foreach ($options['foreignKeys'] as $definition) {
414 $sql[] = $this->getCreateForeignKeySQL($definition, $name);
415 }
416 }
417
418 return $sql;
419 }
420
421 /**
422 * Converts a single boolean value.
423 *
424 * First converts the value to its native PHP boolean type
425 * and passes it to the given callback function to be reconverted
426 * into any custom representation.
427 *
428 * @param mixed $value The value to convert.
429 * @param callable $callback The callback function to use for converting the real boolean value.
430 *
431 * @throws UnexpectedValueException
432 */
433 private function convertSingleBooleanValue(mixed $value, callable $callback): mixed
434 {
435 if ($value === null) {
436 return $callback(null);
437 }
438
439 if (is_bool($value) || is_numeric($value)) {
440 return $callback((bool) $value);
441 }
442
443 if (! is_string($value)) {
444 return $callback(true);
445 }
446
447 /**
448 * Better safe than sorry: http://php.net/in_array#106319
449 */
450 if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) {
451 return $callback(false);
452 }
453
454 if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) {
455 return $callback(true);
456 }
457
458 throw new UnexpectedValueException(sprintf(
459 'Unrecognized boolean literal, %s given.',
460 $value,
461 ));
462 }
463
464 /**
465 * Converts one or multiple boolean values.
466 *
467 * First converts the value(s) to their native PHP boolean type
468 * and passes them to the given callback function to be reconverted
469 * into any custom representation.
470 *
471 * @param mixed $item The value(s) to convert.
472 * @param callable $callback The callback function to use for converting the real boolean value(s).
473 */
474 private function doConvertBooleans(mixed $item, callable $callback): mixed
475 {
476 if (is_array($item)) {
477 foreach ($item as $key => $value) {
478 $item[$key] = $this->convertSingleBooleanValue($value, $callback);
479 }
480
481 return $item;
482 }
483
484 return $this->convertSingleBooleanValue($item, $callback);
485 }
486
487 /**
488 * {@inheritDoc}
489 *
490 * Postgres wants boolean values converted to the strings 'true'/'false'.
491 */
492 public function convertBooleans(mixed $item): mixed
493 {
494 if (! $this->useBooleanTrueFalseStrings) {
495 return parent::convertBooleans($item);
496 }
497
498 return $this->doConvertBooleans(
499 $item,
500 /** @param mixed $value */
501 static function ($value): string {
502 if ($value === null) {
503 return 'NULL';
504 }
505
506 return $value === true ? 'true' : 'false';
507 },
508 );
509 }
510
511 public function convertBooleansToDatabaseValue(mixed $item): mixed
512 {
513 if (! $this->useBooleanTrueFalseStrings) {
514 return parent::convertBooleansToDatabaseValue($item);
515 }
516
517 return $this->doConvertBooleans(
518 $item,
519 /** @param mixed $value */
520 static function ($value): ?int {
521 return $value === null ? null : (int) $value;
522 },
523 );
524 }
525
526 /**
527 * @param T $item
528 *
529 * @return (T is null ? null : bool)
530 *
531 * @template T
532 */
533 public function convertFromBoolean(mixed $item): ?bool
534 {
535 if (in_array($item, $this->booleanLiterals['false'], true)) {
536 return false;
537 }
538
539 return parent::convertFromBoolean($item);
540 }
541
542 public function getSequenceNextValSQL(string $sequence): string
543 {
544 return "SELECT NEXTVAL('" . $sequence . "')";
545 }
546
547 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
548 {
549 return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
550 . $this->_getTransactionIsolationLevelSQL($level);
551 }
552
553 /**
554 * {@inheritDoc}
555 */
556 public function getBooleanTypeDeclarationSQL(array $column): string
557 {
558 return 'BOOLEAN';
559 }
560
561 /**
562 * {@inheritDoc}
563 */
564 public function getIntegerTypeDeclarationSQL(array $column): string
565 {
566 return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
567 }
568
569 /**
570 * {@inheritDoc}
571 */
572 public function getBigIntTypeDeclarationSQL(array $column): string
573 {
574 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
575 }
576
577 /**
578 * {@inheritDoc}
579 */
580 public function getSmallIntTypeDeclarationSQL(array $column): string
581 {
582 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
583 }
584
585 /**
586 * {@inheritDoc}
587 */
588 public function getGuidTypeDeclarationSQL(array $column): string
589 {
590 return 'UUID';
591 }
592
593 /**
594 * {@inheritDoc}
595 */
596 public function getDateTimeTypeDeclarationSQL(array $column): string
597 {
598 return 'TIMESTAMP(0) WITHOUT TIME ZONE';
599 }
600
601 /**
602 * {@inheritDoc}
603 */
604 public function getDateTimeTzTypeDeclarationSQL(array $column): string
605 {
606 return 'TIMESTAMP(0) WITH TIME ZONE';
607 }
608
609 /**
610 * {@inheritDoc}
611 */
612 public function getDateTypeDeclarationSQL(array $column): string
613 {
614 return 'DATE';
615 }
616
617 /**
618 * {@inheritDoc}
619 */
620 public function getTimeTypeDeclarationSQL(array $column): string
621 {
622 return 'TIME(0) WITHOUT TIME ZONE';
623 }
624
625 /**
626 * {@inheritDoc}
627 */
628 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
629 {
630 if (! empty($column['autoincrement'])) {
631 return ' GENERATED BY DEFAULT AS IDENTITY';
632 }
633
634 return '';
635 }
636
637 protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string
638 {
639 $sql = 'VARCHAR';
640
641 if ($length !== null) {
642 $sql .= sprintf('(%d)', $length);
643 }
644
645 return $sql;
646 }
647
648 protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string
649 {
650 return 'BYTEA';
651 }
652
653 protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string
654 {
655 return 'BYTEA';
656 }
657
658 /**
659 * {@inheritDoc}
660 */
661 public function getClobTypeDeclarationSQL(array $column): string
662 {
663 return 'TEXT';
664 }
665
666 public function getDateTimeTzFormatString(): string
667 {
668 return 'Y-m-d H:i:sO';
669 }
670
671 public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string
672 {
673 return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
674 }
675
676 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
677 {
678 $tableIdentifier = new Identifier($tableName);
679 $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
680
681 if ($cascade) {
682 $sql .= ' CASCADE';
683 }
684
685 return $sql;
686 }
687
688 protected function initializeDoctrineTypeMappings(): void
689 {
690 $this->doctrineTypeMapping = [
691 'bigint' => Types::BIGINT,
692 'bigserial' => Types::BIGINT,
693 'bool' => Types::BOOLEAN,
694 'boolean' => Types::BOOLEAN,
695 'bpchar' => Types::STRING,
696 'bytea' => Types::BLOB,
697 'char' => Types::STRING,
698 'date' => Types::DATE_MUTABLE,
699 'datetime' => Types::DATETIME_MUTABLE,
700 'decimal' => Types::DECIMAL,
701 'double' => Types::FLOAT,
702 'double precision' => Types::FLOAT,
703 'float' => Types::FLOAT,
704 'float4' => Types::FLOAT,
705 'float8' => Types::FLOAT,
706 'inet' => Types::STRING,
707 'int' => Types::INTEGER,
708 'int2' => Types::SMALLINT,
709 'int4' => Types::INTEGER,
710 'int8' => Types::BIGINT,
711 'integer' => Types::INTEGER,
712 'interval' => Types::STRING,
713 'json' => Types::JSON,
714 'jsonb' => Types::JSON,
715 'money' => Types::DECIMAL,
716 'numeric' => Types::DECIMAL,
717 'serial' => Types::INTEGER,
718 'serial4' => Types::INTEGER,
719 'serial8' => Types::BIGINT,
720 'real' => Types::FLOAT,
721 'smallint' => Types::SMALLINT,
722 'text' => Types::TEXT,
723 'time' => Types::TIME_MUTABLE,
724 'timestamp' => Types::DATETIME_MUTABLE,
725 'timestamptz' => Types::DATETIMETZ_MUTABLE,
726 'timetz' => Types::TIME_MUTABLE,
727 'tsvector' => Types::TEXT,
728 'uuid' => Types::GUID,
729 'varchar' => Types::STRING,
730 'year' => Types::DATE_MUTABLE,
731 '_varchar' => Types::STRING,
732 ];
733 }
734
735 protected function createReservedKeywordsList(): KeywordList
736 {
737 return new PostgreSQLKeywords();
738 }
739
740 /**
741 * {@inheritDoc}
742 */
743 public function getBlobTypeDeclarationSQL(array $column): string
744 {
745 return 'BYTEA';
746 }
747
748 /**
749 * {@inheritDoc}
750 *
751 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
752 */
753 public function getDefaultValueDeclarationSQL(array $column): string
754 {
755 if (isset($column['autoincrement']) && $column['autoincrement'] === true) {
756 return '';
757 }
758
759 return parent::getDefaultValueDeclarationSQL($column);
760 }
761
762 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
763 public function supportsColumnCollation(): bool
764 {
765 return true;
766 }
767
768 /**
769 * {@inheritDoc}
770 */
771 public function getJsonTypeDeclarationSQL(array $column): string
772 {
773 if (! empty($column['jsonb'])) {
774 return 'JSONB';
775 }
776
777 return 'JSON';
778 }
779
780 public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager
781 {
782 return new PostgreSQLSchemaManager($connection, $this);
783 }
784}
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php
new file mode 100644
index 0000000..aa8d9fb
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php
@@ -0,0 +1,50 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLServer;
6
7use Doctrine\DBAL\Platforms\SQLServerPlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use Doctrine\DBAL\Schema\TableDiff;
11
12/**
13 * Compares schemas in the context of SQL Server platform.
14 *
15 * @link https://docs.microsoft.com/en-us/sql/t-sql/statements/collations?view=sql-server-ver15
16 */
17class Comparator extends BaseComparator
18{
19 /** @internal The comparator can be only instantiated by a schema manager. */
20 public function __construct(SQLServerPlatform $platform, private readonly string $databaseCollation)
21 {
22 parent::__construct($platform);
23 }
24
25 public function compareTables(Table $oldTable, Table $newTable): TableDiff
26 {
27 return parent::compareTables(
28 $this->normalizeColumns($oldTable),
29 $this->normalizeColumns($newTable),
30 );
31 }
32
33 private function normalizeColumns(Table $table): Table
34 {
35 $table = clone $table;
36
37 foreach ($table->getColumns() as $column) {
38 $options = $column->getPlatformOptions();
39
40 if (! isset($options['collation']) || $options['collation'] !== $this->databaseCollation) {
41 continue;
42 }
43
44 unset($options['collation']);
45 $column->setPlatformOptions($options);
46 }
47
48 return $table;
49 }
50}
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php
new file mode 100644
index 0000000..ea6bb60
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php
@@ -0,0 +1,84 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder;
6
7use Doctrine\DBAL\Platforms\SQLServerPlatform;
8use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
9use Doctrine\DBAL\Query\SelectQuery;
10use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
11
12use function count;
13use function implode;
14
15final class SQLServerSelectSQLBuilder implements SelectSQLBuilder
16{
17 /** @internal The SQL builder should be instantiated only by database platforms. */
18 public function __construct(
19 private readonly SQLServerPlatform $platform,
20 ) {
21 }
22
23 public function buildSQL(SelectQuery $query): string
24 {
25 $parts = ['SELECT'];
26
27 if ($query->isDistinct()) {
28 $parts[] = 'DISTINCT';
29 }
30
31 $parts[] = implode(', ', $query->getColumns());
32
33 $from = $query->getFrom();
34
35 if (count($from) > 0) {
36 $parts[] = 'FROM ' . implode(', ', $from);
37 }
38
39 $forUpdate = $query->getForUpdate();
40
41 if ($forUpdate !== null) {
42 $with = ['UPDLOCK', 'ROWLOCK'];
43
44 if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) {
45 $with[] = 'READPAST';
46 }
47
48 $parts[] = 'WITH (' . implode(', ', $with) . ')';
49 }
50
51 $where = $query->getWhere();
52
53 if ($where !== null) {
54 $parts[] = 'WHERE ' . $where;
55 }
56
57 $groupBy = $query->getGroupBy();
58
59 if (count($groupBy) > 0) {
60 $parts[] = 'GROUP BY ' . implode(', ', $groupBy);
61 }
62
63 $having = $query->getHaving();
64
65 if ($having !== null) {
66 $parts[] = 'HAVING ' . $having;
67 }
68
69 $orderBy = $query->getOrderBy();
70
71 if (count($orderBy) > 0) {
72 $parts[] = 'ORDER BY ' . implode(', ', $orderBy);
73 }
74
75 $sql = implode(' ', $parts);
76 $limit = $query->getLimit();
77
78 if ($limit->isDefined()) {
79 $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult());
80 }
81
82 return $sql;
83 }
84}
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php b/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php
new file mode 100644
index 0000000..7a10a32
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php
@@ -0,0 +1,1223 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
9use Doctrine\DBAL\LockMode;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords;
12use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder;
13use Doctrine\DBAL\Schema\Column;
14use Doctrine\DBAL\Schema\ColumnDiff;
15use Doctrine\DBAL\Schema\Identifier;
16use Doctrine\DBAL\Schema\Index;
17use Doctrine\DBAL\Schema\Sequence;
18use Doctrine\DBAL\Schema\SQLServerSchemaManager;
19use Doctrine\DBAL\Schema\TableDiff;
20use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
21use Doctrine\DBAL\TransactionIsolationLevel;
22use Doctrine\DBAL\Types\Types;
23use InvalidArgumentException;
24
25use function array_merge;
26use function array_unique;
27use function array_values;
28use function explode;
29use function implode;
30use function is_array;
31use function is_bool;
32use function is_numeric;
33use function preg_match;
34use function preg_match_all;
35use function sprintf;
36use function str_contains;
37use function str_ends_with;
38use function str_replace;
39use function str_starts_with;
40use function strtoupper;
41use function substr;
42use function substr_count;
43
44use const PREG_OFFSET_CAPTURE;
45
46/**
47 * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform
48 * of the oldest supported version.
49 */
50class SQLServerPlatform extends AbstractPlatform
51{
52 /** @internal Should be used only from within the {@see AbstractSchemaManager} class hierarchy. */
53 public const OPTION_DEFAULT_CONSTRAINT_NAME = 'default_constraint_name';
54
55 public function createSelectSQLBuilder(): SelectSQLBuilder
56 {
57 return new SQLServerSelectSQLBuilder($this);
58 }
59
60 public function getCurrentDateSQL(): string
61 {
62 return $this->getConvertExpression('date', 'GETDATE()');
63 }
64
65 public function getCurrentTimeSQL(): string
66 {
67 return $this->getConvertExpression('time', 'GETDATE()');
68 }
69
70 /**
71 * Returns an expression that converts an expression of one data type to another.
72 *
73 * @param string $dataType The target native data type. Alias data types cannot be used.
74 * @param string $expression The SQL expression to convert.
75 */
76 private function getConvertExpression(string $dataType, string $expression): string
77 {
78 return sprintf('CONVERT(%s, %s)', $dataType, $expression);
79 }
80
81 protected function getDateArithmeticIntervalExpression(
82 string $date,
83 string $operator,
84 string $interval,
85 DateIntervalUnit $unit,
86 ): string {
87 $factorClause = '';
88
89 if ($operator === '-') {
90 $factorClause = '-1 * ';
91 }
92
93 return 'DATEADD(' . $unit->value . ', ' . $factorClause . $interval . ', ' . $date . ')';
94 }
95
96 public function getDateDiffExpression(string $date1, string $date2): string
97 {
98 return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')';
99 }
100
101 /**
102 * {@inheritDoc}
103 *
104 * Microsoft SQL Server supports this through AUTO_INCREMENT columns.
105 */
106 public function supportsIdentityColumns(): bool
107 {
108 return true;
109 }
110
111 public function supportsReleaseSavepoints(): bool
112 {
113 return false;
114 }
115
116 public function supportsSchemas(): bool
117 {
118 return true;
119 }
120
121 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
122 public function supportsColumnCollation(): bool
123 {
124 return true;
125 }
126
127 public function supportsSequences(): bool
128 {
129 return true;
130 }
131
132 public function getAlterSequenceSQL(Sequence $sequence): string
133 {
134 return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
135 ' INCREMENT BY ' . $sequence->getAllocationSize();
136 }
137
138 public function getCreateSequenceSQL(Sequence $sequence): string
139 {
140 return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
141 ' START WITH ' . $sequence->getInitialValue() .
142 ' INCREMENT BY ' . $sequence->getAllocationSize() .
143 ' MINVALUE ' . $sequence->getInitialValue();
144 }
145
146 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
147 public function getListSequencesSQL(string $database): string
148 {
149 return 'SELECT seq.name,
150 CAST(
151 seq.increment AS VARCHAR(MAX)
152 ) AS increment, -- CAST avoids driver error for sql_variant type
153 CAST(
154 seq.start_value AS VARCHAR(MAX)
155 ) AS start_value -- CAST avoids driver error for sql_variant type
156 FROM sys.sequences AS seq';
157 }
158
159 public function getSequenceNextValSQL(string $sequence): string
160 {
161 return 'SELECT NEXT VALUE FOR ' . $sequence;
162 }
163
164 public function getDropForeignKeySQL(string $foreignKey, string $table): string
165 {
166 return $this->getDropConstraintSQL($foreignKey, $table);
167 }
168
169 public function getDropIndexSQL(string $name, string $table): string
170 {
171 return 'DROP INDEX ' . $name . ' ON ' . $table;
172 }
173
174 /**
175 * {@inheritDoc}
176 */
177 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
178 {
179 $defaultConstraintsSql = [];
180 $commentsSql = [];
181
182 $tableComment = $options['comment'] ?? null;
183 if ($tableComment !== null) {
184 $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment);
185 }
186
187 // @todo does other code breaks because of this?
188 // force primary keys to be not null
189 foreach ($columns as &$column) {
190 if (! empty($column['primary'])) {
191 $column['notnull'] = true;
192 }
193
194 // Build default constraints SQL statements.
195 if (isset($column['default'])) {
196 $defaultConstraintsSql[] = 'ALTER TABLE ' . $name .
197 ' ADD' . $this->getDefaultConstraintDeclarationSQL($column);
198 }
199
200 if (empty($column['comment']) && ! is_numeric($column['comment'])) {
201 continue;
202 }
203
204 $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']);
205 }
206
207 $columnListSql = $this->getColumnDeclarationListSQL($columns);
208
209 if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
210 foreach ($options['uniqueConstraints'] as $definition) {
211 $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition);
212 }
213 }
214
215 if (isset($options['primary']) && ! empty($options['primary'])) {
216 $flags = '';
217 if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) {
218 $flags = ' NONCLUSTERED';
219 }
220
221 $columnListSql .= ', PRIMARY KEY' . $flags
222 . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')';
223 }
224
225 $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql;
226
227 $check = $this->getCheckDeclarationSQL($columns);
228 if (! empty($check)) {
229 $query .= ', ' . $check;
230 }
231
232 $query .= ')';
233
234 $sql = [$query];
235
236 if (isset($options['indexes']) && ! empty($options['indexes'])) {
237 foreach ($options['indexes'] as $index) {
238 $sql[] = $this->getCreateIndexSQL($index, $name);
239 }
240 }
241
242 if (isset($options['foreignKeys'])) {
243 foreach ($options['foreignKeys'] as $definition) {
244 $sql[] = $this->getCreateForeignKeySQL($definition, $name);
245 }
246 }
247
248 return array_merge($sql, $commentsSql, $defaultConstraintsSql);
249 }
250
251 public function getCreatePrimaryKeySQL(Index $index, string $table): string
252 {
253 $sql = 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY';
254
255 if ($index->hasFlag('nonclustered')) {
256 $sql .= ' NONCLUSTERED';
257 }
258
259 return $sql . ' (' . implode(', ', $index->getQuotedColumns($this)) . ')';
260 }
261
262 private function unquoteSingleIdentifier(string $possiblyQuotedName): string
263 {
264 return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']')
265 ? substr($possiblyQuotedName, 1, -1)
266 : $possiblyQuotedName;
267 }
268
269 /**
270 * Returns the SQL statement for creating a column comment.
271 *
272 * SQL Server does not support native column comments,
273 * therefore the extended properties functionality is used
274 * as a workaround to store them.
275 * The property name used to store column comments is "MS_Description"
276 * which provides compatibility with SQL Server Management Studio,
277 * as column comments are stored in the same property there when
278 * specifying a column's "Description" attribute.
279 *
280 * @param string $tableName The quoted table name to which the column belongs.
281 * @param string $columnName The quoted column name to create the comment for.
282 * @param string $comment The column's comment.
283 */
284 protected function getCreateColumnCommentSQL(string $tableName, string $columnName, string $comment): string
285 {
286 if (str_contains($tableName, '.')) {
287 [$schemaName, $tableName] = explode('.', $tableName);
288 } else {
289 $schemaName = 'dbo';
290 }
291
292 return $this->getAddExtendedPropertySQL(
293 'MS_Description',
294 $comment,
295 'SCHEMA',
296 $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)),
297 'TABLE',
298 $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)),
299 'COLUMN',
300 $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)),
301 );
302 }
303
304 /**
305 * Returns the SQL snippet for declaring a default constraint.
306 *
307 * @param mixed[] $column Column definition.
308 */
309 protected function getDefaultConstraintDeclarationSQL(array $column): string
310 {
311 if (! isset($column['default'])) {
312 throw new InvalidArgumentException('Incomplete column definition. "default" required.');
313 }
314
315 $columnName = new Identifier($column['name']);
316
317 return $this->getDefaultValueDeclarationSQL($column) . ' FOR ' . $columnName->getQuotedName($this);
318 }
319
320 public function getCreateIndexSQL(Index $index, string $table): string
321 {
322 $constraint = parent::getCreateIndexSQL($index, $table);
323
324 if ($index->isUnique() && ! $index->isPrimary()) {
325 $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
326 }
327
328 return $constraint;
329 }
330
331 protected function getCreateIndexSQLFlags(Index $index): string
332 {
333 $type = '';
334 if ($index->isUnique()) {
335 $type .= 'UNIQUE ';
336 }
337
338 if ($index->hasFlag('clustered')) {
339 $type .= 'CLUSTERED ';
340 } elseif ($index->hasFlag('nonclustered')) {
341 $type .= 'NONCLUSTERED ';
342 }
343
344 return $type;
345 }
346
347 /**
348 * Extend unique key constraint with required filters
349 */
350 private function _appendUniqueConstraintDefinition(string $sql, Index $index): string
351 {
352 $fields = [];
353
354 foreach ($index->getQuotedColumns($this) as $field) {
355 $fields[] = $field . ' IS NOT NULL';
356 }
357
358 return $sql . ' WHERE ' . implode(' AND ', $fields);
359 }
360
361 /**
362 * {@inheritDoc}
363 */
364 public function getAlterTableSQL(TableDiff $diff): array
365 {
366 $queryParts = [];
367 $sql = [];
368 $columnSql = [];
369 $commentsSql = [];
370
371 $table = $diff->getOldTable();
372
373 $tableName = $table->getName();
374
375 foreach ($diff->getAddedColumns() as $column) {
376 $columnProperties = $column->toArray();
377
378 $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties);
379
380 if (isset($columnProperties['default'])) {
381 $addColumnSql .= $this->getDefaultValueDeclarationSQL($columnProperties);
382 }
383
384 $queryParts[] = $addColumnSql;
385
386 $comment = $column->getComment();
387
388 if ($comment === '') {
389 continue;
390 }
391
392 $commentsSql[] = $this->getCreateColumnCommentSQL(
393 $tableName,
394 $column->getQuotedName($this),
395 $comment,
396 );
397 }
398
399 foreach ($diff->getDroppedColumns() as $column) {
400 if ($column->getDefault() !== null) {
401 $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($column);
402 }
403
404 $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
405 }
406
407 foreach ($diff->getModifiedColumns() as $columnDiff) {
408 $newColumn = $columnDiff->getNewColumn();
409 $newComment = $newColumn->getComment();
410 $hasNewComment = $newComment !== '';
411
412 $oldColumn = $columnDiff->getOldColumn();
413 $oldComment = $oldColumn->getComment();
414 $hasOldComment = $oldComment !== '';
415
416 if ($hasOldComment && $hasNewComment && $newComment !== $oldComment) {
417 $commentsSql[] = $this->getAlterColumnCommentSQL(
418 $tableName,
419 $newColumn->getQuotedName($this),
420 $newComment,
421 );
422 } elseif ($hasOldComment && ! $hasNewComment) {
423 $commentsSql[] = $this->getDropColumnCommentSQL(
424 $tableName,
425 $newColumn->getQuotedName($this),
426 );
427 } elseif (! $hasOldComment && $hasNewComment) {
428 $commentsSql[] = $this->getCreateColumnCommentSQL(
429 $tableName,
430 $newColumn->getQuotedName($this),
431 $newComment,
432 );
433 }
434
435 $columnNameSQL = $newColumn->getQuotedName($this);
436
437 $oldDeclarationSQL = $this->getColumnDeclarationSQL($columnNameSQL, $oldColumn->toArray());
438 $newDeclarationSQL = $this->getColumnDeclarationSQL($columnNameSQL, $newColumn->toArray());
439
440 $declarationSQLChanged = $newDeclarationSQL !== $oldDeclarationSQL;
441 $defaultChanged = $columnDiff->hasDefaultChanged();
442
443 if (! $declarationSQLChanged && ! $defaultChanged) {
444 continue;
445 }
446
447 $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff);
448
449 if ($requireDropDefaultConstraint) {
450 $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($oldColumn);
451 }
452
453 if ($declarationSQLChanged) {
454 $queryParts[] = 'ALTER COLUMN ' . $newDeclarationSQL;
455 }
456
457 if (
458 $newColumn->getDefault() === null
459 || (! $requireDropDefaultConstraint && ! $defaultChanged)
460 ) {
461 continue;
462 }
463
464 $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn);
465 }
466
467 $tableNameSQL = $table->getQuotedName($this);
468
469 foreach ($diff->getRenamedColumns() as $oldColumnName => $newColumn) {
470 $oldColumnName = new Identifier($oldColumnName);
471
472 $sql[] = sprintf(
473 "sp_rename '%s.%s', '%s', 'COLUMN'",
474 $tableNameSQL,
475 $oldColumnName->getQuotedName($this),
476 $newColumn->getQuotedName($this),
477 );
478 }
479
480 foreach ($queryParts as $query) {
481 $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query;
482 }
483
484 return array_merge(
485 $this->getPreAlterTableIndexForeignKeySQL($diff),
486 $sql,
487 $commentsSql,
488 $this->getPostAlterTableIndexForeignKeySQL($diff),
489 $columnSql,
490 );
491 }
492
493 public function getRenameTableSQL(string $oldName, string $newName): string
494 {
495 return sprintf(
496 'sp_rename %s, %s',
497 $this->quoteStringLiteral($oldName),
498 $this->quoteStringLiteral($newName),
499 );
500 }
501
502 /**
503 * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement.
504 *
505 * @param string $tableName The name of the table to generate the clause for.
506 * @param Column $column The column to generate the clause for.
507 */
508 private function getAlterTableAddDefaultConstraintClause(string $tableName, Column $column): string
509 {
510 $columnDef = $column->toArray();
511 $columnDef['name'] = $column->getQuotedName($this);
512
513 return 'ADD' . $this->getDefaultConstraintDeclarationSQL($columnDef);
514 }
515
516 /**
517 * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement.
518 */
519 private function getAlterTableDropDefaultConstraintClause(Column $column): string
520 {
521 if (! $column->hasPlatformOption(self::OPTION_DEFAULT_CONSTRAINT_NAME)) {
522 throw new InvalidArgumentException(
523 'Column ' . $column->getName() . ' was not properly introspected as it has a default value'
524 . ' but does not have the default constraint name.',
525 );
526 }
527
528 return 'DROP CONSTRAINT ' . $this->quoteIdentifier(
529 $column->getPlatformOption(self::OPTION_DEFAULT_CONSTRAINT_NAME),
530 );
531 }
532
533 /**
534 * Checks whether a column alteration requires dropping its default constraint first.
535 *
536 * Different to other database vendors SQL Server implements column default values
537 * as constraints and therefore changes in a column's default value as well as changes
538 * in a column's type require dropping the default constraint first before being to
539 * alter the particular column to the new definition.
540 */
541 private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool
542 {
543 // We only need to drop an existing default constraint if we know the
544 // column was defined with a default value before.
545 if ($columnDiff->getOldColumn()->getDefault() === null) {
546 return false;
547 }
548
549 // We need to drop an existing default constraint if the column was
550 // defined with a default value before and it has changed.
551 if ($columnDiff->hasDefaultChanged()) {
552 return true;
553 }
554
555 // We need to drop an existing default constraint if the column was
556 // defined with a default value before and the native column type has changed.
557 return $columnDiff->hasTypeChanged() || $columnDiff->hasFixedChanged();
558 }
559
560 /**
561 * Returns the SQL statement for altering a column comment.
562 *
563 * SQL Server does not support native column comments,
564 * therefore the extended properties functionality is used
565 * as a workaround to store them.
566 * The property name used to store column comments is "MS_Description"
567 * which provides compatibility with SQL Server Management Studio,
568 * as column comments are stored in the same property there when
569 * specifying a column's "Description" attribute.
570 *
571 * @param string $tableName The quoted table name to which the column belongs.
572 * @param string $columnName The quoted column name to alter the comment for.
573 * @param string $comment The column's comment.
574 */
575 protected function getAlterColumnCommentSQL(string $tableName, string $columnName, string $comment): string
576 {
577 if (str_contains($tableName, '.')) {
578 [$schemaName, $tableName] = explode('.', $tableName);
579 } else {
580 $schemaName = 'dbo';
581 }
582
583 return $this->getUpdateExtendedPropertySQL(
584 'MS_Description',
585 $comment,
586 'SCHEMA',
587 $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)),
588 'TABLE',
589 $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)),
590 'COLUMN',
591 $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)),
592 );
593 }
594
595 /**
596 * Returns the SQL statement for dropping a column comment.
597 *
598 * SQL Server does not support native column comments,
599 * therefore the extended properties functionality is used
600 * as a workaround to store them.
601 * The property name used to store column comments is "MS_Description"
602 * which provides compatibility with SQL Server Management Studio,
603 * as column comments are stored in the same property there when
604 * specifying a column's "Description" attribute.
605 *
606 * @param string $tableName The quoted table name to which the column belongs.
607 * @param string $columnName The quoted column name to drop the comment for.
608 */
609 protected function getDropColumnCommentSQL(string $tableName, string $columnName): string
610 {
611 if (str_contains($tableName, '.')) {
612 [$schemaName, $tableName] = explode('.', $tableName);
613 } else {
614 $schemaName = 'dbo';
615 }
616
617 return $this->getDropExtendedPropertySQL(
618 'MS_Description',
619 'SCHEMA',
620 $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)),
621 'TABLE',
622 $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)),
623 'COLUMN',
624 $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)),
625 );
626 }
627
628 /**
629 * {@inheritDoc}
630 */
631 protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
632 {
633 return [sprintf(
634 "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'",
635 $tableName,
636 $oldIndexName,
637 $index->getQuotedName($this),
638 ),
639 ];
640 }
641
642 /**
643 * Returns the SQL statement for adding an extended property to a database object.
644 *
645 * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx
646 *
647 * @param string $name The name of the property to add.
648 * @param string|null $value The value of the property to add.
649 * @param string|null $level0Type The type of the object at level 0 the property belongs to.
650 * @param string|null $level0Name The name of the object at level 0 the property belongs to.
651 * @param string|null $level1Type The type of the object at level 1 the property belongs to.
652 * @param string|null $level1Name The name of the object at level 1 the property belongs to.
653 * @param string|null $level2Type The type of the object at level 2 the property belongs to.
654 * @param string|null $level2Name The name of the object at level 2 the property belongs to.
655 */
656 protected function getAddExtendedPropertySQL(
657 string $name,
658 ?string $value = null,
659 ?string $level0Type = null,
660 ?string $level0Name = null,
661 ?string $level1Type = null,
662 ?string $level1Name = null,
663 ?string $level2Type = null,
664 ?string $level2Name = null,
665 ): string {
666 return 'EXEC sp_addextendedproperty ' .
667 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' .
668 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' .
669 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name .
670 ($level2Type !== null || $level2Name !== null
671 ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name
672 : ''
673 );
674 }
675
676 /**
677 * Returns the SQL statement for dropping an extended property from a database object.
678 *
679 * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx
680 *
681 * @param string $name The name of the property to drop.
682 * @param string|null $level0Type The type of the object at level 0 the property belongs to.
683 * @param string|null $level0Name The name of the object at level 0 the property belongs to.
684 * @param string|null $level1Type The type of the object at level 1 the property belongs to.
685 * @param string|null $level1Name The name of the object at level 1 the property belongs to.
686 * @param string|null $level2Type The type of the object at level 2 the property belongs to.
687 * @param string|null $level2Name The name of the object at level 2 the property belongs to.
688 */
689 protected function getDropExtendedPropertySQL(
690 string $name,
691 ?string $level0Type = null,
692 ?string $level0Name = null,
693 ?string $level1Type = null,
694 ?string $level1Name = null,
695 ?string $level2Type = null,
696 ?string $level2Name = null,
697 ): string {
698 return 'EXEC sp_dropextendedproperty ' .
699 'N' . $this->quoteStringLiteral($name) . ', ' .
700 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' .
701 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name .
702 ($level2Type !== null || $level2Name !== null
703 ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name
704 : ''
705 );
706 }
707
708 /**
709 * Returns the SQL statement for updating an extended property of a database object.
710 *
711 * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx
712 *
713 * @param string $name The name of the property to update.
714 * @param string|null $value The value of the property to update.
715 * @param string|null $level0Type The type of the object at level 0 the property belongs to.
716 * @param string|null $level0Name The name of the object at level 0 the property belongs to.
717 * @param string|null $level1Type The type of the object at level 1 the property belongs to.
718 * @param string|null $level1Name The name of the object at level 1 the property belongs to.
719 * @param string|null $level2Type The type of the object at level 2 the property belongs to.
720 * @param string|null $level2Name The name of the object at level 2 the property belongs to.
721 */
722 protected function getUpdateExtendedPropertySQL(
723 string $name,
724 ?string $value = null,
725 ?string $level0Type = null,
726 ?string $level0Name = null,
727 ?string $level1Type = null,
728 ?string $level1Name = null,
729 ?string $level2Type = null,
730 ?string $level2Name = null,
731 ): string {
732 return 'EXEC sp_updateextendedproperty ' .
733 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' .
734 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' .
735 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name .
736 ($level2Type !== null || $level2Name !== null
737 ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name
738 : ''
739 );
740 }
741
742 public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string
743 {
744 return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
745 }
746
747 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
748 public function getListViewsSQL(string $database): string
749 {
750 return "SELECT name, definition FROM sysobjects
751 INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id
752 WHERE type = 'V' ORDER BY name";
753 }
754
755 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
756 {
757 if ($start === null) {
758 return sprintf('CHARINDEX(%s, %s)', $substring, $string);
759 }
760
761 return sprintf('CHARINDEX(%s, %s, %s)', $substring, $string, $start);
762 }
763
764 public function getModExpression(string $dividend, string $divisor): string
765 {
766 return $dividend . ' % ' . $divisor;
767 }
768
769 public function getTrimExpression(
770 string $str,
771 TrimMode $mode = TrimMode::UNSPECIFIED,
772 ?string $char = null,
773 ): string {
774 if ($char === null) {
775 return match ($mode) {
776 TrimMode::LEADING => 'LTRIM(' . $str . ')',
777 TrimMode::TRAILING => 'RTRIM(' . $str . ')',
778 default => 'LTRIM(RTRIM(' . $str . '))',
779 };
780 }
781
782 $pattern = "'%[^' + " . $char . " + ']%'";
783
784 if ($mode === TrimMode::LEADING) {
785 return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)';
786 }
787
788 if ($mode === TrimMode::TRAILING) {
789 return 'reverse(stuff(reverse(' . $str . '), 1, '
790 . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))';
791 }
792
793 return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, '
794 . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str
795 . ') - 1, null))) - 1, null))';
796 }
797
798 public function getConcatExpression(string ...$string): string
799 {
800 return sprintf('CONCAT(%s)', implode(', ', $string));
801 }
802
803 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
804 public function getListDatabasesSQL(): string
805 {
806 return 'SELECT * FROM sys.databases';
807 }
808
809 public function getSubstringExpression(string $string, string $start, ?string $length = null): string
810 {
811 if ($length === null) {
812 return sprintf('SUBSTRING(%s, %s, LEN(%s) - %s + 1)', $string, $start, $string, $start);
813 }
814
815 return sprintf('SUBSTRING(%s, %s, %s)', $string, $start, $length);
816 }
817
818 public function getLengthExpression(string $string): string
819 {
820 return 'LEN(' . $string . ')';
821 }
822
823 public function getCurrentDatabaseExpression(): string
824 {
825 return 'DB_NAME()';
826 }
827
828 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
829 {
830 return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
831 }
832
833 /**
834 * {@inheritDoc}
835 */
836 public function getIntegerTypeDeclarationSQL(array $column): string
837 {
838 return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
839 }
840
841 /**
842 * {@inheritDoc}
843 */
844 public function getBigIntTypeDeclarationSQL(array $column): string
845 {
846 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
847 }
848
849 /**
850 * {@inheritDoc}
851 */
852 public function getSmallIntTypeDeclarationSQL(array $column): string
853 {
854 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
855 }
856
857 /**
858 * {@inheritDoc}
859 */
860 public function getGuidTypeDeclarationSQL(array $column): string
861 {
862 return 'UNIQUEIDENTIFIER';
863 }
864
865 /**
866 * {@inheritDoc}
867 */
868 public function getDateTimeTzTypeDeclarationSQL(array $column): string
869 {
870 return 'DATETIMEOFFSET(6)';
871 }
872
873 protected function getCharTypeDeclarationSQLSnippet(?int $length): string
874 {
875 $sql = 'NCHAR';
876
877 if ($length !== null) {
878 $sql .= sprintf('(%d)', $length);
879 }
880
881 return $sql;
882 }
883
884 protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string
885 {
886 if ($length === null) {
887 throw ColumnLengthRequired::new($this, 'NVARCHAR');
888 }
889
890 return sprintf('NVARCHAR(%d)', $length);
891 }
892
893 /**
894 * {@inheritDoc}
895 */
896 public function getAsciiStringTypeDeclarationSQL(array $column): string
897 {
898 $length = $column['length'] ?? null;
899
900 if (empty($column['fixed'])) {
901 return parent::getVarcharTypeDeclarationSQLSnippet($length);
902 }
903
904 return parent::getCharTypeDeclarationSQLSnippet($length);
905 }
906
907 /**
908 * {@inheritDoc}
909 */
910 public function getClobTypeDeclarationSQL(array $column): string
911 {
912 return 'VARCHAR(MAX)';
913 }
914
915 /**
916 * {@inheritDoc}
917 */
918 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
919 {
920 return ! empty($column['autoincrement']) ? ' IDENTITY' : '';
921 }
922
923 /**
924 * {@inheritDoc}
925 */
926 public function getDateTimeTypeDeclarationSQL(array $column): string
927 {
928 // 3 - microseconds precision length
929 // http://msdn.microsoft.com/en-us/library/ms187819.aspx
930 return 'DATETIME2(6)';
931 }
932
933 /**
934 * {@inheritDoc}
935 */
936 public function getDateTypeDeclarationSQL(array $column): string
937 {
938 return 'DATE';
939 }
940
941 /**
942 * {@inheritDoc}
943 */
944 public function getTimeTypeDeclarationSQL(array $column): string
945 {
946 return 'TIME(0)';
947 }
948
949 /**
950 * {@inheritDoc}
951 */
952 public function getBooleanTypeDeclarationSQL(array $column): string
953 {
954 return 'BIT';
955 }
956
957 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
958 {
959 if ($limit === null && $offset <= 0) {
960 return $query;
961 }
962
963 if ($this->shouldAddOrderBy($query)) {
964 if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) {
965 // SQL Server won't let us order by a non-selected column in a DISTINCT query,
966 // so we have to do this madness. This says, order by the first column in the
967 // result. SQL Server's docs say that a nonordered query's result order is non-
968 // deterministic anyway, so this won't do anything that a bunch of update and
969 // deletes to the table wouldn't do anyway.
970 $query .= ' ORDER BY 1';
971 } else {
972 // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you
973 // use constant expressions in the order by list.
974 $query .= ' ORDER BY (SELECT 0)';
975 }
976 }
977
978 // This looks somewhat like MYSQL, but limit/offset are in inverse positions
979 // Supposedly SQL:2008 core standard.
980 // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS.
981 $query .= sprintf(' OFFSET %d ROWS', $offset);
982
983 if ($limit !== null) {
984 $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit);
985 }
986
987 return $query;
988 }
989
990 public function convertBooleans(mixed $item): mixed
991 {
992 if (is_array($item)) {
993 foreach ($item as $key => $value) {
994 if (! is_bool($value) && ! is_numeric($value)) {
995 continue;
996 }
997
998 $item[$key] = (int) (bool) $value;
999 }
1000 } elseif (is_bool($item) || is_numeric($item)) {
1001 $item = (int) (bool) $item;
1002 }
1003
1004 return $item;
1005 }
1006
1007 public function getCreateTemporaryTableSnippetSQL(): string
1008 {
1009 return 'CREATE TABLE';
1010 }
1011
1012 public function getTemporaryTableName(string $tableName): string
1013 {
1014 return '#' . $tableName;
1015 }
1016
1017 public function getDateTimeFormatString(): string
1018 {
1019 return 'Y-m-d H:i:s.u';
1020 }
1021
1022 public function getDateFormatString(): string
1023 {
1024 return 'Y-m-d';
1025 }
1026
1027 public function getTimeFormatString(): string
1028 {
1029 return 'H:i:s';
1030 }
1031
1032 public function getDateTimeTzFormatString(): string
1033 {
1034 return 'Y-m-d H:i:s.u P';
1035 }
1036
1037 protected function initializeDoctrineTypeMappings(): void
1038 {
1039 $this->doctrineTypeMapping = [
1040 'bigint' => Types::BIGINT,
1041 'binary' => Types::BINARY,
1042 'bit' => Types::BOOLEAN,
1043 'blob' => Types::BLOB,
1044 'char' => Types::STRING,
1045 'date' => Types::DATE_MUTABLE,
1046 'datetime' => Types::DATETIME_MUTABLE,
1047 'datetime2' => Types::DATETIME_MUTABLE,
1048 'datetimeoffset' => Types::DATETIMETZ_MUTABLE,
1049 'decimal' => Types::DECIMAL,
1050 'double' => Types::FLOAT,
1051 'double precision' => Types::FLOAT,
1052 'float' => Types::FLOAT,
1053 'image' => Types::BLOB,
1054 'int' => Types::INTEGER,
1055 'money' => Types::INTEGER,
1056 'nchar' => Types::STRING,
1057 'ntext' => Types::TEXT,
1058 'numeric' => Types::DECIMAL,
1059 'nvarchar' => Types::STRING,
1060 'real' => Types::FLOAT,
1061 'smalldatetime' => Types::DATETIME_MUTABLE,
1062 'smallint' => Types::SMALLINT,
1063 'smallmoney' => Types::INTEGER,
1064 'text' => Types::TEXT,
1065 'time' => Types::TIME_MUTABLE,
1066 'tinyint' => Types::SMALLINT,
1067 'uniqueidentifier' => Types::GUID,
1068 'varbinary' => Types::BINARY,
1069 'varchar' => Types::STRING,
1070 ];
1071 }
1072
1073 public function createSavePoint(string $savepoint): string
1074 {
1075 return 'SAVE TRANSACTION ' . $savepoint;
1076 }
1077
1078 public function releaseSavePoint(string $savepoint): string
1079 {
1080 return '';
1081 }
1082
1083 public function rollbackSavePoint(string $savepoint): string
1084 {
1085 return 'ROLLBACK TRANSACTION ' . $savepoint;
1086 }
1087
1088 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
1089 public function getForeignKeyReferentialActionSQL(string $action): string
1090 {
1091 // RESTRICT is not supported, therefore falling back to NO ACTION.
1092 if (strtoupper($action) === 'RESTRICT') {
1093 return 'NO ACTION';
1094 }
1095
1096 return parent::getForeignKeyReferentialActionSQL($action);
1097 }
1098
1099 public function appendLockHint(string $fromClause, LockMode $lockMode): string
1100 {
1101 return match ($lockMode) {
1102 LockMode::NONE,
1103 LockMode::OPTIMISTIC => $fromClause,
1104 LockMode::PESSIMISTIC_READ => $fromClause . ' WITH (HOLDLOCK, ROWLOCK)',
1105 LockMode::PESSIMISTIC_WRITE => $fromClause . ' WITH (UPDLOCK, ROWLOCK)',
1106 };
1107 }
1108
1109 protected function createReservedKeywordsList(): KeywordList
1110 {
1111 return new SQLServerKeywords();
1112 }
1113
1114 public function quoteSingleIdentifier(string $str): string
1115 {
1116 return '[' . str_replace(']', ']]', $str) . ']';
1117 }
1118
1119 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
1120 {
1121 $tableIdentifier = new Identifier($tableName);
1122
1123 return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1124 }
1125
1126 /**
1127 * {@inheritDoc}
1128 */
1129 public function getBlobTypeDeclarationSQL(array $column): string
1130 {
1131 return 'VARBINARY(MAX)';
1132 }
1133
1134 /**
1135 * {@inheritDoc}
1136 *
1137 * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1138 */
1139 public function getColumnDeclarationSQL(string $name, array $column): string
1140 {
1141 if (isset($column['columnDefinition'])) {
1142 $declaration = $column['columnDefinition'];
1143 } else {
1144 $collation = ! empty($column['collation']) ?
1145 ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : '';
1146
1147 $notnull = ! empty($column['notnull']) ? ' NOT NULL' : '';
1148
1149 $typeDecl = $column['type']->getSQLDeclaration($column, $this);
1150 $declaration = $typeDecl . $collation . $notnull;
1151 }
1152
1153 return $name . ' ' . $declaration;
1154 }
1155
1156 /**
1157 * SQL Server does not support quoting collation identifiers.
1158 */
1159 public function getColumnCollationDeclarationSQL(string $collation): string
1160 {
1161 return 'COLLATE ' . $collation;
1162 }
1163
1164 public function columnsEqual(Column $column1, Column $column2): bool
1165 {
1166 if (! parent::columnsEqual($column1, $column2)) {
1167 return false;
1168 }
1169
1170 return $this->getDefaultValueDeclarationSQL($column1->toArray())
1171 === $this->getDefaultValueDeclarationSQL($column2->toArray());
1172 }
1173
1174 protected function getLikeWildcardCharacters(): string
1175 {
1176 return parent::getLikeWildcardCharacters() . '[]^';
1177 }
1178
1179 protected function getCommentOnTableSQL(string $tableName, string $comment): string
1180 {
1181 return $this->getAddExtendedPropertySQL(
1182 'MS_Description',
1183 $comment,
1184 'SCHEMA',
1185 $this->quoteStringLiteral('dbo'),
1186 'TABLE',
1187 $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)),
1188 );
1189 }
1190
1191 private function shouldAddOrderBy(string $query): bool
1192 {
1193 // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement
1194 // but can be in a newline
1195 $matches = [];
1196 $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE);
1197 if ($matchesCount === 0) {
1198 return true;
1199 }
1200
1201 // ORDER BY instance may be in a subquery after ORDER BY
1202 // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2)
1203 // if in the searched query ORDER BY clause was found where
1204 // number of open parentheses after the occurrence of the clause is equal to
1205 // number of closed brackets after the occurrence of the clause,
1206 // it means that ORDER BY is included in the query being checked
1207 while ($matchesCount > 0) {
1208 $orderByPos = $matches[0][--$matchesCount][1];
1209 $openBracketsCount = substr_count($query, '(', $orderByPos);
1210 $closedBracketsCount = substr_count($query, ')', $orderByPos);
1211 if ($openBracketsCount === $closedBracketsCount) {
1212 return false;
1213 }
1214 }
1215
1216 return true;
1217 }
1218
1219 public function createSchemaManager(Connection $connection): SQLServerSchemaManager
1220 {
1221 return new SQLServerSchemaManager($connection, $this);
1222 }
1223}
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php
new file mode 100644
index 0000000..f27e1b4
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php
@@ -0,0 +1,52 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLite;
6
7use Doctrine\DBAL\Platforms\SQLitePlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use Doctrine\DBAL\Schema\TableDiff;
11
12use function strcasecmp;
13
14/**
15 * Compares schemas in the context of SQLite platform.
16 *
17 * BINARY is the default column collation and should be ignored if specified explicitly.
18 */
19class Comparator extends BaseComparator
20{
21 /** @internal The comparator can be only instantiated by a schema manager. */
22 public function __construct(SQLitePlatform $platform)
23 {
24 parent::__construct($platform);
25 }
26
27 public function compareTables(Table $oldTable, Table $newTable): TableDiff
28 {
29 return parent::compareTables(
30 $this->normalizeColumns($oldTable),
31 $this->normalizeColumns($newTable),
32 );
33 }
34
35 private function normalizeColumns(Table $table): Table
36 {
37 $table = clone $table;
38
39 foreach ($table->getColumns() as $column) {
40 $options = $column->getPlatformOptions();
41
42 if (! isset($options['collation']) || strcasecmp($options['collation'], 'binary') !== 0) {
43 continue;
44 }
45
46 unset($options['collation']);
47 $column->setPlatformOptions($options);
48 }
49
50 return $table;
51 }
52}
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php b/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php
new file mode 100644
index 0000000..acec163
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php
@@ -0,0 +1,994 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\Exception\NotSupported;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords;
12use Doctrine\DBAL\Schema\Column;
13use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
14use Doctrine\DBAL\Schema\ForeignKeyConstraint;
15use Doctrine\DBAL\Schema\Identifier;
16use Doctrine\DBAL\Schema\Index;
17use Doctrine\DBAL\Schema\SQLiteSchemaManager;
18use Doctrine\DBAL\Schema\Table;
19use Doctrine\DBAL\Schema\TableDiff;
20use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
21use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
22use Doctrine\DBAL\TransactionIsolationLevel;
23use Doctrine\DBAL\Types;
24use InvalidArgumentException;
25
26use function array_combine;
27use function array_keys;
28use function array_merge;
29use function array_search;
30use function array_unique;
31use function array_values;
32use function count;
33use function explode;
34use function implode;
35use function sprintf;
36use function str_replace;
37use function strpos;
38use function strtolower;
39use function substr;
40use function trim;
41
42/**
43 * The SQLitePlatform class describes the specifics and dialects of the SQLite
44 * database platform.
45 */
46class SQLitePlatform extends AbstractPlatform
47{
48 public function getCreateDatabaseSQL(string $name): string
49 {
50 throw NotSupported::new(__METHOD__);
51 }
52
53 public function getDropDatabaseSQL(string $name): string
54 {
55 throw NotSupported::new(__METHOD__);
56 }
57
58 public function getRegexpExpression(): string
59 {
60 return 'REGEXP';
61 }
62
63 public function getModExpression(string $dividend, string $divisor): string
64 {
65 return $dividend . ' % ' . $divisor;
66 }
67
68 public function getTrimExpression(
69 string $str,
70 TrimMode $mode = TrimMode::UNSPECIFIED,
71 ?string $char = null,
72 ): string {
73 $trimFn = match ($mode) {
74 TrimMode::UNSPECIFIED,
75 TrimMode::BOTH => 'TRIM',
76 TrimMode::LEADING => 'LTRIM',
77 TrimMode::TRAILING => 'RTRIM',
78 };
79
80 $arguments = [$str];
81
82 if ($char !== null) {
83 $arguments[] = $char;
84 }
85
86 return sprintf('%s(%s)', $trimFn, implode(', ', $arguments));
87 }
88
89 public function getSubstringExpression(string $string, string $start, ?string $length = null): string
90 {
91 if ($length === null) {
92 return sprintf('SUBSTR(%s, %s)', $string, $start);
93 }
94
95 return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
96 }
97
98 public function getLocateExpression(string $string, string $substring, ?string $start = null): string
99 {
100 if ($start === null || $start === '1') {
101 return sprintf('INSTR(%s, %s)', $string, $substring);
102 }
103
104 return sprintf(
105 'CASE WHEN INSTR(SUBSTR(%1$s, %3$s), %2$s) > 0 THEN INSTR(SUBSTR(%1$s, %3$s), %2$s) + %3$s - 1 ELSE 0 END',
106 $string,
107 $substring,
108 $start,
109 );
110 }
111
112 protected function getDateArithmeticIntervalExpression(
113 string $date,
114 string $operator,
115 string $interval,
116 DateIntervalUnit $unit,
117 ): string {
118 switch ($unit) {
119 case DateIntervalUnit::WEEK:
120 $interval = $this->multiplyInterval($interval, 7);
121 $unit = DateIntervalUnit::DAY;
122 break;
123
124 case DateIntervalUnit::QUARTER:
125 $interval = $this->multiplyInterval($interval, 3);
126 $unit = DateIntervalUnit::MONTH;
127 break;
128 }
129
130 return 'DATETIME(' . $date . ',' . $this->getConcatExpression(
131 $this->quoteStringLiteral($operator),
132 $interval,
133 $this->quoteStringLiteral(' ' . $unit->value),
134 ) . ')';
135 }
136
137 public function getDateDiffExpression(string $date1, string $date2): string
138 {
139 return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
140 }
141
142 /**
143 * {@inheritDoc}
144 *
145 * The DBAL doesn't support databases on the SQLite platform. The expression here always returns a fixed string
146 * as an indicator of an implicitly selected database.
147 *
148 * @link https://www.sqlite.org/lang_select.html
149 * @see Connection::getDatabase()
150 */
151 public function getCurrentDatabaseExpression(): string
152 {
153 return "'main'";
154 }
155
156 /** @link https://www2.sqlite.org/cvstrac/wiki?p=UnsupportedSql */
157 public function createSelectSQLBuilder(): SelectSQLBuilder
158 {
159 return new DefaultSelectSQLBuilder($this, null, null);
160 }
161
162 protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string
163 {
164 return match ($level) {
165 TransactionIsolationLevel::READ_UNCOMMITTED => '0',
166 TransactionIsolationLevel::READ_COMMITTED,
167 TransactionIsolationLevel::REPEATABLE_READ,
168 TransactionIsolationLevel::SERIALIZABLE => '1',
169 };
170 }
171
172 public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string
173 {
174 return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
175 }
176
177 /**
178 * {@inheritDoc}
179 */
180 public function getBooleanTypeDeclarationSQL(array $column): string
181 {
182 return 'BOOLEAN';
183 }
184
185 /**
186 * {@inheritDoc}
187 */
188 public function getIntegerTypeDeclarationSQL(array $column): string
189 {
190 return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column);
191 }
192
193 /**
194 * {@inheritDoc}
195 */
196 public function getBigIntTypeDeclarationSQL(array $column): string
197 {
198 // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
199 if (! empty($column['autoincrement'])) {
200 return $this->getIntegerTypeDeclarationSQL($column);
201 }
202
203 return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
204 }
205
206 /**
207 * {@inheritDoc}
208 */
209 public function getSmallIntTypeDeclarationSQL(array $column): string
210 {
211 // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
212 if (! empty($column['autoincrement'])) {
213 return $this->getIntegerTypeDeclarationSQL($column);
214 }
215
216 return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 public function getDateTimeTypeDeclarationSQL(array $column): string
223 {
224 return 'DATETIME';
225 }
226
227 /**
228 * {@inheritDoc}
229 */
230 public function getDateTypeDeclarationSQL(array $column): string
231 {
232 return 'DATE';
233 }
234
235 /**
236 * {@inheritDoc}
237 */
238 public function getTimeTypeDeclarationSQL(array $column): string
239 {
240 return 'TIME';
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 protected function _getCommonIntegerTypeDeclarationSQL(array $column): string
247 {
248 // sqlite autoincrement is only possible for the primary key
249 if (! empty($column['autoincrement'])) {
250 return ' PRIMARY KEY AUTOINCREMENT';
251 }
252
253 return ! empty($column['unsigned']) ? ' UNSIGNED' : '';
254 }
255
256 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
257 public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey): string
258 {
259 return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
260 $foreignKey->getQuotedLocalColumns($this),
261 $foreignKey->getQuotedForeignTableName($this),
262 $foreignKey->getQuotedForeignColumns($this),
263 $foreignKey->getName(),
264 $foreignKey->getOptions(),
265 ));
266 }
267
268 /**
269 * {@inheritDoc}
270 */
271 protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array
272 {
273 $queryFields = $this->getColumnDeclarationListSQL($columns);
274
275 if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
276 foreach ($options['uniqueConstraints'] as $definition) {
277 $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition);
278 }
279 }
280
281 $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
282
283 if (isset($options['foreignKeys'])) {
284 foreach ($options['foreignKeys'] as $foreignKey) {
285 $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
286 }
287 }
288
289 $tableComment = '';
290 if (isset($options['comment'])) {
291 $comment = trim($options['comment'], " '");
292
293 $tableComment = $this->getInlineTableCommentSQL($comment);
294 }
295
296 $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')'];
297
298 if (isset($options['alter']) && $options['alter'] === true) {
299 return $query;
300 }
301
302 if (isset($options['indexes']) && ! empty($options['indexes'])) {
303 foreach ($options['indexes'] as $indexDef) {
304 $query[] = $this->getCreateIndexSQL($indexDef, $name);
305 }
306 }
307
308 if (isset($options['unique']) && ! empty($options['unique'])) {
309 foreach ($options['unique'] as $indexDef) {
310 $query[] = $this->getCreateIndexSQL($indexDef, $name);
311 }
312 }
313
314 return $query;
315 }
316
317 /**
318 * Generate a PRIMARY KEY definition if no autoincrement value is used
319 *
320 * @param mixed[][] $columns
321 * @param mixed[] $options
322 */
323 private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string
324 {
325 if (empty($options['primary'])) {
326 return '';
327 }
328
329 $keyColumns = array_unique(array_values($options['primary']));
330
331 foreach ($keyColumns as $keyColumn) {
332 foreach ($columns as $column) {
333 if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) {
334 return '';
335 }
336 }
337 }
338
339 return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
340 }
341
342 protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string
343 {
344 return 'BLOB';
345 }
346
347 protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string
348 {
349 $sql = 'VARCHAR';
350
351 if ($length !== null) {
352 $sql .= sprintf('(%d)', $length);
353 }
354
355 return $sql;
356 }
357
358 protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string
359 {
360 return 'BLOB';
361 }
362
363 /**
364 * {@inheritDoc}
365 */
366 public function getClobTypeDeclarationSQL(array $column): string
367 {
368 return 'CLOB';
369 }
370
371 /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */
372 public function getListViewsSQL(string $database): string
373 {
374 return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
375 }
376
377 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
378 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string
379 {
380 $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
381
382 if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) {
383 $query .= ' NOT';
384 }
385
386 $query .= ' DEFERRABLE';
387 $query .= ' INITIALLY';
388
389 if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) {
390 $query .= ' DEFERRED';
391 } else {
392 $query .= ' IMMEDIATE';
393 }
394
395 return $query;
396 }
397
398 public function supportsIdentityColumns(): bool
399 {
400 return true;
401 }
402
403 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
404 public function supportsColumnCollation(): bool
405 {
406 return true;
407 }
408
409 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
410 public function supportsInlineColumnComments(): bool
411 {
412 return true;
413 }
414
415 public function getTruncateTableSQL(string $tableName, bool $cascade = false): string
416 {
417 $tableIdentifier = new Identifier($tableName);
418
419 return 'DELETE FROM ' . $tableIdentifier->getQuotedName($this);
420 }
421
422 /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */
423 public function getInlineColumnCommentSQL(string $comment): string
424 {
425 if ($comment === '') {
426 return '';
427 }
428
429 return '--' . str_replace("\n", "\n--", $comment) . "\n";
430 }
431
432 private function getInlineTableCommentSQL(string $comment): string
433 {
434 return $this->getInlineColumnCommentSQL($comment);
435 }
436
437 protected function initializeDoctrineTypeMappings(): void
438 {
439 $this->doctrineTypeMapping = [
440 'bigint' => 'bigint',
441 'bigserial' => 'bigint',
442 'blob' => 'blob',
443 'boolean' => 'boolean',
444 'char' => 'string',
445 'clob' => 'text',
446 'date' => 'date',
447 'datetime' => 'datetime',
448 'decimal' => 'decimal',
449 'double' => 'float',
450 'double precision' => 'float',
451 'float' => 'float',
452 'image' => 'string',
453 'int' => 'integer',
454 'integer' => 'integer',
455 'longtext' => 'text',
456 'longvarchar' => 'string',
457 'mediumint' => 'integer',
458 'mediumtext' => 'text',
459 'ntext' => 'string',
460 'numeric' => 'decimal',
461 'nvarchar' => 'string',
462 'real' => 'float',
463 'serial' => 'integer',
464 'smallint' => 'smallint',
465 'string' => 'string',
466 'text' => 'text',
467 'time' => 'time',
468 'timestamp' => 'datetime',
469 'tinyint' => 'boolean',
470 'tinytext' => 'text',
471 'varchar' => 'string',
472 'varchar2' => 'string',
473 ];
474 }
475
476 protected function createReservedKeywordsList(): KeywordList
477 {
478 return new SQLiteKeywords();
479 }
480
481 /**
482 * {@inheritDoc}
483 */
484 protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array
485 {
486 return [];
487 }
488
489 /**
490 * {@inheritDoc}
491 */
492 protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array
493 {
494 $table = $diff->getOldTable();
495
496 $sql = [];
497
498 foreach ($this->getIndexesInAlteredTable($diff, $table) as $index) {
499 if ($index->isPrimary()) {
500 continue;
501 }
502
503 $sql[] = $this->getCreateIndexSQL($index, $table->getQuotedName($this));
504 }
505
506 return $sql;
507 }
508
509 protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
510 {
511 if ($limit === null && $offset > 0) {
512 $limit = -1;
513 }
514
515 return parent::doModifyLimitQuery($query, $limit, $offset);
516 }
517
518 /**
519 * {@inheritDoc}
520 */
521 public function getBlobTypeDeclarationSQL(array $column): string
522 {
523 return 'BLOB';
524 }
525
526 public function getTemporaryTableName(string $tableName): string
527 {
528 return $tableName;
529 }
530
531 /**
532 * {@inheritDoc}
533 */
534 public function getCreateTablesSQL(array $tables): array
535 {
536 $sql = [];
537
538 foreach ($tables as $table) {
539 $sql = array_merge($sql, $this->getCreateTableSQL($table));
540 }
541
542 return $sql;
543 }
544
545 /** {@inheritDoc} */
546 public function getCreateIndexSQL(Index $index, string $table): string
547 {
548 $name = $index->getQuotedName($this);
549 $columns = $index->getColumns();
550
551 if (count($columns) === 0) {
552 throw new InvalidArgumentException(sprintf(
553 'Incomplete or invalid index definition %s on table %s',
554 $name,
555 $table,
556 ));
557 }
558
559 if ($index->isPrimary()) {
560 return $this->getCreatePrimaryKeySQL($index, $table);
561 }
562
563 if (strpos($table, '.') !== false) {
564 [$schema, $table] = explode('.', $table);
565 $name = $schema . '.' . $name;
566 }
567
568 $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
569 $query .= ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index);
570
571 return $query;
572 }
573
574 /**
575 * {@inheritDoc}
576 */
577 public function getDropTablesSQL(array $tables): array
578 {
579 $sql = [];
580
581 foreach ($tables as $table) {
582 $sql[] = $this->getDropTableSQL($table->getQuotedName($this));
583 }
584
585 return $sql;
586 }
587
588 public function getCreatePrimaryKeySQL(Index $index, string $table): string
589 {
590 throw NotSupported::new(__METHOD__);
591 }
592
593 public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, string $table): string
594 {
595 throw NotSupported::new(__METHOD__);
596 }
597
598 public function getDropForeignKeySQL(string $foreignKey, string $table): string
599 {
600 throw NotSupported::new(__METHOD__);
601 }
602
603 /**
604 * {@inheritDoc}
605 */
606 public function getAlterTableSQL(TableDiff $diff): array
607 {
608 $sql = $this->getSimpleAlterTableSQL($diff);
609 if ($sql !== false) {
610 return $sql;
611 }
612
613 $table = $diff->getOldTable();
614
615 $columns = [];
616 $oldColumnNames = [];
617 $newColumnNames = [];
618 $columnSql = [];
619
620 foreach ($table->getColumns() as $column) {
621 $columnName = strtolower($column->getName());
622 $columns[$columnName] = $column;
623 $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
624 }
625
626 foreach ($diff->getDroppedColumns() as $column) {
627 $columnName = strtolower($column->getName());
628 if (! isset($columns[$columnName])) {
629 continue;
630 }
631
632 unset(
633 $columns[$columnName],
634 $oldColumnNames[$columnName],
635 $newColumnNames[$columnName],
636 );
637 }
638
639 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
640 $oldColumnName = strtolower($oldColumnName);
641
642 $columns = $this->replaceColumn(
643 $table->getName(),
644 $columns,
645 $oldColumnName,
646 $column,
647 );
648
649 if (! isset($newColumnNames[$oldColumnName])) {
650 continue;
651 }
652
653 $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
654 }
655
656 foreach ($diff->getModifiedColumns() as $columnDiff) {
657 $oldColumnName = strtolower($columnDiff->getOldColumn()->getName());
658 $newColumn = $columnDiff->getNewColumn();
659
660 $columns = $this->replaceColumn(
661 $table->getName(),
662 $columns,
663 $oldColumnName,
664 $newColumn,
665 );
666
667 if (! isset($newColumnNames[$oldColumnName])) {
668 continue;
669 }
670
671 $newColumnNames[$oldColumnName] = $newColumn->getQuotedName($this);
672 }
673
674 foreach ($diff->getAddedColumns() as $column) {
675 $columns[strtolower($column->getName())] = $column;
676 }
677
678 $tableName = $table->getName();
679 $pos = strpos($tableName, '.');
680 if ($pos !== false) {
681 $tableName = substr($tableName, $pos + 1);
682 }
683
684 $dataTable = new Table('__temp__' . $tableName);
685
686 $newTable = new Table(
687 $table->getQuotedName($this),
688 $columns,
689 $this->getPrimaryIndexInAlteredTable($diff, $table),
690 [],
691 $this->getForeignKeysInAlteredTable($diff, $table),
692 $table->getOptions(),
693 );
694
695 $newTable->addOption('alter', true);
696
697 $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
698
699 $sql[] = sprintf(
700 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s',
701 $dataTable->getQuotedName($this),
702 implode(', ', $oldColumnNames),
703 $table->getQuotedName($this),
704 );
705 $sql[] = $this->getDropTableSQL($table->getQuotedName($this));
706
707 $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
708 $sql[] = sprintf(
709 'INSERT INTO %s (%s) SELECT %s FROM %s',
710 $newTable->getQuotedName($this),
711 implode(', ', $newColumnNames),
712 implode(', ', $oldColumnNames),
713 $dataTable->getQuotedName($this),
714 );
715 $sql[] = $this->getDropTableSQL($dataTable->getQuotedName($this));
716
717 return array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff), $columnSql);
718 }
719
720 /**
721 * Replace the column with the given name with the new column.
722 *
723 * @param array<string,Column> $columns
724 *
725 * @return array<string,Column>
726 *
727 * @throws Exception
728 */
729 private function replaceColumn(string $tableName, array $columns, string $columnName, Column $column): array
730 {
731 $keys = array_keys($columns);
732 $index = array_search($columnName, $keys, true);
733
734 if ($index === false) {
735 throw ColumnDoesNotExist::new($columnName, $tableName);
736 }
737
738 $values = array_values($columns);
739
740 $keys[$index] = strtolower($column->getName());
741 $values[$index] = $column;
742
743 return array_combine($keys, $values);
744 }
745
746 /**
747 * @return list<string>|false
748 *
749 * @throws Exception
750 */
751 private function getSimpleAlterTableSQL(TableDiff $diff): array|false
752 {
753 if (
754 count($diff->getModifiedColumns()) > 0
755 || count($diff->getDroppedColumns()) > 0
756 || count($diff->getRenamedColumns()) > 0
757 || count($diff->getAddedIndexes()) > 0
758 || count($diff->getModifiedIndexes()) > 0
759 || count($diff->getDroppedIndexes()) > 0
760 || count($diff->getRenamedIndexes()) > 0
761 || count($diff->getAddedForeignKeys()) > 0
762 || count($diff->getModifiedForeignKeys()) > 0
763 || count($diff->getDroppedForeignKeys()) > 0
764 ) {
765 return false;
766 }
767
768 $table = $diff->getOldTable();
769
770 $sql = [];
771 $columnSql = [];
772
773 foreach ($diff->getAddedColumns() as $column) {
774 $definition = array_merge([
775 'unique' => null,
776 'autoincrement' => null,
777 'default' => null,
778 ], $column->toArray());
779
780 $type = $definition['type'];
781
782 /** @psalm-suppress RiskyTruthyFalsyComparison */
783 switch (true) {
784 case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']:
785 case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL():
786 case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL():
787 case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL():
788 return false;
789 }
790
791 $definition['name'] = $column->getQuotedName($this);
792
793 $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN '
794 . $this->getColumnDeclarationSQL($definition['name'], $definition);
795 }
796
797 return array_merge($sql, $columnSql);
798 }
799
800 /** @return string[] */
801 private function getColumnNamesInAlteredTable(TableDiff $diff, Table $oldTable): array
802 {
803 $columns = [];
804
805 foreach ($oldTable->getColumns() as $column) {
806 $columnName = $column->getName();
807 $columns[strtolower($columnName)] = $columnName;
808 }
809
810 foreach ($diff->getDroppedColumns() as $column) {
811 $columnName = strtolower($column->getName());
812 if (! isset($columns[$columnName])) {
813 continue;
814 }
815
816 unset($columns[$columnName]);
817 }
818
819 foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
820 $columnName = $column->getName();
821 $columns[strtolower($oldColumnName)] = $columnName;
822 $columns[strtolower($columnName)] = $columnName;
823 }
824
825 foreach ($diff->getModifiedColumns() as $columnDiff) {
826 $oldColumnName = $columnDiff->getOldColumn()->getName();
827 $newColumnName = $columnDiff->getNewColumn()->getName();
828 $columns[strtolower($oldColumnName)] = $newColumnName;
829 $columns[strtolower($newColumnName)] = $newColumnName;
830 }
831
832 foreach ($diff->getAddedColumns() as $column) {
833 $columnName = $column->getName();
834 $columns[strtolower($columnName)] = $columnName;
835 }
836
837 return $columns;
838 }
839
840 /** @return Index[] */
841 private function getIndexesInAlteredTable(TableDiff $diff, Table $oldTable): array
842 {
843 $indexes = $oldTable->getIndexes();
844 $columnNames = $this->getColumnNamesInAlteredTable($diff, $oldTable);
845
846 foreach ($indexes as $key => $index) {
847 foreach ($diff->getRenamedIndexes() as $oldIndexName => $renamedIndex) {
848 if (strtolower($key) !== strtolower($oldIndexName)) {
849 continue;
850 }
851
852 unset($indexes[$key]);
853 }
854
855 $changed = false;
856 $indexColumns = [];
857 foreach ($index->getColumns() as $columnName) {
858 $normalizedColumnName = strtolower($columnName);
859 if (! isset($columnNames[$normalizedColumnName])) {
860 unset($indexes[$key]);
861 continue 2;
862 }
863
864 $indexColumns[] = $columnNames[$normalizedColumnName];
865 if ($columnName === $columnNames[$normalizedColumnName]) {
866 continue;
867 }
868
869 $changed = true;
870 }
871
872 if (! $changed) {
873 continue;
874 }
875
876 $indexes[$key] = new Index(
877 $index->getName(),
878 $indexColumns,
879 $index->isUnique(),
880 $index->isPrimary(),
881 $index->getFlags(),
882 );
883 }
884
885 foreach ($diff->getDroppedIndexes() as $index) {
886 $indexName = $index->getName();
887
888 if ($indexName === '') {
889 continue;
890 }
891
892 unset($indexes[strtolower($indexName)]);
893 }
894
895 foreach (
896 array_merge(
897 $diff->getModifiedIndexes(),
898 $diff->getAddedIndexes(),
899 $diff->getRenamedIndexes(),
900 ) as $index
901 ) {
902 $indexName = $index->getName();
903
904 if ($indexName !== '') {
905 $indexes[strtolower($indexName)] = $index;
906 } else {
907 $indexes[] = $index;
908 }
909 }
910
911 return $indexes;
912 }
913
914 /** @return ForeignKeyConstraint[] */
915 private function getForeignKeysInAlteredTable(TableDiff $diff, Table $oldTable): array
916 {
917 $foreignKeys = $oldTable->getForeignKeys();
918 $columnNames = $this->getColumnNamesInAlteredTable($diff, $oldTable);
919
920 foreach ($foreignKeys as $key => $constraint) {
921 $changed = false;
922 $localColumns = [];
923 foreach ($constraint->getLocalColumns() as $columnName) {
924 $normalizedColumnName = strtolower($columnName);
925 if (! isset($columnNames[$normalizedColumnName])) {
926 unset($foreignKeys[$key]);
927 continue 2;
928 }
929
930 $localColumns[] = $columnNames[$normalizedColumnName];
931 if ($columnName === $columnNames[$normalizedColumnName]) {
932 continue;
933 }
934
935 $changed = true;
936 }
937
938 if (! $changed) {
939 continue;
940 }
941
942 $foreignKeys[$key] = new ForeignKeyConstraint(
943 $localColumns,
944 $constraint->getForeignTableName(),
945 $constraint->getForeignColumns(),
946 $constraint->getName(),
947 $constraint->getOptions(),
948 );
949 }
950
951 foreach ($diff->getDroppedForeignKeys() as $constraint) {
952 $constraintName = $constraint->getName();
953
954 if ($constraintName === '') {
955 continue;
956 }
957
958 unset($foreignKeys[strtolower($constraintName)]);
959 }
960
961 foreach (array_merge($diff->getModifiedForeignKeys(), $diff->getAddedForeignKeys()) as $constraint) {
962 $constraintName = $constraint->getName();
963
964 if ($constraintName !== '') {
965 $foreignKeys[strtolower($constraintName)] = $constraint;
966 } else {
967 $foreignKeys[] = $constraint;
968 }
969 }
970
971 return $foreignKeys;
972 }
973
974 /** @return Index[] */
975 private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $oldTable): array
976 {
977 $primaryIndex = [];
978
979 foreach ($this->getIndexesInAlteredTable($diff, $oldTable) as $index) {
980 if (! $index->isPrimary()) {
981 continue;
982 }
983
984 $primaryIndex = [$index->getName() => $index];
985 }
986
987 return $primaryIndex;
988 }
989
990 public function createSchemaManager(Connection $connection): SQLiteSchemaManager
991 {
992 return new SQLiteSchemaManager($connection, $this);
993 }
994}
diff --git a/vendor/doctrine/dbal/src/Platforms/TrimMode.php b/vendor/doctrine/dbal/src/Platforms/TrimMode.php
new file mode 100644
index 0000000..31c2375
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Platforms/TrimMode.php
@@ -0,0 +1,13 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7enum TrimMode
8{
9 case UNSPECIFIED;
10 case LEADING;
11 case TRAILING;
12 case BOTH;
13}
diff --git a/vendor/doctrine/dbal/src/Portability/Connection.php b/vendor/doctrine/dbal/src/Portability/Connection.php
new file mode 100644
index 0000000..4a821d8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Connection.php
@@ -0,0 +1,41 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
8use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
9
10/**
11 * Portability wrapper for a Connection.
12 */
13final class Connection extends AbstractConnectionMiddleware
14{
15 public const PORTABILITY_ALL = 255;
16 public const PORTABILITY_NONE = 0;
17 public const PORTABILITY_RTRIM = 1;
18 public const PORTABILITY_EMPTY_TO_NULL = 4;
19 public const PORTABILITY_FIX_CASE = 8;
20
21 public function __construct(ConnectionInterface $connection, private readonly Converter $converter)
22 {
23 parent::__construct($connection);
24 }
25
26 public function prepare(string $sql): Statement
27 {
28 return new Statement(
29 parent::prepare($sql),
30 $this->converter,
31 );
32 }
33
34 public function query(string $sql): Result
35 {
36 return new Result(
37 parent::query($sql),
38 $this->converter,
39 );
40 }
41}
diff --git a/vendor/doctrine/dbal/src/Portability/Converter.php b/vendor/doctrine/dbal/src/Portability/Converter.php
new file mode 100644
index 0000000..09a4712
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Converter.php
@@ -0,0 +1,280 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Closure;
8
9use function array_change_key_case;
10use function array_map;
11use function array_reduce;
12use function is_string;
13use function rtrim;
14
15use const CASE_LOWER;
16use const CASE_UPPER;
17
18final class Converter
19{
20 public const CASE_LOWER = CASE_LOWER;
21 public const CASE_UPPER = CASE_UPPER;
22
23 private readonly Closure $convertNumeric;
24 private readonly Closure $convertAssociative;
25 private readonly Closure $convertOne;
26 private readonly Closure $convertAllNumeric;
27 private readonly Closure $convertAllAssociative;
28 private readonly Closure $convertFirstColumn;
29
30 /**
31 * @param bool $convertEmptyStringToNull Whether each empty string should
32 * be converted to NULL
33 * @param bool $rightTrimString Whether each string should right-trimmed
34 * @param self::CASE_LOWER|self::CASE_UPPER|null $case Convert the case of the column names
35 * (one of {@see self::CASE_LOWER} and
36 * {@see self::CASE_UPPER})
37 */
38 public function __construct(bool $convertEmptyStringToNull, bool $rightTrimString, ?int $case)
39 {
40 $convertValue = $this->createConvertValue($convertEmptyStringToNull, $rightTrimString);
41 $convertNumeric = $this->createConvertRow($convertValue, null);
42 $convertAssociative = $this->createConvertRow($convertValue, $case);
43
44 $this->convertNumeric = $this->createConvert($convertNumeric);
45 $this->convertAssociative = $this->createConvert($convertAssociative);
46 $this->convertOne = $this->createConvert($convertValue);
47
48 $this->convertAllNumeric = $this->createConvertAll($convertNumeric);
49 $this->convertAllAssociative = $this->createConvertAll($convertAssociative);
50 $this->convertFirstColumn = $this->createConvertAll($convertValue);
51 }
52
53 /**
54 * @param array<int,mixed>|false $row
55 *
56 * @return list<mixed>|false
57 */
58 public function convertNumeric(array|false $row): array|false
59 {
60 return ($this->convertNumeric)($row);
61 }
62
63 /**
64 * @param array<string,mixed>|false $row
65 *
66 * @return array<string,mixed>|false
67 */
68 public function convertAssociative(array|false $row): array|false
69 {
70 return ($this->convertAssociative)($row);
71 }
72
73 public function convertOne(mixed $value): mixed
74 {
75 return ($this->convertOne)($value);
76 }
77
78 /**
79 * @param list<list<mixed>> $data
80 *
81 * @return list<list<mixed>>
82 */
83 public function convertAllNumeric(array $data): array
84 {
85 return ($this->convertAllNumeric)($data);
86 }
87
88 /**
89 * @param list<array<string,mixed>> $data
90 *
91 * @return list<array<string,mixed>>
92 */
93 public function convertAllAssociative(array $data): array
94 {
95 return ($this->convertAllAssociative)($data);
96 }
97
98 /**
99 * @param list<mixed> $data
100 *
101 * @return list<mixed>
102 */
103 public function convertFirstColumn(array $data): array
104 {
105 return ($this->convertFirstColumn)($data);
106 }
107
108 /**
109 * @param T $value
110 *
111 * @return T
112 *
113 * @template T
114 */
115 private static function id(mixed $value): mixed
116 {
117 return $value;
118 }
119
120 /**
121 * @param T $value
122 *
123 * @return T|null
124 *
125 * @template T
126 */
127 private static function convertEmptyStringToNull(mixed $value): mixed
128 {
129 if ($value === '') {
130 return null;
131 }
132
133 return $value;
134 }
135
136 /**
137 * @param T $value
138 *
139 * @return T|string
140 * @psalm-return (T is string ? string : T)
141 *
142 * @template T
143 */
144 private static function rightTrimString(mixed $value): mixed
145 {
146 if (! is_string($value)) {
147 return $value;
148 }
149
150 return rtrim($value);
151 }
152
153 /**
154 * Creates a function that will convert each individual value retrieved from the database
155 *
156 * @param bool $convertEmptyStringToNull Whether each empty string should be converted to NULL
157 * @param bool $rightTrimString Whether each string should right-trimmed
158 *
159 * @return Closure|null The resulting function or NULL if no conversion is needed
160 */
161 private function createConvertValue(bool $convertEmptyStringToNull, bool $rightTrimString): ?Closure
162 {
163 $functions = [];
164
165 if ($convertEmptyStringToNull) {
166 $functions[] = self::convertEmptyStringToNull(...);
167 }
168
169 if ($rightTrimString) {
170 $functions[] = self::rightTrimString(...);
171 }
172
173 return $this->compose(...$functions);
174 }
175
176 /**
177 * Creates a function that will convert each array-row retrieved from the database
178 *
179 * @param Closure|null $function The function that will convert each value
180 * @param self::CASE_LOWER|self::CASE_UPPER|null $case Column name case
181 *
182 * @return Closure|null The resulting function or NULL if no conversion is needed
183 */
184 private function createConvertRow(?Closure $function, ?int $case): ?Closure
185 {
186 $functions = [];
187
188 if ($function !== null) {
189 $functions[] = $this->createMapper($function);
190 }
191
192 if ($case !== null) {
193 $functions[] = static fn (array $row): array => array_change_key_case($row, $case);
194 }
195
196 return $this->compose(...$functions);
197 }
198
199 /**
200 * Creates a function that will be applied to the return value of Statement::fetch*()
201 * or an identity function if no conversion is needed
202 *
203 * @param Closure|null $function The function that will convert each tow
204 */
205 private function createConvert(?Closure $function): Closure
206 {
207 if ($function === null) {
208 return self::id(...);
209 }
210
211 return /**
212 * @param T $value
213 *
214 * @psalm-return (T is false ? false : T)
215 *
216 * @template T
217 */
218 static function (mixed $value) use ($function): mixed {
219 if ($value === false) {
220 return false;
221 }
222
223 return $function($value);
224 };
225 }
226
227 /**
228 * Creates a function that will be applied to the return value of Statement::fetchAll*()
229 * or an identity function if no transformation is required
230 *
231 * @param Closure|null $function The function that will transform each value
232 */
233 private function createConvertAll(?Closure $function): Closure
234 {
235 if ($function === null) {
236 return self::id(...);
237 }
238
239 return $this->createMapper($function);
240 }
241
242 /**
243 * Creates a function that maps each value of the array using the given function
244 *
245 * @param Closure $function The function that maps each value of the array
246 *
247 * @return Closure(array<mixed>):array<mixed>
248 */
249 private function createMapper(Closure $function): Closure
250 {
251 return static fn (array $array): array => array_map($function, $array);
252 }
253
254 /**
255 * Creates a composition of the given set of functions
256 *
257 * @param Closure(T):T ...$functions The functions to compose
258 *
259 * @return Closure(T):T|null
260 *
261 * @template T
262 */
263 private function compose(Closure ...$functions): ?Closure
264 {
265 return array_reduce($functions, static function (?Closure $carry, Closure $item): Closure {
266 if ($carry === null) {
267 return $item;
268 }
269
270 return /**
271 * @param T $value
272 *
273 * @return T
274 *
275 * @template T
276 */
277 static fn (mixed $value): mixed => $item($carry($value));
278 });
279 }
280}
diff --git a/vendor/doctrine/dbal/src/Portability/Driver.php b/vendor/doctrine/dbal/src/Portability/Driver.php
new file mode 100644
index 0000000..bb67c25
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Driver.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\ColumnCase;
8use Doctrine\DBAL\Driver as DriverInterface;
9use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
10use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
11use PDO;
12use SensitiveParameter;
13
14use const CASE_LOWER;
15use const CASE_UPPER;
16
17final class Driver extends AbstractDriverMiddleware
18{
19 public function __construct(
20 DriverInterface $driver,
21 private readonly int $mode,
22 private readonly ?ColumnCase $case,
23 ) {
24 parent::__construct($driver);
25 }
26
27 /**
28 * {@inheritDoc}
29 */
30 public function connect(
31 #[SensitiveParameter]
32 array $params,
33 ): ConnectionInterface {
34 $connection = parent::connect($params);
35
36 $portability = (new OptimizeFlags())(
37 $this->getDatabasePlatform($connection),
38 $this->mode,
39 );
40
41 $case = null;
42
43 if ($this->case !== null && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) {
44 $nativeConnection = $connection->getNativeConnection();
45
46 $case = match ($this->case) {
47 ColumnCase::LOWER => CASE_LOWER,
48 ColumnCase::UPPER => CASE_UPPER,
49 };
50
51 if ($nativeConnection instanceof PDO) {
52 $portability &= ~Connection::PORTABILITY_FIX_CASE;
53 $nativeConnection->setAttribute(PDO::ATTR_CASE, $case);
54 }
55 }
56
57 $convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0;
58 $rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0;
59
60 if (! $convertEmptyStringToNull && ! $rightTrimString && $case === null) {
61 return $connection;
62 }
63
64 return new Connection(
65 $connection,
66 new Converter($convertEmptyStringToNull, $rightTrimString, $case),
67 );
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Portability/Middleware.php b/vendor/doctrine/dbal/src/Portability/Middleware.php
new file mode 100644
index 0000000..b97897c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Middleware.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\ColumnCase;
8use Doctrine\DBAL\Driver as DriverInterface;
9use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
10
11final class Middleware implements MiddlewareInterface
12{
13 public function __construct(private readonly int $mode, private readonly ?ColumnCase $case)
14 {
15 }
16
17 public function wrap(DriverInterface $driver): DriverInterface
18 {
19 if ($this->mode !== 0) {
20 return new Driver($driver, $this->mode, $this->case);
21 }
22
23 return $driver;
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php b/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php
new file mode 100644
index 0000000..c985d4b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/OptimizeFlags.php
@@ -0,0 +1,42 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Platforms\DB2Platform;
9use Doctrine\DBAL\Platforms\OraclePlatform;
10use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
11use Doctrine\DBAL\Platforms\SQLitePlatform;
12use Doctrine\DBAL\Platforms\SQLServerPlatform;
13
14final class OptimizeFlags
15{
16 /**
17 * Platform-specific portability flags that need to be excluded from the user-provided mode
18 * since the platform already operates in this mode to avoid unnecessary conversion overhead.
19 *
20 * @var array<class-string, int>
21 */
22 private static array $platforms = [
23 DB2Platform::class => 0,
24 OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL,
25 PostgreSQLPlatform::class => 0,
26 SQLitePlatform::class => 0,
27 SQLServerPlatform::class => 0,
28 ];
29
30 public function __invoke(AbstractPlatform $platform, int $flags): int
31 {
32 foreach (self::$platforms as $class => $mask) {
33 if ($platform instanceof $class) {
34 $flags &= ~$mask;
35
36 break;
37 }
38 }
39
40 return $flags;
41 }
42}
diff --git a/vendor/doctrine/dbal/src/Portability/Result.php b/vendor/doctrine/dbal/src/Portability/Result.php
new file mode 100644
index 0000000..d158683
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Result.php
@@ -0,0 +1,68 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\Driver\Middleware\AbstractResultMiddleware;
8use Doctrine\DBAL\Driver\Result as ResultInterface;
9
10final class Result extends AbstractResultMiddleware
11{
12 /** @internal The result can be only instantiated by the portability connection or statement. */
13 public function __construct(ResultInterface $result, private readonly Converter $converter)
14 {
15 parent::__construct($result);
16 }
17
18 public function fetchNumeric(): array|false
19 {
20 return $this->converter->convertNumeric(
21 parent::fetchNumeric(),
22 );
23 }
24
25 public function fetchAssociative(): array|false
26 {
27 return $this->converter->convertAssociative(
28 parent::fetchAssociative(),
29 );
30 }
31
32 public function fetchOne(): mixed
33 {
34 return $this->converter->convertOne(
35 parent::fetchOne(),
36 );
37 }
38
39 /**
40 * {@inheritDoc}
41 */
42 public function fetchAllNumeric(): array
43 {
44 return $this->converter->convertAllNumeric(
45 parent::fetchAllNumeric(),
46 );
47 }
48
49 /**
50 * {@inheritDoc}
51 */
52 public function fetchAllAssociative(): array
53 {
54 return $this->converter->convertAllAssociative(
55 parent::fetchAllAssociative(),
56 );
57 }
58
59 /**
60 * {@inheritDoc}
61 */
62 public function fetchFirstColumn(): array
63 {
64 return $this->converter->convertFirstColumn(
65 parent::fetchFirstColumn(),
66 );
67 }
68}
diff --git a/vendor/doctrine/dbal/src/Portability/Statement.php b/vendor/doctrine/dbal/src/Portability/Statement.php
new file mode 100644
index 0000000..de0c76f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Portability/Statement.php
@@ -0,0 +1,31 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Portability;
6
7use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
8use Doctrine\DBAL\Driver\Result as ResultInterface;
9use Doctrine\DBAL\Driver\Statement as DriverStatement;
10
11/**
12 * Portability wrapper for a Statement.
13 */
14final class Statement extends AbstractStatementMiddleware
15{
16 /**
17 * Wraps <tt>Statement</tt> and applies portability measures.
18 */
19 public function __construct(DriverStatement $stmt, private readonly Converter $converter)
20 {
21 parent::__construct($stmt);
22 }
23
24 public function execute(): ResultInterface
25 {
26 return new Result(
27 parent::execute(),
28 $this->converter,
29 );
30 }
31}
diff --git a/vendor/doctrine/dbal/src/Query.php b/vendor/doctrine/dbal/src/Query.php
new file mode 100644
index 0000000..b673ba8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query.php
@@ -0,0 +1,44 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7/**
8 * An SQL query together with its bound parameters.
9 *
10 * @psalm-immutable
11 * @psalm-import-type WrapperParameterType from Connection
12 */
13final class Query
14{
15 /**
16 * @param array<mixed> $params
17 * @psalm-param array<WrapperParameterType> $types
18 *
19 * @psalm-suppress ImpurePropertyAssignment
20 */
21 public function __construct(
22 private readonly string $sql,
23 private readonly array $params,
24 private readonly array $types,
25 ) {
26 }
27
28 public function getSQL(): string
29 {
30 return $this->sql;
31 }
32
33 /** @return array<mixed> */
34 public function getParams(): array
35 {
36 return $this->params;
37 }
38
39 /** @psalm-return array<WrapperParameterType> */
40 public function getTypes(): array
41 {
42 return $this->types;
43 }
44}
diff --git a/vendor/doctrine/dbal/src/Query/Exception/NonUniqueAlias.php b/vendor/doctrine/dbal/src/Query/Exception/NonUniqueAlias.php
new file mode 100644
index 0000000..8aab602
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Exception/NonUniqueAlias.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query\Exception;
6
7use Doctrine\DBAL\Query\QueryException;
8
9use function implode;
10use function sprintf;
11
12/** @psalm-immutable */
13final class NonUniqueAlias extends QueryException
14{
15 /** @param string[] $registeredAliases */
16 public static function new(string $alias, array $registeredAliases): self
17 {
18 return new self(
19 sprintf(
20 'The given alias "%s" is not unique in FROM and JOIN clause table. '
21 . 'The currently registered aliases are: %s.',
22 $alias,
23 implode(', ', $registeredAliases),
24 ),
25 );
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Query/Exception/UnknownAlias.php b/vendor/doctrine/dbal/src/Query/Exception/UnknownAlias.php
new file mode 100644
index 0000000..7b08d51
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Exception/UnknownAlias.php
@@ -0,0 +1,27 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query\Exception;
6
7use Doctrine\DBAL\Query\QueryException;
8
9use function implode;
10use function sprintf;
11
12/** @psalm-immutable */
13final class UnknownAlias extends QueryException
14{
15 /** @param string[] $registeredAliases */
16 public static function new(string $alias, array $registeredAliases): self
17 {
18 return new self(
19 sprintf(
20 'The given alias "%s" is not part of any FROM or JOIN clause table. '
21 . 'The currently registered aliases are: %s.',
22 $alias,
23 implode(', ', $registeredAliases),
24 ),
25 );
26 }
27}
diff --git a/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php b/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php
new file mode 100644
index 0000000..efe307a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Expression/CompositeExpression.php
@@ -0,0 +1,98 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query\Expression;
6
7use Countable;
8
9use function array_merge;
10use function array_values;
11use function count;
12use function implode;
13
14/**
15 * Composite expression is responsible to build a group of similar expression.
16 *
17 * This class is immutable.
18 */
19class CompositeExpression implements Countable
20{
21 /**
22 * Constant that represents an AND composite expression.
23 */
24 final public const TYPE_AND = 'AND';
25
26 /**
27 * Constant that represents an OR composite expression.
28 */
29 final public const TYPE_OR = 'OR';
30
31 /**
32 * Each expression part of the composite expression.
33 *
34 * @var array<int, self|string>
35 */
36 private array $parts;
37
38 /** @internal Use the and() / or() factory methods. */
39 public function __construct(
40 private readonly string $type,
41 self|string $part,
42 self|string ...$parts,
43 ) {
44 $this->parts = array_merge([$part], array_values($parts));
45 }
46
47 public static function and(self|string $part, self|string ...$parts): self
48 {
49 return new self(self::TYPE_AND, $part, ...$parts);
50 }
51
52 public static function or(self|string $part, self|string ...$parts): self
53 {
54 return new self(self::TYPE_OR, $part, ...$parts);
55 }
56
57 /**
58 * Returns a new CompositeExpression with the given parts added.
59 */
60 public function with(self|string $part, self|string ...$parts): self
61 {
62 $that = clone $this;
63
64 $that->parts = array_merge($that->parts, [$part], array_values($parts));
65
66 return $that;
67 }
68
69 /**
70 * Retrieves the amount of expressions on composite expression.
71 *
72 * @psalm-return int<0, max>
73 */
74 public function count(): int
75 {
76 return count($this->parts);
77 }
78
79 /**
80 * Retrieves the string representation of this composite expression.
81 */
82 public function __toString(): string
83 {
84 if ($this->count() === 1) {
85 return (string) $this->parts[0];
86 }
87
88 return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')';
89 }
90
91 /**
92 * Returns the type of this composite expression (AND/OR).
93 */
94 public function getType(): string
95 {
96 return $this->type;
97 }
98}
diff --git a/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php b/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php
new file mode 100644
index 0000000..14942b2
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Expression/ExpressionBuilder.php
@@ -0,0 +1,244 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query\Expression;
6
7use Doctrine\DBAL\Connection;
8
9use function implode;
10use function sprintf;
11
12/**
13 * ExpressionBuilder class is responsible to dynamically create SQL query parts.
14 */
15class ExpressionBuilder
16{
17 final public const EQ = '=';
18 final public const NEQ = '<>';
19 final public const LT = '<';
20 final public const LTE = '<=';
21 final public const GT = '>';
22 final public const GTE = '>=';
23
24 /**
25 * Initializes a new <tt>ExpressionBuilder</tt>.
26 *
27 * @param Connection $connection The DBAL Connection.
28 */
29 public function __construct(private readonly Connection $connection)
30 {
31 }
32
33 /**
34 * Creates a conjunction of the given expressions.
35 */
36 public function and(
37 string|CompositeExpression $expression,
38 string|CompositeExpression ...$expressions,
39 ): CompositeExpression {
40 return CompositeExpression::and($expression, ...$expressions);
41 }
42
43 /**
44 * Creates a disjunction of the given expressions.
45 */
46 public function or(
47 string|CompositeExpression $expression,
48 string|CompositeExpression ...$expressions,
49 ): CompositeExpression {
50 return CompositeExpression::or($expression, ...$expressions);
51 }
52
53 /**
54 * Creates a comparison expression.
55 *
56 * @param string $x The left expression.
57 * @param string $operator The comparison operator.
58 * @param string $y The right expression.
59 */
60 public function comparison(string $x, string $operator, string $y): string
61 {
62 return $x . ' ' . $operator . ' ' . $y;
63 }
64
65 /**
66 * Creates an equality comparison expression with the given arguments.
67 *
68 * First argument is considered the left expression and the second is the right expression.
69 * When converted to string, it will generated a <left expr> = <right expr>. Example:
70 *
71 * [php]
72 * // u.id = ?
73 * $expr->eq('u.id', '?');
74 *
75 * @param string $x The left expression.
76 * @param string $y The right expression.
77 */
78 public function eq(string $x, string $y): string
79 {
80 return $this->comparison($x, self::EQ, $y);
81 }
82
83 /**
84 * Creates a non equality comparison expression with the given arguments.
85 * First argument is considered the left expression and the second is the right expression.
86 * When converted to string, it will generated a <left expr> <> <right expr>. Example:
87 *
88 * [php]
89 * // u.id <> 1
90 * $q->where($q->expr()->neq('u.id', '1'));
91 *
92 * @param string $x The left expression.
93 * @param string $y The right expression.
94 */
95 public function neq(string $x, string $y): string
96 {
97 return $this->comparison($x, self::NEQ, $y);
98 }
99
100 /**
101 * Creates a lower-than comparison expression with the given arguments.
102 * First argument is considered the left expression and the second is the right expression.
103 * When converted to string, it will generated a <left expr> < <right expr>. Example:
104 *
105 * [php]
106 * // u.id < ?
107 * $q->where($q->expr()->lt('u.id', '?'));
108 *
109 * @param string $x The left expression.
110 * @param string $y The right expression.
111 */
112 public function lt(string $x, string $y): string
113 {
114 return $this->comparison($x, self::LT, $y);
115 }
116
117 /**
118 * Creates a lower-than-equal comparison expression with the given arguments.
119 * First argument is considered the left expression and the second is the right expression.
120 * When converted to string, it will generated a <left expr> <= <right expr>. Example:
121 *
122 * [php]
123 * // u.id <= ?
124 * $q->where($q->expr()->lte('u.id', '?'));
125 *
126 * @param string $x The left expression.
127 * @param string $y The right expression.
128 */
129 public function lte(string $x, string $y): string
130 {
131 return $this->comparison($x, self::LTE, $y);
132 }
133
134 /**
135 * Creates a greater-than comparison expression with the given arguments.
136 * First argument is considered the left expression and the second is the right expression.
137 * When converted to string, it will generated a <left expr> > <right expr>. Example:
138 *
139 * [php]
140 * // u.id > ?
141 * $q->where($q->expr()->gt('u.id', '?'));
142 *
143 * @param string $x The left expression.
144 * @param string $y The right expression.
145 */
146 public function gt(string $x, string $y): string
147 {
148 return $this->comparison($x, self::GT, $y);
149 }
150
151 /**
152 * Creates a greater-than-equal comparison expression with the given arguments.
153 * First argument is considered the left expression and the second is the right expression.
154 * When converted to string, it will generated a <left expr> >= <right expr>. Example:
155 *
156 * [php]
157 * // u.id >= ?
158 * $q->where($q->expr()->gte('u.id', '?'));
159 *
160 * @param string $x The left expression.
161 * @param string $y The right expression.
162 */
163 public function gte(string $x, string $y): string
164 {
165 return $this->comparison($x, self::GTE, $y);
166 }
167
168 /**
169 * Creates an IS NULL expression with the given arguments.
170 *
171 * @param string $x The expression to be restricted by IS NULL.
172 */
173 public function isNull(string $x): string
174 {
175 return $x . ' IS NULL';
176 }
177
178 /**
179 * Creates an IS NOT NULL expression with the given arguments.
180 *
181 * @param string $x The expression to be restricted by IS NOT NULL.
182 */
183 public function isNotNull(string $x): string
184 {
185 return $x . ' IS NOT NULL';
186 }
187
188 /**
189 * Creates a LIKE comparison expression.
190 *
191 * @param string $expression The expression to be inspected by the LIKE comparison
192 * @param string $pattern The pattern to compare against
193 */
194 public function like(string $expression, string $pattern, ?string $escapeChar = null): string
195 {
196 return $this->comparison($expression, 'LIKE', $pattern) .
197 ($escapeChar !== null ? sprintf(' ESCAPE %s', $escapeChar) : '');
198 }
199
200 /**
201 * Creates a NOT LIKE comparison expression
202 *
203 * @param string $expression The expression to be inspected by the NOT LIKE comparison
204 * @param string $pattern The pattern to compare against
205 */
206 public function notLike(string $expression, string $pattern, ?string $escapeChar = null): string
207 {
208 return $this->comparison($expression, 'NOT LIKE', $pattern) .
209 ($escapeChar !== null ? sprintf(' ESCAPE %s', $escapeChar) : '');
210 }
211
212 /**
213 * Creates an IN () comparison expression with the given arguments.
214 *
215 * @param string $x The SQL expression to be matched against the set.
216 * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set.
217 */
218 public function in(string $x, string|array $y): string
219 {
220 return $this->comparison($x, 'IN', '(' . implode(', ', (array) $y) . ')');
221 }
222
223 /**
224 * Creates a NOT IN () comparison expression with the given arguments.
225 *
226 * @param string $x The SQL expression to be matched against the set.
227 * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set.
228 */
229 public function notIn(string $x, string|array $y): string
230 {
231 return $this->comparison($x, 'NOT IN', '(' . implode(', ', (array) $y) . ')');
232 }
233
234 /**
235 * Creates an SQL literal expression from the string.
236 *
237 * The usage of this method is discouraged. Use prepared statements
238 * or {@see AbstractPlatform::quoteStringLiteral()} instead.
239 */
240 public function literal(string $input): string
241 {
242 return $this->connection->quote($input);
243 }
244}
diff --git a/vendor/doctrine/dbal/src/Query/ForUpdate.php b/vendor/doctrine/dbal/src/Query/ForUpdate.php
new file mode 100644
index 0000000..24a853e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/ForUpdate.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
8
9/** @internal */
10final class ForUpdate
11{
12 public function __construct(
13 private readonly ConflictResolutionMode $conflictResolutionMode,
14 ) {
15 }
16
17 public function getConflictResolutionMode(): ConflictResolutionMode
18 {
19 return $this->conflictResolutionMode;
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php b/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php
new file mode 100644
index 0000000..f45f774
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/ForUpdate/ConflictResolutionMode.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query\ForUpdate;
6
7enum ConflictResolutionMode
8{
9 /**
10 * Wait for the row to be unlocked
11 */
12 case ORDINARY;
13
14 /**
15 * Skip the row if it is locked
16 */
17 case SKIP_LOCKED;
18}
diff --git a/vendor/doctrine/dbal/src/Query/From.php b/vendor/doctrine/dbal/src/Query/From.php
new file mode 100644
index 0000000..71ff50d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/From.php
@@ -0,0 +1,15 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7/** @internal */
8final class From
9{
10 public function __construct(
11 public readonly string $table,
12 public readonly ?string $alias = null,
13 ) {
14 }
15}
diff --git a/vendor/doctrine/dbal/src/Query/Join.php b/vendor/doctrine/dbal/src/Query/Join.php
new file mode 100644
index 0000000..df0e2ed
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Join.php
@@ -0,0 +1,32 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7/** @internal */
8final class Join
9{
10 private function __construct(
11 public readonly string $type,
12 public readonly string $table,
13 public readonly string $alias,
14 public readonly ?string $condition,
15 ) {
16 }
17
18 public static function inner(string $table, string $alias, ?string $condition): Join
19 {
20 return new self('INNER', $table, $alias, $condition);
21 }
22
23 public static function left(string $table, string $alias, ?string $condition): Join
24 {
25 return new self('LEFT', $table, $alias, $condition);
26 }
27
28 public static function right(string $table, string $alias, ?string $condition): Join
29 {
30 return new self('RIGHT', $table, $alias, $condition);
31 }
32}
diff --git a/vendor/doctrine/dbal/src/Query/Limit.php b/vendor/doctrine/dbal/src/Query/Limit.php
new file mode 100644
index 0000000..32178e8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/Limit.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7final class Limit
8{
9 public function __construct(
10 private readonly ?int $maxResults,
11 private readonly int $firstResult,
12 ) {
13 }
14
15 public function isDefined(): bool
16 {
17 return $this->maxResults !== null || $this->firstResult !== 0;
18 }
19
20 public function getMaxResults(): ?int
21 {
22 return $this->maxResults;
23 }
24
25 public function getFirstResult(): int
26 {
27 return $this->firstResult;
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Query/QueryBuilder.php b/vendor/doctrine/dbal/src/Query/QueryBuilder.php
new file mode 100644
index 0000000..c6d3344
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/QueryBuilder.php
@@ -0,0 +1,1479 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7use Doctrine\DBAL\ArrayParameterType;
8use Doctrine\DBAL\Cache\QueryCacheProfile;
9use Doctrine\DBAL\Connection;
10use Doctrine\DBAL\Exception;
11use Doctrine\DBAL\ParameterType;
12use Doctrine\DBAL\Query\Exception\NonUniqueAlias;
13use Doctrine\DBAL\Query\Exception\UnknownAlias;
14use Doctrine\DBAL\Query\Expression\CompositeExpression;
15use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
16use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
17use Doctrine\DBAL\Result;
18use Doctrine\DBAL\Statement;
19use Doctrine\DBAL\Types\Type;
20
21use function array_key_exists;
22use function array_keys;
23use function array_merge;
24use function array_unshift;
25use function count;
26use function implode;
27use function is_object;
28use function substr;
29
30/**
31 * QueryBuilder class is responsible to dynamically create SQL queries.
32 *
33 * Important: Verify that every feature you use will work with your database vendor.
34 * SQL Query Builder does not attempt to validate the generated SQL at all.
35 *
36 * The query builder does no validation whatsoever if certain features even work with the
37 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
38 * even if some vendors such as MySQL support it.
39 *
40 * @psalm-import-type WrapperParameterTypeArray from Connection
41 */
42class QueryBuilder
43{
44 /**
45 * The complete SQL string for this query.
46 */
47 private ?string $sql = null;
48
49 /**
50 * The query parameters.
51 *
52 * @var list<mixed>|array<string, mixed>
53 */
54 private array $params = [];
55
56 /**
57 * The parameter type map of this query.
58 *
59 * @psalm-var WrapperParameterTypeArray
60 */
61 private array $types = [];
62
63 /**
64 * The type of query this is. Can be select, update or delete.
65 */
66 private QueryType $type = QueryType::SELECT;
67
68 /**
69 * The index of the first result to retrieve.
70 */
71 private int $firstResult = 0;
72
73 /**
74 * The maximum number of results to retrieve or NULL to retrieve all results.
75 */
76 private ?int $maxResults = null;
77
78 /**
79 * The counter of bound parameters used with {@see bindValue).
80 *
81 * @var int<0, max>
82 */
83 private int $boundCounter = 0;
84
85 /**
86 * The SELECT parts of the query.
87 *
88 * @var string[]
89 */
90 private array $select = [];
91
92 /**
93 * Whether this is a SELECT DISTINCT query.
94 */
95 private bool $distinct = false;
96
97 /**
98 * The FROM parts of a SELECT query.
99 *
100 * @var From[]
101 */
102 private array $from = [];
103
104 /**
105 * The table name for an INSERT, UPDATE or DELETE query.
106 */
107 private ?string $table = null;
108
109 /**
110 * The list of joins, indexed by from alias.
111 *
112 * @var array<string, Join[]>
113 */
114 private array $join = [];
115
116 /**
117 * The SET parts of an UPDATE query.
118 *
119 * @var string[]
120 */
121 private array $set = [];
122
123 /**
124 * The WHERE part of a SELECT, UPDATE or DELETE query.
125 */
126 private string|CompositeExpression|null $where = null;
127
128 /**
129 * The GROUP BY part of a SELECT query.
130 *
131 * @var string[]
132 */
133 private array $groupBy = [];
134
135 /**
136 * The HAVING part of a SELECT query.
137 */
138 private string|CompositeExpression|null $having = null;
139
140 /**
141 * The ORDER BY parts of a SELECT query.
142 *
143 * @var string[]
144 */
145 private array $orderBy = [];
146
147 private ?ForUpdate $forUpdate = null;
148
149 /**
150 * The values of an INSERT query.
151 *
152 * @var array<string, mixed>
153 */
154 private array $values = [];
155
156 /**
157 * The query cache profile used for caching results.
158 */
159 private ?QueryCacheProfile $resultCacheProfile = null;
160
161 /**
162 * Initializes a new <tt>QueryBuilder</tt>.
163 *
164 * @param Connection $connection The DBAL Connection.
165 */
166 public function __construct(private readonly Connection $connection)
167 {
168 }
169
170 /**
171 * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
172 * This producer method is intended for convenient inline usage. Example:
173 *
174 * <code>
175 * $qb = $conn->createQueryBuilder()
176 * ->select('u')
177 * ->from('users', 'u')
178 * ->where($qb->expr()->eq('u.id', 1));
179 * </code>
180 *
181 * For more complex expression construction, consider storing the expression
182 * builder object in a local variable.
183 */
184 public function expr(): ExpressionBuilder
185 {
186 return $this->connection->createExpressionBuilder();
187 }
188
189 /**
190 * Prepares and executes an SQL query and returns the first row of the result
191 * as an associative array.
192 *
193 * @return array<string, mixed>|false False is returned if no rows are found.
194 *
195 * @throws Exception
196 */
197 public function fetchAssociative(): array|false
198 {
199 return $this->executeQuery()->fetchAssociative();
200 }
201
202 /**
203 * Prepares and executes an SQL query and returns the first row of the result
204 * as a numerically indexed array.
205 *
206 * @return array<int, mixed>|false False is returned if no rows are found.
207 *
208 * @throws Exception
209 */
210 public function fetchNumeric(): array|false
211 {
212 return $this->executeQuery()->fetchNumeric();
213 }
214
215 /**
216 * Prepares and executes an SQL query and returns the value of a single column
217 * of the first row of the result.
218 *
219 * @return mixed|false False is returned if no rows are found.
220 *
221 * @throws Exception
222 */
223 public function fetchOne(): mixed
224 {
225 return $this->executeQuery()->fetchOne();
226 }
227
228 /**
229 * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
230 *
231 * @return array<int,array<int,mixed>>
232 *
233 * @throws Exception
234 */
235 public function fetchAllNumeric(): array
236 {
237 return $this->executeQuery()->fetchAllNumeric();
238 }
239
240 /**
241 * Prepares and executes an SQL query and returns the result as an array of associative arrays.
242 *
243 * @return array<int,array<string,mixed>>
244 *
245 * @throws Exception
246 */
247 public function fetchAllAssociative(): array
248 {
249 return $this->executeQuery()->fetchAllAssociative();
250 }
251
252 /**
253 * Prepares and executes an SQL query and returns the result as an associative array with the keys
254 * mapped to the first column and the values mapped to the second column.
255 *
256 * @return array<mixed,mixed>
257 *
258 * @throws Exception
259 */
260 public function fetchAllKeyValue(): array
261 {
262 return $this->executeQuery()->fetchAllKeyValue();
263 }
264
265 /**
266 * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
267 * to the first column and the values being an associative array representing the rest of the columns
268 * and their values.
269 *
270 * @return array<mixed,array<string,mixed>>
271 *
272 * @throws Exception
273 */
274 public function fetchAllAssociativeIndexed(): array
275 {
276 return $this->executeQuery()->fetchAllAssociativeIndexed();
277 }
278
279 /**
280 * Prepares and executes an SQL query and returns the result as an array of the first column values.
281 *
282 * @return array<int,mixed>
283 *
284 * @throws Exception
285 */
286 public function fetchFirstColumn(): array
287 {
288 return $this->executeQuery()->fetchFirstColumn();
289 }
290
291 /**
292 * Executes an SQL query (SELECT) and returns a Result.
293 *
294 * @throws Exception
295 */
296 public function executeQuery(): Result
297 {
298 return $this->connection->executeQuery(
299 $this->getSQL(),
300 $this->params,
301 $this->types,
302 $this->resultCacheProfile,
303 );
304 }
305
306 /**
307 * Executes an SQL statement and returns the number of affected rows.
308 *
309 * Should be used for INSERT, UPDATE and DELETE
310 *
311 * @return int|numeric-string The number of affected rows.
312 *
313 * @throws Exception
314 */
315 public function executeStatement(): int|string
316 {
317 return $this->connection->executeStatement($this->getSQL(), $this->params, $this->types);
318 }
319
320 /**
321 * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
322 *
323 * <code>
324 * $qb = $em->createQueryBuilder()
325 * ->select('u')
326 * ->from('User', 'u')
327 * echo $qb->getSQL(); // SELECT u FROM User u
328 * </code>
329 *
330 * @return string The SQL query string.
331 */
332 public function getSQL(): string
333 {
334 return $this->sql ??= match ($this->type) {
335 QueryType::INSERT => $this->getSQLForInsert(),
336 QueryType::DELETE => $this->getSQLForDelete(),
337 QueryType::UPDATE => $this->getSQLForUpdate(),
338 QueryType::SELECT => $this->getSQLForSelect(),
339 };
340 }
341
342 /**
343 * Sets a query parameter for the query being constructed.
344 *
345 * <code>
346 * $qb = $conn->createQueryBuilder()
347 * ->select('u')
348 * ->from('users', 'u')
349 * ->where('u.id = :user_id')
350 * ->setParameter('user_id', 1);
351 * </code>
352 *
353 * @param int<0, max>|string $key Parameter position or name
354 *
355 * @return $this This QueryBuilder instance.
356 */
357 public function setParameter(
358 int|string $key,
359 mixed $value,
360 string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING,
361 ): self {
362 $this->params[$key] = $value;
363 $this->types[$key] = $type;
364
365 return $this;
366 }
367
368 /**
369 * Sets a collection of query parameters for the query being constructed.
370 *
371 * <code>
372 * $qb = $conn->createQueryBuilder()
373 * ->select('u')
374 * ->from('users', 'u')
375 * ->where('u.id = :user_id1 OR u.id = :user_id2')
376 * ->setParameters(array(
377 * 'user_id1' => 1,
378 * 'user_id2' => 2
379 * ));
380 * </code>
381 *
382 * @param list<mixed>|array<string, mixed> $params
383 * @psalm-param WrapperParameterTypeArray $types
384 *
385 * @return $this This QueryBuilder instance.
386 */
387 public function setParameters(array $params, array $types = []): self
388 {
389 $this->params = $params;
390 $this->types = $types;
391
392 return $this;
393 }
394
395 /**
396 * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
397 *
398 * @return list<mixed>|array<string, mixed> The currently defined query parameters
399 */
400 public function getParameters(): array
401 {
402 return $this->params;
403 }
404
405 /**
406 * Gets a (previously set) query parameter of the query being constructed.
407 *
408 * @param string|int $key The key (index or name) of the bound parameter.
409 *
410 * @return mixed The value of the bound parameter.
411 */
412 public function getParameter(string|int $key): mixed
413 {
414 return $this->params[$key] ?? null;
415 }
416
417 /**
418 * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
419 *
420 * @psalm-return WrapperParameterTypeArray
421 */
422 public function getParameterTypes(): array
423 {
424 return $this->types;
425 }
426
427 /**
428 * Gets a (previously set) query parameter type of the query being constructed.
429 *
430 * @param int|string $key The key of the bound parameter type
431 */
432 public function getParameterType(int|string $key): string|ParameterType|Type|ArrayParameterType
433 {
434 return $this->types[$key] ?? ParameterType::STRING;
435 }
436
437 /**
438 * Sets the position of the first result to retrieve (the "offset").
439 *
440 * @param int $firstResult The first result to return.
441 *
442 * @return $this This QueryBuilder instance.
443 */
444 public function setFirstResult(int $firstResult): self
445 {
446 $this->firstResult = $firstResult;
447
448 $this->sql = null;
449
450 return $this;
451 }
452
453 /**
454 * Gets the position of the first result the query object was set to retrieve (the "offset").
455 *
456 * @return int The position of the first result.
457 */
458 public function getFirstResult(): int
459 {
460 return $this->firstResult;
461 }
462
463 /**
464 * Sets the maximum number of results to retrieve (the "limit").
465 *
466 * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
467 *
468 * @return $this This QueryBuilder instance.
469 */
470 public function setMaxResults(?int $maxResults): self
471 {
472 $this->maxResults = $maxResults;
473
474 $this->sql = null;
475
476 return $this;
477 }
478
479 /**
480 * Gets the maximum number of results the query object was set to retrieve (the "limit").
481 * Returns NULL if all results will be returned.
482 *
483 * @return int|null The maximum number of results.
484 */
485 public function getMaxResults(): ?int
486 {
487 return $this->maxResults;
488 }
489
490 /**
491 * Locks the queried rows for a subsequent update.
492 *
493 * @return $this
494 */
495 public function forUpdate(ConflictResolutionMode $conflictResolutionMode = ConflictResolutionMode::ORDINARY): self
496 {
497 $this->forUpdate = new ForUpdate($conflictResolutionMode);
498
499 $this->sql = null;
500
501 return $this;
502 }
503
504 /**
505 * Specifies an item that is to be returned in the query result.
506 * Replaces any previously specified selections, if any.
507 *
508 * <code>
509 * $qb = $conn->createQueryBuilder()
510 * ->select('u.id', 'p.id')
511 * ->from('users', 'u')
512 * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
513 * </code>
514 *
515 * @param string ...$expressions The selection expressions.
516 *
517 * @return $this This QueryBuilder instance.
518 */
519 public function select(string ...$expressions): self
520 {
521 $this->type = QueryType::SELECT;
522
523 $this->select = $expressions;
524
525 $this->sql = null;
526
527 return $this;
528 }
529
530 /**
531 * Adds or removes DISTINCT to/from the query.
532 *
533 * <code>
534 * $qb = $conn->createQueryBuilder()
535 * ->select('u.id')
536 * ->distinct()
537 * ->from('users', 'u')
538 * </code>
539 *
540 * @return $this This QueryBuilder instance.
541 */
542 public function distinct(bool $distinct = true): self
543 {
544 $this->distinct = $distinct;
545 $this->sql = null;
546
547 return $this;
548 }
549
550 /**
551 * Adds an item that is to be returned in the query result.
552 *
553 * <code>
554 * $qb = $conn->createQueryBuilder()
555 * ->select('u.id')
556 * ->addSelect('p.id')
557 * ->from('users', 'u')
558 * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
559 * </code>
560 *
561 * @param string $expression The selection expression.
562 * @param string ...$expressions Additional selection expressions.
563 *
564 * @return $this This QueryBuilder instance.
565 */
566 public function addSelect(string $expression, string ...$expressions): self
567 {
568 $this->type = QueryType::SELECT;
569
570 $this->select = array_merge($this->select, [$expression], $expressions);
571
572 $this->sql = null;
573
574 return $this;
575 }
576
577 /**
578 * Turns the query being built into a bulk delete query that ranges over
579 * a certain table.
580 *
581 * <code>
582 * $qb = $conn->createQueryBuilder()
583 * ->delete('users u')
584 * ->where('u.id = :user_id')
585 * ->setParameter(':user_id', 1);
586 * </code>
587 *
588 * @param string $table The table whose rows are subject to the deletion.
589 *
590 * @return $this This QueryBuilder instance.
591 */
592 public function delete(string $table): self
593 {
594 $this->type = QueryType::DELETE;
595
596 $this->table = $table;
597
598 $this->sql = null;
599
600 return $this;
601 }
602
603 /**
604 * Turns the query being built into a bulk update query that ranges over
605 * a certain table
606 *
607 * <code>
608 * $qb = $conn->createQueryBuilder()
609 * ->update('counters c')
610 * ->set('c.value', 'c.value + 1')
611 * ->where('c.id = ?');
612 * </code>
613 *
614 * @param string $table The table whose rows are subject to the update.
615 *
616 * @return $this This QueryBuilder instance.
617 */
618 public function update(string $table): self
619 {
620 $this->type = QueryType::UPDATE;
621
622 $this->table = $table;
623
624 $this->sql = null;
625
626 return $this;
627 }
628
629 /**
630 * Turns the query being built into an insert query that inserts into
631 * a certain table
632 *
633 * <code>
634 * $qb = $conn->createQueryBuilder()
635 * ->insert('users')
636 * ->values(
637 * array(
638 * 'name' => '?',
639 * 'password' => '?'
640 * )
641 * );
642 * </code>
643 *
644 * @param string $table The table into which the rows should be inserted.
645 *
646 * @return $this This QueryBuilder instance.
647 */
648 public function insert(string $table): self
649 {
650 $this->type = QueryType::INSERT;
651
652 $this->table = $table;
653
654 $this->sql = null;
655
656 return $this;
657 }
658
659 /**
660 * Creates and adds a query root corresponding to the table identified by the
661 * given alias, forming a cartesian product with any existing query roots.
662 *
663 * <code>
664 * $qb = $conn->createQueryBuilder()
665 * ->select('u.id')
666 * ->from('users', 'u')
667 * </code>
668 *
669 * @param string $table The table.
670 * @param string|null $alias The alias of the table.
671 *
672 * @return $this This QueryBuilder instance.
673 */
674 public function from(string $table, ?string $alias = null): self
675 {
676 $this->from[] = new From($table, $alias);
677
678 $this->sql = null;
679
680 return $this;
681 }
682
683 /**
684 * Creates and adds a join to the query.
685 *
686 * <code>
687 * $qb = $conn->createQueryBuilder()
688 * ->select('u.name')
689 * ->from('users', 'u')
690 * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
691 * </code>
692 *
693 * @param string $fromAlias The alias that points to a from clause.
694 * @param string $join The table name to join.
695 * @param string $alias The alias of the join table.
696 * @param string $condition The condition for the join.
697 *
698 * @return $this This QueryBuilder instance.
699 */
700 public function join(string $fromAlias, string $join, string $alias, ?string $condition = null): self
701 {
702 return $this->innerJoin($fromAlias, $join, $alias, $condition);
703 }
704
705 /**
706 * Creates and adds a join to the query.
707 *
708 * <code>
709 * $qb = $conn->createQueryBuilder()
710 * ->select('u.name')
711 * ->from('users', 'u')
712 * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
713 * </code>
714 *
715 * @param string $fromAlias The alias that points to a from clause.
716 * @param string $join The table name to join.
717 * @param string $alias The alias of the join table.
718 * @param string $condition The condition for the join.
719 *
720 * @return $this This QueryBuilder instance.
721 */
722 public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
723 {
724 $this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
725
726 $this->sql = null;
727
728 return $this;
729 }
730
731 /**
732 * Creates and adds a left join to the query.
733 *
734 * <code>
735 * $qb = $conn->createQueryBuilder()
736 * ->select('u.name')
737 * ->from('users', 'u')
738 * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
739 * </code>
740 *
741 * @param string $fromAlias The alias that points to a from clause.
742 * @param string $join The table name to join.
743 * @param string $alias The alias of the join table.
744 * @param string $condition The condition for the join.
745 *
746 * @return $this This QueryBuilder instance.
747 */
748 public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
749 {
750 $this->join[$fromAlias][] = Join::left($join, $alias, $condition);
751
752 $this->sql = null;
753
754 return $this;
755 }
756
757 /**
758 * Creates and adds a right join to the query.
759 *
760 * <code>
761 * $qb = $conn->createQueryBuilder()
762 * ->select('u.name')
763 * ->from('users', 'u')
764 * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
765 * </code>
766 *
767 * @param string $fromAlias The alias that points to a from clause.
768 * @param string $join The table name to join.
769 * @param string $alias The alias of the join table.
770 * @param string $condition The condition for the join.
771 *
772 * @return $this This QueryBuilder instance.
773 */
774 public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
775 {
776 $this->join[$fromAlias][] = Join::right($join, $alias, $condition);
777
778 $this->sql = null;
779
780 return $this;
781 }
782
783 /**
784 * Sets a new value for a column in a bulk update query.
785 *
786 * <code>
787 * $qb = $conn->createQueryBuilder()
788 * ->update('counters c')
789 * ->set('c.value', 'c.value + 1')
790 * ->where('c.id = ?');
791 * </code>
792 *
793 * @param string $key The column to set.
794 * @param string $value The value, expression, placeholder, etc.
795 *
796 * @return $this This QueryBuilder instance.
797 */
798 public function set(string $key, string $value): self
799 {
800 $this->set[] = $key . ' = ' . $value;
801
802 $this->sql = null;
803
804 return $this;
805 }
806
807 /**
808 * Specifies one or more restrictions to the query result.
809 * Replaces any previously specified restrictions, if any.
810 *
811 * <code>
812 * $qb = $conn->createQueryBuilder()
813 * ->select('c.value')
814 * ->from('counters', 'c')
815 * ->where('c.id = ?');
816 *
817 * // You can optionally programmatically build and/or expressions
818 * $qb = $conn->createQueryBuilder();
819 *
820 * $or = $qb->expr()->orx();
821 * $or->add($qb->expr()->eq('c.id', 1));
822 * $or->add($qb->expr()->eq('c.id', 2));
823 *
824 * $qb->update('counters c')
825 * ->set('c.value', 'c.value + 1')
826 * ->where($or);
827 * </code>
828 *
829 * @param string|CompositeExpression $predicate The WHERE clause predicate.
830 * @param string|CompositeExpression ...$predicates Additional WHERE clause predicates.
831 *
832 * @return $this This QueryBuilder instance.
833 */
834 public function where(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
835 {
836 $this->where = $this->createPredicate($predicate, ...$predicates);
837
838 $this->sql = null;
839
840 return $this;
841 }
842
843 /**
844 * Adds one or more restrictions to the query results, forming a logical
845 * conjunction with any previously specified restrictions.
846 *
847 * <code>
848 * $qb = $conn->createQueryBuilder()
849 * ->select('u')
850 * ->from('users', 'u')
851 * ->where('u.username LIKE ?')
852 * ->andWhere('u.is_active = 1');
853 * </code>
854 *
855 * @see where()
856 *
857 * @param string|CompositeExpression $predicate The predicate to append.
858 * @param string|CompositeExpression ...$predicates Additional predicates to append.
859 *
860 * @return $this This QueryBuilder instance.
861 */
862 public function andWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
863 {
864 $this->where = $this->appendToPredicate(
865 $this->where,
866 CompositeExpression::TYPE_AND,
867 $predicate,
868 ...$predicates,
869 );
870
871 $this->sql = null;
872
873 return $this;
874 }
875
876 /**
877 * Adds one or more restrictions to the query results, forming a logical
878 * disjunction with any previously specified restrictions.
879 *
880 * <code>
881 * $qb = $em->createQueryBuilder()
882 * ->select('u.name')
883 * ->from('users', 'u')
884 * ->where('u.id = 1')
885 * ->orWhere('u.id = 2');
886 * </code>
887 *
888 * @see where()
889 *
890 * @param string|CompositeExpression $predicate The predicate to append.
891 * @param string|CompositeExpression ...$predicates Additional predicates to append.
892 *
893 * @return $this This QueryBuilder instance.
894 */
895 public function orWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
896 {
897 $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
898
899 $this->sql = null;
900
901 return $this;
902 }
903
904 /**
905 * Specifies one or more grouping expressions over the results of the query.
906 * Replaces any previously specified groupings, if any.
907 *
908 * <code>
909 * $qb = $conn->createQueryBuilder()
910 * ->select('u.name')
911 * ->from('users', 'u')
912 * ->groupBy('u.id');
913 * </code>
914 *
915 * @param string $expression The grouping expression
916 * @param string ...$expressions Additional grouping expressions
917 *
918 * @return $this This QueryBuilder instance.
919 */
920 public function groupBy(string $expression, string ...$expressions): self
921 {
922 $this->groupBy = array_merge([$expression], $expressions);
923
924 $this->sql = null;
925
926 return $this;
927 }
928
929 /**
930 * Adds one or more grouping expressions to the query.
931 *
932 * <code>
933 * $qb = $conn->createQueryBuilder()
934 * ->select('u.name')
935 * ->from('users', 'u')
936 * ->groupBy('u.lastLogin')
937 * ->addGroupBy('u.createdAt');
938 * </code>
939 *
940 * @param string $expression The grouping expression
941 * @param string ...$expressions Additional grouping expressions
942 *
943 * @return $this This QueryBuilder instance.
944 */
945 public function addGroupBy(string $expression, string ...$expressions): self
946 {
947 $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
948
949 $this->sql = null;
950
951 return $this;
952 }
953
954 /**
955 * Sets a value for a column in an insert query.
956 *
957 * <code>
958 * $qb = $conn->createQueryBuilder()
959 * ->insert('users')
960 * ->values(
961 * array(
962 * 'name' => '?'
963 * )
964 * )
965 * ->setValue('password', '?');
966 * </code>
967 *
968 * @param string $column The column into which the value should be inserted.
969 * @param string $value The value that should be inserted into the column.
970 *
971 * @return $this This QueryBuilder instance.
972 */
973 public function setValue(string $column, string $value): self
974 {
975 $this->values[$column] = $value;
976
977 return $this;
978 }
979
980 /**
981 * Specifies values for an insert query indexed by column names.
982 * Replaces any previous values, if any.
983 *
984 * <code>
985 * $qb = $conn->createQueryBuilder()
986 * ->insert('users')
987 * ->values(
988 * array(
989 * 'name' => '?',
990 * 'password' => '?'
991 * )
992 * );
993 * </code>
994 *
995 * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
996 *
997 * @return $this This QueryBuilder instance.
998 */
999 public function values(array $values): self
1000 {
1001 $this->values = $values;
1002
1003 $this->sql = null;
1004
1005 return $this;
1006 }
1007
1008 /**
1009 * Specifies a restriction over the groups of the query.
1010 * Replaces any previous having restrictions, if any.
1011 *
1012 * @param string|CompositeExpression $predicate The HAVING clause predicate.
1013 * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
1014 *
1015 * @return $this This QueryBuilder instance.
1016 */
1017 public function having(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
1018 {
1019 $this->having = $this->createPredicate($predicate, ...$predicates);
1020
1021 $this->sql = null;
1022
1023 return $this;
1024 }
1025
1026 /**
1027 * Adds a restriction over the groups of the query, forming a logical
1028 * conjunction with any existing having restrictions.
1029 *
1030 * @param string|CompositeExpression $predicate The predicate to append.
1031 * @param string|CompositeExpression ...$predicates Additional predicates to append.
1032 *
1033 * @return $this This QueryBuilder instance.
1034 */
1035 public function andHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
1036 {
1037 $this->having = $this->appendToPredicate(
1038 $this->having,
1039 CompositeExpression::TYPE_AND,
1040 $predicate,
1041 ...$predicates,
1042 );
1043
1044 $this->sql = null;
1045
1046 return $this;
1047 }
1048
1049 /**
1050 * Adds a restriction over the groups of the query, forming a logical
1051 * disjunction with any existing having restrictions.
1052 *
1053 * @param string|CompositeExpression $predicate The predicate to append.
1054 * @param string|CompositeExpression ...$predicates Additional predicates to append.
1055 *
1056 * @return $this This QueryBuilder instance.
1057 */
1058 public function orHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
1059 {
1060 $this->having = $this->appendToPredicate(
1061 $this->having,
1062 CompositeExpression::TYPE_OR,
1063 $predicate,
1064 ...$predicates,
1065 );
1066
1067 $this->sql = null;
1068
1069 return $this;
1070 }
1071
1072 /**
1073 * Creates a CompositeExpression from one or more predicates combined by the AND logic.
1074 */
1075 private function createPredicate(
1076 string|CompositeExpression $predicate,
1077 string|CompositeExpression ...$predicates,
1078 ): string|CompositeExpression {
1079 if (count($predicates) === 0) {
1080 return $predicate;
1081 }
1082
1083 return new CompositeExpression(CompositeExpression::TYPE_AND, $predicate, ...$predicates);
1084 }
1085
1086 /**
1087 * Appends the given predicates combined by the given type of logic to the current predicate.
1088 */
1089 private function appendToPredicate(
1090 string|CompositeExpression|null $currentPredicate,
1091 string $type,
1092 string|CompositeExpression ...$predicates,
1093 ): string|CompositeExpression {
1094 if ($currentPredicate instanceof CompositeExpression && $currentPredicate->getType() === $type) {
1095 return $currentPredicate->with(...$predicates);
1096 }
1097
1098 if ($currentPredicate !== null) {
1099 array_unshift($predicates, $currentPredicate);
1100 } elseif (count($predicates) === 1) {
1101 return $predicates[0];
1102 }
1103
1104 return new CompositeExpression($type, ...$predicates);
1105 }
1106
1107 /**
1108 * Specifies an ordering for the query results.
1109 * Replaces any previously specified orderings, if any.
1110 *
1111 * @param string $sort The ordering expression.
1112 * @param string $order The ordering direction.
1113 *
1114 * @return $this This QueryBuilder instance.
1115 */
1116 public function orderBy(string $sort, ?string $order = null): self
1117 {
1118 $orderBy = $sort;
1119
1120 if ($order !== null) {
1121 $orderBy .= ' ' . $order;
1122 }
1123
1124 $this->orderBy = [$orderBy];
1125
1126 $this->sql = null;
1127
1128 return $this;
1129 }
1130
1131 /**
1132 * Adds an ordering to the query results.
1133 *
1134 * @param string $sort The ordering expression.
1135 * @param string $order The ordering direction.
1136 *
1137 * @return $this This QueryBuilder instance.
1138 */
1139 public function addOrderBy(string $sort, ?string $order = null): self
1140 {
1141 $orderBy = $sort;
1142
1143 if ($order !== null) {
1144 $orderBy .= ' ' . $order;
1145 }
1146
1147 $this->orderBy[] = $orderBy;
1148
1149 $this->sql = null;
1150
1151 return $this;
1152 }
1153
1154 /**
1155 * Resets the WHERE conditions for the query.
1156 *
1157 * @return $this This QueryBuilder instance.
1158 */
1159 public function resetWhere(): self
1160 {
1161 $this->where = null;
1162 $this->sql = null;
1163
1164 return $this;
1165 }
1166
1167 /**
1168 * Resets the grouping for the query.
1169 *
1170 * @return $this This QueryBuilder instance.
1171 */
1172 public function resetGroupBy(): self
1173 {
1174 $this->groupBy = [];
1175 $this->sql = null;
1176
1177 return $this;
1178 }
1179
1180 /**
1181 * Resets the HAVING conditions for the query.
1182 *
1183 * @return $this This QueryBuilder instance.
1184 */
1185 public function resetHaving(): self
1186 {
1187 $this->having = null;
1188 $this->sql = null;
1189
1190 return $this;
1191 }
1192
1193 /**
1194 * Resets the ordering for the query.
1195 *
1196 * @return $this This QueryBuilder instance.
1197 */
1198 public function resetOrderBy(): self
1199 {
1200 $this->orderBy = [];
1201 $this->sql = null;
1202
1203 return $this;
1204 }
1205
1206 /** @throws Exception */
1207 private function getSQLForSelect(): string
1208 {
1209 if (count($this->select) === 0) {
1210 throw new QueryException('No SELECT expressions given. Please use select() or addSelect().');
1211 }
1212
1213 return $this->connection->getDatabasePlatform()
1214 ->createSelectSQLBuilder()
1215 ->buildSQL(
1216 new SelectQuery(
1217 $this->distinct,
1218 $this->select,
1219 $this->getFromClauses(),
1220 $this->where !== null ? (string) $this->where : null,
1221 $this->groupBy,
1222 $this->having !== null ? (string) $this->having : null,
1223 $this->orderBy,
1224 new Limit($this->maxResults, $this->firstResult),
1225 $this->forUpdate,
1226 ),
1227 );
1228 }
1229
1230 /**
1231 * @return array<string, string>
1232 *
1233 * @throws QueryException
1234 */
1235 private function getFromClauses(): array
1236 {
1237 $fromClauses = [];
1238 $knownAliases = [];
1239
1240 foreach ($this->from as $from) {
1241 if ($from->alias === null || $from->alias === $from->table) {
1242 $tableSql = $from->table;
1243 $tableReference = $from->table;
1244 } else {
1245 $tableSql = $from->table . ' ' . $from->alias;
1246 $tableReference = $from->alias;
1247 }
1248
1249 $knownAliases[$tableReference] = true;
1250
1251 $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1252 }
1253
1254 $this->verifyAllAliasesAreKnown($knownAliases);
1255
1256 return $fromClauses;
1257 }
1258
1259 /**
1260 * @param array<string, true> $knownAliases
1261 *
1262 * @throws QueryException
1263 */
1264 private function verifyAllAliasesAreKnown(array $knownAliases): void
1265 {
1266 foreach ($this->join as $fromAlias => $joins) {
1267 if (! isset($knownAliases[$fromAlias])) {
1268 throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1269 }
1270 }
1271 }
1272
1273 /**
1274 * Converts this instance into an INSERT string in SQL.
1275 */
1276 private function getSQLForInsert(): string
1277 {
1278 return 'INSERT INTO ' . $this->table .
1279 ' (' . implode(', ', array_keys($this->values)) . ')' .
1280 ' VALUES(' . implode(', ', $this->values) . ')';
1281 }
1282
1283 /**
1284 * Converts this instance into an UPDATE string in SQL.
1285 */
1286 private function getSQLForUpdate(): string
1287 {
1288 $query = 'UPDATE ' . $this->table
1289 . ' SET ' . implode(', ', $this->set);
1290
1291 if ($this->where !== null) {
1292 $query .= ' WHERE ' . $this->where;
1293 }
1294
1295 return $query;
1296 }
1297
1298 /**
1299 * Converts this instance into a DELETE string in SQL.
1300 */
1301 private function getSQLForDelete(): string
1302 {
1303 $query = 'DELETE FROM ' . $this->table;
1304
1305 if ($this->where !== null) {
1306 $query .= ' WHERE ' . $this->where;
1307 }
1308
1309 return $query;
1310 }
1311
1312 /**
1313 * Gets a string representation of this QueryBuilder which corresponds to
1314 * the final SQL query being constructed.
1315 *
1316 * @return string The string representation of this QueryBuilder.
1317 */
1318 public function __toString(): string
1319 {
1320 return $this->getSQL();
1321 }
1322
1323 /**
1324 * Creates a new named parameter and bind the value $value to it.
1325 *
1326 * This method provides a shortcut for {@see Statement::bindValue()}
1327 * when using prepared statements.
1328 *
1329 * The parameter $value specifies the value that you want to bind. If
1330 * $placeholder is not provided createNamedParameter() will automatically
1331 * create a placeholder for you. An automatic placeholder will be of the
1332 * name ':dcValue1', ':dcValue2' etc.
1333 *
1334 * Example:
1335 * <code>
1336 * $value = 2;
1337 * $q->eq( 'id', $q->createNamedParameter( $value ) );
1338 * $stmt = $q->executeQuery(); // executed with 'id = 2'
1339 * </code>
1340 *
1341 * @link http://www.zetacomponents.org
1342 *
1343 * @param string|null $placeHolder The name to bind with. The string must start with a colon ':'.
1344 *
1345 * @return string the placeholder name used.
1346 */
1347 public function createNamedParameter(
1348 mixed $value,
1349 string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING,
1350 ?string $placeHolder = null,
1351 ): string {
1352 if ($placeHolder === null) {
1353 $this->boundCounter++;
1354 $placeHolder = ':dcValue' . $this->boundCounter;
1355 }
1356
1357 $this->setParameter(substr($placeHolder, 1), $value, $type);
1358
1359 return $placeHolder;
1360 }
1361
1362 /**
1363 * Creates a new positional parameter and bind the given value to it.
1364 *
1365 * Attention: If you are using positional parameters with the query builder you have
1366 * to be very careful to bind all parameters in the order they appear in the SQL
1367 * statement , otherwise they get bound in the wrong order which can lead to serious
1368 * bugs in your code.
1369 *
1370 * Example:
1371 * <code>
1372 * $qb = $conn->createQueryBuilder();
1373 * $qb->select('u.*')
1374 * ->from('users', 'u')
1375 * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1376 * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1377 * </code>
1378 */
1379 public function createPositionalParameter(
1380 mixed $value,
1381 string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING,
1382 ): string {
1383 $this->setParameter($this->boundCounter, $value, $type);
1384 $this->boundCounter++;
1385
1386 return '?';
1387 }
1388
1389 /**
1390 * @param array<string, true> $knownAliases
1391 *
1392 * @throws QueryException
1393 */
1394 private function getSQLForJoins(string $fromAlias, array &$knownAliases): string
1395 {
1396 $sql = '';
1397
1398 if (! isset($this->join[$fromAlias])) {
1399 return $sql;
1400 }
1401
1402 foreach ($this->join[$fromAlias] as $join) {
1403 if (array_key_exists($join->alias, $knownAliases)) {
1404 throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1405 }
1406
1407 $sql .= ' ' . $join->type . ' JOIN ' . $join->table . ' ' . $join->alias;
1408
1409 if ($join->condition !== null) {
1410 $sql .= ' ON ' . $join->condition;
1411 }
1412
1413 $knownAliases[$join->alias] = true;
1414 }
1415
1416 foreach ($this->join[$fromAlias] as $join) {
1417 $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1418 }
1419
1420 return $sql;
1421 }
1422
1423 /**
1424 * Deep clone of all expression objects in the SQL parts.
1425 */
1426 public function __clone()
1427 {
1428 foreach ($this->from as $key => $from) {
1429 $this->from[$key] = clone $from;
1430 }
1431
1432 foreach ($this->join as $fromAlias => $joins) {
1433 foreach ($joins as $key => $join) {
1434 $this->join[$fromAlias][$key] = clone $join;
1435 }
1436 }
1437
1438 if (is_object($this->where)) {
1439 $this->where = clone $this->where;
1440 }
1441
1442 if (is_object($this->having)) {
1443 $this->having = clone $this->having;
1444 }
1445
1446 foreach ($this->params as $name => $param) {
1447 if (! is_object($param)) {
1448 continue;
1449 }
1450
1451 $this->params[$name] = clone $param;
1452 }
1453 }
1454
1455 /**
1456 * Enables caching of the results of this query, for given amount of seconds
1457 * and optionally specified which key to use for the cache entry.
1458 *
1459 * @return $this
1460 */
1461 public function enableResultCache(QueryCacheProfile $cacheProfile): self
1462 {
1463 $this->resultCacheProfile = $cacheProfile;
1464
1465 return $this;
1466 }
1467
1468 /**
1469 * Disables caching of the results of this query.
1470 *
1471 * @return $this
1472 */
1473 public function disableResultCache(): self
1474 {
1475 $this->resultCacheProfile = null;
1476
1477 return $this;
1478 }
1479}
diff --git a/vendor/doctrine/dbal/src/Query/QueryException.php b/vendor/doctrine/dbal/src/Query/QueryException.php
new file mode 100644
index 0000000..eac3836
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/QueryException.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7use Doctrine\DBAL\Exception;
8
9/** @psalm-immutable */
10class QueryException extends \Exception implements Exception
11{
12}
diff --git a/vendor/doctrine/dbal/src/Query/QueryType.php b/vendor/doctrine/dbal/src/Query/QueryType.php
new file mode 100644
index 0000000..632c495
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/QueryType.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7/** @internal */
8enum QueryType
9{
10 case SELECT;
11 case DELETE;
12 case UPDATE;
13 case INSERT;
14}
diff --git a/vendor/doctrine/dbal/src/Query/SelectQuery.php b/vendor/doctrine/dbal/src/Query/SelectQuery.php
new file mode 100644
index 0000000..e6ef9f2
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Query/SelectQuery.php
@@ -0,0 +1,78 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Query;
6
7final class SelectQuery
8{
9 /**
10 * @internal This class should be instantiated only by {@link QueryBuilder}.
11 *
12 * @param string[] $columns
13 * @param string[] $from
14 * @param string[] $groupBy
15 * @param string[] $orderBy
16 */
17 public function __construct(
18 private readonly bool $distinct,
19 private readonly array $columns,
20 private readonly array $from,
21 private readonly ?string $where,
22 private readonly array $groupBy,
23 private readonly ?string $having,
24 private readonly array $orderBy,
25 private readonly Limit $limit,
26 private readonly ?ForUpdate $forUpdate,
27 ) {
28 }
29
30 public function isDistinct(): bool
31 {
32 return $this->distinct;
33 }
34
35 /** @return string[] */
36 public function getColumns(): array
37 {
38 return $this->columns;
39 }
40
41 /** @return string[] */
42 public function getFrom(): array
43 {
44 return $this->from;
45 }
46
47 public function getWhere(): ?string
48 {
49 return $this->where;
50 }
51
52 /** @return string[] */
53 public function getGroupBy(): array
54 {
55 return $this->groupBy;
56 }
57
58 public function getHaving(): ?string
59 {
60 return $this->having;
61 }
62
63 /** @return string[] */
64 public function getOrderBy(): array
65 {
66 return $this->orderBy;
67 }
68
69 public function getLimit(): Limit
70 {
71 return $this->limit;
72 }
73
74 public function getForUpdate(): ?ForUpdate
75 {
76 return $this->forUpdate;
77 }
78}
diff --git a/vendor/doctrine/dbal/src/Result.php b/vendor/doctrine/dbal/src/Result.php
new file mode 100644
index 0000000..6bc7c6d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Result.php
@@ -0,0 +1,270 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\Driver\Exception as DriverException;
8use Doctrine\DBAL\Driver\Result as DriverResult;
9use Doctrine\DBAL\Exception\NoKeyValue;
10use Traversable;
11
12use function array_shift;
13use function assert;
14use function count;
15
16class Result
17{
18 /** @internal The result can be only instantiated by {@see Connection} or {@see Statement}. */
19 public function __construct(private readonly DriverResult $result, private readonly Connection $connection)
20 {
21 }
22
23 /**
24 * Returns the next row of the result as a numeric array or FALSE if there are no more rows.
25 *
26 * @return list<mixed>|false
27 *
28 * @throws Exception
29 */
30 public function fetchNumeric(): array|false
31 {
32 try {
33 return $this->result->fetchNumeric();
34 } catch (DriverException $e) {
35 throw $this->connection->convertException($e);
36 }
37 }
38
39 /**
40 * Returns the next row of the result as an associative array or FALSE if there are no more rows.
41 *
42 * @return array<string,mixed>|false
43 *
44 * @throws Exception
45 */
46 public function fetchAssociative(): array|false
47 {
48 try {
49 return $this->result->fetchAssociative();
50 } catch (DriverException $e) {
51 throw $this->connection->convertException($e);
52 }
53 }
54
55 /**
56 * Returns the first value of the next row of the result or FALSE if there are no more rows.
57 *
58 * @throws Exception
59 */
60 public function fetchOne(): mixed
61 {
62 try {
63 return $this->result->fetchOne();
64 } catch (DriverException $e) {
65 throw $this->connection->convertException($e);
66 }
67 }
68
69 /**
70 * Returns an array containing all of the result rows represented as numeric arrays.
71 *
72 * @return list<list<mixed>>
73 *
74 * @throws Exception
75 */
76 public function fetchAllNumeric(): array
77 {
78 try {
79 return $this->result->fetchAllNumeric();
80 } catch (DriverException $e) {
81 throw $this->connection->convertException($e);
82 }
83 }
84
85 /**
86 * Returns an array containing all of the result rows represented as associative arrays.
87 *
88 * @return list<array<string,mixed>>
89 *
90 * @throws Exception
91 */
92 public function fetchAllAssociative(): array
93 {
94 try {
95 return $this->result->fetchAllAssociative();
96 } catch (DriverException $e) {
97 throw $this->connection->convertException($e);
98 }
99 }
100
101 /**
102 * Returns an array containing the values of the first column of the result.
103 *
104 * @return array<mixed,mixed>
105 *
106 * @throws Exception
107 */
108 public function fetchAllKeyValue(): array
109 {
110 $this->ensureHasKeyValue();
111
112 $data = [];
113
114 foreach ($this->fetchAllNumeric() as $row) {
115 assert(count($row) >= 2);
116 [$key, $value] = $row;
117 $data[$key] = $value;
118 }
119
120 return $data;
121 }
122
123 /**
124 * Returns an associative array with the keys mapped to the first column and the values being
125 * an associative array representing the rest of the columns and their values.
126 *
127 * @return array<mixed,array<string,mixed>>
128 *
129 * @throws Exception
130 */
131 public function fetchAllAssociativeIndexed(): array
132 {
133 $data = [];
134
135 foreach ($this->fetchAllAssociative() as $row) {
136 $data[array_shift($row)] = $row;
137 }
138
139 return $data;
140 }
141
142 /**
143 * @return list<mixed>
144 *
145 * @throws Exception
146 */
147 public function fetchFirstColumn(): array
148 {
149 try {
150 return $this->result->fetchFirstColumn();
151 } catch (DriverException $e) {
152 throw $this->connection->convertException($e);
153 }
154 }
155
156 /**
157 * @return Traversable<int,list<mixed>>
158 *
159 * @throws Exception
160 */
161 public function iterateNumeric(): Traversable
162 {
163 while (($row = $this->fetchNumeric()) !== false) {
164 yield $row;
165 }
166 }
167
168 /**
169 * @return Traversable<int,array<string,mixed>>
170 *
171 * @throws Exception
172 */
173 public function iterateAssociative(): Traversable
174 {
175 while (($row = $this->fetchAssociative()) !== false) {
176 yield $row;
177 }
178 }
179
180 /**
181 * @return Traversable<mixed, mixed>
182 *
183 * @throws Exception
184 */
185 public function iterateKeyValue(): Traversable
186 {
187 $this->ensureHasKeyValue();
188
189 foreach ($this->iterateNumeric() as $row) {
190 assert(count($row) >= 2);
191 [$key, $value] = $row;
192
193 yield $key => $value;
194 }
195 }
196
197 /**
198 * Returns an iterator over the result set with the keys mapped to the first column and the values being
199 * an associative array representing the rest of the columns and their values.
200 *
201 * @return Traversable<mixed,array<string,mixed>>
202 *
203 * @throws Exception
204 */
205 public function iterateAssociativeIndexed(): Traversable
206 {
207 foreach ($this->iterateAssociative() as $row) {
208 yield array_shift($row) => $row;
209 }
210 }
211
212 /**
213 * @return Traversable<int,mixed>
214 *
215 * @throws Exception
216 */
217 public function iterateColumn(): Traversable
218 {
219 while (($value = $this->fetchOne()) !== false) {
220 yield $value;
221 }
222 }
223
224 /**
225 * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result.
226 *
227 * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.),
228 * some database drivers may return the number of rows returned by that query. However, this behaviour
229 * is not guaranteed for all drivers and should not be relied on in portable applications.
230 *
231 * If the number of rows exceeds {@see PHP_INT_MAX}, it might be returned as string if the driver supports it.
232 *
233 * @return int|numeric-string
234 *
235 * @throws Exception
236 */
237 public function rowCount(): int|string
238 {
239 try {
240 return $this->result->rowCount();
241 } catch (DriverException $e) {
242 throw $this->connection->convertException($e);
243 }
244 }
245
246 /** @throws Exception */
247 public function columnCount(): int
248 {
249 try {
250 return $this->result->columnCount();
251 } catch (DriverException $e) {
252 throw $this->connection->convertException($e);
253 }
254 }
255
256 public function free(): void
257 {
258 $this->result->free();
259 }
260
261 /** @throws Exception */
262 private function ensureHasKeyValue(): void
263 {
264 $columnCount = $this->columnCount();
265
266 if ($columnCount < 2) {
267 throw NoKeyValue::fromColumnCount($columnCount);
268 }
269 }
270}
diff --git a/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php
new file mode 100644
index 0000000..5f6a77a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php
@@ -0,0 +1,73 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Builder;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Schema\Schema;
9use Doctrine\DBAL\Schema\Sequence;
10use Doctrine\DBAL\Schema\Table;
11
12use function array_merge;
13
14final class CreateSchemaObjectsSQLBuilder
15{
16 public function __construct(private readonly AbstractPlatform $platform)
17 {
18 }
19
20 /** @return list<string> */
21 public function buildSQL(Schema $schema): array
22 {
23 return array_merge(
24 $this->buildNamespaceStatements($schema->getNamespaces()),
25 $this->buildSequenceStatements($schema->getSequences()),
26 $this->buildTableStatements($schema->getTables()),
27 );
28 }
29
30 /**
31 * @param list<string> $namespaces
32 *
33 * @return list<string>
34 */
35 private function buildNamespaceStatements(array $namespaces): array
36 {
37 $statements = [];
38
39 if ($this->platform->supportsSchemas()) {
40 foreach ($namespaces as $namespace) {
41 $statements[] = $this->platform->getCreateSchemaSQL($namespace);
42 }
43 }
44
45 return $statements;
46 }
47
48 /**
49 * @param list<Table> $tables
50 *
51 * @return list<string>
52 */
53 private function buildTableStatements(array $tables): array
54 {
55 return $this->platform->getCreateTablesSQL($tables);
56 }
57
58 /**
59 * @param list<Sequence> $sequences
60 *
61 * @return list<string>
62 */
63 private function buildSequenceStatements(array $sequences): array
64 {
65 $statements = [];
66
67 foreach ($sequences as $sequence) {
68 $statements[] = $this->platform->getCreateSequenceSQL($sequence);
69 }
70
71 return $statements;
72 }
73}
diff --git a/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php
new file mode 100644
index 0000000..a30120e
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php
@@ -0,0 +1,94 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Builder;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Platforms\Exception\NotSupported;
10use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
11use Doctrine\DBAL\Query\SelectQuery;
12
13use function count;
14use function implode;
15
16final class DefaultSelectSQLBuilder implements SelectSQLBuilder
17{
18 /** @internal The SQL builder should be instantiated only by database platforms. */
19 public function __construct(
20 private readonly AbstractPlatform $platform,
21 private readonly ?string $forUpdateSQL,
22 private readonly ?string $skipLockedSQL,
23 ) {
24 }
25
26 /** @throws Exception */
27 public function buildSQL(SelectQuery $query): string
28 {
29 $parts = ['SELECT'];
30
31 if ($query->isDistinct()) {
32 $parts[] = 'DISTINCT';
33 }
34
35 $parts[] = implode(', ', $query->getColumns());
36
37 $from = $query->getFrom();
38
39 if (count($from) > 0) {
40 $parts[] = 'FROM ' . implode(', ', $from);
41 }
42
43 $where = $query->getWhere();
44
45 if ($where !== null) {
46 $parts[] = 'WHERE ' . $where;
47 }
48
49 $groupBy = $query->getGroupBy();
50
51 if (count($groupBy) > 0) {
52 $parts[] = 'GROUP BY ' . implode(', ', $groupBy);
53 }
54
55 $having = $query->getHaving();
56
57 if ($having !== null) {
58 $parts[] = 'HAVING ' . $having;
59 }
60
61 $orderBy = $query->getOrderBy();
62
63 if (count($orderBy) > 0) {
64 $parts[] = 'ORDER BY ' . implode(', ', $orderBy);
65 }
66
67 $sql = implode(' ', $parts);
68 $limit = $query->getLimit();
69
70 if ($limit->isDefined()) {
71 $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult());
72 }
73
74 $forUpdate = $query->getForUpdate();
75
76 if ($forUpdate !== null) {
77 if ($this->forUpdateSQL === null) {
78 throw NotSupported::new('FOR UPDATE');
79 }
80
81 $sql .= ' ' . $this->forUpdateSQL;
82
83 if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) {
84 if ($this->skipLockedSQL === null) {
85 throw NotSupported::new('SKIP LOCKED');
86 }
87
88 $sql .= ' ' . $this->skipLockedSQL;
89 }
90 }
91
92 return $sql;
93 }
94}
diff --git a/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php
new file mode 100644
index 0000000..c038489
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php
@@ -0,0 +1,54 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Builder;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Schema\Schema;
9use Doctrine\DBAL\Schema\Sequence;
10use Doctrine\DBAL\Schema\Table;
11
12use function array_merge;
13
14final class DropSchemaObjectsSQLBuilder
15{
16 public function __construct(private readonly AbstractPlatform $platform)
17 {
18 }
19
20 /** @return list<string> */
21 public function buildSQL(Schema $schema): array
22 {
23 return array_merge(
24 $this->buildSequenceStatements($schema->getSequences()),
25 $this->buildTableStatements($schema->getTables()),
26 );
27 }
28
29 /**
30 * @param list<Table> $tables
31 *
32 * @return list<string>
33 */
34 private function buildTableStatements(array $tables): array
35 {
36 return $this->platform->getDropTablesSQL($tables);
37 }
38
39 /**
40 * @param list<Sequence> $sequences
41 *
42 * @return list<string>
43 */
44 private function buildSequenceStatements(array $sequences): array
45 {
46 $statements = [];
47
48 foreach ($sequences as $sequence) {
49 $statements[] = $this->platform->getDropSequenceSQL($sequence->getQuotedName($this->platform));
50 }
51
52 return $statements;
53 }
54}
diff --git a/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php b/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php
new file mode 100644
index 0000000..c013f96
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Builder/SelectSQLBuilder.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Builder;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Query\SelectQuery;
9
10interface SelectSQLBuilder
11{
12 /** @throws Exception */
13 public function buildSQL(SelectQuery $query): string;
14}
diff --git a/vendor/doctrine/dbal/src/SQL/Parser.php b/vendor/doctrine/dbal/src/SQL/Parser.php
new file mode 100644
index 0000000..ad30f09
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Parser.php
@@ -0,0 +1,129 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL;
6
7use Doctrine\DBAL\SQL\Parser\Exception;
8use Doctrine\DBAL\SQL\Parser\Exception\RegularExpressionError;
9use Doctrine\DBAL\SQL\Parser\Visitor;
10
11use function array_merge;
12use function assert;
13use function current;
14use function implode;
15use function key;
16use function next;
17use function preg_last_error;
18use function preg_match;
19use function reset;
20use function sprintf;
21use function strlen;
22
23use const PREG_NO_ERROR;
24
25/**
26 * The SQL parser that focuses on identifying prepared statement parameters. It implements parsing other tokens like
27 * string literals and comments only as a way to not confuse their contents with the the parameter placeholders.
28 *
29 * The parsing logic and the implementation is inspired by the PHP PDO parser.
30 *
31 * @internal
32 *
33 * @see https://github.com/php/php-src/blob/php-7.4.12/ext/pdo/pdo_sql_parser.re#L49-L69
34 */
35final class Parser
36{
37 private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/';
38
39 private const BACKTICK_IDENTIFIER = '`[^`]*`';
40 private const BRACKET_IDENTIFIER = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]';
41 private const MULTICHAR = ':{2,}';
42 private const NAMED_PARAMETER = ':[a-zA-Z0-9_]+';
43 private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)';
44 private const ONE_LINE_COMMENT = '--[^\r\n]*';
45 private const MULTI_LINE_COMMENT = '/\*([^*]+|\*+[^/*])*\**\*/';
46 private const SPECIAL = '[' . self::SPECIAL_CHARS . ']';
47 private const OTHER = '[^' . self::SPECIAL_CHARS . ']+';
48
49 private readonly string $sqlPattern;
50
51 public function __construct(bool $mySQLStringEscaping)
52 {
53 if ($mySQLStringEscaping) {
54 $patterns = [
55 $this->getMySQLStringLiteralPattern("'"),
56 $this->getMySQLStringLiteralPattern('"'),
57 ];
58 } else {
59 $patterns = [
60 $this->getAnsiSQLStringLiteralPattern("'"),
61 $this->getAnsiSQLStringLiteralPattern('"'),
62 ];
63 }
64
65 $patterns = array_merge($patterns, [
66 self::BACKTICK_IDENTIFIER,
67 self::BRACKET_IDENTIFIER,
68 self::MULTICHAR,
69 self::ONE_LINE_COMMENT,
70 self::MULTI_LINE_COMMENT,
71 self::OTHER,
72 ]);
73
74 $this->sqlPattern = sprintf('(%s)', implode('|', $patterns));
75 }
76
77 /**
78 * Parses the given SQL statement
79 *
80 * @throws Exception
81 */
82 public function parse(string $sql, Visitor $visitor): void
83 {
84 /** @var array<string,callable> $patterns */
85 $patterns = [
86 self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void {
87 $visitor->acceptNamedParameter($sql);
88 },
89 self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void {
90 $visitor->acceptPositionalParameter($sql);
91 },
92 $this->sqlPattern => static function (string $sql) use ($visitor): void {
93 $visitor->acceptOther($sql);
94 },
95 self::SPECIAL => static function (string $sql) use ($visitor): void {
96 $visitor->acceptOther($sql);
97 },
98 ];
99
100 $offset = 0;
101
102 while (($handler = current($patterns)) !== false) {
103 if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) {
104 $handler($matches[0]);
105 reset($patterns);
106
107 $offset += strlen($matches[0]);
108 } elseif (preg_last_error() !== PREG_NO_ERROR) {
109 // @codeCoverageIgnoreStart
110 throw RegularExpressionError::new();
111 // @codeCoverageIgnoreEnd
112 } else {
113 next($patterns);
114 }
115 }
116
117 assert($offset === strlen($sql));
118 }
119
120 private function getMySQLStringLiteralPattern(string $delimiter): string
121 {
122 return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter;
123 }
124
125 private function getAnsiSQLStringLiteralPattern(string $delimiter): string
126 {
127 return $delimiter . '[^' . $delimiter . ']*' . $delimiter;
128 }
129}
diff --git a/vendor/doctrine/dbal/src/SQL/Parser/Exception.php b/vendor/doctrine/dbal/src/SQL/Parser/Exception.php
new file mode 100644
index 0000000..0c14b35
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Parser/Exception.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Parser;
6
7use Throwable;
8
9interface Exception extends Throwable
10{
11}
diff --git a/vendor/doctrine/dbal/src/SQL/Parser/Exception/RegularExpressionError.php b/vendor/doctrine/dbal/src/SQL/Parser/Exception/RegularExpressionError.php
new file mode 100644
index 0000000..81ae215
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Parser/Exception/RegularExpressionError.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Parser\Exception;
6
7use Doctrine\DBAL\SQL\Parser\Exception;
8use RuntimeException;
9
10use function preg_last_error;
11use function preg_last_error_msg;
12
13class RegularExpressionError extends RuntimeException implements Exception
14{
15 public static function new(): self
16 {
17 return new self(preg_last_error_msg(), preg_last_error());
18 }
19}
diff --git a/vendor/doctrine/dbal/src/SQL/Parser/Visitor.php b/vendor/doctrine/dbal/src/SQL/Parser/Visitor.php
new file mode 100644
index 0000000..c0b9e92
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Parser/Visitor.php
@@ -0,0 +1,28 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL\Parser;
6
7/**
8 * SQL parser visitor
9 *
10 * @internal
11 */
12interface Visitor
13{
14 /**
15 * Accepts an SQL fragment containing a positional parameter
16 */
17 public function acceptPositionalParameter(string $sql): void;
18
19 /**
20 * Accepts an SQL fragment containing a named parameter
21 */
22 public function acceptNamedParameter(string $sql): void;
23
24 /**
25 * Accepts other SQL fragments
26 */
27 public function acceptOther(string $sql): void;
28}
diff --git a/vendor/doctrine/dbal/src/Schema/AbstractAsset.php b/vendor/doctrine/dbal/src/Schema/AbstractAsset.php
new file mode 100644
index 0000000..de5b56f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/AbstractAsset.php
@@ -0,0 +1,157 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function array_map;
10use function crc32;
11use function dechex;
12use function explode;
13use function implode;
14use function str_contains;
15use function str_replace;
16use function strtolower;
17use function strtoupper;
18use function substr;
19
20/**
21 * The abstract asset allows to reset the name of all assets without publishing this to the public userland.
22 *
23 * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables
24 * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure this does not get
25 * recreated during schema migration.
26 */
27abstract class AbstractAsset
28{
29 protected string $_name = '';
30
31 /**
32 * Namespace of the asset. If none isset the default namespace is assumed.
33 */
34 protected ?string $_namespace = null;
35
36 protected bool $_quoted = false;
37
38 /**
39 * Sets the name of this asset.
40 */
41 protected function _setName(string $name): void
42 {
43 if ($this->isIdentifierQuoted($name)) {
44 $this->_quoted = true;
45 $name = $this->trimQuotes($name);
46 }
47
48 if (str_contains($name, '.')) {
49 $parts = explode('.', $name);
50 $this->_namespace = $parts[0];
51 $name = $parts[1];
52 }
53
54 $this->_name = $name;
55 }
56
57 /**
58 * Is this asset in the default namespace?
59 */
60 public function isInDefaultNamespace(string $defaultNamespaceName): bool
61 {
62 return $this->_namespace === $defaultNamespaceName || $this->_namespace === null;
63 }
64
65 /**
66 * Gets the namespace name of this asset.
67 *
68 * If NULL is returned this means the default namespace is used.
69 */
70 public function getNamespaceName(): ?string
71 {
72 return $this->_namespace;
73 }
74
75 /**
76 * The shortest name is stripped of the default namespace. All other
77 * namespaced elements are returned as full-qualified names.
78 */
79 public function getShortestName(?string $defaultNamespaceName): string
80 {
81 $shortestName = $this->getName();
82 if ($this->_namespace === $defaultNamespaceName) {
83 $shortestName = $this->_name;
84 }
85
86 return strtolower($shortestName);
87 }
88
89 /**
90 * Checks if this asset's name is quoted.
91 */
92 public function isQuoted(): bool
93 {
94 return $this->_quoted;
95 }
96
97 /**
98 * Checks if this identifier is quoted.
99 */
100 protected function isIdentifierQuoted(string $identifier): bool
101 {
102 return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '[');
103 }
104
105 /**
106 * Trim quotes from the identifier.
107 */
108 protected function trimQuotes(string $identifier): string
109 {
110 return str_replace(['`', '"', '[', ']'], '', $identifier);
111 }
112
113 /**
114 * Returns the name of this schema asset.
115 */
116 public function getName(): string
117 {
118 if ($this->_namespace !== null) {
119 return $this->_namespace . '.' . $this->_name;
120 }
121
122 return $this->_name;
123 }
124
125 /**
126 * Gets the quoted representation of this asset but only if it was defined with one. Otherwise
127 * return the plain unquoted value as inserted.
128 */
129 public function getQuotedName(AbstractPlatform $platform): string
130 {
131 $keywords = $platform->getReservedKeywordsList();
132 $parts = explode('.', $this->getName());
133 foreach ($parts as $k => $v) {
134 $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v;
135 }
136
137 return implode('.', $parts);
138 }
139
140 /**
141 * Generates an identifier from a list of column names obeying a certain string length.
142 *
143 * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars,
144 * however building idents automatically for foreign keys, composite keys or such can easily create
145 * very long names.
146 *
147 * @param array<int, string> $columnNames
148 */
149 protected function _generateIdentifierName(array $columnNames, string $prefix = '', int $maxSize = 30): string
150 {
151 $hash = implode('', array_map(static function ($column): string {
152 return dechex(crc32($column));
153 }, $columnNames));
154
155 return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize));
156 }
157}
diff --git a/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php
new file mode 100644
index 0000000..9730797
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php
@@ -0,0 +1,864 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Exception\DatabaseRequired;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use Doctrine\DBAL\Platforms\Exception\NotSupported;
12use Doctrine\DBAL\Result;
13use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
14
15use function array_filter;
16use function array_intersect;
17use function array_map;
18use function array_values;
19use function count;
20use function strtolower;
21
22/**
23 * Base class for schema managers. Schema managers are used to inspect and/or
24 * modify the database schema/structure.
25 *
26 * @template-covariant T of AbstractPlatform
27 */
28abstract class AbstractSchemaManager
29{
30 /** @param T $platform */
31 public function __construct(protected Connection $connection, protected AbstractPlatform $platform)
32 {
33 }
34
35 /**
36 * Lists the available databases for this connection.
37 *
38 * @return array<int, string>
39 *
40 * @throws Exception
41 */
42 public function listDatabases(): array
43 {
44 return array_map(function (array $row): string {
45 return $this->_getPortableDatabaseDefinition($row);
46 }, $this->connection->fetchAllAssociative(
47 $this->platform->getListDatabasesSQL(),
48 ));
49 }
50
51 /**
52 * Returns a list of the names of all schemata in the current database.
53 *
54 * @return list<string>
55 *
56 * @throws Exception
57 */
58 public function listSchemaNames(): array
59 {
60 throw NotSupported::new(__METHOD__);
61 }
62
63 /**
64 * Lists the available sequences for this connection.
65 *
66 * @return array<int, Sequence>
67 *
68 * @throws Exception
69 */
70 public function listSequences(): array
71 {
72 return $this->filterAssetNames(
73 array_map(function (array $row): Sequence {
74 return $this->_getPortableSequenceDefinition($row);
75 }, $this->connection->fetchAllAssociative(
76 $this->platform->getListSequencesSQL(
77 $this->getDatabase(__METHOD__),
78 ),
79 )),
80 );
81 }
82
83 /**
84 * Lists the columns for a given table.
85 *
86 * In contrast to other libraries and to the old version of Doctrine,
87 * this column definition does try to contain the 'primary' column for
88 * the reason that it is not portable across different RDBMS. Use
89 * {@see listTableIndexes($tableName)} to retrieve the primary key
90 * of a table. Where a RDBMS specifies more details, these are held
91 * in the platformDetails array.
92 *
93 * @return array<string, Column>
94 *
95 * @throws Exception
96 */
97 public function listTableColumns(string $table): array
98 {
99 $database = $this->getDatabase(__METHOD__);
100
101 return $this->_getPortableTableColumnList(
102 $table,
103 $database,
104 $this->selectTableColumns($database, $this->normalizeName($table))
105 ->fetchAllAssociative(),
106 );
107 }
108
109 /**
110 * Lists the indexes for a given table returning an array of Index instances.
111 *
112 * Keys of the portable indexes list are all lower-cased.
113 *
114 * @return array<string, Index>
115 *
116 * @throws Exception
117 */
118 public function listTableIndexes(string $table): array
119 {
120 $database = $this->getDatabase(__METHOD__);
121 $table = $this->normalizeName($table);
122
123 return $this->_getPortableTableIndexesList(
124 $this->selectIndexColumns(
125 $database,
126 $table,
127 )->fetchAllAssociative(),
128 $table,
129 );
130 }
131
132 /**
133 * Returns true if all the given tables exist.
134 *
135 * @param array<int, string> $names
136 *
137 * @throws Exception
138 */
139 public function tablesExist(array $names): bool
140 {
141 $names = array_map('strtolower', $names);
142
143 return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames())));
144 }
145
146 public function tableExists(string $tableName): bool
147 {
148 return $this->tablesExist([$tableName]);
149 }
150
151 /**
152 * Returns a list of all tables in the current database.
153 *
154 * @return array<int, string>
155 *
156 * @throws Exception
157 */
158 public function listTableNames(): array
159 {
160 return $this->filterAssetNames(
161 array_map(function (array $row): string {
162 return $this->_getPortableTableDefinition($row);
163 }, $this->selectTableNames(
164 $this->getDatabase(__METHOD__),
165 )->fetchAllAssociative()),
166 );
167 }
168
169 /**
170 * Filters asset names if they are configured to return only a subset of all
171 * the found elements.
172 *
173 * @param array<int, mixed> $assetNames
174 *
175 * @return array<int, mixed>
176 */
177 private function filterAssetNames(array $assetNames): array
178 {
179 $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter();
180
181 return array_values(array_filter($assetNames, $filter));
182 }
183
184 /**
185 * Lists the tables for this connection.
186 *
187 * @return list<Table>
188 *
189 * @throws Exception
190 */
191 public function listTables(): array
192 {
193 $database = $this->getDatabase(__METHOD__);
194
195 $tableColumnsByTable = $this->fetchTableColumnsByTable($database);
196 $indexColumnsByTable = $this->fetchIndexColumnsByTable($database);
197 $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database);
198 $tableOptionsByTable = $this->fetchTableOptionsByTable($database);
199
200 $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter();
201 $tables = [];
202
203 foreach ($tableColumnsByTable as $tableName => $tableColumns) {
204 if (! $filter($tableName)) {
205 continue;
206 }
207
208 $tables[] = new Table(
209 $tableName,
210 $this->_getPortableTableColumnList($tableName, $database, $tableColumns),
211 $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName),
212 [],
213 $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []),
214 $tableOptionsByTable[$tableName] ?? [],
215 );
216 }
217
218 return $tables;
219 }
220
221 /**
222 * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted.
223 *
224 * Such platforms should convert a possibly quoted name into a value of the corresponding case.
225 */
226 protected function normalizeName(string $name): string
227 {
228 $identifier = new Identifier($name);
229
230 return $identifier->getName();
231 }
232
233 /**
234 * Selects names of tables in the specified database.
235 *
236 * @throws Exception
237 */
238 abstract protected function selectTableNames(string $databaseName): Result;
239
240 /**
241 * Selects definitions of table columns in the specified database. If the table name is specified, narrows down
242 * the selection to this table.
243 *
244 * @throws Exception
245 */
246 abstract protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result;
247
248 /**
249 * Selects definitions of index columns in the specified database. If the table name is specified, narrows down
250 * the selection to this table.
251 *
252 * @throws Exception
253 */
254 abstract protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result;
255
256 /**
257 * Selects definitions of foreign key columns in the specified database. If the table name is specified,
258 * narrows down the selection to this table.
259 *
260 * @throws Exception
261 */
262 abstract protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result;
263
264 /**
265 * Fetches definitions of table columns in the specified database and returns them grouped by table name.
266 *
267 * @return array<string,list<array<string,mixed>>>
268 *
269 * @throws Exception
270 */
271 protected function fetchTableColumnsByTable(string $databaseName): array
272 {
273 return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName));
274 }
275
276 /**
277 * Fetches definitions of index columns in the specified database and returns them grouped by table name.
278 *
279 * @return array<string,list<array<string,mixed>>>
280 *
281 * @throws Exception
282 */
283 protected function fetchIndexColumnsByTable(string $databaseName): array
284 {
285 return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName));
286 }
287
288 /**
289 * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name.
290 *
291 * @return array<string, list<array<string, mixed>>>
292 *
293 * @throws Exception
294 */
295 protected function fetchForeignKeyColumnsByTable(string $databaseName): array
296 {
297 return $this->fetchAllAssociativeGrouped(
298 $this->selectForeignKeyColumns($databaseName),
299 );
300 }
301
302 /**
303 * Fetches table options for the tables in the specified database and returns them grouped by table name.
304 * If the table name is specified, narrows down the selection to this table.
305 *
306 * @return array<string,array<string,mixed>>
307 *
308 * @throws Exception
309 */
310 abstract protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array;
311
312 /**
313 * Introspects the table with the given name.
314 *
315 * @throws Exception
316 */
317 public function introspectTable(string $name): Table
318 {
319 $columns = $this->listTableColumns($name);
320
321 if ($columns === []) {
322 throw TableDoesNotExist::new($name);
323 }
324
325 return new Table(
326 $name,
327 $columns,
328 $this->listTableIndexes($name),
329 [],
330 $this->listTableForeignKeys($name),
331 $this->getTableOptions($name),
332 );
333 }
334
335 /**
336 * Lists the views this connection has.
337 *
338 * @return list<View>
339 *
340 * @throws Exception
341 */
342 public function listViews(): array
343 {
344 return array_map(function (array $row): View {
345 return $this->_getPortableViewDefinition($row);
346 }, $this->connection->fetchAllAssociative(
347 $this->platform->getListViewsSQL(
348 $this->getDatabase(__METHOD__),
349 ),
350 ));
351 }
352
353 /**
354 * Lists the foreign keys for the given table.
355 *
356 * @return array<int|string, ForeignKeyConstraint>
357 *
358 * @throws Exception
359 */
360 public function listTableForeignKeys(string $table): array
361 {
362 $database = $this->getDatabase(__METHOD__);
363
364 return $this->_getPortableTableForeignKeysList(
365 $this->selectForeignKeyColumns(
366 $database,
367 $this->normalizeName($table),
368 )->fetchAllAssociative(),
369 );
370 }
371
372 /**
373 * @return array<string, mixed>
374 *
375 * @throws Exception
376 */
377 private function getTableOptions(string $name): array
378 {
379 $normalizedName = $this->normalizeName($name);
380
381 return $this->fetchTableOptionsByTable(
382 $this->getDatabase(__METHOD__),
383 $normalizedName,
384 )[$normalizedName] ?? [];
385 }
386
387 /* drop*() Methods */
388
389 /**
390 * Drops a database.
391 *
392 * NOTE: You can not drop the database this SchemaManager is currently connected to.
393 *
394 * @throws Exception
395 */
396 public function dropDatabase(string $database): void
397 {
398 $this->connection->executeStatement(
399 $this->platform->getDropDatabaseSQL($database),
400 );
401 }
402
403 /**
404 * Drops a schema.
405 *
406 * @throws Exception
407 */
408 public function dropSchema(string $schemaName): void
409 {
410 $this->connection->executeStatement(
411 $this->platform->getDropSchemaSQL($schemaName),
412 );
413 }
414
415 /**
416 * Drops the given table.
417 *
418 * @throws Exception
419 */
420 public function dropTable(string $name): void
421 {
422 $this->connection->executeStatement(
423 $this->platform->getDropTableSQL($name),
424 );
425 }
426
427 /**
428 * Drops the index from the given table.
429 *
430 * @throws Exception
431 */
432 public function dropIndex(string $index, string $table): void
433 {
434 $this->connection->executeStatement(
435 $this->platform->getDropIndexSQL($index, $table),
436 );
437 }
438
439 /**
440 * Drops a foreign key from a table.
441 *
442 * @throws Exception
443 */
444 public function dropForeignKey(string $name, string $table): void
445 {
446 $this->connection->executeStatement(
447 $this->platform->getDropForeignKeySQL($name, $table),
448 );
449 }
450
451 /**
452 * Drops a sequence with a given name.
453 *
454 * @throws Exception
455 */
456 public function dropSequence(string $name): void
457 {
458 $this->connection->executeStatement(
459 $this->platform->getDropSequenceSQL($name),
460 );
461 }
462
463 /**
464 * Drops the unique constraint from the given table.
465 *
466 * @throws Exception
467 */
468 public function dropUniqueConstraint(string $name, string $tableName): void
469 {
470 $this->connection->executeStatement(
471 $this->platform->getDropUniqueConstraintSQL($name, $tableName),
472 );
473 }
474
475 /**
476 * Drops a view.
477 *
478 * @throws Exception
479 */
480 public function dropView(string $name): void
481 {
482 $this->connection->executeStatement(
483 $this->platform->getDropViewSQL($name),
484 );
485 }
486
487 /* create*() Methods */
488
489 /** @throws Exception */
490 public function createSchemaObjects(Schema $schema): void
491 {
492 $this->executeStatements($schema->toSql($this->platform));
493 }
494
495 /**
496 * Creates a new database.
497 *
498 * @throws Exception
499 */
500 public function createDatabase(string $database): void
501 {
502 $this->connection->executeStatement(
503 $this->platform->getCreateDatabaseSQL($database),
504 );
505 }
506
507 /**
508 * Creates a new table.
509 *
510 * @throws Exception
511 */
512 public function createTable(Table $table): void
513 {
514 $this->executeStatements($this->platform->getCreateTableSQL($table));
515 }
516
517 /**
518 * Creates a new sequence.
519 *
520 * @throws Exception
521 */
522 public function createSequence(Sequence $sequence): void
523 {
524 $this->connection->executeStatement(
525 $this->platform->getCreateSequenceSQL($sequence),
526 );
527 }
528
529 /**
530 * Creates a new index on a table.
531 *
532 * @param string $table The name of the table on which the index is to be created.
533 *
534 * @throws Exception
535 */
536 public function createIndex(Index $index, string $table): void
537 {
538 $this->connection->executeStatement(
539 $this->platform->getCreateIndexSQL($index, $table),
540 );
541 }
542
543 /**
544 * Creates a new foreign key.
545 *
546 * @param ForeignKeyConstraint $foreignKey The ForeignKey instance.
547 * @param string $table The name of the table on which the foreign key is to be created.
548 *
549 * @throws Exception
550 */
551 public function createForeignKey(ForeignKeyConstraint $foreignKey, string $table): void
552 {
553 $this->connection->executeStatement(
554 $this->platform->getCreateForeignKeySQL($foreignKey, $table),
555 );
556 }
557
558 /**
559 * Creates a unique constraint on a table.
560 *
561 * @throws Exception
562 */
563 public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void
564 {
565 $this->connection->executeStatement(
566 $this->platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName),
567 );
568 }
569
570 /**
571 * Creates a new view.
572 *
573 * @throws Exception
574 */
575 public function createView(View $view): void
576 {
577 $this->connection->executeStatement(
578 $this->platform->getCreateViewSQL(
579 $view->getQuotedName($this->platform),
580 $view->getSql(),
581 ),
582 );
583 }
584
585 /** @throws Exception */
586 public function dropSchemaObjects(Schema $schema): void
587 {
588 $this->executeStatements($schema->toDropSql($this->platform));
589 }
590
591 /**
592 * Alters an existing schema.
593 *
594 * @throws Exception
595 */
596 public function alterSchema(SchemaDiff $schemaDiff): void
597 {
598 $this->executeStatements($this->platform->getAlterSchemaSQL($schemaDiff));
599 }
600
601 /**
602 * Migrates an existing schema to a new schema.
603 *
604 * @throws Exception
605 */
606 public function migrateSchema(Schema $newSchema): void
607 {
608 $schemaDiff = $this->createComparator()
609 ->compareSchemas($this->introspectSchema(), $newSchema);
610
611 $this->alterSchema($schemaDiff);
612 }
613
614 /* alterTable() Methods */
615
616 /**
617 * Alters an existing tables schema.
618 *
619 * @throws Exception
620 */
621 public function alterTable(TableDiff $tableDiff): void
622 {
623 $this->executeStatements($this->platform->getAlterTableSQL($tableDiff));
624 }
625
626 /**
627 * Renames a given table to another name.
628 *
629 * @throws Exception
630 */
631 public function renameTable(string $name, string $newName): void
632 {
633 $this->connection->executeStatement(
634 $this->platform->getRenameTableSQL($name, $newName),
635 );
636 }
637
638 /**
639 * Methods for filtering return values of list*() methods to convert
640 * the native DBMS data definition to a portable Doctrine definition
641 */
642
643 /** @param array<string, string> $database */
644 protected function _getPortableDatabaseDefinition(array $database): string
645 {
646 throw NotSupported::new(__METHOD__);
647 }
648
649 /** @param array<string, mixed> $sequence */
650 protected function _getPortableSequenceDefinition(array $sequence): Sequence
651 {
652 throw NotSupported::new(__METHOD__);
653 }
654
655 /**
656 * Independent of the database the keys of the column list result are lowercased.
657 *
658 * The name of the created column instance however is kept in its case.
659 *
660 * @param array<int, array<string, mixed>> $tableColumns
661 *
662 * @return array<string, Column>
663 *
664 * @throws Exception
665 */
666 protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array
667 {
668 $list = [];
669 foreach ($tableColumns as $tableColumn) {
670 $column = $this->_getPortableTableColumnDefinition($tableColumn);
671
672 $name = strtolower($column->getQuotedName($this->platform));
673 $list[$name] = $column;
674 }
675
676 return $list;
677 }
678
679 /**
680 * Gets Table Column Definition.
681 *
682 * @param array<string, mixed> $tableColumn
683 *
684 * @throws Exception
685 */
686 abstract protected function _getPortableTableColumnDefinition(array $tableColumn): Column;
687
688 /**
689 * Aggregates and groups the index results according to the required data result.
690 *
691 * @param array<int, array<string, mixed>> $tableIndexes
692 *
693 * @return array<string, Index>
694 *
695 * @throws Exception
696 */
697 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
698 {
699 $result = [];
700 foreach ($tableIndexes as $tableIndex) {
701 $indexName = $keyName = $tableIndex['key_name'];
702 if ($tableIndex['primary']) {
703 $keyName = 'primary';
704 }
705
706 $keyName = strtolower($keyName);
707
708 if (! isset($result[$keyName])) {
709 $options = [
710 'lengths' => [],
711 ];
712
713 if (isset($tableIndex['where'])) {
714 $options['where'] = $tableIndex['where'];
715 }
716
717 $result[$keyName] = [
718 'name' => $indexName,
719 'columns' => [],
720 'unique' => ! $tableIndex['non_unique'],
721 'primary' => $tableIndex['primary'],
722 'flags' => $tableIndex['flags'] ?? [],
723 'options' => $options,
724 ];
725 }
726
727 $result[$keyName]['columns'][] = $tableIndex['column_name'];
728 $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null;
729 }
730
731 $indexes = [];
732 foreach ($result as $indexKey => $data) {
733 $indexes[$indexKey] = new Index(
734 $data['name'],
735 $data['columns'],
736 $data['unique'],
737 $data['primary'],
738 $data['flags'],
739 $data['options'],
740 );
741 }
742
743 return $indexes;
744 }
745
746 /** @param array<string, string> $table */
747 abstract protected function _getPortableTableDefinition(array $table): string;
748
749 /** @param array<string, mixed> $view */
750 abstract protected function _getPortableViewDefinition(array $view): View;
751
752 /**
753 * @param array<int|string, array<string, mixed>> $tableForeignKeys
754 *
755 * @return array<int, ForeignKeyConstraint>
756 */
757 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
758 {
759 $list = [];
760
761 foreach ($tableForeignKeys as $value) {
762 $list[] = $this->_getPortableTableForeignKeyDefinition($value);
763 }
764
765 return $list;
766 }
767
768 /** @param array<string, mixed> $tableForeignKey */
769 abstract protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint;
770
771 /**
772 * @param array<int, string> $sql
773 *
774 * @throws Exception
775 */
776 private function executeStatements(array $sql): void
777 {
778 foreach ($sql as $query) {
779 $this->connection->executeStatement($query);
780 }
781 }
782
783 /**
784 * Returns a {@see Schema} instance representing the current database schema.
785 *
786 * @throws Exception
787 */
788 public function introspectSchema(): Schema
789 {
790 $schemaNames = [];
791
792 if ($this->platform->supportsSchemas()) {
793 $schemaNames = $this->listSchemaNames();
794 }
795
796 $sequences = [];
797
798 if ($this->platform->supportsSequences()) {
799 $sequences = $this->listSequences();
800 }
801
802 $tables = $this->listTables();
803
804 return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames);
805 }
806
807 /**
808 * Creates the configuration for this schema.
809 *
810 * @throws Exception
811 */
812 public function createSchemaConfig(): SchemaConfig
813 {
814 $schemaConfig = new SchemaConfig();
815 $schemaConfig->setMaxIdentifierLength($this->platform->getMaxIdentifierLength());
816
817 $params = $this->connection->getParams();
818 if (! isset($params['defaultTableOptions'])) {
819 $params['defaultTableOptions'] = [];
820 }
821
822 if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
823 $params['defaultTableOptions']['charset'] = $params['charset'];
824 }
825
826 $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
827
828 return $schemaConfig;
829 }
830
831 /** @throws Exception */
832 private function getDatabase(string $methodName): string
833 {
834 $database = $this->connection->getDatabase();
835
836 if ($database === null) {
837 throw DatabaseRequired::new($methodName);
838 }
839
840 return $database;
841 }
842
843 public function createComparator(): Comparator
844 {
845 return new Comparator($this->platform);
846 }
847
848 /**
849 * @return array<string,list<array<string,mixed>>>
850 *
851 * @throws Exception
852 */
853 private function fetchAllAssociativeGrouped(Result $result): array
854 {
855 $data = [];
856
857 foreach ($result->fetchAllAssociative() as $row) {
858 $tableName = $this->_getPortableTableDefinition($row);
859 $data[$tableName][] = $row;
860 }
861
862 return $data;
863 }
864}
diff --git a/vendor/doctrine/dbal/src/Schema/Column.php b/vendor/doctrine/dbal/src/Schema/Column.php
new file mode 100644
index 0000000..8963cd7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Column.php
@@ -0,0 +1,252 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Schema\Exception\UnknownColumnOption;
8use Doctrine\DBAL\Types\Type;
9
10use function array_merge;
11use function method_exists;
12
13/**
14 * Object representation of a database column.
15 */
16class Column extends AbstractAsset
17{
18 protected Type $_type;
19
20 protected ?int $_length = null;
21
22 protected ?int $_precision = null;
23
24 protected int $_scale = 0;
25
26 protected bool $_unsigned = false;
27
28 protected bool $_fixed = false;
29
30 protected bool $_notnull = true;
31
32 protected mixed $_default = null;
33
34 protected bool $_autoincrement = false;
35
36 /** @var array<string, mixed> */
37 protected array $_platformOptions = [];
38
39 protected ?string $_columnDefinition = null;
40
41 protected string $_comment = '';
42
43 /**
44 * Creates a new Column.
45 *
46 * @param array<string, mixed> $options
47 */
48 public function __construct(string $name, Type $type, array $options = [])
49 {
50 $this->_setName($name);
51 $this->setType($type);
52 $this->setOptions($options);
53 }
54
55 /** @param array<string, mixed> $options */
56 public function setOptions(array $options): self
57 {
58 foreach ($options as $name => $value) {
59 $method = 'set' . $name;
60
61 if (! method_exists($this, $method)) {
62 throw UnknownColumnOption::new($name);
63 }
64
65 $this->$method($value);
66 }
67
68 return $this;
69 }
70
71 public function setType(Type $type): self
72 {
73 $this->_type = $type;
74
75 return $this;
76 }
77
78 public function setLength(?int $length): self
79 {
80 $this->_length = $length;
81
82 return $this;
83 }
84
85 public function setPrecision(?int $precision): self
86 {
87 $this->_precision = $precision;
88
89 return $this;
90 }
91
92 public function setScale(int $scale): self
93 {
94 $this->_scale = $scale;
95
96 return $this;
97 }
98
99 public function setUnsigned(bool $unsigned): self
100 {
101 $this->_unsigned = $unsigned;
102
103 return $this;
104 }
105
106 public function setFixed(bool $fixed): self
107 {
108 $this->_fixed = $fixed;
109
110 return $this;
111 }
112
113 public function setNotnull(bool $notnull): self
114 {
115 $this->_notnull = $notnull;
116
117 return $this;
118 }
119
120 public function setDefault(mixed $default): self
121 {
122 $this->_default = $default;
123
124 return $this;
125 }
126
127 /** @param array<string, mixed> $platformOptions */
128 public function setPlatformOptions(array $platformOptions): self
129 {
130 $this->_platformOptions = $platformOptions;
131
132 return $this;
133 }
134
135 public function setPlatformOption(string $name, mixed $value): self
136 {
137 $this->_platformOptions[$name] = $value;
138
139 return $this;
140 }
141
142 public function setColumnDefinition(?string $value): self
143 {
144 $this->_columnDefinition = $value;
145
146 return $this;
147 }
148
149 public function getType(): Type
150 {
151 return $this->_type;
152 }
153
154 public function getLength(): ?int
155 {
156 return $this->_length;
157 }
158
159 public function getPrecision(): ?int
160 {
161 return $this->_precision;
162 }
163
164 public function getScale(): int
165 {
166 return $this->_scale;
167 }
168
169 public function getUnsigned(): bool
170 {
171 return $this->_unsigned;
172 }
173
174 public function getFixed(): bool
175 {
176 return $this->_fixed;
177 }
178
179 public function getNotnull(): bool
180 {
181 return $this->_notnull;
182 }
183
184 public function getDefault(): mixed
185 {
186 return $this->_default;
187 }
188
189 /** @return array<string, mixed> */
190 public function getPlatformOptions(): array
191 {
192 return $this->_platformOptions;
193 }
194
195 public function hasPlatformOption(string $name): bool
196 {
197 return isset($this->_platformOptions[$name]);
198 }
199
200 public function getPlatformOption(string $name): mixed
201 {
202 return $this->_platformOptions[$name];
203 }
204
205 public function getColumnDefinition(): ?string
206 {
207 return $this->_columnDefinition;
208 }
209
210 public function getAutoincrement(): bool
211 {
212 return $this->_autoincrement;
213 }
214
215 public function setAutoincrement(bool $flag): self
216 {
217 $this->_autoincrement = $flag;
218
219 return $this;
220 }
221
222 public function setComment(string $comment): self
223 {
224 $this->_comment = $comment;
225
226 return $this;
227 }
228
229 public function getComment(): string
230 {
231 return $this->_comment;
232 }
233
234 /** @return array<string, mixed> */
235 public function toArray(): array
236 {
237 return array_merge([
238 'name' => $this->_name,
239 'type' => $this->_type,
240 'default' => $this->_default,
241 'notnull' => $this->_notnull,
242 'length' => $this->_length,
243 'precision' => $this->_precision,
244 'scale' => $this->_scale,
245 'fixed' => $this->_fixed,
246 'unsigned' => $this->_unsigned,
247 'autoincrement' => $this->_autoincrement,
248 'columnDefinition' => $this->_columnDefinition,
249 'comment' => $this->_comment,
250 ], $this->_platformOptions);
251 }
252}
diff --git a/vendor/doctrine/dbal/src/Schema/ColumnDiff.php b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php
new file mode 100644
index 0000000..3e4950a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php
@@ -0,0 +1,106 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7/**
8 * Represents the change of a column.
9 */
10class ColumnDiff
11{
12 /** @internal The diff can be only instantiated by a {@see Comparator}. */
13 public function __construct(private readonly Column $oldColumn, private readonly Column $newColumn)
14 {
15 }
16
17 public function getOldColumn(): Column
18 {
19 return $this->oldColumn;
20 }
21
22 public function getNewColumn(): Column
23 {
24 return $this->newColumn;
25 }
26
27 public function hasTypeChanged(): bool
28 {
29 return $this->newColumn->getType()::class !== $this->oldColumn->getType()::class;
30 }
31
32 public function hasLengthChanged(): bool
33 {
34 return $this->hasPropertyChanged(static function (Column $column): ?int {
35 return $column->getLength();
36 });
37 }
38
39 public function hasPrecisionChanged(): bool
40 {
41 return $this->hasPropertyChanged(static function (Column $column): ?int {
42 return $column->getPrecision();
43 });
44 }
45
46 public function hasScaleChanged(): bool
47 {
48 return $this->hasPropertyChanged(static function (Column $column): int {
49 return $column->getScale();
50 });
51 }
52
53 public function hasUnsignedChanged(): bool
54 {
55 return $this->hasPropertyChanged(static function (Column $column): bool {
56 return $column->getUnsigned();
57 });
58 }
59
60 public function hasFixedChanged(): bool
61 {
62 return $this->hasPropertyChanged(static function (Column $column): bool {
63 return $column->getFixed();
64 });
65 }
66
67 public function hasNotNullChanged(): bool
68 {
69 return $this->hasPropertyChanged(static function (Column $column): bool {
70 return $column->getNotnull();
71 });
72 }
73
74 public function hasDefaultChanged(): bool
75 {
76 $oldDefault = $this->oldColumn->getDefault();
77 $newDefault = $this->newColumn->getDefault();
78
79 // Null values need to be checked additionally as they tell whether to create or drop a default value.
80 // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation.
81 if (($newDefault === null) xor ($oldDefault === null)) {
82 return true;
83 }
84
85 return $newDefault != $oldDefault;
86 }
87
88 public function hasAutoIncrementChanged(): bool
89 {
90 return $this->hasPropertyChanged(static function (Column $column): bool {
91 return $column->getAutoincrement();
92 });
93 }
94
95 public function hasCommentChanged(): bool
96 {
97 return $this->hasPropertyChanged(static function (Column $column): string {
98 return $column->getComment();
99 });
100 }
101
102 private function hasPropertyChanged(callable $property): bool
103 {
104 return $property($this->newColumn) !== $property($this->oldColumn);
105 }
106}
diff --git a/vendor/doctrine/dbal/src/Schema/Comparator.php b/vendor/doctrine/dbal/src/Schema/Comparator.php
new file mode 100644
index 0000000..4c60c07
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Comparator.php
@@ -0,0 +1,417 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function array_map;
10use function assert;
11use function count;
12use function strtolower;
13
14/**
15 * Compares two Schemas and return an instance of SchemaDiff.
16 */
17class Comparator
18{
19 /** @internal The comparator can be only instantiated by a schema manager. */
20 public function __construct(private readonly AbstractPlatform $platform)
21 {
22 }
23
24 /**
25 * Returns the differences between the schemas.
26 */
27 public function compareSchemas(Schema $oldSchema, Schema $newSchema): SchemaDiff
28 {
29 $createdSchemas = [];
30 $droppedSchemas = [];
31 $createdTables = [];
32 $alteredTables = [];
33 $droppedTables = [];
34 $createdSequences = [];
35 $alteredSequences = [];
36 $droppedSequences = [];
37
38 foreach ($newSchema->getNamespaces() as $newNamespace) {
39 if ($oldSchema->hasNamespace($newNamespace)) {
40 continue;
41 }
42
43 $createdSchemas[] = $newNamespace;
44 }
45
46 foreach ($oldSchema->getNamespaces() as $oldNamespace) {
47 if ($newSchema->hasNamespace($oldNamespace)) {
48 continue;
49 }
50
51 $droppedSchemas[] = $oldNamespace;
52 }
53
54 foreach ($newSchema->getTables() as $newTable) {
55 $newTableName = $newTable->getShortestName($newSchema->getName());
56 if (! $oldSchema->hasTable($newTableName)) {
57 $createdTables[] = $newSchema->getTable($newTableName);
58 } else {
59 $tableDiff = $this->compareTables(
60 $oldSchema->getTable($newTableName),
61 $newSchema->getTable($newTableName),
62 );
63
64 if (! $tableDiff->isEmpty()) {
65 $alteredTables[] = $tableDiff;
66 }
67 }
68 }
69
70 // Check if there are tables removed
71 foreach ($oldSchema->getTables() as $oldTable) {
72 $oldTableName = $oldTable->getShortestName($oldSchema->getName());
73
74 $oldTable = $oldSchema->getTable($oldTableName);
75 if ($newSchema->hasTable($oldTableName)) {
76 continue;
77 }
78
79 $droppedTables[] = $oldTable;
80 }
81
82 foreach ($newSchema->getSequences() as $newSequence) {
83 $newSequenceName = $newSequence->getShortestName($newSchema->getName());
84 if (! $oldSchema->hasSequence($newSequenceName)) {
85 if (! $this->isAutoIncrementSequenceInSchema($oldSchema, $newSequence)) {
86 $createdSequences[] = $newSequence;
87 }
88 } else {
89 if ($this->diffSequence($newSequence, $oldSchema->getSequence($newSequenceName))) {
90 $alteredSequences[] = $newSchema->getSequence($newSequenceName);
91 }
92 }
93 }
94
95 foreach ($oldSchema->getSequences() as $oldSequence) {
96 if ($this->isAutoIncrementSequenceInSchema($newSchema, $oldSequence)) {
97 continue;
98 }
99
100 $oldSequenceName = $oldSequence->getShortestName($oldSchema->getName());
101
102 if ($newSchema->hasSequence($oldSequenceName)) {
103 continue;
104 }
105
106 $droppedSequences[] = $oldSequence;
107 }
108
109 return new SchemaDiff(
110 $createdSchemas,
111 $droppedSchemas,
112 $createdTables,
113 $alteredTables,
114 $droppedTables,
115 $createdSequences,
116 $alteredSequences,
117 $droppedSequences,
118 );
119 }
120
121 private function isAutoIncrementSequenceInSchema(Schema $schema, Sequence $sequence): bool
122 {
123 foreach ($schema->getTables() as $table) {
124 if ($sequence->isAutoIncrementsFor($table)) {
125 return true;
126 }
127 }
128
129 return false;
130 }
131
132 public function diffSequence(Sequence $sequence1, Sequence $sequence2): bool
133 {
134 if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) {
135 return true;
136 }
137
138 return $sequence1->getInitialValue() !== $sequence2->getInitialValue();
139 }
140
141 /**
142 * Compares the tables and returns the difference between them.
143 */
144 public function compareTables(Table $oldTable, Table $newTable): TableDiff
145 {
146 $addedColumns = [];
147 $modifiedColumns = [];
148 $droppedColumns = [];
149 $addedIndexes = [];
150 $modifiedIndexes = [];
151 $droppedIndexes = [];
152 $addedForeignKeys = [];
153 $modifiedForeignKeys = [];
154 $droppedForeignKeys = [];
155
156 $oldColumns = $oldTable->getColumns();
157 $newColumns = $newTable->getColumns();
158
159 // See if all the columns in the old table exist in the new table
160 foreach ($newColumns as $newColumn) {
161 $newColumnName = strtolower($newColumn->getName());
162
163 if ($oldTable->hasColumn($newColumnName)) {
164 continue;
165 }
166
167 $addedColumns[$newColumnName] = $newColumn;
168 }
169
170 // See if there are any removed columns in the new table
171 foreach ($oldColumns as $oldColumn) {
172 $oldColumnName = strtolower($oldColumn->getName());
173
174 // See if column is removed in the new table.
175 if (! $newTable->hasColumn($oldColumnName)) {
176 $droppedColumns[$oldColumnName] = $oldColumn;
177
178 continue;
179 }
180
181 $newColumn = $newTable->getColumn($oldColumnName);
182
183 if ($this->columnsEqual($oldColumn, $newColumn)) {
184 continue;
185 }
186
187 $modifiedColumns[] = new ColumnDiff($oldColumn, $newColumn);
188 }
189
190 $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns);
191
192 $oldIndexes = $oldTable->getIndexes();
193 $newIndexes = $newTable->getIndexes();
194
195 // See if all the indexes from the old table exist in the new one
196 foreach ($newIndexes as $newIndexName => $newIndex) {
197 if (($newIndex->isPrimary() && $oldTable->getPrimaryKey() !== null) || $oldTable->hasIndex($newIndexName)) {
198 continue;
199 }
200
201 $addedIndexes[$newIndexName] = $newIndex;
202 }
203
204 // See if there are any removed indexes in the new table
205 foreach ($oldIndexes as $oldIndexName => $oldIndex) {
206 // See if the index is removed in the new table.
207 if (
208 ($oldIndex->isPrimary() && $newTable->getPrimaryKey() === null) ||
209 ! $oldIndex->isPrimary() && ! $newTable->hasIndex($oldIndexName)
210 ) {
211 $droppedIndexes[$oldIndexName] = $oldIndex;
212
213 continue;
214 }
215
216 // See if index has changed in the new table.
217 $newIndex = $oldIndex->isPrimary() ? $newTable->getPrimaryKey() : $newTable->getIndex($oldIndexName);
218 assert($newIndex instanceof Index);
219
220 if (! $this->diffIndex($oldIndex, $newIndex)) {
221 continue;
222 }
223
224 $modifiedIndexes[] = $newIndex;
225 }
226
227 $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes);
228
229 $oldForeignKeys = $oldTable->getForeignKeys();
230 $newForeignKeys = $newTable->getForeignKeys();
231
232 foreach ($oldForeignKeys as $oldKey => $oldForeignKey) {
233 foreach ($newForeignKeys as $newKey => $newForeignKey) {
234 if ($this->diffForeignKey($oldForeignKey, $newForeignKey) === false) {
235 unset($oldForeignKeys[$oldKey], $newForeignKeys[$newKey]);
236 } else {
237 if (strtolower($oldForeignKey->getName()) === strtolower($newForeignKey->getName())) {
238 $modifiedForeignKeys[] = $newForeignKey;
239
240 unset($oldForeignKeys[$oldKey], $newForeignKeys[$newKey]);
241 }
242 }
243 }
244 }
245
246 foreach ($oldForeignKeys as $oldForeignKey) {
247 $droppedForeignKeys[] = $oldForeignKey;
248 }
249
250 foreach ($newForeignKeys as $newForeignKey) {
251 $addedForeignKeys[] = $newForeignKey;
252 }
253
254 return new TableDiff(
255 $oldTable,
256 $addedColumns,
257 $modifiedColumns,
258 $droppedColumns,
259 $renamedColumns,
260 $addedIndexes,
261 $modifiedIndexes,
262 $droppedIndexes,
263 $renamedIndexes,
264 $addedForeignKeys,
265 $modifiedForeignKeys,
266 $droppedForeignKeys,
267 );
268 }
269
270 /**
271 * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
272 * however ambiguities between different possibilities should not lead to renaming at all.
273 *
274 * @param array<string,Column> $addedColumns
275 * @param array<string,Column> $removedColumns
276 *
277 * @return array<string,Column>
278 */
279 private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array
280 {
281 $candidatesByName = [];
282
283 foreach ($addedColumns as $addedColumnName => $addedColumn) {
284 foreach ($removedColumns as $removedColumn) {
285 if (! $this->columnsEqual($addedColumn, $removedColumn)) {
286 continue;
287 }
288
289 $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName];
290 }
291 }
292
293 $renamedColumns = [];
294
295 foreach ($candidatesByName as $candidates) {
296 if (count($candidates) !== 1) {
297 continue;
298 }
299
300 [$removedColumn, $addedColumn] = $candidates[0];
301 $removedColumnName = $removedColumn->getName();
302 $addedColumnName = strtolower($addedColumn->getName());
303
304 if (isset($renamedColumns[$removedColumnName])) {
305 continue;
306 }
307
308 $renamedColumns[$removedColumnName] = $addedColumn;
309 unset(
310 $addedColumns[$addedColumnName],
311 $removedColumns[strtolower($removedColumnName)],
312 );
313 }
314
315 return $renamedColumns;
316 }
317
318 /**
319 * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop
320 * however ambiguities between different possibilities should not lead to renaming at all.
321 *
322 * @param array<string,Index> $addedIndexes
323 * @param array<string,Index> $removedIndexes
324 *
325 * @return array<string,Index>
326 */
327 private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array
328 {
329 $candidatesByName = [];
330
331 // Gather possible rename candidates by comparing each added and removed index based on semantics.
332 foreach ($addedIndexes as $addedIndexName => $addedIndex) {
333 foreach ($removedIndexes as $removedIndex) {
334 if ($this->diffIndex($addedIndex, $removedIndex)) {
335 continue;
336 }
337
338 $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName];
339 }
340 }
341
342 $renamedIndexes = [];
343
344 foreach ($candidatesByName as $candidates) {
345 // If the current rename candidate contains exactly one semantically equal index,
346 // we can safely rename it.
347 // Otherwise, it is unclear if a rename action is really intended,
348 // therefore we let those ambiguous indexes be added/dropped.
349 if (count($candidates) !== 1) {
350 continue;
351 }
352
353 [$removedIndex, $addedIndex] = $candidates[0];
354
355 $removedIndexName = strtolower($removedIndex->getName());
356 $addedIndexName = strtolower($addedIndex->getName());
357
358 if (isset($renamedIndexes[$removedIndexName])) {
359 continue;
360 }
361
362 $renamedIndexes[$removedIndexName] = $addedIndex;
363 unset(
364 $addedIndexes[$addedIndexName],
365 $removedIndexes[$removedIndexName],
366 );
367 }
368
369 return $renamedIndexes;
370 }
371
372 protected function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2): bool
373 {
374 if (
375 array_map('strtolower', $key1->getUnquotedLocalColumns())
376 !== array_map('strtolower', $key2->getUnquotedLocalColumns())
377 ) {
378 return true;
379 }
380
381 if (
382 array_map('strtolower', $key1->getUnquotedForeignColumns())
383 !== array_map('strtolower', $key2->getUnquotedForeignColumns())
384 ) {
385 return true;
386 }
387
388 if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) {
389 return true;
390 }
391
392 if ($key1->onUpdate() !== $key2->onUpdate()) {
393 return true;
394 }
395
396 return $key1->onDelete() !== $key2->onDelete();
397 }
398
399 /**
400 * Compares the definitions of the given columns
401 */
402 protected function columnsEqual(Column $column1, Column $column2): bool
403 {
404 return $this->platform->columnsEqual($column1, $column2);
405 }
406
407 /**
408 * Finds the difference between the indexes $index1 and $index2.
409 *
410 * Compares $index1 with $index2 and returns true if there are any
411 * differences or false in case there are no differences.
412 */
413 protected function diffIndex(Index $index1, Index $index2): bool
414 {
415 return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1));
416 }
417}
diff --git a/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php b/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php
new file mode 100644
index 0000000..f2ed089
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php
@@ -0,0 +1,371 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\DB2Platform;
9use Doctrine\DBAL\Result;
10use Doctrine\DBAL\Types\Type;
11use Doctrine\DBAL\Types\Types;
12
13use function array_change_key_case;
14use function implode;
15use function preg_match;
16use function str_replace;
17use function strpos;
18use function strtolower;
19use function strtoupper;
20use function substr;
21
22use const CASE_LOWER;
23
24/**
25 * IBM Db2 Schema Manager.
26 *
27 * @extends AbstractSchemaManager<DB2Platform>
28 */
29class DB2SchemaManager extends AbstractSchemaManager
30{
31 /**
32 * {@inheritDoc}
33 *
34 * @throws Exception
35 */
36 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
37 {
38 $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
39
40 $length = $precision = $default = null;
41 $scale = 0;
42 $fixed = false;
43
44 if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') {
45 $default = $tableColumn['default'];
46
47 if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
48 $default = str_replace("''", "'", $matches[1]);
49 }
50 }
51
52 $type = $this->platform->getDoctrineTypeMapping($tableColumn['typename']);
53
54 switch (strtolower($tableColumn['typename'])) {
55 case 'varchar':
56 if ($tableColumn['codepage'] === 0) {
57 $type = Types::BINARY;
58 }
59
60 $length = $tableColumn['length'];
61 break;
62
63 case 'character':
64 if ($tableColumn['codepage'] === 0) {
65 $type = Types::BINARY;
66 }
67
68 $length = $tableColumn['length'];
69 $fixed = true;
70 break;
71
72 case 'clob':
73 $length = $tableColumn['length'];
74 break;
75
76 case 'decimal':
77 case 'double':
78 case 'real':
79 $scale = $tableColumn['scale'];
80 $precision = $tableColumn['length'];
81 break;
82 }
83
84 $options = [
85 'length' => $length,
86 'unsigned' => false,
87 'fixed' => $fixed,
88 'default' => $default,
89 'autoincrement' => (bool) $tableColumn['autoincrement'],
90 'notnull' => $tableColumn['nulls'] === 'N',
91 'platformOptions' => [],
92 ];
93
94 if (isset($tableColumn['comment'])) {
95 $options['comment'] = $tableColumn['comment'];
96 }
97
98 if ($scale !== null && $precision !== null) {
99 $options['scale'] = $scale;
100 $options['precision'] = $precision;
101 }
102
103 return new Column($tableColumn['colname'], Type::getType($type), $options);
104 }
105
106 /**
107 * {@inheritDoc}
108 */
109 protected function _getPortableTableDefinition(array $table): string
110 {
111 $table = array_change_key_case($table, CASE_LOWER);
112
113 return $table['name'];
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
120 {
121 foreach ($tableIndexes as &$tableIndexRow) {
122 $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER);
123 $tableIndexRow['primary'] = (bool) $tableIndexRow['primary'];
124 }
125
126 return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
127 }
128
129 /**
130 * {@inheritDoc}
131 */
132 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
133 {
134 return new ForeignKeyConstraint(
135 $tableForeignKey['local_columns'],
136 $tableForeignKey['foreign_table'],
137 $tableForeignKey['foreign_columns'],
138 $tableForeignKey['name'],
139 $tableForeignKey['options'],
140 );
141 }
142
143 /**
144 * {@inheritDoc}
145 */
146 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
147 {
148 $foreignKeys = [];
149
150 foreach ($tableForeignKeys as $tableForeignKey) {
151 $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
152
153 if (! isset($foreignKeys[$tableForeignKey['index_name']])) {
154 $foreignKeys[$tableForeignKey['index_name']] = [
155 'local_columns' => [$tableForeignKey['local_column']],
156 'foreign_table' => $tableForeignKey['foreign_table'],
157 'foreign_columns' => [$tableForeignKey['foreign_column']],
158 'name' => $tableForeignKey['index_name'],
159 'options' => [
160 'onUpdate' => $tableForeignKey['on_update'],
161 'onDelete' => $tableForeignKey['on_delete'],
162 ],
163 ];
164 } else {
165 $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column'];
166 $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column'];
167 }
168 }
169
170 return parent::_getPortableTableForeignKeysList($foreignKeys);
171 }
172
173 /**
174 * {@inheritDoc}
175 */
176 protected function _getPortableViewDefinition(array $view): View
177 {
178 $view = array_change_key_case($view, CASE_LOWER);
179
180 $sql = '';
181 $pos = strpos($view['text'], ' AS ');
182
183 if ($pos !== false) {
184 $sql = substr($view['text'], $pos + 4);
185 }
186
187 return new View($view['name'], $sql);
188 }
189
190 protected function normalizeName(string $name): string
191 {
192 $identifier = new Identifier($name);
193
194 return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
195 }
196
197 protected function selectTableNames(string $databaseName): Result
198 {
199 $sql = <<<'SQL'
200SELECT NAME
201FROM SYSIBM.SYSTABLES
202WHERE TYPE = 'T'
203 AND CREATOR = ?
204SQL;
205
206 return $this->connection->executeQuery($sql, [$databaseName]);
207 }
208
209 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
210 {
211 $sql = 'SELECT';
212
213 if ($tableName === null) {
214 $sql .= ' C.TABNAME AS NAME,';
215 }
216
217 $sql .= <<<'SQL'
218 C.COLNAME,
219 C.TYPENAME,
220 C.CODEPAGE,
221 C.NULLS,
222 C.LENGTH,
223 C.SCALE,
224 C.REMARKS AS COMMENT,
225 CASE
226 WHEN C.GENERATED = 'D' THEN 1
227 ELSE 0
228 END AS AUTOINCREMENT,
229 C.DEFAULT
230FROM SYSCAT.COLUMNS C
231 JOIN SYSCAT.TABLES AS T
232 ON T.TABSCHEMA = C.TABSCHEMA
233 AND T.TABNAME = C.TABNAME
234SQL;
235
236 $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"];
237 $params = [$databaseName];
238
239 if ($tableName !== null) {
240 $conditions[] = 'C.TABNAME = ?';
241 $params[] = $tableName;
242 }
243
244 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO';
245
246 return $this->connection->executeQuery($sql, $params);
247 }
248
249 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
250 {
251 $sql = 'SELECT';
252
253 if ($tableName === null) {
254 $sql .= ' IDX.TABNAME AS NAME,';
255 }
256
257 $sql .= <<<'SQL'
258 IDX.INDNAME AS KEY_NAME,
259 IDXCOL.COLNAME AS COLUMN_NAME,
260 CASE
261 WHEN IDX.UNIQUERULE = 'P' THEN 1
262 ELSE 0
263 END AS PRIMARY,
264 CASE
265 WHEN IDX.UNIQUERULE = 'D' THEN 1
266 ELSE 0
267 END AS NON_UNIQUE
268 FROM SYSCAT.INDEXES AS IDX
269 JOIN SYSCAT.TABLES AS T
270 ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME
271 JOIN SYSCAT.INDEXCOLUSE AS IDXCOL
272 ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME
273SQL;
274
275 $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"];
276 $params = [$databaseName];
277
278 if ($tableName !== null) {
279 $conditions[] = 'IDX.TABNAME = ?';
280 $params[] = $tableName;
281 }
282
283 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ';
284
285 return $this->connection->executeQuery($sql, $params);
286 }
287
288 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
289 {
290 $sql = 'SELECT';
291
292 if ($tableName === null) {
293 $sql .= ' R.TABNAME AS NAME,';
294 }
295
296 $sql .= <<<'SQL'
297 FKCOL.COLNAME AS LOCAL_COLUMN,
298 R.REFTABNAME AS FOREIGN_TABLE,
299 PKCOL.COLNAME AS FOREIGN_COLUMN,
300 R.CONSTNAME AS INDEX_NAME,
301 CASE
302 WHEN R.UPDATERULE = 'R' THEN 'RESTRICT'
303 END AS ON_UPDATE,
304 CASE
305 WHEN R.DELETERULE = 'C' THEN 'CASCADE'
306 WHEN R.DELETERULE = 'N' THEN 'SET NULL'
307 WHEN R.DELETERULE = 'R' THEN 'RESTRICT'
308 END AS ON_DELETE
309 FROM SYSCAT.REFERENCES AS R
310 JOIN SYSCAT.TABLES AS T
311 ON T.TABSCHEMA = R.TABSCHEMA
312 AND T.TABNAME = R.TABNAME
313 JOIN SYSCAT.KEYCOLUSE AS FKCOL
314 ON FKCOL.CONSTNAME = R.CONSTNAME
315 AND FKCOL.TABSCHEMA = R.TABSCHEMA
316 AND FKCOL.TABNAME = R.TABNAME
317 JOIN SYSCAT.KEYCOLUSE AS PKCOL
318 ON PKCOL.CONSTNAME = R.REFKEYNAME
319 AND PKCOL.TABSCHEMA = R.REFTABSCHEMA
320 AND PKCOL.TABNAME = R.REFTABNAME
321 AND PKCOL.COLSEQ = FKCOL.COLSEQ
322SQL;
323
324 $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"];
325 $params = [$databaseName];
326
327 if ($tableName !== null) {
328 $conditions[] = 'R.TABNAME = ?';
329 $params[] = $tableName;
330 }
331
332 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ';
333
334 return $this->connection->executeQuery($sql, $params);
335 }
336
337 /**
338 * {@inheritDoc}
339 */
340 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
341 {
342 $sql = 'SELECT NAME, REMARKS';
343
344 $conditions = [];
345 $params = [];
346
347 if ($tableName !== null) {
348 $conditions[] = 'NAME = ?';
349 $params[] = $tableName;
350 }
351
352 $sql .= ' FROM SYSIBM.SYSTABLES';
353
354 if ($conditions !== []) {
355 $sql .= ' WHERE ' . implode(' AND ', $conditions);
356 }
357
358 /** @var array<string,array<string,mixed>> $metadata */
359 $metadata = $this->connection->executeQuery($sql, $params)
360 ->fetchAllAssociativeIndexed();
361
362 $tableOptions = [];
363 foreach ($metadata as $table => $data) {
364 $data = array_change_key_case($data, CASE_LOWER);
365
366 $tableOptions[$table] = ['comment' => $data['remarks']];
367 }
368
369 return $tableOptions;
370 }
371}
diff --git a/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php
new file mode 100644
index 0000000..efba87f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php
@@ -0,0 +1,20 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9
10/**
11 * A schema manager factory that returns the default schema manager for the given platform.
12 */
13final class DefaultSchemaManagerFactory implements SchemaManagerFactory
14{
15 /** @throws Exception If the platform does not support creating schema managers yet. */
16 public function createSchemaManager(Connection $connection): AbstractSchemaManager
17 {
18 return $connection->getDatabasePlatform()->createSchemaManager($connection);
19 }
20}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php
new file mode 100644
index 0000000..53daac9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class ColumnAlreadyExists extends LogicException implements SchemaException
14{
15 public static function new(string $tableName, string $columnName): self
16 {
17 return new self(sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php
new file mode 100644
index 0000000..cb1cedf
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class ColumnDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $columnName, string $table): self
16 {
17 return new self(sprintf('There is no column with name "%s" on table "%s".', $columnName, $table));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php
new file mode 100644
index 0000000..3e4c6c6
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class ForeignKeyDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $foreignKeyName, string $table): self
16 {
17 return new self(
18 sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table),
19 );
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php
new file mode 100644
index 0000000..e8592be
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class IndexAlreadyExists extends LogicException implements SchemaException
14{
15 public static function new(string $indexName, string $table): self
16 {
17 return new self(
18 sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table),
19 );
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php
new file mode 100644
index 0000000..ee25fd8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class IndexDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $indexName, string $table): self
16 {
17 return new self(sprintf('Index "%s" does not exist on table "%s".', $indexName, $table));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php
new file mode 100644
index 0000000..d295eb8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use InvalidArgumentException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class IndexNameInvalid extends InvalidArgumentException implements SchemaException
14{
15 public static function new(string $indexName): self
16 {
17 return new self(sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php b/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php
new file mode 100644
index 0000000..3b5d89d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use InvalidArgumentException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class InvalidTableName extends InvalidArgumentException implements SchemaException
14{
15 public static function new(string $tableName): self
16 {
17 return new self(sprintf('Invalid table name specified "%s".', $tableName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php
new file mode 100644
index 0000000..4109af5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class NamespaceAlreadyExists extends LogicException implements SchemaException
14{
15 public static function new(string $namespaceName): self
16 {
17 return new self(sprintf('The namespace with name "%s" already exists.', $namespaceName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php
new file mode 100644
index 0000000..d374f27
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class SequenceAlreadyExists extends LogicException implements SchemaException
14{
15 public static function new(string $sequenceName): self
16 {
17 return new self(sprintf('The sequence "%s" already exists.', $sequenceName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php
new file mode 100644
index 0000000..fa98cee
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class SequenceDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $sequenceName): self
16 {
17 return new self(sprintf('There exists no sequence with the name "%s".', $sequenceName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php
new file mode 100644
index 0000000..b978dbc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class TableAlreadyExists extends LogicException implements SchemaException
14{
15 public static function new(string $tableName): self
16 {
17 return new self(sprintf('The table with name "%s" already exists.', $tableName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php
new file mode 100644
index 0000000..8c66a11
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php
@@ -0,0 +1,19 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class TableDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $tableName): self
16 {
17 return new self(sprintf('There is no table with name "%s" in the schema.', $tableName));
18 }
19}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php
new file mode 100644
index 0000000..3ae5aaa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use LogicException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class UniqueConstraintDoesNotExist extends LogicException implements SchemaException
14{
15 public static function new(string $constraintName, string $table): self
16 {
17 return new self(
18 sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table),
19 );
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php b/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php
new file mode 100644
index 0000000..a97ee76
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema\Exception;
6
7use Doctrine\DBAL\Schema\SchemaException;
8use InvalidArgumentException;
9
10use function sprintf;
11
12/** @psalm-immutable */
13final class UnknownColumnOption extends InvalidArgumentException implements SchemaException
14{
15 public static function new(string $name): self
16 {
17 return new self(
18 sprintf('The "%s" column option is not supported.', $name),
19 );
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php b/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php
new file mode 100644
index 0000000..bb5ef7f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php
@@ -0,0 +1,291 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function array_keys;
10use function array_map;
11use function strrpos;
12use function strtolower;
13use function strtoupper;
14use function substr;
15
16/**
17 * An abstraction class for a foreign key constraint.
18 */
19class ForeignKeyConstraint extends AbstractAsset
20{
21 /**
22 * Asset identifier instances of the referencing table column names the foreign key constraint is associated with.
23 *
24 * @var array<string, Identifier>
25 */
26 protected array $_localColumnNames;
27
28 /**
29 * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with.
30 */
31 protected Identifier $_foreignTableName;
32
33 /**
34 * Asset identifier instances of the referenced table column names the foreign key constraint is associated with.
35 *
36 * @var array<string, Identifier>
37 */
38 protected array $_foreignColumnNames;
39
40 /**
41 * Initializes the foreign key constraint.
42 *
43 * @param array<int, string> $localColumnNames Names of the referencing table columns.
44 * @param string $foreignTableName Referenced table.
45 * @param array<int, string> $foreignColumnNames Names of the referenced table columns.
46 * @param string $name Name of the foreign key constraint.
47 * @param array<string, mixed> $options Options associated with the foreign key constraint.
48 */
49 public function __construct(
50 array $localColumnNames,
51 string $foreignTableName,
52 array $foreignColumnNames,
53 string $name = '',
54 protected array $options = [],
55 ) {
56 $this->_setName($name);
57
58 $this->_localColumnNames = $this->createIdentifierMap($localColumnNames);
59 $this->_foreignTableName = new Identifier($foreignTableName);
60
61 $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames);
62 }
63
64 /**
65 * @param array<int, string> $names
66 *
67 * @return array<string, Identifier>
68 */
69 private function createIdentifierMap(array $names): array
70 {
71 $identifiers = [];
72
73 foreach ($names as $name) {
74 $identifiers[$name] = new Identifier($name);
75 }
76
77 return $identifiers;
78 }
79
80 /**
81 * Returns the names of the referencing table columns
82 * the foreign key constraint is associated with.
83 *
84 * @return array<int, string>
85 */
86 public function getLocalColumns(): array
87 {
88 return array_keys($this->_localColumnNames);
89 }
90
91 /**
92 * Returns the quoted representation of the referencing table column names
93 * the foreign key constraint is associated with.
94 *
95 * But only if they were defined with one or the referencing table column name
96 * is a keyword reserved by the platform.
97 * Otherwise the plain unquoted value as inserted is returned.
98 *
99 * @param AbstractPlatform $platform The platform to use for quotation.
100 *
101 * @return array<int, string>
102 */
103 public function getQuotedLocalColumns(AbstractPlatform $platform): array
104 {
105 $columns = [];
106
107 foreach ($this->_localColumnNames as $column) {
108 $columns[] = $column->getQuotedName($platform);
109 }
110
111 return $columns;
112 }
113
114 /**
115 * Returns unquoted representation of local table column names for comparison with other FK
116 *
117 * @return array<int, string>
118 */
119 public function getUnquotedLocalColumns(): array
120 {
121 return array_map($this->trimQuotes(...), $this->getLocalColumns());
122 }
123
124 /**
125 * Returns unquoted representation of foreign table column names for comparison with other FK
126 *
127 * @return array<int, string>
128 */
129 public function getUnquotedForeignColumns(): array
130 {
131 return array_map($this->trimQuotes(...), $this->getForeignColumns());
132 }
133
134 /**
135 * Returns the name of the referenced table
136 * the foreign key constraint is associated with.
137 */
138 public function getForeignTableName(): string
139 {
140 return $this->_foreignTableName->getName();
141 }
142
143 /**
144 * Returns the non-schema qualified foreign table name.
145 */
146 public function getUnqualifiedForeignTableName(): string
147 {
148 $name = $this->_foreignTableName->getName();
149 $position = strrpos($name, '.');
150
151 if ($position !== false) {
152 $name = substr($name, $position + 1);
153 }
154
155 return strtolower($name);
156 }
157
158 /**
159 * Returns the quoted representation of the referenced table name
160 * the foreign key constraint is associated with.
161 *
162 * But only if it was defined with one or the referenced table name
163 * is a keyword reserved by the platform.
164 * Otherwise the plain unquoted value as inserted is returned.
165 *
166 * @param AbstractPlatform $platform The platform to use for quotation.
167 */
168 public function getQuotedForeignTableName(AbstractPlatform $platform): string
169 {
170 return $this->_foreignTableName->getQuotedName($platform);
171 }
172
173 /**
174 * Returns the names of the referenced table columns
175 * the foreign key constraint is associated with.
176 *
177 * @return array<int, string>
178 */
179 public function getForeignColumns(): array
180 {
181 return array_keys($this->_foreignColumnNames);
182 }
183
184 /**
185 * Returns the quoted representation of the referenced table column names
186 * the foreign key constraint is associated with.
187 *
188 * But only if they were defined with one or the referenced table column name
189 * is a keyword reserved by the platform.
190 * Otherwise the plain unquoted value as inserted is returned.
191 *
192 * @param AbstractPlatform $platform The platform to use for quotation.
193 *
194 * @return array<int, string>
195 */
196 public function getQuotedForeignColumns(AbstractPlatform $platform): array
197 {
198 $columns = [];
199
200 foreach ($this->_foreignColumnNames as $column) {
201 $columns[] = $column->getQuotedName($platform);
202 }
203
204 return $columns;
205 }
206
207 /**
208 * Returns whether or not a given option
209 * is associated with the foreign key constraint.
210 */
211 public function hasOption(string $name): bool
212 {
213 return isset($this->options[$name]);
214 }
215
216 /**
217 * Returns an option associated with the foreign key constraint.
218 */
219 public function getOption(string $name): mixed
220 {
221 return $this->options[$name];
222 }
223
224 /**
225 * Returns the options associated with the foreign key constraint.
226 *
227 * @return array<string, mixed>
228 */
229 public function getOptions(): array
230 {
231 return $this->options;
232 }
233
234 /**
235 * Returns the referential action for UPDATE operations
236 * on the referenced table the foreign key constraint is associated with.
237 */
238 public function onUpdate(): ?string
239 {
240 return $this->onEvent('onUpdate');
241 }
242
243 /**
244 * Returns the referential action for DELETE operations
245 * on the referenced table the foreign key constraint is associated with.
246 */
247 public function onDelete(): ?string
248 {
249 return $this->onEvent('onDelete');
250 }
251
252 /**
253 * Returns the referential action for a given database operation
254 * on the referenced table the foreign key constraint is associated with.
255 *
256 * @param string $event Name of the database operation/event to return the referential action for.
257 */
258 private function onEvent(string $event): ?string
259 {
260 if (isset($this->options[$event])) {
261 $onEvent = strtoupper($this->options[$event]);
262
263 if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') {
264 return $onEvent;
265 }
266 }
267
268 return null;
269 }
270
271 /**
272 * Checks whether this foreign key constraint intersects the given index columns.
273 *
274 * Returns `true` if at least one of this foreign key's local columns
275 * matches one of the given index's columns, `false` otherwise.
276 *
277 * @param Index $index The index to be checked against.
278 */
279 public function intersectsIndexColumns(Index $index): bool
280 {
281 foreach ($index->getColumns() as $indexColumn) {
282 foreach ($this->_localColumnNames as $localColumn) {
283 if (strtolower($indexColumn) === strtolower($localColumn->getName())) {
284 return true;
285 }
286 }
287 }
288
289 return false;
290 }
291}
diff --git a/vendor/doctrine/dbal/src/Schema/Identifier.php b/vendor/doctrine/dbal/src/Schema/Identifier.php
new file mode 100644
index 0000000..c3c84a7
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Identifier.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7/**
8 * An abstraction class for an asset identifier.
9 *
10 * Wraps identifier names like column names in indexes / foreign keys
11 * in an abstract class for proper quotation capabilities.
12 */
13class Identifier extends AbstractAsset
14{
15 /**
16 * @param string $identifier Identifier name to wrap.
17 * @param bool $quote Whether to force quoting the given identifier.
18 */
19 public function __construct(string $identifier, bool $quote = false)
20 {
21 $this->_setName($identifier);
22
23 if (! $quote || $this->_quoted) {
24 return;
25 }
26
27 $this->_setName('"' . $this->getName() . '"');
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Schema/Index.php b/vendor/doctrine/dbal/src/Schema/Index.php
new file mode 100644
index 0000000..1755654
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Index.php
@@ -0,0 +1,314 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function array_filter;
10use function array_keys;
11use function array_map;
12use function array_search;
13use function array_shift;
14use function count;
15use function strtolower;
16
17class Index extends AbstractAsset
18{
19 /**
20 * Asset identifier instances of the column names the index is associated with.
21 *
22 * @var array<string, Identifier>
23 */
24 protected array $_columns = [];
25
26 protected bool $_isUnique = false;
27
28 protected bool $_isPrimary = false;
29
30 /**
31 * Platform specific flags for indexes.
32 *
33 * @var array<string, true>
34 */
35 protected array $_flags = [];
36
37 /**
38 * @param array<int, string> $columns
39 * @param array<int, string> $flags
40 * @param array<string, mixed> $options
41 */
42 public function __construct(
43 ?string $name,
44 array $columns,
45 bool $isUnique = false,
46 bool $isPrimary = false,
47 array $flags = [],
48 private readonly array $options = [],
49 ) {
50 $isUnique = $isUnique || $isPrimary;
51
52 if ($name !== null) {
53 $this->_setName($name);
54 }
55
56 $this->_isUnique = $isUnique;
57 $this->_isPrimary = $isPrimary;
58
59 foreach ($columns as $column) {
60 $this->_addColumn($column);
61 }
62
63 foreach ($flags as $flag) {
64 $this->addFlag($flag);
65 }
66 }
67
68 protected function _addColumn(string $column): void
69 {
70 $this->_columns[$column] = new Identifier($column);
71 }
72
73 /**
74 * Returns the names of the referencing table columns the constraint is associated with.
75 *
76 * @return list<string>
77 */
78 public function getColumns(): array
79 {
80 return array_keys($this->_columns);
81 }
82
83 /**
84 * Returns the quoted representation of the column names the constraint is associated with.
85 *
86 * But only if they were defined with one or a column name
87 * is a keyword reserved by the platform.
88 * Otherwise, the plain unquoted value as inserted is returned.
89 *
90 * @param AbstractPlatform $platform The platform to use for quotation.
91 *
92 * @return list<string>
93 */
94 public function getQuotedColumns(AbstractPlatform $platform): array
95 {
96 $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths')
97 ? $this->getOption('lengths') : [];
98
99 $columns = [];
100
101 foreach ($this->_columns as $column) {
102 $length = array_shift($subParts);
103
104 $quotedColumn = $column->getQuotedName($platform);
105
106 if ($length !== null) {
107 $quotedColumn .= '(' . $length . ')';
108 }
109
110 $columns[] = $quotedColumn;
111 }
112
113 return $columns;
114 }
115
116 /** @return array<int, string> */
117 public function getUnquotedColumns(): array
118 {
119 return array_map($this->trimQuotes(...), $this->getColumns());
120 }
121
122 /**
123 * Is the index neither unique nor primary key?
124 */
125 public function isSimpleIndex(): bool
126 {
127 return ! $this->_isPrimary && ! $this->_isUnique;
128 }
129
130 public function isUnique(): bool
131 {
132 return $this->_isUnique;
133 }
134
135 public function isPrimary(): bool
136 {
137 return $this->_isPrimary;
138 }
139
140 public function hasColumnAtPosition(string $name, int $pos = 0): bool
141 {
142 $name = $this->trimQuotes(strtolower($name));
143 $indexColumns = array_map('strtolower', $this->getUnquotedColumns());
144
145 return array_search($name, $indexColumns, true) === $pos;
146 }
147
148 /**
149 * Checks if this index exactly spans the given column names in the correct order.
150 *
151 * @param array<int, string> $columnNames
152 */
153 public function spansColumns(array $columnNames): bool
154 {
155 $columns = $this->getColumns();
156 $numberOfColumns = count($columns);
157 $sameColumns = true;
158
159 for ($i = 0; $i < $numberOfColumns; $i++) {
160 if (
161 isset($columnNames[$i])
162 && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i]))
163 ) {
164 continue;
165 }
166
167 $sameColumns = false;
168 }
169
170 return $sameColumns;
171 }
172
173 /**
174 * Checks if the other index already fulfills all the indexing and constraint needs of the current one.
175 */
176 public function isFulfilledBy(Index $other): bool
177 {
178 // allow the other index to be equally large only. It being larger is an option
179 // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo)
180 if (count($other->getColumns()) !== count($this->getColumns())) {
181 return false;
182 }
183
184 // Check if columns are the same, and even in the same order
185 $sameColumns = $this->spansColumns($other->getColumns());
186
187 if ($sameColumns) {
188 if (! $this->samePartialIndex($other)) {
189 return false;
190 }
191
192 if (! $this->hasSameColumnLengths($other)) {
193 return false;
194 }
195
196 if (! $this->isUnique() && ! $this->isPrimary()) {
197 // this is a special case: If the current key is neither primary or unique, any unique or
198 // primary key will always have the same effect for the index and there cannot be any constraint
199 // overlaps. This means a primary or unique index can always fulfill the requirements of just an
200 // index that has no constraints.
201 return true;
202 }
203
204 if ($other->isPrimary() !== $this->isPrimary()) {
205 return false;
206 }
207
208 return $other->isUnique() === $this->isUnique();
209 }
210
211 return false;
212 }
213
214 /**
215 * Detects if the other index is a non-unique, non primary index that can be overwritten by this one.
216 */
217 public function overrules(Index $other): bool
218 {
219 if ($other->isPrimary()) {
220 return false;
221 }
222
223 if ($this->isSimpleIndex() && $other->isUnique()) {
224 return false;
225 }
226
227 return $this->spansColumns($other->getColumns())
228 && ($this->isPrimary() || $this->isUnique())
229 && $this->samePartialIndex($other);
230 }
231
232 /**
233 * Returns platform specific flags for indexes.
234 *
235 * @return array<int, string>
236 */
237 public function getFlags(): array
238 {
239 return array_keys($this->_flags);
240 }
241
242 /**
243 * Adds Flag for an index that translates to platform specific handling.
244 *
245 * @example $index->addFlag('CLUSTERED')
246 */
247 public function addFlag(string $flag): self
248 {
249 $this->_flags[strtolower($flag)] = true;
250
251 return $this;
252 }
253
254 /**
255 * Does this index have a specific flag?
256 */
257 public function hasFlag(string $flag): bool
258 {
259 return isset($this->_flags[strtolower($flag)]);
260 }
261
262 /**
263 * Removes a flag.
264 */
265 public function removeFlag(string $flag): void
266 {
267 unset($this->_flags[strtolower($flag)]);
268 }
269
270 public function hasOption(string $name): bool
271 {
272 return isset($this->options[strtolower($name)]);
273 }
274
275 public function getOption(string $name): mixed
276 {
277 return $this->options[strtolower($name)];
278 }
279
280 /** @return array<string, mixed> */
281 public function getOptions(): array
282 {
283 return $this->options;
284 }
285
286 /**
287 * Return whether the two indexes have the same partial index
288 */
289 private function samePartialIndex(Index $other): bool
290 {
291 if (
292 $this->hasOption('where')
293 && $other->hasOption('where')
294 && $this->getOption('where') === $other->getOption('where')
295 ) {
296 return true;
297 }
298
299 return ! $this->hasOption('where') && ! $other->hasOption('where');
300 }
301
302 /**
303 * Returns whether the index has the same column lengths as the other
304 */
305 private function hasSameColumnLengths(self $other): bool
306 {
307 $filter = static function (?int $length): bool {
308 return $length !== null;
309 };
310
311 return array_filter($this->options['lengths'] ?? [], $filter)
312 === array_filter($other->options['lengths'] ?? [], $filter);
313 }
314}
diff --git a/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php
new file mode 100644
index 0000000..249be13
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php
@@ -0,0 +1,548 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
9use Doctrine\DBAL\Platforms\MariaDBPlatform;
10use Doctrine\DBAL\Platforms\MySQL;
11use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\CachingCharsetMetadataProvider;
12use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\ConnectionCharsetMetadataProvider;
13use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider;
14use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider;
15use Doctrine\DBAL\Platforms\MySQL\DefaultTableOptions;
16use Doctrine\DBAL\Result;
17use Doctrine\DBAL\Types\Type;
18
19use function array_change_key_case;
20use function assert;
21use function explode;
22use function implode;
23use function is_string;
24use function preg_match;
25use function str_contains;
26use function strtok;
27use function strtolower;
28use function strtr;
29
30use const CASE_LOWER;
31
32/**
33 * Schema manager for the MySQL RDBMS.
34 *
35 * @extends AbstractSchemaManager<AbstractMySQLPlatform>
36 */
37class MySQLSchemaManager extends AbstractSchemaManager
38{
39 /** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */
40 private const MARIADB_ESCAPE_SEQUENCES = [
41 '\\0' => "\0",
42 "\\'" => "'",
43 '\\"' => '"',
44 '\\b' => "\b",
45 '\\n' => "\n",
46 '\\r' => "\r",
47 '\\t' => "\t",
48 '\\Z' => "\x1a",
49 '\\\\' => '\\',
50 '\\%' => '%',
51 '\\_' => '_',
52
53 // Internally, MariaDB escapes single quotes using the standard syntax
54 "''" => "'",
55 ];
56
57 private ?DefaultTableOptions $defaultTableOptions = null;
58
59 /**
60 * {@inheritDoc}
61 */
62 protected function _getPortableTableDefinition(array $table): string
63 {
64 return $table['TABLE_NAME'];
65 }
66
67 /**
68 * {@inheritDoc}
69 */
70 protected function _getPortableViewDefinition(array $view): View
71 {
72 return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']);
73 }
74
75 /**
76 * {@inheritDoc}
77 */
78 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
79 {
80 foreach ($tableIndexes as $k => $v) {
81 $v = array_change_key_case($v, CASE_LOWER);
82 if ($v['key_name'] === 'PRIMARY') {
83 $v['primary'] = true;
84 } else {
85 $v['primary'] = false;
86 }
87
88 if (str_contains($v['index_type'], 'FULLTEXT')) {
89 $v['flags'] = ['FULLTEXT'];
90 } elseif (str_contains($v['index_type'], 'SPATIAL')) {
91 $v['flags'] = ['SPATIAL'];
92 }
93
94 // Ignore prohibited prefix `length` for spatial index
95 if (! str_contains($v['index_type'], 'SPATIAL')) {
96 $v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null;
97 }
98
99 $tableIndexes[$k] = $v;
100 }
101
102 return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
103 }
104
105 /**
106 * {@inheritDoc}
107 */
108 protected function _getPortableDatabaseDefinition(array $database): string
109 {
110 return $database['Database'];
111 }
112
113 /**
114 * {@inheritDoc}
115 */
116 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
117 {
118 $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
119
120 $dbType = strtolower($tableColumn['type']);
121 $dbType = strtok($dbType, '(), ');
122 assert(is_string($dbType));
123
124 $length = $tableColumn['length'] ?? strtok('(), ');
125
126 $fixed = false;
127
128 if (! isset($tableColumn['name'])) {
129 $tableColumn['name'] = '';
130 }
131
132 $scale = 0;
133 $precision = null;
134
135 $type = $this->platform->getDoctrineTypeMapping($dbType);
136
137 switch ($dbType) {
138 case 'char':
139 case 'binary':
140 $fixed = true;
141 break;
142
143 case 'float':
144 case 'double':
145 case 'real':
146 case 'numeric':
147 case 'decimal':
148 if (
149 preg_match(
150 '([A-Za-z]+\(([0-9]+),([0-9]+)\))',
151 $tableColumn['type'],
152 $match,
153 ) === 1
154 ) {
155 $precision = (int) $match[1];
156 $scale = (int) $match[2];
157 $length = null;
158 }
159
160 break;
161
162 case 'tinytext':
163 $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT;
164 break;
165
166 case 'text':
167 $length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT;
168 break;
169
170 case 'mediumtext':
171 $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
172 break;
173
174 case 'tinyblob':
175 $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB;
176 break;
177
178 case 'blob':
179 $length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB;
180 break;
181
182 case 'mediumblob':
183 $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
184 break;
185
186 case 'tinyint':
187 case 'smallint':
188 case 'mediumint':
189 case 'int':
190 case 'integer':
191 case 'bigint':
192 case 'year':
193 $length = null;
194 break;
195 }
196
197 if ($this->platform instanceof MariaDBPlatform) {
198 $columnDefault = $this->getMariaDBColumnDefault($this->platform, $tableColumn['default']);
199 } else {
200 $columnDefault = $tableColumn['default'];
201 }
202
203 $options = [
204 'length' => $length !== null ? (int) $length : null,
205 'unsigned' => str_contains($tableColumn['type'], 'unsigned'),
206 'fixed' => $fixed,
207 'default' => $columnDefault,
208 'notnull' => $tableColumn['null'] !== 'YES',
209 'scale' => $scale,
210 'precision' => $precision,
211 'autoincrement' => str_contains($tableColumn['extra'], 'auto_increment'),
212 ];
213
214 if (isset($tableColumn['comment'])) {
215 $options['comment'] = $tableColumn['comment'];
216 }
217
218 $column = new Column($tableColumn['field'], Type::getType($type), $options);
219
220 if (isset($tableColumn['characterset'])) {
221 $column->setPlatformOption('charset', $tableColumn['characterset']);
222 }
223
224 if (isset($tableColumn['collation'])) {
225 $column->setPlatformOption('collation', $tableColumn['collation']);
226 }
227
228 return $column;
229 }
230
231 /**
232 * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
233 *
234 * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
235 * to distinguish them from expressions (see MDEV-10134).
236 * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
237 * as current_timestamp(), currdate(), currtime()
238 * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
239 * null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
240 * - \' is always stored as '' in information_schema (normalized)
241 *
242 * @link https://mariadb.com/kb/en/library/information-schema-columns-table/
243 * @link https://jira.mariadb.org/browse/MDEV-13132
244 *
245 * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
246 */
247 private function getMariaDBColumnDefault(MariaDBPlatform $platform, ?string $columnDefault): ?string
248 {
249 if ($columnDefault === 'NULL' || $columnDefault === null) {
250 return null;
251 }
252
253 if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
254 return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
255 }
256
257 return match ($columnDefault) {
258 'current_timestamp()' => $platform->getCurrentTimestampSQL(),
259 'curdate()' => $platform->getCurrentDateSQL(),
260 'curtime()' => $platform->getCurrentTimeSQL(),
261 default => $columnDefault,
262 };
263 }
264
265 /**
266 * {@inheritDoc}
267 */
268 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
269 {
270 $list = [];
271 foreach ($tableForeignKeys as $value) {
272 $value = array_change_key_case($value, CASE_LOWER);
273 if (! isset($list[$value['constraint_name']])) {
274 if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') {
275 $value['delete_rule'] = null;
276 }
277
278 if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') {
279 $value['update_rule'] = null;
280 }
281
282 $list[$value['constraint_name']] = [
283 'name' => $value['constraint_name'],
284 'local' => [],
285 'foreign' => [],
286 'foreignTable' => $value['referenced_table_name'],
287 'onDelete' => $value['delete_rule'],
288 'onUpdate' => $value['update_rule'],
289 ];
290 }
291
292 $list[$value['constraint_name']]['local'][] = $value['column_name'];
293 $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name'];
294 }
295
296 return parent::_getPortableTableForeignKeysList($list);
297 }
298
299 /**
300 * {@inheritDoc}
301 */
302 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
303 {
304 return new ForeignKeyConstraint(
305 $tableForeignKey['local'],
306 $tableForeignKey['foreignTable'],
307 $tableForeignKey['foreign'],
308 $tableForeignKey['name'],
309 [
310 'onDelete' => $tableForeignKey['onDelete'],
311 'onUpdate' => $tableForeignKey['onUpdate'],
312 ],
313 );
314 }
315
316 /** @throws Exception */
317 public function createComparator(): Comparator
318 {
319 return new MySQL\Comparator(
320 $this->platform,
321 new CachingCharsetMetadataProvider(
322 new ConnectionCharsetMetadataProvider($this->connection),
323 ),
324 new CachingCollationMetadataProvider(
325 new ConnectionCollationMetadataProvider($this->connection),
326 ),
327 $this->getDefaultTableOptions(),
328 );
329 }
330
331 protected function selectTableNames(string $databaseName): Result
332 {
333 $sql = <<<'SQL'
334SELECT TABLE_NAME
335FROM information_schema.TABLES
336WHERE TABLE_SCHEMA = ?
337 AND TABLE_TYPE = 'BASE TABLE'
338ORDER BY TABLE_NAME
339SQL;
340
341 return $this->connection->executeQuery($sql, [$databaseName]);
342 }
343
344 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
345 {
346 $columnTypeSQL = $this->platform->getColumnTypeSQLSnippet('c', $databaseName);
347
348 $sql = 'SELECT';
349
350 if ($tableName === null) {
351 $sql .= ' c.TABLE_NAME,';
352 }
353
354 $sql .= <<<SQL
355 c.COLUMN_NAME AS field,
356 $columnTypeSQL AS type,
357 c.IS_NULLABLE AS `null`,
358 c.COLUMN_KEY AS `key`,
359 c.COLUMN_DEFAULT AS `default`,
360 c.EXTRA,
361 c.COLUMN_COMMENT AS comment,
362 c.CHARACTER_SET_NAME AS characterset,
363 c.COLLATION_NAME AS collation
364FROM information_schema.COLUMNS c
365 INNER JOIN information_schema.TABLES t
366 ON t.TABLE_NAME = c.TABLE_NAME
367SQL;
368
369 // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition
370 // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
371 // caused by https://bugs.mysql.com/bug.php?id=81347
372 $conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?', "t.TABLE_TYPE = 'BASE TABLE'"];
373 $params = [$databaseName, $databaseName];
374
375 if ($tableName !== null) {
376 $conditions[] = 't.TABLE_NAME = ?';
377 $params[] = $tableName;
378 }
379
380 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION';
381
382 return $this->connection->executeQuery($sql, $params);
383 }
384
385 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
386 {
387 $sql = 'SELECT';
388
389 if ($tableName === null) {
390 $sql .= ' TABLE_NAME,';
391 }
392
393 $sql .= <<<'SQL'
394 NON_UNIQUE AS Non_Unique,
395 INDEX_NAME AS Key_name,
396 COLUMN_NAME AS Column_Name,
397 SUB_PART AS Sub_Part,
398 INDEX_TYPE AS Index_Type
399FROM information_schema.STATISTICS
400SQL;
401
402 $conditions = ['TABLE_SCHEMA = ?'];
403 $params = [$databaseName];
404
405 if ($tableName !== null) {
406 $conditions[] = 'TABLE_NAME = ?';
407 $params[] = $tableName;
408 }
409
410 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX';
411
412 return $this->connection->executeQuery($sql, $params);
413 }
414
415 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
416 {
417 $sql = 'SELECT DISTINCT';
418
419 if ($tableName === null) {
420 $sql .= ' k.TABLE_NAME,';
421 }
422
423 $sql .= <<<'SQL'
424 k.CONSTRAINT_NAME,
425 k.COLUMN_NAME,
426 k.REFERENCED_TABLE_NAME,
427 k.REFERENCED_COLUMN_NAME,
428 k.ORDINAL_POSITION,
429 c.UPDATE_RULE,
430 c.DELETE_RULE
431FROM information_schema.key_column_usage k
432INNER JOIN information_schema.referential_constraints c
433ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME
434AND c.TABLE_NAME = k.TABLE_NAME
435SQL;
436
437 $conditions = ['k.TABLE_SCHEMA = ?'];
438 $params = [$databaseName];
439
440 if ($tableName !== null) {
441 $conditions[] = 'k.TABLE_NAME = ?';
442 $params[] = $tableName;
443 }
444
445 // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition
446 // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
447 // caused by https://bugs.mysql.com/bug.php?id=81347
448 $conditions[] = 'c.CONSTRAINT_SCHEMA = ?';
449 $params[] = $databaseName;
450
451 $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL';
452
453 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY k.ORDINAL_POSITION';
454
455 return $this->connection->executeQuery($sql, $params);
456 }
457
458 /**
459 * {@inheritDoc}
460 */
461 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
462 {
463 // MariaDB-10.10.1 added FULL_COLLATION_NAME to the information_schema.COLLATION_CHARACTER_SET_APPLICABILITY.
464 // A base collation like uca1400_ai_ci can refer to multiple character sets. The value in the
465 // information_schema.TABLES.TABLE_COLLATION corresponds to the full collation name.
466 // The MariaDB executable comment syntax with version, /*M!101001, is exclusively executed on
467 // MariaDB-10.10.1+ servers for backwards compatibility, and compatiblity to MySQL servers.
468 $sql = <<<'SQL'
469 SELECT t.TABLE_NAME,
470 t.ENGINE,
471 t.AUTO_INCREMENT,
472 t.TABLE_COMMENT,
473 t.CREATE_OPTIONS,
474 t.TABLE_COLLATION,
475 ccsa.CHARACTER_SET_NAME
476 FROM information_schema.TABLES t
477 INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa
478 ON /*M!101001 ccsa.FULL_COLLATION_NAME = t.TABLE_COLLATION OR */
479 ccsa.COLLATION_NAME = t.TABLE_COLLATION
480SQL;
481
482 $conditions = ['t.TABLE_SCHEMA = ?'];
483 $params = [$databaseName];
484
485 if ($tableName !== null) {
486 $conditions[] = 't.TABLE_NAME = ?';
487 $params[] = $tableName;
488 }
489
490 $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'";
491
492 $sql .= ' WHERE ' . implode(' AND ', $conditions);
493
494 /** @var array<string,array<string,mixed>> $metadata */
495 $metadata = $this->connection->executeQuery($sql, $params)
496 ->fetchAllAssociativeIndexed();
497
498 $tableOptions = [];
499 foreach ($metadata as $table => $data) {
500 $data = array_change_key_case($data, CASE_LOWER);
501
502 $tableOptions[$table] = [
503 'engine' => $data['engine'],
504 'collation' => $data['table_collation'],
505 'charset' => $data['character_set_name'],
506 'autoincrement' => $data['auto_increment'],
507 'comment' => $data['table_comment'],
508 'create_options' => $this->parseCreateOptions($data['create_options']),
509 ];
510 }
511
512 return $tableOptions;
513 }
514
515 /** @return array<string, string>|array<string, true> */
516 private function parseCreateOptions(?string $string): array
517 {
518 $options = [];
519
520 if ($string === null || $string === '') {
521 return $options;
522 }
523
524 foreach (explode(' ', $string) as $pair) {
525 $parts = explode('=', $pair, 2);
526
527 $options[$parts[0]] = $parts[1] ?? true;
528 }
529
530 return $options;
531 }
532
533 /** @throws Exception */
534 private function getDefaultTableOptions(): DefaultTableOptions
535 {
536 if ($this->defaultTableOptions === null) {
537 $row = $this->connection->fetchNumeric(
538 'SELECT @@character_set_database, @@collation_database',
539 );
540
541 assert($row !== false);
542
543 $this->defaultTableOptions = new DefaultTableOptions(...$row);
544 }
545
546 return $this->defaultTableOptions;
547 }
548}
diff --git a/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php
new file mode 100644
index 0000000..f973eaa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php
@@ -0,0 +1,475 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
9use Doctrine\DBAL\Platforms\OraclePlatform;
10use Doctrine\DBAL\Result;
11use Doctrine\DBAL\Types\Type;
12
13use function array_change_key_case;
14use function array_key_exists;
15use function array_values;
16use function assert;
17use function implode;
18use function is_string;
19use function preg_match;
20use function str_contains;
21use function str_replace;
22use function str_starts_with;
23use function strtolower;
24use function strtoupper;
25use function trim;
26
27use const CASE_LOWER;
28
29/**
30 * Oracle Schema Manager.
31 *
32 * @extends AbstractSchemaManager<OraclePlatform>
33 */
34class OracleSchemaManager extends AbstractSchemaManager
35{
36 /**
37 * {@inheritDoc}
38 */
39 protected function _getPortableViewDefinition(array $view): View
40 {
41 $view = array_change_key_case($view, CASE_LOWER);
42
43 return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
44 }
45
46 /**
47 * {@inheritDoc}
48 */
49 protected function _getPortableTableDefinition(array $table): string
50 {
51 $table = array_change_key_case($table, CASE_LOWER);
52
53 return $this->getQuotedIdentifierName($table['table_name']);
54 }
55
56 /**
57 * {@inheritDoc}
58 *
59 * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
60 */
61 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
62 {
63 $indexBuffer = [];
64 foreach ($tableIndexes as $tableIndex) {
65 $tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
66
67 $keyName = strtolower($tableIndex['name']);
68 $buffer = [];
69
70 if ($tableIndex['is_primary'] === 'P') {
71 $keyName = 'primary';
72 $buffer['primary'] = true;
73 $buffer['non_unique'] = false;
74 } else {
75 $buffer['primary'] = false;
76 $buffer['non_unique'] = ! $tableIndex['is_unique'];
77 }
78
79 $buffer['key_name'] = $keyName;
80 $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
81 $indexBuffer[] = $buffer;
82 }
83
84 return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
85 }
86
87 /**
88 * {@inheritDoc}
89 */
90 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
91 {
92 $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
93
94 $dbType = strtolower($tableColumn['data_type']);
95 if (str_starts_with($dbType, 'timestamp(')) {
96 if (str_contains($dbType, 'with time zone')) {
97 $dbType = 'timestamptz';
98 } else {
99 $dbType = 'timestamp';
100 }
101 }
102
103 $length = $precision = null;
104 $scale = 0;
105 $fixed = false;
106
107 if (! isset($tableColumn['column_name'])) {
108 $tableColumn['column_name'] = '';
109 }
110
111 assert(array_key_exists('data_default', $tableColumn));
112
113 // Default values returned from database sometimes have trailing spaces.
114 if (is_string($tableColumn['data_default'])) {
115 $tableColumn['data_default'] = trim($tableColumn['data_default']);
116 }
117
118 if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
119 $tableColumn['data_default'] = null;
120 }
121
122 if ($tableColumn['data_default'] !== null) {
123 // Default values returned from database are represented as literal expressions
124 if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) {
125 $tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
126 }
127 }
128
129 if ($tableColumn['data_precision'] !== null) {
130 $precision = (int) $tableColumn['data_precision'];
131 }
132
133 if ($tableColumn['data_scale'] !== null) {
134 $scale = (int) $tableColumn['data_scale'];
135 }
136
137 $type = $this->platform->getDoctrineTypeMapping($dbType);
138
139 switch ($dbType) {
140 case 'number':
141 if ($precision === 20 && $scale === 0) {
142 $type = 'bigint';
143 } elseif ($precision === 5 && $scale === 0) {
144 $type = 'smallint';
145 } elseif ($precision === 1 && $scale === 0) {
146 $type = 'boolean';
147 } elseif ($scale > 0) {
148 $type = 'decimal';
149 }
150
151 break;
152
153 case 'varchar':
154 case 'varchar2':
155 case 'nvarchar2':
156 $length = (int) $tableColumn['char_length'];
157 break;
158
159 case 'raw':
160 $length = (int) $tableColumn['data_length'];
161 $fixed = true;
162 break;
163
164 case 'char':
165 case 'nchar':
166 $length = (int) $tableColumn['char_length'];
167 $fixed = true;
168 break;
169 }
170
171 $options = [
172 'notnull' => $tableColumn['nullable'] === 'N',
173 'fixed' => $fixed,
174 'default' => $tableColumn['data_default'],
175 'length' => $length,
176 'precision' => $precision,
177 'scale' => $scale,
178 ];
179
180 if (isset($tableColumn['comments'])) {
181 $options['comment'] = $tableColumn['comments'];
182 }
183
184 return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
191 {
192 $list = [];
193 foreach ($tableForeignKeys as $value) {
194 $value = array_change_key_case($value, CASE_LOWER);
195 if (! isset($list[$value['constraint_name']])) {
196 if ($value['delete_rule'] === 'NO ACTION') {
197 $value['delete_rule'] = null;
198 }
199
200 $list[$value['constraint_name']] = [
201 'name' => $this->getQuotedIdentifierName($value['constraint_name']),
202 'local' => [],
203 'foreign' => [],
204 'foreignTable' => $value['references_table'],
205 'onDelete' => $value['delete_rule'],
206 ];
207 }
208
209 $localColumn = $this->getQuotedIdentifierName($value['local_column']);
210 $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
211
212 $list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
213 $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
214 }
215
216 return parent::_getPortableTableForeignKeysList($list);
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
223 {
224 return new ForeignKeyConstraint(
225 array_values($tableForeignKey['local']),
226 $this->getQuotedIdentifierName($tableForeignKey['foreignTable']),
227 array_values($tableForeignKey['foreign']),
228 $this->getQuotedIdentifierName($tableForeignKey['name']),
229 ['onDelete' => $tableForeignKey['onDelete']],
230 );
231 }
232
233 /**
234 * {@inheritDoc}
235 */
236 protected function _getPortableSequenceDefinition(array $sequence): Sequence
237 {
238 $sequence = array_change_key_case($sequence, CASE_LOWER);
239
240 return new Sequence(
241 $this->getQuotedIdentifierName($sequence['sequence_name']),
242 (int) $sequence['increment_by'],
243 (int) $sequence['min_value'],
244 );
245 }
246
247 /**
248 * {@inheritDoc}
249 */
250 protected function _getPortableDatabaseDefinition(array $database): string
251 {
252 $database = array_change_key_case($database, CASE_LOWER);
253
254 return $database['username'];
255 }
256
257 public function createDatabase(string $database): void
258 {
259 $statement = $this->platform->getCreateDatabaseSQL($database);
260
261 $params = $this->connection->getParams();
262
263 if (isset($params['password'])) {
264 $statement .= ' IDENTIFIED BY ' . $params['password'];
265 }
266
267 $this->connection->executeStatement($statement);
268
269 $statement = 'GRANT DBA TO ' . $database;
270 $this->connection->executeStatement($statement);
271 }
272
273 /** @throws Exception */
274 protected function dropAutoincrement(string $table): bool
275 {
276 $sql = $this->platform->getDropAutoincrementSql($table);
277 foreach ($sql as $query) {
278 $this->connection->executeStatement($query);
279 }
280
281 return true;
282 }
283
284 public function dropTable(string $name): void
285 {
286 try {
287 $this->dropAutoincrement($name);
288 } catch (DatabaseObjectNotFoundException) {
289 }
290
291 parent::dropTable($name);
292 }
293
294 /**
295 * Returns the quoted representation of the given identifier name.
296 *
297 * Quotes non-uppercase identifiers explicitly to preserve case
298 * and thus make references to the particular identifier work.
299 */
300 private function getQuotedIdentifierName(string $identifier): string
301 {
302 if (preg_match('/[a-z]/', $identifier) === 1) {
303 return $this->platform->quoteIdentifier($identifier);
304 }
305
306 return $identifier;
307 }
308
309 protected function selectTableNames(string $databaseName): Result
310 {
311 $sql = <<<'SQL'
312SELECT TABLE_NAME
313FROM ALL_TABLES
314WHERE OWNER = :OWNER
315ORDER BY TABLE_NAME
316SQL;
317
318 return $this->connection->executeQuery($sql, ['OWNER' => $databaseName]);
319 }
320
321 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
322 {
323 $sql = 'SELECT';
324
325 if ($tableName === null) {
326 $sql .= ' C.TABLE_NAME,';
327 }
328
329 $sql .= <<<'SQL'
330 C.COLUMN_NAME,
331 C.DATA_TYPE,
332 C.DATA_DEFAULT,
333 C.DATA_PRECISION,
334 C.DATA_SCALE,
335 C.CHAR_LENGTH,
336 C.DATA_LENGTH,
337 C.NULLABLE,
338 D.COMMENTS
339 FROM ALL_TAB_COLUMNS C
340 INNER JOIN ALL_TABLES T
341 ON T.OWNER = C.OWNER
342 AND T.TABLE_NAME = C.TABLE_NAME
343 LEFT JOIN ALL_COL_COMMENTS D
344 ON D.OWNER = C.OWNER
345 AND D.TABLE_NAME = C.TABLE_NAME
346 AND D.COLUMN_NAME = C.COLUMN_NAME
347SQL;
348
349 $conditions = ['C.OWNER = :OWNER'];
350 $params = ['OWNER' => $databaseName];
351
352 if ($tableName !== null) {
353 $conditions[] = 'C.TABLE_NAME = :TABLE_NAME';
354 $params['TABLE_NAME'] = $tableName;
355 }
356
357 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID';
358
359 return $this->connection->executeQuery($sql, $params);
360 }
361
362 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
363 {
364 $sql = 'SELECT';
365
366 if ($tableName === null) {
367 $sql .= ' IND_COL.TABLE_NAME,';
368 }
369
370 $sql .= <<<'SQL'
371 IND_COL.INDEX_NAME AS NAME,
372 IND.INDEX_TYPE AS TYPE,
373 DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE,
374 IND_COL.COLUMN_NAME,
375 IND_COL.COLUMN_POSITION AS COLUMN_POS,
376 CON.CONSTRAINT_TYPE AS IS_PRIMARY
377 FROM ALL_IND_COLUMNS IND_COL
378 LEFT JOIN ALL_INDEXES IND
379 ON IND.OWNER = IND_COL.INDEX_OWNER
380 AND IND.INDEX_NAME = IND_COL.INDEX_NAME
381 LEFT JOIN ALL_CONSTRAINTS CON
382 ON CON.OWNER = IND_COL.INDEX_OWNER
383 AND CON.INDEX_NAME = IND_COL.INDEX_NAME
384SQL;
385
386 $conditions = ['IND_COL.INDEX_OWNER = :OWNER'];
387 $params = ['OWNER' => $databaseName];
388
389 if ($tableName !== null) {
390 $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME';
391 $params['TABLE_NAME'] = $tableName;
392 }
393
394 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME'
395 . ', IND_COL.COLUMN_POSITION';
396
397 return $this->connection->executeQuery($sql, $params);
398 }
399
400 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
401 {
402 $sql = 'SELECT';
403
404 if ($tableName === null) {
405 $sql .= ' COLS.TABLE_NAME,';
406 }
407
408 $sql .= <<<'SQL'
409 ALC.CONSTRAINT_NAME,
410 ALC.DELETE_RULE,
411 COLS.COLUMN_NAME LOCAL_COLUMN,
412 COLS.POSITION,
413 R_COLS.TABLE_NAME REFERENCES_TABLE,
414 R_COLS.COLUMN_NAME FOREIGN_COLUMN
415 FROM ALL_CONS_COLUMNS COLS
416 LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME
417 LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND
418 R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND
419 R_COLS.POSITION = COLS.POSITION
420SQL;
421
422 $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER'];
423 $params = ['OWNER' => $databaseName];
424
425 if ($tableName !== null) {
426 $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME';
427 $params['TABLE_NAME'] = $tableName;
428 }
429
430 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME'
431 . ', COLS.POSITION';
432
433 return $this->connection->executeQuery($sql, $params);
434 }
435
436 /**
437 * {@inheritDoc}
438 */
439 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
440 {
441 $sql = 'SELECT TABLE_NAME, COMMENTS';
442
443 $conditions = ['OWNER = :OWNER'];
444 $params = ['OWNER' => $databaseName];
445
446 if ($tableName !== null) {
447 $conditions[] = 'TABLE_NAME = :TABLE_NAME';
448 $params['TABLE_NAME'] = $tableName;
449 }
450
451 $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions);
452
453 /** @var array<string,array<string,mixed>> $metadata */
454 $metadata = $this->connection->executeQuery($sql, $params)
455 ->fetchAllAssociativeIndexed();
456
457 $tableOptions = [];
458 foreach ($metadata as $table => $data) {
459 $data = array_change_key_case($data, CASE_LOWER);
460
461 $tableOptions[$table] = [
462 'comment' => $data['comments'],
463 ];
464 }
465
466 return $tableOptions;
467 }
468
469 protected function normalizeName(string $name): string
470 {
471 $identifier = new Identifier($name);
472
473 return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
474 }
475}
diff --git a/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php
new file mode 100644
index 0000000..9af16c9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php
@@ -0,0 +1,572 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
9use Doctrine\DBAL\Result;
10use Doctrine\DBAL\Types\JsonType;
11use Doctrine\DBAL\Types\Type;
12
13use function array_change_key_case;
14use function array_key_exists;
15use function array_map;
16use function array_merge;
17use function assert;
18use function explode;
19use function implode;
20use function in_array;
21use function is_string;
22use function preg_match;
23use function sprintf;
24use function str_contains;
25use function str_replace;
26use function strtolower;
27use function trim;
28
29use const CASE_LOWER;
30
31/**
32 * PostgreSQL Schema Manager.
33 *
34 * @extends AbstractSchemaManager<PostgreSQLPlatform>
35 */
36class PostgreSQLSchemaManager extends AbstractSchemaManager
37{
38 private ?string $currentSchema = null;
39
40 /**
41 * {@inheritDoc}
42 */
43 public function listSchemaNames(): array
44 {
45 return $this->connection->fetchFirstColumn(
46 <<<'SQL'
47SELECT schema_name
48FROM information_schema.schemata
49WHERE schema_name NOT LIKE 'pg\_%'
50AND schema_name != 'information_schema'
51SQL,
52 );
53 }
54
55 public function createSchemaConfig(): SchemaConfig
56 {
57 $config = parent::createSchemaConfig();
58
59 $config->setName($this->getCurrentSchema());
60
61 return $config;
62 }
63
64 /**
65 * Returns the name of the current schema.
66 *
67 * @throws Exception
68 */
69 protected function getCurrentSchema(): ?string
70 {
71 return $this->currentSchema ??= $this->determineCurrentSchema();
72 }
73
74 /**
75 * Determines the name of the current schema.
76 *
77 * @throws Exception
78 */
79 protected function determineCurrentSchema(): string
80 {
81 $currentSchema = $this->connection->fetchOne('SELECT current_schema()');
82 assert(is_string($currentSchema));
83
84 return $currentSchema;
85 }
86
87 /**
88 * {@inheritDoc}
89 */
90 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
91 {
92 $onUpdate = null;
93 $onDelete = null;
94
95 if (
96 preg_match(
97 '(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
98 $tableForeignKey['condef'],
99 $match,
100 ) === 1
101 ) {
102 $onUpdate = $match[1];
103 }
104
105 if (
106 preg_match(
107 '(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
108 $tableForeignKey['condef'],
109 $match,
110 ) === 1
111 ) {
112 $onDelete = $match[1];
113 }
114
115 $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values);
116 assert($result === 1);
117
118 // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get
119 // the idea to trim them here.
120 $localColumns = array_map('trim', explode(',', $values[1]));
121 $foreignColumns = array_map('trim', explode(',', $values[3]));
122 $foreignTable = $values[2];
123
124 return new ForeignKeyConstraint(
125 $localColumns,
126 $foreignTable,
127 $foreignColumns,
128 $tableForeignKey['conname'],
129 ['onUpdate' => $onUpdate, 'onDelete' => $onDelete],
130 );
131 }
132
133 /**
134 * {@inheritDoc}
135 */
136 protected function _getPortableViewDefinition(array $view): View
137 {
138 return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']);
139 }
140
141 /**
142 * {@inheritDoc}
143 */
144 protected function _getPortableTableDefinition(array $table): string
145 {
146 $currentSchema = $this->getCurrentSchema();
147
148 if ($table['schema_name'] === $currentSchema) {
149 return $table['table_name'];
150 }
151
152 return $table['schema_name'] . '.' . $table['table_name'];
153 }
154
155 /**
156 * {@inheritDoc}
157 *
158 * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
159 */
160 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
161 {
162 $buffer = [];
163 foreach ($tableIndexes as $row) {
164 $colNumbers = array_map('intval', explode(' ', $row['indkey']));
165 $columnNameSql = sprintf(
166 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
167 $row['indrelid'],
168 implode(' ,', $colNumbers),
169 );
170
171 $indexColumns = $this->connection->fetchAllAssociative($columnNameSql);
172
173 // required for getting the order of the columns right.
174 foreach ($colNumbers as $colNum) {
175 foreach ($indexColumns as $colRow) {
176 if ($colNum !== $colRow['attnum']) {
177 continue;
178 }
179
180 $buffer[] = [
181 'key_name' => $row['relname'],
182 'column_name' => trim($colRow['attname']),
183 'non_unique' => ! $row['indisunique'],
184 'primary' => $row['indisprimary'],
185 'where' => $row['where'],
186 ];
187 }
188 }
189 }
190
191 return parent::_getPortableTableIndexesList($buffer, $tableName);
192 }
193
194 /**
195 * {@inheritDoc}
196 */
197 protected function _getPortableDatabaseDefinition(array $database): string
198 {
199 return $database['datname'];
200 }
201
202 /**
203 * {@inheritDoc}
204 */
205 protected function _getPortableSequenceDefinition(array $sequence): Sequence
206 {
207 if ($sequence['schemaname'] !== 'public') {
208 $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
209 } else {
210 $sequenceName = $sequence['relname'];
211 }
212
213 return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']);
214 }
215
216 /**
217 * {@inheritDoc}
218 */
219 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
220 {
221 $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
222
223 $length = null;
224
225 if (
226 in_array(strtolower($tableColumn['type']), ['varchar', 'bpchar'], true)
227 && preg_match('/\((\d*)\)/', $tableColumn['complete_type'], $matches) === 1
228 ) {
229 $length = (int) $matches[1];
230 }
231
232 $autoincrement = $tableColumn['attidentity'] === 'd';
233
234 $matches = [];
235
236 assert(array_key_exists('default', $tableColumn));
237 assert(array_key_exists('complete_type', $tableColumn));
238
239 if ($tableColumn['default'] !== null) {
240 if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) {
241 $tableColumn['default'] = $matches[1];
242 } elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) {
243 $tableColumn['default'] = null;
244 }
245 }
246
247 if ($length === -1 && isset($tableColumn['atttypmod'])) {
248 $length = $tableColumn['atttypmod'] - 4;
249 }
250
251 if ((int) $length <= 0) {
252 $length = null;
253 }
254
255 $fixed = false;
256
257 if (! isset($tableColumn['name'])) {
258 $tableColumn['name'] = '';
259 }
260
261 $precision = null;
262 $scale = 0;
263 $jsonb = null;
264
265 $dbType = strtolower($tableColumn['type']);
266 if (
267 $tableColumn['domain_type'] !== null
268 && $tableColumn['domain_type'] !== ''
269 && ! $this->platform->hasDoctrineTypeMappingFor($tableColumn['type'])
270 ) {
271 $dbType = strtolower($tableColumn['domain_type']);
272 $tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
273 }
274
275 $type = $this->platform->getDoctrineTypeMapping($dbType);
276
277 switch ($dbType) {
278 case 'smallint':
279 case 'int2':
280 case 'int':
281 case 'int4':
282 case 'integer':
283 case 'bigint':
284 case 'int8':
285 $length = null;
286 break;
287
288 case 'bool':
289 case 'boolean':
290 if ($tableColumn['default'] === 'true') {
291 $tableColumn['default'] = true;
292 }
293
294 if ($tableColumn['default'] === 'false') {
295 $tableColumn['default'] = false;
296 }
297
298 $length = null;
299 break;
300
301 case 'json':
302 case 'text':
303 case '_varchar':
304 case 'varchar':
305 $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']);
306 break;
307
308 case 'char':
309 case 'bpchar':
310 $fixed = true;
311 break;
312
313 case 'float':
314 case 'float4':
315 case 'float8':
316 case 'double':
317 case 'double precision':
318 case 'real':
319 case 'decimal':
320 case 'money':
321 case 'numeric':
322 if (
323 preg_match(
324 '([A-Za-z]+\(([0-9]+),([0-9]+)\))',
325 $tableColumn['complete_type'],
326 $match,
327 ) === 1
328 ) {
329 $precision = (int) $match[1];
330 $scale = (int) $match[2];
331 $length = null;
332 }
333
334 break;
335
336 case 'year':
337 $length = null;
338 break;
339
340 // PostgreSQL 9.4+ only
341 case 'jsonb':
342 $jsonb = true;
343 break;
344 }
345
346 if (
347 is_string($tableColumn['default']) && preg_match(
348 "('([^']+)'::)",
349 $tableColumn['default'],
350 $match,
351 ) === 1
352 ) {
353 $tableColumn['default'] = $match[1];
354 }
355
356 $options = [
357 'length' => $length,
358 'notnull' => (bool) $tableColumn['isnotnull'],
359 'default' => $tableColumn['default'],
360 'precision' => $precision,
361 'scale' => $scale,
362 'fixed' => $fixed,
363 'autoincrement' => $autoincrement,
364 ];
365
366 if (isset($tableColumn['comment'])) {
367 $options['comment'] = $tableColumn['comment'];
368 }
369
370 $column = new Column($tableColumn['field'], Type::getType($type), $options);
371
372 if (! empty($tableColumn['collation'])) {
373 $column->setPlatformOption('collation', $tableColumn['collation']);
374 }
375
376 if ($column->getType() instanceof JsonType) {
377 $column->setPlatformOption('jsonb', $jsonb);
378 }
379
380 return $column;
381 }
382
383 /**
384 * Parses a default value expression as given by PostgreSQL
385 */
386 private function parseDefaultExpression(?string $default): ?string
387 {
388 if ($default === null) {
389 return $default;
390 }
391
392 return str_replace("''", "'", $default);
393 }
394
395 protected function selectTableNames(string $databaseName): Result
396 {
397 $sql = <<<'SQL'
398SELECT quote_ident(table_name) AS table_name,
399 table_schema AS schema_name
400FROM information_schema.tables
401WHERE table_catalog = ?
402 AND table_schema NOT LIKE 'pg\_%'
403 AND table_schema != 'information_schema'
404 AND table_name != 'geometry_columns'
405 AND table_name != 'spatial_ref_sys'
406 AND table_type = 'BASE TABLE'
407SQL;
408
409 return $this->connection->executeQuery($sql, [$databaseName]);
410 }
411
412 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
413 {
414 $sql = 'SELECT';
415
416 if ($tableName === null) {
417 $sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
418 }
419
420 $sql .= <<<'SQL'
421 a.attnum,
422 quote_ident(a.attname) AS field,
423 t.typname AS type,
424 format_type(a.atttypid, a.atttypmod) AS complete_type,
425 (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
426 (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
427 (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
428 pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
429 a.attnotnull AS isnotnull,
430 a.attidentity,
431 (SELECT 't'
432 FROM pg_index
433 WHERE c.oid = pg_index.indrelid
434 AND pg_index.indkey[0] = a.attnum
435 AND pg_index.indisprimary = 't'
436 ) AS pri,
437 (SELECT pg_get_expr(adbin, adrelid)
438 FROM pg_attrdef
439 WHERE c.oid = pg_attrdef.adrelid
440 AND pg_attrdef.adnum=a.attnum
441 ) AS default,
442 (SELECT pg_description.description
443 FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
444 ) AS comment
445 FROM pg_attribute a
446 INNER JOIN pg_class c
447 ON c.oid = a.attrelid
448 INNER JOIN pg_type t
449 ON t.oid = a.atttypid
450 INNER JOIN pg_namespace n
451 ON n.oid = c.relnamespace
452 LEFT JOIN pg_depend d
453 ON d.objid = c.oid
454 AND d.deptype = 'e'
455 AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
456SQL;
457
458 $conditions = array_merge([
459 'a.attnum > 0',
460 "c.relkind = 'r'",
461 'd.refobjid IS NULL',
462 ], $this->buildQueryConditions($tableName));
463
464 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum';
465
466 return $this->connection->executeQuery($sql);
467 }
468
469 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
470 {
471 $sql = 'SELECT';
472
473 if ($tableName === null) {
474 $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
475 }
476
477 $sql .= <<<'SQL'
478 quote_ident(ic.relname) AS relname,
479 i.indisunique,
480 i.indisprimary,
481 i.indkey,
482 i.indrelid,
483 pg_get_expr(indpred, indrelid) AS "where"
484 FROM pg_index i
485 JOIN pg_class AS tc ON tc.oid = i.indrelid
486 JOIN pg_namespace tn ON tn.oid = tc.relnamespace
487 JOIN pg_class AS ic ON ic.oid = i.indexrelid
488 WHERE ic.oid IN (
489 SELECT indexrelid
490 FROM pg_index i, pg_class c, pg_namespace n
491SQL;
492
493 $conditions = array_merge([
494 'c.oid = i.indrelid',
495 'c.relnamespace = n.oid',
496 ], $this->buildQueryConditions($tableName));
497
498 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')';
499
500 return $this->connection->executeQuery($sql);
501 }
502
503 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
504 {
505 $sql = 'SELECT';
506
507 if ($tableName === null) {
508 $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
509 }
510
511 $sql .= <<<'SQL'
512 quote_ident(r.conname) as conname,
513 pg_get_constraintdef(r.oid, true) as condef
514 FROM pg_constraint r
515 JOIN pg_class AS tc ON tc.oid = r.conrelid
516 JOIN pg_namespace tn ON tn.oid = tc.relnamespace
517 WHERE r.conrelid IN
518 (
519 SELECT c.oid
520 FROM pg_class c, pg_namespace n
521SQL;
522
523 $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName));
524
525 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'";
526
527 return $this->connection->executeQuery($sql);
528 }
529
530 /**
531 * {@inheritDoc}
532 */
533 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
534 {
535 $sql = <<<'SQL'
536SELECT c.relname,
537 CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged,
538 obj_description(c.oid, 'pg_class') AS comment
539FROM pg_class c
540 INNER JOIN pg_namespace n
541 ON n.oid = c.relnamespace
542SQL;
543
544 $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName));
545
546 $sql .= ' WHERE ' . implode(' AND ', $conditions);
547
548 return $this->connection->fetchAllAssociativeIndexed($sql);
549 }
550
551 /** @return list<string> */
552 private function buildQueryConditions(?string $tableName): array
553 {
554 $conditions = [];
555
556 if ($tableName !== null) {
557 if (str_contains($tableName, '.')) {
558 [$schemaName, $tableName] = explode('.', $tableName);
559 $conditions[] = 'n.nspname = ' . $this->platform->quoteStringLiteral($schemaName);
560 } else {
561 $conditions[] = 'n.nspname = ANY(current_schemas(false))';
562 }
563
564 $identifier = new Identifier($tableName);
565 $conditions[] = 'c.relname = ' . $this->platform->quoteStringLiteral($identifier->getName());
566 }
567
568 $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')";
569
570 return $conditions;
571 }
572}
diff --git a/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php
new file mode 100644
index 0000000..e0a74ce
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php
@@ -0,0 +1,498 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\SQLServer;
9use Doctrine\DBAL\Platforms\SQLServerPlatform;
10use Doctrine\DBAL\Result;
11use Doctrine\DBAL\Types\Type;
12
13use function array_change_key_case;
14use function assert;
15use function explode;
16use function implode;
17use function is_string;
18use function preg_match;
19use function sprintf;
20use function str_contains;
21use function str_replace;
22use function strtok;
23
24use const CASE_LOWER;
25
26/**
27 * SQL Server Schema Manager.
28 *
29 * @extends AbstractSchemaManager<SQLServerPlatform>
30 */
31class SQLServerSchemaManager extends AbstractSchemaManager
32{
33 private ?string $databaseCollation = null;
34
35 /**
36 * {@inheritDoc}
37 */
38 public function listSchemaNames(): array
39 {
40 return $this->connection->fetchFirstColumn(
41 <<<'SQL'
42SELECT name
43FROM sys.schemas
44WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')
45SQL,
46 );
47 }
48
49 /**
50 * {@inheritDoc}
51 */
52 protected function _getPortableSequenceDefinition(array $sequence): Sequence
53 {
54 return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']);
55 }
56
57 /**
58 * {@inheritDoc}
59 */
60 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
61 {
62 $dbType = strtok($tableColumn['type'], '(), ');
63 assert(is_string($dbType));
64
65 $length = (int) $tableColumn['length'];
66
67 $precision = null;
68
69 $scale = 0;
70 $fixed = false;
71
72 if (! isset($tableColumn['name'])) {
73 $tableColumn['name'] = '';
74 }
75
76 if ($tableColumn['scale'] !== null) {
77 $scale = (int) $tableColumn['scale'];
78 }
79
80 if ($tableColumn['precision'] !== null) {
81 $precision = (int) $tableColumn['precision'];
82 }
83
84 switch ($dbType) {
85 case 'nchar':
86 case 'ntext':
87 // Unicode data requires 2 bytes per character
88 $length /= 2;
89 break;
90
91 case 'nvarchar':
92 if ($length === -1) {
93 break;
94 }
95
96 // Unicode data requires 2 bytes per character
97 $length /= 2;
98 break;
99
100 case 'varchar':
101 // TEXT type is returned as VARCHAR(MAX) with a length of -1
102 if ($length === -1) {
103 $dbType = 'text';
104 }
105
106 break;
107
108 case 'varbinary':
109 if ($length === -1) {
110 $dbType = 'blob';
111 }
112
113 break;
114 }
115
116 if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') {
117 $fixed = true;
118 }
119
120 $type = $this->platform->getDoctrineTypeMapping($dbType);
121
122 $options = [
123 'fixed' => $fixed,
124 'notnull' => (bool) $tableColumn['notnull'],
125 'scale' => $scale,
126 'precision' => $precision,
127 'autoincrement' => (bool) $tableColumn['autoincrement'],
128 ];
129
130 if (isset($tableColumn['comment'])) {
131 $options['comment'] = $tableColumn['comment'];
132 }
133
134 if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) {
135 $options['length'] = $length;
136 }
137
138 $column = new Column($tableColumn['name'], Type::getType($type), $options);
139
140 if ($tableColumn['default'] !== null) {
141 $default = $this->parseDefaultExpression($tableColumn['default']);
142
143 $column->setDefault($default);
144 $column->setPlatformOption(
145 SQLServerPlatform::OPTION_DEFAULT_CONSTRAINT_NAME,
146 $tableColumn['df_name'],
147 );
148 }
149
150 if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') {
151 $column->setPlatformOption('collation', $tableColumn['collation']);
152 }
153
154 return $column;
155 }
156
157 private function parseDefaultExpression(string $value): ?string
158 {
159 while (preg_match('/^\((.*)\)$/s', $value, $matches)) {
160 $value = $matches[1];
161 }
162
163 if ($value === 'NULL') {
164 return null;
165 }
166
167 if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) {
168 $value = str_replace("''", "'", $matches[1]);
169 }
170
171 if ($value === 'getdate()') {
172 return $this->platform->getCurrentTimestampSQL();
173 }
174
175 return $value;
176 }
177
178 /**
179 * {@inheritDoc}
180 */
181 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
182 {
183 $foreignKeys = [];
184
185 foreach ($tableForeignKeys as $tableForeignKey) {
186 $name = $tableForeignKey['ForeignKey'];
187
188 if (! isset($foreignKeys[$name])) {
189 $foreignKeys[$name] = [
190 'local_columns' => [$tableForeignKey['ColumnName']],
191 'foreign_table' => $tableForeignKey['ReferenceTableName'],
192 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']],
193 'name' => $name,
194 'options' => [
195 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']),
196 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']),
197 ],
198 ];
199 } else {
200 $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName'];
201 $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName'];
202 }
203 }
204
205 return parent::_getPortableTableForeignKeysList($foreignKeys);
206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
212 {
213 foreach ($tableIndexes as &$tableIndex) {
214 $tableIndex['non_unique'] = (bool) $tableIndex['non_unique'];
215 $tableIndex['primary'] = (bool) $tableIndex['primary'];
216 $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null;
217 }
218
219 return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
220 }
221
222 /**
223 * {@inheritDoc}
224 */
225 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
226 {
227 return new ForeignKeyConstraint(
228 $tableForeignKey['local_columns'],
229 $tableForeignKey['foreign_table'],
230 $tableForeignKey['foreign_columns'],
231 $tableForeignKey['name'],
232 $tableForeignKey['options'],
233 );
234 }
235
236 /**
237 * {@inheritDoc}
238 */
239 protected function _getPortableTableDefinition(array $table): string
240 {
241 if ($table['schema_name'] !== 'dbo') {
242 return $table['schema_name'] . '.' . $table['table_name'];
243 }
244
245 return $table['table_name'];
246 }
247
248 /**
249 * {@inheritDoc}
250 */
251 protected function _getPortableDatabaseDefinition(array $database): string
252 {
253 return $database['name'];
254 }
255
256 /**
257 * {@inheritDoc}
258 */
259 protected function _getPortableViewDefinition(array $view): View
260 {
261 return new View($view['name'], $view['definition']);
262 }
263
264 /** @throws Exception */
265 public function createComparator(): Comparator
266 {
267 return new SQLServer\Comparator($this->platform, $this->getDatabaseCollation());
268 }
269
270 /** @throws Exception */
271 private function getDatabaseCollation(): string
272 {
273 if ($this->databaseCollation === null) {
274 $databaseCollation = $this->connection->fetchOne(
275 'SELECT collation_name FROM sys.databases WHERE name = '
276 . $this->platform->getCurrentDatabaseExpression(),
277 );
278
279 // a database is always selected, even if omitted in the connection parameters
280 assert(is_string($databaseCollation));
281
282 $this->databaseCollation = $databaseCollation;
283 }
284
285 return $this->databaseCollation;
286 }
287
288 protected function selectTableNames(string $databaseName): Result
289 {
290 // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
291 $sql = <<<'SQL'
292SELECT name AS table_name,
293 SCHEMA_NAME(schema_id) AS schema_name
294FROM sys.objects
295WHERE type = 'U'
296 AND name != 'sysdiagrams'
297ORDER BY name
298SQL;
299
300 return $this->connection->executeQuery($sql);
301 }
302
303 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
304 {
305 $sql = 'SELECT';
306
307 if ($tableName === null) {
308 $sql .= ' obj.name AS table_name, scm.name AS schema_name,';
309 }
310
311 $sql .= <<<'SQL'
312 col.name,
313 type.name AS type,
314 col.max_length AS length,
315 ~col.is_nullable AS notnull,
316 def.definition AS [default],
317 def.name AS df_name,
318 col.scale,
319 col.precision,
320 col.is_identity AS autoincrement,
321 col.collation_name AS collation,
322 -- CAST avoids driver error for sql_variant type
323 CAST(prop.value AS NVARCHAR(MAX)) AS comment
324 FROM sys.columns AS col
325 JOIN sys.types AS type
326 ON col.user_type_id = type.user_type_id
327 JOIN sys.objects AS obj
328 ON col.object_id = obj.object_id
329 JOIN sys.schemas AS scm
330 ON obj.schema_id = scm.schema_id
331 LEFT JOIN sys.default_constraints def
332 ON col.default_object_id = def.object_id
333 AND col.object_id = def.parent_object_id
334 LEFT JOIN sys.extended_properties AS prop
335 ON obj.object_id = prop.major_id
336 AND col.column_id = prop.minor_id
337 AND prop.name = 'MS_Description'
338SQL;
339
340 // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
341 $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"];
342 $params = [];
343
344 if ($tableName !== null) {
345 $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name');
346 }
347
348 $sql .= ' WHERE ' . implode(' AND ', $conditions);
349
350 return $this->connection->executeQuery($sql, $params);
351 }
352
353 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
354 {
355 $sql = 'SELECT';
356
357 if ($tableName === null) {
358 $sql .= ' tbl.name AS table_name, scm.name AS schema_name,';
359 }
360
361 $sql .= <<<'SQL'
362 idx.name AS key_name,
363 col.name AS column_name,
364 ~idx.is_unique AS non_unique,
365 idx.is_primary_key AS [primary],
366 CASE idx.type
367 WHEN '1' THEN 'clustered'
368 WHEN '2' THEN 'nonclustered'
369 ELSE NULL
370 END AS flags
371 FROM sys.tables AS tbl
372 JOIN sys.schemas AS scm
373 ON tbl.schema_id = scm.schema_id
374 JOIN sys.indexes AS idx
375 ON tbl.object_id = idx.object_id
376 JOIN sys.index_columns AS idxcol
377 ON idx.object_id = idxcol.object_id
378 AND idx.index_id = idxcol.index_id
379 JOIN sys.columns AS col
380 ON idxcol.object_id = col.object_id
381 AND idxcol.column_id = col.column_id
382SQL;
383
384 $conditions = [];
385 $params = [];
386
387 if ($tableName !== null) {
388 $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name');
389 $sql .= ' WHERE ' . implode(' AND ', $conditions);
390 }
391
392 $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal';
393
394 return $this->connection->executeQuery($sql, $params);
395 }
396
397 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
398 {
399 $sql = 'SELECT';
400
401 if ($tableName === null) {
402 $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,';
403 }
404
405 $sql .= <<<'SQL'
406 f.name AS ForeignKey,
407 SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
408 OBJECT_NAME (f.parent_object_id) AS TableName,
409 COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
410 SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
411 OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
412 COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
413 f.delete_referential_action_desc,
414 f.update_referential_action_desc
415 FROM sys.foreign_keys AS f
416 INNER JOIN sys.foreign_key_columns AS fc
417 INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
418 ON f.OBJECT_ID = fc.constraint_object_id
419SQL;
420
421 $conditions = [];
422 $params = [];
423
424 if ($tableName !== null) {
425 $conditions[] = $this->getTableWhereClause(
426 $tableName,
427 'SCHEMA_NAME(f.schema_id)',
428 'OBJECT_NAME(f.parent_object_id)',
429 );
430
431 $sql .= ' WHERE ' . implode(' AND ', $conditions);
432 }
433
434 $sql .= ' ORDER BY fc.constraint_column_id';
435
436 return $this->connection->executeQuery($sql, $params);
437 }
438
439 /**
440 * {@inheritDoc}
441 */
442 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
443 {
444 $sql = <<<'SQL'
445 SELECT
446 tbl.name,
447 p.value AS [table_comment]
448 FROM
449 sys.tables AS tbl
450 INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1
451SQL;
452
453 $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"];
454 $params = [];
455
456 if ($tableName !== null) {
457 $conditions[] = "tbl.name = N'" . $tableName . "'";
458 }
459
460 $sql .= ' WHERE ' . implode(' AND ', $conditions);
461
462 /** @var array<string,array<string,mixed>> $metadata */
463 $metadata = $this->connection->executeQuery($sql, $params)
464 ->fetchAllAssociativeIndexed();
465
466 $tableOptions = [];
467 foreach ($metadata as $table => $data) {
468 $data = array_change_key_case($data, CASE_LOWER);
469
470 $tableOptions[$table] = [
471 'comment' => $data['table_comment'],
472 ];
473 }
474
475 return $tableOptions;
476 }
477
478 /**
479 * Returns the where clause to filter schema and table name in a query.
480 *
481 * @param string $table The full qualified name of the table.
482 * @param string $schemaColumn The name of the column to compare the schema to in the where clause.
483 * @param string $tableColumn The name of the column to compare the table to in the where clause.
484 */
485 private function getTableWhereClause(string $table, string $schemaColumn, string $tableColumn): string
486 {
487 if (str_contains($table, '.')) {
488 [$schema, $table] = explode('.', $table);
489 $schema = $this->platform->quoteStringLiteral($schema);
490 $table = $this->platform->quoteStringLiteral($table);
491 } else {
492 $schema = 'SCHEMA_NAME()';
493 $table = $this->platform->quoteStringLiteral($table);
494 }
495
496 return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema);
497 }
498}
diff --git a/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php
new file mode 100644
index 0000000..c001c25
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php
@@ -0,0 +1,620 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\SQLite;
9use Doctrine\DBAL\Platforms\SQLitePlatform;
10use Doctrine\DBAL\Result;
11use Doctrine\DBAL\Types\StringType;
12use Doctrine\DBAL\Types\TextType;
13use Doctrine\DBAL\Types\Type;
14
15use function array_change_key_case;
16use function array_merge;
17use function assert;
18use function count;
19use function implode;
20use function is_string;
21use function preg_match;
22use function preg_match_all;
23use function preg_quote;
24use function preg_replace;
25use function rtrim;
26use function str_contains;
27use function str_replace;
28use function str_starts_with;
29use function strcasecmp;
30use function strtolower;
31use function trim;
32use function usort;
33
34use const CASE_LOWER;
35
36/**
37 * SQLite SchemaManager.
38 *
39 * @extends AbstractSchemaManager<SQLitePlatform>
40 */
41class SQLiteSchemaManager extends AbstractSchemaManager
42{
43 /**
44 * {@inheritDoc}
45 */
46 protected function fetchForeignKeyColumnsByTable(string $databaseName): array
47 {
48 $columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName);
49
50 if (count($columnsByTable) > 0) {
51 foreach ($columnsByTable as $table => $columns) {
52 $columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns);
53 }
54 }
55
56 return $columnsByTable;
57 }
58
59 public function createForeignKey(ForeignKeyConstraint $foreignKey, string $table): void
60 {
61 $table = $this->introspectTable($table);
62
63 $this->alterTable(new TableDiff($table, [], [], [], [], [], [], [], [], [$foreignKey], [], []));
64 }
65
66 public function dropForeignKey(string $name, string $table): void
67 {
68 $table = $this->introspectTable($table);
69
70 $foreignKey = $table->getForeignKey($name);
71
72 $this->alterTable(new TableDiff($table, [], [], [], [], [], [], [], [], [], [], [$foreignKey]));
73 }
74
75 /**
76 * {@inheritDoc}
77 */
78 public function listTableForeignKeys(string $table): array
79 {
80 $table = $this->normalizeName($table);
81
82 $columns = $this->selectForeignKeyColumns('main', $table)
83 ->fetchAllAssociative();
84
85 if (count($columns) > 0) {
86 $columns = $this->addDetailsToTableForeignKeyColumns($table, $columns);
87 }
88
89 return $this->_getPortableTableForeignKeysList($columns);
90 }
91
92 /**
93 * {@inheritDoc}
94 */
95 protected function _getPortableTableDefinition(array $table): string
96 {
97 return $table['table_name'];
98 }
99
100 /**
101 * {@inheritDoc}
102 *
103 * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
104 */
105 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
106 {
107 $indexBuffer = [];
108
109 // fetch primary
110 $indexArray = $this->connection->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]);
111
112 usort(
113 $indexArray,
114 /**
115 * @param array<string,mixed> $a
116 * @param array<string,mixed> $b
117 */
118 static function (array $a, array $b): int {
119 if ($a['pk'] === $b['pk']) {
120 return $a['cid'] - $b['cid'];
121 }
122
123 return $a['pk'] - $b['pk'];
124 },
125 );
126
127 foreach ($indexArray as $indexColumnRow) {
128 if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') {
129 continue;
130 }
131
132 $indexBuffer[] = [
133 'key_name' => 'primary',
134 'primary' => true,
135 'non_unique' => false,
136 'column_name' => $indexColumnRow['name'],
137 ];
138 }
139
140 // fetch regular indexes
141 foreach ($tableIndexes as $tableIndex) {
142 // Ignore indexes with reserved names, e.g. autoindexes
143 if (str_starts_with($tableIndex['name'], 'sqlite_')) {
144 continue;
145 }
146
147 $keyName = $tableIndex['name'];
148 $idx = [];
149 $idx['key_name'] = $keyName;
150 $idx['primary'] = false;
151 $idx['non_unique'] = ! $tableIndex['unique'];
152
153 $indexArray = $this->connection->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]);
154
155 foreach ($indexArray as $indexColumnRow) {
156 $idx['column_name'] = $indexColumnRow['name'];
157 $indexBuffer[] = $idx;
158 }
159 }
160
161 return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
162 }
163
164 /**
165 * {@inheritDoc}
166 */
167 protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array
168 {
169 $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
170
171 // find column with autoincrement
172 $autoincrementColumn = null;
173 $autoincrementCount = 0;
174
175 foreach ($tableColumns as $tableColumn) {
176 if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') {
177 continue;
178 }
179
180 $autoincrementCount++;
181 if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
182 continue;
183 }
184
185 $autoincrementColumn = $tableColumn['name'];
186 }
187
188 if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
189 foreach ($list as $column) {
190 if ($autoincrementColumn !== $column->getName()) {
191 continue;
192 }
193
194 $column->setAutoincrement(true);
195 }
196 }
197
198 // inspect column collation and comments
199 $createSql = $this->getCreateTableSQL($table);
200
201 foreach ($list as $columnName => $column) {
202 $type = $column->getType();
203
204 if ($type instanceof StringType || $type instanceof TextType) {
205 $column->setPlatformOption(
206 'collation',
207 $this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY',
208 );
209 }
210
211 $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
212
213 $column->setComment($comment);
214 }
215
216 return $list;
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
223 {
224 preg_match('/^([^()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches);
225
226 $dbType = trim(strtolower($matches[1]));
227
228 $length = $precision = $unsigned = null;
229 $fixed = $unsigned = false;
230 $scale = 0;
231
232 if (count($matches) >= 6) {
233 $precision = (int) $matches[4];
234 $scale = (int) $matches[6];
235 } elseif (count($matches) >= 4) {
236 $length = (int) $matches[4];
237 }
238
239 if (str_contains($dbType, ' unsigned')) {
240 $dbType = str_replace(' unsigned', '', $dbType);
241 $unsigned = true;
242 }
243
244 $type = $this->platform->getDoctrineTypeMapping($dbType);
245 $default = $tableColumn['dflt_value'];
246 if ($default === 'NULL') {
247 $default = null;
248 }
249
250 if ($default !== null) {
251 // SQLite returns the default value as a literal expression, so we need to parse it
252 if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
253 $default = str_replace("''", "'", $matches[1]);
254 }
255 }
256
257 $notnull = (bool) $tableColumn['notnull'];
258
259 if (! isset($tableColumn['name'])) {
260 $tableColumn['name'] = '';
261 }
262
263 if ($dbType === 'char') {
264 $fixed = true;
265 }
266
267 $options = [
268 'length' => $length,
269 'unsigned' => $unsigned,
270 'fixed' => $fixed,
271 'notnull' => $notnull,
272 'default' => $default,
273 'precision' => $precision,
274 'scale' => $scale,
275 ];
276
277 return new Column($tableColumn['name'], Type::getType($type), $options);
278 }
279
280 /**
281 * {@inheritDoc}
282 */
283 protected function _getPortableViewDefinition(array $view): View
284 {
285 return new View($view['name'], $view['sql']);
286 }
287
288 /**
289 * {@inheritDoc}
290 */
291 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
292 {
293 $list = [];
294 foreach ($tableForeignKeys as $value) {
295 $value = array_change_key_case($value, CASE_LOWER);
296 $id = $value['id'];
297 if (! isset($list[$id])) {
298 if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
299 $value['on_delete'] = null;
300 }
301
302 if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
303 $value['on_update'] = null;
304 }
305
306 $list[$id] = [
307 'name' => $value['constraint_name'],
308 'local' => [],
309 'foreign' => [],
310 'foreignTable' => $value['table'],
311 'onDelete' => $value['on_delete'],
312 'onUpdate' => $value['on_update'],
313 'deferrable' => $value['deferrable'],
314 'deferred' => $value['deferred'],
315 ];
316 }
317
318 $list[$id]['local'][] = $value['from'];
319
320 if ($value['to'] === null) {
321 // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty.
322 // @see https://www.sqlite.org/foreignkeys.html#fk_indexes.
323 $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']);
324
325 if (! isset($foreignTableIndexes['primary'])) {
326 continue;
327 }
328
329 $list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()];
330
331 continue;
332 }
333
334 $list[$id]['foreign'][] = $value['to'];
335 }
336
337 return parent::_getPortableTableForeignKeysList($list);
338 }
339
340 /**
341 * {@inheritDoc}
342 */
343 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
344 {
345 return new ForeignKeyConstraint(
346 $tableForeignKey['local'],
347 $tableForeignKey['foreignTable'],
348 $tableForeignKey['foreign'],
349 $tableForeignKey['name'],
350 [
351 'onDelete' => $tableForeignKey['onDelete'],
352 'onUpdate' => $tableForeignKey['onUpdate'],
353 'deferrable' => $tableForeignKey['deferrable'],
354 'deferred' => $tableForeignKey['deferred'],
355 ],
356 );
357 }
358
359 private function parseColumnCollationFromSQL(string $column, string $sql): ?string
360 {
361 $pattern = '{(?:\W' . preg_quote($column) . '\W|\W'
362 . preg_quote($this->platform->quoteSingleIdentifier($column))
363 . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
364
365 if (preg_match($pattern, $sql, $match) !== 1) {
366 return null;
367 }
368
369 return $match[1];
370 }
371
372 private function parseTableCommentFromSQL(string $table, string $sql): ?string
373 {
374 $pattern = '/\s* # Allow whitespace characters at start of line
375CREATE\sTABLE # Match "CREATE TABLE"
376(?:\W"' . preg_quote($this->platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/')
377 . '\W) # Match table name (quoted and unquoted)
378( # Start capture
379 (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
380)/ix';
381
382 if (preg_match($pattern, $sql, $match) !== 1) {
383 return null;
384 }
385
386 $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
387
388 return $comment === '' ? null : $comment;
389 }
390
391 private function parseColumnCommentFromSQL(string $column, string $sql): string
392 {
393 $pattern = '{[\s(,](?:\W' . preg_quote($this->platform->quoteSingleIdentifier($column))
394 . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
395
396 if (preg_match($pattern, $sql, $match) !== 1) {
397 return '';
398 }
399
400 $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
401 assert(is_string($comment));
402
403 return $comment;
404 }
405
406 /** @throws Exception */
407 private function getCreateTableSQL(string $table): string
408 {
409 $sql = $this->connection->fetchOne(
410 <<<'SQL'
411SELECT sql
412 FROM (
413 SELECT *
414 FROM sqlite_master
415 UNION ALL
416 SELECT *
417 FROM sqlite_temp_master
418 )
419WHERE type = 'table'
420AND name = ?
421SQL
422 ,
423 [$table],
424 );
425
426 if ($sql !== false) {
427 return $sql;
428 }
429
430 return '';
431 }
432
433 /**
434 * @param list<array<string,mixed>> $columns
435 *
436 * @return list<array<string,mixed>>
437 *
438 * @throws Exception
439 */
440 private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array
441 {
442 $foreignKeyDetails = $this->getForeignKeyDetails($table);
443 $foreignKeyCount = count($foreignKeyDetails);
444
445 foreach ($columns as $i => $column) {
446 // SQLite identifies foreign keys in reverse order of appearance in SQL
447 $columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]);
448 }
449
450 return $columns;
451 }
452
453 /**
454 * @return list<array<string, mixed>>
455 *
456 * @throws Exception
457 */
458 private function getForeignKeyDetails(string $table): array
459 {
460 $createSql = $this->getCreateTableSQL($table);
461
462 if (
463 preg_match_all(
464 '#
465 (?:CONSTRAINT\s+(\S+)\s+)?
466 (?:FOREIGN\s+KEY[^)]+\)\s*)?
467 REFERENCES\s+\S+\s*(?:\([^)]+\))?
468 (?:
469 [^,]*?
470 (NOT\s+DEFERRABLE|DEFERRABLE)
471 (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
472 )?#isx',
473 $createSql,
474 $match,
475 ) === 0
476 ) {
477 return [];
478 }
479
480 $names = $match[1];
481 $deferrable = $match[2];
482 $deferred = $match[3];
483 $details = [];
484
485 for ($i = 0, $count = count($match[0]); $i < $count; $i++) {
486 $details[] = [
487 'constraint_name' => $names[$i] ?? '',
488 'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0,
489 'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0,
490 ];
491 }
492
493 return $details;
494 }
495
496 public function createComparator(): Comparator
497 {
498 return new SQLite\Comparator($this->platform);
499 }
500
501 protected function selectTableNames(string $databaseName): Result
502 {
503 $sql = <<<'SQL'
504SELECT name AS table_name
505FROM sqlite_master
506WHERE type = 'table'
507 AND name != 'sqlite_sequence'
508 AND name != 'geometry_columns'
509 AND name != 'spatial_ref_sys'
510UNION ALL
511SELECT name
512FROM sqlite_temp_master
513WHERE type = 'table'
514ORDER BY name
515SQL;
516
517 return $this->connection->executeQuery($sql);
518 }
519
520 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
521 {
522 $sql = <<<'SQL'
523 SELECT t.name AS table_name,
524 c.*
525 FROM sqlite_master t
526 JOIN pragma_table_info(t.name) c
527SQL;
528
529 $conditions = [
530 "t.type = 'table'",
531 "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
532 ];
533 $params = [];
534
535 if ($tableName !== null) {
536 $conditions[] = 't.name = ?';
537 $params[] = str_replace('.', '__', $tableName);
538 }
539
540 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid';
541
542 return $this->connection->executeQuery($sql, $params);
543 }
544
545 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
546 {
547 $sql = <<<'SQL'
548 SELECT t.name AS table_name,
549 i.*
550 FROM sqlite_master t
551 JOIN pragma_index_list(t.name) i
552SQL;
553
554 $conditions = [
555 "t.type = 'table'",
556 "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
557 ];
558 $params = [];
559
560 if ($tableName !== null) {
561 $conditions[] = 't.name = ?';
562 $params[] = str_replace('.', '__', $tableName);
563 }
564
565 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq';
566
567 return $this->connection->executeQuery($sql, $params);
568 }
569
570 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
571 {
572 $sql = <<<'SQL'
573 SELECT t.name AS table_name,
574 p.*
575 FROM sqlite_master t
576 JOIN pragma_foreign_key_list(t.name) p
577 ON p."seq" != '-1'
578SQL;
579
580 $conditions = [
581 "t.type = 'table'",
582 "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
583 ];
584 $params = [];
585
586 if ($tableName !== null) {
587 $conditions[] = 't.name = ?';
588 $params[] = str_replace('.', '__', $tableName);
589 }
590
591 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq';
592
593 return $this->connection->executeQuery($sql, $params);
594 }
595
596 /**
597 * {@inheritDoc}
598 */
599 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
600 {
601 if ($tableName === null) {
602 $tables = $this->listTableNames();
603 } else {
604 $tables = [$tableName];
605 }
606
607 $tableOptions = [];
608 foreach ($tables as $table) {
609 $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table));
610
611 if ($comment === null) {
612 continue;
613 }
614
615 $tableOptions[$table]['comment'] = $comment;
616 }
617
618 return $tableOptions;
619 }
620}
diff --git a/vendor/doctrine/dbal/src/Schema/Schema.php b/vendor/doctrine/dbal/src/Schema/Schema.php
new file mode 100644
index 0000000..25fe4a3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Schema.php
@@ -0,0 +1,374 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists;
10use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists;
11use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist;
12use Doctrine\DBAL\Schema\Exception\TableAlreadyExists;
13use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
14use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder;
15use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder;
16
17use function array_values;
18use function str_contains;
19use function strtolower;
20
21/**
22 * Object representation of a database schema.
23 *
24 * Different vendors have very inconsistent naming with regard to the concept
25 * of a "schema". Doctrine understands a schema as the entity that conceptually
26 * wraps a set of database objects such as tables, sequences, indexes and
27 * foreign keys that belong to each other into a namespace. A Doctrine Schema
28 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
29 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
30 *
31 * Every asset in the doctrine schema has a name. A name consists of either a
32 * namespace.local name pair or just a local unqualified name.
33 *
34 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
35 * database object (asset). A schema can have a name, which will be used as
36 * default namespace for the unqualified database objects that are created in
37 * the schema.
38 *
39 * In the case of MySQL where cross-database queries are allowed this leads to
40 * databases being "misinterpreted" as namespaces. This is intentional, however
41 * the CREATE/DROP SQL visitors will just filter this queries and do not
42 * execute them. Only the queries for the currently connected database are
43 * executed.
44 */
45class Schema extends AbstractAsset
46{
47 /**
48 * The namespaces in this schema.
49 *
50 * @var array<string, string>
51 */
52 private array $namespaces = [];
53
54 /** @var array<string, Table> */
55 protected array $_tables = [];
56
57 /** @var array<string, Sequence> */
58 protected array $_sequences = [];
59
60 protected SchemaConfig $_schemaConfig;
61
62 /**
63 * @param array<Table> $tables
64 * @param array<Sequence> $sequences
65 * @param array<string> $namespaces
66 */
67 public function __construct(
68 array $tables = [],
69 array $sequences = [],
70 ?SchemaConfig $schemaConfig = null,
71 array $namespaces = [],
72 ) {
73 $schemaConfig ??= new SchemaConfig();
74
75 $this->_schemaConfig = $schemaConfig;
76
77 $name = $schemaConfig->getName();
78
79 if ($name !== null) {
80 $this->_setName($name);
81 }
82
83 foreach ($namespaces as $namespace) {
84 $this->createNamespace($namespace);
85 }
86
87 foreach ($tables as $table) {
88 $this->_addTable($table);
89 }
90
91 foreach ($sequences as $sequence) {
92 $this->_addSequence($sequence);
93 }
94 }
95
96 protected function _addTable(Table $table): void
97 {
98 $namespaceName = $table->getNamespaceName();
99 $tableName = $this->normalizeName($table);
100
101 if (isset($this->_tables[$tableName])) {
102 throw TableAlreadyExists::new($tableName);
103 }
104
105 if (
106 $namespaceName !== null
107 && ! $table->isInDefaultNamespace($this->getName())
108 && ! $this->hasNamespace($namespaceName)
109 ) {
110 $this->createNamespace($namespaceName);
111 }
112
113 $this->_tables[$tableName] = $table;
114 $table->setSchemaConfig($this->_schemaConfig);
115 }
116
117 protected function _addSequence(Sequence $sequence): void
118 {
119 $namespaceName = $sequence->getNamespaceName();
120 $seqName = $this->normalizeName($sequence);
121
122 if (isset($this->_sequences[$seqName])) {
123 throw SequenceAlreadyExists::new($seqName);
124 }
125
126 if (
127 $namespaceName !== null
128 && ! $sequence->isInDefaultNamespace($this->getName())
129 && ! $this->hasNamespace($namespaceName)
130 ) {
131 $this->createNamespace($namespaceName);
132 }
133
134 $this->_sequences[$seqName] = $sequence;
135 }
136
137 /**
138 * Returns the namespaces of this schema.
139 *
140 * @return list<string> A list of namespace names.
141 */
142 public function getNamespaces(): array
143 {
144 return array_values($this->namespaces);
145 }
146
147 /**
148 * Gets all tables of this schema.
149 *
150 * @return list<Table>
151 */
152 public function getTables(): array
153 {
154 return array_values($this->_tables);
155 }
156
157 public function getTable(string $name): Table
158 {
159 $name = $this->getFullQualifiedAssetName($name);
160 if (! isset($this->_tables[$name])) {
161 throw TableDoesNotExist::new($name);
162 }
163
164 return $this->_tables[$name];
165 }
166
167 private function getFullQualifiedAssetName(string $name): string
168 {
169 $name = $this->getUnquotedAssetName($name);
170
171 if (! str_contains($name, '.')) {
172 $name = $this->getName() . '.' . $name;
173 }
174
175 return strtolower($name);
176 }
177
178 /**
179 * The normalized name is qualified and lower-cased. Lower-casing is
180 * actually wrong, but we have to do it to keep our sanity. If you are
181 * using database objects that only differentiate in the casing (FOO vs
182 * Foo) then you will NOT be able to use Doctrine Schema abstraction.
183 *
184 * Every non-namespaced element is prefixed with this schema name.
185 */
186 private function normalizeName(AbstractAsset $asset): string
187 {
188 $name = $asset->getName();
189
190 if ($asset->getNamespaceName() === null) {
191 $name = $this->getName() . '.' . $name;
192 }
193
194 return strtolower($name);
195 }
196
197 /**
198 * Returns the unquoted representation of a given asset name.
199 */
200 private function getUnquotedAssetName(string $assetName): string
201 {
202 if ($this->isIdentifierQuoted($assetName)) {
203 return $this->trimQuotes($assetName);
204 }
205
206 return $assetName;
207 }
208
209 /**
210 * Does this schema have a namespace with the given name?
211 */
212 public function hasNamespace(string $name): bool
213 {
214 $name = strtolower($this->getUnquotedAssetName($name));
215
216 return isset($this->namespaces[$name]);
217 }
218
219 /**
220 * Does this schema have a table with the given name?
221 */
222 public function hasTable(string $name): bool
223 {
224 $name = $this->getFullQualifiedAssetName($name);
225
226 return isset($this->_tables[$name]);
227 }
228
229 public function hasSequence(string $name): bool
230 {
231 $name = $this->getFullQualifiedAssetName($name);
232
233 return isset($this->_sequences[$name]);
234 }
235
236 public function getSequence(string $name): Sequence
237 {
238 $name = $this->getFullQualifiedAssetName($name);
239 if (! $this->hasSequence($name)) {
240 throw SequenceDoesNotExist::new($name);
241 }
242
243 return $this->_sequences[$name];
244 }
245
246 /** @return list<Sequence> */
247 public function getSequences(): array
248 {
249 return array_values($this->_sequences);
250 }
251
252 /**
253 * Creates a new namespace.
254 *
255 * @return $this
256 */
257 public function createNamespace(string $name): self
258 {
259 $unquotedName = strtolower($this->getUnquotedAssetName($name));
260
261 if (isset($this->namespaces[$unquotedName])) {
262 throw NamespaceAlreadyExists::new($unquotedName);
263 }
264
265 $this->namespaces[$unquotedName] = $name;
266
267 return $this;
268 }
269
270 /**
271 * Creates a new table.
272 */
273 public function createTable(string $name): Table
274 {
275 $table = new Table($name);
276 $this->_addTable($table);
277
278 foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) {
279 $table->addOption($option, $value);
280 }
281
282 return $table;
283 }
284
285 /**
286 * Renames a table.
287 *
288 * @return $this
289 */
290 public function renameTable(string $oldName, string $newName): self
291 {
292 $table = $this->getTable($oldName);
293 $table->_setName($newName);
294
295 $this->dropTable($oldName);
296 $this->_addTable($table);
297
298 return $this;
299 }
300
301 /**
302 * Drops a table from the schema.
303 *
304 * @return $this
305 */
306 public function dropTable(string $name): self
307 {
308 $name = $this->getFullQualifiedAssetName($name);
309 $this->getTable($name);
310 unset($this->_tables[$name]);
311
312 return $this;
313 }
314
315 /**
316 * Creates a new sequence.
317 */
318 public function createSequence(string $name, int $allocationSize = 1, int $initialValue = 1): Sequence
319 {
320 $seq = new Sequence($name, $allocationSize, $initialValue);
321 $this->_addSequence($seq);
322
323 return $seq;
324 }
325
326 /** @return $this */
327 public function dropSequence(string $name): self
328 {
329 $name = $this->getFullQualifiedAssetName($name);
330 unset($this->_sequences[$name]);
331
332 return $this;
333 }
334
335 /**
336 * Returns an array of necessary SQL queries to create the schema on the given platform.
337 *
338 * @return list<string>
339 *
340 * @throws Exception
341 */
342 public function toSql(AbstractPlatform $platform): array
343 {
344 $builder = new CreateSchemaObjectsSQLBuilder($platform);
345
346 return $builder->buildSQL($this);
347 }
348
349 /**
350 * Return an array of necessary SQL queries to drop the schema on the given platform.
351 *
352 * @return list<string>
353 */
354 public function toDropSql(AbstractPlatform $platform): array
355 {
356 $builder = new DropSchemaObjectsSQLBuilder($platform);
357
358 return $builder->buildSQL($this);
359 }
360
361 /**
362 * Cloning a Schema triggers a deep clone of all related assets.
363 */
364 public function __clone()
365 {
366 foreach ($this->_tables as $k => $table) {
367 $this->_tables[$k] = clone $table;
368 }
369
370 foreach ($this->_sequences as $k => $sequence) {
371 $this->_sequences[$k] = clone $sequence;
372 }
373 }
374}
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaConfig.php b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php
new file mode 100644
index 0000000..86cc84f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php
@@ -0,0 +1,61 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7/**
8 * Configuration for a Schema.
9 */
10class SchemaConfig
11{
12 protected int $maxIdentifierLength = 63;
13
14 protected ?string $name = null;
15
16 /** @var array<string, mixed> */
17 protected array $defaultTableOptions = [];
18
19 public function setMaxIdentifierLength(int $length): void
20 {
21 $this->maxIdentifierLength = $length;
22 }
23
24 public function getMaxIdentifierLength(): int
25 {
26 return $this->maxIdentifierLength;
27 }
28
29 /**
30 * Gets the default namespace of schema objects.
31 */
32 public function getName(): ?string
33 {
34 return $this->name;
35 }
36
37 /**
38 * Sets the default namespace name of schema objects.
39 */
40 public function setName(?string $name): void
41 {
42 $this->name = $name;
43 }
44
45 /**
46 * Gets the default options that are passed to Table instances created with
47 * Schema#createTable().
48 *
49 * @return array<string, mixed>
50 */
51 public function getDefaultTableOptions(): array
52 {
53 return $this->defaultTableOptions;
54 }
55
56 /** @param array<string, mixed> $defaultTableOptions */
57 public function setDefaultTableOptions(array $defaultTableOptions): void
58 {
59 $this->defaultTableOptions = $defaultTableOptions;
60 }
61}
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaDiff.php b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php
new file mode 100644
index 0000000..28c014d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php
@@ -0,0 +1,109 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use function array_filter;
8use function count;
9
10/**
11 * Differences between two schemas.
12 */
13class SchemaDiff
14{
15 /** @var array<TableDiff> */
16 private readonly array $alteredTables;
17
18 /**
19 * Constructs an SchemaDiff object.
20 *
21 * @internal The diff can be only instantiated by a {@see Comparator}.
22 *
23 * @param array<string> $createdSchemas
24 * @param array<string> $droppedSchemas
25 * @param array<Table> $createdTables
26 * @param array<TableDiff> $alteredTables
27 * @param array<Table> $droppedTables
28 * @param array<Sequence> $createdSequences
29 * @param array<Sequence> $alteredSequences
30 * @param array<Sequence> $droppedSequences
31 */
32 public function __construct(
33 private readonly array $createdSchemas,
34 private readonly array $droppedSchemas,
35 private readonly array $createdTables,
36 array $alteredTables,
37 private readonly array $droppedTables,
38 private readonly array $createdSequences,
39 private readonly array $alteredSequences,
40 private readonly array $droppedSequences,
41 ) {
42 $this->alteredTables = array_filter($alteredTables, static function (TableDiff $diff): bool {
43 return ! $diff->isEmpty();
44 });
45 }
46
47 /** @return array<string> */
48 public function getCreatedSchemas(): array
49 {
50 return $this->createdSchemas;
51 }
52
53 /** @return array<string> */
54 public function getDroppedSchemas(): array
55 {
56 return $this->droppedSchemas;
57 }
58
59 /** @return array<Table> */
60 public function getCreatedTables(): array
61 {
62 return $this->createdTables;
63 }
64
65 /** @return array<TableDiff> */
66 public function getAlteredTables(): array
67 {
68 return $this->alteredTables;
69 }
70
71 /** @return array<Table> */
72 public function getDroppedTables(): array
73 {
74 return $this->droppedTables;
75 }
76
77 /** @return array<Sequence> */
78 public function getCreatedSequences(): array
79 {
80 return $this->createdSequences;
81 }
82
83 /** @return array<Sequence> */
84 public function getAlteredSequences(): array
85 {
86 return $this->alteredSequences;
87 }
88
89 /** @return array<Sequence> */
90 public function getDroppedSequences(): array
91 {
92 return $this->droppedSequences;
93 }
94
95 /**
96 * Returns whether the diff is empty (contains no changes).
97 */
98 public function isEmpty(): bool
99 {
100 return count($this->createdSchemas) === 0
101 && count($this->droppedSchemas) === 0
102 && count($this->createdTables) === 0
103 && count($this->alteredTables) === 0
104 && count($this->droppedTables) === 0
105 && count($this->createdSequences) === 0
106 && count($this->alteredSequences) === 0
107 && count($this->droppedSequences) === 0;
108 }
109}
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaException.php b/vendor/doctrine/dbal/src/Schema/SchemaException.php
new file mode 100644
index 0000000..43dd2ad
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SchemaException.php
@@ -0,0 +1,12 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8
9/** @psalm-immutable */
10interface SchemaException extends Exception
11{
12}
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php
new file mode 100644
index 0000000..37c32e0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php
@@ -0,0 +1,17 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Connection;
8
9/**
10 * Creates a schema manager for the given connection.
11 *
12 * This interface is an extension point for applications that need to override schema managers.
13 */
14interface SchemaManagerFactory
15{
16 public function createSchemaManager(Connection $connection): AbstractSchemaManager;
17}
diff --git a/vendor/doctrine/dbal/src/Schema/Sequence.php b/vendor/doctrine/dbal/src/Schema/Sequence.php
new file mode 100644
index 0000000..32a5e67
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Sequence.php
@@ -0,0 +1,98 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use function count;
8use function sprintf;
9
10/**
11 * Sequence structure.
12 */
13class Sequence extends AbstractAsset
14{
15 protected int $allocationSize = 1;
16
17 protected int $initialValue = 1;
18
19 public function __construct(
20 string $name,
21 int $allocationSize = 1,
22 int $initialValue = 1,
23 protected ?int $cache = null,
24 ) {
25 $this->_setName($name);
26 $this->setAllocationSize($allocationSize);
27 $this->setInitialValue($initialValue);
28 }
29
30 public function getAllocationSize(): int
31 {
32 return $this->allocationSize;
33 }
34
35 public function getInitialValue(): int
36 {
37 return $this->initialValue;
38 }
39
40 public function getCache(): ?int
41 {
42 return $this->cache;
43 }
44
45 public function setAllocationSize(int $allocationSize): self
46 {
47 $this->allocationSize = $allocationSize;
48
49 return $this;
50 }
51
52 public function setInitialValue(int $initialValue): self
53 {
54 $this->initialValue = $initialValue;
55
56 return $this;
57 }
58
59 public function setCache(int $cache): self
60 {
61 $this->cache = $cache;
62
63 return $this;
64 }
65
66 /**
67 * Checks if this sequence is an autoincrement sequence for a given table.
68 *
69 * This is used inside the comparator to not report sequences as missing,
70 * when the "from" schema implicitly creates the sequences.
71 */
72 public function isAutoIncrementsFor(Table $table): bool
73 {
74 $primaryKey = $table->getPrimaryKey();
75
76 if ($primaryKey === null) {
77 return false;
78 }
79
80 $pkColumns = $primaryKey->getColumns();
81
82 if (count($pkColumns) !== 1) {
83 return false;
84 }
85
86 $column = $table->getColumn($pkColumns[0]);
87
88 if (! $column->getAutoincrement()) {
89 return false;
90 }
91
92 $sequenceName = $this->getShortestName($table->getNamespaceName());
93 $tableName = $table->getShortestName($table->getNamespaceName());
94 $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName()));
95
96 return $tableSequenceName === $sequenceName;
97 }
98}
diff --git a/vendor/doctrine/dbal/src/Schema/Table.php b/vendor/doctrine/dbal/src/Schema/Table.php
new file mode 100644
index 0000000..cc7f04d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/Table.php
@@ -0,0 +1,753 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists;
8use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
9use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist;
10use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists;
11use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist;
12use Doctrine\DBAL\Schema\Exception\IndexNameInvalid;
13use Doctrine\DBAL\Schema\Exception\InvalidTableName;
14use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist;
15use Doctrine\DBAL\Types\Type;
16
17use function array_merge;
18use function array_values;
19use function in_array;
20use function is_string;
21use function preg_match;
22use function strtolower;
23
24/**
25 * Object Representation of a table.
26 */
27class Table extends AbstractAsset
28{
29 /** @var Column[] */
30 protected array $_columns = [];
31
32 /** @var Index[] */
33 private array $implicitIndexes = [];
34
35 /** @var Index[] */
36 protected array $_indexes = [];
37
38 protected ?string $_primaryKeyName = null;
39
40 /** @var UniqueConstraint[] */
41 protected array $uniqueConstraints = [];
42
43 /** @var ForeignKeyConstraint[] */
44 protected array $_fkConstraints = [];
45
46 /** @var mixed[] */
47 protected array $_options = [
48 'create_options' => [],
49 ];
50
51 protected ?SchemaConfig $_schemaConfig = null;
52
53 /**
54 * @param array<Column> $columns
55 * @param array<Index> $indexes
56 * @param array<UniqueConstraint> $uniqueConstraints
57 * @param array<ForeignKeyConstraint> $fkConstraints
58 * @param array<string, mixed> $options
59 */
60 public function __construct(
61 string $name,
62 array $columns = [],
63 array $indexes = [],
64 array $uniqueConstraints = [],
65 array $fkConstraints = [],
66 array $options = [],
67 ) {
68 if ($name === '') {
69 throw InvalidTableName::new($name);
70 }
71
72 $this->_setName($name);
73
74 foreach ($columns as $column) {
75 $this->_addColumn($column);
76 }
77
78 foreach ($indexes as $idx) {
79 $this->_addIndex($idx);
80 }
81
82 foreach ($uniqueConstraints as $uniqueConstraint) {
83 $this->_addUniqueConstraint($uniqueConstraint);
84 }
85
86 foreach ($fkConstraints as $fkConstraint) {
87 $this->_addForeignKeyConstraint($fkConstraint);
88 }
89
90 $this->_options = array_merge($this->_options, $options);
91 }
92
93 public function setSchemaConfig(SchemaConfig $schemaConfig): void
94 {
95 $this->_schemaConfig = $schemaConfig;
96 }
97
98 /**
99 * Sets the Primary Key.
100 *
101 * @param array<int, string> $columnNames
102 */
103 public function setPrimaryKey(array $columnNames, ?string $indexName = null): self
104 {
105 if ($indexName === null) {
106 $indexName = 'primary';
107 }
108
109 $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true));
110
111 foreach ($columnNames as $columnName) {
112 $column = $this->getColumn($columnName);
113 $column->setNotnull(true);
114 }
115
116 return $this;
117 }
118
119 /**
120 * @param array<int, string> $columnNames
121 * @param array<int, string> $flags
122 * @param array<string, mixed> $options
123 */
124 public function addUniqueConstraint(
125 array $columnNames,
126 ?string $indexName = null,
127 array $flags = [],
128 array $options = [],
129 ): self {
130 $indexName ??= $this->_generateIdentifierName(
131 array_merge([$this->getName()], $columnNames),
132 'uniq',
133 $this->_getMaxIdentifierLength(),
134 );
135
136 return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
137 }
138
139 /**
140 * @param array<int, string> $columnNames
141 * @param array<int, string> $flags
142 * @param array<string, mixed> $options
143 */
144 public function addIndex(
145 array $columnNames,
146 ?string $indexName = null,
147 array $flags = [],
148 array $options = [],
149 ): self {
150 $indexName ??= $this->_generateIdentifierName(
151 array_merge([$this->getName()], $columnNames),
152 'idx',
153 $this->_getMaxIdentifierLength(),
154 );
155
156 return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
157 }
158
159 /**
160 * Drops the primary key from this table.
161 */
162 public function dropPrimaryKey(): void
163 {
164 if ($this->_primaryKeyName === null) {
165 return;
166 }
167
168 $this->dropIndex($this->_primaryKeyName);
169 $this->_primaryKeyName = null;
170 }
171
172 /**
173 * Drops an index from this table.
174 */
175 public function dropIndex(string $name): void
176 {
177 $name = $this->normalizeIdentifier($name);
178
179 if (! $this->hasIndex($name)) {
180 throw IndexDoesNotExist::new($name, $this->_name);
181 }
182
183 unset($this->_indexes[$name]);
184 }
185
186 /**
187 * @param array<int, string> $columnNames
188 * @param array<string, mixed> $options
189 */
190 public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []): self
191 {
192 $indexName ??= $this->_generateIdentifierName(
193 array_merge([$this->getName()], $columnNames),
194 'uniq',
195 $this->_getMaxIdentifierLength(),
196 );
197
198 return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
199 }
200
201 /**
202 * Renames an index.
203 *
204 * @param string $oldName The name of the index to rename from.
205 * @param string|null $newName The name of the index to rename to. If null is given, the index name
206 * will be auto-generated.
207 */
208 public function renameIndex(string $oldName, ?string $newName = null): self
209 {
210 $oldName = $this->normalizeIdentifier($oldName);
211 $normalizedNewName = $this->normalizeIdentifier($newName);
212
213 if ($oldName === $normalizedNewName) {
214 return $this;
215 }
216
217 if (! $this->hasIndex($oldName)) {
218 throw IndexDoesNotExist::new($oldName, $this->_name);
219 }
220
221 if ($this->hasIndex($normalizedNewName)) {
222 throw IndexAlreadyExists::new($normalizedNewName, $this->_name);
223 }
224
225 $oldIndex = $this->_indexes[$oldName];
226
227 if ($oldIndex->isPrimary()) {
228 $this->dropPrimaryKey();
229
230 return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? null);
231 }
232
233 unset($this->_indexes[$oldName]);
234
235 if ($oldIndex->isUnique()) {
236 return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions());
237 }
238
239 return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions());
240 }
241
242 /**
243 * Checks if an index begins in the order of the given columns.
244 *
245 * @param array<int, string> $columnNames
246 */
247 public function columnsAreIndexed(array $columnNames): bool
248 {
249 foreach ($this->getIndexes() as $index) {
250 if ($index->spansColumns($columnNames)) {
251 return true;
252 }
253 }
254
255 return false;
256 }
257
258 /** @param array<string, mixed> $options */
259 public function addColumn(string $name, string $typeName, array $options = []): Column
260 {
261 $column = new Column($name, Type::getType($typeName), $options);
262
263 $this->_addColumn($column);
264
265 return $column;
266 }
267
268 /** @param array<string, mixed> $options */
269 public function modifyColumn(string $name, array $options): self
270 {
271 $column = $this->getColumn($name);
272 $column->setOptions($options);
273
274 return $this;
275 }
276
277 /**
278 * Drops a Column from the Table.
279 */
280 public function dropColumn(string $name): self
281 {
282 $name = $this->normalizeIdentifier($name);
283
284 unset($this->_columns[$name]);
285
286 return $this;
287 }
288
289 /**
290 * Adds a foreign key constraint.
291 *
292 * Name is inferred from the local columns.
293 *
294 * @param array<int, string> $localColumnNames
295 * @param array<int, string> $foreignColumnNames
296 * @param array<string, mixed> $options
297 */
298 public function addForeignKeyConstraint(
299 string $foreignTableName,
300 array $localColumnNames,
301 array $foreignColumnNames,
302 array $options = [],
303 ?string $name = null,
304 ): self {
305 $name ??= $this->_generateIdentifierName(
306 array_merge([$this->getName()], $localColumnNames),
307 'fk',
308 $this->_getMaxIdentifierLength(),
309 );
310
311 foreach ($localColumnNames as $columnName) {
312 if (! $this->hasColumn($columnName)) {
313 throw ColumnDoesNotExist::new($columnName, $this->_name);
314 }
315 }
316
317 $constraint = new ForeignKeyConstraint(
318 $localColumnNames,
319 $foreignTableName,
320 $foreignColumnNames,
321 $name,
322 $options,
323 );
324
325 return $this->_addForeignKeyConstraint($constraint);
326 }
327
328 public function addOption(string $name, mixed $value): self
329 {
330 $this->_options[$name] = $value;
331
332 return $this;
333 }
334
335 /**
336 * Returns whether this table has a foreign key constraint with the given name.
337 */
338 public function hasForeignKey(string $name): bool
339 {
340 $name = $this->normalizeIdentifier($name);
341
342 return isset($this->_fkConstraints[$name]);
343 }
344
345 /**
346 * Returns the foreign key constraint with the given name.
347 */
348 public function getForeignKey(string $name): ForeignKeyConstraint
349 {
350 $name = $this->normalizeIdentifier($name);
351
352 if (! $this->hasForeignKey($name)) {
353 throw ForeignKeyDoesNotExist::new($name, $this->_name);
354 }
355
356 return $this->_fkConstraints[$name];
357 }
358
359 /**
360 * Removes the foreign key constraint with the given name.
361 */
362 public function removeForeignKey(string $name): void
363 {
364 $name = $this->normalizeIdentifier($name);
365
366 if (! $this->hasForeignKey($name)) {
367 throw ForeignKeyDoesNotExist::new($name, $this->_name);
368 }
369
370 unset($this->_fkConstraints[$name]);
371 }
372
373 /**
374 * Returns whether this table has a unique constraint with the given name.
375 */
376 public function hasUniqueConstraint(string $name): bool
377 {
378 $name = $this->normalizeIdentifier($name);
379
380 return isset($this->uniqueConstraints[$name]);
381 }
382
383 /**
384 * Returns the unique constraint with the given name.
385 */
386 public function getUniqueConstraint(string $name): UniqueConstraint
387 {
388 $name = $this->normalizeIdentifier($name);
389
390 if (! $this->hasUniqueConstraint($name)) {
391 throw UniqueConstraintDoesNotExist::new($name, $this->_name);
392 }
393
394 return $this->uniqueConstraints[$name];
395 }
396
397 /**
398 * Removes the unique constraint with the given name.
399 */
400 public function removeUniqueConstraint(string $name): void
401 {
402 $name = $this->normalizeIdentifier($name);
403
404 if (! $this->hasUniqueConstraint($name)) {
405 throw UniqueConstraintDoesNotExist::new($name, $this->_name);
406 }
407
408 unset($this->uniqueConstraints[$name]);
409 }
410
411 /**
412 * Returns the list of table columns.
413 *
414 * @return list<Column>
415 */
416 public function getColumns(): array
417 {
418 return array_values($this->_columns);
419 }
420
421 /**
422 * Returns whether this table has a Column with the given name.
423 */
424 public function hasColumn(string $name): bool
425 {
426 $name = $this->normalizeIdentifier($name);
427
428 return isset($this->_columns[$name]);
429 }
430
431 /**
432 * Returns the Column with the given name.
433 */
434 public function getColumn(string $name): Column
435 {
436 $name = $this->normalizeIdentifier($name);
437
438 if (! $this->hasColumn($name)) {
439 throw ColumnDoesNotExist::new($name, $this->_name);
440 }
441
442 return $this->_columns[$name];
443 }
444
445 /**
446 * Returns the primary key.
447 */
448 public function getPrimaryKey(): ?Index
449 {
450 if ($this->_primaryKeyName !== null) {
451 return $this->getIndex($this->_primaryKeyName);
452 }
453
454 return null;
455 }
456
457 /**
458 * Returns whether this table has an Index with the given name.
459 */
460 public function hasIndex(string $name): bool
461 {
462 $name = $this->normalizeIdentifier($name);
463
464 return isset($this->_indexes[$name]);
465 }
466
467 /**
468 * Returns the Index with the given name.
469 */
470 public function getIndex(string $name): Index
471 {
472 $name = $this->normalizeIdentifier($name);
473
474 if (! $this->hasIndex($name)) {
475 throw IndexDoesNotExist::new($name, $this->_name);
476 }
477
478 return $this->_indexes[$name];
479 }
480
481 /** @return array<string, Index> */
482 public function getIndexes(): array
483 {
484 return $this->_indexes;
485 }
486
487 /**
488 * Returns the unique constraints.
489 *
490 * @return array<string, UniqueConstraint>
491 */
492 public function getUniqueConstraints(): array
493 {
494 return $this->uniqueConstraints;
495 }
496
497 /**
498 * Returns the foreign key constraints.
499 *
500 * @return array<string, ForeignKeyConstraint>
501 */
502 public function getForeignKeys(): array
503 {
504 return $this->_fkConstraints;
505 }
506
507 public function hasOption(string $name): bool
508 {
509 return isset($this->_options[$name]);
510 }
511
512 public function getOption(string $name): mixed
513 {
514 return $this->_options[$name] ?? null;
515 }
516
517 /** @return array<string, mixed> */
518 public function getOptions(): array
519 {
520 return $this->_options;
521 }
522
523 /**
524 * Clone of a Table triggers a deep clone of all affected assets.
525 */
526 public function __clone()
527 {
528 foreach ($this->_columns as $k => $column) {
529 $this->_columns[$k] = clone $column;
530 }
531
532 foreach ($this->_indexes as $k => $index) {
533 $this->_indexes[$k] = clone $index;
534 }
535
536 foreach ($this->_fkConstraints as $k => $fk) {
537 $this->_fkConstraints[$k] = clone $fk;
538 }
539 }
540
541 protected function _getMaxIdentifierLength(): int
542 {
543 return $this->_schemaConfig instanceof SchemaConfig
544 ? $this->_schemaConfig->getMaxIdentifierLength()
545 : 63;
546 }
547
548 protected function _addColumn(Column $column): void
549 {
550 $columnName = $column->getName();
551 $columnName = $this->normalizeIdentifier($columnName);
552
553 if (isset($this->_columns[$columnName])) {
554 throw ColumnAlreadyExists::new($this->getName(), $columnName);
555 }
556
557 $this->_columns[$columnName] = $column;
558 }
559
560 /**
561 * Adds an index to the table.
562 */
563 protected function _addIndex(Index $indexCandidate): self
564 {
565 $indexName = $indexCandidate->getName();
566 $indexName = $this->normalizeIdentifier($indexName);
567 $replacedImplicitIndexes = [];
568
569 foreach ($this->implicitIndexes as $name => $implicitIndex) {
570 if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
571 continue;
572 }
573
574 $replacedImplicitIndexes[] = $name;
575 }
576
577 if (
578 (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
579 ($this->_primaryKeyName !== null && $indexCandidate->isPrimary())
580 ) {
581 throw IndexAlreadyExists::new($indexName, $this->_name);
582 }
583
584 foreach ($replacedImplicitIndexes as $name) {
585 unset($this->_indexes[$name], $this->implicitIndexes[$name]);
586 }
587
588 if ($indexCandidate->isPrimary()) {
589 $this->_primaryKeyName = $indexName;
590 }
591
592 $this->_indexes[$indexName] = $indexCandidate;
593
594 return $this;
595 }
596
597 protected function _addUniqueConstraint(UniqueConstraint $constraint): self
598 {
599 $name = $constraint->getName() !== ''
600 ? $constraint->getName()
601 : $this->_generateIdentifierName(
602 array_merge((array) $this->getName(), $constraint->getColumns()),
603 'fk',
604 $this->_getMaxIdentifierLength(),
605 );
606
607 $name = $this->normalizeIdentifier($name);
608
609 $this->uniqueConstraints[$name] = $constraint;
610
611 // If there is already an index that fulfills this requirements drop the request. In the case of __construct
612 // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
613 // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
614 $indexName = $this->_generateIdentifierName(
615 array_merge([$this->getName()], $constraint->getColumns()),
616 'idx',
617 $this->_getMaxIdentifierLength(),
618 );
619
620 $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
621
622 foreach ($this->_indexes as $existingIndex) {
623 if ($indexCandidate->isFulfilledBy($existingIndex)) {
624 return $this;
625 }
626 }
627
628 $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
629
630 return $this;
631 }
632
633 protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint): self
634 {
635 $name = $constraint->getName() !== ''
636 ? $constraint->getName()
637 : $this->_generateIdentifierName(
638 array_merge((array) $this->getName(), $constraint->getLocalColumns()),
639 'fk',
640 $this->_getMaxIdentifierLength(),
641 );
642
643 $name = $this->normalizeIdentifier($name);
644
645 $this->_fkConstraints[$name] = $constraint;
646
647 // add an explicit index on the foreign key columns.
648 // If there is already an index that fulfills this requirements drop the request. In the case of __construct
649 // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
650 // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
651 $indexName = $this->_generateIdentifierName(
652 array_merge([$this->getName()], $constraint->getLocalColumns()),
653 'idx',
654 $this->_getMaxIdentifierLength(),
655 );
656
657 $indexCandidate = $this->_createIndex($constraint->getLocalColumns(), $indexName, false, false);
658
659 foreach ($this->_indexes as $existingIndex) {
660 if ($indexCandidate->isFulfilledBy($existingIndex)) {
661 return $this;
662 }
663 }
664
665 $this->_addIndex($indexCandidate);
666 $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
667
668 return $this;
669 }
670
671 /**
672 * Normalizes a given identifier.
673 *
674 * Trims quotes and lowercases the given identifier.
675 */
676 private function normalizeIdentifier(?string $identifier): string
677 {
678 if ($identifier === null) {
679 return '';
680 }
681
682 return $this->trimQuotes(strtolower($identifier));
683 }
684
685 public function setComment(string $comment): self
686 {
687 // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
688 $this->addOption('comment', $comment);
689
690 return $this;
691 }
692
693 public function getComment(): ?string
694 {
695 return $this->_options['comment'] ?? null;
696 }
697
698 /**
699 * @param array<string|int, string> $columns
700 * @param array<int, string> $flags
701 * @param array<string, mixed> $options
702 */
703 private function _createUniqueConstraint(
704 array $columns,
705 string $indexName,
706 array $flags = [],
707 array $options = [],
708 ): UniqueConstraint {
709 if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
710 throw IndexNameInvalid::new($indexName);
711 }
712
713 foreach ($columns as $index => $value) {
714 if (is_string($index)) {
715 $columnName = $index;
716 } else {
717 $columnName = $value;
718 }
719
720 if (! $this->hasColumn($columnName)) {
721 throw ColumnDoesNotExist::new($columnName, $this->_name);
722 }
723 }
724
725 return new UniqueConstraint($indexName, $columns, $flags, $options);
726 }
727
728 /**
729 * @param array<int, string> $columns
730 * @param array<int, string> $flags
731 * @param array<string, mixed> $options
732 */
733 private function _createIndex(
734 array $columns,
735 string $indexName,
736 bool $isUnique,
737 bool $isPrimary,
738 array $flags = [],
739 array $options = [],
740 ): Index {
741 if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
742 throw IndexNameInvalid::new($indexName);
743 }
744
745 foreach ($columns as $columnName) {
746 if (! $this->hasColumn($columnName)) {
747 throw ColumnDoesNotExist::new($columnName, $this->_name);
748 }
749 }
750
751 return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options);
752 }
753}
diff --git a/vendor/doctrine/dbal/src/Schema/TableDiff.php b/vendor/doctrine/dbal/src/Schema/TableDiff.php
new file mode 100644
index 0000000..1cd59e8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/TableDiff.php
@@ -0,0 +1,164 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use function array_filter;
8use function count;
9
10/**
11 * Table Diff.
12 */
13class TableDiff
14{
15 /**
16 * Constructs a TableDiff object.
17 *
18 * @internal The diff can be only instantiated by a {@see Comparator}.
19 *
20 * @param array<ForeignKeyConstraint> $droppedForeignKeys
21 * @param array<Column> $addedColumns
22 * @param array<ColumnDiff> $modifiedColumns
23 * @param array<Column> $droppedColumns
24 * @param array<string, Column> $renamedColumns
25 * @param array<Index> $addedIndexes
26 * @param array<Index> $modifiedIndexes
27 * @param array<Index> $droppedIndexes
28 * @param array<string, Index> $renamedIndexes
29 * @param array<ForeignKeyConstraint> $addedForeignKeys
30 * @param array<ForeignKeyConstraint> $modifiedForeignKeys
31 */
32 public function __construct(
33 private readonly Table $oldTable,
34 private readonly array $addedColumns,
35 private readonly array $modifiedColumns,
36 private readonly array $droppedColumns,
37 private readonly array $renamedColumns,
38 private array $addedIndexes,
39 private readonly array $modifiedIndexes,
40 private array $droppedIndexes,
41 private readonly array $renamedIndexes,
42 private readonly array $addedForeignKeys,
43 private readonly array $modifiedForeignKeys,
44 private readonly array $droppedForeignKeys,
45 ) {
46 }
47
48 public function getOldTable(): Table
49 {
50 return $this->oldTable;
51 }
52
53 /** @return array<Column> */
54 public function getAddedColumns(): array
55 {
56 return $this->addedColumns;
57 }
58
59 /** @return array<ColumnDiff> */
60 public function getModifiedColumns(): array
61 {
62 return $this->modifiedColumns;
63 }
64
65 /** @return array<Column> */
66 public function getDroppedColumns(): array
67 {
68 return $this->droppedColumns;
69 }
70
71 /** @return array<string,Column> */
72 public function getRenamedColumns(): array
73 {
74 return $this->renamedColumns;
75 }
76
77 /** @return array<Index> */
78 public function getAddedIndexes(): array
79 {
80 return $this->addedIndexes;
81 }
82
83 /**
84 * @internal This method exists only for compatibility with the current implementation of schema managers
85 * that modify the diff while processing it.
86 */
87 public function unsetAddedIndex(Index $index): void
88 {
89 $this->addedIndexes = array_filter(
90 $this->addedIndexes,
91 static function (Index $addedIndex) use ($index): bool {
92 return $addedIndex !== $index;
93 },
94 );
95 }
96
97 /** @return array<Index> */
98 public function getModifiedIndexes(): array
99 {
100 return $this->modifiedIndexes;
101 }
102
103 /** @return array<Index> */
104 public function getDroppedIndexes(): array
105 {
106 return $this->droppedIndexes;
107 }
108
109 /**
110 * @internal This method exists only for compatibility with the current implementation of schema managers
111 * that modify the diff while processing it.
112 */
113 public function unsetDroppedIndex(Index $index): void
114 {
115 $this->droppedIndexes = array_filter(
116 $this->droppedIndexes,
117 static function (Index $droppedIndex) use ($index): bool {
118 return $droppedIndex !== $index;
119 },
120 );
121 }
122
123 /** @return array<string,Index> */
124 public function getRenamedIndexes(): array
125 {
126 return $this->renamedIndexes;
127 }
128
129 /** @return array<ForeignKeyConstraint> */
130 public function getAddedForeignKeys(): array
131 {
132 return $this->addedForeignKeys;
133 }
134
135 /** @return array<ForeignKeyConstraint> */
136 public function getModifiedForeignKeys(): array
137 {
138 return $this->modifiedForeignKeys;
139 }
140
141 /** @return array<ForeignKeyConstraint> */
142 public function getDroppedForeignKeys(): array
143 {
144 return $this->droppedForeignKeys;
145 }
146
147 /**
148 * Returns whether the diff is empty (contains no changes).
149 */
150 public function isEmpty(): bool
151 {
152 return count($this->addedColumns) === 0
153 && count($this->modifiedColumns) === 0
154 && count($this->droppedColumns) === 0
155 && count($this->renamedColumns) === 0
156 && count($this->addedIndexes) === 0
157 && count($this->modifiedIndexes) === 0
158 && count($this->droppedIndexes) === 0
159 && count($this->renamedIndexes) === 0
160 && count($this->addedForeignKeys) === 0
161 && count($this->modifiedForeignKeys) === 0
162 && count($this->droppedForeignKeys) === 0;
163 }
164}
diff --git a/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php
new file mode 100644
index 0000000..a33d446
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php
@@ -0,0 +1,152 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function array_keys;
10use function array_map;
11use function strtolower;
12
13/**
14 * Class for a unique constraint.
15 */
16class UniqueConstraint extends AbstractAsset
17{
18 /**
19 * Asset identifier instances of the column names the unique constraint is associated with.
20 *
21 * @var array<string, Identifier>
22 */
23 protected array $columns = [];
24
25 /**
26 * Platform specific flags
27 *
28 * @var array<string, true>
29 */
30 protected array $flags = [];
31
32 /**
33 * @param array<string> $columns
34 * @param array<string> $flags
35 * @param array<string, mixed> $options
36 */
37 public function __construct(
38 string $name,
39 array $columns,
40 array $flags = [],
41 private readonly array $options = [],
42 ) {
43 $this->_setName($name);
44
45 foreach ($columns as $column) {
46 $this->addColumn($column);
47 }
48
49 foreach ($flags as $flag) {
50 $this->addFlag($flag);
51 }
52 }
53
54 /**
55 * Returns the names of the referencing table columns the constraint is associated with.
56 *
57 * @return list<string>
58 */
59 public function getColumns(): array
60 {
61 return array_keys($this->columns);
62 }
63
64 /**
65 * Returns the quoted representation of the column names the constraint is associated with.
66 *
67 * But only if they were defined with one or a column name
68 * is a keyword reserved by the platform.
69 * Otherwise, the plain unquoted value as inserted is returned.
70 *
71 * @param AbstractPlatform $platform The platform to use for quotation.
72 *
73 * @return list<string>
74 */
75 public function getQuotedColumns(AbstractPlatform $platform): array
76 {
77 $columns = [];
78
79 foreach ($this->columns as $column) {
80 $columns[] = $column->getQuotedName($platform);
81 }
82
83 return $columns;
84 }
85
86 /** @return array<int, string> */
87 public function getUnquotedColumns(): array
88 {
89 return array_map($this->trimQuotes(...), $this->getColumns());
90 }
91
92 /**
93 * Returns platform specific flags for unique constraint.
94 *
95 * @return array<int, string>
96 */
97 public function getFlags(): array
98 {
99 return array_keys($this->flags);
100 }
101
102 /**
103 * Adds flag for a unique constraint that translates to platform specific handling.
104 *
105 * @return $this
106 *
107 * @example $uniqueConstraint->addFlag('CLUSTERED')
108 */
109 public function addFlag(string $flag): self
110 {
111 $this->flags[strtolower($flag)] = true;
112
113 return $this;
114 }
115
116 /**
117 * Does this unique constraint have a specific flag?
118 */
119 public function hasFlag(string $flag): bool
120 {
121 return isset($this->flags[strtolower($flag)]);
122 }
123
124 /**
125 * Removes a flag.
126 */
127 public function removeFlag(string $flag): void
128 {
129 unset($this->flags[strtolower($flag)]);
130 }
131
132 public function hasOption(string $name): bool
133 {
134 return isset($this->options[strtolower($name)]);
135 }
136
137 public function getOption(string $name): mixed
138 {
139 return $this->options[strtolower($name)];
140 }
141
142 /** @return array<string, mixed> */
143 public function getOptions(): array
144 {
145 return $this->options;
146 }
147
148 protected function addColumn(string $column): void
149 {
150 $this->columns[$column] = new Identifier($column);
151 }
152}
diff --git a/vendor/doctrine/dbal/src/Schema/View.php b/vendor/doctrine/dbal/src/Schema/View.php
new file mode 100644
index 0000000..81f5f8a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/View.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7/**
8 * Representation of a Database View.
9 */
10class View extends AbstractAsset
11{
12 public function __construct(string $name, private readonly string $sql)
13 {
14 $this->_setName($name);
15 }
16
17 public function getSql(): string
18 {
19 return $this->sql;
20 }
21}
diff --git a/vendor/doctrine/dbal/src/ServerVersionProvider.php b/vendor/doctrine/dbal/src/ServerVersionProvider.php
new file mode 100644
index 0000000..91dd9ab
--- /dev/null
+++ b/vendor/doctrine/dbal/src/ServerVersionProvider.php
@@ -0,0 +1,13 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7interface ServerVersionProvider
8{
9 /**
10 * Returns the database server version
11 */
12 public function getServerVersion(): string;
13}
diff --git a/vendor/doctrine/dbal/src/Statement.php b/vendor/doctrine/dbal/src/Statement.php
new file mode 100644
index 0000000..9b4a3b4
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Statement.php
@@ -0,0 +1,143 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Types\Type;
9
10use function is_string;
11
12/**
13 * A database abstraction-level statement that implements support for logging, DBAL mapping types, etc.
14 */
15class Statement
16{
17 /**
18 * The bound parameters.
19 *
20 * @var mixed[]
21 */
22 protected array $params = [];
23
24 /**
25 * The parameter types.
26 *
27 * @var ParameterType[]|string[]|Type[]
28 */
29 protected array $types = [];
30
31 /**
32 * The underlying database platform.
33 */
34 protected AbstractPlatform $platform;
35
36 /**
37 * Creates a new <tt>Statement</tt> for the given SQL and <tt>Connection</tt>.
38 *
39 * @internal The statement can be only instantiated by {@see Connection}.
40 *
41 * @param Connection $conn The connection for handling statement errors.
42 * @param Driver\Statement $stmt The underlying driver-level statement.
43 * @param string $sql The SQL of the statement.
44 *
45 * @throws Exception
46 */
47 public function __construct(
48 protected Connection $conn,
49 protected Driver\Statement $stmt,
50 protected string $sql,
51 ) {
52 $this->platform = $conn->getDatabasePlatform();
53 }
54
55 /**
56 * Binds a parameter value to the statement.
57 *
58 * The value can optionally be bound with a DBAL mapping type.
59 * If bound with a DBAL mapping type, the binding type is derived from the mapping
60 * type and the value undergoes the conversion routines of the mapping type before
61 * being bound.
62 *
63 * @param string|int $param Parameter identifier. For a prepared statement using named placeholders,
64 * this will be a parameter name of the form :name. For a prepared statement
65 * using question mark placeholders, this will be the 1-indexed position
66 * of the parameter.
67 * @param mixed $value The value to bind to the parameter.
68 * @param ParameterType|string|Type $type Either a {@see \Doctrine\DBAL\ParameterType} or a DBAL mapping type name
69 * or instance.
70 *
71 * @throws Exception
72 */
73 public function bindValue(
74 string|int $param,
75 mixed $value,
76 string|ParameterType|Type $type = ParameterType::STRING,
77 ): void {
78 $this->params[$param] = $value;
79 $this->types[$param] = $type;
80
81 if (is_string($type)) {
82 $type = Type::getType($type);
83 }
84
85 if ($type instanceof Type) {
86 $value = $type->convertToDatabaseValue($value, $this->platform);
87 $bindingType = $type->getBindingType();
88 } else {
89 $bindingType = $type;
90 }
91
92 try {
93 $this->stmt->bindValue($param, $value, $bindingType);
94 } catch (Driver\Exception $e) {
95 throw $this->conn->convertException($e);
96 }
97 }
98
99 /** @throws Exception */
100 private function execute(): Result
101 {
102 try {
103 return new Result(
104 $this->stmt->execute(),
105 $this->conn,
106 );
107 } catch (Driver\Exception $ex) {
108 throw $this->conn->convertExceptionDuringQuery($ex, $this->sql, $this->params, $this->types);
109 }
110 }
111
112 /**
113 * Executes the statement with the currently bound parameters and return result.
114 *
115 * @throws Exception
116 */
117 public function executeQuery(): Result
118 {
119 return $this->execute();
120 }
121
122 /**
123 * Executes the statement with the currently bound parameters and return affected rows.
124 *
125 * If the number of rows exceeds {@see PHP_INT_MAX}, it might be returned as string if the driver supports it.
126 *
127 * @return int|numeric-string
128 *
129 * @throws Exception
130 */
131 public function executeStatement(): int|string
132 {
133 return $this->execute()->rowCount();
134 }
135
136 /**
137 * Gets the wrapped driver statement.
138 */
139 public function getWrappedStatement(): Driver\Statement
140 {
141 return $this->stmt;
142 }
143}
diff --git a/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php b/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php
new file mode 100644
index 0000000..8ec6f23
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Tools/Console/Command/RunSqlCommand.php
@@ -0,0 +1,119 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Tools\Console\Command;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Tools\Console\ConnectionProvider;
10use RuntimeException;
11use Symfony\Component\Console\Command\Command;
12use Symfony\Component\Console\Input\InputArgument;
13use Symfony\Component\Console\Input\InputInterface;
14use Symfony\Component\Console\Input\InputOption;
15use Symfony\Component\Console\Output\OutputInterface;
16use Symfony\Component\Console\Style\SymfonyStyle;
17
18use function array_keys;
19use function assert;
20use function is_bool;
21use function is_string;
22use function sprintf;
23use function stripos;
24
25/**
26 * Task for executing arbitrary SQL that can come from a file or directly from
27 * the command line.
28 */
29class RunSqlCommand extends Command
30{
31 public function __construct(private readonly ConnectionProvider $connectionProvider)
32 {
33 parent::__construct();
34 }
35
36 protected function configure(): void
37 {
38 $this
39 ->setName('dbal:run-sql')
40 ->setDescription('Executes arbitrary SQL directly from the command line.')
41 ->setDefinition([
42 new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'),
43 new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'),
44 new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set (deprecated).'),
45 new InputOption('force-fetch', null, InputOption::VALUE_NONE, 'Forces fetching the result.'),
46 ])
47 ->setHelp(<<<'EOT'
48The <info>%command.name%</info> command executes the given SQL query and
49outputs the results:
50
51<info>php %command.full_name% "SELECT * FROM users"</info>
52EOT);
53 }
54
55 /**
56 * {@inheritDoc}
57 *
58 * @throws Exception
59 */
60 protected function execute(InputInterface $input, OutputInterface $output): int
61 {
62 $conn = $this->getConnection($input);
63 $io = new SymfonyStyle($input, $output);
64
65 $sql = $input->getArgument('sql');
66
67 if ($sql === null) {
68 throw new RuntimeException('Argument "sql" is required in order to execute this command correctly.');
69 }
70
71 assert(is_string($sql));
72
73 if ($input->getOption('depth') !== null) {
74 $io->warning('Parameter "depth" is deprecated and has no effect anymore.');
75 }
76
77 $forceFetch = $input->getOption('force-fetch');
78 assert(is_bool($forceFetch));
79
80 if (stripos($sql, 'select') === 0 || $forceFetch) {
81 $this->runQuery($io, $conn, $sql);
82 } else {
83 $this->runStatement($io, $conn, $sql);
84 }
85
86 return 0;
87 }
88
89 private function getConnection(InputInterface $input): Connection
90 {
91 $connectionName = $input->getOption('connection');
92 assert(is_string($connectionName) || $connectionName === null);
93
94 if ($connectionName !== null) {
95 return $this->connectionProvider->getConnection($connectionName);
96 }
97
98 return $this->connectionProvider->getDefaultConnection();
99 }
100
101 /** @throws Exception */
102 private function runQuery(SymfonyStyle $io, Connection $conn, string $sql): void
103 {
104 $resultSet = $conn->fetchAllAssociative($sql);
105 if ($resultSet === []) {
106 $io->success('The query yielded an empty result set.');
107
108 return;
109 }
110
111 $io->table(array_keys($resultSet[0]), $resultSet);
112 }
113
114 /** @throws Exception */
115 private function runStatement(SymfonyStyle $io, Connection $conn, string $sql): void
116 {
117 $io->success(sprintf('%d rows affected.', $conn->executeStatement($sql)));
118 }
119}
diff --git a/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php b/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php
new file mode 100644
index 0000000..049d658
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Tools/Console/ConnectionNotFound.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Tools\Console;
6
7use OutOfBoundsException;
8
9final class ConnectionNotFound extends OutOfBoundsException
10{
11}
diff --git a/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider.php b/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider.php
new file mode 100644
index 0000000..0ae28c5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider.php
@@ -0,0 +1,15 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Tools\Console;
6
7use Doctrine\DBAL\Connection;
8
9interface ConnectionProvider
10{
11 public function getDefaultConnection(): Connection;
12
13 /** @throws ConnectionNotFound in case a connection with the given name does not exist. */
14 public function getConnection(string $name): Connection;
15}
diff --git a/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider/SingleConnectionProvider.php b/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider/SingleConnectionProvider.php
new file mode 100644
index 0000000..12b40c0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Tools/Console/ConnectionProvider/SingleConnectionProvider.php
@@ -0,0 +1,34 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Tools\Console\ConnectionProvider;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Tools\Console\ConnectionNotFound;
9use Doctrine\DBAL\Tools\Console\ConnectionProvider;
10
11use function sprintf;
12
13class SingleConnectionProvider implements ConnectionProvider
14{
15 public function __construct(
16 private readonly Connection $connection,
17 private readonly string $defaultConnectionName = 'default',
18 ) {
19 }
20
21 public function getDefaultConnection(): Connection
22 {
23 return $this->connection;
24 }
25
26 public function getConnection(string $name): Connection
27 {
28 if ($name !== $this->defaultConnectionName) {
29 throw new ConnectionNotFound(sprintf('Connection with name "%s" does not exist.', $name));
30 }
31
32 return $this->connection;
33 }
34}
diff --git a/vendor/doctrine/dbal/src/Tools/DsnParser.php b/vendor/doctrine/dbal/src/Tools/DsnParser.php
new file mode 100644
index 0000000..61edc6a
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Tools/DsnParser.php
@@ -0,0 +1,217 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Tools;
6
7use Doctrine\DBAL\Driver;
8use Doctrine\DBAL\DriverManager;
9use Doctrine\DBAL\Exception\MalformedDsnException;
10use SensitiveParameter;
11
12use function array_merge;
13use function assert;
14use function is_a;
15use function is_string;
16use function parse_str;
17use function parse_url;
18use function preg_replace;
19use function rawurldecode;
20use function str_replace;
21use function strpos;
22use function substr;
23
24/** @psalm-import-type Params from DriverManager */
25final class DsnParser
26{
27 /** @param array<string, string|class-string<Driver>> $schemeMapping An array used to map DSN schemes to DBAL drivers */
28 public function __construct(
29 private readonly array $schemeMapping = [],
30 ) {
31 }
32
33 /**
34 * @psalm-return Params
35 *
36 * @throws MalformedDsnException
37 */
38 public function parse(
39 #[SensitiveParameter]
40 string $dsn,
41 ): array {
42 // (pdo-)?sqlite3?:///... => (pdo-)?sqlite3?://localhost/... or else the URL will be invalid
43 $url = preg_replace('#^((?:pdo-)?sqlite3?):///#', '$1://localhost/', $dsn);
44 assert($url !== null);
45
46 $url = parse_url($url);
47
48 if ($url === false) {
49 throw MalformedDsnException::new();
50 }
51
52 foreach ($url as $param => $value) {
53 if (! is_string($value)) {
54 continue;
55 }
56
57 $url[$param] = rawurldecode($value);
58 }
59
60 $params = [];
61
62 if (isset($url['scheme'])) {
63 $params['driver'] = $this->parseDatabaseUrlScheme($url['scheme']);
64 }
65
66 if (isset($url['host'])) {
67 $params['host'] = $url['host'];
68 }
69
70 if (isset($url['port'])) {
71 $params['port'] = $url['port'];
72 }
73
74 if (isset($url['user'])) {
75 $params['user'] = $url['user'];
76 }
77
78 if (isset($url['pass'])) {
79 $params['password'] = $url['pass'];
80 }
81
82 if (isset($params['driver']) && is_a($params['driver'], Driver::class, true)) {
83 $params['driverClass'] = $params['driver'];
84 unset($params['driver']);
85 }
86
87 $params = $this->parseDatabaseUrlPath($url, $params);
88 $params = $this->parseDatabaseUrlQuery($url, $params);
89
90 return $params;
91 }
92
93 /**
94 * Parses the given connection URL and resolves the given connection parameters.
95 *
96 * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
97 * via {@see parseDatabaseUrlScheme}.
98 *
99 * @see parseDatabaseUrlScheme
100 *
101 * @param mixed[] $url The URL parts to evaluate.
102 * @param mixed[] $params The connection parameters to resolve.
103 *
104 * @return mixed[] The resolved connection parameters.
105 */
106 private function parseDatabaseUrlPath(array $url, array $params): array
107 {
108 if (! isset($url['path'])) {
109 return $params;
110 }
111
112 $url['path'] = $this->normalizeDatabaseUrlPath($url['path']);
113
114 // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
115 // and therefore treat the path as a regular DBAL connection URL path.
116 if (! isset($params['driver'])) {
117 return $this->parseRegularDatabaseUrlPath($url, $params);
118 }
119
120 if (strpos($params['driver'], 'sqlite') !== false) {
121 return $this->parseSqliteDatabaseUrlPath($url, $params);
122 }
123
124 return $this->parseRegularDatabaseUrlPath($url, $params);
125 }
126
127 /**
128 * Normalizes the given connection URL path.
129 *
130 * @return string The normalized connection URL path
131 */
132 private function normalizeDatabaseUrlPath(string $urlPath): string
133 {
134 // Trim leading slash from URL path.
135 return substr($urlPath, 1);
136 }
137
138 /**
139 * Parses the query part of the given connection URL and resolves the given connection parameters.
140 *
141 * @param mixed[] $url The connection URL parts to evaluate.
142 * @param mixed[] $params The connection parameters to resolve.
143 *
144 * @return mixed[] The resolved connection parameters.
145 */
146 private function parseDatabaseUrlQuery(array $url, array $params): array
147 {
148 if (! isset($url['query'])) {
149 return $params;
150 }
151
152 $query = [];
153
154 parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
155
156 return array_merge($params, $query); // parse_str wipes existing array elements
157 }
158
159 /**
160 * Parses the given regular connection URL and resolves the given connection parameters.
161 *
162 * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}.
163 *
164 * @see normalizeDatabaseUrlPath
165 *
166 * @param mixed[] $url The regular connection URL parts to evaluate.
167 * @param mixed[] $params The connection parameters to resolve.
168 *
169 * @return mixed[] The resolved connection parameters.
170 */
171 private function parseRegularDatabaseUrlPath(array $url, array $params): array
172 {
173 $params['dbname'] = $url['path'];
174
175 return $params;
176 }
177
178 /**
179 * Parses the given SQLite connection URL and resolves the given connection parameters.
180 *
181 * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}.
182 *
183 * @see normalizeDatabaseUrlPath
184 *
185 * @param mixed[] $url The SQLite connection URL parts to evaluate.
186 * @param mixed[] $params The connection parameters to resolve.
187 *
188 * @return mixed[] The resolved connection parameters.
189 */
190 private function parseSqliteDatabaseUrlPath(array $url, array $params): array
191 {
192 if ($url['path'] === ':memory:') {
193 $params['memory'] = true;
194
195 return $params;
196 }
197
198 $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key
199
200 return $params;
201 }
202
203 /**
204 * Parses the scheme part from given connection URL and resolves the given connection parameters.
205 *
206 * @return string The resolved driver.
207 */
208 private function parseDatabaseUrlScheme(string $scheme): string
209 {
210 // URL schemes must not contain underscores, but dashes are ok
211 $driver = str_replace('-', '_', $scheme);
212
213 // If the driver is an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql").
214 // Otherwise, let checkParams decide later if the driver exists.
215 return $this->schemeMapping[$driver] ?? $driver;
216 }
217}
diff --git a/vendor/doctrine/dbal/src/TransactionIsolationLevel.php b/vendor/doctrine/dbal/src/TransactionIsolationLevel.php
new file mode 100644
index 0000000..2b094db
--- /dev/null
+++ b/vendor/doctrine/dbal/src/TransactionIsolationLevel.php
@@ -0,0 +1,13 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL;
6
7enum TransactionIsolationLevel
8{
9 case READ_UNCOMMITTED;
10 case READ_COMMITTED;
11 case REPEATABLE_READ;
12 case SERIALIZABLE;
13}
diff --git a/vendor/doctrine/dbal/src/Types/AsciiStringType.php b/vendor/doctrine/dbal/src/Types/AsciiStringType.php
new file mode 100644
index 0000000..cd928ee
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/AsciiStringType.php
@@ -0,0 +1,24 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10final class AsciiStringType extends StringType
11{
12 /**
13 * {@inheritDoc}
14 */
15 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
16 {
17 return $platform->getAsciiStringTypeDeclarationSQL($column);
18 }
19
20 public function getBindingType(): ParameterType
21 {
22 return ParameterType::ASCII;
23 }
24}
diff --git a/vendor/doctrine/dbal/src/Types/BigIntType.php b/vendor/doctrine/dbal/src/Types/BigIntType.php
new file mode 100644
index 0000000..0cb14c5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/BigIntType.php
@@ -0,0 +1,61 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10use function assert;
11use function is_int;
12use function is_string;
13
14use const PHP_INT_MAX;
15use const PHP_INT_MIN;
16
17/**
18 * Type that attempts to map a database BIGINT to a PHP int.
19 *
20 * If the presented value is outside of PHP's integer range, the value is returned as-is (usually a string).
21 */
22class BigIntType extends Type implements PhpIntegerMappingType
23{
24 /**
25 * {@inheritDoc}
26 */
27 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
28 {
29 return $platform->getBigIntTypeDeclarationSQL($column);
30 }
31
32 public function getBindingType(): ParameterType
33 {
34 return ParameterType::STRING;
35 }
36
37 /**
38 * @param T $value
39 *
40 * @return (T is null ? null : int|string)
41 *
42 * @template T
43 */
44 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): int|string|null
45 {
46 if ($value === null || is_int($value)) {
47 return $value;
48 }
49
50 if ($value > PHP_INT_MIN && $value < PHP_INT_MAX) {
51 return (int) $value;
52 }
53
54 assert(
55 is_string($value),
56 'DBAL assumes values outside of the integer range to be returned as string by the database driver.',
57 );
58
59 return $value;
60 }
61}
diff --git a/vendor/doctrine/dbal/src/Types/BinaryType.php b/vendor/doctrine/dbal/src/Types/BinaryType.php
new file mode 100644
index 0000000..d400dd5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/BinaryType.php
@@ -0,0 +1,49 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
10
11use function is_resource;
12use function is_string;
13use function stream_get_contents;
14
15/**
16 * Type that maps ab SQL BINARY/VARBINARY to a PHP resource stream.
17 */
18class BinaryType extends Type
19{
20 /**
21 * {@inheritDoc}
22 */
23 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
24 {
25 return $platform->getBinaryTypeDeclarationSQL($column);
26 }
27
28 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?string
29 {
30 if ($value === null) {
31 return null;
32 }
33
34 if (is_resource($value)) {
35 $value = stream_get_contents($value);
36 }
37
38 if (! is_string($value)) {
39 throw ValueNotConvertible::new($value, Types::BINARY);
40 }
41
42 return $value;
43 }
44
45 public function getBindingType(): ParameterType
46 {
47 return ParameterType::BINARY;
48 }
49}
diff --git a/vendor/doctrine/dbal/src/Types/BlobType.php b/vendor/doctrine/dbal/src/Types/BlobType.php
new file mode 100644
index 0000000..c5415bc
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/BlobType.php
@@ -0,0 +1,56 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
10
11use function assert;
12use function fopen;
13use function fseek;
14use function fwrite;
15use function is_resource;
16use function is_string;
17
18/**
19 * Type that maps an SQL BLOB to a PHP resource stream.
20 */
21class BlobType extends Type
22{
23 /**
24 * {@inheritDoc}
25 */
26 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
27 {
28 return $platform->getBlobTypeDeclarationSQL($column);
29 }
30
31 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
32 {
33 if ($value === null) {
34 return null;
35 }
36
37 if (is_string($value)) {
38 $fp = fopen('php://temp', 'rb+');
39 assert(is_resource($fp));
40 fwrite($fp, $value);
41 fseek($fp, 0);
42 $value = $fp;
43 }
44
45 if (! is_resource($value)) {
46 throw ValueNotConvertible::new($value, Types::BLOB);
47 }
48
49 return $value;
50 }
51
52 public function getBindingType(): ParameterType
53 {
54 return ParameterType::LARGE_OBJECT;
55 }
56}
diff --git a/vendor/doctrine/dbal/src/Types/BooleanType.php b/vendor/doctrine/dbal/src/Types/BooleanType.php
new file mode 100644
index 0000000..e837e58
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/BooleanType.php
@@ -0,0 +1,44 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10/**
11 * Type that maps an SQL boolean to a PHP boolean.
12 */
13class BooleanType extends Type
14{
15 /**
16 * {@inheritDoc}
17 */
18 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
19 {
20 return $platform->getBooleanTypeDeclarationSQL($column);
21 }
22
23 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
24 {
25 return $platform->convertBooleansToDatabaseValue($value);
26 }
27
28 /**
29 * @param T $value
30 *
31 * @return (T is null ? null : bool)
32 *
33 * @template T
34 */
35 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?bool
36 {
37 return $platform->convertFromBoolean($value);
38 }
39
40 public function getBindingType(): ParameterType
41 {
42 return ParameterType::BOOLEAN;
43 }
44}
diff --git a/vendor/doctrine/dbal/src/Types/ConversionException.php b/vendor/doctrine/dbal/src/Types/ConversionException.php
new file mode 100644
index 0000000..b34c77c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/ConversionException.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Exception;
8
9/**
10 * Conversion Exception is thrown when the database to PHP conversion fails.
11 *
12 * @psalm-immutable
13 */
14class ConversionException extends \Exception implements Exception
15{
16}
diff --git a/vendor/doctrine/dbal/src/Types/DateImmutableType.php b/vendor/doctrine/dbal/src/Types/DateImmutableType.php
new file mode 100644
index 0000000..732efcd
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateImmutableType.php
@@ -0,0 +1,74 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTimeImmutable;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * Immutable type of {@see DateType}.
14 */
15class DateImmutableType extends Type implements PhpDateMappingType
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getDateTypeDeclarationSQL($column);
23 }
24
25 /**
26 * @param T $value
27 *
28 * @return (T is null ? null : string)
29 *
30 * @template T
31 */
32 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
33 {
34 if ($value === null) {
35 return $value;
36 }
37
38 if ($value instanceof DateTimeImmutable) {
39 return $value->format($platform->getDateFormatString());
40 }
41
42 throw InvalidType::new(
43 $value,
44 static::class,
45 ['null', DateTimeImmutable::class],
46 );
47 }
48
49 /**
50 * @param T $value
51 *
52 * @return (T is null ? null : DateTimeImmutable)
53 *
54 * @template T
55 */
56 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable
57 {
58 if ($value === null || $value instanceof DateTimeImmutable) {
59 return $value;
60 }
61
62 $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getDateFormatString(), $value);
63
64 if ($dateTime === false) {
65 throw InvalidFormat::new(
66 $value,
67 static::class,
68 $platform->getDateFormatString(),
69 );
70 }
71
72 return $dateTime;
73 }
74}
diff --git a/vendor/doctrine/dbal/src/Types/DateIntervalType.php b/vendor/doctrine/dbal/src/Types/DateIntervalType.php
new file mode 100644
index 0000000..29842ae
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateIntervalType.php
@@ -0,0 +1,84 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateInterval;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11use Throwable;
12
13use function substr;
14
15/**
16 * Type that maps interval string to a PHP DateInterval Object.
17 */
18class DateIntervalType extends Type
19{
20 final public const FORMAT = '%RP%YY%MM%DDT%HH%IM%SS';
21
22 /**
23 * {@inheritDoc}
24 */
25 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
26 {
27 $column['length'] = 255;
28
29 return $platform->getStringTypeDeclarationSQL($column);
30 }
31
32 /**
33 * @param T $value
34 *
35 * @return (T is null ? null : string)
36 *
37 * @template T
38 */
39 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
40 {
41 if ($value === null) {
42 return null;
43 }
44
45 if ($value instanceof DateInterval) {
46 return $value->format(self::FORMAT);
47 }
48
49 throw InvalidType::new($value, static::class, ['null', DateInterval::class]);
50 }
51
52 /**
53 * @param T $value
54 *
55 * @return (T is null ? null : DateInterval)
56 *
57 * @template T
58 */
59 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateInterval
60 {
61 if ($value === null || $value instanceof DateInterval) {
62 return $value;
63 }
64
65 $negative = false;
66
67 if (isset($value[0]) && ($value[0] === '+' || $value[0] === '-')) {
68 $negative = $value[0] === '-';
69 $value = substr($value, 1);
70 }
71
72 try {
73 $interval = new DateInterval($value);
74
75 if ($negative) {
76 $interval->invert = 1;
77 }
78
79 return $interval;
80 } catch (Throwable $exception) {
81 throw InvalidFormat::new($value, static::class, self::FORMAT, $exception);
82 }
83 }
84}
diff --git a/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php b/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php
new file mode 100644
index 0000000..2d49d1d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateTimeImmutableType.php
@@ -0,0 +1,80 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTimeImmutable;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11use Exception;
12
13/**
14 * Immutable type of {@see DateTimeType}.
15 */
16class DateTimeImmutableType extends Type implements PhpDateTimeMappingType
17{
18 /**
19 * {@inheritDoc}
20 */
21 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
22 {
23 return $platform->getDateTimeTypeDeclarationSQL($column);
24 }
25
26 /**
27 * @param T $value
28 *
29 * @return (T is null ? null : string)
30 *
31 * @template T
32 */
33 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
34 {
35 if ($value === null) {
36 return $value;
37 }
38
39 if ($value instanceof DateTimeImmutable) {
40 return $value->format($platform->getDateTimeFormatString());
41 }
42
43 throw InvalidType::new(
44 $value,
45 static::class,
46 ['null', DateTimeImmutable::class],
47 );
48 }
49
50 /**
51 * @param T $value
52 *
53 * @return (T is null ? null : DateTimeImmutable)
54 *
55 * @template T
56 */
57 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable
58 {
59 if ($value === null || $value instanceof DateTimeImmutable) {
60 return $value;
61 }
62
63 $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeFormatString(), $value);
64
65 if ($dateTime !== false) {
66 return $dateTime;
67 }
68
69 try {
70 return new DateTimeImmutable($value);
71 } catch (Exception $e) {
72 throw InvalidFormat::new(
73 $value,
74 static::class,
75 $platform->getDateTimeFormatString(),
76 $e,
77 );
78 }
79 }
80}
diff --git a/vendor/doctrine/dbal/src/Types/DateTimeType.php b/vendor/doctrine/dbal/src/Types/DateTimeType.php
new file mode 100644
index 0000000..9fd0ba0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateTimeType.php
@@ -0,0 +1,80 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTime;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11use Exception;
12
13/**
14 * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object.
15 */
16class DateTimeType extends Type implements PhpDateTimeMappingType
17{
18 /**
19 * {@inheritDoc}
20 */
21 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
22 {
23 return $platform->getDateTimeTypeDeclarationSQL($column);
24 }
25
26 /**
27 * @param T $value
28 *
29 * @return (T is null ? null : string)
30 *
31 * @template T
32 */
33 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
34 {
35 if ($value === null) {
36 return $value;
37 }
38
39 if ($value instanceof DateTime) {
40 return $value->format($platform->getDateTimeFormatString());
41 }
42
43 throw InvalidType::new(
44 $value,
45 static::class,
46 ['null', DateTime::class],
47 );
48 }
49
50 /**
51 * @param T $value
52 *
53 * @return (T is null ? null : DateTime)
54 *
55 * @template T
56 */
57 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime
58 {
59 if ($value === null || $value instanceof DateTime) {
60 return $value;
61 }
62
63 $dateTime = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value);
64
65 if ($dateTime !== false) {
66 return $dateTime;
67 }
68
69 try {
70 return new DateTime($value);
71 } catch (Exception $e) {
72 throw InvalidFormat::new(
73 $value,
74 static::class,
75 $platform->getDateTimeFormatString(),
76 $e,
77 );
78 }
79 }
80}
diff --git a/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php b/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php
new file mode 100644
index 0000000..a7d662d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateTimeTzImmutableType.php
@@ -0,0 +1,74 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTimeImmutable;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * Immutable type of {@see DateTimeTzType}.
14 */
15class DateTimeTzImmutableType extends Type implements PhpDateTimeMappingType
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getDateTimeTzTypeDeclarationSQL($column);
23 }
24
25 /**
26 * @psalm-param T $value
27 *
28 * @return (T is null ? null : string)
29 *
30 * @template T
31 */
32 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
33 {
34 if ($value === null) {
35 return $value;
36 }
37
38 if ($value instanceof DateTimeImmutable) {
39 return $value->format($platform->getDateTimeTzFormatString());
40 }
41
42 throw InvalidType::new(
43 $value,
44 static::class,
45 ['null', DateTimeImmutable::class],
46 );
47 }
48
49 /**
50 * @param T $value
51 *
52 * @return (T is null ? null : DateTimeImmutable)
53 *
54 * @template T
55 */
56 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable
57 {
58 if ($value === null || $value instanceof DateTimeImmutable) {
59 return $value;
60 }
61
62 $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeTzFormatString(), $value);
63
64 if ($dateTime !== false) {
65 return $dateTime;
66 }
67
68 throw InvalidFormat::new(
69 $value,
70 static::class,
71 $platform->getDateTimeTzFormatString(),
72 );
73 }
74}
diff --git a/vendor/doctrine/dbal/src/Types/DateTimeTzType.php b/vendor/doctrine/dbal/src/Types/DateTimeTzType.php
new file mode 100644
index 0000000..98e6569
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateTimeTzType.php
@@ -0,0 +1,87 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTime;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * DateTime type accepting additional information about timezone offsets.
14 *
15 * Caution: Databases are not necessarily experts at storing timezone related
16 * data of dates. First, of not all the supported vendors support storing Timezone data, and some of
17 * them only use the offset to calculate the timestamp in its default timezone (usually UTC) and persist
18 * the value without the offset information. They even don't save the actual timezone names attached
19 * to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") but the current offset
20 * of them related to UTC. That means, depending on daylight saving times or not, you may get different
21 * offsets.
22 *
23 * This datatype makes only sense to use, if your application only needs to accept the timezone offset,
24 * not the actual timezone that uses transitions. Otherwise your DateTime instance
25 * attached with a timezone such as "Europe/Berlin" gets saved into the database with
26 * the offset and re-created from persistence with only the offset, not the original timezone
27 * attached.
28 */
29class DateTimeTzType extends Type implements PhpDateTimeMappingType
30{
31 /**
32 * {@inheritDoc}
33 */
34 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
35 {
36 return $platform->getDateTimeTzTypeDeclarationSQL($column);
37 }
38
39 /**
40 * @param T $value
41 *
42 * @return (T is null ? null : string)
43 *
44 * @template T
45 */
46 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
47 {
48 if ($value === null) {
49 return $value;
50 }
51
52 if ($value instanceof DateTime) {
53 return $value->format($platform->getDateTimeTzFormatString());
54 }
55
56 throw InvalidType::new(
57 $value,
58 static::class,
59 ['null', DateTime::class],
60 );
61 }
62
63 /**
64 * @param T $value
65 *
66 * @return (T is null ? null : DateTime)
67 *
68 * @template T
69 */
70 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime
71 {
72 if ($value === null || $value instanceof DateTime) {
73 return $value;
74 }
75
76 $dateTime = DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value);
77 if ($dateTime !== false) {
78 return $dateTime;
79 }
80
81 throw InvalidFormat::new(
82 $value,
83 static::class,
84 $platform->getDateTimeTzFormatString(),
85 );
86 }
87}
diff --git a/vendor/doctrine/dbal/src/Types/DateType.php b/vendor/doctrine/dbal/src/Types/DateType.php
new file mode 100644
index 0000000..6548c53
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DateType.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTime;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * Type that maps an SQL DATE to a PHP Date object.
14 */
15class DateType extends Type implements PhpDateMappingType
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getDateTypeDeclarationSQL($column);
23 }
24
25 /**
26 * @psalm-param T $value
27 *
28 * @return (T is null ? null : string)
29 *
30 * @template T
31 */
32 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
33 {
34 if ($value === null) {
35 return $value;
36 }
37
38 if ($value instanceof DateTime) {
39 return $value->format($platform->getDateFormatString());
40 }
41
42 throw InvalidType::new($value, static::class, ['null', DateTime::class]);
43 }
44
45 /**
46 * @param T $value
47 *
48 * @return (T is null ? null : DateTime)
49 *
50 * @template T
51 */
52 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime
53 {
54 if ($value === null || $value instanceof DateTime) {
55 return $value;
56 }
57
58 $dateTime = DateTime::createFromFormat('!' . $platform->getDateFormatString(), $value);
59 if ($dateTime !== false) {
60 return $dateTime;
61 }
62
63 throw InvalidFormat::new(
64 $value,
65 static::class,
66 $platform->getDateFormatString(),
67 );
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Types/DecimalType.php b/vendor/doctrine/dbal/src/Types/DecimalType.php
new file mode 100644
index 0000000..7301a33
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/DecimalType.php
@@ -0,0 +1,35 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function is_float;
10use function is_int;
11
12/**
13 * Type that maps an SQL DECIMAL to a PHP string.
14 */
15class DecimalType extends Type
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getDecimalTypeDeclarationSQL($column);
23 }
24
25 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?string
26 {
27 // Some drivers can represent decimals as float/int
28 // See also: https://github.com/doctrine/dbal/pull/4818
29 if (is_float($value) || is_int($value)) {
30 return (string) $value;
31 }
32
33 return $value;
34 }
35}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/InvalidFormat.php b/vendor/doctrine/dbal/src/Types/Exception/InvalidFormat.php
new file mode 100644
index 0000000..e7cd639
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/InvalidFormat.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\ConversionException;
8use Throwable;
9
10use function sprintf;
11use function strlen;
12use function substr;
13
14/**
15 * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement
16 * about the expected format.
17 *
18 * @psalm-immutable
19 */
20final class InvalidFormat extends ConversionException implements TypesException
21{
22 public static function new(
23 string $value,
24 string $toType,
25 ?string $expectedFormat,
26 ?Throwable $previous = null,
27 ): self {
28 return new self(
29 sprintf(
30 'Could not convert database value "%s" to Doctrine Type %s. Expected format "%s".',
31 strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value,
32 $toType,
33 $expectedFormat ?? '',
34 ),
35 0,
36 $previous,
37 );
38 }
39}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/InvalidType.php b/vendor/doctrine/dbal/src/Types/Exception/InvalidType.php
new file mode 100644
index 0000000..56aa877
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/InvalidType.php
@@ -0,0 +1,53 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\ConversionException;
8use Throwable;
9
10use function get_debug_type;
11use function implode;
12use function is_scalar;
13use function sprintf;
14use function var_export;
15
16/**
17 * Thrown when the PHP value passed to the converter was not of the expected type.
18 *
19 * @psalm-immutable
20 */
21final class InvalidType extends ConversionException implements TypesException
22{
23 /**
24 * @param string[] $possibleTypes
25 *
26 * @todo split into two methods
27 * @todo sanitize value
28 */
29 public static function new(
30 mixed $value,
31 string $toType,
32 array $possibleTypes,
33 ?Throwable $previous = null,
34 ): self {
35 if (is_scalar($value) || $value === null) {
36 $message = sprintf(
37 'Could not convert PHP value %s to type %s. Expected one of the following types: %s.',
38 var_export($value, true),
39 $toType,
40 implode(', ', $possibleTypes),
41 );
42 } else {
43 $message = sprintf(
44 'Could not convert PHP value of type %s to type %s. Expected one of the following types: %s.',
45 get_debug_type($value),
46 $toType,
47 implode(', ', $possibleTypes),
48 );
49 }
50
51 return new self($message, 0, $previous);
52 }
53}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/SerializationFailed.php b/vendor/doctrine/dbal/src/Types/Exception/SerializationFailed.php
new file mode 100644
index 0000000..6c4de7c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/SerializationFailed.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\ConversionException;
8use Throwable;
9
10use function get_debug_type;
11use function sprintf;
12
13/** @psalm-immutable */
14final class SerializationFailed extends ConversionException implements TypesException
15{
16 public static function new(mixed $value, string $format, string $error, ?Throwable $previous = null): self
17 {
18 return new self(
19 sprintf(
20 'Could not convert PHP type "%s" to "%s". An error was triggered by the serialization: %s',
21 get_debug_type($value),
22 $format,
23 $error,
24 ),
25 0,
26 $previous,
27 );
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/TypeAlreadyRegistered.php b/vendor/doctrine/dbal/src/Types/Exception/TypeAlreadyRegistered.php
new file mode 100644
index 0000000..93ae812
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/TypeAlreadyRegistered.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\Type;
8use Exception;
9
10use function get_debug_type;
11use function spl_object_hash;
12use function sprintf;
13
14/** @psalm-immutable */
15final class TypeAlreadyRegistered extends Exception implements TypesException
16{
17 public static function new(Type $type): self
18 {
19 return new self(sprintf(
20 'Type of the class %s@%s is already registered.',
21 get_debug_type($type),
22 spl_object_hash($type),
23 ));
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/TypeNotFound.php b/vendor/doctrine/dbal/src/Types/Exception/TypeNotFound.php
new file mode 100644
index 0000000..7c29a87
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/TypeNotFound.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class TypeNotFound extends Exception implements TypesException
13{
14 public static function new(string $name): self
15 {
16 return new self(sprintf('Type to be overwritten "%s" does not exist.', $name));
17 }
18}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/TypeNotRegistered.php b/vendor/doctrine/dbal/src/Types/Exception/TypeNotRegistered.php
new file mode 100644
index 0000000..130c624
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/TypeNotRegistered.php
@@ -0,0 +1,25 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\Type;
8use Exception;
9
10use function get_debug_type;
11use function spl_object_hash;
12use function sprintf;
13
14/** @psalm-immutable */
15final class TypeNotRegistered extends Exception implements TypesException
16{
17 public static function new(Type $type): self
18 {
19 return new self(sprintf(
20 'Type of the class %s@%s is not registered.',
21 get_debug_type($type),
22 spl_object_hash($type),
23 ));
24 }
25}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/TypesAlreadyExists.php b/vendor/doctrine/dbal/src/Types/Exception/TypesAlreadyExists.php
new file mode 100644
index 0000000..21b022b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/TypesAlreadyExists.php
@@ -0,0 +1,18 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class TypesAlreadyExists extends Exception implements TypesException
13{
14 public static function new(string $name): self
15 {
16 return new self(sprintf('Type "%s" already exists.', $name));
17 }
18}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/TypesException.php b/vendor/doctrine/dbal/src/Types/Exception/TypesException.php
new file mode 100644
index 0000000..a98830b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/TypesException.php
@@ -0,0 +1,11 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Exception;
8
9interface TypesException extends Exception
10{
11}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/UnknownColumnType.php b/vendor/doctrine/dbal/src/Types/Exception/UnknownColumnType.php
new file mode 100644
index 0000000..6ac30a5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/UnknownColumnType.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final class UnknownColumnType extends Exception implements TypesException
13{
14 public static function new(string $name): self
15 {
16 return new self(
17 sprintf(
18 'Unknown column type "%s" requested. Any Doctrine type that you use has '
19 . 'to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the '
20 . 'known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database '
21 . 'introspection then you might have forgotten to register all database types for a Doctrine Type. '
22 . 'Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement '
23 . 'Type#getMappedDatabaseTypes(). If the type name is empty you might '
24 . 'have a problem with the cache or forgot some mapping information.',
25 $name,
26 ),
27 );
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Types/Exception/ValueNotConvertible.php b/vendor/doctrine/dbal/src/Types/Exception/ValueNotConvertible.php
new file mode 100644
index 0000000..ad39a2b
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Exception/ValueNotConvertible.php
@@ -0,0 +1,44 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types\Exception;
6
7use Doctrine\DBAL\Types\ConversionException;
8use Throwable;
9
10use function is_string;
11use function sprintf;
12use function strlen;
13use function substr;
14
15/**
16 * Thrown when a Database to Doctrine Type Conversion fails.
17 *
18 * @psalm-immutable
19 */
20final class ValueNotConvertible extends ConversionException implements TypesException
21{
22 public static function new(
23 mixed $value,
24 string $toType,
25 ?string $message = null,
26 ?Throwable $previous = null,
27 ): self {
28 if ($message !== null) {
29 $message = sprintf(
30 'Could not convert database value to "%s" as an error was triggered by the unserialization: %s',
31 $toType,
32 $message,
33 );
34 } else {
35 $message = sprintf(
36 'Could not convert database value "%s" to Doctrine Type "%s".',
37 is_string($value) && strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value,
38 $toType,
39 );
40 }
41
42 return new self($message, 0, $previous);
43 }
44}
diff --git a/vendor/doctrine/dbal/src/Types/FloatType.php b/vendor/doctrine/dbal/src/Types/FloatType.php
new file mode 100644
index 0000000..40e11f9
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/FloatType.php
@@ -0,0 +1,30 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9class FloatType extends Type
10{
11 /**
12 * {@inheritDoc}
13 */
14 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
15 {
16 return $platform->getFloatDeclarationSQL($column);
17 }
18
19 /**
20 * @param T $value
21 *
22 * @return (T is null ? null : float)
23 *
24 * @template T
25 */
26 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?float
27 {
28 return $value === null ? null : (float) $value;
29 }
30}
diff --git a/vendor/doctrine/dbal/src/Types/GuidType.php b/vendor/doctrine/dbal/src/Types/GuidType.php
new file mode 100644
index 0000000..cc7cc5f
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/GuidType.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9/**
10 * Represents a GUID/UUID datatype (both are actually synonyms) in the database.
11 */
12class GuidType extends StringType
13{
14 /**
15 * {@inheritDoc}
16 */
17 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
18 {
19 return $platform->getGuidTypeDeclarationSQL($column);
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Types/IntegerType.php b/vendor/doctrine/dbal/src/Types/IntegerType.php
new file mode 100644
index 0000000..8a711c0
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/IntegerType.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10/**
11 * Type that maps an SQL INT to a PHP integer.
12 */
13class IntegerType extends Type implements PhpIntegerMappingType
14{
15 /**
16 * {@inheritDoc}
17 */
18 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
19 {
20 return $platform->getIntegerTypeDeclarationSQL($column);
21 }
22
23 /**
24 * @param T $value
25 *
26 * @return (T is null ? null : int)
27 *
28 * @template T
29 */
30 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?int
31 {
32 return $value === null ? null : (int) $value;
33 }
34
35 public function getBindingType(): ParameterType
36 {
37 return ParameterType::INTEGER;
38 }
39}
diff --git a/vendor/doctrine/dbal/src/Types/JsonType.php b/vendor/doctrine/dbal/src/Types/JsonType.php
new file mode 100644
index 0000000..04c3dce
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/JsonType.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Types\Exception\SerializationFailed;
9use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
10use JsonException;
11
12use function is_resource;
13use function json_decode;
14use function json_encode;
15use function stream_get_contents;
16
17use const JSON_PRESERVE_ZERO_FRACTION;
18use const JSON_THROW_ON_ERROR;
19
20/**
21 * Type generating json objects values
22 */
23class JsonType extends Type
24{
25 /**
26 * {@inheritDoc}
27 */
28 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
29 {
30 return $platform->getJsonTypeDeclarationSQL($column);
31 }
32
33 /**
34 * @param T $value
35 *
36 * @return (T is null ? null : string)
37 *
38 * @template T
39 */
40 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
41 {
42 if ($value === null) {
43 return null;
44 }
45
46 try {
47 return json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION);
48 } catch (JsonException $e) {
49 throw SerializationFailed::new($value, 'json', $e->getMessage(), $e);
50 }
51 }
52
53 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
54 {
55 if ($value === null || $value === '') {
56 return null;
57 }
58
59 if (is_resource($value)) {
60 $value = stream_get_contents($value);
61 }
62
63 try {
64 return json_decode($value, true, 512, JSON_THROW_ON_ERROR);
65 } catch (JsonException $e) {
66 throw ValueNotConvertible::new($value, 'json', $e->getMessage(), $e);
67 }
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Types/PhpDateMappingType.php b/vendor/doctrine/dbal/src/Types/PhpDateMappingType.php
new file mode 100644
index 0000000..200f277
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/PhpDateMappingType.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7/**
8 * Implementations should map a database type to a PHP DateTimeInterface instance.
9 *
10 * @internal
11 */
12interface PhpDateMappingType
13{
14}
diff --git a/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php b/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php
new file mode 100644
index 0000000..501bc37
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/PhpDateTimeMappingType.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7/**
8 * Implementations should map a database type to a PHP DateTimeInterface instance.
9 *
10 * @internal
11 */
12interface PhpDateTimeMappingType
13{
14}
diff --git a/vendor/doctrine/dbal/src/Types/PhpIntegerMappingType.php b/vendor/doctrine/dbal/src/Types/PhpIntegerMappingType.php
new file mode 100644
index 0000000..d2b2084
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/PhpIntegerMappingType.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7/**
8 * Implementations should map a database type to a PHP integer.
9 *
10 * @internal
11 */
12interface PhpIntegerMappingType
13{
14}
diff --git a/vendor/doctrine/dbal/src/Types/PhpTimeMappingType.php b/vendor/doctrine/dbal/src/Types/PhpTimeMappingType.php
new file mode 100644
index 0000000..2cffc30
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/PhpTimeMappingType.php
@@ -0,0 +1,14 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7/**
8 * Implementations should map a database type to a PHP DateTimeInterface instance.
9 *
10 * @internal
11 */
12interface PhpTimeMappingType
13{
14}
diff --git a/vendor/doctrine/dbal/src/Types/SimpleArrayType.php b/vendor/doctrine/dbal/src/Types/SimpleArrayType.php
new file mode 100644
index 0000000..c8a802d
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/SimpleArrayType.php
@@ -0,0 +1,51 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function count;
10use function explode;
11use function implode;
12use function is_array;
13use function is_resource;
14use function stream_get_contents;
15
16/**
17 * Array Type which can be used for simple values.
18 *
19 * Only use this type if you are sure that your values cannot contain a ",".
20 */
21class SimpleArrayType extends Type
22{
23 /**
24 * {@inheritDoc}
25 */
26 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
27 {
28 return $platform->getClobTypeDeclarationSQL($column);
29 }
30
31 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
32 {
33 if (! is_array($value) || count($value) === 0) {
34 return null;
35 }
36
37 return implode(',', $value);
38 }
39
40 /** @return list<string> */
41 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): array
42 {
43 if ($value === null) {
44 return [];
45 }
46
47 $value = is_resource($value) ? stream_get_contents($value) : $value;
48
49 return explode(',', $value);
50 }
51}
diff --git a/vendor/doctrine/dbal/src/Types/SmallIntType.php b/vendor/doctrine/dbal/src/Types/SmallIntType.php
new file mode 100644
index 0000000..ba0899c
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/SmallIntType.php
@@ -0,0 +1,39 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\ParameterType;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9
10/**
11 * Type that maps a database SMALLINT to a PHP integer.
12 */
13class SmallIntType extends Type implements PhpIntegerMappingType
14{
15 /**
16 * {@inheritDoc}
17 */
18 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
19 {
20 return $platform->getSmallIntTypeDeclarationSQL($column);
21 }
22
23 /**
24 * @param T $value
25 *
26 * @return (T is null ? null : int)
27 *
28 * @template T
29 */
30 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?int
31 {
32 return $value === null ? null : (int) $value;
33 }
34
35 public function getBindingType(): ParameterType
36 {
37 return ParameterType::INTEGER;
38 }
39}
diff --git a/vendor/doctrine/dbal/src/Types/StringType.php b/vendor/doctrine/dbal/src/Types/StringType.php
new file mode 100644
index 0000000..d3f92aa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/StringType.php
@@ -0,0 +1,21 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9/**
10 * Type that maps an SQL VARCHAR to a PHP string.
11 */
12class StringType extends Type
13{
14 /**
15 * {@inheritDoc}
16 */
17 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
18 {
19 return $platform->getStringTypeDeclarationSQL($column);
20 }
21}
diff --git a/vendor/doctrine/dbal/src/Types/TextType.php b/vendor/doctrine/dbal/src/Types/TextType.php
new file mode 100644
index 0000000..a682be5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/TextType.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8
9use function is_resource;
10use function stream_get_contents;
11
12/**
13 * Type that maps an SQL CLOB to a PHP string.
14 */
15class TextType extends Type
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getClobTypeDeclarationSQL($column);
23 }
24
25 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
26 {
27 return is_resource($value) ? stream_get_contents($value) : $value;
28 }
29}
diff --git a/vendor/doctrine/dbal/src/Types/TimeImmutableType.php b/vendor/doctrine/dbal/src/Types/TimeImmutableType.php
new file mode 100644
index 0000000..c1c24d8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/TimeImmutableType.php
@@ -0,0 +1,74 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTimeImmutable;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * Immutable type of {@see TimeType}.
14 */
15class TimeImmutableType extends Type implements PhpTimeMappingType
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getTimeTypeDeclarationSQL($column);
23 }
24
25 /**
26 * @param T $value
27 *
28 * @return (T is null ? null : string)
29 *
30 * @template T
31 */
32 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
33 {
34 if ($value === null) {
35 return $value;
36 }
37
38 if ($value instanceof DateTimeImmutable) {
39 return $value->format($platform->getTimeFormatString());
40 }
41
42 throw InvalidType::new(
43 $value,
44 static::class,
45 ['null', DateTimeImmutable::class],
46 );
47 }
48
49 /**
50 * @param T $value
51 *
52 * @return (T is null ? null : DateTimeImmutable)
53 *
54 * @template T
55 */
56 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable
57 {
58 if ($value === null || $value instanceof DateTimeImmutable) {
59 return $value;
60 }
61
62 $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getTimeFormatString(), $value);
63
64 if ($dateTime !== false) {
65 return $dateTime;
66 }
67
68 throw InvalidFormat::new(
69 $value,
70 static::class,
71 $platform->getTimeFormatString(),
72 );
73 }
74}
diff --git a/vendor/doctrine/dbal/src/Types/TimeType.php b/vendor/doctrine/dbal/src/Types/TimeType.php
new file mode 100644
index 0000000..0f96fd5
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/TimeType.php
@@ -0,0 +1,69 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTime;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidFormat;
10use Doctrine\DBAL\Types\Exception\InvalidType;
11
12/**
13 * Type that maps an SQL TIME to a PHP DateTime object.
14 */
15class TimeType extends Type implements PhpTimeMappingType
16{
17 /**
18 * {@inheritDoc}
19 */
20 public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
21 {
22 return $platform->getTimeTypeDeclarationSQL($column);
23 }
24
25 /**
26 * @param T $value
27 *
28 * @return (T is null ? null : string)
29 *
30 * @template T
31 */
32 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
33 {
34 if ($value === null) {
35 return $value;
36 }
37
38 if ($value instanceof DateTime) {
39 return $value->format($platform->getTimeFormatString());
40 }
41
42 throw InvalidType::new($value, static::class, ['null', DateTime::class]);
43 }
44
45 /**
46 * @param T $value
47 *
48 * @return (T is null ? null : DateTime)
49 *
50 * @template T
51 */
52 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime
53 {
54 if ($value === null || $value instanceof DateTime) {
55 return $value;
56 }
57
58 $dateTime = DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value);
59 if ($dateTime !== false) {
60 return $dateTime;
61 }
62
63 throw InvalidFormat::new(
64 $value,
65 static::class,
66 $platform->getTimeFormatString(),
67 );
68 }
69}
diff --git a/vendor/doctrine/dbal/src/Types/Type.php b/vendor/doctrine/dbal/src/Types/Type.php
new file mode 100644
index 0000000..9e058b1
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Type.php
@@ -0,0 +1,221 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\ParameterType;
9use Doctrine\DBAL\Platforms\AbstractPlatform;
10
11use function array_map;
12
13/**
14 * The base class for so-called Doctrine mapping types.
15 *
16 * A Type object is obtained by calling the static {@see getType()} method.
17 */
18abstract class Type
19{
20 /**
21 * The map of supported doctrine mapping types.
22 */
23 private const BUILTIN_TYPES_MAP = [
24 Types::ASCII_STRING => AsciiStringType::class,
25 Types::BIGINT => BigIntType::class,
26 Types::BINARY => BinaryType::class,
27 Types::BLOB => BlobType::class,
28 Types::BOOLEAN => BooleanType::class,
29 Types::DATE_MUTABLE => DateType::class,
30 Types::DATE_IMMUTABLE => DateImmutableType::class,
31 Types::DATEINTERVAL => DateIntervalType::class,
32 Types::DATETIME_MUTABLE => DateTimeType::class,
33 Types::DATETIME_IMMUTABLE => DateTimeImmutableType::class,
34 Types::DATETIMETZ_MUTABLE => DateTimeTzType::class,
35 Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class,
36 Types::DECIMAL => DecimalType::class,
37 Types::FLOAT => FloatType::class,
38 Types::GUID => GuidType::class,
39 Types::INTEGER => IntegerType::class,
40 Types::JSON => JsonType::class,
41 Types::SIMPLE_ARRAY => SimpleArrayType::class,
42 Types::SMALLINT => SmallIntType::class,
43 Types::STRING => StringType::class,
44 Types::TEXT => TextType::class,
45 Types::TIME_MUTABLE => TimeType::class,
46 Types::TIME_IMMUTABLE => TimeImmutableType::class,
47 ];
48
49 private static ?TypeRegistry $typeRegistry = null;
50
51 /** @internal Do not instantiate directly - use {@see Type::addType()} method instead. */
52 final public function __construct()
53 {
54 }
55
56 /**
57 * Converts a value from its PHP representation to its database representation
58 * of this type.
59 *
60 * @param mixed $value The value to convert.
61 * @param AbstractPlatform $platform The currently used database platform.
62 *
63 * @return mixed The database representation of the value.
64 *
65 * @throws ConversionException
66 */
67 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
68 {
69 return $value;
70 }
71
72 /**
73 * Converts a value from its database representation to its PHP representation
74 * of this type.
75 *
76 * @param mixed $value The value to convert.
77 * @param AbstractPlatform $platform The currently used database platform.
78 *
79 * @return mixed The PHP representation of the value.
80 *
81 * @throws ConversionException
82 */
83 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
84 {
85 return $value;
86 }
87
88 /**
89 * Gets the SQL declaration snippet for a column of this type.
90 *
91 * @param array<string, mixed> $column The column definition
92 * @param AbstractPlatform $platform The currently used database platform.
93 */
94 abstract public function getSQLDeclaration(array $column, AbstractPlatform $platform): string;
95
96 final public static function getTypeRegistry(): TypeRegistry
97 {
98 return self::$typeRegistry ??= self::createTypeRegistry();
99 }
100
101 private static function createTypeRegistry(): TypeRegistry
102 {
103 $instances = [];
104
105 foreach (self::BUILTIN_TYPES_MAP as $name => $class) {
106 $instances[$name] = new $class();
107 }
108
109 return new TypeRegistry($instances);
110 }
111
112 /**
113 * Factory method to create type instances.
114 *
115 * @param string $name The name of the type.
116 *
117 * @throws Exception
118 */
119 public static function getType(string $name): self
120 {
121 return self::getTypeRegistry()->get($name);
122 }
123
124 /**
125 * Finds a name for the given type.
126 *
127 * @throws Exception
128 */
129 public static function lookupName(self $type): string
130 {
131 return self::getTypeRegistry()->lookupName($type);
132 }
133
134 /**
135 * Adds a custom type to the type map.
136 *
137 * @param string $name The name of the type.
138 * @param class-string<Type> $className The class name of the custom type.
139 *
140 * @throws Exception
141 */
142 public static function addType(string $name, string $className): void
143 {
144 self::getTypeRegistry()->register($name, new $className());
145 }
146
147 /**
148 * Checks if exists support for a type.
149 *
150 * @param string $name The name of the type.
151 *
152 * @return bool TRUE if type is supported; FALSE otherwise.
153 */
154 public static function hasType(string $name): bool
155 {
156 return self::getTypeRegistry()->has($name);
157 }
158
159 /**
160 * Overrides an already defined type to use a different implementation.
161 *
162 * @param class-string<Type> $className
163 *
164 * @throws Exception
165 */
166 public static function overrideType(string $name, string $className): void
167 {
168 self::getTypeRegistry()->override($name, new $className());
169 }
170
171 /**
172 * Gets the (preferred) binding type for values of this type that
173 * can be used when binding parameters to prepared statements.
174 */
175 public function getBindingType(): ParameterType
176 {
177 return ParameterType::STRING;
178 }
179
180 /**
181 * Gets the types array map which holds all registered types and the corresponding
182 * type class
183 *
184 * @return array<string, string>
185 */
186 public static function getTypesMap(): array
187 {
188 return array_map(
189 static function (Type $type): string {
190 return $type::class;
191 },
192 self::getTypeRegistry()->getMap(),
193 );
194 }
195
196 /**
197 * Modifies the SQL expression (identifier, parameter) to convert to a database value.
198 */
199 public function convertToDatabaseValueSQL(string $sqlExpr, AbstractPlatform $platform): string
200 {
201 return $sqlExpr;
202 }
203
204 /**
205 * Modifies the SQL expression (identifier, parameter) to convert to a PHP value.
206 */
207 public function convertToPHPValueSQL(string $sqlExpr, AbstractPlatform $platform): string
208 {
209 return $sqlExpr;
210 }
211
212 /**
213 * Gets an array of database types that map to this Doctrine type.
214 *
215 * @return array<int, string>
216 */
217 public function getMappedDatabaseTypes(AbstractPlatform $platform): array
218 {
219 return [];
220 }
221}
diff --git a/vendor/doctrine/dbal/src/Types/TypeRegistry.php b/vendor/doctrine/dbal/src/Types/TypeRegistry.php
new file mode 100644
index 0000000..5ef36c3
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/TypeRegistry.php
@@ -0,0 +1,131 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Types\Exception\TypeAlreadyRegistered;
9use Doctrine\DBAL\Types\Exception\TypeNotFound;
10use Doctrine\DBAL\Types\Exception\TypeNotRegistered;
11use Doctrine\DBAL\Types\Exception\TypesAlreadyExists;
12use Doctrine\DBAL\Types\Exception\UnknownColumnType;
13
14use function spl_object_id;
15
16/**
17 * The type registry is responsible for holding a map of all known DBAL types.
18 */
19final class TypeRegistry
20{
21 /** @var array<string, Type> Map of type names and their corresponding flyweight objects. */
22 private array $instances;
23 /** @var array<int, string> */
24 private array $instancesReverseIndex;
25
26 /** @param array<string, Type> $instances */
27 public function __construct(array $instances = [])
28 {
29 $this->instances = [];
30 $this->instancesReverseIndex = [];
31 foreach ($instances as $name => $type) {
32 $this->register($name, $type);
33 }
34 }
35
36 /**
37 * Finds a type by the given name.
38 *
39 * @throws Exception
40 */
41 public function get(string $name): Type
42 {
43 $type = $this->instances[$name] ?? null;
44 if ($type === null) {
45 throw UnknownColumnType::new($name);
46 }
47
48 return $type;
49 }
50
51 /**
52 * Finds a name for the given type.
53 *
54 * @throws Exception
55 */
56 public function lookupName(Type $type): string
57 {
58 $name = $this->findTypeName($type);
59
60 if ($name === null) {
61 throw TypeNotRegistered::new($type);
62 }
63
64 return $name;
65 }
66
67 /**
68 * Checks if there is a type of the given name.
69 */
70 public function has(string $name): bool
71 {
72 return isset($this->instances[$name]);
73 }
74
75 /**
76 * Registers a custom type to the type map.
77 *
78 * @throws Exception
79 */
80 public function register(string $name, Type $type): void
81 {
82 if (isset($this->instances[$name])) {
83 throw TypesAlreadyExists::new($name);
84 }
85
86 if ($this->findTypeName($type) !== null) {
87 throw TypeAlreadyRegistered::new($type);
88 }
89
90 $this->instances[$name] = $type;
91 $this->instancesReverseIndex[spl_object_id($type)] = $name;
92 }
93
94 /**
95 * Overrides an already defined type to use a different implementation.
96 *
97 * @throws Exception
98 */
99 public function override(string $name, Type $type): void
100 {
101 $origType = $this->instances[$name] ?? null;
102 if ($origType === null) {
103 throw TypeNotFound::new($name);
104 }
105
106 if (($this->findTypeName($type) ?? $name) !== $name) {
107 throw TypeAlreadyRegistered::new($type);
108 }
109
110 unset($this->instancesReverseIndex[spl_object_id($origType)]);
111 $this->instances[$name] = $type;
112 $this->instancesReverseIndex[spl_object_id($type)] = $name;
113 }
114
115 /**
116 * Gets the map of all registered types and their corresponding type instances.
117 *
118 * @internal
119 *
120 * @return array<string, Type>
121 */
122 public function getMap(): array
123 {
124 return $this->instances;
125 }
126
127 private function findTypeName(Type $type): ?string
128 {
129 return $this->instancesReverseIndex[spl_object_id($type)] ?? null;
130 }
131}
diff --git a/vendor/doctrine/dbal/src/Types/Types.php b/vendor/doctrine/dbal/src/Types/Types.php
new file mode 100644
index 0000000..07cf1ff
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/Types.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7/**
8 * Default built-in types provided by Doctrine DBAL.
9 */
10final class Types
11{
12 public const ASCII_STRING = 'ascii_string';
13 public const BIGINT = 'bigint';
14 public const BINARY = 'binary';
15 public const BLOB = 'blob';
16 public const BOOLEAN = 'boolean';
17 public const DATE_MUTABLE = 'date';
18 public const DATE_IMMUTABLE = 'date_immutable';
19 public const DATEINTERVAL = 'dateinterval';
20 public const DATETIME_MUTABLE = 'datetime';
21 public const DATETIME_IMMUTABLE = 'datetime_immutable';
22 public const DATETIMETZ_MUTABLE = 'datetimetz';
23 public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable';
24 public const DECIMAL = 'decimal';
25 public const FLOAT = 'float';
26 public const GUID = 'guid';
27 public const INTEGER = 'integer';
28 public const JSON = 'json';
29 public const SIMPLE_ARRAY = 'simple_array';
30 public const SMALLINT = 'smallint';
31 public const STRING = 'string';
32 public const TEXT = 'text';
33 public const TIME_MUTABLE = 'time';
34 public const TIME_IMMUTABLE = 'time_immutable';
35
36 /** @codeCoverageIgnore */
37 private function __construct()
38 {
39 }
40}
diff --git a/vendor/doctrine/dbal/src/Types/VarDateTimeImmutableType.php b/vendor/doctrine/dbal/src/Types/VarDateTimeImmutableType.php
new file mode 100644
index 0000000..67c2cd8
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/VarDateTimeImmutableType.php
@@ -0,0 +1,63 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTimeImmutable;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\InvalidType;
10use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
11use Exception;
12
13/**
14 * Immutable type of {@see VarDateTimeType}.
15 */
16class VarDateTimeImmutableType extends DateTimeImmutableType
17{
18 /**
19 * @param T $value
20 *
21 * @return (T is null ? null : string)
22 *
23 * @template T
24 */
25 public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
26 {
27 if ($value === null) {
28 return $value;
29 }
30
31 if ($value instanceof DateTimeImmutable) {
32 return $value->format($platform->getDateTimeFormatString());
33 }
34
35 throw InvalidType::new(
36 $value,
37 static::class,
38 ['null', DateTimeImmutable::class],
39 );
40 }
41
42 /**
43 * @param T $value
44 *
45 * @return (T is null ? null : DateTimeImmutable)
46 *
47 * @template T
48 */
49 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable
50 {
51 if ($value === null || $value instanceof DateTimeImmutable) {
52 return $value;
53 }
54
55 try {
56 $dateTime = new DateTimeImmutable($value);
57 } catch (Exception $e) {
58 throw ValueNotConvertible::new($value, DateTimeImmutable::class, $e->getMessage(), $e);
59 }
60
61 return $dateTime;
62 }
63}
diff --git a/vendor/doctrine/dbal/src/Types/VarDateTimeType.php b/vendor/doctrine/dbal/src/Types/VarDateTimeType.php
new file mode 100644
index 0000000..55dec49
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Types/VarDateTimeType.php
@@ -0,0 +1,42 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Types;
6
7use DateTime;
8use Doctrine\DBAL\Platforms\AbstractPlatform;
9use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
10use Exception;
11
12/**
13 * Variable DateTime Type using DateTime::__construct() instead of DateTime::createFromFormat().
14 *
15 * This type has performance implications as it runs twice as long as the regular
16 * {@see DateTimeType}, however in certain PostgreSQL configurations with
17 * TIMESTAMP(n) columns where n > 0 it is necessary to use this type.
18 */
19class VarDateTimeType extends DateTimeType
20{
21 /**
22 * @param T $value
23 *
24 * @return (T is null ? null : DateTime)
25 *
26 * @template T
27 */
28 public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime
29 {
30 if ($value === null || $value instanceof DateTime) {
31 return $value;
32 }
33
34 try {
35 $dateTime = new DateTime($value);
36 } catch (Exception $e) {
37 throw ValueNotConvertible::new($value, DateTime::class, $e->getMessage(), $e);
38 }
39
40 return $dateTime;
41 }
42}