From d5b447be17aefe00813a2ae3b1c2580ed6217252 Mon Sep 17 00:00:00 2001 From: Pierre Rineau Date: Tue, 21 Nov 2023 17:59:15 +0100 Subject: [PATCH] issue #49 - SQL Server support --- composer.json | 2 +- dev.sh | 13 ++ docker-compose.yaml | 12 ++ docker/php/Dockerfile | 25 ++- docs/content/anonymization/internals.md | 25 +++ docs/content/configuration.md | 4 + docs/content/database-vendors.md | 2 +- docs/content/getting-started/installation.md | 3 +- src/Anonymization/Anonymizator.php | 163 ++++++++++++++++-- .../Anonymizer/AbstractAnonymizer.php | 54 ++++-- .../Anonymizer/AbstractEnumAnonymizer.php | 5 +- .../AbstractMultipleColumnAnonymizer.php | 5 +- .../Anonymizer/Core/EmailAnonymizer.php | 12 +- .../Anonymizer/Core/FloatAnonymizer.php | 2 +- .../Anonymizer/Core/IntegerAnonymizer.php | 4 +- .../Anonymizer/Core/Md5Anonymizer.php | 5 +- .../Anonymizer/FrFR/PhoneNumberAnonymizer.php | 6 +- .../Anonymizer/AnonymizatorTest.php | 38 ++-- tests/FunctionalTestCase.php | 14 +- .../Anonymizer/Core/EmailAnonymizerTest.php | 10 +- .../FrFR/PhoneNumberAnonymizerTest.php | 2 +- 21 files changed, 330 insertions(+), 76 deletions(-) diff --git a/composer.json b/composer.json index ccdb1d0a..72e6349f 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "php": ">=8.1", "doctrine/doctrine-bundle": "^2.10.0", "doctrine/orm": "^2.15", - "makinacorpus/query-builder": "^0.1", + "makinacorpus/query-builder": "^0.1.9", "symfony/config": "^6.0", "symfony/console": "^6.0", "symfony/dependency-injection": "^6.0", diff --git a/dev.sh b/dev.sh index 7c46a824..300d0047 100755 --- a/dev.sh +++ b/dev.sh @@ -106,6 +106,19 @@ do_test() { -e DBAL_USER=postgres \ -e DATABASE_URL="postgresql://postgres:password@postgresql16:5432/test_db?serverVersion=16&charset=utf8" \ phpunit vendor/bin/phpunit $@ + + section_title "Running tests with SQL Server 2019" + docker compose -p db_tools_bundle_test exec \ + -e DBAL_DRIVER=pdo_sqlsrv \ + -e DBAL_DBNAME=test_db \ + -e DBAL_HOST=sqlsrv2019 \ + -e DBAL_PASSWORD=P@ssword123 \ + -e DBAL_PORT=1433 \ + -e DBAL_ROOT_PASSWORD=P@ssword123 \ + -e DBAL_ROOT_USER=sa \ + -e DBAL_USER=sa \ + -e DATABASE_URL="pdo-sqlsrv://sa:P%40ssword123@sqlsrv2019:1433/test_db?serverVersion=2019&charset=utf8&driverOptions[TrustServerCertificate]=true" \ + phpunit vendor/bin/phpunit $@ } # Display help diff --git a/docker-compose.yaml b/docker-compose.yaml index 9d41d805..b4882cd8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -54,6 +54,18 @@ services: - 9505:5432 networks: - db-tools-test + sqlsrv2019: + image: mcr.microsoft.com/mssql/server:2019-latest + restart: unless-stopped + environment: + ACCEPT_EULA: "y" + MSSQL_PID: Developer + MSSQL_SA_PASSWORD: P@ssword123 + SA_PASSWORD: P@ssword123 + ports: + - 9506:1433 + networks: + - db-tools-test networks: db-tools-test: \ No newline at end of file diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 631a304e..0f62855b 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -5,18 +5,29 @@ RUN apt-get update RUN apt-get install -yqq --no-install-recommends default-mysql-client acl iproute2 zip zlib1g-dev libzip-dev \ libxml2-dev libpng-dev libghc-curl-dev libldb-dev libldap2-dev gnupg2 libpq-dev -# PHP required extensions -RUN pecl install apcu -RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql -RUN docker-php-ext-install -j$(nproc) pgsql pdo_pgsql pdo mysqli pdo_mysql zip xml gd curl bcmath -RUN docker-php-ext-enable apcu pdo_pgsql pdo_mysql sodium -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - # Instaling postgresql-client-16 RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc| gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg && \ sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \ apt-get update && apt-get install -y postgresql-16 +# PHP required extensions +RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql +RUN docker-php-ext-install -j$(nproc) pgsql pdo_pgsql pdo mysqli pdo_mysql zip xml gd curl bcmath +RUN docker-php-ext-enable pdo_pgsql pdo_mysql sodium + +# SQL Server support +ENV ACCEPT_EULA=Y +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list +RUN apt-get update +RUN apt-get -y --no-install-recommends install msodbcsql18 unixodbc-dev +RUN pecl install sqlsrv +RUN pecl install pdo_sqlsrv +RUN docker-php-ext-enable sqlsrv pdo_sqlsrv + +# Cleanup. +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + COPY --from=composer /usr/bin/composer /usr/bin/composer WORKDIR /var/www diff --git a/docs/content/anonymization/internals.md b/docs/content/anonymization/internals.md index 9e4bb7da..a8ba6ccd 100644 --- a/docs/content/anonymization/internals.md +++ b/docs/content/anonymization/internals.md @@ -121,6 +121,31 @@ MySQL has many limitations: In order to workaround this, we chose to create an index over the anonymizer identifier sequence, which wouldn't be necessary otherwise. +### SQL Server specifics + +When using the same table as the updated one in the `FROM` clause, SQL Server +will shadow the updated table for the benefit of the one from the `FROM` +clause, hence the generated `UPDATE` not working. + +For working around this, SQL Server has its own variant which is: + +```sql +UPDATE + "client" +SET + "nom" = "sample_1"."value", + "civilite" = "sample_2"."value" + +FROM ( + SELECT * FROM "client" +) AS "_target_table" + +-- ... +``` + +Which is semantically equivalent and solve the table reference shadowing +issue. + ### Other variants Only PostgreSQL and MySQL are extensively tested for now, other SQL dialects diff --git a/docs/content/configuration.md b/docs/content/configuration.md index 8bf08bd8..a5c1405a 100644 --- a/docs/content/configuration.md +++ b/docs/content/configuration.md @@ -123,6 +123,10 @@ RUN apt-get update && \ ``` ::: +:::warning +Dump and restore is not supported yet for SQL Server. +::: + ## Anonymizer paths By default, the *DbToolsBundle* will look for *anonymizers* in 2 directories diff --git a/docs/content/database-vendors.md b/docs/content/database-vendors.md index eaef15b6..b8667453 100644 --- a/docs/content/database-vendors.md +++ b/docs/content/database-vendors.md @@ -24,6 +24,6 @@ Here is a matrix of the current state of support: | MySQL 5.7 or higher | | | | | SQLite | | | | | Oracle | | | | -| MS SQL Server | | | | +| SQL Server | | | | Working - Only unit-tested - Unsupported diff --git a/docs/content/getting-started/installation.md b/docs/content/getting-started/installation.md index 1cbd21c3..f549581b 100644 --- a/docs/content/getting-started/installation.md +++ b/docs/content/getting-started/installation.md @@ -11,9 +11,10 @@ you should not be lost if you are a regular Symfony developper. Currently supported database vendors: -- PostgreSQL 10 or higher +- PostgreSQL 10 or higher (previous versions from 9.5 are untested but should work) - MariaDB 11 or higher - MySQL 5.7 or higher +- SQL Server 2019 or higher (previous versions from 2015 are untested but should work) ::: info The bundle could also work with other database vendors. Check out the [database vendors support page](../database-vendors). diff --git a/src/Anonymization/Anonymizator.php b/src/Anonymization/Anonymizator.php index 8d26308f..dfa0477e 100644 --- a/src/Anonymization/Anonymizator.php +++ b/src/Anonymization/Anonymizator.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\SchemaDiff; use Doctrine\DBAL\Schema\TableDiff; @@ -16,6 +17,7 @@ use MakinaCorpus\DbToolsBundle\Anonymization\Config\AnonymizationConfig; use MakinaCorpus\DbToolsBundle\Anonymization\Config\AnonymizerConfig; use MakinaCorpus\DbToolsBundle\Helper\Format; +use MakinaCorpus\QueryBuilder\Bridge\AbstractBridge; use MakinaCorpus\QueryBuilder\Bridge\Doctrine\DoctrineQueryBuilder; use MakinaCorpus\QueryBuilder\Bridge\Doctrine\Query\DoctrineUpdate; @@ -233,21 +235,63 @@ protected function anonymizeTablePerColumn(string $table, array $anonymizers): \ protected function createUpdateQuery(string $table): DoctrineUpdate { - $update = $this->getQueryBuilder()->update($table); + $builder = $this->getQueryBuilder(); + $update = $builder->update($table); + $expr = $update->expression(); + + // // Add target table a second time into the FROM statement of the // UPDATE query, in order for anonymizers to be able to JOIN over // it. Otherwise, JOIN would not be possible for RDBMS that speak // standard SQL. - $expr = $update->expression(); - $update->join( - $table, - $expr->where()->isEqual( - $expr->column(AbstractAnonymizer::JOIN_ID, $table), - $expr->column(AbstractAnonymizer::JOIN_ID, AbstractAnonymizer::JOIN_TABLE), - ), - AbstractAnonymizer::JOIN_TABLE - ); + // + // This is the only and single hack regarding the UPDATE clause + // syntax, all RDBMS accept the following query: + // + // UPDATE foo + // SET val = bar.val + // FROM foo AS foo_2 + // JOIN bar ON bar.id = foo_2.id + // WHERE foo.id = foo_2.id + // + // Except for SQL Server, which cannot deambiguate the foo table + // reference in the WHERE clause, so we have to write it this + // way: + // + // UPDATE foo + // SET val = bar.val + // FROM ( + // SELECT * + // FROM foo + // ) AS foo_2 + // JOIN bar ON bar.id = foo_2.id + // WHERE foo.id = foo_2.id + // + // Which by the way also works with other RDBMS, but is an + // optimization fence for some, because the nested SELECT becomes + // a temporary table (especially for MySQL...). For those we need + // to keep the original query, even if semantically identical. + // + if (AbstractBridge::SERVER_SQLSERVER === $builder->getServerFlavor()) { + $update->join( + $builder->select($table), + $expr->where()->isEqual( + $expr->column(AbstractAnonymizer::JOIN_ID, $table), + $expr->column(AbstractAnonymizer::JOIN_ID, AbstractAnonymizer::JOIN_TABLE), + ), + AbstractAnonymizer::JOIN_TABLE + ); + } else { + $update->join( + $table, + $expr->where()->isEqual( + $expr->column(AbstractAnonymizer::JOIN_ID, $table), + $expr->column(AbstractAnonymizer::JOIN_ID, AbstractAnonymizer::JOIN_TABLE), + ), + AbstractAnonymizer::JOIN_TABLE + ); + } return $update; } @@ -299,6 +343,12 @@ public function addAnonymizerIdColumn(string $table): void return; } + if ($platform instanceof SQLServerPlatform) { + $this->addAnonymizerIdColumnSqlServer($table); + + return; + } + $schemaManager->alterSchema( new SchemaDiff( changedTables: [ @@ -464,6 +514,99 @@ protected function addAnonymizerIdColumnMySql(string $table) } } + /** + * Add second identity column for SQL Server. + * + * Pretty much like MySQL, SQL Server doesn't allow a second identity + * column, we need to manually create a sequence. It's much easier than + * MySQL thought. + */ + protected function addAnonymizerIdColumnSqlServer(string $table) + { + $platform = $this->connection->getDatabasePlatform(); + $queryBuilder = $this->getQueryBuilder(); + + $sequenceName = $platform->quoteIdentifier('_db_tools_seq_' . $table); + + $queryBuilder->executeStatement( + <<executeStatement( + <<executeStatement( + <<executeStatement( + <<executeStatement( + <<executeStatement( + <<createIndex($table, AbstractAnonymizer::JOIN_ID); + } + /** * Create an index. */ diff --git a/src/Anonymization/Anonymizer/AbstractAnonymizer.php b/src/Anonymization/Anonymizer/AbstractAnonymizer.php index eb7e840b..cc7f6773 100644 --- a/src/Anonymization/Anonymizer/AbstractAnonymizer.php +++ b/src/Anonymization/Anonymizer/AbstractAnonymizer.php @@ -5,14 +5,15 @@ namespace MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; use MakinaCorpus\DbToolsBundle\Attribute\AsAnonymizer; use MakinaCorpus\QueryBuilder\Expression; use MakinaCorpus\QueryBuilder\ExpressionFactory; -use MakinaCorpus\QueryBuilder\Query\Update; use MakinaCorpus\QueryBuilder\Bridge\Doctrine\DoctrineQueryBuilder; +use MakinaCorpus\QueryBuilder\Query\Update; abstract class AbstractAnonymizer { @@ -176,26 +177,49 @@ protected function generateTempTableName(): string return \uniqid(self::TEMP_TABLE_PREFIX); } - protected function getSetIfNotNullExpression(mixed $valueExpression, mixed $columnExpression = null): Expression + /** + * Return a random float between 0 and 1 expression. + * + * makina-corpus/query-builder already support this, but due to an odd SQL + * Server non standard behaviour, we reimplement it here: SQL Server RAND() + * return value will always be the same no matter how many time you call it + * inside a single SQL query. This means that when you update many rows + * using a RAND() based value, all rows will have the same value. + * + * We are going to get arround by injected a random value as seed for the + * RAND() function, because SQL Server allows this. Using NEWID() which + * generates an GUID might be slow, but we'll that at usage later. + * + * This function takes care of this, and will return an expression that + * works with all supported RDBMS. + */ + protected function getRandomExpression(): Expression { - if (null === $columnExpression) { - $columnExpression = ExpressionFactory::column($this->columnName, $this->tableName); + if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) { + return ExpressionFactory::raw('rand(abs(checksum(newid())))'); } - return ExpressionFactory::ifThen(ExpressionFactory::where()->isNotNull($columnExpression), $valueExpression); + return ExpressionFactory::random(); } /** - * Generate an SQL text pad left expression. + * For the same reason as getRandomExpression(). */ - protected function getSqlTextPadLeftExpression(mixed $textExpression, int $padSize, string $rawPadString): Expression + protected function getRandomIntExpression(int $max, int $min = 0): Expression + { + if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) { + return ExpressionFactory::raw( + 'FLOOR(? * (? - ? + 1) + ?)', + [$this->getRandomExpression(), ExpressionFactory::cast($max, 'int'), $min, $min] + ); + } + return ExpressionFactory::randomInt($max, $min); + } + + protected function getSetIfNotNullExpression(mixed $valueExpression, mixed $columnExpression = null): Expression { - return ExpressionFactory::raw( - 'lpad(?, ?, ?)', - [ - ExpressionFactory::cast($textExpression, 'text'), - $padSize, - $rawPadString, - ], - ); + if (null === $columnExpression) { + $columnExpression = ExpressionFactory::column($this->columnName, $this->tableName); + } + return ExpressionFactory::ifThen(ExpressionFactory::where()->isNotNull($columnExpression), $valueExpression); } } diff --git a/src/Anonymization/Anonymizer/AbstractEnumAnonymizer.php b/src/Anonymization/Anonymizer/AbstractEnumAnonymizer.php index 55190897..d11004c6 100644 --- a/src/Anonymization/Anonymizer/AbstractEnumAnonymizer.php +++ b/src/Anonymization/Anonymizer/AbstractEnumAnonymizer.php @@ -65,10 +65,9 @@ public function anonymize(Update $update): void $expr ->where() ->raw( - 'MOD(?, ?) + 1 = ?', + '? + 1 = ?', [ - $expr->column(self::JOIN_ID, self::JOIN_TABLE), - $sampleCount, + $expr->mod($expr->column(self::JOIN_ID, self::JOIN_TABLE), $sampleCount), $expr->column('rownum', $joinAlias), ] ) diff --git a/src/Anonymization/Anonymizer/AbstractMultipleColumnAnonymizer.php b/src/Anonymization/Anonymizer/AbstractMultipleColumnAnonymizer.php index b65f416a..385c8fe5 100644 --- a/src/Anonymization/Anonymizer/AbstractMultipleColumnAnonymizer.php +++ b/src/Anonymization/Anonymizer/AbstractMultipleColumnAnonymizer.php @@ -88,10 +88,9 @@ public function anonymize(Update $update): void $update->join( $join, $expr->where()->raw( - 'MOD(?, ?) + 1 = ?', + '? + 1 = ?', [ - $expr->column(self::JOIN_ID, self::JOIN_TABLE), - $sampleCount, + $expr->mod($expr->column(self::JOIN_ID, self::JOIN_TABLE), $sampleCount), $expr->column('rownum', $joinAlias), ] ), diff --git a/src/Anonymization/Anonymizer/Core/EmailAnonymizer.php b/src/Anonymization/Anonymizer/Core/EmailAnonymizer.php index 112f53ca..96d260aa 100644 --- a/src/Anonymization/Anonymizer/Core/EmailAnonymizer.php +++ b/src/Anonymization/Anonymizer/Core/EmailAnonymizer.php @@ -26,11 +26,13 @@ public function anonymize(Update $update): void $update->set( $this->columnName, - $expr->concat( - 'anon-', - $expr->functionCall('md5', $expr->column($this->columnName, $this->tableName)), - '@', - $this->options->get('domain', 'example.com'), + $this->getSetIfNotNullExpression( + $expr->concat( + 'anon-', + $expr->md5($expr->column($this->columnName, $this->tableName)), + '@', + $this->options->get('domain', 'example.com'), + ), ), ); } diff --git a/src/Anonymization/Anonymizer/Core/FloatAnonymizer.php b/src/Anonymization/Anonymizer/Core/FloatAnonymizer.php index 53241e57..d9e49e48 100644 --- a/src/Anonymization/Anonymizer/Core/FloatAnonymizer.php +++ b/src/Anonymization/Anonymizer/Core/FloatAnonymizer.php @@ -39,7 +39,7 @@ public function anonymize(Update $update): void $expr->raw( 'FLOOR(? * (? - ? + 1) + ?) / ?', [ - $expr->random(), + $this->getRandomExpression(), $expr->cast($max * $precision, 'int'), $min * $precision, $min * $precision, diff --git a/src/Anonymization/Anonymizer/Core/IntegerAnonymizer.php b/src/Anonymization/Anonymizer/Core/IntegerAnonymizer.php index 50637b20..7476b6d4 100644 --- a/src/Anonymization/Anonymizer/Core/IntegerAnonymizer.php +++ b/src/Anonymization/Anonymizer/Core/IntegerAnonymizer.php @@ -27,12 +27,10 @@ public function anonymize(Update $update): void throw new \InvalidArgumentException("You should provide 2 options (min and max) with this anonymizer"); } - $expr = $update->expression(); - $update->set( $this->columnName, $this->getSetIfNotNullExpression( - $expr->randomInt( + $this->getRandomIntExpression( $this->options->get('max'), $this->options->get('min'), ), diff --git a/src/Anonymization/Anonymizer/Core/Md5Anonymizer.php b/src/Anonymization/Anonymizer/Core/Md5Anonymizer.php index 7149e854..9f62aa10 100644 --- a/src/Anonymization/Anonymizer/Core/Md5Anonymizer.php +++ b/src/Anonymization/Anonymizer/Core/Md5Anonymizer.php @@ -24,10 +24,7 @@ public function anonymize(Update $update): void $update->set( $this->columnName, - $expr->functionCall( - 'md5', - $expr->column($this->columnName, $this->tableName) - ), + $expr->md5($expr->column($this->columnName, $this->tableName)), ); } } diff --git a/src/Anonymization/Anonymizer/FrFR/PhoneNumberAnonymizer.php b/src/Anonymization/Anonymizer/FrFR/PhoneNumberAnonymizer.php index a5091d1c..15d26824 100644 --- a/src/Anonymization/Anonymizer/FrFR/PhoneNumberAnonymizer.php +++ b/src/Anonymization/Anonymizer/FrFR/PhoneNumberAnonymizer.php @@ -52,11 +52,7 @@ public function anonymize(Update $update): void 'landline' => '026191', default => throw new \InvalidArgumentException('"mode" option can be "mobile", "landline"'), }, - $this->getSqlTextPadLeftExpression( - $expr->randomInt(9999), - 4, - '0' - ), + $expr->lpad($this->getRandomIntExpression(9999), 4, '0') ), ) ); diff --git a/tests/Functional/Anonymizer/AnonymizatorTest.php b/tests/Functional/Anonymizer/AnonymizatorTest.php index bcb8466b..9bdbbb05 100644 --- a/tests/Functional/Anonymizer/AnonymizatorTest.php +++ b/tests/Functional/Anonymizer/AnonymizatorTest.php @@ -101,35 +101,51 @@ public function testMultipleAnonymizersAtOnce(): void public function testSerial(): void { + // Some connectors will return string values for int. + $actual = \array_map( + fn (array $item) => ['id' => (int) $item['id']] + $item, + $this + ->getConnection() + ->executeQuery( + 'select id, value from table_test order by id' + ) + ->fetchAllAssociative(), + ); + self::assertSame( [ ['id' => 1, 'value' => 'foo'], ['id' => 2, 'value' => 'bar'], ['id' => 3, 'value' => 'baz'], ], + $actual, + ); + + $anonymizator = new Anonymizator($this->getConnection(), new AnonymizerRegistry(), new AnonymizationConfig()); + $anonymizator->addAnonymizerIdColumn('table_test'); + + // Some connectors will return string values for int. + $actual = \array_map( + fn (array $item) => [ + 'id' => (int) $item['id'], + 'value' => $item['value'], + AbstractAnonymizer::JOIN_ID => (int) $item[AbstractAnonymizer::JOIN_ID], + ], $this ->getConnection() ->executeQuery( - 'select id, value from table_test order by id' + 'select id, value, _db_tools_id from table_test order by id' ) - ->fetchAllAssociative() + ->fetchAllAssociative(), ); - $anonymizator = new Anonymizator($this->getConnection(), new AnonymizerRegistry(), new AnonymizationConfig()); - $anonymizator->addAnonymizerIdColumn('table_test'); - self::assertSame( [ ['id' => 1, 'value' => 'foo', AbstractAnonymizer::JOIN_ID => 1], ['id' => 2, 'value' => 'bar', AbstractAnonymizer::JOIN_ID => 2], ['id' => 3, 'value' => 'baz', AbstractAnonymizer::JOIN_ID => 3], ], - $this - ->getConnection() - ->executeQuery( - 'select id, value, _db_tools_id from table_test order by id' - ) - ->fetchAllAssociative() + $actual ); } } diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 232de96f..ed01b2ab 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -6,14 +6,15 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware\EnableForeignKeys; use Doctrine\DBAL\Driver\OCI8\Middleware\InitializeSession; -use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; -use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; use Doctrine\DBAL\Types\Type; use MakinaCorpus\QueryBuilder\Bridge\Doctrine\DoctrineQueryBuilder; @@ -86,7 +87,7 @@ protected function dropTableIfExist(string $tableName): void ->createSchemaManager() ->dropTable($tableName) ; - } catch (TableDoesNotExist|TableNotFoundException) { + } catch (TableDoesNotExist|TableNotFoundException|DatabaseObjectNotFoundException) { } } @@ -141,9 +142,16 @@ private function getConnectionParameters(): array self::markTestSkipped("Missing 'DBAL_DRIVER' environment variable."); } + $driverOptions = []; + if (\str_contains($driver, 'sqlsrv')) { + // https://stackoverflow.com/questions/71688125/odbc-driver-18-for-sql-serverssl-provider-error1416f086 + $driverOptions['TrustServerCertificate'] = "true"; + } + $params = \array_filter([ 'dbname' => \getenv('DBAL_DBNAME'), 'driver' => $driver, + 'driverOptions' => $driverOptions, 'host' => \getenv('DBAL_HOST'), 'password' => \getenv('DBAL_PASSWORD'), 'port' => \getenv('DBAL_PORT'), diff --git a/tests/Unit/Anonymization/Anonymizer/Core/EmailAnonymizerTest.php b/tests/Unit/Anonymization/Anonymizer/Core/EmailAnonymizerTest.php index 2f618f99..f08fcfdc 100644 --- a/tests/Unit/Anonymization/Anonymizer/Core/EmailAnonymizerTest.php +++ b/tests/Unit/Anonymization/Anonymizer/Core/EmailAnonymizerTest.php @@ -29,7 +29,10 @@ public function testAnonymizeWithDefaultDomain(): void <<