Skip to content

[jOOQ] MetricsDSLContext calls time() twice on fetchValue(SelectField) path, causing tag loss #6659

@HeeChanN

Description

@HeeChanN

Describe the bug

When using MetricsDSLContext with jOOQ 3.20.x, calling DSLContext#fetchValue(SelectField) leads to double instrumentation: time() is invoked twice along the select(...) path. This causes tag state to be reset/overwritten and results in missing or unexpected tags on the recorded jooq.query timer.
This occurs even if Micrometer is compiled against jOOQ 3.14.x, because at runtime jOOQ 3.20.x introduces a new overload that changes the call path.

Environment

- Micrometer version: 1.15.3
- jOOQ version: 3.20.6
- Java version: 21

To Reproduce

Minimal JUnit test that demonstrates the issue:
How to reproduce the bug:

@Test
void jooqMethodTest() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:test")) {
        Configuration config = new DefaultConfiguration().set(conn).set(SQLDialect.H2);
        MeterRegistry registry = new SimpleMeterRegistry();

        MetricsDSLContext jooq =
            MetricsDSLContext.withMetrics(DSL.using(config), registry, Tags.empty());

        Integer result = jooq.tag("name", "checkAuthorExists")
                             .fetchValue(DSL.inline(123));

        assertThat(registry.get("jooq.query")
                .tag("name", "checkAuthorExists")
                .tag("type", "read")
                .timer()
                .count()).isEqualTo(1);
    }
}

In practice, the assertion fails because the timer is not recorded with the expected tags (or the meter cannot be found under that tag set).

Expected behavior

A single jooq.query timer is recorded once with the expected tags, e.g. name=checkAuthorExists, type=read.

Additional context

Call path (showing the double time()):

DefaultDSLContext.fetchValue(DSL.inline(123)) -> fetchValue(SelectField<T> field)
  -> DefaultDSLContext.fetchValue(select(field))
      -> MetricsDSLContext.select(SelectField<T1> field1)                 // time() #1
          -> DefaultDSLContext.select(SelectField<T1> field1)
              -> MetricsDSLContext.select(SelectFieldOrAsterisk... fields) // time() 

Root cause (fetchValue() in jOOQ 3.20.x):

@Override
public <T> T fetchValue(SelectField<T> field) {
    return field instanceof TableField
         ? fetchValue((TableField<?, T>) field)
         : field instanceof Table<?>
         ? fetchValue(select(field).from((Table<?>) field))
         : fetchValue(select(field));
}

Because MetricsDSLContext instruments select(...), the new delegation path results in double instrumentation and tag loss.

So, Please add a compatibility note to the official Micrometer documentation for for users running jOOQ newer(i.e., latest versions), informing users that certain newly added overloads (e.g., DefaultDSLContext#fetchValue(SelectField)) explaining that certain newly added overloads (e.g., DefaultDSLContext#fetchValue(SelectField)) may internally delegate to select(...), which can cause double instrumentation and tag loss with MetricsDSLContext unless Micrometer provides corresponding overrides.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA general bughelp wantedAn issue that a contributor can help us withinstrumentationAn issue that is related to instrumenting a componentmodule: micrometer-coreAn issue that is related to our core module

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions