Skip to content

Commit 68bf5c5

Browse files
committed
feat(PDO): correct a bug on prepared statement regarding DBM correlation
Signed-off-by: Alexandre Rulleau <alexandre.rulleau@datadoghq.com>
1 parent 5b09e4d commit 68bf5c5

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/DDTrace/Integrations/PDO/PDOIntegration.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class PDOIntegration extends Integration
1515
const NAME = 'pdo';
1616

1717
const CONNECTION_TAGS_KEY = 'connection_tags';
18+
const PREPARE_SPAN_KEY = 'prepare_span';
1819

1920
const DB_DRIVER_TO_SYSTEM = [
2021
'cubrid' => 'other_sql',
@@ -129,6 +130,10 @@ public static function init(): int
129130
PDOIntegration::handleRasp($instance, $span);
130131
}, static function (HookData $hook) {
131132
ObjectKVStore::propagate($hook->instance, $hook->returned, PDOIntegration::CONNECTION_TAGS_KEY);
133+
// Store the prepare span so execute can use it as parent
134+
if ($hook->returned instanceof \PDOStatement) {
135+
ObjectKVStore::put($hook->returned, PDOIntegration::PREPARE_SPAN_KEY, $hook->span());
136+
}
132137
});
133138

134139
// public bool PDO::commit ( void )
@@ -143,7 +148,9 @@ public static function init(): int
143148
\DDTrace\install_hook(
144149
'PDOStatement::execute',
145150
static function (HookData $hook) {
146-
$hook->span();
151+
// If this statement was prepared with PDO::prepare, use that span as parent
152+
$prepareSpan = ObjectKVStore::get($hook->instance, PDOIntegration::PREPARE_SPAN_KEY);
153+
$hook->span($prepareSpan);
147154
},
148155
static function (HookData $hook) {
149156
$span = $hook->span();

tests/Integrations/PDO/PDOTest.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,97 @@ public function testPDOStatementExceptionPeerServiceEnabled()
680680
]);
681681
}
682682

683+
public function testPreparedStatementExecuteIsChildOfPrepare()
684+
{
685+
$query = "SELECT * FROM tests WHERE id = ?";
686+
$traces = $this->isolateTracer(function () use ($query) {
687+
$pdo = $this->pdoInstance();
688+
$stmt = $pdo->prepare($query);
689+
$stmt->execute([1]);
690+
$results = $stmt->fetchAll();
691+
$this->assertEquals('Tom', $results[0]['name']);
692+
$stmt->closeCursor();
693+
$stmt = null;
694+
$pdo = null;
695+
});
696+
697+
// Get the raw spans to check parent-child relationship
698+
$spans = $traces[0];
699+
700+
// Find prepare and execute spans
701+
$constructSpan = null;
702+
$prepareSpan = null;
703+
$executeSpan = null;
704+
705+
foreach ($spans as $span) {
706+
if ($span['name'] === 'PDO.__construct') {
707+
$constructSpan = $span;
708+
} elseif ($span['name'] === 'PDO.prepare') {
709+
$prepareSpan = $span;
710+
} elseif ($span['name'] === 'PDOStatement.execute') {
711+
$executeSpan = $span;
712+
}
713+
}
714+
715+
$this->assertNotNull($constructSpan, 'PDO.__construct span should exist');
716+
$this->assertNotNull($prepareSpan, 'PDO.prepare span should exist');
717+
$this->assertNotNull($executeSpan, 'PDOStatement.execute span should exist');
718+
719+
// Verify that execute span's parent is the prepare span
720+
$this->assertEquals(
721+
$prepareSpan['span_id'],
722+
$executeSpan['parent_id'],
723+
'PDOStatement.execute should be a child of PDO.prepare'
724+
);
725+
}
726+
727+
public function testDirectQueryHasNoParentIssues()
728+
{
729+
$query = "SELECT * FROM tests WHERE id=1";
730+
$traces = $this->isolateTracer(function () use ($query) {
731+
$pdo = $this->pdoInstance();
732+
$pdo->query($query);
733+
$pdo = null;
734+
});
735+
736+
// Get the raw spans
737+
$spans = $traces[0];
738+
739+
// Find construct and query spans
740+
$constructSpan = null;
741+
$querySpan = null;
742+
743+
foreach ($spans as $span) {
744+
if ($span['name'] === 'PDO.__construct') {
745+
$constructSpan = $span;
746+
} elseif ($span['name'] === 'PDO.query') {
747+
$querySpan = $span;
748+
}
749+
}
750+
751+
$this->assertNotNull($constructSpan, 'PDO.__construct span should exist');
752+
$this->assertNotNull($querySpan, 'PDO.query span should exist');
753+
754+
// Verify that query span has a parent (should be root or construct)
755+
$this->assertTrue(
756+
isset($querySpan['parent_id']),
757+
'PDO.query should have a parent_id'
758+
);
759+
760+
// Verify spans are created correctly with proper structure
761+
$this->assertSpans($traces, [
762+
SpanAssertion::exists('PDO.__construct'),
763+
SpanAssertion::build('PDO.query', 'pdo', 'sql', $query)
764+
->withExactTags($this->baseTags())
765+
->withExactMetrics([
766+
Tag::DB_ROW_COUNT => 1.0,
767+
Tag::ANALYTICS_KEY => 1.0,
768+
'_dd.agent_psr' => 1.0,
769+
'_sampling_priority_v1' => 1.0,
770+
]),
771+
]);
772+
}
773+
683774
public function testLimitedTracerPDO()
684775
{
685776
$query = "SELECT * FROM tests WHERE id = ?";

0 commit comments

Comments
 (0)