@@ -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 ' ,
@@ -118,17 +119,37 @@ public static function init(): int
118119 \DDTrace \install_hook ('PDO::prepare ' , static function (HookData $ hook ) {
119120 list ($ query ) = $ hook ->args ;
120121
121- $ span = $ hook ->span ();
122- Integration::handleOrphan ($ span );
123- $ span ->name = 'PDO.prepare ' ;
124- $ span ->resource = Integration::toString ($ query );
122+ // Create the prepare span
123+ $ prepareSpan = $ hook ->span ();
124+ Integration::handleOrphan ($ prepareSpan );
125+ $ prepareSpan ->name = 'PDO.prepare ' ;
126+ $ prepareSpan ->resource = Integration::toString ($ query );
125127 $ instance = $ hook ->instance ;
126- PDOIntegration::setCommonSpanInfo ($ instance , $ span );
128+ PDOIntegration::setCommonSpanInfo ($ instance , $ prepareSpan );
129+
130+ // Create the execute span as a child of prepare for DBM injection
131+ // This ensures traceparent points to the execute span
132+ $ executeSpan = \DDTrace \start_span ();
133+ $ executeSpan ->name = 'PDOStatement.execute ' ;
134+ $ executeSpan ->resource = Integration::toString ($ query );
135+ PDOIntegration::setCommonSpanInfo ($ instance , $ executeSpan );
127136
137+ // Inject DBM with the execute span's ID
128138 PDOIntegration::injectDBIntegration ($ instance , $ hook );
129- PDOIntegration::handleRasp ($ instance , $ span );
139+ PDOIntegration::handleRasp ($ instance , $ executeSpan );
140+
141+ // Close the execute span (removes from stack) but don't finish it yet
142+ // It will be resumed during execute
143+ \DDTrace \close_span ();
144+
145+ // Store the execute span to be resumed by execute
146+ $ hook ->data = ['execute_span ' => $ executeSpan ];
130147 }, static function (HookData $ hook ) {
131148 ObjectKVStore::propagate ($ hook ->instance , $ hook ->returned , PDOIntegration::CONNECTION_TAGS_KEY );
149+ // Store the execute span in the PDOStatement for later use
150+ if ($ hook ->returned instanceof \PDOStatement && !$ hook ->exception && isset ($ hook ->data ['execute_span ' ])) {
151+ ObjectKVStore::put ($ hook ->returned , PDOIntegration::PREPARE_SPAN_KEY , $ hook ->data ['execute_span ' ]);
152+ }
132153 });
133154
134155 // public bool PDO::commit ( void )
@@ -143,12 +164,29 @@ public static function init(): int
143164 \DDTrace \install_hook (
144165 'PDOStatement::execute ' ,
145166 static function (HookData $ hook ) {
146- $ hook ->span ();
167+ // Check if we have a stored execute span from prepare
168+ $ storedSpan = ObjectKVStore::get ($ hook ->instance , PDOIntegration::PREPARE_SPAN_KEY );
169+ if (!$ storedSpan ) {
170+ // No stored span (e.g., from PDO::query()), create normally
171+ $ hook ->span ();
172+ }
173+ // If we have a stored span, don't create a new one - we'll use the stored one
174+ // The stored span was already created during prepare with the correct traceparent
147175 },
148176 static function (HookData $ hook ) {
149- $ span = $ hook ->span ();
150177 $ instance = $ hook ->instance ;
178+ $ storedSpan = ObjectKVStore::get ($ instance , PDOIntegration::PREPARE_SPAN_KEY );
179+
180+ if ($ storedSpan ) {
181+ // Use the execute span that was created during prepare
182+ $ span = $ storedSpan ;
183+ } else {
184+ // No stored span (e.g., from PDO::query()), use the hook's span
185+ $ span = $ hook ->span ();
186+ }
187+
151188 Integration::handleOrphan ($ span );
189+ // Update span properties now that execute has completed
152190 $ span ->name = 'PDOStatement.execute ' ;
153191 Integration::handleInternalSpanServiceName ($ span , PDOIntegration::NAME );
154192 $ span ->type = Type::SQL ;
0 commit comments