This is an automated email from the ASF dual-hosted git repository.

kgyrtkirk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 26e3c44f4bf Quidem record (#16624)
26e3c44f4bf is described below

commit 26e3c44f4bf1993655e9fcf8233707709357d04e
Author: Zoltan Haindrich <[email protected]>
AuthorDate: Mon Aug 5 14:58:32 2024 +0200

    Quidem record (#16624)
    
    * enables to launch a fake broker based on test resources (druidtest uri)
    * could record queries into new testfiles during usage
    * instead of re-purpose Calcite's Hook migrates to use DruidHook which we 
can add further keys
    * added a quidem-ut module which could be the place for tests which could 
iteract with modules/etc
---
 .gitignore                                         |   2 +-
 .../druid/benchmark/query/InPlanningBenchmark.java |   4 +-
 .../apache/druid/benchmark/query/SqlBenchmark.java |   4 +-
 .../benchmark/query/SqlExpressionBenchmark.java    |   4 +-
 .../druid/benchmark/query/SqlGroupByBenchmark.java |   4 +-
 .../benchmark/query/SqlNestedDataBenchmark.java    |   4 +-
 .../benchmark/query/SqlVsNativeBenchmark.java      |   4 +-
 dev/quidem                                         |   4 +-
 extensions-contrib/compressed-bigdecimal/pom.xml   |   5 +
 extensions-contrib/tdigestsketch/pom.xml           |   5 +
 extensions-core/datasketches/pom.xml               |   5 +
 extensions-core/druid-bloom-filter/pom.xml         |   5 +
 extensions-core/druid-catalog/pom.xml              |   5 +
 extensions-core/histogram/pom.xml                  |   5 +
 extensions-core/multi-stage-query/pom.xml          |   5 +
 .../druid/msq/exec/ResultsContextSerdeTest.java    |   4 +-
 .../org/apache/druid/msq/test/MSQTestBase.java     |   4 +-
 extensions-core/stats/pom.xml                      |   6 +-
 extensions-core/testing-tools/pom.xml              |   5 +
 pom.xml                                            |   1 +
 quidem-ut/README.md                                |  74 +++
 quidem-ut/pom.xml                                  | 546 +++++++++++++++++++++
 ...posedAsBrokerQueryComponentSupplierWrapper.java | 370 ++++++++++++++
 .../java/org/apache/druid/quidem/Launcher.java     | 100 ++++
 .../apache/druid/quidem/QuidemCaptureModule.java   |  23 +-
 .../apache/druid/quidem/QuidemCaptureResource.java |  81 +++
 .../org/apache/druid/quidem/QuidemRecorder.java    |  85 ++++
 .../org/apache/druid/quidem/LauncherSmokeTest.java |  76 +++
 .../test/java/org/apache/druid/quidem/QTest.java   |  32 +-
 .../org.apache.druid.quidem.QTest/example.iq       |  46 ++
 .../org/apache/druid/server/StatusResource.java    |   3 +-
 .../sql/calcite/planner/CalciteRulesManager.java   |   5 +-
 .../druid/sql/calcite/planner/PlannerContext.java  |   6 +
 .../druid/sql/calcite/planner/PlannerFactory.java  |   9 +-
 .../druid/sql/calcite/planner/PlannerHook.java     |   4 +
 .../druid/sql/calcite/planner/PlannerToolbox.java  |  11 +-
 .../druid/sql/calcite/planner/QueryHandler.java    |   9 +-
 .../java/org/apache/druid/sql/hook/DruidHook.java  |  75 +++
 .../apache/druid/sql/hook/DruidHookDispatcher.java |  85 ++++
 .../druid/quidem/DruidAvaticaDriverTest.java       |   3 +-
 .../druid/quidem/DruidAvaticaTestDriver.java       |  47 +-
 .../apache/druid/quidem/DruidConnectionExtras.java |  23 +-
 .../druid/quidem/DruidQuidemCommandHandler.java    |  34 +-
 .../apache/druid/quidem/DruidQuidemTestBase.java   |   6 +
 ...st.java => EnabledOnlyInSqlCompatibleMode.java} |  27 +-
 .../org/apache/druid/quidem/SqlQuidemTest.java     |  10 -
 .../org/apache/druid/sql/SqlStatementTest.java     |   4 +-
 .../druid/sql/avatica/DruidAvaticaHandlerTest.java |   4 +-
 .../druid/sql/avatica/DruidStatementTest.java      |   4 +-
 .../druid/sql/calcite/SqlTestFrameworkConfig.java  |  67 ++-
 .../sql/calcite/SqlTestFrameworkConfigTest.java    |  13 +
 .../calcite/SqlVectorizedExpressionSanityTest.java |   4 +-
 .../calcite/expression/ExpressionTestHelper.java   |   6 +-
 .../external/ExternalTableScanRuleTest.java        |   4 +-
 .../calcite/planner/CalcitePlannerModuleTest.java  |   7 +-
 .../sql/calcite/planner/DruidRexExecutorTest.java  |   4 +-
 .../calcite/run/DruidHookTest.java}                |  29 +-
 .../druid/sql/calcite/util/CalciteTests.java       |  24 +-
 .../druid/sql/calcite/util/SqlTestFramework.java   |  91 +++-
 .../org/apache/druid/sql/http/SqlResourceTest.java |   4 +-
 60 files changed, 1963 insertions(+), 177 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0828365f2a7..adaf4d14c2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,7 +27,7 @@ README
 .pmdruleset.xml
 .java-version
 integration-tests/gen-scripts/
-/bin/
+**/bin/
 *.hprof
 **/.ipynb_checkpoints/
 website/.yarn/
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/InPlanningBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/InPlanningBenchmark.java
index ce01324116f..046da257423 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/InPlanningBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/InPlanningBenchmark.java
@@ -55,6 +55,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -202,7 +203,8 @@ public class InPlanningBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     String prefix = ("explain plan for select long1 from foo where long1 in ");
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java
index 3441e31c049..06ea2aa9e19 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlBenchmark.java
@@ -89,6 +89,7 @@ import org.apache.druid.sql.calcite.util.CalciteTests;
 import org.apache.druid.sql.calcite.util.LookylooModule;
 import org.apache.druid.sql.calcite.util.QueryFrameworkUtils;
 import 
org.apache.druid.sql.calcite.util.testoperator.CalciteTestOperatorModule;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
@@ -674,7 +675,8 @@ public class SqlBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         new 
JoinableFactoryWrapper(QueryFrameworkUtils.createDefaultJoinableFactory(injector)),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     return Pair.of(plannerFactory, engine);
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
index 8b2172182a0..d351b40f2db 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
@@ -59,6 +59,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -364,7 +365,8 @@ public class SqlExpressionBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     try {
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlGroupByBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlGroupByBenchmark.java
index bf521b83163..1f58f97b8e8 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlGroupByBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlGroupByBenchmark.java
@@ -57,6 +57,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -347,7 +348,8 @@ public class SqlGroupByBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     try {
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlNestedDataBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlNestedDataBenchmark.java
index c329e9da30e..b896f8df52f 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlNestedDataBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlNestedDataBenchmark.java
@@ -63,6 +63,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -402,7 +403,8 @@ public class SqlNestedDataBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     try {
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java
index bffc03469e8..2150b2100fb 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlVsNativeBenchmark.java
@@ -50,6 +50,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
 import org.openjdk.jmh.annotations.Benchmark;
@@ -132,7 +133,8 @@ public class SqlVsNativeBenchmark
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
     groupByQuery = GroupByQuery
         .builder()
diff --git a/dev/quidem b/dev/quidem
index a68e08b3cfc..1a7d3f0ec96 100755
--- a/dev/quidem
+++ b/dev/quidem
@@ -28,13 +28,13 @@ OPTS+=" -Dsurefire.rerunFailingTestsCount=0"
 OPTS+=" 
-Dorg.slf4j.simpleLogger.log.org.apache.maven.plugin.surefire.SurefirePlugin=INFO"
 [[ $@ =~ "-q" ]] && OPTS+=" -Dsurefire.trimStackTrace=true"
 
-OPTS+=" -pl sql -Dtest=SqlQuidemTest"
+OPTS+=" -pl quidem-ut -Dtest=QTest"
 OPTS+=" org.apache.maven.plugins:maven-surefire-plugin:test"
 
 case "$1" in
  -h|--help)
        cat << EOF
-Run SqlQuidemTest tests.
+Run QTest tests.
   -q                           quiet (recommened)
   -Dquidem.overwrite           enables overwrite mode
   -Dquidem.filter=*join*       runs only tests matching path expression
diff --git a/extensions-contrib/compressed-bigdecimal/pom.xml 
b/extensions-contrib/compressed-bigdecimal/pom.xml
index 33b368b255e..d6c27d88812 100644
--- a/extensions-contrib/compressed-bigdecimal/pom.xml
+++ b/extensions-contrib/compressed-bigdecimal/pom.xml
@@ -166,6 +166,11 @@
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-annotations</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/extensions-contrib/tdigestsketch/pom.xml 
b/extensions-contrib/tdigestsketch/pom.xml
index 489b0b99556..4c192d62746 100644
--- a/extensions-contrib/tdigestsketch/pom.xml
+++ b/extensions-contrib/tdigestsketch/pom.xml
@@ -164,6 +164,11 @@
       <artifactId>junit-vintage-engine</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.easymock</groupId>
       <artifactId>easymock</artifactId>
diff --git a/extensions-core/datasketches/pom.xml 
b/extensions-core/datasketches/pom.xml
index ee423909d19..75ff5a0868e 100644
--- a/extensions-core/datasketches/pom.xml
+++ b/extensions-core/datasketches/pom.xml
@@ -170,6 +170,11 @@
       <artifactId>junit-jupiter-params</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.junit.vintage</groupId>
       <artifactId>junit-vintage-engine</artifactId>
diff --git a/extensions-core/druid-bloom-filter/pom.xml 
b/extensions-core/druid-bloom-filter/pom.xml
index 8ce419095de..b57ede7b16b 100644
--- a/extensions-core/druid-bloom-filter/pom.xml
+++ b/extensions-core/druid-bloom-filter/pom.xml
@@ -139,6 +139,11 @@
       <artifactId>junit-vintage-engine</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.druid</groupId>
       <artifactId>druid-processing</artifactId>
diff --git a/extensions-core/druid-catalog/pom.xml 
b/extensions-core/druid-catalog/pom.xml
index a48a7f1e7b8..c5bafbeb8dc 100644
--- a/extensions-core/druid-catalog/pom.xml
+++ b/extensions-core/druid-catalog/pom.xml
@@ -177,6 +177,11 @@
             <artifactId>junit-vintage-engine</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.easymock</groupId>
             <artifactId>easymock</artifactId>
diff --git a/extensions-core/histogram/pom.xml 
b/extensions-core/histogram/pom.xml
index c252cfb87d3..a1c1f91e379 100644
--- a/extensions-core/histogram/pom.xml
+++ b/extensions-core/histogram/pom.xml
@@ -123,6 +123,11 @@
             <artifactId>junit-vintage-engine</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.druid</groupId>
             <artifactId>druid-processing</artifactId>
diff --git a/extensions-core/multi-stage-query/pom.xml 
b/extensions-core/multi-stage-query/pom.xml
index 9e637acff00..ab23a26e36f 100644
--- a/extensions-core/multi-stage-query/pom.xml
+++ b/extensions-core/multi-stage-query/pom.xml
@@ -233,6 +233,11 @@
             <artifactId>junit-jupiter-params</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.easymock</groupId>
             <artifactId>easymock</artifactId>
diff --git 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/ResultsContextSerdeTest.java
 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/ResultsContextSerdeTest.java
index b6023392576..72f2c817622 100644
--- 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/ResultsContextSerdeTest.java
+++ 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/ResultsContextSerdeTest.java
@@ -43,6 +43,7 @@ import org.apache.druid.sql.calcite.schema.NamedDruidSchema;
 import org.apache.druid.sql.calcite.schema.NamedViewSchema;
 import org.apache.druid.sql.calcite.schema.ViewSchema;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Before;
@@ -75,7 +76,8 @@ public class ResultsContextSerdeTest
         "druid",
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.TEST_AUTHORIZER_MAPPER,
-        AuthConfig.newBuilder().build()
+        AuthConfig.newBuilder().build(),
+        new DruidHookDispatcher()
     );
     final NativeSqlEngine engine = CalciteTests.createMockSqlEngine(
         EasyMock.createMock(QuerySegmentWalker.class),
diff --git 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
index 080ba843204..5076e9f3d44 100644
--- 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
+++ 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
@@ -186,6 +186,7 @@ import 
org.apache.druid.sql.calcite.util.SqlTestFramework.StandardComponentSuppl
 import org.apache.druid.sql.calcite.util.TestDataBuilder;
 import org.apache.druid.sql.calcite.view.InProcessViewManager;
 import org.apache.druid.sql.guice.SqlBindings;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.storage.StorageConfig;
 import org.apache.druid.storage.StorageConnector;
 import org.apache.druid.storage.StorageConnectorModule;
@@ -562,7 +563,8 @@ public class MSQTestBase extends BaseCalciteQueryTest
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         catalogResolver,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     sqlStatementFactory = CalciteTests.createSqlStatementFactory(engine, 
plannerFactory);
diff --git a/extensions-core/stats/pom.xml b/extensions-core/stats/pom.xml
index 1b3a531dc42..048ad964bf8 100644
--- a/extensions-core/stats/pom.xml
+++ b/extensions-core/stats/pom.xml
@@ -123,7 +123,11 @@
             <artifactId>junit-vintage-engine</artifactId>
             <scope>test</scope>
         </dependency>
-
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.druid</groupId>
             <artifactId>druid-processing</artifactId>
diff --git a/extensions-core/testing-tools/pom.xml 
b/extensions-core/testing-tools/pom.xml
index 086d32559d3..fd0eed880c2 100644
--- a/extensions-core/testing-tools/pom.xml
+++ b/extensions-core/testing-tools/pom.xml
@@ -145,6 +145,11 @@
       <artifactId>junit-jupiter-params</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.junit.vintage</groupId>
       <artifactId>junit-vintage-engine</artifactId>
diff --git a/pom.xml b/pom.xml
index ea9c3dcfa17..11738830512 100644
--- a/pom.xml
+++ b/pom.xml
@@ -242,6 +242,7 @@
         <module>integration-tests-ex/tools</module>
         <module>integration-tests-ex/image</module>
         <module>integration-tests-ex/cases</module>
+        <module>quidem-ut</module>
     </modules>
 
     <repositories>
diff --git a/quidem-ut/README.md b/quidem-ut/README.md
new file mode 100644
index 00000000000..5b594a3c8f4
--- /dev/null
+++ b/quidem-ut/README.md
@@ -0,0 +1,74 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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
+  ~
+  ~   http://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.
+  -->
+
+# Quidem UT
+
+Enables to write sql level tests easily.
+Can be used to write tests against existing test backends (ComponentSupplier) 
- by doing so the testcases can be moved closer to the excercised codes.
+
+## Usage
+
+### Install java&maven (if needed)
+
+If you don't have java&maven - one way to set that up is by using sdkman like 
this:
+```bash
+# install sdkman
+curl -s "https://get.sdkman.io"; | bash
+# at the end of installation either open a new terminal; or follow the 
instructions at the end
+
+# install java&maven
+sdk install java 11.0.23-zulu
+sdk install maven
+
+# run mvn to see if it works
+mvn --version
+
+# download druid sourcces (FIXME: change this to the main repo/branch before 
merging)
+git clone --branch quidem-record https://github.com/kgyrtkirk/druid
+```
+
+
+
+
+### Running these tests
+
+* CI execution happens by a standard JUnit test `QTest` in this module
+* the `dev/quidem` script can be used to run these tests (after the project is 
built)
+
+### Launching a test generating broker
+
+* make sure to build the project first; one way to do that is:
+  ```bash
+  mvn install -pl quidem-ut/ -am -DskipTests -Pskip-static-checks
+  ```
+* launch the broker instance with:
+  ```bash
+  mvn exec:exec -pl quidem-ut -Dquidem.record.autostart=true
+  ```
+  * the broker will be running at http://localhost:12345
+  * the used test configuration backend can configured by supplying 
`quidem.uri`
+    ```bash
+    mvn exec:exec -pl quidem-ut 
-Dquidem.uri=druidtest:///?componentSupplier=ThetaSketchComponentSupplier
+    ``` 
+  * new record files can be started by calling 
http://localhost:12345/quidem/start
+    * if `quidem.record.autostart` is omitted recording will not start
+* after finished with the usage of the broker ; a new `record-123.iq` file 
will be in the project's worktree - it will contain all the executed statements
+  * running `dev/quidem -Dquidem.overwrite` updates the resultsets of all `iq` 
files around there
+  * rename the testfile to have a more descriptive name
+
diff --git a/quidem-ut/pom.xml b/quidem-ut/pom.xml
new file mode 100644
index 00000000000..ac48af25c17
--- /dev/null
+++ b/quidem-ut/pom.xml
@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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
+  ~
+  ~   http://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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>druid-quidem-ut</artifactId>
+    <name>druid-quidem-ut</name>
+    <description>druid-quidem-ut</description>
+
+    <parent>
+        <groupId>org.apache.druid</groupId>
+        <artifactId>druid</artifactId>
+        <version>31.0.0-SNAPSHOT</version>
+    </parent>
+
+    <repositories>
+        <repository>
+            <id>confluent</id>
+            <url>https://packages.confluent.io/maven/</url>
+        </repository>
+    </repositories>
+
+    <properties>
+        <quidem.uri>druidtest:///</quidem.uri>
+        <quidem.record.autostart>false</quidem.record.autostart>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>amazon-kinesis-producer</artifactId>
+            <version>0.13.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.avro</groupId>
+            <artifactId>avro</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.rvesse</groupId>
+            <artifactId>airline</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-kinesis</artifactId>
+            <version>${aws.sdk.version}</version>
+            <exclusions>
+                <!-- aws-java-sdk-core is provided by Druid core. -->
+                <exclusion>
+                    <groupId>com.amazonaws</groupId>
+                    <artifactId>aws-java-sdk-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-core</artifactId>
+            <version>${aws.sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+            <version>${aws.sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-orc-extensions</artifactId>
+            <version>${project.parent.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.squareup.okhttp</groupId>
+                    <artifactId>okhttp</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-parquet-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-avro-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-protobuf-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-s3-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-kinesis-indexing-service</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-azure-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-google-extensions</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-hdfs-storage</artifactId>
+            <version>${project.parent.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.amazonaws</groupId>
+                    <artifactId>aws-java-sdk-bundle</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-datasketches</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-histogram</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-aws-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-processing</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-sql</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-indexing-service</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>mysql-metadata-storage</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-kafka-indexing-service</artifactId>
+            <version>${project.parent.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.kafka</groupId>
+                    <artifactId>kafka-clients</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-basic-security</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-lookups-cached-global</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-testing-tools</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>simple-client-sslcontext</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-multi-stage-query</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-services</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-server</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+            <version>${apache.kafka.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-smile-provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.inject</groupId>
+            <artifactId>jakarta.inject-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.github.docker-java</groupId>
+                    <artifactId>docker-java-transport-jersey</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-transport-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.hydromatic</groupId>
+            <artifactId>quidem</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-native-kqueue</artifactId>
+            <classifier>osx-x86_64</classifier>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-native-kqueue</artifactId>
+            <classifier>osx-aarch_64</classifier>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.confluent</groupId>
+            <artifactId>kafka-schema-registry-client</artifactId>
+            <version>6.2.12</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.avro</groupId>
+                    <artifactId>avro</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>javax.ws.rs-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>javax.ws.rs-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>jakarta.ws.rs</groupId>
+                    <artifactId>jakarta.ws.rs-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.confluent</groupId>
+            <artifactId>kafka-protobuf-provider</artifactId>
+            <version>6.2.12</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-sql</artifactId>
+            <type>test-jar</type>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-processing</artifactId>
+            <type>test-jar</type>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.druid</groupId>
+            <artifactId>druid-server</artifactId>
+            <type>test-jar</type>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.druid.extensions</groupId>
+            <artifactId>druid-datasketches</artifactId>
+            <type>test-jar</type>
+            <version>${project.parent.version}</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+
+        <!-- Tests -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.vintage</groupId>
+            <artifactId>junit-vintage-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.calcite.avatica</groupId>
+            <artifactId>avatica</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.calcite.avatica</groupId>
+            <artifactId>avatica-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.calcite</groupId>
+            <artifactId>calcite-testkit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.os72</groupId>
+            <artifactId>protobuf-dynamic</artifactId>
+            <version>0.9.3</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-sts</artifactId>
+            <version>${aws.sdk.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.datasketches</groupId>
+            <artifactId>datasketches-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.owasp</groupId>
+                <artifactId>dependency-check-maven</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <executable>java</executable>
+                    <arguments>
+                        <argument>-classpath</argument>
+                        <classpath />
+                        <argument>-Dquidem.uri=${quidem.uri}</argument>
+                        
<argument>-Dquidem.record.autostart=${quidem.record.autostart}</argument>
+                        <argument>org.apache.druid.quidem.Launcher</argument>
+                    </arguments>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/quidem-ut/src/main/java/org/apache/druid/quidem/ExposedAsBrokerQueryComponentSupplierWrapper.java
 
b/quidem-ut/src/main/java/org/apache/druid/quidem/ExposedAsBrokerQueryComponentSupplierWrapper.java
new file mode 100644
index 00000000000..f853dd317d1
--- /dev/null
+++ 
b/quidem-ut/src/main/java/org/apache/druid/quidem/ExposedAsBrokerQueryComponentSupplierWrapper.java
@@ -0,0 +1,370 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.quidem;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.name.Names;
+import org.apache.druid.cli.CliBroker;
+import org.apache.druid.cli.QueryJettyServerInitializer;
+import org.apache.druid.client.BrokerSegmentWatcherConfig;
+import org.apache.druid.client.BrokerServerView;
+import org.apache.druid.client.InternalQueryConfig;
+import org.apache.druid.client.TimelineServerView;
+import org.apache.druid.client.selector.CustomTierSelectorStrategyConfig;
+import org.apache.druid.client.selector.ServerSelectorStrategy;
+import org.apache.druid.client.selector.TierSelectorStrategy;
+import org.apache.druid.curator.CuratorModule;
+import org.apache.druid.curator.discovery.DiscoveryModule;
+import org.apache.druid.discovery.DruidNodeDiscoveryProvider;
+import org.apache.druid.guice.AnnouncerModule;
+import org.apache.druid.guice.BrokerProcessingModule;
+import org.apache.druid.guice.BrokerServiceModule;
+import org.apache.druid.guice.BuiltInTypesModule;
+import org.apache.druid.guice.CoordinatorDiscoveryModule;
+import org.apache.druid.guice.DruidInjectorBuilder;
+import org.apache.druid.guice.ExpressionModule;
+import org.apache.druid.guice.ExtensionsModule;
+import org.apache.druid.guice.JacksonConfigManagerModule;
+import org.apache.druid.guice.JavaScriptModule;
+import org.apache.druid.guice.Jerseys;
+import org.apache.druid.guice.JoinableFactoryModule;
+import org.apache.druid.guice.JsonConfigProvider;
+import org.apache.druid.guice.LazySingleton;
+import org.apache.druid.guice.LifecycleModule;
+import org.apache.druid.guice.LocalDataStorageDruidModule;
+import org.apache.druid.guice.MetadataConfigModule;
+import org.apache.druid.guice.QueryRunnerFactoryModule;
+import org.apache.druid.guice.SegmentWranglerModule;
+import org.apache.druid.guice.ServerModule;
+import org.apache.druid.guice.ServerTypeConfig;
+import org.apache.druid.guice.ServerViewModule;
+import org.apache.druid.guice.StartupLoggingModule;
+import org.apache.druid.guice.StorageNodeModule;
+import org.apache.druid.guice.annotations.Client;
+import org.apache.druid.guice.annotations.EscalatedClient;
+import org.apache.druid.guice.annotations.Json;
+import org.apache.druid.guice.http.HttpClientModule;
+import org.apache.druid.guice.security.AuthenticatorModule;
+import org.apache.druid.guice.security.AuthorizerModule;
+import org.apache.druid.guice.security.DruidAuthModule;
+import org.apache.druid.initialization.CoreInjectorBuilder;
+import org.apache.druid.initialization.Log4jShutterDownerModule;
+import org.apache.druid.initialization.ServerInjectorBuilder;
+import org.apache.druid.initialization.TombstoneDataStorageModule;
+import org.apache.druid.java.util.common.io.Closer;
+import org.apache.druid.metadata.storage.derby.DerbyMetadataStorageDruidModule;
+import org.apache.druid.query.QueryRunnerFactoryConglomerate;
+import org.apache.druid.query.QuerySegmentWalker;
+import org.apache.druid.query.RetryQueryRunnerConfig;
+import org.apache.druid.query.lookup.LookupExtractorFactoryContainerProvider;
+import org.apache.druid.rpc.guice.ServiceClientModule;
+import org.apache.druid.segment.join.JoinableFactoryWrapper;
+import org.apache.druid.segment.writeout.SegmentWriteOutMediumModule;
+import org.apache.druid.server.BrokerQueryResource;
+import org.apache.druid.server.ClientInfoResource;
+import org.apache.druid.server.DruidNode;
+import org.apache.druid.server.QueryLifecycleFactory;
+import org.apache.druid.server.QuerySchedulerProvider;
+import org.apache.druid.server.ResponseContextConfig;
+import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker;
+import org.apache.druid.server.SubqueryGuardrailHelper;
+import org.apache.druid.server.SubqueryGuardrailHelperProvider;
+import org.apache.druid.server.coordination.ServerType;
+import org.apache.druid.server.emitter.EmitterModule;
+import org.apache.druid.server.http.BrokerResource;
+import org.apache.druid.server.http.SelfDiscoveryResource;
+import org.apache.druid.server.initialization.AuthorizerMapperModule;
+import 
org.apache.druid.server.initialization.ExternalStorageAccessSecurityModule;
+import org.apache.druid.server.initialization.jetty.JettyServerInitializer;
+import org.apache.druid.server.initialization.jetty.JettyServerModule;
+import org.apache.druid.server.log.NoopRequestLogger;
+import org.apache.druid.server.log.RequestLogger;
+import org.apache.druid.server.metrics.QueryCountStatsProvider;
+import org.apache.druid.server.metrics.SubqueryCountStatsProvider;
+import org.apache.druid.server.router.TieredBrokerConfig;
+import org.apache.druid.server.security.AuthenticatorMapper;
+import org.apache.druid.server.security.Escalator;
+import org.apache.druid.server.security.TLSCertificateCheckerModule;
+import org.apache.druid.sql.calcite.planner.CalciteRulesManager;
+import org.apache.druid.sql.calcite.planner.CatalogResolver;
+import org.apache.druid.sql.calcite.run.NativeSqlEngine;
+import org.apache.druid.sql.calcite.run.SqlEngine;
+import org.apache.druid.sql.calcite.schema.BrokerSegmentMetadataCache;
+import org.apache.druid.sql.calcite.schema.DruidSchemaName;
+import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.calcite.util.SqlTestFramework;
+import org.apache.druid.sql.calcite.util.SqlTestFramework.Builder;
+import 
org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier;
+import 
org.apache.druid.sql.calcite.util.SqlTestFramework.QueryComponentSupplier;
+import org.apache.druid.sql.guice.SqlModule;
+import org.apache.druid.storage.StorageConnectorModule;
+import org.apache.druid.timeline.PruneLoadSpec;
+import org.eclipse.jetty.server.Server;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * A wrapper class to expose a {@link QueryComponentSupplier} as a Broker 
service.
+ */
+public class ExposedAsBrokerQueryComponentSupplierWrapper implements 
QueryComponentSupplier
+{
+  private QueryComponentSupplier delegate;
+
+  public ExposedAsBrokerQueryComponentSupplierWrapper(QueryComponentSupplier 
delegate)
+  {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public void gatherProperties(Properties properties)
+  {
+    delegate.gatherProperties(properties);
+  }
+
+  @Override
+  public void configureGuice(DruidInjectorBuilder builder)
+  {
+  }
+
+  @Override
+  public void configureGuice(CoreInjectorBuilder builder, List<Module> 
overrideModules)
+  {
+    delegate.configureGuice(builder);
+
+    installForServerModules(builder);
+
+    
overrideModules.addAll(ExposedAsBrokerQueryComponentSupplierWrapper.brokerModules());
+    overrideModules.add(new BrokerTestModule());
+    builder.add(QuidemCaptureModule.class);
+  }
+
+  @Override
+  public QueryRunnerFactoryConglomerate createCongolmerate(Builder builder, 
Closer closer, ObjectMapper om)
+  {
+    return delegate.createCongolmerate(builder, closer, om);
+  }
+
+  @Override
+  public SpecificSegmentsQuerySegmentWalker 
createQuerySegmentWalker(QueryRunnerFactoryConglomerate conglomerate,
+      JoinableFactoryWrapper joinableFactory, Injector injector)
+  {
+    return delegate.createQuerySegmentWalker(conglomerate, joinableFactory, 
injector);
+  }
+
+  @Override
+  public SqlEngine createEngine(QueryLifecycleFactory qlf, ObjectMapper 
objectMapper, Injector injector)
+  {
+    return delegate.createEngine(qlf, objectMapper, injector);
+  }
+
+  @Override
+  public void configureJsonMapper(ObjectMapper mapper)
+  {
+    delegate.configureJsonMapper(mapper);
+  }
+
+  @Override
+  public JoinableFactoryWrapper 
createJoinableFactoryWrapper(LookupExtractorFactoryContainerProvider 
lookupProvider)
+  {
+    return delegate.createJoinableFactoryWrapper(lookupProvider);
+  }
+
+  @Override
+  public void finalizeTestFramework(SqlTestFramework sqlTestFramework)
+  {
+    delegate.finalizeTestFramework(sqlTestFramework);
+  }
+
+  @Override
+  public void close() throws IOException
+  {
+    delegate.close();
+  }
+
+  @Override
+  public PlannerComponentSupplier getPlannerComponentSupplier()
+  {
+    return delegate.getPlannerComponentSupplier();
+  }
+
+  public static class BrokerTestModule extends AbstractModule
+  {
+    @Override
+    protected void configure()
+    {
+      
bind(AuthenticatorMapper.class).toInstance(CalciteTests.TEST_AUTHENTICATOR_MAPPER);
+      
bind(Escalator.class).toInstance(CalciteTests.TEST_AUTHENTICATOR_ESCALATOR);
+      bind(RequestLogger.class).toInstance(new NoopRequestLogger());
+      bind(String.class)
+          .annotatedWith(DruidSchemaName.class)
+          .toInstance(CalciteTests.DRUID_SCHEMA_NAME);
+      bind(QuerySchedulerProvider.class).in(LazySingleton.class);
+      bind(CalciteRulesManager.class).toInstance(new 
CalciteRulesManager(ImmutableSet.of()));
+      bind(CatalogResolver.class).toInstance(CatalogResolver.NULL_RESOLVER);
+    }
+
+    @Provides
+    @LazySingleton
+    public BrokerSegmentMetadataCache provideCache()
+    {
+      return null;
+    }
+
+    @Provides
+    @LazySingleton
+    public Properties getProps()
+    {
+      Properties localProps = new Properties();
+      localProps.put("druid.enableTlsPort", "false");
+      localProps.put("druid.zk.service.enabled", "false");
+      localProps.put("druid.plaintextPort", "12345");
+      localProps.put("druid.host", "localhost");
+      localProps.put("druid.broker.segment.awaitInitializationOnStart", 
"false");
+      return localProps;
+    }
+
+    @Provides
+    @LazySingleton
+    public SqlEngine createMockSqlEngine(
+        final QuerySegmentWalker walker,
+        final QueryRunnerFactoryConglomerate conglomerate,
+        @Json ObjectMapper jsonMapper)
+    {
+      return new 
NativeSqlEngine(CalciteTests.createMockQueryLifecycleFactory(walker, 
conglomerate), jsonMapper);
+    }
+
+    @Provides
+    @LazySingleton
+    DruidNodeDiscoveryProvider getDruidNodeDiscoveryProvider()
+    {
+      final DruidNode coordinatorNode = CalciteTests.mockCoordinatorNode();
+      return CalciteTests.mockDruidNodeDiscoveryProvider(coordinatorNode);
+    }
+  }
+
+  /**
+   * Closely related to {@link CoreInjectorBuilder#forServer()}
+   */
+  private void installForServerModules(CoreInjectorBuilder builder)
+  {
+
+    builder.add(
+        new Log4jShutterDownerModule(),
+        new LifecycleModule(),
+        ExtensionsModule.SecondaryModule.class,
+        new DruidAuthModule(),
+        TLSCertificateCheckerModule.class,
+        EmitterModule.class,
+        HttpClientModule.global(),
+        HttpClientModule.escalatedGlobal(),
+        new HttpClientModule("druid.broker.http", Client.class, true),
+        new HttpClientModule("druid.broker.http", EscalatedClient.class, true),
+        new CuratorModule(),
+        new AnnouncerModule(),
+        new SegmentWriteOutMediumModule(),
+        new ServerModule(),
+        new StorageNodeModule(),
+        new JettyServerModule(),
+        new ExpressionModule(),
+        new BuiltInTypesModule(),
+        new DiscoveryModule(),
+        new ServerViewModule(),
+        new MetadataConfigModule(),
+        new DerbyMetadataStorageDruidModule(),
+        new JacksonConfigManagerModule(),
+        new CoordinatorDiscoveryModule(),
+        new LocalDataStorageDruidModule(),
+        new TombstoneDataStorageModule(),
+        new JavaScriptModule(),
+        new AuthenticatorModule(),
+        new AuthorizerModule(),
+        new AuthorizerMapperModule(),
+        new StartupLoggingModule(),
+        new ExternalStorageAccessSecurityModule(),
+        new ServiceClientModule(),
+        new StorageConnectorModule(),
+        new SqlModule(),
+        ServerInjectorBuilder.registerNodeRoleModule(ImmutableSet.of())
+    );
+  }
+
+  /**
+   * Closely related to {@link CliBroker#getModules}.
+   */
+  static List<? extends Module> brokerModules()
+  {
+    return ImmutableList.of(
+        new BrokerProcessingModule(),
+        new QueryRunnerFactoryModule(),
+        new SegmentWranglerModule(),
+        new JoinableFactoryModule(),
+        new BrokerServiceModule(),
+        binder -> {
+
+          binder.bindConstant().annotatedWith(Names.named("serviceName")).to(
+              TieredBrokerConfig.DEFAULT_BROKER_SERVICE_NAME
+          );
+          
binder.bindConstant().annotatedWith(Names.named("servicePort")).to(8082);
+          
binder.bindConstant().annotatedWith(Names.named("tlsServicePort")).to(8282);
+          binder.bindConstant().annotatedWith(PruneLoadSpec.class).to(true);
+          
binder.bind(ResponseContextConfig.class).toInstance(ResponseContextConfig.newConfig(false));
+
+          
binder.bind(TimelineServerView.class).to(BrokerServerView.class).in(LazySingleton.class);
+
+          JsonConfigProvider.bind(binder, "druid.broker.select", 
TierSelectorStrategy.class);
+          JsonConfigProvider.bind(binder, "druid.broker.select.tier.custom", 
CustomTierSelectorStrategyConfig.class);
+          JsonConfigProvider.bind(binder, "druid.broker.balancer", 
ServerSelectorStrategy.class);
+          JsonConfigProvider.bind(binder, "druid.broker.retryPolicy", 
RetryQueryRunnerConfig.class);
+          JsonConfigProvider.bind(binder, "druid.broker.segment", 
BrokerSegmentWatcherConfig.class);
+          JsonConfigProvider.bind(binder, 
"druid.broker.internal.query.config", InternalQueryConfig.class);
+          
binder.bind(JettyServerInitializer.class).to(QueryJettyServerInitializer.class).in(LazySingleton.class);
+
+          binder.bind(BrokerQueryResource.class).in(LazySingleton.class);
+          Jerseys.addResource(binder, BrokerQueryResource.class);
+          
binder.bind(SubqueryGuardrailHelper.class).toProvider(SubqueryGuardrailHelperProvider.class);
+          
binder.bind(QueryCountStatsProvider.class).to(BrokerQueryResource.class).in(LazySingleton.class);
+          binder.bind(SubqueryCountStatsProvider.class).toInstance(new 
SubqueryCountStatsProvider());
+          Jerseys.addResource(binder, BrokerResource.class);
+          Jerseys.addResource(binder, ClientInfoResource.class);
+
+          LifecycleModule.register(binder, BrokerQueryResource.class);
+
+          LifecycleModule.register(binder, Server.class);
+          binder.bind(ServerTypeConfig.class).toInstance(new 
ServerTypeConfig(ServerType.BROKER));
+
+          binder.bind(String.class)
+              .annotatedWith(DruidSchemaName.class)
+              .toInstance(CalciteTests.DRUID_SCHEMA_NAME);
+
+          Jerseys.addResource(binder, SelfDiscoveryResource.class);
+          LifecycleModule.registerKey(binder, 
Key.get(SelfDiscoveryResource.class));
+        }
+    );
+  }
+}
diff --git a/quidem-ut/src/main/java/org/apache/druid/quidem/Launcher.java 
b/quidem-ut/src/main/java/org/apache/druid/quidem/Launcher.java
new file mode 100644
index 00000000000..b185084e0a7
--- /dev/null
+++ b/quidem-ut/src/main/java/org/apache/druid/quidem/Launcher.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.quidem;
+
+import com.google.common.base.Stopwatch;
+import org.apache.druid.cli.GuiceRunnable;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.sql.calcite.SqlTestFrameworkConfig;
+import 
org.apache.druid.sql.calcite.SqlTestFrameworkConfig.ConfigurationInstance;
+import 
org.apache.druid.sql.calcite.SqlTestFrameworkConfig.SqlTestFrameworkConfigStore;
+import org.apache.druid.sql.calcite.util.SqlTestFramework;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+public class Launcher
+{
+  static final SqlTestFrameworkConfigStore CONFIG_STORE = new 
SqlTestFrameworkConfigStore(
+      x -> new ExposedAsBrokerQueryComponentSupplierWrapper(x)
+  );
+  private static final String QUIDEM_URI = "quidem.uri";
+  private static Logger log = new Logger(Launcher.class);
+  private final SqlTestFramework framework;
+  private final ConfigurationInstance configurationInstance;
+  private Lifecycle lifecycle;
+
+  public Launcher(String uri) throws Exception
+  {
+    SqlTestFrameworkConfig config = SqlTestFrameworkConfig.fromURL(uri);
+    configurationInstance = CONFIG_STORE.getConfigurationInstance(config);
+    framework = configurationInstance.framework;
+  }
+
+  public void start() throws Exception
+  {
+    lifecycle = GuiceRunnable.initLifecycle(framework.injector(), log);
+    if (withAutoStart()) {
+      callQuidemStart();
+    }
+  }
+
+  private void callQuidemStart() throws Exception
+  {
+    CloseableHttpClient client = HttpClients.createDefault();
+    HttpGet request = new HttpGet("http://localhost:12345/quidem/start";);
+    request.addHeader("Content-Type", "application/json");
+    client.execute(request);
+  }
+
+  private boolean withAutoStart()
+  {
+    return Boolean.valueOf(System.getProperty("quidem.record.autostart", 
"false"));
+  }
+
+  public void shutdown()
+  {
+    lifecycle.stop();
+  }
+
+  public static void main(String[] args) throws Exception
+  {
+    String quidemUri = System.getProperty(QUIDEM_URI, "druidtest:///");
+    Properties p = System.getProperties();
+    for (Entry<Object, Object> entry : p.entrySet()) {
+      Object key = entry.getKey();
+      if (key.toString().startsWith("quidem")) {
+        log.info("[%s] -> %s", key, entry.getValue());
+      }
+    }
+    log.info("Starting Quidem with URI[%s]", quidemUri);
+    Stopwatch stopwatch = Stopwatch.createStarted();
+    Launcher launcher = new Launcher(quidemUri);
+    log.info("Framework creation time: %d ms", 
stopwatch.elapsed(TimeUnit.MILLISECONDS));
+    launcher.start();
+    log.info("Total time to launch: %d ms", 
stopwatch.elapsed(TimeUnit.MILLISECONDS));
+    launcher.lifecycle.join();
+  }
+}
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java 
b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureModule.java
similarity index 65%
copy from sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
copy to quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureModule.java
index 75bdd4280fa..17d9009994e 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
+++ b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureModule.java
@@ -19,25 +19,16 @@
 
 package org.apache.druid.quidem;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import org.apache.druid.guice.Jerseys;
 
-public interface DruidConnectionExtras
+public class QuidemCaptureModule implements Module
 {
-  ObjectMapper getObjectMapper();
 
-  class DruidConnectionExtrasImpl implements DruidConnectionExtras
+  @Override
+  public void configure(Binder binder)
   {
-    private final ObjectMapper objectMapper;
-
-    public DruidConnectionExtrasImpl(ObjectMapper objectMapper)
-    {
-      this.objectMapper = objectMapper;
-    }
-
-    @Override
-    public ObjectMapper getObjectMapper()
-    {
-      return objectMapper;
-    }
+    Jerseys.addResource(binder, QuidemCaptureResource.class);
   }
 }
diff --git 
a/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureResource.java 
b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureResource.java
new file mode 100644
index 00000000000..68d2f31ac81
--- /dev/null
+++ b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemCaptureResource.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.quidem;
+
+import com.google.inject.Inject;
+import org.apache.druid.guice.LazySingleton;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
+
+import javax.inject.Named;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import java.io.File;
+import java.net.URI;
+
+@Path("/quidem")
+@LazySingleton
+public class QuidemCaptureResource
+{
+  public static final File RECORD_PATH = ProjectPathUtils
+      
.getPathFromProjectRoot("quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest");
+  private URI quidemURI;
+  private QuidemRecorder recorder = null;
+  private DruidHookDispatcher hookDispatcher;
+
+  @Inject
+  public QuidemCaptureResource(@Named("quidem") URI quidemURI, 
DruidHookDispatcher hookDispatcher)
+  {
+    this.quidemURI = quidemURI;
+    this.hookDispatcher = hookDispatcher;
+  }
+
+  @GET
+  @Path("/start")
+  @Produces(MediaType.TEXT_PLAIN)
+  public synchronized String start()
+  {
+    stopIfRunning();
+    recorder = new QuidemRecorder(
+        quidemURI,
+        hookDispatcher,
+        genRecordFilePath()
+    );
+    return recorder.toString();
+  }
+
+  private File genRecordFilePath()
+  {
+    String fileName = StringUtils.format("record-%d.iq", 
System.currentTimeMillis());
+    return new File(RECORD_PATH, fileName);
+  }
+
+  private synchronized void stopIfRunning()
+  {
+    if (recorder != null) {
+      recorder.close();
+      recorder = null;
+    }
+
+  }
+}
diff --git 
a/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemRecorder.java 
b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemRecorder.java
new file mode 100644
index 00000000000..5f2d6a192ed
--- /dev/null
+++ b/quidem-ut/src/main/java/org/apache/druid/quidem/QuidemRecorder.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.quidem;
+
+import org.apache.druid.sql.hook.DruidHook;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+public class QuidemRecorder implements AutoCloseable, DruidHook<String>
+{
+  private PrintStream printStream;
+  private File file;
+  private DruidHookDispatcher hookDispatcher;
+
+  public QuidemRecorder(URI quidemURI, DruidHookDispatcher hookDispatcher, 
File file)
+  {
+    this.hookDispatcher = hookDispatcher;
+    this.file = file;
+    try {
+      this.printStream = new PrintStream(new FileOutputStream(file), true, 
StandardCharsets.UTF_8.name());
+    }
+    catch (UnsupportedEncodingException | FileNotFoundException e) {
+      throw new RuntimeException(e);
+    }
+    printStream.println("#started " + new Date());
+    printStream.println("!use " + quidemURI);
+    printStream.println("!set outputformat mysql");
+    hookDispatcher.register(DruidHook.SQL, this);
+  }
+
+  @Override
+  public synchronized void close()
+  {
+    if (printStream != null) {
+      printStream.close();
+      printStream = null;
+    }
+    hookDispatcher.unregister(DruidHook.SQL, this);
+  }
+
+  @Override
+  public synchronized void invoke(HookKey<String> key, String query)
+  {
+    if (DruidHook.SQL.equals(key)) {
+      printStream.println("# " + new Date());
+      printStream.print(query);
+      printStream.println(";");
+      printStream.println("!ok");
+      printStream.flush();
+      return;
+    }
+  }
+
+  @Override
+  public String toString()
+  {
+    return "QuidemRecorder [file=" + file + "]";
+  }
+
+}
diff --git 
a/quidem-ut/src/test/java/org/apache/druid/quidem/LauncherSmokeTest.java 
b/quidem-ut/src/test/java/org/apache/druid/quidem/LauncherSmokeTest.java
new file mode 100644
index 00000000000..6402cd63e43
--- /dev/null
+++ b/quidem-ut/src/test/java/org/apache/druid/quidem/LauncherSmokeTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.quidem;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class LauncherSmokeTest
+{
+  private static Launcher launcher;
+
+  @BeforeClass
+  public static void setUp() throws Exception
+  {
+    launcher = new Launcher("druidtest:///");
+    launcher.start();
+  }
+
+  @AfterClass
+  public static void tearDown()
+  {
+    launcher.shutdown();
+  }
+
+  @Test
+  public void chkSelectFromFoo() throws Exception
+  {
+    CloseableHttpClient client = HttpClients.createDefault();
+    HttpPost request = new HttpPost("http://localhost:12345/druid/v2/sql";);
+    request.addHeader("Content-Type", "application/json");
+    request.setEntity(new StringEntity("{\"query\":\"Select * from foo\"}"));
+    CloseableHttpResponse response = client.execute(request);
+    assertEquals(200, response.getStatusLine().getStatusCode());
+  }
+
+  @Test
+  public void chkStatusWorks() throws Exception
+  {
+    CloseableHttpClient client = HttpClients.createDefault();
+    HttpGet request = new HttpGet("http://localhost:12345/status";);
+    request.addHeader("Content-Type", "application/json");
+    CloseableHttpResponse response = client.execute(request);
+    assertEquals(200, response.getStatusLine().getStatusCode());
+    String responseStr = EntityUtils.toString(response.getEntity());
+    MatcherAssert.assertThat(responseStr, 
Matchers.containsString("\"version\":\""));
+  }
+}
diff --git a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java 
b/quidem-ut/src/test/java/org/apache/druid/quidem/QTest.java
similarity index 57%
copy from sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
copy to quidem-ut/src/test/java/org/apache/druid/quidem/QTest.java
index d1922472d7f..83167a18a16 100644
--- a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
+++ b/quidem-ut/src/test/java/org/apache/druid/quidem/QTest.java
@@ -19,21 +19,17 @@
 
 package org.apache.druid.quidem;
 
-import org.apache.druid.common.config.NullHandling;
-import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.api.Test;
 
 import java.io.File;
+import java.io.IOException;
 
-@EnabledIf(value = "enabled", disabledReason = "These tests are only run in 
SqlCompatible mode!")
-public class SqlQuidemTest extends DruidQuidemTestBase
-{
-  public static boolean enabled()
-  {
-    NullHandling.initializeForTests();
-    return NullHandling.sqlCompatible();
-  }
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 
-  public SqlQuidemTest()
+public class QTest extends DruidQuidemTestBase
+{
+  public QTest()
   {
     super();
   }
@@ -41,6 +37,18 @@ public class SqlQuidemTest extends DruidQuidemTestBase
   @Override
   protected File getTestRoot()
   {
-    return ProjectPathUtils.getPathFromProjectRoot("sql/src/test/quidem/" + 
getClass().getName());
+    return 
ProjectPathUtils.getPathFromProjectRoot("quidem-ut/src/test/quidem/" + 
getClass().getName());
+  }
+
+  @Test
+  public void ensureNoRecordFilesPresent() throws IOException
+  {
+    // ensure that the captured ones are saved into this test's input path
+    assertEquals(QuidemCaptureResource.RECORD_PATH, getTestRoot());
+    for (String name : getFileNames()) {
+      if (name.startsWith("record-")) {
+        fail("Record file found: " + name);
+      }
+    }
   }
 }
diff --git a/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/example.iq 
b/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/example.iq
new file mode 100644
index 00000000000..3a8105927ee
--- /dev/null
+++ b/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/example.iq
@@ -0,0 +1,46 @@
+#started
+!use druidtest:///
+!set outputformat mysql
+
+select * from numfoo;
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+| __time                  | dim1 | dim2 | dim3      | dim4 | dim5 | dim6 | d1  
| d2  | f1  | f2  | l1     | l2     | cnt | m1  | m2  | unique_dim1        |
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+| 2000-01-01 00:00:00.000 |      | a    | ["a","b"] | a    | aa   | 1    | 1.0 
|     | 1.0 |     |      7 |        |   1 | 1.0 | 1.0 | "AQAAAEAAAA=="     |
+| 2000-01-02 00:00:00.000 | 10.1 |      | ["b","c"] | a    | ab   | 2    | 1.7 
| 1.7 | 0.1 | 0.1 | 325323 | 325323 |   1 | 2.0 | 2.0 | "AQAAAQAAAAHNBA==" |
+| 2000-01-03 00:00:00.000 | 2    |      | d         | a    | ba   | 3    | 0.0 
| 0.0 | 0.0 | 0.0 |      0 |      0 |   1 | 3.0 | 3.0 | "AQAAAQAAAAOzAg==" |
+| 2001-01-01 00:00:00.000 | 1    | a    |           | b    | ad   | 4    |     
|     |     |     |        |        |   1 | 4.0 | 4.0 | "AQAAAQAAAAFREA==" |
+| 2001-01-02 00:00:00.000 | def  | abc  |           | b    | aa   | 5    |     
|     |     |     |        |        |   1 | 5.0 | 5.0 | "AQAAAQAAAACyEA==" |
+| 2001-01-03 00:00:00.000 | abc  |      |           | b    | ab   | 6    |     
|     |     |     |        |        |   1 | 6.0 | 6.0 | "AQAAAQAAAAEkAQ==" |
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+(6 rows)
+
+!ok
+select * from numfoo;
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+| __time                  | dim1 | dim2 | dim3      | dim4 | dim5 | dim6 | d1  
| d2  | f1  | f2  | l1     | l2     | cnt | m1  | m2  | unique_dim1        |
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+| 2000-01-01 00:00:00.000 |      | a    | ["a","b"] | a    | aa   | 1    | 1.0 
|     | 1.0 |     |      7 |        |   1 | 1.0 | 1.0 | "AQAAAEAAAA=="     |
+| 2000-01-02 00:00:00.000 | 10.1 |      | ["b","c"] | a    | ab   | 2    | 1.7 
| 1.7 | 0.1 | 0.1 | 325323 | 325323 |   1 | 2.0 | 2.0 | "AQAAAQAAAAHNBA==" |
+| 2000-01-03 00:00:00.000 | 2    |      | d         | a    | ba   | 3    | 0.0 
| 0.0 | 0.0 | 0.0 |      0 |      0 |   1 | 3.0 | 3.0 | "AQAAAQAAAAOzAg==" |
+| 2001-01-01 00:00:00.000 | 1    | a    |           | b    | ad   | 4    |     
|     |     |     |        |        |   1 | 4.0 | 4.0 | "AQAAAQAAAAFREA==" |
+| 2001-01-02 00:00:00.000 | def  | abc  |           | b    | aa   | 5    |     
|     |     |     |        |        |   1 | 5.0 | 5.0 | "AQAAAQAAAACyEA==" |
+| 2001-01-03 00:00:00.000 | abc  |      |           | b    | ab   | 6    |     
|     |     |     |        |        |   1 | 6.0 | 6.0 | "AQAAAQAAAAEkAQ==" |
++-------------------------+------+------+-----------+------+------+------+-----+-----+-----+-----+--------+--------+-----+-----+-----+--------------------+
+(6 rows)
+
+!ok
+select length(dim1) from numfoo;
++--------+
+| EXPR$0 |
++--------+
+|      0 |
+|      1 |
+|      1 |
+|      3 |
+|      3 |
+|      4 |
++--------+
+(6 rows)
+
+!ok
diff --git a/server/src/main/java/org/apache/druid/server/StatusResource.java 
b/server/src/main/java/org/apache/druid/server/StatusResource.java
index e15ddaf43bd..cf0ed113c47 100644
--- a/server/src/main/java/org/apache/druid/server/StatusResource.java
+++ b/server/src/main/java/org/apache/druid/server/StatusResource.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.Maps;
 import com.sun.jersey.spi.container.ResourceFilters;
 import org.apache.druid.client.DruidServerConfig;
+import org.apache.druid.common.guava.GuavaUtils;
 import org.apache.druid.guice.ExtensionsLoader;
 import org.apache.druid.initialization.DruidModule;
 import org.apache.druid.java.util.common.StringUtils;
@@ -144,7 +145,7 @@ public class StatusResource
 
     private String getDruidVersion()
     {
-      return Status.class.getPackage().getImplementationVersion();
+      return 
GuavaUtils.firstNonNull(Status.class.getPackage().getImplementationVersion(), 
"unknown");
     }
 
     @JsonProperty
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
index 8d3b6a743e3..c42c237ee36 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/CalciteRulesManager.java
@@ -40,7 +40,6 @@ import org.apache.calcite.rel.rules.DateRangeRules;
 import org.apache.calcite.rel.rules.JoinPushThroughJoinRule;
 import org.apache.calcite.rel.rules.ProjectMergeRule;
 import org.apache.calcite.rel.rules.PruneEmptyRules;
-import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.sql.SqlExplainFormat;
 import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql2rel.RelDecorrelator;
@@ -70,6 +69,7 @@ import org.apache.druid.sql.calcite.rule.SortCollapseRule;
 import 
org.apache.druid.sql.calcite.rule.logical.DruidAggregateRemoveRedundancyRule;
 import org.apache.druid.sql.calcite.rule.logical.DruidLogicalRules;
 import org.apache.druid.sql.calcite.run.EngineFeature;
+import org.apache.druid.sql.hook.DruidHook;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -395,7 +395,8 @@ public class CalciteRulesManager
     public RelNode run(RelOptPlanner planner, RelNode rel, RelTraitSet 
requiredOutputTraits,
         List<RelOptMaterialization> materializations, List<RelOptLattice> 
lattices)
     {
-      Hook.TRIMMED.run(rel);
+      PlannerContext pctx = 
planner.getContext().unwrapOrThrow(PlannerContext.class);
+      pctx.dispatchHook(DruidHook.LOGICAL_PLAN, rel);
       return rel;
     }
   }
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
index b7e2de3e66b..21ad4c23663 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
@@ -55,6 +55,7 @@ import org.apache.druid.sql.calcite.rule.ReverseLookupRule;
 import org.apache.druid.sql.calcite.run.EngineFeature;
 import org.apache.druid.sql.calcite.run.QueryMaker;
 import org.apache.druid.sql.calcite.run.SqlEngine;
+import org.apache.druid.sql.hook.DruidHook.HookKey;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.Interval;
@@ -668,4 +669,9 @@ public class PlannerContext
 
     return lookupCache.getLookup(lookupName);
   }
+
+  public <T> void dispatchHook(HookKey<T> key, T object)
+  {
+    plannerToolbox.getHookDispatcher().dispatch(key, object);
+  }
 }
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
index a5cdc028e7f..514952972d3 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
@@ -49,6 +49,8 @@ import 
org.apache.druid.sql.calcite.planner.convertlet.DruidConvertletTable;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.schema.DruidSchemaName;
+import org.apache.druid.sql.hook.DruidHook;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 
 import java.util.Map;
 import java.util.Properties;
@@ -77,7 +79,8 @@ public class PlannerFactory extends PlannerToolbox
       final CalciteRulesManager calciteRuleManager,
       final JoinableFactoryWrapper joinableFactoryWrapper,
       final CatalogResolver catalog,
-      final AuthConfig authConfig
+      final AuthConfig authConfig,
+      final DruidHookDispatcher hookDispatcher
   )
   {
     super(
@@ -91,7 +94,8 @@ public class PlannerFactory extends PlannerToolbox
         druidSchemaName,
         calciteRuleManager,
         authorizerMapper,
-        authConfig
+        authConfig,
+        hookDispatcher
     );
   }
 
@@ -112,6 +116,7 @@ public class PlannerFactory extends PlannerToolbox
         queryContext,
         hook
     );
+    context.dispatchHook(DruidHook.SQL, sql);
 
     return new DruidPlanner(buildFrameworkConfig(context), context, engine, 
hook);
   }
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerHook.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerHook.java
index fd245b096d8..8158a314f51 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerHook.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerHook.java
@@ -26,6 +26,7 @@ import org.apache.calcite.sql.SqlInsert;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.druid.guice.annotations.UnstableApi;
 import org.apache.druid.sql.calcite.rel.DruidRel;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 
 /**
  * Druid-specific version of Calcite's {@link org.apache.calcite.runtime.Hook 
Hook}
@@ -33,8 +34,11 @@ import org.apache.druid.sql.calcite.rel.DruidRel;
  * for test validation. Calcite's hook has multiple low-level events, but, 
sadly,
  * none at the points where tests want to verify, except for the opportunity to
  * capture the native query.
+ *
+ * Should be removed ; use {@link DruidHookDispatcher} instead.
  */
 @UnstableApi
+@Deprecated
 public interface PlannerHook
 {
   void captureSql(String sql);
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerToolbox.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerToolbox.java
index f0009826118..d8e3a25f458 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerToolbox.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerToolbox.java
@@ -26,6 +26,7 @@ import org.apache.druid.segment.join.JoinableFactoryWrapper;
 import org.apache.druid.server.security.AuthConfig;
 import org.apache.druid.server.security.AuthorizerMapper;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 
 public class PlannerToolbox
 {
@@ -40,6 +41,7 @@ public class PlannerToolbox
   protected final CalciteRulesManager calciteRuleManager;
   protected final AuthorizerMapper authorizerMapper;
   protected final AuthConfig authConfig;
+  protected final DruidHookDispatcher hookDispatcher;
 
   public PlannerToolbox(
       final DruidOperatorTable operatorTable,
@@ -52,7 +54,8 @@ public class PlannerToolbox
       final String druidSchemaName,
       final CalciteRulesManager calciteRuleManager,
       final AuthorizerMapper authorizerMapper,
-      final AuthConfig authConfig
+      final AuthConfig authConfig,
+      final DruidHookDispatcher hookDispatcher
   )
   {
     this.operatorTable = operatorTable;
@@ -66,6 +69,7 @@ public class PlannerToolbox
     this.calciteRuleManager = calciteRuleManager;
     this.authorizerMapper = authorizerMapper;
     this.authConfig = authConfig;
+    this.hookDispatcher = hookDispatcher;
   }
 
   public DruidOperatorTable operatorTable()
@@ -117,4 +121,9 @@ public class PlannerToolbox
   {
     return authConfig;
   }
+
+  public DruidHookDispatcher getHookDispatcher()
+  {
+    return hookDispatcher;
+  }
 }
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
index 9f15d382286..b543be135cc 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
@@ -47,7 +47,6 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.ScannableTable;
 import org.apache.calcite.sql.SqlExplain;
 import org.apache.calcite.sql.SqlNode;
@@ -76,6 +75,7 @@ import 
org.apache.druid.sql.calcite.rel.logical.DruidLogicalNode;
 import org.apache.druid.sql.calcite.run.EngineFeature;
 import org.apache.druid.sql.calcite.run.QueryMaker;
 import org.apache.druid.sql.calcite.table.DruidTable;
+import org.apache.druid.sql.hook.DruidHook;
 import org.apache.druid.utils.Throwables;
 
 import javax.annotation.Nullable;
@@ -155,7 +155,7 @@ public abstract class QueryHandler extends 
SqlStatementHandler.BaseStatementHand
     isPrepared = true;
     SqlNode validatedQueryNode = validatedQueryNode();
     rootQueryRel = handlerContext.planner().rel(validatedQueryNode);
-    Hook.CONVERTED.run(rootQueryRel.rel);
+    handlerContext.plannerContext().dispatchHook(DruidHook.CONVERTED_PLAN, 
rootQueryRel.rel);
     handlerContext.hook().captureQueryRel(rootQueryRel);
     final RelDataTypeFactory typeFactory = 
rootQueryRel.rel.getCluster().getTypeFactory();
     final SqlValidator validator = handlerContext.planner().getValidator();
@@ -563,7 +563,8 @@ public abstract class QueryHandler extends 
SqlStatementHandler.BaseStatementHand
                  .plus(DruidLogicalConvention.instance()),
           newRoot
       );
-      Hook.JAVA_PLAN.run(newRoot);
+
+      plannerContext.dispatchHook(DruidHook.DRUID_PLAN, newRoot);
 
       DruidQueryGenerator generator = new DruidQueryGenerator(plannerContext, 
(DruidLogicalNode) newRoot, rexBuilder);
       DruidQuery baseQuery = generator.buildQuery();
@@ -591,7 +592,7 @@ public abstract class QueryHandler extends 
SqlStatementHandler.BaseStatementHand
 
       handlerContext.hook().captureDruidRel(druidRel);
 
-      Hook.JAVA_PLAN.run(druidRel);
+      plannerContext.dispatchHook(DruidHook.DRUID_PLAN, druidRel);
 
       if (explain != null) {
         return planExplanation(possiblyLimitedRoot, druidRel, true);
diff --git a/sql/src/main/java/org/apache/druid/sql/hook/DruidHook.java 
b/sql/src/main/java/org/apache/druid/sql/hook/DruidHook.java
new file mode 100644
index 00000000000..5969d608f2f
--- /dev/null
+++ b/sql/src/main/java/org/apache/druid/sql/hook/DruidHook.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.sql.hook;
+
+import com.google.errorprone.annotations.Immutable;
+import org.apache.calcite.rel.RelNode;
+import java.util.Objects;
+
+/**
+ * Interface for hooks that can be invoked by {@link DruidHookDispatcher}.
+ *
+ * HookKey should be added at every place a new hook is needed.
+ */
+@FunctionalInterface
+public interface DruidHook<T>
+{
+  HookKey<RelNode> CONVERTED_PLAN = new HookKey<>("converted", RelNode.class);
+  HookKey<RelNode> LOGICAL_PLAN = new HookKey<>("logicalPlan", RelNode.class);
+  HookKey<RelNode> DRUID_PLAN = new HookKey<>("druidPlan", RelNode.class);
+  HookKey<String> SQL = new HookKey<>("sql", String.class);
+
+  @Immutable
+  class HookKey<T>
+  {
+    private final String label;
+    private final Class<T> type;
+
+    public HookKey(String label, Class<T> type)
+    {
+      this.label = label;
+      this.type = type;
+    }
+
+    @Override
+    public int hashCode()
+    {
+      return Objects.hash(label, type);
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      HookKey<?> other = (HookKey<?>) obj;
+      return Objects.equals(label, other.label) && Objects.equals(type, 
other.type);
+    }
+  }
+
+  void invoke(HookKey<T> key, T object);
+}
diff --git 
a/sql/src/main/java/org/apache/druid/sql/hook/DruidHookDispatcher.java 
b/sql/src/main/java/org/apache/druid/sql/hook/DruidHookDispatcher.java
new file mode 100644
index 00000000000..0114699d638
--- /dev/null
+++ b/sql/src/main/java/org/apache/druid/sql/hook/DruidHookDispatcher.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.sql.hook;
+
+import com.google.inject.Inject;
+import org.apache.druid.guice.LazySingleton;
+import org.apache.druid.sql.calcite.planner.PlannerContext;
+import org.apache.druid.sql.hook.DruidHook.HookKey;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dispatcher for Druid hooks.
+ *
+ * A single instance should live in the system and be used to dispatch hooks.
+ * Usual way to dispatch should be via
+ * {@link PlannerContext#dispatchHook(HookKey, Object)}. Access to this class 
is
+ * also possible thru DruidConnectionExtras.
+ */
+@LazySingleton
+public class DruidHookDispatcher
+{
+  Map<HookKey<?>, List<DruidHook<?>>> hooks = new HashMap<>();
+
+  @Inject
+  public DruidHookDispatcher()
+  {
+  }
+
+  public void register(HookKey<?> label, DruidHook<?> hook)
+  {
+    hooks.computeIfAbsent(label, k -> new ArrayList<>()).add(hook);
+  }
+
+  public void unregister(HookKey<?> key, DruidHook<?> hook)
+  {
+    hooks.get(key).remove(hook);
+  }
+
+  public <T> Closeable withHook(HookKey<T> key, DruidHook<T> hook)
+  {
+    register(key, hook);
+    return new Closeable()
+    {
+      @Override
+      public void close()
+      {
+        unregister(key, hook);
+      }
+    };
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public <T> void dispatch(HookKey<T> key, T object)
+  {
+    List<DruidHook<?>> currentHooks = hooks.get(key);
+    if (currentHooks != null) {
+      for (DruidHook hook : currentHooks) {
+        hook.invoke(key, object);
+      }
+    }
+  }
+
+}
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaDriverTest.java 
b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaDriverTest.java
index efdf782d147..3f039814c0f 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaDriverTest.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaDriverTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.druid.quidem;
 
+import org.apache.druid.sql.calcite.SqlTestFrameworkConfig;
 import org.junit.jupiter.api.Test;
 
 import java.sql.Connection;
@@ -51,6 +52,6 @@ public class DruidAvaticaDriverTest
   @Test
   public void testURIParse() throws SQLException
   {
-    DruidAvaticaTestDriver.buildConfigfromURIParams("druidtest:///");
+    SqlTestFrameworkConfig.fromURL("druidtest:///");
   }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java 
b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
index 123019dad04..d2a7b461dfc 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
@@ -70,25 +70,18 @@ import 
org.apache.druid.sql.calcite.util.SqlTestFramework.Builder;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.QueryComponentSupplier;
 import org.apache.druid.sql.guice.SqlModule;
-import org.apache.http.NameValuePair;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.client.utils.URLEncodedUtils;
 import org.eclipse.jetty.server.Server;
 
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.DriverManager;
 import java.sql.DriverPropertyInfo;
 import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import java.util.Properties;
 import java.util.logging.Logger;
 
@@ -98,10 +91,13 @@ public class DruidAvaticaTestDriver implements Driver
     new DruidAvaticaTestDriver().register();
   }
 
-  public static final String URI_PREFIX = "druidtest://";
+  public static final String SCHEME = "druidtest";
+  public static final String URI_PREFIX = SCHEME + "://";
   public static final String DEFAULT_URI = URI_PREFIX + "/";
 
-  static final SqlTestFrameworkConfigStore CONFIG_STORE = new 
SqlTestFrameworkConfigStore();
+  static final SqlTestFrameworkConfigStore CONFIG_STORE = new 
SqlTestFrameworkConfigStore(
+      x -> new AvaticaBasedTestConnectionSupplier(x)
+  );
 
   public DruidAvaticaTestDriver()
   {
@@ -114,13 +110,8 @@ public class DruidAvaticaTestDriver implements Driver
       return null;
     }
     try {
-      SqlTestFrameworkConfig config = buildConfigfromURIParams(url);
-
-      ConfigurationInstance ci = CONFIG_STORE.getConfigurationInstance(
-          config,
-          x -> new AvaticaBasedTestConnectionSupplier(x)
-      );
-
+      SqlTestFrameworkConfig config = SqlTestFrameworkConfig.fromURL(url);
+      ConfigurationInstance ci = CONFIG_STORE.getConfigurationInstance(config);
       AvaticaJettyServer server = 
ci.framework.injector().getInstance(AvaticaJettyServer.class);
       return server.getConnection(info);
     }
@@ -148,9 +139,9 @@ public class DruidAvaticaTestDriver implements Driver
 
     @Provides
     @LazySingleton
-    public DruidConnectionExtras getConnectionExtras(ObjectMapper objectMapper)
+    public DruidConnectionExtras getConnectionExtras(ObjectMapper 
objectMapper, DruidHookDispatcher druidHookDispatcher)
     {
-      return new DruidConnectionExtras.DruidConnectionExtrasImpl(objectMapper);
+      return new DruidConnectionExtras.DruidConnectionExtrasImpl(objectMapper, 
druidHookDispatcher);
     }
 
     @Provides
@@ -349,24 +340,6 @@ public class DruidAvaticaTestDriver implements Driver
     return tempDir;
   }
 
-  public static SqlTestFrameworkConfig buildConfigfromURIParams(String url) 
throws SQLException
-  {
-    Map<String, String> queryParams;
-    queryParams = new HashMap<>();
-    try {
-      List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), 
StandardCharsets.UTF_8);
-      for (NameValuePair pair : params) {
-        queryParams.put(pair.getName(), pair.getValue());
-      }
-      // possible caveat: duplicate entries overwrite earlier ones
-    }
-    catch (URISyntaxException e) {
-      throw new SQLException("Can't decode URI", e);
-    }
-
-    return new SqlTestFrameworkConfig(queryParams);
-  }
-
   private void register()
   {
     try {
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java 
b/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
index 75bdd4280fa..176411a6bb7 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
@@ -20,18 +20,25 @@
 package org.apache.druid.quidem;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
+
+import java.sql.Connection;
 
 public interface DruidConnectionExtras
 {
   ObjectMapper getObjectMapper();
 
+  DruidHookDispatcher getDruidHookDispatcher();
+
   class DruidConnectionExtrasImpl implements DruidConnectionExtras
   {
     private final ObjectMapper objectMapper;
+    private final DruidHookDispatcher druidHookDispatcher;
 
-    public DruidConnectionExtrasImpl(ObjectMapper objectMapper)
+    public DruidConnectionExtrasImpl(ObjectMapper objectMapper, 
DruidHookDispatcher druidHookDispatcher)
     {
       this.objectMapper = objectMapper;
+      this.druidHookDispatcher = druidHookDispatcher;
     }
 
     @Override
@@ -39,5 +46,19 @@ public interface DruidConnectionExtras
     {
       return objectMapper;
     }
+
+    @Override
+    public DruidHookDispatcher getDruidHookDispatcher()
+    {
+      return druidHookDispatcher;
+    }
+  }
+
+  static DruidConnectionExtras unwrapOrThrow(Connection connection)
+  {
+    if (connection instanceof DruidConnectionExtras) {
+      return (DruidConnectionExtras) connection;
+    }
+    throw new UnsupportedOperationException("Expected DruidConnectionExtras to 
be implemented by connection!");
   }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidQuidemCommandHandler.java 
b/sql/src/test/java/org/apache/druid/quidem/DruidQuidemCommandHandler.java
index 8c2b99d80b6..7ae8f97650c 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidQuidemCommandHandler.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidQuidemCommandHandler.java
@@ -35,12 +35,16 @@ import org.apache.druid.query.Query;
 import org.apache.druid.sql.calcite.BaseCalciteQueryTest;
 import org.apache.druid.sql.calcite.rel.DruidRel;
 import org.apache.druid.sql.calcite.util.QueryLogHook;
+import org.apache.druid.sql.hook.DruidHook;
+import org.apache.druid.sql.hook.DruidHook.HookKey;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.sql.ResultSet;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 public class DruidQuidemCommandHandler implements CommandHandler
@@ -56,7 +60,7 @@ public class DruidQuidemCommandHandler implements 
CommandHandler
       return new LogicalPlanCommand(lines, content);
     }
     if (line.startsWith("druidPlan")) {
-      return new PhysicalPlanCommand(lines, content);
+      return new DruidPlanCommand(lines, content);
     }
     if (line.startsWith("nativePlan")) {
       return new NativePlanCommand(lines, content);
@@ -155,19 +159,22 @@ public class DruidQuidemCommandHandler implements 
CommandHandler
    */
   abstract static class AbstractRelPlanCommand extends AbstractPlanCommand
   {
-    Hook hook;
+    HookKey<RelNode> hook;
 
-    AbstractRelPlanCommand(List<String> lines, List<String> content, Hook hook)
+    AbstractRelPlanCommand(List<String> lines, List<String> content, 
DruidHook.HookKey<RelNode> hook)
     {
       super(lines, content);
       this.hook = hook;
     }
 
     @Override
-    protected final void executeExplain(Context x)
+    protected final void executeExplain(Context x) throws IOException
     {
+      DruidHookDispatcher dhp = unwrapDruidHookDispatcher(x);
       List<RelNode> logged = new ArrayList<>();
-      try (final Hook.Closeable unhook = hook.add((Consumer<RelNode>) 
logged::add)) {
+      try (Closeable unhook = dhp.withHook(hook, (key, relNode) -> {
+        logged.add(relNode);
+      })) {
         executeQuery(x);
       }
 
@@ -179,21 +186,26 @@ public class DruidQuidemCommandHandler implements 
CommandHandler
         x.echo(ImmutableList.of(str));
       }
     }
+
+    protected final DruidHookDispatcher unwrapDruidHookDispatcher(Context x)
+    {
+      return 
DruidConnectionExtras.unwrapOrThrow(x.connection()).getDruidHookDispatcher();
+    }
   }
 
   static class LogicalPlanCommand extends AbstractRelPlanCommand
   {
     LogicalPlanCommand(List<String> lines, List<String> content)
     {
-      super(lines, content, Hook.TRIMMED);
+      super(lines, content, DruidHook.LOGICAL_PLAN);
     }
   }
 
-  static class PhysicalPlanCommand extends AbstractRelPlanCommand
+  static class DruidPlanCommand extends AbstractRelPlanCommand
   {
-    PhysicalPlanCommand(List<String> lines, List<String> content)
+    DruidPlanCommand(List<String> lines, List<String> content)
     {
-      super(lines, content, Hook.JAVA_PLAN);
+      super(lines, content, DruidHook.DRUID_PLAN);
     }
   }
 
@@ -201,7 +213,7 @@ public class DruidQuidemCommandHandler implements 
CommandHandler
   {
     ConvertedPlanCommand(List<String> lines, List<String> content)
     {
-      super(lines, content, Hook.CONVERTED);
+      super(lines, content, DruidHook.CONVERTED_PLAN);
     }
   }
 }
diff --git a/sql/src/test/java/org/apache/druid/quidem/DruidQuidemTestBase.java 
b/sql/src/test/java/org/apache/druid/quidem/DruidQuidemTestBase.java
index 6058cb2a8bd..650af0880f9 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidQuidemTestBase.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidQuidemTestBase.java
@@ -29,12 +29,14 @@ import org.apache.calcite.util.Closer;
 import org.apache.calcite.util.Util;
 import org.apache.commons.io.filefilter.TrueFileFilter;
 import org.apache.commons.io.filefilter.WildcardFileFilter;
+import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.java.util.common.FileUtils;
 import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.RE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -76,8 +78,12 @@ import static org.junit.jupiter.api.Assertions.fail;
  *
  */
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@ExtendWith(EnabledOnlyInSqlCompatibleMode.class)
 public abstract class DruidQuidemTestBase
 {
+  static {
+    NullHandling.initializeForTests();
+  }
 
   public static final String IQ_SUFFIX = ".iq";
   /**
diff --git a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java 
b/sql/src/test/java/org/apache/druid/quidem/EnabledOnlyInSqlCompatibleMode.java
similarity index 62%
copy from sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
copy to 
sql/src/test/java/org/apache/druid/quidem/EnabledOnlyInSqlCompatibleMode.java
index d1922472d7f..96cf0a72acf 100644
--- a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
+++ 
b/sql/src/test/java/org/apache/druid/quidem/EnabledOnlyInSqlCompatibleMode.java
@@ -20,27 +20,24 @@
 package org.apache.druid.quidem;
 
 import org.apache.druid.common.config.NullHandling;
-import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
 
-import java.io.File;
-
-@EnabledIf(value = "enabled", disabledReason = "These tests are only run in 
SqlCompatible mode!")
-public class SqlQuidemTest extends DruidQuidemTestBase
+public class EnabledOnlyInSqlCompatibleMode implements ExecutionCondition
 {
-  public static boolean enabled()
-  {
+  static {
     NullHandling.initializeForTests();
-    return NullHandling.sqlCompatible();
-  }
-
-  public SqlQuidemTest()
-  {
-    super();
   }
 
   @Override
-  protected File getTestRoot()
+  public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext 
context)
   {
-    return ProjectPathUtils.getPathFromProjectRoot("sql/src/test/quidem/" + 
getClass().getName());
+    if (NullHandling.sqlCompatible()) {
+      return ConditionEvaluationResult.enabled("SQL compatible mode is 
enabled");
+    } else {
+      return ConditionEvaluationResult.disabled("SQL compatible mode is 
disabled");
+    }
   }
+
 }
diff --git a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java 
b/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
index d1922472d7f..9e6a965891c 100644
--- a/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
+++ b/sql/src/test/java/org/apache/druid/quidem/SqlQuidemTest.java
@@ -19,20 +19,10 @@
 
 package org.apache.druid.quidem;
 
-import org.apache.druid.common.config.NullHandling;
-import org.junit.jupiter.api.condition.EnabledIf;
-
 import java.io.File;
 
-@EnabledIf(value = "enabled", disabledReason = "These tests are only run in 
SqlCompatible mode!")
 public class SqlQuidemTest extends DruidQuidemTestBase
 {
-  public static boolean enabled()
-  {
-    NullHandling.initializeForTests();
-    return NullHandling.sqlCompatible();
-  }
-
   public SqlQuidemTest()
   {
     super();
diff --git a/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java 
b/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java
index b332b6dabfa..1a7b6e3a7c5 100644
--- a/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java
@@ -58,6 +58,7 @@ import org.apache.druid.sql.calcite.planner.PlannerFactory;
 import org.apache.druid.sql.calcite.planner.PrepareResult;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.sql.http.SqlQuery;
 import org.easymock.EasyMock;
 import org.hamcrest.MatcherAssert;
@@ -158,7 +159,8 @@ public class SqlStatementTest
         new CalciteRulesManager(ImmutableSet.of()),
         joinableFactoryWrapper,
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     this.sqlStatementFactory = new SqlStatementFactory(
diff --git 
a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java 
b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
index cccaab75b6f..518e225c491 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
@@ -89,6 +89,7 @@ import org.apache.druid.sql.calcite.schema.NamedSchema;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
 import org.apache.druid.sql.guice.SqlModule;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.eclipse.jetty.server.Server;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -1048,7 +1049,8 @@ public class DruidAvaticaHandlerTest extends 
CalciteTestBase
             new CalciteRulesManager(ImmutableSet.of()),
             CalciteTests.createJoinableFactoryWrapper(),
             CatalogResolver.NULL_RESOLVER,
-            new AuthConfig()
+            new AuthConfig(),
+            new DruidHookDispatcher()
         )
     );
   }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java 
b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java
index 81cd1c69231..ed855f9c5f8 100644
--- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java
@@ -47,6 +47,7 @@ import org.apache.druid.sql.calcite.planner.PlannerFactory;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.junit.Assert;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -112,7 +113,8 @@ public class DruidStatementTest extends CalciteTestBase
         new CalciteRulesManager(ImmutableSet.of()),
         joinableFactoryWrapper,
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
     this.sqlStatementFactory = CalciteTests.createSqlStatementFactory(
         CalciteTests.createMockSqlEngine(walker, conglomerate),
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
index 9eda5849fcf..1fc39d52ec5 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
@@ -30,15 +30,23 @@ import com.google.common.collect.Sets;
 import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.query.topn.TopNQueryConfig;
+import org.apache.druid.quidem.DruidAvaticaTestDriver;
 import org.apache.druid.sql.calcite.util.CacheTestHelperModule.ResultCacheMode;
 import org.apache.druid.sql.calcite.util.SqlTestFramework;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.QueryComponentSupplier;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.StandardComponentSupplier;
+import org.apache.http.NameValuePair;
 import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.client.utils.URLEncodedUtils;
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
+import org.reflections.Configuration;
 import org.reflections.Reflections;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
+import org.reflections.util.FilterBuilder;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
@@ -51,6 +59,8 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -224,11 +234,18 @@ public class SqlTestFrameworkConfig
 
   public static class SqlTestFrameworkConfigStore implements Closeable
   {
+    private final Function<QueryComponentSupplier, QueryComponentSupplier> 
queryComponentSupplierWrapper;
+
+    public SqlTestFrameworkConfigStore(
+        Function<QueryComponentSupplier, QueryComponentSupplier> 
queryComponentSupplierWrapper)
+    {
+      this.queryComponentSupplierWrapper = queryComponentSupplierWrapper;
+    }
+
     Map<SqlTestFrameworkConfig, ConfigurationInstance> configMap = new 
HashMap<>();
 
     public ConfigurationInstance getConfigurationInstance(
-        SqlTestFrameworkConfig config,
-        Function<QueryComponentSupplier, QueryComponentSupplier> 
queryComponentSupplierWrapper) throws Exception
+        SqlTestFrameworkConfig config) throws Exception
     {
       ConfigurationInstance ret = configMap.get(config);
       if (!configMap.containsKey(config)) {
@@ -267,7 +284,7 @@ public class SqlTestFrameworkConfig
    */
   public static class Rule implements AfterAllCallback, BeforeEachCallback
   {
-    SqlTestFrameworkConfigStore configStore = new 
SqlTestFrameworkConfigStore();
+    SqlTestFrameworkConfigStore configStore = new 
SqlTestFrameworkConfigStore(Function.identity());
     private SqlTestFrameworkConfig config;
     private Method method;
     private String testName;
@@ -318,7 +335,7 @@ public class SqlTestFrameworkConfig
 
     public SqlTestFramework get() throws Exception
     {
-      return configStore.getConfigurationInstance(config, 
Function.identity()).framework;
+      return configStore.getConfigurationInstance(config).framework;
     }
 
     public <T extends Annotation> T getAnnotation(Class<T> annotationType)
@@ -344,6 +361,7 @@ public class SqlTestFrameworkConfig
           .minTopNThreshold(config.minTopNThreshold)
           .mergeBufferCount(config.numMergeBuffers)
           .withOverrideModule(config.resultCache.makeModule());
+
       framework = builder.build();
     }
 
@@ -393,12 +411,43 @@ public class SqlTestFrameworkConfig
     if (def.minTopNThreshold != minTopNThreshold) {
       map.put("minTopNThreshold", String.valueOf(minTopNThreshold));
     }
+    if (def.componentSupplier != componentSupplier) {
+      map.put("componentSupplier", componentSupplier.getSimpleName());
+    }
     if (!equals(new SqlTestFrameworkConfig(map))) {
       throw new IAE("Can't reproduce config via map!");
     }
     return map;
   }
 
+  public static SqlTestFrameworkConfig fromURL(String url) throws SQLException
+  {
+
+    Map<String, String> queryParams;
+    queryParams = new HashMap<>();
+    try {
+      URI uri = new URI(url);
+      if (!DruidAvaticaTestDriver.SCHEME.equals(uri.getScheme())) {
+        throw new SQLException(
+            StringUtils.format("URI [%s] is invalid ; only scheme [%s] is 
supported.", url, DruidAvaticaTestDriver.SCHEME)
+        );
+      }
+      if (uri.getHost() != null || uri.getPort() != -1) {
+        throw new SQLException(StringUtils.format("URI [%s] is invalid ; only 
query parameters are supported.", url));
+      }
+      List<NameValuePair> params = URLEncodedUtils.parse(uri, 
StandardCharsets.UTF_8);
+      for (NameValuePair pair : params) {
+        queryParams.put(pair.getName(), pair.getValue());
+      }
+      // possible caveat: duplicate entries overwrite earlier ones
+    }
+    catch (URISyntaxException e) {
+      throw new SQLException("Can't decode URI", e);
+    }
+
+    return new SqlTestFrameworkConfig(queryParams);
+  }
+
   abstract static class ConfigOptionProcessor<T>
   {
     final Class<? extends Annotation> annotationClass;
@@ -459,7 +508,15 @@ public class SqlTestFrameworkConfig
         @Override
         public Set<Class<? extends QueryComponentSupplier>> load(String pkg)
         {
-          return new 
Reflections(pkg).getSubTypesOf(QueryComponentSupplier.class);
+          Configuration cfg = new ConfigurationBuilder()
+              .setScanners(new SubTypesScanner(true))
+              .setUrls(ClasspathHelper.forJavaClassPath())
+              .filterInputsBy(
+                  new FilterBuilder()
+                      .includePackage(pkg)
+                      .and(s -> s.contains("ComponentSupplier"))
+              );
+          return new 
Reflections(cfg).getSubTypesOf(QueryComponentSupplier.class);
         }
       });
 
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfigTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfigTest.java
index 3a7dd2d22d3..844251e8ac3 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfigTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfigTest.java
@@ -22,6 +22,7 @@ package org.apache.druid.sql.calcite;
 import com.google.common.collect.ImmutableMap;
 import nl.jqno.equalsverifier.EqualsVerifier;
 import org.apache.druid.java.util.common.IAE;
+import 
org.apache.druid.sql.calcite.DrillWindowQueryTest.DrillComponentSupplier;
 import org.apache.druid.sql.calcite.SqlTestFrameworkConfig.MinTopNThreshold;
 import org.apache.druid.sql.calcite.SqlTestFrameworkConfig.NumMergeBuffers;
 import org.apache.druid.sql.calcite.SqlTestFrameworkConfig.ResultCache;
@@ -29,6 +30,7 @@ import 
org.apache.druid.sql.calcite.util.CacheTestHelperModule.ResultCacheMode;
 import org.junit.jupiter.api.Test;
 
 import java.lang.annotation.Annotation;
+import java.net.URI;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -139,4 +141,15 @@ public class SqlTestFrameworkConfigTest
     );
   }
 
+  @Test
+  public void testURI()
+  {
+    SqlTestFrameworkConfig c = new SqlTestFrameworkConfig(
+        ImmutableMap.of(
+            "componentSupplier", DrillComponentSupplier.class.getSimpleName()
+        )
+    );
+    URI uri = c.getDruidTestURI();
+    assertEquals("druidtest:///?componentSupplier=DrillComponentSupplier", 
uri.toString());
+  }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java
index 304400bc3d8..07f57a5deeb 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlVectorizedExpressionSanityTest.java
@@ -49,6 +49,7 @@ import org.apache.druid.sql.calcite.planner.PlannerResult;
 import org.apache.druid.sql.calcite.run.SqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.testing.InitializedNullHandlingTest;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.druid.timeline.partition.LinearShardSpec;
@@ -152,7 +153,8 @@ public class SqlVectorizedExpressionSanityTest extends 
InitializedNullHandlingTe
         new CalciteRulesManager(ImmutableSet.of()),
         joinableFactoryWrapper,
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
   }
 
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java
index 6155678c845..f4ada1f1c17 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestHelper.java
@@ -62,6 +62,7 @@ import org.apache.druid.sql.calcite.schema.ViewSchema;
 import org.apache.druid.sql.calcite.table.RowSignatures;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.easymock.EasyMock;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -97,7 +98,8 @@ public class ExpressionTestHelper
       "druid",
       new CalciteRulesManager(ImmutableSet.of()),
       CalciteTests.TEST_AUTHORIZER_MAPPER,
-      AuthConfig.newBuilder().build()
+      AuthConfig.newBuilder().build(),
+      new DruidHookDispatcher()
   );
   public static final PlannerContext PLANNER_CONTEXT = PlannerContext.create(
       PLANNER_TOOLBOX,
@@ -336,7 +338,7 @@ public class ExpressionTestHelper
     }
 
     ExprEval<?> result = 
PLANNER_CONTEXT.parseExpression(expression.getExpression())
-                                        
+
                                         .eval(expressionBindings);
 
     Assert.assertEquals("Result for: " + rexNode, expectedResult, 
result.value());
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/external/ExternalTableScanRuleTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/external/ExternalTableScanRuleTest.java
index 64a626f682c..fe26ba9db8c 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/external/ExternalTableScanRuleTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/external/ExternalTableScanRuleTest.java
@@ -39,6 +39,7 @@ import org.apache.druid.sql.calcite.schema.NamedDruidSchema;
 import org.apache.druid.sql.calcite.schema.NamedViewSchema;
 import org.apache.druid.sql.calcite.schema.ViewSchema;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Test;
@@ -71,7 +72,8 @@ public class ExternalTableScanRuleTest
         "druid",
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.TEST_AUTHORIZER_MAPPER,
-        AuthConfig.newBuilder().build()
+        AuthConfig.newBuilder().build(),
+        new DruidHookDispatcher()
     );
     final PlannerContext plannerContext = PlannerContext.create(
         toolbox,
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java
index 43d47733277..89df405b7f1 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java
@@ -50,6 +50,7 @@ import org.apache.druid.sql.calcite.schema.DruidSchemaName;
 import org.apache.druid.sql.calcite.schema.NamedSchema;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.easymock.EasyMock;
 import org.easymock.EasyMockExtension;
 import org.easymock.Mock;
@@ -192,7 +193,8 @@ public class CalcitePlannerModuleTest extends 
CalciteTestBase
         "druid",
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.TEST_AUTHORIZER_MAPPER,
-        AuthConfig.newBuilder().build()
+        AuthConfig.newBuilder().build(),
+        new DruidHookDispatcher()
     );
 
     PlannerContext context = PlannerContext.create(
@@ -224,7 +226,8 @@ public class CalcitePlannerModuleTest extends 
CalciteTestBase
             "druid",
             new CalciteRulesManager(ImmutableSet.of()),
             CalciteTests.TEST_AUTHORIZER_MAPPER,
-            AuthConfig.newBuilder().build()
+            AuthConfig.newBuilder().build(),
+            new DruidHookDispatcher()
     );
 
     PlannerContext contextWithBloat = PlannerContext.create(
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidRexExecutorTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidRexExecutorTest.java
index 205f3379d71..072bdd6b6e8 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidRexExecutorTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/planner/DruidRexExecutorTest.java
@@ -54,6 +54,7 @@ import org.apache.druid.sql.calcite.schema.ViewSchema;
 import org.apache.druid.sql.calcite.table.RowSignatures;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.testing.InitializedNullHandlingTest;
 import org.easymock.EasyMock;
 import org.junit.Assert;
@@ -100,7 +101,8 @@ public class DruidRexExecutorTest extends 
InitializedNullHandlingTest
       "druid",
       new CalciteRulesManager(ImmutableSet.of()),
       CalciteTests.TEST_AUTHORIZER_MAPPER,
-      AuthConfig.newBuilder().build()
+      AuthConfig.newBuilder().build(),
+      new DruidHookDispatcher()
   );
   private static final PlannerContext PLANNER_CONTEXT = PlannerContext.create(
       PLANNER_TOOLBOX,
diff --git 
a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/run/DruidHookTest.java
similarity index 62%
copy from sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
copy to sql/src/test/java/org/apache/druid/sql/calcite/run/DruidHookTest.java
index 75bdd4280fa..62190fb0c69 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidConnectionExtras.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/run/DruidHookTest.java
@@ -17,27 +17,20 @@
  * under the License.
  */
 
-package org.apache.druid.quidem;
+package org.apache.druid.sql.calcite.run;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.apache.druid.sql.hook.DruidHook;
+import org.junit.Test;
 
-public interface DruidConnectionExtras
+public class DruidHookTest
 {
-  ObjectMapper getObjectMapper();
-
-  class DruidConnectionExtrasImpl implements DruidConnectionExtras
+  @Test
+  public void testHookKeyEquals()
   {
-    private final ObjectMapper objectMapper;
-
-    public DruidConnectionExtrasImpl(ObjectMapper objectMapper)
-    {
-      this.objectMapper = objectMapper;
-    }
-
-    @Override
-    public ObjectMapper getObjectMapper()
-    {
-      return objectMapper;
-    }
+    EqualsVerifier.forClass(DruidHook.HookKey.class)
+        .withNonnullFields("label", "type")
+        .usingGetClass()
+        .verify();
   }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java
index 3fdbafb71a8..3d0d0352e60 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java
@@ -365,18 +365,30 @@ public class CalciteTests
     return QueryFrameworkUtils.createOperatorTable(INJECTOR);
   }
 
-  public static SystemSchema createMockSystemSchema(
-      final DruidSchema druidSchema,
-      final SpecificSegmentsQuerySegmentWalker walker,
-      final AuthorizerMapper authorizerMapper
-  )
+
+  public static DruidNode mockCoordinatorNode()
+  {
+    return new DruidNode("test-coordinator", "dummy", false, 8081, null, true, 
false);
+  }
+
+  public static FakeDruidNodeDiscoveryProvider 
mockDruidNodeDiscoveryProvider(final DruidNode coordinatorNode)
   {
-    final DruidNode coordinatorNode = new DruidNode("test-coordinator", 
"dummy", false, 8081, null, true, false);
     FakeDruidNodeDiscoveryProvider provider = new 
FakeDruidNodeDiscoveryProvider(
         ImmutableMap.of(
             NodeRole.COORDINATOR, new 
FakeDruidNodeDiscovery(ImmutableMap.of(NodeRole.COORDINATOR, coordinatorNode))
         )
     );
+    return provider;
+  }
+
+  public static SystemSchema createMockSystemSchema(
+      final DruidSchema druidSchema,
+      final SpecificSegmentsQuerySegmentWalker walker,
+      final AuthorizerMapper authorizerMapper
+  )
+  {
+    final DruidNode coordinatorNode = mockCoordinatorNode();
+    FakeDruidNodeDiscoveryProvider provider = 
mockDruidNodeDiscoveryProvider(coordinatorNode);
 
     final DruidNode overlordNode = new DruidNode("test-overlord", "dummy", 
false, 8090, null, true, false);
 
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java
index 5d710600a11..c43cb5555ad 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java
@@ -39,6 +39,7 @@ import org.apache.druid.java.util.common.io.Closer;
 import org.apache.druid.math.expr.ExprMacroTable;
 import org.apache.druid.query.GlobalTableDataSource;
 import org.apache.druid.query.QueryRunnerFactoryConglomerate;
+import org.apache.druid.query.QuerySegmentWalker;
 import org.apache.druid.query.lookup.LookupExtractorFactoryContainerProvider;
 import org.apache.druid.query.topn.TopNQueryConfig;
 import org.apache.druid.segment.DefaultColumnFormatConfig;
@@ -67,8 +68,11 @@ import 
org.apache.druid.sql.calcite.schema.NoopDruidSchemaManager;
 import org.apache.druid.sql.calcite.view.DruidViewMacroFactory;
 import org.apache.druid.sql.calcite.view.InProcessViewManager;
 import org.apache.druid.sql.calcite.view.ViewManager;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.apache.druid.timeline.DataSegment;
 
+import javax.inject.Named;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.net.URI;
@@ -184,6 +188,11 @@ public class SqlTestFramework
     default void close() throws IOException
     {
     }
+
+    default void configureGuice(CoreInjectorBuilder injectorBuilder, 
List<Module> overrideModules)
+    {
+      configureGuice(injectorBuilder);
+    }
   }
 
   public interface PlannerComponentSupplier
@@ -494,7 +503,8 @@ public class SqlTestFramework
           new CalciteRulesManager(componentSupplier.extensionCalciteRules()),
           framework.injector.getInstance(JoinableFactoryWrapper.class),
           framework.builder.catalogResolver,
-          authConfig != null ? authConfig : new AuthConfig()
+          authConfig != null ? authConfig : new AuthConfig(),
+          new DruidHookDispatcher()
       );
       componentSupplier.finalizePlanner(this);
       this.statementFactory = QueryFrameworkUtils.createSqlStatementFactory(
@@ -569,7 +579,14 @@ public class SqlTestFramework
 
     @Provides
     @LazySingleton
-    public SpecificSegmentsQuerySegmentWalker segmentsQuerySegmentWalker(final 
Injector injector)
+    public QuerySegmentWalker querySegmentWalker(final Injector injector)
+    {
+      return injector.getInstance(SpecificSegmentsQuerySegmentWalker.class);
+    }
+
+    @Provides
+    @LazySingleton
+    public SpecificSegmentsQuerySegmentWalker 
specificSegmentsQuerySegmentWalker(final Injector injector)
     {
       SpecificSegmentsQuerySegmentWalker walker = 
componentSupplier.createQuerySegmentWalker(
           injector.getInstance(QueryRunnerFactoryConglomerate.class),
@@ -585,10 +602,54 @@ public class SqlTestFramework
     public QueryLifecycleFactory queryLifecycleFactory(final Injector injector)
     {
       return QueryFrameworkUtils.createMockQueryLifecycleFactory(
-          injector.getInstance(SpecificSegmentsQuerySegmentWalker.class),
+          injector.getInstance(QuerySegmentWalker.class),
           injector.getInstance(QueryRunnerFactoryConglomerate.class)
       );
     }
+
+    @Provides
+    @LazySingleton
+    ViewManager createViewManager()
+    {
+      return 
componentSupplier.getPlannerComponentSupplier().createViewManager();
+    }
+
+    @Provides
+    @LazySingleton
+    public DruidSchemaCatalog makeCatalog(
+        final Injector injector,
+        final PlannerConfig plannerConfig,
+        final AuthConfig authConfig,
+        final ViewManager viewManager,
+        QueryRunnerFactoryConglomerate conglomerate,
+        QuerySegmentWalker walker
+    )
+    {
+      final DruidSchemaCatalog rootSchema = 
QueryFrameworkUtils.createMockRootSchema(
+          injector,
+          conglomerate,
+          (SpecificSegmentsQuerySegmentWalker) walker,
+          plannerConfig,
+          viewManager,
+          
componentSupplier.getPlannerComponentSupplier().createSchemaManager(),
+          authorizerMapper,
+          builder.catalogResolver
+      );
+      return rootSchema;
+    }
+
+    @Provides
+    SqlTestFrameworkConfig getTestConfig()
+    {
+      return builder.config;
+    }
+
+    @Provides
+    @Named("quidem")
+    public URI getDruidTestURI()
+    {
+      return getTestConfig().getDruidTestURI();
+    }
   }
 
   public static final DruidViewMacroFactory DRUID_VIEW_MACRO_FACTORY = new 
TestDruidViewMacroFactory();
@@ -613,17 +674,18 @@ public class SqlTestFramework
         // Ignore load scopes. This is a unit test, not a Druid node. If a
         // test pulls in a module, then pull in that module, even though we are
         // not the Druid node to which the module is scoped.
-        .ignoreLoadScopes()
-        .addModule(binder -> 
binder.bind(Closer.class).toInstance(resourceCloser))
-        .addModule(new LookylooModule())
-        .addModule(new SegmentWranglerModule())
-        .addModule(new SqlAggregationModule())
-        .addModule(new ExpressionModule())
-        .addModule(new TestSetupModule(builder));
-    builder.componentSupplier.configureGuice(injectorBuilder);
+        .ignoreLoadScopes();
+    List<Module> overrideModules = new ArrayList<>(builder.overrideModules);
+    overrideModules.add(new LookylooModule());
+    overrideModules.add(new SqlAggregationModule());
+    overrideModules.add(new SegmentWranglerModule());
+    overrideModules.add(new ExpressionModule());
+
+    overrideModules.add(testSetupModule());
+    builder.componentSupplier.configureGuice(injectorBuilder, overrideModules);
 
     ServiceInjectorBuilder serviceInjector = new 
ServiceInjectorBuilder(injectorBuilder);
-    serviceInjector.addAll(builder.overrideModules);
+    serviceInjector.addAll(overrideModules);
 
     this.injector = serviceInjector.build();
     this.engine = 
builder.componentSupplier.createEngine(queryLifecycleFactory(), 
queryJsonMapper(), injector);
@@ -631,6 +693,11 @@ public class SqlTestFramework
     componentSupplier.finalizeTestFramework(this);
   }
 
+  public TestSetupModule testSetupModule()
+  {
+    return new TestSetupModule(builder);
+  }
+
   public Injector injector()
   {
     return injector;
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java 
b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
index fa3c1edcea2..665d5873b46 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
@@ -102,6 +102,7 @@ import org.apache.druid.sql.calcite.run.NativeSqlEngine;
 import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
 import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.apache.druid.sql.calcite.util.CalciteTests;
+import org.apache.druid.sql.hook.DruidHookDispatcher;
 import org.hamcrest.CoreMatchers;
 import org.junit.Assert;
 import org.junit.jupiter.api.AfterAll;
@@ -259,7 +260,8 @@ public class SqlResourceTest extends CalciteTestBase
         new CalciteRulesManager(ImmutableSet.of()),
         CalciteTests.createJoinableFactoryWrapper(),
         CatalogResolver.NULL_RESOLVER,
-        new AuthConfig()
+        new AuthConfig(),
+        new DruidHookDispatcher()
     );
 
     lifecycleManager = new SqlLifecycleManager()


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to