diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 62f24d4e7..4036e97bf 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -89,7 +89,7 @@ jobs: fail-fast: false matrix: # BROKEN: tpch - benchmark: [ 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] + benchmark: [ 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'templated', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] steps: - name: Download artifact uses: actions/download-artifact@v3 @@ -112,7 +112,16 @@ jobs: - name: Run benchmark run: | - java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlite/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + # For templated benchmarks, we need to preload some data for the test since by design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + if [[ ${{matrix.benchmark}} == templated ]]; then + # Disable synchronous mode for sqlite tpcc data loading to save some time. + java -jar benchbase.jar -b tpcc -c config/sqlite/sample_tpcc_nosync_config.xml --create=true --load=true --execute=false --json-histograms results/histograms.json + # Run the templated benchmark. + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlite/sample_${{matrix.benchmark}}_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json + else + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlite/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + fi # FIXME: Reduce the error rate so we don't need these overrides. if [ ${{matrix.benchmark}} == auctionmark ]; then ERRORS_THRESHOLD=0.02 @@ -134,7 +143,7 @@ jobs: strategy: fail-fast: false matrix: - benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] + benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'templated', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] services: mariadb: # https://hub.docker.com/_/mariadb image: mariadb:latest @@ -176,7 +185,16 @@ jobs: MARIADB_PORT: ${{ job.services.mariadb.ports[3306] }} run: | mysql -h127.0.0.1 -P$MARIADB_PORT -uadmin -ppassword -e "DROP DATABASE IF EXISTS benchbase; CREATE DATABASE benchbase" - java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mariadb/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + + # For templated benchmarks, we need to preload some data for the test since by design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + if [[ ${{matrix.benchmark}} == templated ]]; then + java -jar benchbase.jar -b tpcc -c config/mariadb/sample_tpcc_config.xml --create=true --load=true --execute=false --json-histograms results/histograms.json + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mariadb/sample_${{matrix.benchmark}}_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json + else + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mariadb/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + fi + # FIXME: Reduce the error rate so we don't need these overrides. if [ ${{matrix.benchmark}} == auctionmark ]; then ERRORS_THRESHOLD=0.02 @@ -194,7 +212,7 @@ jobs: strategy: fail-fast: false matrix: - benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] + benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'templated', 'tpcc', 'twitter', 'voter', 'wikipedia', 'ycsb' ] services: mysql: # https://hub.docker.com/_/mysql image: mysql:latest @@ -235,7 +253,16 @@ jobs: MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} run: | mysql -h127.0.0.1 -P$MYSQL_PORT -uadmin -ppassword -e "DROP DATABASE IF EXISTS benchbase; CREATE DATABASE benchbase" - java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mysql/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + + # For templated benchmarks, we need to preload some data for the test since by design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + if [[ ${{matrix.benchmark}} == templated ]]; then + java -jar benchbase.jar -b tpcc -c config/mysql/sample_tpcc_config.xml --create=true --load=true --execute=false --json-histograms results/histograms.json + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mysql/sample_${{matrix.benchmark}}_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json + else + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/mysql/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + fi + # FIXME: Reduce the error rate so we don't need these overrides. if [ ${{matrix.benchmark}} == auctionmark ]; then ERRORS_THRESHOLD=0.02 @@ -253,7 +280,7 @@ jobs: strategy: fail-fast: false matrix: - benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'tpcc', 'tpch', 'twitter', 'voter', 'wikipedia', 'ycsb' ] + benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'templated', 'tpcc', 'tpch', 'twitter', 'voter', 'wikipedia', 'ycsb' ] services: postgres: # https://hub.docker.com/_/postgres image: postgres:latest @@ -292,7 +319,16 @@ jobs: run: | PGPASSWORD=password dropdb -h localhost -U admin benchbase --if-exists PGPASSWORD=password createdb -h localhost -U admin benchbase - java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/postgres/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + + # For templated benchmarks, we need to preload some data for the test since by design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + if [[ ${{matrix.benchmark}} == templated ]]; then + java -jar benchbase.jar -b tpcc -c config/postgres/sample_tpcc_config.xml --create=true --load=true --execute=false --json-histograms results/histograms.json + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/postgres/sample_${{matrix.benchmark}}_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json + else + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/postgres/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + fi + # FIXME: Reduce the error rate so we don't need these overrides. if [ ${{matrix.benchmark}} == auctionmark ]; then ERRORS_THRESHOLD=0.02 @@ -362,7 +398,7 @@ jobs: matrix: # TODO: add more benchmarks #benchmark: [ 'auctionmark', 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'seats', 'sibench', 'smallbank', 'tatp', 'tpcc', 'tpch', 'twitter', 'voter', 'wikipedia', 'ycsb' ] - benchmark: [ 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'sibench', 'smallbank', 'tatp', 'tpcc', 'tpch', 'twitter', 'voter', 'wikipedia', 'ycsb' ] + benchmark: [ 'epinions', 'hyadapt', 'noop', 'otmetrics', 'resourcestresser', 'sibench', 'smallbank', 'tatp', 'tpcc', 'templated', 'tpch', 'twitter', 'voter', 'wikipedia', 'ycsb' ] services: sqlserver: image: mcr.microsoft.com/mssql/server:latest @@ -423,7 +459,15 @@ jobs: - name: Run benchmark # Note: user/pass should match those used in sample configs. run: | - java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlserver/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + # For templated benchmarks, we need to preload some data for the test since by design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + if [[ ${{matrix.benchmark}} == templated ]]; then + java -jar benchbase.jar -b tpcc -c config/sqlserver/sample_tpcc_config.xml --create=true --load=true --execute=false --json-histograms results/histograms.json + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlserver/sample_${{matrix.benchmark}}_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json + else + java -jar benchbase.jar -b ${{matrix.benchmark}} -c config/sqlserver/sample_${{matrix.benchmark}}_config.xml --create=true --load=true --execute=true --json-histograms results/histograms.json + fi + # FIXME: Reduce the error rate so we don't need these overrides. if [ ${{matrix.benchmark}} == tatp ]; then ERRORS_THRESHOLD=0.05 diff --git a/config/mariadb/sample_templated_config.xml b/config/mariadb/sample_templated_config.xml new file mode 100644 index 000000000..506469f45 --- /dev/null +++ b/config/mariadb/sample_templated_config.xml @@ -0,0 +1,49 @@ + + + + + MARIADB + org.mariadb.jdbc.Driver + jdbc:mariadb://localhost:3306/benchbase?useServerPrepStmts + admin + password + TRANSACTION_SERIALIZABLE + 128 + + + + data/templated/example.xml + + + 1 + + + + 100 + 30,20,10,30,10 + + + + + + + GetOrder + + + GetCust + + + GetCustNull + + + GetWarehouse + + + GetItemByPrice + + + diff --git a/config/mysql/sample_templated_config.xml b/config/mysql/sample_templated_config.xml new file mode 100644 index 000000000..ef10edc33 --- /dev/null +++ b/config/mysql/sample_templated_config.xml @@ -0,0 +1,49 @@ + + + + + MYSQL + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/benchbase?rewriteBatchedStatements=true&allowPublicKeyRetrieval=True&sslMode=DISABLED + admin + password + TRANSACTION_SERIALIZABLE + 128 + + + + data/templated/example.xml + + + 1 + + + + 100 + 30,20,10,30,10 + + + + + + + GetOrder + + + GetCust + + + GetCustNull + + + GetWarehouse + + + GetItemByPrice + + + diff --git a/config/plugin.xml b/config/plugin.xml index a31108273..baf699147 100644 --- a/config/plugin.xml +++ b/config/plugin.xml @@ -17,4 +17,5 @@ com.oltpbenchmark.benchmarks.smallbank.SmallBankBenchmark com.oltpbenchmark.benchmarks.hyadapt.HYADAPTBenchmark com.oltpbenchmark.benchmarks.otmetrics.OTMetricsBenchmark + com.oltpbenchmark.benchmarks.templated.TemplatedBenchmark diff --git a/config/postgres/sample_templated_config.xml b/config/postgres/sample_templated_config.xml new file mode 100644 index 000000000..2ef942e93 --- /dev/null +++ b/config/postgres/sample_templated_config.xml @@ -0,0 +1,49 @@ + + + + + POSTGRES + org.postgresql.Driver + jdbc:postgresql://localhost:5432/benchbase?sslmode=disable&ApplicationName=templated&reWriteBatchedInserts=true + admin + password + TRANSACTION_SERIALIZABLE + 128 + + + + data/templated/example.xml + + + 1 + + + + 100 + 30,20,10,30,10 + + + + + + + GetOrder + + + GetCust + + + GetCustNull + + + GetWarehouse + + + GetItemByPrice + + + diff --git a/config/sqlite/sample_templated_config.xml b/config/sqlite/sample_templated_config.xml new file mode 100644 index 000000000..e8d5a0835 --- /dev/null +++ b/config/sqlite/sample_templated_config.xml @@ -0,0 +1,47 @@ + + + + + SQLITE + org.sqlite.JDBC + jdbc:sqlite:tpcc.db + TRANSACTION_SERIALIZABLE + 128 + + + + data/templated/example.xml + + + 1 + + + + 100 + 30,20,10,30,10 + + + + + + + GetOrder + + + GetCust + + + GetCustNull + + + GetWarehouse + + + GetItemByPrice + + + diff --git a/config/sqlite/sample_tpcc_config.xml b/config/sqlite/sample_tpcc_config.xml index c54688835..3b9901886 100644 --- a/config/sqlite/sample_tpcc_config.xml +++ b/config/sqlite/sample_tpcc_config.xml @@ -10,7 +10,7 @@ 1 - + 1 diff --git a/config/sqlite/sample_tpcc_nosync_config.xml b/config/sqlite/sample_tpcc_nosync_config.xml new file mode 100644 index 000000000..bea1e6e87 --- /dev/null +++ b/config/sqlite/sample_tpcc_nosync_config.xml @@ -0,0 +1,55 @@ + + + + + SQLITE + org.sqlite.JDBC + jdbc:sqlite:tpcc.db?synchronous=off + TRANSACTION_SERIALIZABLE + 128 + + + 1 + + + 1 + + + 1 + + + + 10000 + 45,43,4,4,4 + + + + + + + NewOrder + + + + + Payment + + + + + OrderStatus + + + + + Delivery + + + + + StockLevel + + + + + diff --git a/config/sqlserver/sample_templated_config.xml b/config/sqlserver/sample_templated_config.xml new file mode 100644 index 000000000..148fb9a9f --- /dev/null +++ b/config/sqlserver/sample_templated_config.xml @@ -0,0 +1,49 @@ + + + + + sqlserver + com.microsoft.sqlserver.jdbc.SQLServerDriver + jdbc:sqlserver://localhost:1433;encrypt=false;database=benchbase; + benchuser01 + P@ssw0rd + TRANSACTION_SERIALIZABLE + 128 + + + + data/templated/example.xml + + + 1 + + + + 100 + 30,20,10,30,10 + + + + + + + GetOrder + + + GetCust + + + GetCustNull + + + GetWarehouse + + + GetItemByPrice + + + diff --git a/data/templated/example.xml b/data/templated/example.xml new file mode 100644 index 000000000..2e2177859 --- /dev/null +++ b/data/templated/example.xml @@ -0,0 +1,64 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docker/build-run-benchmark-with-docker.sh b/docker/build-run-benchmark-with-docker.sh index a897bf3d9..3643da0c2 100755 --- a/docker/build-run-benchmark-with-docker.sh +++ b/docker/build-run-benchmark-with-docker.sh @@ -27,6 +27,11 @@ if [ "$BENCHBASE_PROFILE" == 'sqlite' ]; then SRC_DIR="$LOCAL_WORKSPACE_FOLDER" fi EXTRA_DOCKER_ARGS="-v $SRC_DIR/$benchmark.db:/benchbase/profiles/sqlite/$benchmark.db" + + if [ "$benchmark" == 'templated' ]; then + # See notes below: + EXTRA_DOCKER_ARGS+=" -v $SRC_DIR/$benchmark.db:/benchbase/profiles/sqlite/tpcc.db" + fi else if [ ! -x "docker/${BENCHBASE_PROFILE}-${PROFILE_VERSION}/up.sh" ]; then echo "ERROR: No docker up.sh script available for '$BENCHBASE_PROFILE'" @@ -38,6 +43,25 @@ fi CREATE_DB_ARGS='--create=true --load=true' if [ "${SKIP_LOAD_DB:-false}" == 'true' ]; then CREATE_DB_ARGS='' +elif [ "$benchmark" == 'templated' ]; then + # For templated benchmarks, we need to preload some data for the test since by + # design, templated benchmarks do not support the 'load' operation + # In this case, we load the tpcc data. + echo "INFO: Loading tpcc data for templated benchmark" + if [ "$BENCHBASE_PROFILE" == 'sqlite' ]; then + # Sqlite will load much faster if we disable sync. + tpcc_config="config/sample_tpcc_nosync_config.xml" + else + tpcc_config="config/sample_tpcc_config.xml" + fi + SKIP_TESTS=${SKIP_TESTS:-true} EXTRA_DOCKER_ARGS="--network=host $EXTRA_DOCKER_ARGS" \ + ./docker/benchbase/run-full-image.sh \ + --config "$tpcc_config" --bench tpcc \ + $CREATE_DB_ARGS --execute=false + + # Mark those actions as completed. + CREATE_DB_ARGS='' + SKIP_TESTS=true fi SKIP_TESTS=${SKIP_TESTS:-true} EXTRA_DOCKER_ARGS="--network=host $EXTRA_DOCKER_ARGS" \ diff --git a/pom.xml b/pom.xml index c6671bf0c..b2b486d36 100644 --- a/pom.xml +++ b/pom.xml @@ -241,6 +241,19 @@ test + + org.immutables + value + 2.9.0 + provided + + + + org.codehaus.janino + janino + 3.1.9 + + diff --git a/src/main/java/com/oltpbenchmark/DBWorkload.java b/src/main/java/com/oltpbenchmark/DBWorkload.java index dfc9beb5a..15b05fddc 100644 --- a/src/main/java/com/oltpbenchmark/DBWorkload.java +++ b/src/main/java/com/oltpbenchmark/DBWorkload.java @@ -496,8 +496,7 @@ private static Options buildOptions(XMLConfiguration pluginConfig) { return options; } - private static XMLConfiguration buildConfiguration(String filename) throws ConfigurationException { - + public static XMLConfiguration buildConfiguration(String filename) throws ConfigurationException { Parameters params = new Parameters(); FileBasedConfigurationBuilder builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class) .configure(params.xml() @@ -505,7 +504,6 @@ private static XMLConfiguration buildConfiguration(String filename) throws Confi .setListDelimiterHandler(new DisabledListDelimiterHandler()) .setExpressionEngine(new XPathExpressionEngine())); return builder.getConfiguration(); - } private static void writeHistograms(Results r) { diff --git a/src/main/java/com/oltpbenchmark/api/BenchmarkModule.java b/src/main/java/com/oltpbenchmark/api/BenchmarkModule.java index ddd0a6cc0..247546483 100644 --- a/src/main/java/com/oltpbenchmark/api/BenchmarkModule.java +++ b/src/main/java/com/oltpbenchmark/api/BenchmarkModule.java @@ -48,6 +48,11 @@ public abstract class BenchmarkModule { */ protected final WorkloadConfiguration workConf; + /** + * Class loader variable for this benchmark + */ + protected ClassLoader classLoader; + /** * These are the variations of the Procedure's Statement SQL */ @@ -72,6 +77,15 @@ public abstract class BenchmarkModule { public BenchmarkModule(WorkloadConfiguration workConf) { this.workConf = workConf; this.dialects = new StatementDialects(workConf); + initClassLoader(); + } + + /** + * Instantiates the classLoader variable, needs to be overwritten if + * benchmark uses a custom implementation. + */ + protected void initClassLoader() { + this.classLoader = ClassLoader.getSystemClassLoader(); } // -------------------------------------------------------------------------- @@ -331,7 +345,7 @@ public final TransactionType initTransactionType(String procName, int id, long p Package pkg = this.getProcedurePackageImpl(); String fullName = pkg.getName() + "." + procName; - Class procClass = (Class) ClassUtil.getClass(fullName); + Class procClass = (Class) ClassUtil.getClass(this.classLoader, fullName); return new TransactionType(procClass, id, false, preExecutionWait, postExecutionWait); } diff --git a/src/main/java/com/oltpbenchmark/api/templates/ObjectFactory.java b/src/main/java/com/oltpbenchmark/api/templates/ObjectFactory.java new file mode 100644 index 000000000..82cbd4b96 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/api/templates/ObjectFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + */ + +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2011.12.28 at 11:42:38 PM EST +// + + +package com.oltpbenchmark.api.templates; + +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the com.oltpbenchmark.api.templates package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _Templates_QNAME = new QName("", "templates"); + + /** + * Create a new ObjectFactory that can be used to create new instances of + * schema derived classes for package: com.oltpbenchmark.api.templates + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link TemplateType } + */ + public TemplateType createTemplateType() { + return new TemplateType(); + } + + /** + * Create an instance of {@link TypesType } + */ + public TypesType createTypesType() { + return new TypesType(); + } + + /** + * Create an instance of {@link ValuesType } + */ + public ValuesType createValuesType() { + return new ValuesType(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link TemplatesType }{@code >}} + */ + @XmlElementDecl(namespace = "", name = "templates") + public JAXBElement createDialects(TemplatesType value) { + return new JAXBElement<>(_Templates_QNAME, TemplatesType.class, null, value); + } + +} diff --git a/src/main/java/com/oltpbenchmark/api/templates/TemplateType.java b/src/main/java/com/oltpbenchmark/api/templates/TemplateType.java new file mode 100644 index 000000000..1bcba2164 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/api/templates/TemplateType.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + */ + +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2011.12.28 at 11:42:38 PM EST +// + + +package com.oltpbenchmark.api.templates; + +import jakarta.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + + +/** + *

Java class for dialectType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="dialectType">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="procedure" type="{}procedureType" maxOccurs="unbounded"/>
+ *       </sequence>
+ *       <attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "templateType", propOrder = { + "query", "types", "values" +}) +public class TemplateType { + + @XmlAttribute(required = true) + protected String name; + @XmlElement(required = true) + protected String query; + @XmlElement(required = true) + protected TypesType types; + @XmlElement(required = true) + protected List values; + + /** + * Gets the value of the query property. + */ + public String getQuery() { + return this.query; + } + + + /** + * Gets the value of the types property. + */ + public TypesType getTypes() { + return this.types; + } + + /** + * Gets the value of the types property. + * + * Objects of the following type(s) are allowed in the list {@link ValuesType } + */ + public List getValues() { + if (this.values == null) { + this.values = new ArrayList<>(); + } + return this.values; + } + + /** + * Gets the value of the name property. + * + * @return possible object is {@link String } + */ + public String getName() { + return name; + } + +} diff --git a/src/main/java/com/oltpbenchmark/api/templates/TemplatesType.java b/src/main/java/com/oltpbenchmark/api/templates/TemplatesType.java new file mode 100644 index 000000000..6910f5477 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/api/templates/TemplatesType.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + */ + +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2011.12.28 at 11:42:38 PM EST +// + + +package com.oltpbenchmark.api.templates; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + + +/** + *

Java class for dialectsType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="dialectsType">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="dialect" type="{}dialectType" maxOccurs="unbounded"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "templatesType", propOrder = { + "template" +}) +public class TemplatesType { + + @XmlElement(required = true) + protected List template; + + /** + * Gets the value of the dialect property. + * + * Objects of the following type(s) are allowed in the list {@link TemplateType } + */ + public List getTemplateList() { + if (this.template == null) { + this.template = new ArrayList<>(); + } + return this.template; + } + +} diff --git a/src/main/java/com/oltpbenchmark/api/templates/TypesType.java b/src/main/java/com/oltpbenchmark/api/templates/TypesType.java new file mode 100644 index 000000000..170844d78 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/api/templates/TypesType.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + */ + +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2011.12.28 at 11:42:38 PM EST +// + + +package com.oltpbenchmark.api.templates; + +import jakarta.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + + +/** + *

Java class for dialectType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="parameterTypesType">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="parameters_type" type="{}procedureType" maxOccurs="unbounded"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "typesType", propOrder = {"type"}) +public class TypesType { + + @XmlElement(required = true) + protected List type; + + + /** + * Gets the value of the type property. + * + * Objects of the following type(s) are allowed in the list {@link String } + */ + public List getTypeList() { + if (this.type == null) { + this.type = new ArrayList<>(); + } + return this.type; + } + +} diff --git a/src/main/java/com/oltpbenchmark/api/templates/ValuesType.java b/src/main/java/com/oltpbenchmark/api/templates/ValuesType.java new file mode 100644 index 000000000..e2120bb14 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/api/templates/ValuesType.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + */ + +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2011.12.28 at 11:42:38 PM EST +// + + +package com.oltpbenchmark.api.templates; + +import jakarta.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + + +/** + *

Java class for dialectType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="parameterTypesType">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="parameters_type" type="{}procedureType" maxOccurs="unbounded"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "valuesType", propOrder = {"value"}) +public class ValuesType { + + @XmlElement(required = true) + protected List value; + + + /** + * Gets the value of the value property. + * + * Objects of the following type(s) are allowed in the list {@link String } + */ + public List getValueList() { + if (this.value == null) { + this.value = new ArrayList<>(); + } + return this.value; + } + +} diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/README.md b/src/main/java/com/oltpbenchmark/benchmarks/templated/README.md new file mode 100644 index 000000000..38d919f69 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/README.md @@ -0,0 +1,56 @@ +# Templated Benchmarks + +This class is used to execute templated benchmarks, i.e., benchmark queries that have parameters that the user wants to set dynamically. +A templated benchmark config has the following structure: + +```xml + + + + +``` + +where `$ParameterType` is the `java.sql.Types` value (i.e., Integer, Boolean, etc.) and each value tag within `values` contains the values for one instantiation of the parameters set in `$SQLQuery`. +The SQL query string is read as a `PreparedStatement`, i.e., parameters are defined in the string via a `?` placeholder. + +An example for a templated benchmark can be found in [`data/templated/example.xml`](../../../../../../../data/templated/example.xml). +The file path for the XML template has to be defined in the workload configuration using the `templates_file` tag. + +An example configuration can be found in [`config/sqlserver/sample_template_config.xml`](../../../../../../../config/sqlserver/sample_templated_config.xml). + +> Since the templated benchmark is meant to flexibly support a myriad of different database schemas, it doesn't *currently* support the `init` and `load` phases. + + +The example can be executed if a loaded TPC-C instance is used as JDBC endpoint. + +For instance: + +```sh +java -jar benchbase.jar -b tpcc -c config/sqlserver/sample_tpcc_config.xml --create=true --load=true --execute=false +``` + +Templated benchmarks are instantiated using `templated` as benchmark class when running BenchBase via the command line. + +For instance: + +```sh +java -jar benchbase.jar -b templated -c config/sqlserver/sample_templated_config.xml --create=false --load=false --execute=true --json-histograms results/histograms.json +``` + +> For additional examples, please refer to the build pipeline definition in the [`maven.yml`](../../../../../../../.github/workflows/maven.yml#L423) Github Actions workflow file. diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedBenchmark.java b/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedBenchmark.java new file mode 100644 index 000000000..01ed0ec91 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedBenchmark.java @@ -0,0 +1,280 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.benchmarks.templated; + +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.apache.commons.text.StringEscapeUtils; +import org.codehaus.commons.compiler.CompilerFactoryFactory; +import org.codehaus.commons.compiler.ICompilerFactory; +import org.codehaus.commons.compiler.ISimpleCompiler; +import org.immutables.value.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.oltpbenchmark.WorkloadConfiguration; +import com.oltpbenchmark.api.BenchmarkModule; +import com.oltpbenchmark.api.Loader; +import com.oltpbenchmark.api.Procedure; +import com.oltpbenchmark.api.SQLStmt; +import com.oltpbenchmark.api.TransactionType; +import com.oltpbenchmark.api.Worker; +import com.oltpbenchmark.api.templates.ValuesType; +import com.oltpbenchmark.api.templates.TemplateType; +import com.oltpbenchmark.api.templates.TemplatesType; +import com.oltpbenchmark.benchmarks.templated.procedures.GenericQuery; +import com.oltpbenchmark.benchmarks.templated.procedures.GenericQuery.QueryTemplateInfo; +import com.oltpbenchmark.benchmarks.templated.util.GenericQueryOperation; +import com.oltpbenchmark.benchmarks.templated.util.TraceTransactionGenerator; +import com.opencsv.CSVParser; +import com.opencsv.CSVParserBuilder; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.Unmarshaller; + +/** + * This class is used to execute templated benchmarks, i.e., benchmarks that + * have parameters that the user wants to set dynamically. More information + * about the structure of the expected template can be found in the local + * readme file. + */ +public class TemplatedBenchmark extends BenchmarkModule { + private static final Logger LOG = LoggerFactory.getLogger(TemplatedBenchmark.class); + + public TemplatedBenchmark(WorkloadConfiguration workConf) { + super(workConf); + } + + @Override + protected void initClassLoader() { + super.initClassLoader(); + + if (workConf != null && workConf.getXmlConfig().containsKey("query_templates_file")) { + this.classLoader = this.loadQueryTemplates( + workConf.getXmlConfig().getString("query_templates_file")); + } else { + LOG.error("No query_templates_file specified in xml config."); + } + } + + @Override + protected Package getProcedurePackageImpl() { + return (GenericQuery.class.getPackage()); + } + + private CustomClassLoader getCustomClassLoader() { + return (CustomClassLoader) this.classLoader; + } + + public List> getProcedureClasses() { + return this.getCustomClassLoader().getProcedureClasses(); + } + + @Override + protected List> makeWorkersImpl() { + List> workers = new ArrayList<>(); + + try { + final Map, TraceTransactionGenerator> generators = new HashMap<>(); + // Create potential parameter bindings for each template. Add those + // to a trace transaction generator that will determine how the + // parameters are used. + for (Entry kv : getProcedures().entrySet()) { + // Sanity check that the procedure has the right type. + if (!(kv.getValue() instanceof GenericQuery)) { + LOG.error( + String.format( + "Procedure %s does not have the correct class type (GenericQuery).", + kv.getValue().toString())); + continue; + } + GenericQuery proc = (GenericQuery) kv.getValue(); + QueryTemplateInfo info = proc.getQueryTemplateInfo(); + + // Parse parameter values and add each combination to a generator. + // FIXME: This method does not currently support NULLable + // parameters since they will be parsed as an empty string. + // See Also: comments in GenericQuery.getStatement() + // Additionally, it's somewhat unnecessarily expensive, since + // we convert from XML represented values back to CSV separated + // list of params. + List list = new ArrayList<>(); + String[] paramsTypes = info.getParamsTypes(); + CSVParser parser = new CSVParserBuilder() + .withQuoteChar('\'') + .build(); + for (String binding : info.getParamsValues()) { + Object[] params = parser.parseLine(binding); + assert paramsTypes.length == params.length; + list.add(new GenericQueryOperation(params)); + } + generators.put(proc.getClass(), new TraceTransactionGenerator(list)); + } + + // Create workers. + int numTerminals = workConf.getTerminals(); + LOG.info(String.format("Creating %d workers for templated benchmark", numTerminals)); + for (int i = 0; i < numTerminals; i++) { + workers.add(new TemplatedWorker(this, i, generators)); + } + } catch (Exception e) { + throw new IllegalStateException("Unable to create workers", e); + } + return workers; + } + + @Override + protected Loader makeLoaderImpl() { + throw new UnsupportedOperationException("Templated benchmarks do not currently support loading directly."); + } + + private CustomClassLoader loadQueryTemplates(String file) { + // Instantiate Java compiler. + CustomClassLoader ccloader = new CustomClassLoader(this.classLoader); + try { + // Parse template file. + final ICompilerFactory compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory( + TemplatedBenchmark.class.getClassLoader()); + + JAXBContext jc = JAXBContext.newInstance("com.oltpbenchmark.api.templates"); + SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = sf.newSchema(new StreamSource(this.getClass().getResourceAsStream("/templates.xsd"))); + Unmarshaller unmarshaller = jc.createUnmarshaller(); + unmarshaller.setSchema(schema); + + StreamSource streamSource = new StreamSource(new FileInputStream(file)); + JAXBElement result = unmarshaller.unmarshal(streamSource, TemplatesType.class); + TemplatesType templates = result.getValue(); + + for (TemplateType template : templates.getTemplateList()) { + ImmutableParsedQueryTemplate.Builder b = ImmutableParsedQueryTemplate.builder(); + b.name(template.getName()); + b.query(template.getQuery()); + b.paramsTypes(template.getTypes().getTypeList()); + for (ValuesType paramValue : template.getValues()) { + b.addParamsValues(String.join(",", paramValue.getValueList())); + } + + ParsedQueryTemplate qt = b.build(); + // Create and compile class. + final String s = """ + package %s ; + public final class %s extends %s { + @Override + public %s getQueryTemplateInfo() { + return ImmutableQueryTemplateInfo.builder() + .query(new %s(\"%s\")) + .paramsTypes(new String[] {%s}) + .paramsValues(new String[] {%s}) + .build(); + } + } + """.formatted( + GenericQuery.class.getPackageName(), + qt.getName(), + GenericQuery.class.getCanonicalName(), + QueryTemplateInfo.class.getCanonicalName(), + SQLStmt.class.getCanonicalName(), + StringEscapeUtils.escapeJava(qt.getQuery()), + getParamsString(qt.getParamsTypes()), + getParamsString(qt.getParamsValues())); + LOG.debug("Class definition for query template {}:\n {}", qt.getName(), s); + final String qualifiedClassName = GenericQuery.class.getPackageName() + "." + qt.getName(); + final ISimpleCompiler compiler = compilerFactory.newSimpleCompiler(); + compiler.setTargetVersion(17); + compiler.setParentClassLoader(this.classLoader); + compiler.cook(s); + ccloader.putClass(qualifiedClassName, + compiler.getClassLoader().loadClass(qualifiedClassName)); + } + } catch (Exception e) { + throw new IllegalStateException("Unable to load query templates", e); + } + return ccloader; + } + + private String getParamsString(List params) { + String result = ""; + for (String param : params) { + result += "\"" + StringEscapeUtils.escapeJava(param) + "\","; + } + return result.isEmpty() ? "" : result.substring(0, result.length() - 1); + } + + private static class CustomClassLoader extends ClassLoader { + + private final Map> classes = new HashMap<>(); + + private CustomClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + Class clazz = classes.get(name); + return clazz != null ? clazz : super.findClass(name); + } + + public void putClass(String name, Class clazz) { + classes.put(name, clazz); + } + + @SuppressWarnings("unchecked") + public List> getProcedureClasses() { + List> result = new ArrayList<>(); + for (Class clz : classes.values()) { + if (Procedure.class.isAssignableFrom(clz)) { + result.add((Class) clz); + } + } + return result; + } + } + + @Value.Immutable + public interface ParsedQueryTemplate { + + /** Template name. */ + String getName(); + + /** Query string for this template. */ + String getQuery(); + + /** Potential query parameter types. */ + @Value.Default + default List getParamsTypes() { + return List.of(); + } + + /** Potential query parameter values. */ + @Value.Default + default List getParamsValues() { + return List.of(); + } + } + +} diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedWorker.java b/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedWorker.java new file mode 100644 index 000000000..c1fcdfc7e --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/TemplatedWorker.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.benchmarks.templated; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +import com.oltpbenchmark.api.Procedure; +import com.oltpbenchmark.api.Procedure.UserAbortException; +import com.oltpbenchmark.benchmarks.templated.procedures.GenericQuery; +import com.oltpbenchmark.benchmarks.templated.util.TraceTransactionGenerator; +import com.oltpbenchmark.api.TransactionType; +import com.oltpbenchmark.api.Worker; +import com.oltpbenchmark.types.TransactionStatus; + +public class TemplatedWorker extends Worker { + + protected final Map, TraceTransactionGenerator> generators; + + public TemplatedWorker(TemplatedBenchmark benchmarkModule, int id, + Map, TraceTransactionGenerator> generators) { + super(benchmarkModule, id); + this.rng().setSeed(benchmarkModule.getWorkloadConfiguration().getRandomSeed()); + this.generators = generators; + } + + @Override + protected TransactionStatus executeWork(Connection conn, TransactionType nextTransaction) + throws UserAbortException, SQLException { + try { + Class clazz = nextTransaction.getProcedureClass(); + GenericQuery proc = (GenericQuery) this.getProcedure(clazz); + if (!generators.get(clazz).isEmpty()) { + // If there is a generator available use it to create a + // parameter binding. + TraceTransactionGenerator generator = generators.get(clazz); + proc.run(conn, generator.nextTransaction().getParams()); + } else { + // If the generator has no transactions, there are no parameters. + proc.run(conn); + } + + } catch (ClassCastException e) { + throw new RuntimeException(e); + } + + return (TransactionStatus.SUCCESS); + + } +} + diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/procedures/GenericQuery.java b/src/main/java/com/oltpbenchmark/benchmarks/templated/procedures/GenericQuery.java new file mode 100644 index 000000000..2d5df5957 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/procedures/GenericQuery.java @@ -0,0 +1,99 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.benchmarks.templated.procedures; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import org.immutables.value.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.oltpbenchmark.api.Procedure; +import com.oltpbenchmark.api.SQLStmt; + +public abstract class GenericQuery extends Procedure { + + protected static final Logger LOG = LoggerFactory.getLogger(GenericQuery.class); + + /** Execution method with parameters. */ + public void run(Connection conn, List params) throws SQLException { + try (PreparedStatement stmt = getStatement(conn, params); ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + //do nothing + } + } + conn.commit(); + } + + /** Execution method without parameters. */ + public void run(Connection conn) throws SQLException { + QueryTemplateInfo queryTemplateInfo = this.getQueryTemplateInfo(); + + try (PreparedStatement stmt = this.getPreparedStatement(conn, queryTemplateInfo.getQuery()); ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + //do nothing + } + } + conn.commit(); + } + + public PreparedStatement getStatement(Connection conn, List params) throws SQLException { + QueryTemplateInfo queryTemplateInfo = this.getQueryTemplateInfo(); + + PreparedStatement stmt = this.getPreparedStatement(conn, queryTemplateInfo.getQuery()); + String[] paramsTypes = queryTemplateInfo.getParamsTypes(); + for (int i = 0; i < paramsTypes.length; i++) { + if (paramsTypes[i].equalsIgnoreCase("NULL")) { + stmt.setNull(i + 1, Types.NULL); + } else { + try { + // TODO: add support for nullable other types + // For instance, can we provide a tag in the XML file to represent a NULL value? + // Or does it need a special marker like "$null" to signify a NULL value? + Object param = params.get(i); + stmt.setObject(i + 1, param, Integer.parseInt(Types.class.getDeclaredField(paramsTypes[i]).get(null).toString())); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException( + "Error when setting parameters. Parameter type: " + paramsTypes[i] + ", parameter value: " + params.get(i)); + } + } + } + return stmt; + } + + public abstract QueryTemplateInfo getQueryTemplateInfo(); + + @Value.Immutable + public interface QueryTemplateInfo { + + /** Query string for this template. */ + SQLStmt getQuery(); + + /** Query parameter types. */ + String[] getParamsTypes(); + + /** Potential query parameter values. */ + String[] getParamsValues(); + } + +} diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/util/GenericQueryOperation.java b/src/main/java/com/oltpbenchmark/benchmarks/templated/util/GenericQueryOperation.java new file mode 100644 index 000000000..b2a3cdc73 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/util/GenericQueryOperation.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.benchmarks.templated.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.oltpbenchmark.api.Operation; + +/** + * Immutable class containing information about transactions. + */ +public class GenericQueryOperation extends Operation { + + public final List params; + + + public GenericQueryOperation(Object[] params) { + super(); + this.params = Collections.unmodifiableList(Arrays.asList(params)); + } + + public List getParams() { + return params; + } +} diff --git a/src/main/java/com/oltpbenchmark/benchmarks/templated/util/TraceTransactionGenerator.java b/src/main/java/com/oltpbenchmark/benchmarks/templated/util/TraceTransactionGenerator.java new file mode 100644 index 000000000..782df565d --- /dev/null +++ b/src/main/java/com/oltpbenchmark/benchmarks/templated/util/TraceTransactionGenerator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.benchmarks.templated.util; + +import java.util.Collections; +import java.util.List; + +import com.oltpbenchmark.api.TransactionGenerator; +import com.oltpbenchmark.distributions.CyclicCounterGenerator; + +public class TraceTransactionGenerator implements TransactionGenerator { + + private final List transactions; + private final CyclicCounterGenerator nextInTrace; + + /** + * @param transactions a list of transactions shared between threads. + */ + public TraceTransactionGenerator(List transactions) { + this.transactions = Collections.unmodifiableList(transactions); + this.nextInTrace = new CyclicCounterGenerator(transactions.size()); + } + + @Override + public GenericQueryOperation nextTransaction() { + return transactions.get(nextInTrace.nextInt()); + } + + public boolean isEmpty() { + return transactions.size() == 0; + } + +} diff --git a/src/main/java/com/oltpbenchmark/distributions/CyclicCounterGenerator.java b/src/main/java/com/oltpbenchmark/distributions/CyclicCounterGenerator.java new file mode 100644 index 000000000..3b6ec4902 --- /dev/null +++ b/src/main/java/com/oltpbenchmark/distributions/CyclicCounterGenerator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 by OLTPBenchmark Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 com.oltpbenchmark.distributions; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Thread-safe cyclic counter generator. + */ +public class CyclicCounterGenerator extends IntegerGenerator { + + private final int maxVal; + private final AtomicInteger counter; + + public CyclicCounterGenerator(int maxVal) { + this.maxVal = maxVal; + this.counter = new AtomicInteger(0); + } + + protected void setLastInt(int last) { + throw new UnsupportedOperationException("Cyclic counter cannot be set to a value"); + } + + @Override + public int nextInt() { + return counter.accumulateAndGet(1, (index, inc) -> (++index >= maxVal ? 0 : index)); + } + + @Override + public String nextString() { + return "" + nextInt(); + } + + @Override + public String lastString() { + return "" + lastInt(); + } + + @Override + public int lastInt() { + return counter.get(); + } + + @Override + public double mean() { + throw new UnsupportedOperationException("Not implemented yet"); + } + +} diff --git a/src/main/java/com/oltpbenchmark/util/ClassUtil.java b/src/main/java/com/oltpbenchmark/util/ClassUtil.java index 0dffaedbb..9963b6a33 100644 --- a/src/main/java/com/oltpbenchmark/util/ClassUtil.java +++ b/src/main/java/com/oltpbenchmark/util/ClassUtil.java @@ -186,11 +186,23 @@ public static Constructor getConstructor(Class target_class, Class. * @return */ public static Class getClass(String class_name) { + return getClass(ClassLoader.getSystemClassLoader(), class_name); + } + + /** + * @param loader + * @param class_name + * @return + */ + public static Class getClass(ClassLoader loader, String class_name) { + Class target_class = null; try { - return ClassUtils.getClass(class_name); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + target_class = ClassUtils.getClass(loader, class_name); + } catch (Exception ex) { + throw new RuntimeException("Failed to retrieve class for " + class_name, ex); } + return target_class; + } } diff --git a/src/main/java/com/oltpbenchmark/util/SQLUtil.java b/src/main/java/com/oltpbenchmark/util/SQLUtil.java index 3471dda1b..c0cd7e8c9 100644 --- a/src/main/java/com/oltpbenchmark/util/SQLUtil.java +++ b/src/main/java/com/oltpbenchmark/util/SQLUtil.java @@ -481,7 +481,6 @@ public static String selectColValues(DatabaseType databaseType, Table catalog_tb public static AbstractCatalog getCatalog(BenchmarkModule benchmarkModule, DatabaseType databaseType, Connection connection) throws SQLException { switch (databaseType) { case NOISEPAGE: // fall-through - case SQLITE: case HSQLDB: return getCatalogHSQLDB(benchmarkModule); default: diff --git a/src/main/resources/templates.xsd b/src/main/resources/templates.xsd new file mode 100644 index 000000000..a2bdf7763 --- /dev/null +++ b/src/main/resources/templates.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/oltpbenchmark/api/AbstractTestCase.java b/src/test/java/com/oltpbenchmark/api/AbstractTestCase.java index 5afb3aa73..d09a1d366 100644 --- a/src/test/java/com/oltpbenchmark/api/AbstractTestCase.java +++ b/src/test/java/com/oltpbenchmark/api/AbstractTestCase.java @@ -72,12 +72,14 @@ public abstract class AbstractTestCase { public AbstractTestCase(boolean createDatabase, boolean loadDatabase) { + this.benchmark = null; this.createDatabase = createDatabase; this.loadDatabase = loadDatabase; this.ddlOverridePath = null; } public AbstractTestCase(boolean createDatabase, boolean loadDatabase, String ddlOverridePath) { + this.benchmark = null; this.createDatabase = createDatabase; this.loadDatabase = loadDatabase; this.ddlOverridePath = ddlOverridePath; @@ -112,17 +114,10 @@ public final void setUp() throws Exception { server.start(); this.workConf = new WorkloadConfiguration(); - TransactionTypes txnTypes = new TransactionTypes(new ArrayList<>()); - - int id = 0; - for (Class procedureClass : procedures()) { - TransactionType tt = new TransactionType(procedureClass, id++, false, 0, 0); - txnTypes.add(tt); - } String DB_CONNECTION = String.format("jdbc:hsqldb:hsql://localhost:%d/benchbase", server.getPort()); - this.workConf.setTransTypes(txnTypes); + this.workConf.setTransTypes(proceduresToTransactionTypes(procedures())); this.workConf.setDatabaseType(DB_TYPE); this.workConf.setUrl(DB_CONNECTION); this.workConf.setScaleFactor(DB_SCALE_FACTOR); @@ -138,6 +133,12 @@ public final void setUp() throws Exception { new Class[]{WorkloadConfiguration.class}); assertNotNull(this.benchmark); + // HACK: calling this a second time is a cheap no-op for most benchmark + // tests, but actually ensures that the procedures list is populated + // for the TestTemplatedWorker test which doesn't know its procedures + // until after the benchmark is initialized and the config is loaded. + assertNotNull(this.procedures()); + this.conn = this.benchmark.makeConnection(); assertNotNull(this.conn); @@ -146,23 +147,11 @@ public final void setUp() throws Exception { assertNotNull(this.catalog); if (createDatabase) { - try { - this.benchmark.createDatabase(); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - cleanupServer(); - fail("createDatabase() failed"); - } + this.createDatabase(); } if (loadDatabase) { - try { - this.benchmark.loadDatabase(); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - cleanupServer(); - fail("loadDatabase() failed"); - } + this.loadDatabase(); } try { @@ -174,6 +163,38 @@ public final void setUp() throws Exception { } } + protected TransactionTypes proceduresToTransactionTypes(List> procedures) { + TransactionTypes txnTypes = new TransactionTypes(new ArrayList<>()); + + int id = 0; + for (Class procedureClass : procedures) { + TransactionType tt = new TransactionType(procedureClass, id++, false, 0, 0); + txnTypes.add(tt); + } + + return txnTypes; + } + + protected void createDatabase() { + try { + this.benchmark.createDatabase(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + cleanupServer(); + fail("createDatabase() failed"); + } + } + + protected void loadDatabase() { + try { + this.benchmark.loadDatabase(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + cleanupServer(); + fail("loadDatabase() failed"); + } + } + protected void customWorkloadConfiguration(WorkloadConfiguration workConf) { } @@ -193,7 +214,7 @@ public final void tearDown() throws Exception { cleanupServer(); } - private void cleanupServer() { + protected void cleanupServer() { if (server != null) { LOG.trace("shutting down catalogs..."); diff --git a/src/test/java/com/oltpbenchmark/api/AbstractTestWorker.java b/src/test/java/com/oltpbenchmark/api/AbstractTestWorker.java index ed31a07ba..c9a59b751 100644 --- a/src/test/java/com/oltpbenchmark/api/AbstractTestWorker.java +++ b/src/test/java/com/oltpbenchmark/api/AbstractTestWorker.java @@ -39,6 +39,10 @@ public AbstractTestWorker() { super(true, true); } + public AbstractTestWorker(String ddlOverridePath) { + super(true, true, ddlOverridePath); + } + @Override public List ignorableTables() { return null; @@ -97,7 +101,6 @@ public void testExecuteWork() throws Exception { } catch (Throwable ex) { throw new RuntimeException("Failed to execute " + txnType, ex); } finally { - LOG.info("completed execution of [{}] in {} ms", txnType.toString(), sw.getTime(TimeUnit.MILLISECONDS)); } } diff --git a/src/test/java/com/oltpbenchmark/benchmarks/templated/README.md b/src/test/java/com/oltpbenchmark/benchmarks/templated/README.md new file mode 100644 index 000000000..db9b536b3 --- /dev/null +++ b/src/test/java/com/oltpbenchmark/benchmarks/templated/README.md @@ -0,0 +1,9 @@ +# Templated Benchmark Unit Tests + +Note: The templated benchmark does not currently support data loading. + +The current sample config files are built off using the TPC-C data which is expected to be loaded first. + +Hence, in this directory we omit the usual `TestTemplatedBenchmark.java` and `TestTemplatedLoader.java` files. + +To make the unit tests work, we reuse the TPC-C data loader as a part of the `setUp` override. \ No newline at end of file diff --git a/src/test/java/com/oltpbenchmark/benchmarks/templated/TestTemplatedWorker.java b/src/test/java/com/oltpbenchmark/benchmarks/templated/TestTemplatedWorker.java new file mode 100644 index 000000000..19cf7cf99 --- /dev/null +++ b/src/test/java/com/oltpbenchmark/benchmarks/templated/TestTemplatedWorker.java @@ -0,0 +1,123 @@ +package com.oltpbenchmark.benchmarks.templated; + +import com.oltpbenchmark.api.AbstractTestWorker; +import com.oltpbenchmark.api.Procedure; + +import com.oltpbenchmark.DBWorkload; +import com.oltpbenchmark.WorkloadConfiguration; + +import com.oltpbenchmark.benchmarks.tpcc.TPCCBenchmark; + +import java.nio.file.Paths; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.configuration2.XMLConfiguration; +import org.apache.commons.configuration2.ex.ConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; + + +public class TestTemplatedWorker extends AbstractTestWorker { + private static final Logger LOG = LoggerFactory.getLogger(TestTemplatedWorker.class); + + public static final String DDL_OVERRIDE_PATH = Paths.get("src", "main", "resources", "benchmarks", "tpcc", "ddl-generic.sql").toAbsolutePath().toString(); + public static final String SAMPLE_TEMPLATED_LOADING_CONFIG = Paths.get("config", "sqlite", "sample_tpcc_config.xml").toAbsolutePath().toString(); + public static final String SAMPLE_TEMPLATED_CONFIG = Paths.get("config", "sqlite", "sample_templated_config.xml").toAbsolutePath().toString(); + public static final String TEMPLATES_CONFIG = Paths.get("data", "templated", "example.xml").toAbsolutePath().toString(); + + TPCCBenchmark tpccBenchmark = null; + + public TestTemplatedWorker() { + // Technically we aren't creating this schema with the + // TemplatedBenchmark, but specifying the DDL that we are using (see + // below) allows some other checks to pass. + super(DDL_OVERRIDE_PATH); + } + + public static void setWorkloadConfigXml(WorkloadConfiguration workConf) { + // Load the configuration file so we can parse the query_template_file value. + try { + XMLConfiguration xmlConf = DBWorkload.buildConfiguration(SAMPLE_TEMPLATED_CONFIG); + workConf.setXmlConfig(xmlConf); + } + catch (ConfigurationException ex) { + LOG.error("Error loading configuration: " + SAMPLE_TEMPLATED_CONFIG, ex); + } + } + + @Override + protected void customWorkloadConfiguration(WorkloadConfiguration workConf) { + setWorkloadConfigXml(workConf); + } + + @Override + public List> procedures() { + // Note: the first time this is called is before the benchmark is + // initialized, so it should return nothing. + // It's only populated after the config is loaded for the benchmark. + List> procedures = new ArrayList<>(); + if (this.benchmark != null) { + procedures = this.benchmark.getProcedureClasses(); + if (!procedures.isEmpty() && this.workConf.getTransTypes().isEmpty()) { + workConf.setTransTypes(proceduresToTransactionTypes(procedures)); + } + } + return procedures; + } + + @Override + public Class benchmarkClass() { + return TemplatedBenchmark.class; + } + + private void setupTpccBenchmarkHelper() throws SQLException { + if (this.tpccBenchmark != null) { + return; + } + + // Create a second benchmark to re/ab/use for loading the database (tpcc in this case). + WorkloadConfiguration tpccWorkConf = new WorkloadConfiguration(); + tpccWorkConf.setDatabaseType(this.workConf.getDatabaseType()); + tpccWorkConf.setUrl(this.workConf.getUrl()); + tpccWorkConf.setScaleFactor(this.workConf.getScaleFactor()); + tpccWorkConf.setTerminals(this.workConf.getTerminals()); + tpccWorkConf.setBatchSize(this.workConf.getBatchSize()); + // tpccWorkConf.setBenchmarkName(BenchmarkModule.convertBenchmarkClassToBenchmarkName(TPCCBenchmark.class)); + tpccWorkConf.setBenchmarkName(TPCCBenchmark.class.getSimpleName().toLowerCase().replace("benchmark", "")); + + this.tpccBenchmark = new TPCCBenchmark(this.workConf); + conn = this.tpccBenchmark.makeConnection(); + assertNotNull(conn); + this.tpccBenchmark.refreshCatalog(); + catalog = this.tpccBenchmark.getCatalog(); + assertNotNull(catalog); + } + + protected void createDatabase() { + try { + this.setupTpccBenchmarkHelper(); + this.tpccBenchmark.createDatabase(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + cleanupServer(); + fail("createDatabase() failed"); + } + } + + protected void loadDatabase() { + try { + this.setupTpccBenchmarkHelper(); + this.tpccBenchmark.loadDatabase(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + cleanupServer(); + fail("loadDatabase() failed"); + } + } +}