Skip to content

Commit

Permalink
Support jOOQ 3.15 and beyond
Browse files Browse the repository at this point in the history
Implementing jOOQ's `DSLContext` interface was causing revlock and subsequently frequent breaking. Now that jOOQ OSS supported versions no longer work with Java 11, we cannot compile against them to update our implementation `MetricsDSLContext`. This switches to extending `DefaultDSLContext` instead which eliminates a lot of boilerplate implementation and should be more compatible. A sample has been added that runs the tests against the latest version of jOOQ to ensure MetricsDSLContext works with it while compiling against the oldest OSS version of jOOQ that supports Java 8.
  • Loading branch information
shakuzen committed Feb 7, 2024
1 parent 7e0fb8d commit b3d9da5
Show file tree
Hide file tree
Showing 8 changed files with 675 additions and 4,750 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ subprojects {
ignoreMissingClasses = true
includeSynthetic = true

// TODO remove MetricsDSLContext from excludes in 1.14 when comparing against 1.13
classExcludes = [ 'io.micrometer.core.instrument.binder.db.MetricsDSLContext' ]
compatibilityChangeExcludes = [ "METHOD_NEW_DEFAULT" ]

packageExcludes = ['io.micrometer.shaded.*', 'io.micrometer.statsd.internal']
Expand Down
1 change: 0 additions & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def VERSIONS = [
libs.hdrhistogram,
libs.hibernateEntitymanager,
libs.hsqldb,
libs.jooq,
libs.jsr107,
libs.junitJupiter,
libs.junitPlatformLauncher,
Expand Down
8 changes: 6 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ jetty12 = "12.0.3"
jersey2 = "2.41"
jersey3 = "3.0.11"
jmh = "1.37"
jooq = "3.14.16"
# newest version of OSS jOOQ that supports Java 8
jooqOld = "3.14.16"
# latest version of jOOQ to run tests against
jooqNew = "3.19.3"
jsr107 = "1.1.1"
jsr305 = "3.0.2"
junit = "5.10.1"
Expand Down Expand Up @@ -153,7 +156,8 @@ jersey3Hk2 = { module = "org.glassfish.jersey.inject:jersey-hk2", version.ref =
jersey3TestFrameworkJdkHttp = { module = "org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-jdk-http", version.ref = "jersey3" }
jmhCore = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmhAnnotationProcessor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
jooq = { module = "org.jooq:jooq", version.ref = "jooq" }
jooq = { module = "org.jooq:jooq", version.ref = "jooqOld" }
jooqLatest = { module = "org.jooq:jooq", version.ref = "jooqNew" }
jsonPath = { module = "com.jayway.jsonpath:json-path", version = "2.9.0" }
jsr107 = { module = "org.jsr107.ri:cache-ri-impl", version.ref = "jsr107" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
Expand Down
2 changes: 1 addition & 1 deletion micrometer-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ dependencies {

optionalApi 'org.mongodb:mongodb-driver-sync'

optionalApi 'org.jooq:jooq'
optionalApi libs.jooq

optionalApi 'org.apache.kafka:kafka-clients'
optionalApi 'org.apache.kafka:kafka-streams'
Expand Down

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions samples/micrometer-samples-jooq/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id 'java'
}

dependencies {
implementation project(":micrometer-core")
implementation libs.jooqLatest

testImplementation libs.junitJupiter
testImplementation libs.assertj
testImplementation libs.mockitoCore

testRuntimeOnly libs.h2
}

java {
targetCompatibility = JavaVersion.VERSION_11
}

compileJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
options.release = 11
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright 2020 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.samples.jooq;

import io.micrometer.common.lang.NonNull;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.db.MetricsDSLContext;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.jooq.*;
import org.jooq.Record;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import static io.micrometer.core.instrument.binder.db.MetricsDSLContext.withMetrics;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.jooq.impl.DSL.*;
import static org.mockito.Mockito.mock;

/**
* Tests for {@link MetricsDSLContext} run on the latest version of jOOQ.
*
* @author Jon Schneider
* @author Johnny Lim
*/
@SuppressWarnings("deprecation")
class MetricsDSLContextTest {

private MeterRegistry meterRegistry = new SimpleMeterRegistry();

@Test
void timeFluentSelectStatement() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

Result<Record> result = jooq.tag("name", "selectAllAuthors").select(asterisk()).from("author").fetch();
assertThat(result.size()).isEqualTo(1);

// intentionally don't time this operation to demonstrate that the
// configuration hasn't been globally mutated
jooq.select(asterisk()).from("author").fetch();

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

@Test
void timeParsedSelectStatement() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:parsedSelect")) {
MetricsDSLContext jooq = createDatabase(conn);
jooq.tag("name", "selectAllAuthors").fetch("SELECT * FROM author");

// intentionally don't time this operation to demonstrate that the
// configuration hasn't been globally mutated
jooq.fetch("SELECT * FROM author");

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

@Test
void timeFaultySelectStatement() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:faultySelect")) {
MetricsDSLContext jooq = createDatabase(conn);
jooq.tag("name", "selectAllAuthors").fetch("SELECT non_existent_field FROM author");

failBecauseExceptionWasNotThrown(DataAccessException.class);
}
catch (DataAccessException ignored) {
}

assertThat(meterRegistry.get("jooq.query")
.tag("name", "selectAllAuthors")
.tag("type", "read")
.tag("exception", "c42 syntax error or access rule violation")
.tag("exception.subclass", "none")
.timer()
.count()).isEqualTo(1);
}

@Test
void timeExecute() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

jooq.tag("name", "selectAllAuthors").execute("SELECT * FROM author");

assertThat(meterRegistry.get("jooq.query").tag("name", "selectAllAuthors").timer().count()).isEqualTo(1);
}
}

@Test
void timeInsert() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

jooq.tag("name", "insertAuthor").insertInto(table("author")).values("2", "jon", "schneider").execute();

assertThat(meterRegistry.get("jooq.query").tag("name", "insertAuthor").timer().count()).isEqualTo(1);
}
}

@Test
void timeUpdate() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

jooq.tag("name", "updateAuthor").update(table("author")).set(field("author.first_name"), "jon").execute();

assertThat(meterRegistry.get("jooq.query").tag("name", "updateAuthor").timer().count()).isEqualTo(1);
}
}

@Test
void timingBatchQueriesNotSupported() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

jooq.tag("name", "batch")
.batch(jooq.insertInto(table("author")).values("3", "jon", "schneider"),
jooq.insertInto(table("author")).values("4", "jon", "schneider"))
.execute();

assertThat(meterRegistry.find("jooq.query").timer()).isNull();
}
}

/**
* Ensures that we are holding tag state in a way that doesn't conflict between two
* unexecuted queries.
*/
@Test
void timeTwoStatementsCreatedBeforeEitherIsExecuted() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:fluentSelect")) {
MetricsDSLContext jooq = createDatabase(conn);

SelectJoinStep<Record> select1 = jooq.tag("name", "selectAllAuthors").select(asterisk()).from("author");
SelectJoinStep<Record1<Object>> select2 = jooq.tag("name", "selectAllAuthors2")
.select(field("first_name"))
.from("author");

select1.fetch();
select2.fetch();
select1.fetch();

assertThat(meterRegistry.get("jooq.query").tag("name", "selectAllAuthors").timer().count()).isEqualTo(2);

assertThat(meterRegistry.get("jooq.query").tag("name", "selectAllAuthors2").timer().count()).isEqualTo(1);
}
}

@Test
void userExecuteListenerShouldBePreserved() {
ExecuteListener userExecuteListener = mock(ExecuteListener.class);
Configuration configuration = new DefaultConfiguration().set(() -> userExecuteListener);

MetricsDSLContext jooq = withMetrics(using(configuration), meterRegistry, Tags.empty());

ExecuteListenerProvider[] executeListenerProviders = jooq.configuration().executeListenerProviders();
assertThat(executeListenerProviders).hasSize(2);
assertThat(executeListenerProviders[0].provide()).isSameAs(userExecuteListener);
assertThat(executeListenerProviders[1].provide().getClass().getCanonicalName())
.isEqualTo("io.micrometer.core.instrument.binder.db.JooqExecuteListener");

SelectSelectStep<Record> select = jooq.tag("name", "selectAllAuthors").select(asterisk());
executeListenerProviders = select.configuration().executeListenerProviders();
assertThat(executeListenerProviders).hasSize(2);
assertThat(executeListenerProviders[0].provide()).isSameAs(userExecuteListener);
assertThat(executeListenerProviders[1].provide().getClass().getCanonicalName())
.isEqualTo("io.micrometer.core.instrument.binder.db.JooqExecuteListener");
}

@NonNull
private MetricsDSLContext createDatabase(Connection conn) {
Configuration configuration = new DefaultConfiguration().set(conn).set(SQLDialect.H2);

MetricsDSLContext jooq = MetricsDSLContext.withMetrics(DSL.using(configuration), meterRegistry, Tags.empty());

jooq.execute("CREATE TABLE author (" + " id int NOT NULL," + " first_name varchar(255) DEFAULT NULL,"
+ " last_name varchar(255) DEFAULT NULL," + " PRIMARY KEY (id)" + ")");

jooq.execute("INSERT INTO author VALUES(1, 'jon', 'schneider')");
return jooq;
}

}
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ gradleEnterprise {

include 'micrometer-commons', 'micrometer-core', 'micrometer-observation'

['core', 'boot2', 'boot2-reactive', 'spring-integration', 'hazelcast', 'hazelcast3', 'javalin', 'jersey3', 'jetty12', 'kotlin'].each { sample ->
['core', 'boot2', 'boot2-reactive', 'spring-integration', 'hazelcast', 'hazelcast3', 'javalin', 'jersey3', 'jetty12', 'jooq', 'kotlin'].each { sample ->
include "micrometer-samples-$sample"
project(":micrometer-samples-$sample").projectDir = new File(rootProject.projectDir, "samples/micrometer-samples-$sample")
}
Expand Down

0 comments on commit b3d9da5

Please sign in to comment.