Skip to content

Query values not captured when passed as second argument (prepared statements)Β #3193

@estiller

Description

@estiller

What version of OpenTelemetry are you using?

  • @opentelemetry/instrumentation-pg: 0.59.0
  • @opentelemetry/auto-instrumentations-node: 0.65.0
  • @opentelemetry/sdk-node: 0.206.0

What version of Node are you using?

v22.21.0

What did you do?

I'm using Drizzle ORM with PostgreSQL with prepared statements, which internally calls pg.Client.query() with the following signature:

client.query(
  { name: 'statement_name', text: 'SELECT * FROM users WHERE id = $1' },
  [123] // values passed as second argument
)

I enabled enhancedDatabaseReporting: true in the pg instrumentation config:

'@opentelemetry/instrumentation-pg': {
  enabled: true,
  enhancedDatabaseReporting: true,
}

What did you expect to see?

I expected to see the db.postgresql.values attribute in my spans containing the query parameter values (e.g., ["123"]).

What did you see instead?

The db.postgresql.values attribute is not present in the spans. The span only contains:

  • db.statement: The SQL query text
  • db.postgresql.plan: The prepared statement name
  • Other connection attributes

But no parameter values.

Root Cause

The bug is in instrumentation.ts:

const queryConfig = firstArgIsString
  ? {
      text: arg0 as string,
      values: Array.isArray(args[1]) ? args[1] : undefined,  // βœ… Checks args[1]
    }
  : firstArgIsQueryObjectWithText
  ? (arg0 as utils.ObjectWithText)  // ❌ Just casts arg0, ignores args[1]!
  : undefined;

When the first argument is a query config object (common with prepared statements), the code just casts arg0 without checking if values are passed as args[1]. This means queryConfig.values remains undefined even when values are provided.

Proposed Fix

const queryConfig = firstArgIsString
  ? {
      text: arg0 as string,
      values: Array.isArray(args[1]) ? args[1] : undefined,
    }
  : firstArgIsQueryObjectWithText
  ? {
      ...(arg0 as utils.ObjectWithText),
      // Merge values from args[1] if not already in the config object
      values: (arg0 as any).values ?? (Array.isArray(args[1]) ? args[1] : undefined),
    }
  : undefined;

This would check for values in args[1] when using a query config object, just like it already does for string queries.

Workaround

As a temporary workaround, I'm patching pg.Client.prototype.query and pg.Pool.prototype.query before OpenTelemetry instruments them, to merge args[1] into args[0].values when this pattern is detected.

Additional context

This affects any ORM or library that uses prepared statements with the signature query(config, values) where values are passed separately from the config object. This includes:

  • Drizzle ORM (when using .prepare())
  • Potentially other ORMs that use a similar pattern

The requestHook cannot work around this issue because by the time it's called, queryConfig.values is already undefined.

Tip: React with πŸ‘ to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions