diff --git a/composer.json b/composer.json index e28247ee..80e74ddc 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0" }, "require-dev": { - "doctrine/dbal": "^2.13||^3.3", + "doctrine/dbal": "^2.13||^3.3||^4.0", "doctrine/doctrine-bundle": "^2.6", "friendsofphp/php-cs-fixer": "^2.19||^3.40", "masterminds/html5": "^2.8", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4b631601..d2ecc82a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -176,74 +176,34 @@ parameters: path: src/Tracing/Doctrine/DBAL/ConnectionConfigurator.php - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:errorInfo\\(\\) return type has no value type specified in iterable type array\\.$#" + message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionForV4\\:\\:errorInfo\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:exec\\(\\) has parameter \\$sql with no type specified\\.$#" + message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionForV4\\:\\:exec\\(\\) should return int\\|numeric\\-string but returns int\\|string\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:prepare\\(\\) has parameter \\$sql with no type specified\\.$#" + message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionForV4\\:\\:prepare\\(\\) has parameter \\$sql with no type specified\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:query\\(\\) has parameter \\$args with no type specified\\.$#" + message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionForV4\\:\\:query\\(\\) has parameter \\$args with no type specified\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - message: "#^Parameter \\#1 \\$sql of method Doctrine\\\\DBAL\\\\Driver\\\\Connection\\:\\:query\\(\\) expects string, string\\|null given\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - - message: "#^Parameter \\#2 \\$spanDescription of method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:traceFunction\\(\\) expects string, string\\|null given\\.$#" + message: "#^Parameter \\#2 \\$spanDescription of method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionForV4\\:\\:traceFunction\\(\\) expects string, string\\|null given\\.$#" count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php - - - - message: "#^Property Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:\\$spanTags is never read, only written\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php - - - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingServerInfoAwareDriverConnection\\:\\:errorInfo\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php - - - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingServerInfoAwareDriverConnection\\:\\:exec\\(\\) has parameter \\$sql with no type specified\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php - - - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingServerInfoAwareDriverConnection\\:\\:prepare\\(\\) has parameter \\$sql with no type specified\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php - - - - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingServerInfoAwareDriverConnection\\:\\:query\\(\\) has parameter \\$args with no type specified\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php - - - - message: "#^Parameter \\#1 \\$sql of method Doctrine\\\\DBAL\\\\Driver\\\\Connection\\:\\:query\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php - - - - message: "#^Parameter \\#2 \\$callback of method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\AbstractTracingStatement\\:\\:traceFunction\\(\\) expects callable\\(mixed \\.\\.\\.\\)\\: Doctrine\\\\DBAL\\\\Driver\\\\Result, array\\{Doctrine\\\\DBAL\\\\Driver\\\\Statement, 'execute'\\} given\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingStatementForV3.php - - - - message: "#^Parameter \\#4 \\$length of method Doctrine\\\\DBAL\\\\Driver\\\\Statement\\:\\:bindParam\\(\\) expects int\\|null, mixed given\\.$#" - count: 1 - path: src/Tracing/Doctrine/DBAL/TracingStatementForV3.php + path: src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php - message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\HttpClient\\\\AbstractTraceableHttpClient\\:\\:request\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" @@ -425,91 +385,26 @@ parameters: count: 1 path: tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php + - + message: "#^Property Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionFactoryV4Test\\:\\:\\$databasePlatform is never read, only written\\.$#" + count: 1 + path: tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV4Test.php + - message: "#^Trying to mock an undefined method errorCode\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Connection\\.$#" count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingDriverConnectionTest.php + path: tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php - message: "#^Trying to mock an undefined method errorInfo\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Connection\\.$#" count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingDriverConnectionTest.php + path: tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php - message: "#^Parameter \\#1 \\$hubOrConnectionFactory of class Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverMiddleware constructor expects Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionFactoryInterface\\|Sentry\\\\State\\\\HubInterface, null given\\.$#" count: 1 path: tests/Tracing/Doctrine/DBAL/TracingDriverMiddlewareTest.php - - - message: "#^Trying to mock an undefined method errorCode\\(\\) on class Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionInterface\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php - - - - message: "#^Trying to mock an undefined method errorInfo\\(\\) on class Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnectionInterface\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php - - - - message: "#^Trying to mock an undefined method requiresQueryForServerVersion\\(\\) on class Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Fixture\\\\ServerInfoAwareConnectionStub\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php - - - - message: "#^Parameter \\#2 \\$decoratedStatement of class Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingStatementForV2 constructor expects Doctrine\\\\DBAL\\\\Driver\\\\Statement, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingStatementForV2Stub given\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Parameter \\#4 \\$length of method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingStatementForV2\\:\\:bindParam\\(\\) expects int\\|null, mixed given\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method closeCursor\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method columnCount\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method errorCode\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method errorInfo\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method fetch\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method fetchAll\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method fetchColumn\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method rowCount\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - - - message: "#^Trying to mock an undefined method setFetchMode\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\\\Statement\\.$#" - count: 1 - path: tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php - - message: "#^Parameter \\#1 \\$responses of method Sentry\\\\SentryBundle\\\\Tracing\\\\HttpClient\\\\AbstractTraceableHttpClient\\:\\:stream\\(\\) expects iterable\\<\\(int\\|string\\), Symfony\\\\Contracts\\\\HttpClient\\\\ResponseInterface\\>\\|Symfony\\\\Contracts\\\\HttpClient\\\\ResponseInterface, stdClass given\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index 684f3989..d5a9c0a4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,14 +12,26 @@ parameters: - src/aliases.php - src/Tracing/Cache/TraceableCacheAdapterForV2.php - src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php - - src/Tracing/Doctrine/DBAL/TracingStatementForV2.php + - src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV2V3.php + - src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php - src/Tracing/Doctrine/DBAL/TracingDriverForV2.php + - src/Tracing/Doctrine/DBAL/TracingDriverForV3.php + - src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php + - src/Tracing/Doctrine/DBAL/TracingStatementForV2.php + - src/Tracing/Doctrine/DBAL/TracingStatementForV3.php - src/Tracing/HttpClient/TraceableHttpClientForV4.php - src/Tracing/HttpClient/TraceableHttpClientForV5.php - src/Tracing/HttpClient/TraceableResponseForV4.php - src/Tracing/HttpClient/TraceableResponseForV5.php - tests/End2End/App + - tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV2Test.php + - tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV3Test.php + - tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php - tests/Tracing/Doctrine/DBAL/TracingDriverForV2Test.php + - tests/Tracing/Doctrine/DBAL/TracingDriverForV3Test.php + - tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php + - tests/Tracing/Doctrine/DBAL/TracingStatementForV2Test.php + - tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php - tests/EventListener/Fixtures/UserWithoutIdentifierStub.php dynamicConstantNames: - Symfony\Component\HttpKernel\Kernel::VERSION diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 32016223..fc8ebf3e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,95 +1,60 @@ - + - - FatalErrorException + + - - ConsoleListener + + - - public function __construct(HubInterface $hub, bool $captureErrors = true) + + - - - $event instanceof ExceptionEvent - - - getException - - - - isMasterRequest + + - - iterable + + - - - - - ExceptionConverterDriver - - - - - $this->decoratedDriver->getSchemaManager($conn, $platform) - - - AbstractSchemaManager<T> - - - $params + + + + + + + + + getServerVersion())]]> + + + + getDatabasePlatform($versionProvider)]]> + - - toStream + + - - toStream + + - - TracingDriverForV2 + + - - FilterControllerEvent - FilterResponseEvent - GetResponseEvent - GetResponseEvent - GetResponseForExceptionEvent - PostResponseEvent - - - - - $container->getParameter('sentry.tracing.cache.enabled') - - - - - $container->getParameter('sentry.tracing.enabled') - $container->getParameter('sentry.tracing.http_client.enabled') - - - - - $container->getParameter('sentry.tracing.enabled') - $container->getParameter('sentry.tracing.dbal.enabled') - $container->getParameter('sentry.tracing.dbal.connections') - $container->getParameter('doctrine.connections') - diff --git a/psalm.xml b/psalm.xml index 89240539..bb410780 100644 --- a/psalm.xml +++ b/psalm.xml @@ -10,7 +10,13 @@ + + + + + + diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactory.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV2V3.php similarity index 95% rename from src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactory.php rename to src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV2V3.php index 7b5def41..9404f71b 100644 --- a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactory.php +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV2V3.php @@ -18,7 +18,7 @@ /** * @internal */ -final class TracingDriverConnectionFactory implements TracingDriverConnectionFactoryInterface +final class TracingDriverConnectionFactoryForV2V3 implements TracingDriverConnectionFactoryInterface { /** * @var HubInterface The current hub diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV4.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV4.php new file mode 100644 index 00000000..46c64bb6 --- /dev/null +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV4.php @@ -0,0 +1,78 @@ +hub = $hub; + } + + /** + * {@inheritdoc} + */ + public function create(Connection $connection, AbstractPlatform $databasePlatform, array $params): TracingDriverConnectionInterface + { + $tracingDriverConnection = new TracingDriverConnection( + $this->hub, + $connection, + $this->getDatabasePlatform($databasePlatform), + $params + ); + + return $tracingDriverConnection; + } + + private function getDatabasePlatform(AbstractPlatform $databasePlatform): string + { + // https://github.com/open-telemetry/opentelemetry-specification/blob/33113489fb5a1b6da563abb4ffa541447b87f515/specification/trace/semantic_conventions/database.md#connection-level-attributes + switch (true) { + case $databasePlatform instanceof AbstractMySQLPlatform: + return 'mysql'; + + case $databasePlatform instanceof DB2Platform: + return 'db2'; + + case $databasePlatform instanceof OraclePlatform: + return 'oracle'; + + case $databasePlatform instanceof PostgreSQLPlatform: + return 'postgresql'; + + case $databasePlatform instanceof SQLitePlatform: + return 'sqlite'; + + case $databasePlatform instanceof SQLServerPlatform: + return 'mssql'; + + default: + return 'other_sql'; + } + } +} diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnection.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php similarity index 98% rename from src/Tracing/Doctrine/DBAL/TracingDriverConnection.php rename to src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php index b23dff90..2cb2513d 100644 --- a/src/Tracing/Doctrine/DBAL/TracingDriverConnection.php +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php @@ -14,11 +14,11 @@ /** * This implementation wraps a driver connection and adds distributed tracing * capabilities to Doctrine DBAL. This implementation IS and MUST be compatible - * with all versions of Doctrine DBAL >= 2.10. + * with all versions of Doctrine DBAL >= 2.10 and >= 3.3. * * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams */ -final class TracingDriverConnection implements TracingDriverConnectionInterface +final class TracingDriverConnectionForV2V3 implements TracingDriverConnectionInterface { /** * @internal diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php new file mode 100644 index 00000000..88ae34b3 --- /dev/null +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php @@ -0,0 +1,284 @@ += 4.0. + * + * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams + */ +final class TracingDriverConnectionForV4 implements TracingDriverConnectionInterface +{ + /** + * @internal + */ + public const SPAN_OP_CONN_PREPARE = 'db.sql.prepare'; + + /** + * @internal + */ + public const SPAN_OP_CONN_QUERY = 'db.sql.query'; + + /** + * @internal + */ + public const SPAN_OP_CONN_EXEC = 'db.sql.exec'; + + /** + * @internal + */ + public const SPAN_OP_CONN_BEGIN_TRANSACTION = 'db.sql.transaction.begin'; + + /** + * @internal + */ + public const SPAN_OP_TRANSACTION_COMMIT = 'db.sql.transaction.commit'; + + /** + * @internal + */ + public const SPAN_OP_TRANSACTION_ROLLBACK = 'db.sql.transaction.rollback'; + + /** + * @var HubInterface The current hub + */ + private $hub; + + /** + * @var DriverConnectionInterface The decorated connection + */ + private $decoratedConnection; + + /** + * @var array The data to attach to the span + */ + private $spanData; + + /** + * Constructor. + * + * @param HubInterface $hub The current hub + * @param DriverConnectionInterface $decoratedConnection The connection to decorate + * @param string $databasePlatform The name of the database platform + * @param array $params The connection params + * + * @phpstan-param ConnectionParams $params + */ + public function __construct( + HubInterface $hub, + DriverConnectionInterface $decoratedConnection, + string $databasePlatform, + array $params + ) { + $this->hub = $hub; + $this->decoratedConnection = $decoratedConnection; + $this->spanData = $this->getSpanData($databasePlatform, $params); + } + + /** + * {@inheritdoc} + */ + public function prepare($sql): Statement + { + $statement = $this->traceFunction(self::SPAN_OP_CONN_PREPARE, $sql, function () use ($sql): Statement { + return $this->decoratedConnection->prepare($sql); + }); + + return new TracingStatement($this->hub, $statement, $sql, $this->spanData); + } + + /** + * {@inheritdoc} + */ + public function query(?string $sql = null, ...$args): Result + { + return $this->traceFunction(self::SPAN_OP_CONN_QUERY, $sql, function () use ($sql, $args): Result { + return $this->decoratedConnection->query($sql, ...$args); + }); + } + + /** + * {@inheritdoc} + */ + public function quote(string $value): string + { + return $this->decoratedConnection->quote($value); + } + + /** + * {@inheritdoc} + */ + public function exec(string $sql): int|string + { + return $this->traceFunction(self::SPAN_OP_CONN_EXEC, $sql, function () use ($sql): int|string { + return $this->decoratedConnection->exec($sql); + }); + } + + /** + * {@inheritdoc} + * + * @return string|int + */ + public function lastInsertId(): string|int + { + return $this->decoratedConnection->lastInsertId(); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction(): void + { + $this->traceFunction(self::SPAN_OP_CONN_BEGIN_TRANSACTION, 'BEGIN TRANSACTION', function (): void { + $this->decoratedConnection->beginTransaction(); + }); + } + + /** + * {@inheritdoc} + */ + public function commit(): void + { + $this->traceFunction(self::SPAN_OP_TRANSACTION_COMMIT, 'COMMIT', function (): void { + $this->decoratedConnection->commit(); + }); + } + + /** + * {@inheritdoc} + */ + public function rollBack(): void + { + $this->traceFunction(self::SPAN_OP_TRANSACTION_ROLLBACK, 'ROLLBACK', function (): void { + $this->decoratedConnection->rollBack(); + }); + } + + /** + * {@inheritdoc} + * + * @return resource|object + */ + public function getNativeConnection() + { + return $this->decoratedConnection->getNativeConnection(); + } + + /** + * {@inheritdoc} + */ + public function errorCode(): ?string + { + if (method_exists($this->decoratedConnection, 'errorCode')) { + return $this->decoratedConnection->errorCode(); + } + + throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function errorInfo(): array + { + if (method_exists($this->decoratedConnection, 'errorInfo')) { + return $this->decoratedConnection->errorInfo(); + } + + throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + } + + public function getWrappedConnection(): DriverConnectionInterface + { + return $this->decoratedConnection; + } + + public function getServerVersion(): string + { + return $this->decoratedConnection->getServerVersion(); + } + + /** + * @phpstan-template T + * + * @phpstan-param \Closure(): T $callback + * + * @phpstan-return T + */ + private function traceFunction(string $spanOperation, string $spanDescription, \Closure $callback) + { + $span = $this->hub->getSpan(); + + if (null !== $span) { + $spanContext = new SpanContext(); + $spanContext->setOp($spanOperation); + $spanContext->setDescription($spanDescription); + $spanContext->setData($this->spanData); + + $span = $span->startChild($spanContext); + } + + try { + return $callback(); + } finally { + if (null !== $span) { + $span->finish(); + } + } + } + + /** + * Gets a map of key-value pairs that will be set as the span data. + * + * @param array $params The connection params + * + * @return array + * + * @phpstan-param ConnectionParams $params + * + * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + */ + private function getSpanData(string $databasePlatform, array $params): array + { + $data = ['db.system' => $databasePlatform]; + + if (isset($params['user'])) { + $data['db.user'] = $params['user']; + } + + if (isset($params['dbname'])) { + $data['db.name'] = $params['dbname']; + } + + if (isset($params['host']) && !empty($params['host']) && !isset($params['memory'])) { + if (false === filter_var($params['host'], \FILTER_VALIDATE_IP)) { + $data['server.address'] = $params['host']; + } else { + $data['server.address'] = $params['host']; + } + } + + if (isset($params['port'])) { + $data['server.port'] = (string) $params['port']; + } + + if (isset($params['unix_socket'])) { + $data['server.socket.address'] = 'Unix'; + } elseif (isset($params['memory'])) { + $data['server.socket.address'] = 'inproc'; + } + + return $data; + } +} diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverForV4.php b/src/Tracing/Doctrine/DBAL/TracingDriverForV4.php new file mode 100644 index 00000000..1f9c4f56 --- /dev/null +++ b/src/Tracing/Doctrine/DBAL/TracingDriverForV4.php @@ -0,0 +1,56 @@ += 4.0. + * + * @internal + * + * @psalm-import-type Params from \Doctrine\DBAL\DriverManager + */ +final class TracingDriverForV4 extends AbstractDriverMiddleware +{ + /** + * @var TracingDriverConnectionFactoryInterface The connection factory + */ + private $connectionFactory; + + /** + * Constructor. + * + * @param TracingDriverConnectionFactoryInterface $connectionFactory The connection factory + * @param Driver $decoratedDriver The instance of the driver to decorate + */ + public function __construct(TracingDriverConnectionFactoryInterface $connectionFactory, Driver $decoratedDriver) + { + parent::__construct($decoratedDriver); + + $this->connectionFactory = $connectionFactory; + } + + /** + * {@inheritdoc} + * + * @psalm-param Params $params All connection parameters. + */ + public function connect(array $params): TracingDriverConnectionInterface + { + $connection = parent::connect($params); + $versionProvider = new StaticServerVersionProvider($connection->getServerVersion()); + + return $this->connectionFactory->create( + $connection, + $this->getDatabasePlatform($versionProvider), + $params + ); + } +} diff --git a/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php b/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php new file mode 100644 index 00000000..ba958687 --- /dev/null +++ b/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php @@ -0,0 +1,37 @@ +decoratedStatement->bindValue($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function execute(): Result + { + $spanContext = new SpanContext(); + $spanContext->setOp(self::SPAN_OP_STMT_EXECUTE); + $spanContext->setDescription($this->sqlQuery); + $spanContext->setData($this->spanData); + + return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute']); + } +} diff --git a/src/aliases.php b/src/aliases.php index 129fd6a6..fddd2689 100644 --- a/src/aliases.php +++ b/src/aliases.php @@ -5,6 +5,7 @@ namespace Sentry\SentryBundle; use Doctrine\DBAL\Result; +use Doctrine\DBAL\VersionAwarePlatformDriver; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3; @@ -12,11 +13,19 @@ use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriver; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactory; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactoryForV2V3; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactoryForV4; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionForV2V3; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionForV4; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverForV2; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverForV3; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverForV4; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingStatement; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingStatementForV2; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingStatementForV3; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingStatementForV4; use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClient; use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClientForV4; use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClientForV5; @@ -51,12 +60,21 @@ class_alias(TraceableTagAwareCacheAdapterForV2::class, TraceableTagAwareCacheAda } if (!class_exists(TracingStatement::class)) { - if (class_exists(Result::class)) { + if (class_exists(Result::class) && !interface_exists(VersionAwarePlatformDriver::class)) { + class_alias(TracingStatementForV4::class, TracingStatement::class); + class_alias(TracingDriverForV4::class, TracingDriver::class); + class_alias(TracingDriverConnectionForV4::class, TracingDriverConnection::class); + class_alias(TracingDriverConnectionFactoryForV4::class, TracingDriverConnectionFactory::class); + } elseif (class_exists(Result::class)) { class_alias(TracingStatementForV3::class, TracingStatement::class); class_alias(TracingDriverForV3::class, TracingDriver::class); + class_alias(TracingDriverConnectionForV2V3::class, TracingDriverConnection::class); + class_alias(TracingDriverConnectionFactoryForV2V3::class, TracingDriverConnectionFactory::class); } elseif (interface_exists(Result::class)) { class_alias(TracingStatementForV2::class, TracingStatement::class); class_alias(TracingDriverForV2::class, TracingDriver::class); + class_alias(TracingDriverConnectionForV2V3::class, TracingDriverConnection::class); + class_alias(TracingDriverConnectionFactoryForV2V3::class, TracingDriverConnectionFactory::class); } } diff --git a/tests/DependencyInjection/Compiler/DbalTracingPassTest.php b/tests/DependencyInjection/Compiler/DbalTracingPassTest.php index dc850b20..6fabc85f 100644 --- a/tests/DependencyInjection/Compiler/DbalTracingPassTest.php +++ b/tests/DependencyInjection/Compiler/DbalTracingPassTest.php @@ -16,10 +16,28 @@ final class DbalTracingPassTest extends DoctrineTestCase { - public function testProcessWithDoctrineDBALVersionAtLeast30(): void + public function testProcessWithDoctrineDBALVersion4(): void + { + if (!self::isDoctrineDBALVersion4Installed()) { + $this->markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 4.0.'); + } + + $container = $this->createContainerBuilder(); + $container->setParameter('sentry.tracing.dbal.connections', ['foo', 'bar']); + $container->compile(); + + $tracingMiddlewareDefinition = $container->getDefinition(TracingDriverMiddleware::class); + $doctrineMiddlewareTags = $tracingMiddlewareDefinition->getTag('doctrine.middleware'); + + $this->assertCount(2, $doctrineMiddlewareTags); + $this->assertSame(['connection' => 'foo'], $doctrineMiddlewareTags[0]); + $this->assertSame(['connection' => 'bar'], $doctrineMiddlewareTags[1]); + } + + public function testProcessWithDoctrineDBALVersion3(): void { if (!self::isDoctrineDBALVersion3Installed()) { - $this->markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + $this->markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $container = $this->createContainerBuilder(); @@ -34,7 +52,7 @@ public function testProcessWithDoctrineDBALVersionAtLeast30(): void $this->assertSame(['connection' => 'bar'], $doctrineMiddlewareTags[1]); } - public function testProcessWithDoctrineDBALVersionLowerThan30(): void + public function testProcessWithDoctrineDBALVersion2(): void { if (!self::isDoctrineDBALVersion2Installed()) { $this->markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be ^2.13.'); diff --git a/tests/DoctrineTestCase.php b/tests/DoctrineTestCase.php index 6d4c7626..350d1ddf 100644 --- a/tests/DoctrineTestCase.php +++ b/tests/DoctrineTestCase.php @@ -7,6 +7,7 @@ use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\VersionAwarePlatformDriver; use PHPUnit\Framework\TestCase; abstract class DoctrineTestCase extends TestCase @@ -25,7 +26,16 @@ protected static function isDoctrineDBALVersion2Installed(): bool protected static function isDoctrineDBALVersion3Installed(): bool { return self::isDoctrineDBALInstalled() - && !self::isDoctrineDBALVersion2Installed(); + && !self::isDoctrineDBALVersion2Installed() + && interface_exists(VersionAwarePlatformDriver::class); + } + + protected static function isDoctrineDBALVersion4Installed(): bool + { + return self::isDoctrineDBALInstalled() + && !self::isDoctrineDBALVersion2Installed() + && !self::isDoctrineDBALVersion3Installed() + && !interface_exists(VersionAwarePlatformDriver::class); } protected static function isDoctrineBundlePackageInstalled(): bool diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV2Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV2Test.php new file mode 100644 index 00000000..29bd7218 --- /dev/null +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV2Test.php @@ -0,0 +1,104 @@ +hub = $this->createMock(HubInterface::class); + $this->databasePlatform = $this->createMock(AbstractPlatform::class); + $this->tracingDriverConnectionFactory = new TracingDriverConnectionFactory($this->hub); + } + + /** + * @dataProvider createDataProvider + * + * @param class-string $databasePlatformFqcn + */ + public function testCreate(string $databasePlatformFqcn, string $expectedDatabasePlatform): void + { + $connection = $this->createMock(Connection::class); + $databasePlatform = $this->createMock($databasePlatformFqcn); + $driverConnection = $this->tracingDriverConnectionFactory->create($connection, $databasePlatform, []); + $expectedDriverConnection = new TracingDriverConnectionForV2V3($this->hub, $connection, $expectedDatabasePlatform, []); + + $this->assertEquals($expectedDriverConnection, $driverConnection); + } + + public static function createDataProvider(): \Generator + { + yield [ + AbstractMySQLPlatform::class, + 'mysql', + ]; + + yield [ + DB2Platform::class, + 'db2', + ]; + + yield [ + OraclePlatform::class, + 'oracle', + ]; + + yield [ + PostgreSQLPlatform::class, + 'postgresql', + ]; + + yield [ + SqlitePlatform::class, + 'sqlite', + ]; + + yield [ + SQLServerPlatform::class, + 'mssql', + ]; + + yield [ + AbstractPlatform::class, + 'other_sql', + ]; + } +} diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryTest.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV3Test.php similarity index 89% rename from tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryTest.php rename to tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV3Test.php index d3a74639..900dd288 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryTest.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV3Test.php @@ -17,10 +17,11 @@ use Sentry\SentryBundle\Tests\Tracing\Doctrine\DBAL\Fixture\ServerInfoAwareConnectionStub; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactory; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionForV2V3; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingServerInfoAwareDriverConnection; use Sentry\State\HubInterface; -final class TracingDriverConnectionFactoryTest extends DoctrineTestCase +final class TracingDriverConnectionFactoryV3Test extends DoctrineTestCase { /** * @var MockObject&HubInterface @@ -39,8 +40,8 @@ final class TracingDriverConnectionFactoryTest extends DoctrineTestCase public static function setUpBeforeClass(): void { - if (!self::isDoctrineDBALInstalled()) { - self::markTestSkipped('This test requires the "doctrine/dbal" Composer package.'); + if (!self::isDoctrineDBALVersion3Installed()) { + self::markTestSkipped('This test requires the "doctrine/dbal: ^3.3" Composer package.'); } } @@ -61,7 +62,7 @@ public function testCreate(string $databasePlatformFqcn, string $expectedDatabas $connection = $this->createMock(Connection::class); $databasePlatform = $this->createMock($databasePlatformFqcn); $driverConnection = $this->tracingDriverConnectionFactory->create($connection, $databasePlatform, []); - $expectedDriverConnection = new TracingDriverConnection($this->hub, $connection, $expectedDatabasePlatform, []); + $expectedDriverConnection = new TracingDriverConnectionForV2V3($this->hub, $connection, $expectedDatabasePlatform, []); $this->assertEquals($expectedDriverConnection, $driverConnection); } @@ -106,10 +107,6 @@ public static function createDataProvider(): \Generator public function testCreateWithServerInfoAwareConnection(): void { - if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); - } - $connection = $this->createMock(ServerInfoAwareConnectionStub::class); $driverConnection = $this->tracingDriverConnectionFactory->create($connection, $this->databasePlatform, []); $expectedDriverConnection = new TracingServerInfoAwareDriverConnection(new TracingDriverConnection($this->hub, $connection, 'other_sql', [])); diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV4Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV4Test.php new file mode 100644 index 00000000..e4357611 --- /dev/null +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryV4Test.php @@ -0,0 +1,104 @@ +hub = $this->createMock(HubInterface::class); + $this->databasePlatform = $this->createMock(AbstractPlatform::class); + $this->tracingDriverConnectionFactory = new TracingDriverConnectionFactory($this->hub); + } + + /** + * @dataProvider createDataProvider + * + * @param class-string $databasePlatformFqcn + */ + public function testCreate(string $databasePlatformFqcn, string $expectedDatabasePlatform): void + { + $connection = $this->createMock(Connection::class); + $databasePlatform = $this->createMock($databasePlatformFqcn); + $driverConnection = $this->tracingDriverConnectionFactory->create($connection, $databasePlatform, []); + $expectedDriverConnection = new TracingDriverConnectionForV4($this->hub, $connection, $expectedDatabasePlatform, []); + + $this->assertEquals($expectedDriverConnection, $driverConnection); + } + + public static function createDataProvider(): \Generator + { + yield [ + AbstractMySQLPlatform::class, + 'mysql', + ]; + + yield [ + DB2Platform::class, + 'db2', + ]; + + yield [ + OraclePlatform::class, + 'oracle', + ]; + + yield [ + PostgreSQLPlatform::class, + 'postgresql', + ]; + + yield [ + SQLitePlatform::class, + 'sqlite', + ]; + + yield [ + SQLServerPlatform::class, + 'mssql', + ]; + + yield [ + AbstractPlatform::class, + 'other_sql', + ]; + } +} diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionTest.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php similarity index 96% rename from tests/Tracing/Doctrine/DBAL/TracingDriverConnectionTest.php rename to tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php index 43f96214..d8ce3581 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionTest.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\MockObject\MockObject; use Sentry\SentryBundle\Tests\DoctrineTestCase; use Sentry\SentryBundle\Tests\Tracing\Doctrine\DBAL\Fixture\NativeDriverConnectionInterfaceStub; -use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection; +use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionForV4 as TracingDriverConnection; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionInterface; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingStatement; use Sentry\State\HubInterface; @@ -21,7 +21,7 @@ /** * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams */ -final class TracingDriverConnectionTest extends DoctrineTestCase +final class TracingDriverConnectionForV2V3Test extends DoctrineTestCase { /** * @var MockObject&HubInterface @@ -40,7 +40,10 @@ final class TracingDriverConnectionTest extends DoctrineTestCase public static function setUpBeforeClass(): void { - if (!self::isDoctrineBundlePackageInstalled()) { + if ( + !self::isDoctrineDBALVersion2Installed() + || !self::isDoctrineDBALVersion3Installed() + ) { self::markTestSkipped(); } } @@ -240,8 +243,7 @@ public function testBeginTransaction(array $params, array $expectedData): void ->willReturn($transaction); $this->decoratedConnection->expects($this->once()) - ->method('beginTransaction') - ->willReturn(false); + ->method('beginTransaction'); $this->assertFalse($connection->beginTransaction()); $this->assertNotNull($transaction->getSpanRecorder()); @@ -262,8 +264,7 @@ public function testBeginTransactionDoesNothingIfNoSpanIsSetOnHub(): void ->willReturn(null); $this->decoratedConnection->expects($this->once()) - ->method('beginTransaction') - ->willReturn(false); + ->method('beginTransaction'); $this->assertFalse($this->connection->beginTransaction()); } @@ -287,8 +288,7 @@ public function testCommit(array $params, array $expectedData): void ->willReturn($transaction); $this->decoratedConnection->expects($this->once()) - ->method('commit') - ->willReturn(false); + ->method('commit'); $this->assertFalse($connection->commit()); $this->assertNotNull($transaction->getSpanRecorder()); @@ -309,8 +309,7 @@ public function testCommitDoesNothingIfNoSpanIsSetOnHub(): void ->willReturn(null); $this->decoratedConnection->expects($this->once()) - ->method('commit') - ->willReturn(false); + ->method('commit'); $this->assertFalse($this->connection->commit()); } @@ -334,8 +333,7 @@ public function testRollBack(array $params, array $expectedData): void ->willReturn($transaction); $this->decoratedConnection->expects($this->once()) - ->method('rollBack') - ->willReturn(false); + ->method('rollBack'); $this->assertFalse($connection->rollBack()); $this->assertNotNull($transaction->getSpanRecorder()); @@ -356,8 +354,7 @@ public function testRollBackDoesNothingIfNoSpanIsSetOnHub(): void ->willReturn(null); $this->decoratedConnection->expects($this->once()) - ->method('rollBack') - ->willReturn(false); + ->method('rollBack'); $this->assertFalse($this->connection->rollBack()); } @@ -378,7 +375,7 @@ public function testErrorCode(): void public function testErrorCodeThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $this->expectException(\BadMethodCallException::class); @@ -403,7 +400,7 @@ public function testErrorInfo(): void public function testErrorInfoThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $this->expectException(\BadMethodCallException::class); diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php new file mode 100644 index 00000000..e1daefb7 --- /dev/null +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php @@ -0,0 +1,490 @@ +hub = $this->createMock(HubInterface::class); + $this->decoratedConnection = $this->createMock(DriverConnectionInterface::class); + $this->connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', []); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testPrepare(array $params, array $expectedData): void + { + $sql = 'SELECT 1 + 1'; + $statement = $this->createMock(DriverStatementInterface::class); + $resultStatement = new TracingStatement($this->hub, $statement, $sql, $expectedData); + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('prepare') + ->with($sql) + ->willReturn($statement); + + $this->assertEquals($resultStatement, $connection->prepare($sql)); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_CONN_PREPARE, $spans[1]->getOp()); + $this->assertSame($sql, $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testPrepareDoesNothingIfNoSpanIsSetOnHub(): void + { + $sql = 'SELECT 1 + 1'; + $statement = $this->createMock(DriverStatementInterface::class); + $resultStatement = new TracingStatement($this->hub, $statement, $sql, ['db.system' => 'foo_platform']); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedConnection->expects($this->once()) + ->method('prepare') + ->with($sql) + ->willReturn($statement); + + $this->assertEquals($resultStatement, $this->connection->prepare($sql)); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testQuery(array $params, array $expectedData): void + { + $result = $this->createMock(DriverResultInterface::class); + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + $sql = 'SELECT 1 + 1'; + + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('query') + ->with($sql) + ->willReturn($result); + + $this->assertSame($result, $connection->query($sql)); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_CONN_QUERY, $spans[1]->getOp()); + $this->assertSame($sql, $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testQueryDoesNothingIfNoSpanIsSetOnHub(): void + { + $result = $this->createMock(DriverResultInterface::class); + $sql = 'SELECT 1 + 1'; + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedConnection->expects($this->once()) + ->method('query') + ->with($sql) + ->willReturn($result); + + $this->assertSame($result, $this->connection->query($sql)); + } + + public function testQuote(): void + { + $this->decoratedConnection->expects($this->once()) + ->method('quote') + ->with('foo') + ->willReturn('foo'); + + $this->assertSame('foo', $this->connection->quote('foo')); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testExec(array $params, array $expectedData): void + { + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + $sql = 'SELECT 1 + 1'; + + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('exec') + ->with($sql) + ->willReturn(10); + + $this->assertSame(10, $connection->exec($sql)); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_CONN_EXEC, $spans[1]->getOp()); + $this->assertSame($sql, $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testLastInsertId(): void + { + $this->decoratedConnection->expects($this->once()) + ->method('lastInsertId') + ->willReturn('10'); + + $this->assertSame('10', $this->connection->lastInsertId()); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testBeginTransaction(array $params, array $expectedData): void + { + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('beginTransaction'); + + $connection->beginTransaction(); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_CONN_BEGIN_TRANSACTION, $spans[1]->getOp()); + $this->assertSame('BEGIN TRANSACTION', $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testBeginTransactionDoesNothingIfNoSpanIsSetOnHub(): void + { + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedConnection->expects($this->once()) + ->method('beginTransaction'); + + $this->connection->beginTransaction(); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testCommit(array $params, array $expectedData): void + { + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('commit'); + + $connection->commit(); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_TRANSACTION_COMMIT, $spans[1]->getOp()); + $this->assertSame('COMMIT', $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testCommitDoesNothingIfNoSpanIsSetOnHub(): void + { + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedConnection->expects($this->once()) + ->method('commit'); + + $this->connection->commit(); + } + + /** + * @dataProvider spanDataDataProvider + * + * @param array $params + * @param array $expectedData + * + * @phpstan-param ConnectionParams $params + */ + public function testRollBack(array $params, array $expectedData): void + { + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', $params); + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedConnection->expects($this->once()) + ->method('rollBack'); + + $connection->rollBack(); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingDriverConnection::SPAN_OP_TRANSACTION_ROLLBACK, $spans[1]->getOp()); + $this->assertSame('ROLLBACK', $spans[1]->getDescription()); + $this->assertSame($expectedData, $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testRollBackDoesNothingIfNoSpanIsSetOnHub(): void + { + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedConnection->expects($this->once()) + ->method('rollBack'); + + $this->connection->rollBack(); + } + + public function testErrorCode(): void + { + if (!self::isDoctrineDBALVersion2Installed()) { + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be ^2.13.'); + } + + $this->decoratedConnection->expects($this->once()) + ->method('errorCode') + ->willReturn('1002'); + + $this->assertSame('1002', $this->connection->errorCode()); + } + + public function testErrorCodeThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void + { + if (!self::isDoctrineDBALVersion3Installed()) { + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); + } + + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('The Sentry\\SentryBundle\\Tracing\\Doctrine\\DBAL\\TracingDriverConnection::errorCode() method is not supported on Doctrine DBAL 3.0.'); + + $this->connection->errorCode(); + } + + public function testErrorInfo(): void + { + if (!self::isDoctrineDBALVersion2Installed()) { + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be ^2.13.'); + } + + $this->decoratedConnection->expects($this->once()) + ->method('errorInfo') + ->willReturn(['foobar']); + + $this->assertSame(['foobar'], $this->connection->errorInfo()); + } + + public function testErrorInfoThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void + { + if (!self::isDoctrineDBALVersion3Installed()) { + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); + } + + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('The Sentry\\SentryBundle\\Tracing\\Doctrine\\DBAL\\TracingDriverConnection::errorInfo() method is not supported on Doctrine DBAL 3.0.'); + + $this->connection->errorInfo(); + } + + public function testGetWrappedConnection(): void + { + $connection = new TracingDriverConnection($this->hub, $this->decoratedConnection, 'foo_platform', []); + + $this->assertSame($this->decoratedConnection, $connection->getWrappedConnection()); + } + + public function testGetNativeConnection(): void + { + $nativeConnection = new class() { + }; + + $decoratedConnection = $this->createMock(NativeDriverConnectionInterfaceStub::class); + $decoratedConnection->expects($this->once()) + ->method('getNativeConnection') + ->willReturn($nativeConnection); + + $connection = new TracingDriverConnection($this->hub, $decoratedConnection, 'foo_platform', []); + + $this->assertSame($nativeConnection, $connection->getNativeConnection()); + } + + /** + * @return \Generator + */ + public function spanDataDataProvider(): \Generator + { + yield [ + [], + ['db.system' => 'foo_platform'], + ]; + + yield [ + [ + 'user' => 'root', + 'dbname' => 'INFORMATION_SCHEMA', + 'port' => 3306, + 'unix_socket' => '/var/run/mysqld/mysqld.sock', + ], + [ + 'db.system' => 'foo_platform', + 'db.user' => 'root', + 'db.name' => 'INFORMATION_SCHEMA', + 'server.port' => '3306', + 'server.socket.address' => 'Unix', + ], + ]; + + yield [ + [ + 'user' => 'root', + 'dbname' => 'INFORMATION_SCHEMA', + 'port' => 3306, + 'memory' => true, + ], + [ + 'db.system' => 'foo_platform', + 'db.user' => 'root', + 'db.name' => 'INFORMATION_SCHEMA', + 'server.port' => '3306', + 'server.socket.address' => 'inproc', + ], + ]; + + yield [ + [ + 'host' => 'localhost', + ], + [ + 'db.system' => 'foo_platform', + 'server.address' => 'localhost', + ], + ]; + + yield [ + [ + 'host' => '127.0.0.1', + ], + [ + 'db.system' => 'foo_platform', + 'server.address' => '127.0.0.1', + ], + ]; + } +} diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverForV2Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverForV2Test.php index efaac777..0f7ba7c3 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverForV2Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverForV2Test.php @@ -179,7 +179,7 @@ public function testConvertExceptionWhenDriverDoesNotImplementInterface(): void } } -if (interface_exists(Driver::class)) { +if (interface_exists(Driver::class) && interface_exists(VersionAwarePlatformDriver::class)) { interface StubVersionAwarePlatformDriver extends Driver, VersionAwarePlatformDriver { } diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverForV3Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverForV3Test.php index af3a857b..06d7f13b 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverForV3Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverForV3Test.php @@ -4,13 +4,9 @@ namespace Sentry\SentryBundle\Tests\Tracing\Doctrine\DBAL; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Connection as DriverConnectionInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Schema\AbstractSchemaManager; -use Doctrine\DBAL\VersionAwarePlatformDriver as VersionAwarePlatformDriverInterface; use PHPUnit\Framework\MockObject\MockObject; use Sentry\SentryBundle\Tests\DoctrineTestCase; use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactoryInterface; @@ -27,7 +23,7 @@ final class TracingDriverForV3Test extends DoctrineTestCase public static function setUpBeforeClass(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } } @@ -62,78 +58,4 @@ public function testConnect(): void $this->assertSame($tracingDriverConnection, $driver->connect($params)); } - - public function testGetDatabasePlatform(): void - { - $databasePlatform = $this->createMock(AbstractPlatform::class); - - $decoratedDriver = $this->createMock(DriverInterface::class); - $decoratedDriver->expects($this->once()) - ->method('getDatabasePlatform') - ->willReturn($databasePlatform); - - $driver = new TracingDriverForV3($this->connectionFactory, $decoratedDriver); - - $this->assertSame($databasePlatform, $driver->getDatabasePlatform()); - } - - public function testGetSchemaManager(): void - { - $connection = $this->createMock(Connection::class); - $databasePlatform = $this->createMock(AbstractPlatform::class); - $schemaManager = $this->createMock(AbstractSchemaManager::class); - - $decoratedDriver = $this->createMock(DriverInterface::class); - $decoratedDriver->expects($this->once()) - ->method('getSchemaManager') - ->with($connection, $databasePlatform) - ->willReturn($schemaManager); - - $driver = new TracingDriverForV3($this->connectionFactory, $decoratedDriver); - - $this->assertSame($schemaManager, $driver->getSchemaManager($connection, $databasePlatform)); - } - - public function testGetExceptionConverter(): void - { - $exceptionConverter = $this->createMock(ExceptionConverter::class); - - $decoratedDriver = $this->createMock(DriverInterface::class); - $decoratedDriver->expects($this->once()) - ->method('getExceptionConverter') - ->willReturn($exceptionConverter); - - $driver = new TracingDriverForV3($this->connectionFactory, $decoratedDriver); - - $this->assertSame($exceptionConverter, $driver->getExceptionConverter()); - } - - public function testCreateDatabasePlatformForVersion(): void - { - $databasePlatform = $this->createMock(AbstractPlatform::class); - - $decoratedDriver = $this->createMock(VersionAwarePlatformDriverInterface::class); - $decoratedDriver->expects($this->once()) - ->method('createDatabasePlatformForVersion') - ->with('5.7') - ->willReturn($databasePlatform); - - $driver = new TracingDriverForV3($this->connectionFactory, $decoratedDriver); - - $this->assertSame($databasePlatform, $driver->createDatabasePlatformForVersion('5.7')); - } - - public function testCreateDatabasePlatformForVersionWhenDriverDoesNotImplementInterface(): void - { - $databasePlatform = $this->createMock(AbstractPlatform::class); - - $decoratedDriver = $this->createMock(DriverInterface::class); - $decoratedDriver->expects($this->once()) - ->method('getDatabasePlatform') - ->willReturn($databasePlatform); - - $driver = new TracingDriverForV3($this->connectionFactory, $decoratedDriver); - - $this->assertSame($databasePlatform, $driver->createDatabasePlatformForVersion('5.7')); - } } diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverForV4Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverForV4Test.php new file mode 100644 index 00000000..85d872d0 --- /dev/null +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverForV4Test.php @@ -0,0 +1,61 @@ += 4.0.'); + } + } + + protected function setUp(): void + { + $this->connectionFactory = $this->createMock(TracingDriverConnectionFactoryInterface::class); + } + + public function testConnect(): void + { + $params = ['host' => 'localhost']; + $databasePlatform = $this->createMock(AbstractPlatform::class); + $driverConnection = $this->createMock(DriverConnectionInterface::class); + $tracingDriverConnection = $this->createMock(TracingDriverConnectionInterface::class); + $decoratedDriver = $this->createMock(DriverInterface::class); + + $decoratedDriver->expects($this->once()) + ->method('connect') + ->with($params) + ->willReturn($driverConnection); + + $decoratedDriver->expects($this->once()) + ->method('getDatabasePlatform') + ->willReturn($databasePlatform); + + $this->connectionFactory->expects($this->once()) + ->method('create') + ->with($driverConnection, $databasePlatform, $params) + ->willReturn($tracingDriverConnection); + + $driver = new TracingDriverForV4($this->connectionFactory, $decoratedDriver); + + $this->assertSame($tracingDriverConnection, $driver->connect($params)); + } +} diff --git a/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php b/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php index 29bcbb78..f8cd4537 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php +++ b/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php @@ -29,7 +29,10 @@ final class TracingServerInfoAwareDriverConnectionTest extends DoctrineTestCase public static function setUpBeforeClass(): void { - if (!self::isDoctrineBundlePackageInstalled()) { + if ( + !self::isDoctrineDBALVersion2Installed() + && !self::isDoctrineDBALVersion3Installed() + ) { self::markTestSkipped(); } } @@ -185,7 +188,7 @@ public function testRequiresQueryForServerVersionThrowsExceptionIfWrappedConnect public function testRequiresQueryForServerVersionThrowsExceptionIfWrappedConnectionDoesNotImplementMethod(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $this->decoratedConnection->expects($this->once()) @@ -214,7 +217,7 @@ public function testErrorCode(): void public function testErrorCodeThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $this->expectException(\BadMethodCallException::class); @@ -239,7 +242,7 @@ public function testErrorInfo(): void public function testErrorInfoThrowsExceptionIfDecoratedConnectionDoesNotImplementMethod(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } $this->expectException(\BadMethodCallException::class); diff --git a/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php b/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php index 11924259..382fb0aa 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php @@ -34,7 +34,7 @@ final class TracingStatementForV3Test extends DoctrineTestCase public static function setUpBeforeClass(): void { if (!self::isDoctrineDBALVersion3Installed()) { - self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.0.'); + self::markTestSkipped('This test requires the version of the "doctrine/dbal" Composer package to be >= 3.3.'); } } diff --git a/tests/Tracing/Doctrine/DBAL/TracingStatementForV4Test.php b/tests/Tracing/Doctrine/DBAL/TracingStatementForV4Test.php new file mode 100644 index 00000000..476e7938 --- /dev/null +++ b/tests/Tracing/Doctrine/DBAL/TracingStatementForV4Test.php @@ -0,0 +1,97 @@ += 4.0.'); + } + } + + protected function setUp(): void + { + $this->hub = $this->createMock(HubInterface::class); + $this->decoratedStatement = $this->createMock(Statement::class); + $this->statement = new TracingStatementForV4($this->hub, $this->decoratedStatement, 'SELECT 1', ['db.system' => 'sqlite']); + } + + public function testBindValue(): void + { + $this->decoratedStatement->expects($this->once()) + ->method('bindValue') + ->with('foo', 'bar', ParameterType::INTEGER); + + $this->statement->bindValue('foo', 'bar', ParameterType::INTEGER); + } + + public function testExecute(): void + { + $driverResult = $this->createMock(Result::class); + $transaction = new Transaction(new TransactionContext(), $this->hub); + $transaction->initSpanRecorder(); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn($transaction); + + $this->decoratedStatement->expects($this->once()) + ->method('execute') + ->willReturn($driverResult); + + $this->assertSame($driverResult, $this->statement->execute()); + $this->assertNotNull($transaction->getSpanRecorder()); + + $spans = $transaction->getSpanRecorder()->getSpans(); + + $this->assertCount(2, $spans); + $this->assertSame(TracingStatementForV4::SPAN_OP_STMT_EXECUTE, $spans[1]->getOp()); + $this->assertSame('SELECT 1', $spans[1]->getDescription()); + $this->assertSame(['db.system' => 'sqlite'], $spans[1]->getData()); + $this->assertNotNull($spans[1]->getEndTimestamp()); + } + + public function testExecuteDoesNothingIfNoSpanIsSetOnHub(): void + { + $driverResult = $this->createMock(Result::class); + + $this->hub->expects($this->once()) + ->method('getSpan') + ->willReturn(null); + + $this->decoratedStatement->expects($this->once()) + ->method('execute') + ->willReturn($driverResult); + + $this->assertSame($driverResult, $this->statement->execute()); + } +}