diff --git a/bin/bindings.properties b/bin/bindings.properties index e3d1d429..6f03c633 100644 --- a/bin/bindings.properties +++ b/bin/bindings.properties @@ -1,79 +1,80 @@ # # Copyright (c) 2012 - 2016 YCSB contributors. All rights reserved. # # 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. See accompanying # LICENSE file. # #DATABASE BINDINGS # # Available bindings should be listed here in the form of # name:class # # - the name must start in column 0. # - the name is also the directory where the class can be found. # - if the directory contains multiple versions with different classes, # use a dash with the version. (e.g. cassandra-7, cassandra-cql) # accumulo:com.yahoo.ycsb.db.accumulo.AccumuloClient accumulo1.6:com.yahoo.ycsb.db.accumulo.AccumuloClient accumulo1.7:com.yahoo.ycsb.db.accumulo.AccumuloClient accumulo1.8:com.yahoo.ycsb.db.accumulo.AccumuloClient aerospike:com.yahoo.ycsb.db.AerospikeClient asynchbase:com.yahoo.ycsb.db.AsyncHBaseClient arangodb:com.yahoo.ycsb.db.arangodb.ArangoDBClient arangodb3:com.yahoo.ycsb.db.arangodb.ArangoDBClient azuredocumentdb:com.yahoo.ycsb.db.azuredocumentdb.AzureDocumentDBClient azuretablestorage:com.yahoo.ycsb.db.azuretablestorage.AzureClient basic:com.yahoo.ycsb.BasicDB basicts:com.yahoo.ycsb.BasicTSDB cassandra-cql:com.yahoo.ycsb.db.CassandraCQLClient cassandra2-cql:com.yahoo.ycsb.db.CassandraCQLClient cloudspanner:com.yahoo.ycsb.db.cloudspanner.CloudSpannerClient couchbase:com.yahoo.ycsb.db.CouchbaseClient couchbase2:com.yahoo.ycsb.db.couchbase2.Couchbase2Client dynamodb:com.yahoo.ycsb.db.DynamoDBClient elasticsearch:com.yahoo.ycsb.db.ElasticsearchClient elasticsearch5:com.yahoo.ycsb.db.elasticsearch5.ElasticsearchClient elasticsearch5-rest:com.yahoo.ycsb.db.elasticsearch5.ElasticsearchRestClient foundationdb:com.yahoo.ycsb.db.foundationdb.FoundationDBClient geode:com.yahoo.ycsb.db.GeodeClient googlebigtable:com.yahoo.ycsb.db.GoogleBigtableClient googledatastore:com.yahoo.ycsb.db.GoogleDatastoreClient hbase098:com.yahoo.ycsb.db.HBaseClient hbase10:com.yahoo.ycsb.db.HBaseClient10 hbase12:com.yahoo.ycsb.db.hbase12.HBaseClient12 hbase14:com.yahoo.ycsb.db.hbase14.HBaseClient14 hbase20:com.yahoo.ycsb.db.hbase14.HBaseClient20 hypertable:com.yahoo.ycsb.db.HypertableClient ignite:com.yahoo.ycsb.db.ignite.IgniteClient ignite-sql:com.yahoo.ycsb.db.ignite.IgniteSqlClient infinispan-cs:com.yahoo.ycsb.db.InfinispanRemoteClient infinispan:com.yahoo.ycsb.db.InfinispanClient jdbc:com.yahoo.ycsb.db.JdbcDBClient kudu:com.yahoo.ycsb.db.KuduYCSBClient memcached:com.yahoo.ycsb.db.MemcachedClient mongodb:com.yahoo.ycsb.db.MongoDbClient mongodb-async:com.yahoo.ycsb.db.AsyncMongoDbClient +mysql:com.yahoo.ycsb.db.JAsyncMySQLDBClient nosqldb:com.yahoo.ycsb.db.NoSqlDbClient orientdb:com.yahoo.ycsb.db.OrientDBClient rados:com.yahoo.ycsb.db.RadosClient redis:com.yahoo.ycsb.db.RedisClient rest:com.yahoo.ycsb.webservice.rest.RestClient riak:com.yahoo.ycsb.db.riak.RiakKVClient rocksdb:com.yahoo.ycsb.db.rocksdb.RocksDBClient s3:com.yahoo.ycsb.db.S3Client solr:com.yahoo.ycsb.db.solr.SolrClient solr6:com.yahoo.ycsb.db.solr6.SolrClient tarantool:com.yahoo.ycsb.db.TarantoolClient diff --git a/bin/ycsb b/bin/ycsb index cb59ad3e..0db74913 100755 --- a/bin/ycsb +++ b/bin/ycsb @@ -1,342 +1,343 @@ #!/usr/bin/env python # # Copyright (c) 2012 - 2015 YCSB contributors. All rights reserved. # # 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. See accompanying # LICENSE file. # import errno import fnmatch import io import os import shlex import sys import subprocess try: mod = __import__('argparse') import argparse except ImportError: print >> sys.stderr, '[ERROR] argparse not found. Try installing it via "pip".' exit(1) BASE_URL = "https://github.com/brianfrankcooper/YCSB/tree/master/" COMMANDS = { "shell" : { "command" : "", "description" : "Interactive mode", "main" : "com.yahoo.ycsb.CommandLine", }, "load" : { "command" : "-load", "description" : "Execute the load phase", "main" : "com.yahoo.ycsb.Client", }, "run" : { "command" : "-t", "description" : "Execute the transaction phase", "main" : "com.yahoo.ycsb.Client", }, } DATABASES = { "accumulo" : "com.yahoo.ycsb.db.accumulo.AccumuloClient", "accumulo1.6" : "com.yahoo.ycsb.db.accumulo.AccumuloClient", "accumulo1.7" : "com.yahoo.ycsb.db.accumulo.AccumuloClient", "accumulo1.8" : "com.yahoo.ycsb.db.accumulo.AccumuloClient", "aerospike" : "com.yahoo.ycsb.db.AerospikeClient", "arangodb" : "com.yahoo.ycsb.db.arangodb.ArangoDBClient", "arangodb3" : "com.yahoo.ycsb.db.arangodb.ArangoDBClient", "asynchbase" : "com.yahoo.ycsb.db.AsyncHBaseClient", "azuredocumentdb" : "com.yahoo.ycsb.db.azuredocumentdb.AzureDocumentDBClient", "azuretablestorage" : "com.yahoo.ycsb.db.azuretablestorage.AzureClient", "basic" : "com.yahoo.ycsb.BasicDB", "basicts" : "com.yahoo.ycsb.BasicTSDB", "cassandra-cql": "com.yahoo.ycsb.db.CassandraCQLClient", "cassandra2-cql": "com.yahoo.ycsb.db.CassandraCQLClient", "cloudspanner" : "com.yahoo.ycsb.db.cloudspanner.CloudSpannerClient", "couchbase" : "com.yahoo.ycsb.db.CouchbaseClient", "couchbase2" : "com.yahoo.ycsb.db.couchbase2.Couchbase2Client", "dynamodb" : "com.yahoo.ycsb.db.DynamoDBClient", "elasticsearch": "com.yahoo.ycsb.db.ElasticsearchClient", "elasticsearch5": "com.yahoo.ycsb.db.elasticsearch5.ElasticsearchClient", "foundationdb" : "com.yahoo.ycsb.db.foundationdb.FoundationDBClient", "geode" : "com.yahoo.ycsb.db.GeodeClient", "googlebigtable" : "com.yahoo.ycsb.db.GoogleBigtableClient", "googledatastore" : "com.yahoo.ycsb.db.GoogleDatastoreClient", "hbase098" : "com.yahoo.ycsb.db.HBaseClient", "hbase10" : "com.yahoo.ycsb.db.HBaseClient10", "hbase12" : "com.yahoo.ycsb.db.hbase12.HBaseClient12", "hbase14" : "com.yahoo.ycsb.db.hbase14.HBaseClient14", "hbase20" : "com.yahoo.ycsb.db.hbase20.HBaseClient20", "hypertable" : "com.yahoo.ycsb.db.HypertableClient", "ignite" : "com.yahoo.ycsb.db.ignite.IgniteClient", "ignite-sql" : "com.yahoo.ycsb.db.ignite.IgniteSqlClient", "infinispan-cs": "com.yahoo.ycsb.db.InfinispanRemoteClient", "infinispan" : "com.yahoo.ycsb.db.InfinispanClient", "jdbc" : "com.yahoo.ycsb.db.JdbcDBClient", "kudu" : "com.yahoo.ycsb.db.KuduYCSBClient", "memcached" : "com.yahoo.ycsb.db.MemcachedClient", "maprdb" : "com.yahoo.ycsb.db.mapr.MapRDBClient", "maprjsondb" : "com.yahoo.ycsb.db.mapr.MapRJSONDBClient", "mongodb" : "com.yahoo.ycsb.db.MongoDbClient", "mongodb-async": "com.yahoo.ycsb.db.AsyncMongoDbClient", + "mysql" : "com.yahoo.ycsb.db.JAsyncMySQLDBClient", "nosqldb" : "com.yahoo.ycsb.db.NoSqlDbClient", "orientdb" : "com.yahoo.ycsb.db.OrientDBClient", "rados" : "com.yahoo.ycsb.db.RadosClient", "redis" : "com.yahoo.ycsb.db.RedisClient", "rest" : "com.yahoo.ycsb.webservice.rest.RestClient", "riak" : "com.yahoo.ycsb.db.riak.RiakKVClient", "rocksdb" : "com.yahoo.ycsb.db.rocksdb.RocksDBClient", "s3" : "com.yahoo.ycsb.db.S3Client", "solr" : "com.yahoo.ycsb.db.solr.SolrClient", "solr6" : "com.yahoo.ycsb.db.solr6.SolrClient", "tarantool" : "com.yahoo.ycsb.db.TarantoolClient", } OPTIONS = { "-P file" : "Specify workload file", "-p key=value" : "Override workload property", "-s" : "Print status to stderr", "-target n" : "Target ops/sec (default: unthrottled)", "-threads n" : "Number of client threads (default: 1)", "-cp path" : "Additional Java classpath entries", "-jvm-args args" : "Additional arguments to the JVM", } def usage(): output = io.BytesIO() print >> output, "%s command database [options]" % sys.argv[0] print >> output, "\nCommands:" for command in sorted(COMMANDS.keys()): print >> output, " %s %s" % (command.ljust(14), COMMANDS[command]["description"]) print >> output, "\nDatabases:" for db in sorted(DATABASES.keys()): print >> output, " %s %s" % (db.ljust(14), BASE_URL + db.split("-")[0]) print >> output, "\nOptions:" for option in sorted(OPTIONS.keys()): print >> output, " %s %s" % (option.ljust(14), OPTIONS[option]) print >> output, """\nWorkload Files: There are various predefined workloads under workloads/ directory. See https://github.com/brianfrankcooper/YCSB/wiki/Core-Properties for the list of workload properties.""" return output.getvalue() # Python 2.6 doesn't have check_output. Add the method as it is in Python 2.7 # Based on https://github.com/python/cpython/blob/2.7/Lib/subprocess.py#L545 def check_output(*popenargs, **kwargs): r"""Run command with arguments and return its output as a byte string. If the exit code was non-zero it raises a CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute and output in the output attribute. The arguments are the same as for the Popen constructor. Example: >>> check_output(["ls", "-l", "/dev/null"]) 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' The stdout argument is not allowed as it is used internally. To capture standard error in the result, use stderr=STDOUT. >>> check_output(["/bin/sh", "-c", ... "ls -l non_existent_file ; exit 0"], ... stderr=STDOUT) 'ls: non_existent_file: No such file or directory\n' """ if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] error = subprocess.CalledProcessError(retcode, cmd) error.output = output raise error return output def debug(message): print >> sys.stderr, "[DEBUG] ", message def warn(message): print >> sys.stderr, "[WARN] ", message def error(message): print >> sys.stderr, "[ERROR] ", message def find_jars(dir, glob='*.jar'): jars = [] for (dirpath, dirnames, filenames) in os.walk(dir): for filename in fnmatch.filter(filenames, glob): jars.append(os.path.join(dirpath, filename)) return jars def get_ycsb_home(): dir = os.path.abspath(os.path.dirname(sys.argv[0])) while "LICENSE.txt" not in os.listdir(dir): dir = os.path.join(dir, os.path.pardir) return os.path.abspath(dir) def is_distribution(): # If there's a top level pom, we're a source checkout. otherwise a dist artifact return "pom.xml" not in os.listdir(get_ycsb_home()) # Run the maven dependency plugin to get the local jar paths. # presumes maven can run, so should only be run on source checkouts # will invoke the 'package' goal for the given binding in order to resolve intra-project deps # presumes maven properly handles system-specific path separators # Given module is full module name eg. 'core' or 'couchbase-binding' def get_classpath_from_maven(module): try: debug("Running 'mvn -pl com.yahoo.ycsb:" + module + " -am package -DskipTests " "dependency:build-classpath -DincludeScope=compile -Dmdep.outputFilterFile=true'") mvn_output = check_output(["mvn", "-pl", "com.yahoo.ycsb:" + module, "-am", "package", "-DskipTests", "dependency:build-classpath", "-DincludeScope=compile", "-Dmdep.outputFilterFile=true"]) # the above outputs a "classpath=/path/tojar:/path/to/other/jar" for each module # the last module will be the datastore binding line = [x for x in mvn_output.splitlines() if x.startswith("classpath=")][-1:] return line[0][len("classpath="):] except subprocess.CalledProcessError, err: error("Attempting to generate a classpath from Maven failed " "with return code '" + str(err.returncode) + "'. The output from " "Maven follows, try running " "'mvn -DskipTests package dependency:build-classpath' on your " "own and correct errors." + os.linesep + os.linesep + "mvn output:" + os.linesep + err.output) sys.exit(err.returncode) def main(): p = argparse.ArgumentParser( usage=usage(), formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument('-cp', dest='classpath', help="""Additional classpath entries, e.g. '-cp /tmp/hbase-1.0.1.1/conf'. Will be prepended to the YCSB classpath.""") p.add_argument("-jvm-args", default=[], type=shlex.split, help="""Additional arguments to pass to 'java', e.g. '-Xmx4g'""") p.add_argument("command", choices=sorted(COMMANDS), help="""Command to run.""") p.add_argument("database", choices=sorted(DATABASES), help="""Database to test.""") args, remaining = p.parse_known_args() ycsb_home = get_ycsb_home() # Use JAVA_HOME to find java binary if set, otherwise just use PATH. java = "java" java_home = os.getenv("JAVA_HOME") if java_home: java = os.path.join(java_home, "bin", "java") db_classname = DATABASES[args.database] command = COMMANDS[args.command]["command"] main_classname = COMMANDS[args.command]["main"] # Classpath set up binding = args.database.split("-")[0] if binding == "accumulo": warn("The 'accumulo' client has been deprecated in favor of version " "specific bindings. This name still maps to the binding for " "Accumulo 1.6, which is named 'accumulo-1.6'. This alias will " "be removed in a future YCSB release.") binding = "accumulo1.6" if binding == "accumulo1.6": warn("The 'accumulo1.6' client has been deprecated because Accumulo 1.6 " "is EOM. If you are using Accumulo 1.7+ try using the 'accumulo1.7' " "client instead.") if binding == "cassandra2": warn("The 'cassandra2-cql' client has been deprecated. It has been " "renamed to simply 'cassandra-cql'. This alias will be removed" " in the next YCSB release.") binding = "cassandra" if binding == "couchbase": warn("The 'couchbase' client has been deprecated. If you are using " "Couchbase 4.0+ try using the 'couchbase2' client instead.") if binding == "hbase098": warn("The 'hbase098' client has been deprecated because HBase 0.98 " "is EOM. If you are using HBase 1.2+ try using the 'hbase12' " "client instead.") if binding == "hbase10": warn("The 'hbase10' client has been deprecated because HBase 1.0 " "is EOM. If you are using HBase 1.2+ try using the 'hbase12' " "client instead.") if binding == "arangodb3": warn("The 'arangodb3' client has been deprecated. The binding 'arangodb' " "now covers every ArangoDB version. This alias will be removed " "in the next YCSB release.") binding = "arangodb" if is_distribution(): db_dir = os.path.join(ycsb_home, binding + "-binding") # include top-level conf for when we're a binding-specific artifact. # If we add top-level conf to the general artifact, starting here # will allow binding-specific conf to override (because it's prepended) cp = [os.path.join(ycsb_home, "conf")] cp.extend(find_jars(os.path.join(ycsb_home, "lib"))) cp.extend(find_jars(os.path.join(db_dir, "lib"))) else: warn("Running against a source checkout. In order to get our runtime " "dependencies we'll have to invoke Maven. Depending on the state " "of your system, this may take ~30-45 seconds") db_location = "core" if (binding == "basic" or binding == "basicts") else binding project = "core" if (binding == "basic" or binding == "basicts") else binding + "-binding" db_dir = os.path.join(ycsb_home, db_location) # goes first so we can rely on side-effect of package maven_says = get_classpath_from_maven(project) # TODO when we have a version property, skip the glob cp = find_jars(os.path.join(db_dir, "target"), project + "*.jar") # alredy in jar:jar:jar form cp.append(maven_says) cp.insert(0, os.path.join(db_dir, "conf")) classpath = os.pathsep.join(cp) if args.classpath: classpath = os.pathsep.join([args.classpath, classpath]) ycsb_command = ([java] + args.jvm_args + ["-cp", classpath, main_classname, "-db", db_classname] + remaining) if command: ycsb_command.append(command) print >> sys.stderr, " ".join(ycsb_command) try: return subprocess.call(ycsb_command) except OSError as e: if e.errno == errno.ENOENT: error('Command failed. Is java installed and on your PATH?') return 1 else: raise if __name__ == '__main__': sys.exit(main()) diff --git a/distribution/pom.xml b/distribution/pom.xml index b2d55496..939ba1a4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -1,297 +1,302 @@ 4.0.0 com.yahoo.ycsb root 0.15.0 ycsb YCSB Release Distribution Builder pom This module creates the release package of the YCSB with all DB library bindings. It is only used by the build process and does not contain any real code of itself. com.yahoo.ycsb core ${project.version} com.yahoo.ycsb accumulo1.6-binding ${project.version} com.yahoo.ycsb accumulo1.7-binding ${project.version} com.yahoo.ycsb accumulo1.8-binding ${project.version} com.yahoo.ycsb aerospike-binding ${project.version} com.yahoo.ycsb arangodb-binding ${project.version} com.yahoo.ycsb asynchbase-binding ${project.version} com.yahoo.ycsb cassandra-binding ${project.version} com.yahoo.ycsb cloudspanner-binding ${project.version} com.yahoo.ycsb couchbase-binding ${project.version} com.yahoo.ycsb couchbase2-binding ${project.version} com.yahoo.ycsb azuredocumentdb-binding ${project.version} com.yahoo.ycsb azuretablestorage-binding ${project.version} com.yahoo.ycsb dynamodb-binding ${project.version} com.yahoo.ycsb elasticsearch-binding ${project.version} com.yahoo.ycsb elasticsearch5-binding ${project.version} com.yahoo.ycsb foundationdb-binding ${project.version} com.yahoo.ycsb geode-binding ${project.version} com.yahoo.ycsb googledatastore-binding ${project.version} com.yahoo.ycsb googlebigtable-binding ${project.version} com.yahoo.ycsb hbase098-binding ${project.version} com.yahoo.ycsb hbase10-binding ${project.version} com.yahoo.ycsb hbase12-binding ${project.version} com.yahoo.ycsb hbase14-binding ${project.version} com.yahoo.ycsb hbase20-binding ${project.version} com.yahoo.ycsb hypertable-binding ${project.version} com.yahoo.ycsb ignite-binding ${project.version} com.yahoo.ycsb infinispan-binding ${project.version} com.yahoo.ycsb jdbc-binding ${project.version} com.yahoo.ycsb kudu-binding ${project.version} com.yahoo.ycsb memcached-binding ${project.version} com.yahoo.ycsb maprdb-binding ${project.version} com.yahoo.ycsb maprjsondb-binding ${project.version} com.yahoo.ycsb mongodb-binding ${project.version} + + com.yahoo.ycsb + mysql-binding + ${project.version} + com.yahoo.ycsb nosqldb-binding ${project.version} com.yahoo.ycsb orientdb-binding ${project.version} com.yahoo.ycsb rados-binding ${project.version} com.yahoo.ycsb redis-binding ${project.version} com.yahoo.ycsb rest-binding ${project.version} com.yahoo.ycsb riak-binding ${project.version} com.yahoo.ycsb rocksdb-binding ${project.version} com.yahoo.ycsb s3-binding ${project.version} com.yahoo.ycsb solr-binding ${project.version} com.yahoo.ycsb solr6-binding ${project.version} com.yahoo.ycsb tarantool-binding ${project.version} org.apache.maven.plugins maven-assembly-plugin ${maven.assembly.version} src/main/assembly/distribution.xml false posix package single diff --git a/mysql/pom.xml b/mysql/pom.xml new file mode 100644 index 00000000..ec4ecd5c --- /dev/null +++ b/mysql/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + com.yahoo.ycsb + binding-parent + 0.15.0 + ../binding-parent + + + mysql-binding + MySQL DB Binding + jar + + + + JCenter + https://jcenter.bintray.com/ + + + + + + com.github.jasync-sql + jasync-common + 0.8.21 + + + com.github.jasync-sql + jasync-mysql + 0.8.21 + + + com.yahoo.ycsb + core + ${project.version} + provided + + + junit + junit + 4.12 + test + + + org.hsqldb + hsqldb + 2.3.3 + test + + + com.yahoo.ycsb + jdbc-binding + 0.15.0 + compile + + + + \ No newline at end of file diff --git a/mysql/src/main/java/com/yahoo/ycsb/db/JAsyncMySQLDBClient.java b/mysql/src/main/java/com/yahoo/ycsb/db/JAsyncMySQLDBClient.java new file mode 100644 index 00000000..5391dd64 --- /dev/null +++ b/mysql/src/main/java/com/yahoo/ycsb/db/JAsyncMySQLDBClient.java @@ -0,0 +1,209 @@ +package com.yahoo.ycsb.db; + +import com.github.jasync.sql.db.Configuration; +import com.github.jasync.sql.db.QueryResult; +import com.github.jasync.sql.db.RowData; +import com.github.jasync.sql.db.exceptions.UnableToParseURLException; +import com.github.jasync.sql.db.mysql.MySQLConnection; +import com.github.jasync.sql.db.mysql.util.URLParser; +import com.yahoo.ycsb.AsyncDB; +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.StringByteIterator; +import com.yahoo.ycsb.db.flavors.DBFlavor; +import com.yahoo.ycsb.db.flavors.DefaultDBFlavor; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * + */ +public class JAsyncMySQLDBClient extends AsyncDB { + /** The database URL to use when establishing the connection. + * Alternatively db.host, db.port, db.user, and db.passwd can be used. */ + public static final String CONNECTION_URL = "db.url"; + + /** The database host to connect to. */ + private static final String CONNECTION_HOST = "db.host"; + + /** The database port to connect to. */ + private static final String CONNECTION_PORT = "db.port"; + + /** The user name to use to connect to the database. */ + private static final String CONNECTION_USER = "db.user"; + + /** The password to use for establishing the connection. */ + private static final String CONNECTION_PASSWD = "db.passwd"; + + /** The name of the database to connect to. */ + private static final String CONNECTION_DB = "db.name"; + + /** The maximum number of database connections to use (by default unlimited). */ + private static final String CONNECTION_MAX = "db.max_conn"; + + /** Whether to display debug information about the connection pool. */ + private static final String CONNECTION_DEBUG_POOL = "db.debug_pool"; + + + private static final String DEFAULT_PORT = "3306"; + + private static final String DEFAULT_PROP = ""; + private boolean initialized = false; + private DBFlavor dbFlavor = new DefaultDBFlavor(); + + private MySQLConnectionPool connectionPool; + + @Override + public void init() { + if (initialized) { + System.err.println("Client connection already initialized."); + return; + } + + Properties props = getProperties(); + String url = props.getProperty(CONNECTION_URL, DEFAULT_PROP); + Configuration configuration; + try { + configuration = URLParser.INSTANCE.parseOrDie(url, Charset.defaultCharset()); + } catch (UnableToParseURLException e) { + System.err.println("Unable to parse \"db.url\" parameter, falling back to separate parameter parsing."); + String host = props.getProperty(CONNECTION_HOST, DEFAULT_PROP); + String user = props.getProperty(CONNECTION_USER, DEFAULT_PROP); + int port = Integer.parseInt(props.getProperty(CONNECTION_PORT, DEFAULT_PORT)); + String db = props.getProperty(CONNECTION_DB, DEFAULT_PROP); + String password = props.getProperty(CONNECTION_PASSWD, null); + configuration = new Configuration(user, host, port, password, db); + } + int maxConnections = Integer.MAX_VALUE; + if (props.containsKey(CONNECTION_MAX)) { + maxConnections = Integer.parseInt(props.getProperty(CONNECTION_MAX)); + } + boolean debugPool = Boolean.parseBoolean(props.getProperty(CONNECTION_DEBUG_POOL, "false")); + initialized = true; + + connectionPool = new MySQLConnectionPool(configuration, maxConnections); + if (debugPool) { + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { + System.out.println("Available: " + connectionPool.getNumAvailableConnections() + + " Used: " + connectionPool.getNumTakenConnections()); + }, 0, 100, TimeUnit.MILLISECONDS); + } + } + + @Override + public void cleanup() { + connectionPool.close(); + } + + @Override + public CompletableFuture read(String tableName, String key, Set fields, + Map result) { + StatementType type = new StatementType(StatementType.Type.READ, tableName, 1, "", 0); + String read = dbFlavor.createReadStatement(type, key); + MySQLConnection connection = connectionPool.take(); + return connection.sendPreparedStatement(read, Collections.singletonList(key)) + .thenApply(QueryResult::getRows) + .thenApply((resultRows) -> { + connectionPool.giveBack(connection); + if (resultRows == null) { + return Status.UNEXPECTED_STATE; + } + if (resultRows.isEmpty()) { + return Status.NOT_FOUND; + } + if (result != null && fields != null) { + for (String field : fields) { + String value = (String) resultRows.get(0).get(field); + result.put(field, new StringByteIterator(value)); + } + } + return Status.OK; + }); + } + + @Override + public CompletableFuture scan(String tableName, String startKey, int recordCount, Set fields, + Vector> result) { + StatementType type = new StatementType(StatementType.Type.SCAN, tableName, 1, "", 0); + String read = dbFlavor.createScanStatement(type, startKey); + MySQLConnection connection = connectionPool.take(); + return connection.sendPreparedStatement(read, Arrays.asList(startKey, recordCount)) + .thenApply(QueryResult::getRows) + .thenApply((resultRows) -> { + connectionPool.giveBack(connection); + if (resultRows == null || resultRows.size() < recordCount) { + return Status.UNEXPECTED_STATE; + } + for (RowData row : resultRows) { + if (result != null && fields != null) { + HashMap values = new HashMap(); + for (String field : fields) { + String value = (String) row.get(field); + values.put(field, new StringByteIterator(value)); + } + result.add(values); + } + } + return Status.OK; + }); + } + + @Override + public CompletableFuture update(String tableName, String key, Map values) { + Set> orderedValues = values.entrySet(); + String fieldString = orderedValues.stream().map(Map.Entry::getKey) + .reduce((a, b) -> a = a + ", " + b).orElse(""); + List queryParams = orderedValues.stream().map(Map.Entry::getValue) + .map(ByteIterator::toString).collect(Collectors.toCollection(ArrayList::new)); + queryParams.add(key); + StatementType type = new StatementType(StatementType.Type.UPDATE, tableName, + values.size(), fieldString, 0); + + return runModifyingQuery(queryParams, dbFlavor.createUpdateStatement(type, key)); + } + + @Override + public CompletableFuture insert(String table, String key, Map values) { + Set> orderedValues = values.entrySet(); + String fieldString = orderedValues.stream().map(Map.Entry::getKey) + .reduce((a, b) -> a = a + ", " + b).orElse(""); + List queryParams = orderedValues.stream().map(Map.Entry::getValue) + .map(ByteIterator::toString).collect(Collectors.toCollection(ArrayList::new)); + StatementType type = new StatementType(StatementType.Type.INSERT, table, + values.size(), fieldString, 0); + + return runModifyingQuery(queryParams, dbFlavor.createInsertStatement(type, key)); + } + + @Override + public CompletableFuture delete(String tableName, String key) { + StatementType type = new StatementType(StatementType.Type.READ, tableName, 1, "", 0); + return runModifyingQuery(Collections.singletonList(key), dbFlavor.createDeleteStatement(type, key)); + } + + private CompletableFuture runModifyingQuery(List queryParams, String query) { + MySQLConnection connection = connectionPool.take(); + return connection.sendPreparedStatement(query, queryParams).thenApply((qResult) -> { + connectionPool.giveBack(connection); + if (qResult.getRowsAffected() != 1) { + return Status.UNEXPECTED_STATE; + } + return Status.OK; + }); + } + + +} diff --git a/mysql/src/main/java/com/yahoo/ycsb/db/MySQLConnectionPool.java b/mysql/src/main/java/com/yahoo/ycsb/db/MySQLConnectionPool.java new file mode 100644 index 00000000..70b46a47 --- /dev/null +++ b/mysql/src/main/java/com/yahoo/ycsb/db/MySQLConnectionPool.java @@ -0,0 +1,90 @@ +package com.yahoo.ycsb.db; + +import com.github.jasync.sql.db.Configuration; +import com.github.jasync.sql.db.mysql.MySQLConnection; +import com.github.jasync.sql.db.mysql.pool.MySQLConnectionFactory; +import com.github.jasync.sql.db.pool.PoolAlreadyTerminatedException; +import com.github.jasync.sql.db.pool.PoolExhaustedException; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +/** + * + */ +public class MySQLConnectionPool { + private final MySQLConnectionFactory connectionFactory; + private ConcurrentLinkedQueue available = new ConcurrentLinkedQueue<>(); + private ConcurrentHashMap checkedOut = new ConcurrentHashMap<>(); + private AtomicLong connectionCount = new AtomicLong(0); + private boolean closed = false; + private ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(); + private final long maxConnections; + + public MySQLConnectionPool(Configuration config, long maxConnections) { + this.connectionFactory = new MySQLConnectionFactory(config); + this.maxConnections = maxConnections; + if (maxConnections <= 0) { + throw new IllegalArgumentException("maxConnections cannot be less than or equal to 0."); + } + } + + public MySQLConnection take() { + if (closed) { + throw new PoolAlreadyTerminatedException(); + } + MySQLConnection connection = available.poll(); + if (null == connection) { + long connIdx = connectionCount.getAndUpdate(count -> count == maxConnections ? count : count + 1); + if (connIdx == maxConnections) { + throw new PoolExhaustedException("No connections are available."); + } + connection = connectionFactory.create(); + } + checkedOut.put(connection, connection); + return connection; + } + + public void giveBack(MySQLConnection connection) { + if (!checkedOut.containsKey(connection)) { + throw new IllegalArgumentException("This connection is not part of the pool or was already returned."); + } + + if (connectionFactory.validate(connection).isSuccess()) { + checkedOut.remove(connection); + available.offer(connection); + } + } + + public long getTotalConnections() { + return connectionCount.get(); + } + + public int getNumTakenConnections() { + return checkedOut.size(); + } + + public int getNumAvailableConnections() { + return available.size(); + } + + private void closeAllConnections() { + available.forEach((MySQLConnection::disconnect)); + } + + public void close() { + closed = true; + + closeAllConnections(); + if (!checkedOut.isEmpty()) { + ses.scheduleAtFixedRate(() -> { + if (checkedOut.isEmpty()) { + closeAllConnections(); + ses.shutdown(); + } else { + System.out.println("Waiting 100ms for all connections to be released."); + } + }, 0, 100, TimeUnit.MILLISECONDS); + } + } +} diff --git a/mysql/src/main/java/com/yahoo/ycsb/db/package-info.java b/mysql/src/main/java/com/yahoo/ycsb/db/package-info.java new file mode 100644 index 00000000..993a8817 --- /dev/null +++ b/mysql/src/main/java/com/yahoo/ycsb/db/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 - 2016, Yahoo!, Inc. All rights reserved. + * + * 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. See accompanying + * LICENSE file. + */ + +/** + * The YCSB binding for stores that can be accessed via JDBC. + */ +package com.yahoo.ycsb.db; + diff --git a/pom.xml b/pom.xml index 35d0b0c5..ec3483d3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,224 +1,225 @@ 4.0.0 com.yahoo.ycsb root 0.15.0 pom YCSB Root This is the top level project that builds, packages the core and all the DB bindings for YCSB infrastructure. scm:git:git://github.com/brianfrankcooper/YCSB.git master https://github.com/brianfrankcooper/YCSB com.puppycrawl.tools checkstyle 7.7.1 org.jdom jdom 1.1 com.google.collections google-collections 1.0 org.slf4j slf4j-api 1.7.25 2.5.5 2.10 1.6.6 1.7.4 1.9.1 3.1.2 4.4.1 1.7.1 1.8.1 4.0.0 3.0.0 0.24.0-beta 1.4.10 2.3.1 5.5.1 5.2.5 1.2.0 1.3.0 0.98.14-hadoop2 1.0.2 1.2.5 1.4.2 2.0.0 0.9.5.6 2.6.0 7.2.2.Final 1.6.0 1.1.8-mapr-1710 3.6.3 2.0.1 2.1.1 2.2.10 UTF-8 2.9.0 2.0.5 5.11.3 1.10.20 5.5.3 6.4.1 1.6.5 0.8.0 0.81 core binding-parent distribution accumulo1.6 accumulo1.7 accumulo1.8 aerospike arangodb asynchbase azuredocumentdb azuretablestorage cassandra cloudspanner couchbase couchbase2 dynamodb elasticsearch elasticsearch5 foundationdb geode googlebigtable googledatastore hbase098 hbase10 hbase12 hbase14 hbase20 hypertable ignite infinispan jdbc kudu maprdb maprjsondb memcached mongodb + mysql nosqldb orientdb rados redis rest riak rocksdb s3 solr solr6 tarantool org.apache.maven.plugins maven-checkstyle-plugin 2.16 org.apache.maven.plugins maven-enforcer-plugin 3.0.0-M1 enforce-maven enforce 3.1.0 org.apache.maven.plugins maven-compiler-plugin 3.7.0 1.8 1.8 org.apache.maven.plugins maven-checkstyle-plugin validate validate check checkstyle.xml