diff --git a/pom.xml b/pom.xml
index 5e248e87..dc202f93 100644
--- a/pom.xml
+++ b/pom.xml
@@ -142,6 +142,18 @@
1.19.0
test
+
+ org.openjdk.jmh
+ jmh-core
+ 1.37
+ test
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ 1.37
+ test
+
@@ -221,8 +233,29 @@
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ benchmark-stateless-small
+ exec
+
+ test
+ java
+
+ -classpath
+
+ org.openjdk.jmh.Main
+
+ net.starschema.clouddb.jdbc.StatelessSmallQueryBenchmark
+
+
+
+
+
+
-
-
diff --git a/src/test/java/net/starschema/clouddb/jdbc/ConnectionFromResources.java b/src/test/java/net/starschema/clouddb/jdbc/ConnectionFromResources.java
new file mode 100644
index 00000000..ad6120d3
--- /dev/null
+++ b/src/test/java/net/starschema/clouddb/jdbc/ConnectionFromResources.java
@@ -0,0 +1,44 @@
+package net.starschema.clouddb.jdbc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Utility class to enable BigQuery connections from properties resources. */
+public class ConnectionFromResources {
+ private static final Logger logger = LoggerFactory.getLogger(ConnectionFromResources.class);
+
+ /**
+ * Connect to a BigQuery project as described in a properties file
+ *
+ * @param propertiesFilePath the path to the properties in file in src/test/resources
+ * @param extraUrl extra URL arguments to add; must start with &
+ * @return A {@link BQConnection} connected to the given database
+ * @throws IOException if the properties file cannot be read
+ * @throws SQLException if the configuration can't connect to BigQuery
+ */
+ public static BQConnection connect(String propertiesFilePath, String extraUrl)
+ throws IOException, SQLException {
+ final Properties properties = new Properties();
+ final ClassLoader loader = ConnectionFromResources.class.getClassLoader();
+ try (InputStream stream = loader.getResourceAsStream(propertiesFilePath)) {
+ properties.load(stream);
+ }
+ final StringBuilder jdcbUrlBuilder =
+ new StringBuilder(BQSupportFuncts.constructUrlFromPropertiesFile(properties));
+ if (extraUrl != null) {
+ jdcbUrlBuilder.append(extraUrl);
+ }
+ final String jdbcUrl = jdcbUrlBuilder.toString();
+
+ final Connection connection = DriverManager.getConnection(jdbcUrl, properties);
+ final BQConnection bqConnection = (BQConnection) connection;
+ logger.info("Created connection from {} to {}", propertiesFilePath, bqConnection.getURLPART());
+ return bqConnection;
+ }
+}
diff --git a/src/test/java/net/starschema/clouddb/jdbc/StatelessSmallQueryBenchmark.java b/src/test/java/net/starschema/clouddb/jdbc/StatelessSmallQueryBenchmark.java
new file mode 100644
index 00000000..d07c07f3
--- /dev/null
+++ b/src/test/java/net/starschema/clouddb/jdbc/StatelessSmallQueryBenchmark.java
@@ -0,0 +1,84 @@
+package net.starschema.clouddb.jdbc;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import org.assertj.core.api.Assertions;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Threads;
+
+/**
+ * Performance microbenchmark for stateless queries. This uses the example query from {@link
+ * StatelessQuery}, same as the tests.
+ *
+ *
Run with mvn exec:exec@benchmark-stateless - note that mvn install must have been run at least
+ * once before.
+ */
+@Fork(1)
+@Threads(10)
+@BenchmarkMode(Mode.Throughput)
+public class StatelessSmallQueryBenchmark {
+ private static final String CONNECTION_PROPERTIES = "installedaccount1.properties";
+
+ @State(Scope.Thread)
+ public static class RequiredJob {
+ private BQConnection connection;
+
+ @Setup(Level.Trial)
+ public void connect() throws SQLException, IOException {
+ connection = ConnectionFromResources.connect(CONNECTION_PROPERTIES, null);
+ }
+
+ @TearDown
+ public void disconnect() throws SQLException {
+ connection.close();
+ }
+ }
+
+ @State(Scope.Thread)
+ public static class OptionalJob {
+ private BQConnection connection;
+
+ @Setup(Level.Trial)
+ public void connect() throws SQLException, IOException {
+ connection =
+ ConnectionFromResources.connect(
+ CONNECTION_PROPERTIES, "&jobcreationmode=JOB_CREATION_OPTIONAL");
+ }
+
+ @TearDown
+ public void disconnect() throws SQLException {
+ connection.close();
+ }
+ }
+
+ private String[][] benchmarkSmallQuery(final Connection connection) throws SQLException {
+ final Statement statement = connection.createStatement();
+ final ResultSet results = statement.executeQuery(StatelessQuery.exampleQuery());
+ final String[][] rows = BQSupportMethods.GetQueryResult(results);
+ Assertions.assertThat(rows).isEqualTo(StatelessQuery.exampleValues());
+ return rows;
+ }
+
+ @Benchmark
+ public String[][] benchmarkSmallQueryRequiredJob(final RequiredJob requiredJob)
+ throws SQLException {
+ return benchmarkSmallQuery(requiredJob.connection);
+ }
+
+ @Benchmark
+ public String[][] benchmarkSmallQueryOptionalJob(final OptionalJob optionalJob)
+ throws SQLException {
+ return benchmarkSmallQuery(optionalJob.connection);
+ }
+}