From 17a1613ca4f4038a2c93533e219365f77d741336 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 24 Mar 2020 14:21:56 -0500 Subject: [PATCH] Update to unified Java driver 4.5.0 (#20) * Not sure how, but somehow I missed the commit that, you know, changed the driver version we use :facepalm: * Picking up some low-hanging fruit, mostly import changes * Some more low-hanging fruit, mainly conversions to use the shaded Guava * Fixing a package rename that was missed in the earlier round * Convert usages of Guava's FutureCallback (which aren't actually being used in future-handling code) to a generic callback trait * Trying to fix as much of the base CQL stuff as possible. Big chunks of this are based on/copied wholesale from previous work done by Arthur Landim (https://github.com/datastax/gatling-dse-plugin/pull/17) * Added explicit bounded Statement type to DseCqlAttributes. This type propogates through the statement/attribute level but doesn't make it up to the action level (since the actions don't seem to care about the underlying Statement type their executing... or at least they don't so far). * A few minor fixes * Cleaning up PagingState references, a few other minor cleanups * A few miscellaneous fixes * Removed unavailable methods for accessing tried and queried hosts and replaced with accessor for coordinator node. Also cleaned up a fair amount of DseResponse. * Most of the core graph refactoring * Fixes to DseResponseHandler... and a few other miscellaneous fixes * Updates to Cql/GraphRequestAction to match other work done elsewhere in these changes. Still need to update the bits around the explicit interaction with the driver. * A bit of refactring to remove some of the larger fn definitions in the action impls * Fixing some weird import ordering issues * Conversion to use a builder rather than a (template) statement in CqlRequestAction... should make supporting the setting of various options from DseCqlAttributes considerably easier. * Convert graph infrastructure to use builders coming off of DseSession rather than Statement directly * Convert CqlPreparedStatementUtil to work with Bindable instances rather than statements specifically. Facilitates conversion to builders generally. Also converted CQL statements to use builders in order to address immutable statement classes * Cleaning up some type stuff * Converted CQL props to use only what's actually supported on the driver now. Still need to do the same for graph. Some things have moved into driver configuration, most notably the retry policy. Also simplified the DseResponse API. Primary accessor here is for the (Graph|Async)ResultSet... these vals can then be converted to Seqs using ResultSetUtils and managed via Scala's collection API. Preserved as many of the existing checks as possible mainly for backward compatibility; going forward many users would migrate to a Seq-based impl. The DseResponse API changes were (largely) a result of the distinction between ResultSet and AsyncResultSet in the 4.x driver. * Converted graph props to include only what's supported on the driver now. * Converting from GraphResultSet to AsyncGraphResultSet * Simple impls of Iterators for async RS impls * Fixing up some type things * Initial work on spec fixes * Code review feedback * Consolidating check API into something which exposes the various ResultSet impls only. Previously defined checks can now be implemented as transforms off of the ResultSet instance via fns defined somewhere. * More change from code review * Upgrading to new cassandra-unit which includes 4.2.x driver support) * Fixing a few tests * Specs should at least compile now * Base compilation now succeeds... on to actually fixing tests * Fixes for various spec failures. Don't try to create mocks for Durations... it makes them ANGRY! * Fighting with mocks is awesome * All non-cassandra-unit specs should be passing now * Various test fixes. Includes a fix to get cassandra-util tests running again; new version of cassandra-util calls initSession() when trying to retrieve a session from a (possibly not yet initialized) embedded instance. As a result our old pattern of only creating the embedded instance if the session is null was bound to fail. Since we're creating the instance in an object anyway it seems safe to do without the guard. * CqlPreparedStatementUtil spec now passes! * Explicitly specify traversal source for graph queries. This doesn't actually matter in practice since cassandra-unit only runs these simulations against a base C* instance which won't understand graph queries anyway. Still, I'm including it here in case this situation should change. * At long last... we have a clean run of tests! * Removing sbt changes that were accidentally committed * cassandra-unit released an official 4.x release while work was ongoing * Shift to an API based on DataType rather than ints coming off of getProtocolCode() * Upgrade to unified driver * Fixing compile warnings re: unchecked types due to type erasure * Replace DseSession with CqlSession refs * Changing _all_ refs to DseSession to CqlSession instead * Code review feedback * Fixing default DC impl + cleaning up handling of memoized Sessions * Fixing exception messages * Don't bother going to the iterator... just use what Iterable gives us * Added option to set idempotecy based on an existing boolean val * Addressing PR feedback * More review feedback * Code review feedback * More code review feedback * Shifting away from companion objects and towards implicit vals * Adding docs about the new check API * Re-adding support for per-request proxy auth * Some more code review feedback * Removing support for explicit specification of collection classes in the Gatling session from prepared statement support. Olivier pointed out that the prepared statement itself can serve as a source of truth for type info here. * Driver version bump to 4.5.0 --- build.sbt | 25 +- .../datastax/gatling/plugin/DseProtocol.scala | 8 +- .../com/datastax/gatling/plugin/Predef.scala | 4 +- .../gatling/plugin/checks/CqlChecks.scala | 51 +- .../plugin/checks/DseCheckSupport.scala | 108 +++-- .../gatling/plugin/checks/GenericChecks.scala | 129 ----- .../gatling/plugin/checks/GraphChecks.scala | 55 +-- .../plugin/model/DseCqlAttributes.scala | 67 +-- .../model/DseCqlAttributesBuilder.scala | 140 +++--- .../model/DseCqlStatementBuilders.scala | 49 +- .../plugin/model/DseCqlStatements.scala | 161 ++++--- .../plugin/model/DseGraphAttributes.scala | 64 +-- .../model/DseGraphAttributesBuilder.scala | 141 ++---- .../model/DseGraphStatementBuilders.scala | 80 +-- .../plugin/model/DseGraphStatements.scala | 76 ++- .../plugin/request/CqlRequestAction.scala | 107 ++-- .../request/CqlRequestActionBuilder.scala | 9 +- .../plugin/request/DseRequestActor.scala | 12 +- .../plugin/request/GraphRequestAction.scala | 114 +++-- .../request/GraphRequestActionBuilder.scala | 8 +- .../gatling/plugin/response/DseResponse.scala | 148 +----- .../plugin/response/DseResponseHandler.scala | 113 +++-- .../utils/CqlPreparedStatementUtil.scala | 319 +++++++----- .../gatling/plugin/utils/FutureUtils.scala | 39 -- .../gatling/plugin/utils/ResultSetUtils.scala | 73 +++ .../gatling/plugin/DseCqlStatementSpec.scala | 99 ++-- .../plugin/DseGraphStatementSpec.scala | 16 +- .../plugin/base/BaseCassandraServerSpec.scala | 16 +- .../plugin/base/BaseCqlSimulation.scala | 2 +- .../plugin/base/BaseGraphSimulation.scala | 2 +- .../plugin/base/GatlingCqlSession.scala | 58 +++ .../plugin/base/GatlingDseSession.scala | 67 --- .../model/CqlStatementBuildersSpec.scala | 85 ++-- .../plugin/request/CqlRequestActionSpec.scala | 100 ++-- .../request/GraphRequestActionSpec.scala | 118 ++--- .../cql/BatchStatementSimulation.scala | 10 +- .../cql/BoundCqlTypesSimulation.scala | 55 ++- .../cql/NamedStatementSimulation.scala | 12 +- .../cql/PreparedStatementSimulation.scala | 34 +- .../cql/SimpleStatementSimulation.scala | 58 +-- .../cql/UdtStatementSimulation.scala | 51 +- .../graph/GraphStatementSimulation.scala | 40 +- .../utils/CqlPreparedStatementUtilSpec.scala | 455 +++++++++--------- 43 files changed, 1604 insertions(+), 1774 deletions(-) delete mode 100644 src/main/scala/com/datastax/gatling/plugin/checks/GenericChecks.scala delete mode 100644 src/main/scala/com/datastax/gatling/plugin/utils/FutureUtils.scala create mode 100644 src/main/scala/com/datastax/gatling/plugin/utils/ResultSetUtils.scala create mode 100644 src/test/scala/com/datastax/gatling/plugin/base/GatlingCqlSession.scala delete mode 100644 src/test/scala/com/datastax/gatling/plugin/base/GatlingDseSession.scala diff --git a/build.sbt b/build.sbt index cc5ed16..724b868 100644 --- a/build.sbt +++ b/build.sbt @@ -4,20 +4,21 @@ val gatlingVersion = "2.3.0" scalacOptions += "-target:jvm-1.8" -libraryDependencies += "com.datastax.dse" % "dse-java-driver-core" % "1.6.8" -libraryDependencies += "com.datastax.dse" % "dse-java-driver-graph" % "1.6.8" -libraryDependencies += "com.github.nscala-time" %% "nscala-time" % "2.18.0" -libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.1" -libraryDependencies += "org.hdrhistogram" % "HdrHistogram" % "2.1.10" +libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.5.0" +libraryDependencies += "com.github.nscala-time" %% "nscala-time" % "2.18.0" +libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.1" +libraryDependencies += "org.hdrhistogram" % "HdrHistogram" % "2.1.10" -libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion % Provided +libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion % Provided + +libraryDependencies += "org.fusesource" % "sigar" % "1.6.4" % Test +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test +libraryDependencies += "org.easymock" % "easymock" % "3.5" % Test +libraryDependencies += "org.cassandraunit" % "cassandra-unit" % "4.3.1.0" % Test +libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % Test +libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.5.11" % Test +libraryDependencies += "com.datastax.oss" % "java-driver-query-builder" % "4.4.0" % Test -libraryDependencies += "org.fusesource" % "sigar" % "1.6.4" % Test -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test -libraryDependencies += "org.easymock" % "easymock" % "3.5" % Test -libraryDependencies += "org.cassandraunit" % "cassandra-unit" % "3.3.0.2" % Test -libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % Test -libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.5.11" % Test resolvers += Resolver.mavenLocal resolvers += Resolver.mavenCentral diff --git a/src/main/scala/com/datastax/gatling/plugin/DseProtocol.scala b/src/main/scala/com/datastax/gatling/plugin/DseProtocol.scala index e1c8863..17c8f88 100644 --- a/src/main/scala/com/datastax/gatling/plugin/DseProtocol.scala +++ b/src/main/scala/com/datastax/gatling/plugin/DseProtocol.scala @@ -11,10 +11,10 @@ import java.util.concurrent.atomic.AtomicLong import akka.Done import akka.actor.ActorSystem -import com.datastax.driver.dse.DseSession import com.datastax.gatling.plugin.metrics.MetricsLogger import com.datastax.gatling.plugin.request.{CqlRequestActionBuilder, GraphRequestActionBuilder} import com.datastax.gatling.plugin.utils.GatlingTimingSource +import com.datastax.oss.driver.api.core.CqlSession import com.typesafe.scalalogging.StrictLogging import io.gatling.core.CoreComponents import io.gatling.core.config.GatlingConfiguration @@ -63,7 +63,7 @@ object DseProtocol extends StrictLogging { } } -case class DseProtocol(session: DseSession) extends Protocol +case class DseProtocol(session: CqlSession) extends Protocol object DseComponents { private val componentsCache = mutable.Map[ActorSystem, DseComponents]() @@ -126,10 +126,10 @@ case class DseComponents(dseProtocol: DseProtocol, object DseProtocolBuilder { - def session(session: DseSession) = DseProtocolBuilder(session) + def session(session: CqlSession) = DseProtocolBuilder(session) } -case class DseProtocolBuilder(session: DseSession) { +case class DseProtocolBuilder(session: CqlSession) { def build = DseProtocol(session) } diff --git a/src/main/scala/com/datastax/gatling/plugin/Predef.scala b/src/main/scala/com/datastax/gatling/plugin/Predef.scala index ac3f89f..eb1eeea 100644 --- a/src/main/scala/com/datastax/gatling/plugin/Predef.scala +++ b/src/main/scala/com/datastax/gatling/plugin/Predef.scala @@ -36,9 +36,9 @@ trait DsePredefBase extends DseCheckSupport { implicit def protocolBuilder2DseProtocol(builder: DseProtocolBuilder): DseProtocol = builder.build - implicit def cqlRequestAttributes2ActionBuilder(builder: DseCqlAttributesBuilder): ActionBuilder = builder.build() + implicit def cqlRequestAttributes2ActionBuilder(builder: DseCqlAttributesBuilder[_,_]): ActionBuilder = builder.build() - implicit def graphRequestAttributes2ActionBuilder(builder: DseGraphAttributesBuilder): ActionBuilder = builder.build() + implicit def graphRequestAttributes2ActionBuilder(builder: DseGraphAttributesBuilder[_, _]): ActionBuilder = builder.build() } /** diff --git a/src/main/scala/com/datastax/gatling/plugin/checks/CqlChecks.scala b/src/main/scala/com/datastax/gatling/plugin/checks/CqlChecks.scala index 567f533..2729904 100644 --- a/src/main/scala/com/datastax/gatling/plugin/checks/CqlChecks.scala +++ b/src/main/scala/com/datastax/gatling/plugin/checks/CqlChecks.scala @@ -6,16 +6,15 @@ package com.datastax.gatling.plugin.checks -import com.datastax.driver.core.{ResultSet, Row} +import com.datastax.oss.driver.api.core.cql.{AsyncResultSet, Statement, StatementBuilder} import com.datastax.gatling.plugin.response.CqlResponse import io.gatling.commons.validation.{SuccessWrapper, Validation} import io.gatling.core.check._ -import io.gatling.core.check.extractor.{CountArity, CriterionExtractor, Extractor, FindAllArity, FindArity, SingleArity, _} +import io.gatling.core.check.extractor.{Extractor, SingleArity} import io.gatling.core.session.{Expression, ExpressionSuccessWrapper, Session} import scala.collection.mutable - /** * This class serves as model for the CQL-specific checks. * @@ -51,50 +50,10 @@ private class CqlResponseExtractor[X](val name: String, } } -private abstract class ColumnValueExtractor[X] extends CriterionExtractor[CqlResponse, Any, X] { - val criterionName = "columnValue" -} - -private class SingleColumnValueExtractor(val criterion: String, val occurrence: Int) extends ColumnValueExtractor[Any] with FindArity { - def extract(response: CqlResponse): Validation[Option[Any]] = - response.getCqlResultColumnValues(criterion).lift(occurrence).success -} - -private class MultipleColumnValueExtractor(val criterion: String) extends ColumnValueExtractor[Seq[Any]] with FindAllArity { - def extract(response: CqlResponse): Validation[Option[Seq[Any]]] = - response.getCqlResultColumnValues(criterion).liftSeqOption.success -} - -private class CountColumnValueExtractor(val criterion: String) extends ColumnValueExtractor[Int] with CountArity { - def extract(response: CqlResponse): Validation[Option[Int]] = - response.getCqlResultColumnValues(criterion).liftSeqOption.map(_.size).success -} - object CqlChecks { - val resultSet = - new CqlResponseExtractor[ResultSet]( + val resultSet:CqlCheckBuilder[AsyncResultSet] = + new CqlResponseExtractor[AsyncResultSet]( "resultSet", - r => r.getCqlResultSet) - .toCheckBuilder - - val allRows = - new CqlResponseExtractor[Seq[Row]]( - "allRows", - r => r.getAllRows) + r => r.resultSet) .toCheckBuilder - - val oneRow = - new CqlResponseExtractor[Row]( - "oneRow", - r => r.getOneRow) - .toCheckBuilder - - def columnValue(columnName: Expression[String]) = { - val cqlResponseExtender: Extender[DseCqlCheck, CqlResponse] = wrapped => DseCqlCheck(wrapped) - new DefaultMultipleFindCheckBuilder[DseCqlCheck, CqlResponse, CqlResponse, Any](cqlResponseExtender, x => x.success) { - def findExtractor(occurrence: Int) = columnName.map(new SingleColumnValueExtractor(_, occurrence)) - def findAllExtractor = columnName.map(new MultipleColumnValueExtractor(_)) - def countExtractor = columnName.map(new CountColumnValueExtractor(_)) - } - } } diff --git a/src/main/scala/com/datastax/gatling/plugin/checks/DseCheckSupport.scala b/src/main/scala/com/datastax/gatling/plugin/checks/DseCheckSupport.scala index fe0e808..bef0fb6 100644 --- a/src/main/scala/com/datastax/gatling/plugin/checks/DseCheckSupport.scala +++ b/src/main/scala/com/datastax/gatling/plugin/checks/DseCheckSupport.scala @@ -6,49 +6,75 @@ package com.datastax.gatling.plugin.checks -import io.gatling.core.session.ExpressionSuccessWrapper +import com.datastax.dse.driver.api.core.graph.AsyncGraphResultSet +import com.datastax.oss.driver.api.core.cql.AsyncResultSet +import com.datastax.gatling.plugin.utils.ResultSetUtils +/** + * Make both CQL and Graph checks available to the DSL. + * + * Note that as of 1.3.5 (and the upgrade to the unified OSS driver it brings along) the API here has changed. + * The old check API exposed a rich set of checks for various operations including row counts and validating + * data in individual rows. Several (most?) of these checks were built on the idea that all rows were immediately + * available in memory. This design has changed in 1.3.5, so maintaining the old API would've proven quite difficult + * (if not impossible). Additionally, the rich API shields the user from the intricacies of the driver API at the + * cost of limited flexibility; implementing new functionality requires modifications to the plugin itself (or at + * least an awareness of it's innards). + * + * With 1.3.5 this relationship has been inverted. The check API has been reduced to a single check which makes the + * underlying [[AsyncResultSet]] or [[AsyncGraphResultSet]] available. Simulations can then use transform() to + * extract values and evaluate them as necessary. So, for instance, something like this: + * + * {{{ + * .check(columnValue("counter_type") is 2) + * }}} + * + * now becomes: + * + * {{{ + * .check(resultSet.transform(rs => rs.one().getLong("counter_type")) is 2L) + * }}} + * + * Note that these transforms are now managed as Scala code within the simulations so they can be abstracted and + * built into libraries which can be re-used across simulations. Also note that this abstraction can be implemented + * without modifying the plugin itself. + * + * A similar pattern applies to checks based on metadata. So this: + * + * {{{ + * .check(exhausted is true) + * }}} + * + * now becomes: + * + * {{{ + * .check(resultSet.transform(_.hasMorePages) is false) + * .check(resultSet.transform(_.remaining) is 0) + * }}} + * + * At this point we should also note that checks are now explicitly executed in the order in which hey are declared + * in the simulation. This matters because iterating through rows will impact methods such as remaining(). So, for + * example, if you want to validate that a single row was returned and it contained a specific value you should do + * something like: + * + * {{{ + * .check(resultSet.transform(_.remaining) is 1) + * .check(resultSet.transform(rs => rs.one().getLong("counter_type")) is 2L) + * }}} + * + * and not: + * + * {{{ + * .check(resultSet.transform(rs => rs.one().getLong("counter_type")) is 2L) + * .check(resultSet.transform(_.remaining) is 1) + * }}} + * + * Finally, in general the expectation is that you won't need to realize all rows in a result set, but if for some + * reason you find this necessary this functionality is supplied in [[ResultSetUtils]]. This class also serves as + * an example of the kind of abstraction over common extraction operations discussed above. + */ trait DseCheckSupport { - // start global checks - lazy val exhausted = GenericChecks.exhausted - lazy val applied = GenericChecks.applied - lazy val rowCount = GenericChecks.rowCount - - // execution info and subsets - lazy val executionInfo = GenericChecks.executionInfo - lazy val achievedCL = GenericChecks.achievedConsistencyLevel - lazy val pagingState = GenericChecks.pagingState - lazy val queriedHost = GenericChecks.queriedHost - lazy val schemaAgreement = GenericChecks.schemaInAgreement - lazy val successfulExecutionIndex = GenericChecks.successfulExecutionIndex - lazy val triedHosts = GenericChecks.triedHosts - lazy val warnings = GenericChecks.warnings - - // start CQL only checks lazy val resultSet = CqlChecks.resultSet - lazy val allRows = CqlChecks.allRows - lazy val oneRow = CqlChecks.oneRow - - // start Graph only checks - lazy val graphResultSet = GraphChecks.graphResultSet - lazy val allNodes = GraphChecks.allNodes - lazy val oneNode = GraphChecks.oneNode - - def edges(columnName: String) = GraphChecks.edges(columnName) - - def vertexes(columnName: String) = GraphChecks.vertexes(columnName) - - def paths(columnName: String) = GraphChecks.paths(columnName) - - def properties(columnName: String) = GraphChecks.paths(columnName) - - def vertexProperties(columnName: String) = GraphChecks.vertexProperties(columnName) - - /** - * Get a column by name returned by the CQL statement. - * Note that this statement implicitly fetches all rows from the result set! - */ - def columnValue(columnName: String) = CqlChecks.columnValue(columnName.expressionSuccess) + lazy val graphResultSet = GraphChecks.resultSet } - diff --git a/src/main/scala/com/datastax/gatling/plugin/checks/GenericChecks.scala b/src/main/scala/com/datastax/gatling/plugin/checks/GenericChecks.scala deleted file mode 100644 index 13ea365..0000000 --- a/src/main/scala/com/datastax/gatling/plugin/checks/GenericChecks.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2018 Datastax Inc. - * - * This software can be used solely with DataStax products. Please consult the file LICENSE.md. - */ - -package com.datastax.gatling.plugin.checks - -import com.datastax.driver.core._ -import com.datastax.gatling.plugin.response.DseResponse -import io.gatling.commons.validation.{SuccessWrapper, Validation} -import io.gatling.core.check.extractor.{Extractor, SingleArity} -import io.gatling.core.check._ -import io.gatling.core.session.{Expression, ExpressionSuccessWrapper, Session} - -import scala.collection.mutable - -/** - * This class allows to execute checks on either CQL or Graph responses. - * - * There is no class hierarchy between DseResponseCheck and DseCqlCheck or - * DseGraphCheck on purpose. Otherwise any method that accept DseResponseCheck - * would make it possible to execute CQL checks on Graph responses, or - * vice-versa. - */ -case class GenericCheck(wrapped: Check[DseResponse]) extends Check[DseResponse] { - override def check(response: DseResponse, session: Session)(implicit cache: mutable.Map[Any, Any]): Validation[CheckResult] = { - wrapped.check(response, session) - } -} - -class GenericCheckBuilder[X](extractor: Expression[Extractor[DseResponse, X]]) - extends FindCheckBuilder[GenericCheck, DseResponse, DseResponse, X] { - - private val dseResponseExtender: Extender[GenericCheck, DseResponse] = - wrapped => GenericCheck(wrapped) - - def find: ValidatorCheckBuilder[GenericCheck, DseResponse, DseResponse, X] = { - ValidatorCheckBuilder(dseResponseExtender, x => x.success, extractor) - } -} - -private class GenericResponseExtractor[X](val name: String, - val extractor: DseResponse => X) - extends Extractor[DseResponse, X] with SingleArity { - - override def apply(response: DseResponse): Validation[Option[X]] = { - Some(extractor.apply(response)).success - } - - def toCheckBuilder: GenericCheckBuilder[X] = { - new GenericCheckBuilder[X](this.expressionSuccess) - } -} - -object GenericChecks { - val executionInfo = - new GenericResponseExtractor[ExecutionInfo]( - "executionInfo", - r => r.executionInfo()) - .toCheckBuilder - - val queriedHost = - new GenericResponseExtractor[Host]( - "queriedHost", - r => r.queriedHost()) - .toCheckBuilder - - val achievedConsistencyLevel = - new GenericResponseExtractor[ConsistencyLevel]( - "achievedConsistencyLevel", - r => r.achievedConsistencyLevel()) - .toCheckBuilder - - val speculativeExecutionsExtractor = - new GenericResponseExtractor[Int]( - "speculativeExecutions", - r => r.speculativeExecutions()) - .toCheckBuilder - - val pagingState = - new GenericResponseExtractor[PagingState]( - "pagingState", - r => r.pagingState()) - .toCheckBuilder - - val triedHosts = - new GenericResponseExtractor[List[Host]]( - "triedHost", - r => r.triedHosts()) - .toCheckBuilder - - val warnings = - new GenericResponseExtractor[List[String]]( - "warnings", - r => r.warnings()) - .toCheckBuilder - - val successfulExecutionIndex = - new GenericResponseExtractor[Int]( - "successfulExecutionIndex", - r => r.successFullExecutionIndex()) - .toCheckBuilder - - val schemaInAgreement = - new GenericResponseExtractor[Boolean]( - "schemaInAgreement", - r => r.schemaInAgreement()) - .toCheckBuilder - - val rowCount = - new GenericResponseExtractor[Int]( - "rowCount", - r => r.rowCount()) - .toCheckBuilder - - val applied = - new GenericResponseExtractor[Boolean]( - "applied", - r => r.applied()) - .toCheckBuilder - - val exhausted = - new GenericResponseExtractor[Boolean]( - "exhausted", - r => r.exhausted()) - .toCheckBuilder -} - diff --git a/src/main/scala/com/datastax/gatling/plugin/checks/GraphChecks.scala b/src/main/scala/com/datastax/gatling/plugin/checks/GraphChecks.scala index d3ed9ac..a04396c 100644 --- a/src/main/scala/com/datastax/gatling/plugin/checks/GraphChecks.scala +++ b/src/main/scala/com/datastax/gatling/plugin/checks/GraphChecks.scala @@ -6,7 +6,7 @@ package com.datastax.gatling.plugin.checks -import com.datastax.driver.dse.graph._ +import com.datastax.dse.driver.api.core.graph._ import com.datastax.gatling.plugin.response.GraphResponse import io.gatling.commons.validation.{SuccessWrapper, Validation} import io.gatling.core.check.extractor.{Extractor, SingleArity} @@ -15,7 +15,6 @@ import io.gatling.core.session.{Expression, ExpressionSuccessWrapper, Session} import scala.collection.mutable - /** * This class serves as model for the Graph-specific checks. * @@ -52,51 +51,9 @@ private class GraphResponseExtractor[X](val name: String, } object GraphChecks { - val graphResultSet = - new GraphResponseExtractor[GraphResultSet]( - "graphResultSet", - r => r.getGraphResultSet) - .toCheckBuilder - - val allNodes = - new GraphResponseExtractor[Seq[GraphNode]]( - "allNodes", - r => r.getAllNodes) - .toCheckBuilder - - val oneNode = - new GraphResponseExtractor[GraphNode]( - "oneNode", - r => r.getOneNode) - .toCheckBuilder - - def edges(column: String) = - new GraphResponseExtractor[Seq[Edge]]( - "edges", - r => r.getEdges(column)) - .toCheckBuilder - - def vertexes(column: String) = - new GraphResponseExtractor[Seq[Vertex]]( - "vertices", - r => r.getVertexes(column)) - .toCheckBuilder - - def paths(column: String) = - new GraphResponseExtractor[Seq[Path]]( - "paths", - r => r.getPaths(column)) - .toCheckBuilder - - def properties(column: String) = - new GraphResponseExtractor[Seq[Property]]( - "properties", - r => r.getProperties(column)) - .toCheckBuilder - - def vertexProperties(column: String) = - new GraphResponseExtractor[Seq[Property]]( - "vertexProperties", - r => r.getVertexProperties(column)) - .toCheckBuilder + val resultSet:GraphCheckBuilder[AsyncGraphResultSet] = + new GraphResponseExtractor[AsyncGraphResultSet]( + "graphResultSet", + r => r.resultSet) + .toCheckBuilder } \ No newline at end of file diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributes.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributes.scala index 455b820..d523c75 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributes.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributes.scala @@ -7,11 +7,14 @@ package com.datastax.gatling.plugin.model import java.nio.ByteBuffer +import java.time.Duration -import com.datastax.driver.core.policies.RetryPolicy -import com.datastax.driver.core.{ConsistencyLevel, PagingState, Statement} -import com.datastax.gatling.plugin.response.{CqlResponse, DseResponse} -import io.gatling.core.check.Check +import com.datastax.gatling.plugin.checks.DseCqlCheck +import com.datastax.oss.driver.api.core.ConsistencyLevel +import com.datastax.oss.driver.api.core.cql.{Statement, StatementBuilder} +import com.datastax.oss.driver.api.core.metadata.Node +import com.datastax.oss.driver.api.core.metadata.token.Token +import com.datastax.oss.driver.api.core.time.TimestampGenerator /** * CQL Query Attributes to be applied to the current query @@ -22,33 +25,41 @@ import io.gatling.core.check.Check * @param statement CQL Statement to be sent to Cluster * @param cl Consistency Level to be used * @param cqlChecks Data-level checks to be run after response is returned - * @param genericChecks Low-level checks to be run after response is returned - * @param userOrRole User or role to be used when proxy auth is enabled - * @param readTimeout Read timeout to be used * @param idempotent Set request to be idempotent i.e. whether it can be applied multiple times - * @param defaultTimestamp Set default timestamp on request, overriding current system time + * @param node Set the node that should handle this query + * @param userOrRole Set the user/role for this query if proxy authentication is used + * @param customPayload Custom payload for this request * @param enableTrace Whether tracing should be enabled - * @param outGoingPayload Set ByteBuffer custom outgoing Payload - * @param serialCl Serial Consistency Level to be used - * @param fetchSize Set fetchSize of request i.e. paging size - * @param retryPolicy Retry Policy to be used + * @param pageSize Set pageSize (formerly known as fetchSize) * @param pagingState Set paging State wanted + * @param queryTimestamp Set a timestamp to use for this query. If equal to Some(Long.MIN_VALUE) a timestamp + * will be generated by the configured [[TimestampGenerator]] + * @param routingKey Sets the key for token-aware routing + * @param routingKeyspace Sets the keyspace for token-aware routing + * @param routingToken Sets the token to use for token-aware routing + * @param serialCl Serial Consistency Level to be used + * @param timeout How long to wait for this request to complete * @param cqlStatements String version of the CQL statement that is sent * */ -case class DseCqlAttributes(tag: String, - statement: DseStatement[Statement], - cl: Option[ConsistencyLevel] = None, - cqlChecks: List[Check[CqlResponse]] = List.empty, - genericChecks: List[Check[DseResponse]] = List.empty, - userOrRole: Option[String] = None, - readTimeout: Option[Int] = None, - idempotent: Option[Boolean] = None, - defaultTimestamp: Option[Long] = None, - enableTrace: Option[Boolean] = None, - outGoingPayload: Option[Map[String, ByteBuffer]] = None, - serialCl: Option[ConsistencyLevel] = None, - fetchSize: Option[Int] = None, - retryPolicy: Option[RetryPolicy] = None, - pagingState: Option[PagingState] = None, - cqlStatements: Seq[String] = Seq.empty) +case class DseCqlAttributes[T <: Statement[T], B <: StatementBuilder[B,T]] + (tag: String, + statement: DseCqlStatement[T, B], + cqlChecks: List[DseCqlCheck] = List.empty, + cqlStatements: Seq[String] = Seq.empty, + /* General attributes */ + cl: Option[ConsistencyLevel] = None, + idempotent: Option[Boolean] = None, + node: Option[Node] = None, + userOrRole: Option[String] = None, + /* CQL-specific attributes */ + customPayload: Option[Map[String, ByteBuffer]] = None, + enableTrace: Option[Boolean] = None, + pageSize: Option[Int] = None, + pagingState: Option[ByteBuffer] = None, + queryTimestamp: Option[Long] = None, + routingKey: Option[ByteBuffer] = None, + routingKeyspace: Option[String] = None, + routingToken: Option[Token] = None, + serialCl: Option[ConsistencyLevel] = None, + timeout: Option[Duration] = None) diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributesBuilder.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributesBuilder.scala index b8283ad..b9231a4 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributesBuilder.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlAttributesBuilder.scala @@ -6,11 +6,15 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.core.policies.RetryPolicy -import com.datastax.driver.core.{ConsistencyLevel, PagingState} -import com.datastax.gatling.plugin.checks.{DseCqlCheck, GenericCheck} +import java.nio.ByteBuffer +import java.time.Duration + +import com.datastax.oss.driver.api.core.ConsistencyLevel +import com.datastax.oss.driver.api.core.cql.{Statement, StatementBuilder} +import com.datastax.gatling.plugin.checks.DseCqlCheck import com.datastax.gatling.plugin.request.CqlRequestActionBuilder -import io.gatling.core.action.builder.ActionBuilder +import com.datastax.oss.driver.api.core.metadata.Node +import com.datastax.oss.driver.api.core.metadata.token.Token /** @@ -18,13 +22,13 @@ import io.gatling.core.action.builder.ActionBuilder * * @param attr Addition Attributes */ -case class DseCqlAttributesBuilder(attr: DseCqlAttributes) { +case class DseCqlAttributesBuilder[T <: Statement[T], B <: StatementBuilder[B,T]](attr: DseCqlAttributes[T, B]) { /** * Builds to final action to run * * @return */ - def build(): CqlRequestActionBuilder = new CqlRequestActionBuilder(attr) + def build(): CqlRequestActionBuilder[T, B] = new CqlRequestActionBuilder(attr) /** * Set Consistency Level @@ -32,122 +36,136 @@ case class DseCqlAttributesBuilder(attr: DseCqlAttributes) { * @param level ConsistencyLevel * @return */ - def withConsistencyLevel(level: ConsistencyLevel) = DseCqlAttributesBuilder(attr.copy(cl = Some(level))) + def withConsistencyLevel(level: ConsistencyLevel):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(cl = Some(level))) /** - * Execute a query as another user or another role, provided the current logged in user has PROXY.EXECUTE permission. - * - * This permission MUST be granted to the currently logged in user using the CQL statement: `GRANT PROXY.EXECUTE ON - * ROLE someRole TO alice`. The user MUST be logged in with - * [[com.datastax.driver.dse.auth.DsePlainTextAuthProvider]] or - * [[com.datastax.driver.dse.auth.DseGSSAPIAuthProvider]] + * Set custom payload * - * @param userOrRole String + * @param k the key for this custom payload + * @param v the value for this custom payload * @return */ - def withUserOrRole(userOrRole: String) = DseCqlAttributesBuilder(attr.copy(userOrRole = Some(userOrRole))) + def addCustomPayload(k:String, v:ByteBuffer):DseCqlAttributesBuilder[T, B] = { + val newVal = + attr.customPayload + .orElse(Some(Map[String, ByteBuffer]())) + .map(m => m + (k -> v)) + DseCqlAttributesBuilder(attr.copy(customPayload = newVal)) + } /** - * Override the current system time for write time of query + * Set query to be idempotent i.e. run only once * - * @param epochTsInMs timestamp to use * @return */ - def withDefaultTimestamp(epochTsInMs: Long) = DseCqlAttributesBuilder(attr.copy(defaultTimestamp = Some(epochTsInMs))) - + def withIdempotency():DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(idempotent = Some(true))) /** * Set query to be idempotent i.e. run only once * * @return */ - def withIdempotency() = DseCqlAttributesBuilder(attr.copy(idempotent = Some(true))) - + def withIdempotency(idempotency:Boolean):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(idempotent = Some(idempotency))) /** - * Set Read timeout of the query - * - * @param readTimeoutInMs time in milliseconds + * Set the node that should handle this query + * @param node Node * @return */ - def withReadTimeout(readTimeoutInMs: Int) = DseCqlAttributesBuilder(attr.copy(readTimeout = Some(readTimeoutInMs))) + def withNode(node: Node):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(node = Some(node))) + /** + * Set the user or role to use for proxy auth + * @param userOrRole String + * @return + */ + def executeAs(userOrRole: String):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(userOrRole = Some(userOrRole))) /** - * Set Serial Consistency + * Enable CQL Tracing on the query * - * @param level ConsistencyLevel * @return */ - def withSerialConsistencyLevel(level: ConsistencyLevel) = DseCqlAttributesBuilder(attr.copy(serialCl = Some(level))) - + def withTracingEnabled():DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(enableTrace = Some(true))) /** - * Define the [[com.datastax.driver.core.policies.RetryPolicy]] to be used for query + * Set the page size * - * @param retryPolicy DataStax drivers retry policy + * @param pageSize CQL page size * @return */ - def withRetryPolicy(retryPolicy: RetryPolicy) = DseCqlAttributesBuilder(attr.copy(retryPolicy = Some(retryPolicy))) + def withPageSize(pageSize: Int):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(pageSize = Some(pageSize))) /** - * Set fetchSize of query for paging + * Set the paging state * - * @param rowCnt number of rows to fetch at one time + * @param pagingState CQL Paging state * @return */ - def withFetchSize(rowCnt: Int) = DseCqlAttributesBuilder(attr.copy(fetchSize = Some(rowCnt))) - + def withPagingState(pagingState: ByteBuffer):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(pagingState = Some(pagingState))) /** - * Enable CQL Tracing on the query + * Set the query timestamp * + * @param queryTimestamp CQL query timestamp * @return */ - def withTracingEnabled() = DseCqlAttributesBuilder(attr.copy(enableTrace = Some(true))) - + def withQueryTimestamp(queryTimestamp: Long):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(queryTimestamp = Some(queryTimestamp))) /** - * Set the paging state + * Set the routing key * - * @param pagingState CQL Paging state + * @param routingKey the routing key to use * @return */ - def withPagingState(pagingState: PagingState) = DseCqlAttributesBuilder(attr.copy(pagingState = Some(pagingState))) - + def withRoutingKey(routingKey: ByteBuffer):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(routingKey = Some(routingKey))) /** - * For backwards compatibility + * Set the routing keyspace * - * @param level + * @param routingKeyspace the routing keyspace to set * @return */ - @deprecated("Replaced by withSerialConsistencyLevel") - def serialConsistencyLevel(level: ConsistencyLevel) = withSerialConsistencyLevel(level) - + def withRoutingKeyspace(routingKeyspace: String):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(routingKeyspace = Some(routingKeyspace))) /** - * Backwards compatibility to set consistencyLevel + * Set the routing token * - * @see [[DseCqlAttributesBuilder.withConsistencyLevel]] - * @param level Consistency Level to use + * @param routingToken the routing token to set * @return */ - @deprecated("Replaced by withConsistencyLevel") - def consistencyLevel(level: ConsistencyLevel) = withConsistencyLevel(level) - + def withRoutingToken(routingToken: Token):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(routingToken = Some(routingToken))) /** - * For Backwards compatibility + * Set Serial Consistency * - * @see [[DseCqlAttributesBuilder.executeAs]] - * @param userOrRole User or role to use + * @param level ConsistencyLevel * @return */ - @deprecated("Replaced by withUserOrRole") - def executeAs(userOrRole: String) = withUserOrRole(userOrRole: String) + def withSerialConsistencyLevel(level: ConsistencyLevel):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(serialCl = Some(level))) - def check(check: DseCqlCheck) = DseCqlAttributesBuilder(attr.copy(cqlChecks = check :: attr.cqlChecks)) + /** + * Set timeout + * + * @param timeout the timeout to set + * @return + */ + def withTimeout(timeout: Duration):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(timeout = Some(timeout))) - def check(check: GenericCheck) = DseCqlAttributesBuilder(attr.copy(genericChecks = check :: attr.genericChecks)) + def check(check: DseCqlCheck):DseCqlAttributesBuilder[T, B] = + DseCqlAttributesBuilder(attr.copy(cqlChecks = (attr.cqlChecks :+ check))) } diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatementBuilders.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatementBuilders.scala index 1812102..fa1aa1c 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatementBuilders.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatementBuilders.scala @@ -6,7 +6,7 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.core.{PreparedStatement, SimpleStatement} +import com.datastax.oss.driver.api.core.cql._ import com.datastax.gatling.plugin._ import com.datastax.gatling.plugin.utils.CqlPreparedStatementUtil import io.gatling.core.session.Expression @@ -22,6 +22,8 @@ import io.gatling.core.session.Expression */ case class DseCqlStatementBuilder(tag: String) { + implicit val defaultBuilderFn = (s:BoundStatement) => new BoundStatementBuilder(s) + /** * Execute a simple Statement built from a CQL string. * @@ -29,7 +31,7 @@ case class DseCqlStatementBuilder(tag: String) { * @return */ @deprecated("Replaced by executeStatement(String)") - def executeCql(query: String): DseCqlAttributesBuilder = + def executeCql(query: String): DseCqlAttributesBuilder[SimpleStatement, SimpleStatementBuilder] = executeStatement(query) /** @@ -38,8 +40,8 @@ case class DseCqlStatementBuilder(tag: String) { * @param query Simple string query * @return */ - def executeStatement(query: String): DseCqlAttributesBuilder = - executeStatement(new SimpleStatement(query)) + def executeStatement(query: String): DseCqlAttributesBuilder[SimpleStatement, SimpleStatementBuilder] = + executeStatement(new SimpleStatementBuilder(query).build) /** * Execute a Simple Statement @@ -47,12 +49,12 @@ case class DseCqlStatementBuilder(tag: String) { * @param statement SimpleStatement * @return */ - def executeStatement(statement: SimpleStatement): DseCqlAttributesBuilder = + def executeStatement(statement: SimpleStatement): DseCqlAttributesBuilder[SimpleStatement, SimpleStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, DseCqlSimpleStatement(statement), - cqlStatements = Seq(statement.getQueryString)) + cqlStatements = Seq(statement.getQuery)) ) /** @@ -78,7 +80,7 @@ case class DseCqlStatementBuilder(tag: String) { * @param preparedStatement CQL Prepared Statement w/ anon ?'s * @return */ - def executeStatement(preparedStatement: PreparedStatement) = + def executeStatement(preparedStatement: PreparedStatement): DsePreparedCqlStatementBuilder = DsePreparedCqlStatementBuilder(tag, preparedStatement) /** @@ -90,7 +92,7 @@ case class DseCqlStatementBuilder(tag: String) { * * @param preparedStatement CQL Prepared statement with named parameters */ - def executeNamed(preparedStatement: PreparedStatement): DseCqlAttributesBuilder = + def executeNamed(preparedStatement: PreparedStatement): DseCqlAttributesBuilder[BoundStatement, BoundStatementBuilder] = DsePreparedCqlStatementBuilder(tag, preparedStatement).withSessionParams() /** @@ -98,11 +100,12 @@ case class DseCqlStatementBuilder(tag: String) { * * @param preparedStatements Array of prepared statements */ - def executePreparedBatch(preparedStatements: Array[PreparedStatement]) = DseCqlAttributesBuilder( - DseCqlAttributes( - tag, - DseCqlBoundBatchStatement(CqlPreparedStatementUtil, preparedStatements), - cqlStatements = preparedStatements.map(_.getQueryString) + def executePreparedBatch(preparedStatements: Array[PreparedStatement]): DseCqlAttributesBuilder[BatchStatement, BatchStatementBuilder] = + DseCqlAttributesBuilder( + DseCqlAttributes( + tag, + DseCqlBoundBatchStatement(CqlPreparedStatementUtil, preparedStatements), + cqlStatements = preparedStatements.map(_.getQuery) ) ) @@ -113,14 +116,14 @@ case class DseCqlStatementBuilder(tag: String) { * @param payloadSessionKey Session key of the payload from session/feed * @return */ - def executeCustomPayload(statement: SimpleStatement, payloadSessionKey: String): DseCqlAttributesBuilder = + def executeCustomPayload(statement: SimpleStatement, payloadSessionKey: String): DseCqlAttributesBuilder[SimpleStatement, SimpleStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, DseCqlCustomPayloadStatement(statement, payloadSessionKey), - cqlStatements = Seq(statement.getQueryString))) + cqlStatements = Seq(statement.getQuery))) - def executePreparedFromSession(key: String): DseCqlAttributesBuilder = + def executePreparedFromSession(key: String): DseCqlAttributesBuilder[BoundStatement, BoundStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, @@ -135,17 +138,19 @@ case class DseCqlStatementBuilder(tag: String) { */ case class DsePreparedCqlStatementBuilder(tag: String, prepared: PreparedStatement) { + implicit val defaultBuilderFn = (s:BoundStatement) => new BoundStatementBuilder(s) + /** * Alias for the behavior of executeNamed function * * @return */ - def withSessionParams(): DseCqlAttributesBuilder = + def withSessionParams(): DseCqlAttributesBuilder[BoundStatement, BoundStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, DseCqlBoundStatementNamed(CqlPreparedStatementUtil, prepared), - cqlStatements = Seq(prepared.getQueryString))) + cqlStatements = Seq(prepared.getQuery))) /** * Bind Gatling Session Values to CQL Prepared Statement @@ -153,12 +158,12 @@ case class DsePreparedCqlStatementBuilder(tag: String, prepared: PreparedStateme * @param params Gatling Session variables * @return */ - def withParams(params: Expression[AnyRef]*): DseCqlAttributesBuilder = + def withParams(params: Expression[AnyRef]*): DseCqlAttributesBuilder[BoundStatement, BoundStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, DseCqlBoundStatementWithPassedParams(CqlPreparedStatementUtil, prepared, params: _*), - cqlStatements = Seq(prepared.getQueryString)) + cqlStatements = Seq(prepared.getQuery)) ) /** @@ -167,11 +172,11 @@ case class DsePreparedCqlStatementBuilder(tag: String, prepared: PreparedStateme * @param sessionKeys Gatling Session Keys * @return */ - def withParams(sessionKeys: List[String]) = + def withParams(sessionKeys: List[String]): DseCqlAttributesBuilder[BoundStatement, BoundStatementBuilder] = DseCqlAttributesBuilder( DseCqlAttributes( tag, DseCqlBoundStatementWithParamList(CqlPreparedStatementUtil, prepared, sessionKeys), - cqlStatements = Seq(prepared.getQueryString)) + cqlStatements = Seq(prepared.getQuery)) ) } diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatements.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatements.scala index 1561482..9fddc5f 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatements.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseCqlStatements.scala @@ -8,29 +8,28 @@ package com.datastax.gatling.plugin.model import java.nio.ByteBuffer -import com.datastax.driver.core._ +import com.datastax.oss.driver.api.core.cql._ import com.datastax.gatling.plugin.exceptions.DseCqlStatementException import com.datastax.gatling.plugin.utils.CqlPreparedStatementUtil +import com.datastax.oss.driver.api.core.`type`.DataType import io.gatling.commons.validation._ -import io.gatling.core.session.{Session, _} +import io.gatling.core.session._ import scala.collection.JavaConverters._ -import scala.collection.mutable.ArrayBuffer import scala.util.{Try, Failure => TryFailure, Success => TrySuccess} - -trait DseCqlStatement extends DseStatement[Statement] { - def buildFromSession(session: Session): Validation[Statement] -} +trait DseCqlStatement[T <: Statement[T], B <: StatementBuilder[B,T]] extends DseStatement[B] /** * Simple CQL Statement from the java driver * * @param statement the statement to execute */ -case class DseCqlSimpleStatement(statement: SimpleStatement) extends DseCqlStatement { - def buildFromSession(gatlingSession: Session): Validation[SimpleStatement] = { - statement.success +case class DseCqlSimpleStatement(statement: SimpleStatement) + extends DseCqlStatement[SimpleStatement, SimpleStatementBuilder] { + + def buildFromSession(gatlingSession: Session): Validation[SimpleStatementBuilder] = { + SimpleStatement.builder(statement).success } } @@ -39,30 +38,38 @@ case class DseCqlSimpleStatement(statement: SimpleStatement) extends DseCqlState * * @param preparedStatement the prepared statement on which to bind parameters */ -case class DseCqlBoundStatementNamed(cqlTypes: CqlPreparedStatementUtil, preparedStatement: PreparedStatement) - extends DseCqlStatement { +case class DseCqlBoundStatementNamed(cqlTypes: CqlPreparedStatementUtil, + preparedStatement: PreparedStatement) + (implicit builderFn: (BoundStatement) => BoundStatementBuilder) + extends DseCqlStatement[BoundStatement, BoundStatementBuilder] { - def buildFromSession(gatlingSession: Session): Validation[BoundStatement] = - bindParams( + def buildFromSession(gatlingSession: Session): Validation[BoundStatementBuilder] = { + val template:BoundStatement = bindParams( gatlingSession, preparedStatement.bind(), - cqlTypes.getParamsMap(preparedStatement)).success + cqlTypes.getParamsMap(preparedStatement)) + builderFn(template).success + } /** * Bind Gatling Session Params to CQL Statement by Name and Type * * @param gatlingSession Gatling Session - * @param boundStatement CQL Prepared Statement + * @param template CQL Prepared Statement * @param queryParams CQL Query Named Params and Types * @return */ - protected def bindParams(gatlingSession: Session, boundStatement: BoundStatement, - queryParams: Map[String, DataType.Name]): BoundStatement = { - queryParams.foreach { - case (gatlingSessionKey, valType) => - cqlTypes.bindParamByName(gatlingSession, boundStatement, valType, gatlingSessionKey) - } - boundStatement + protected def bindParams(gatlingSession: Session, template: BoundStatement, + queryParams: Map[String, DataType]): BoundStatement = { + val completedBuilder = + queryParams.foldLeft(builderFn(template)) { + (builder, kv) => + kv match { + case (gatlingSessionKey, valType) => + cqlTypes.bindParamByName(gatlingSession, builder, valType, gatlingSessionKey) + } + } + completedBuilder.build() } } @@ -74,9 +81,11 @@ case class DseCqlBoundStatementNamed(cqlTypes: CqlPreparedStatementUtil, prepare */ case class DseCqlBoundStatementWithPassedParams(cqlTypes: CqlPreparedStatementUtil, preparedStatement: PreparedStatement, - params: Expression[AnyRef]*) extends DseCqlStatement { + params: Expression[AnyRef]*) + (implicit builderFn: (BoundStatement) => BoundStatementBuilder) + extends DseCqlStatement[BoundStatement, BoundStatementBuilder] { - def buildFromSession(gatlingSession: Session): Validation[BoundStatement] = { + def buildFromSession(gatlingSession: Session): Validation[BoundStatementBuilder] = { val parsedParams: Seq[Validation[AnyRef]] = params.map(param => param(gatlingSession)) if (parsedParams.exists(_.isInstanceOf[Failure])) { val firstError = StringBuilder.newBuilder @@ -86,7 +95,8 @@ case class DseCqlBoundStatementWithPassedParams(cqlTypes: CqlPreparedStatementUt .onFailure(msg => firstError.append(msg)) firstError.toString().failure } else { - preparedStatement.bind(parsedParams.map(_.get): _*).success + val template:BoundStatement = preparedStatement.bind(parsedParams.map(_.get): _*) + builderFn(template).success } } } @@ -98,109 +108,103 @@ case class DseCqlBoundStatementWithPassedParams(cqlTypes: CqlPreparedStatementUt */ case class DseCqlBoundStatementWithParamList(cqlTypes: CqlPreparedStatementUtil, preparedStatement: PreparedStatement, - sessionKeys: Seq[String]) extends DseCqlStatement { + sessionKeys: Seq[String]) + (implicit builderFn: (BoundStatement) => BoundStatementBuilder) + extends DseCqlStatement[BoundStatement, BoundStatementBuilder] { /** * Apply the Gatling session params to the Prepared statement * - * @param gatlingSession DseSession + * @param gatlingSession current Gatling session * @return */ - def buildFromSession(gatlingSession: Session): Validation[BoundStatement] = { - bindParams(gatlingSession, preparedStatement.bind(), sessionKeys).success + def buildFromSession(gatlingSession: Session): Validation[BoundStatementBuilder] = { + val template:BoundStatement = bindParams(gatlingSession, preparedStatement.bind(), sessionKeys) + builderFn(template).success } /** * Bind the Gatling session params to the CQL Prepared Statement * * @param gatlingSession Gatling Session - * @param boundStatement CQL Prepared Statement + * @param template CQL Prepared Statement * @param sessionKeys List of session params to apply, put in order of query ?'s * @return */ - protected def bindParams(gatlingSession: Session, boundStatement: BoundStatement, + protected def bindParams(gatlingSession: Session, template: BoundStatement, sessionKeys: Seq[String]): BoundStatement = { - val params = cqlTypes.getParamsList(preparedStatement) - var cnt = 0 - - sessionKeys.foreach { gatlingSessionKey => - cqlTypes.bindParamByOrder(gatlingSession, boundStatement, params(cnt), gatlingSessionKey, cnt) - cnt += 1 - } - - boundStatement + val completedBuilder = + sessionKeys.zip(Iterable.range(0,sessionKeys.size)).foldLeft(builderFn(template)) { + (builder, kv) => + kv match { + case (sessionKey, cnt) => cqlTypes.bindParamByOrder(gatlingSession, builder, params(cnt), sessionKey, cnt) + } + } + completedBuilder.build() } - } - /** * Bound CQL Prepared Statement from Named Params * * @param statements CQL Prepared Statements */ -case class DseCqlBoundBatchStatement(cqlTypes: CqlPreparedStatementUtil, statements: Seq[PreparedStatement]) - extends DseCqlStatement { - - def buildFromSession(gatlingSession: Session): Validation[BatchStatement] = { - - val batch = new BatchStatement() - - statements.foreach(s => - batch.add(bindParams(gatlingSession, s, cqlTypes.getParamsMap(s)))) - - batch.success +case class DseCqlBoundBatchStatement(cqlTypes: CqlPreparedStatementUtil, + statements: Seq[PreparedStatement]) + (implicit builderFn: (BoundStatement) => BoundStatementBuilder) + extends DseCqlStatement[BatchStatement, BatchStatementBuilder] { + + def buildFromSession(gatlingSession: Session): Validation[BatchStatementBuilder] = { + val builder:BatchStatementBuilder = BatchStatement.builder(DefaultBatchType.LOGGED) + val batchables = statements.map(bindParams(gatlingSession)) + builder.addStatements(batchables:_*).success } - /** * Bind Gatling Session Params to CQL Statement by Name and Type * * @param gatlingSession Gatling Session * @param statement CQL Prepared Statement - * @param queryParams CQL Query Named Params and Types * @return */ - protected def bindParams(gatlingSession: Session, statement: PreparedStatement, - queryParams: Map[String, DataType.Name]): BoundStatement = { - - val boundStatement = statement.bind() - - if (queryParams.nonEmpty) { - queryParams.foreach { - case (gatlingSessionKey, valType) => - cqlTypes.bindParamByName(gatlingSession, boundStatement, valType, gatlingSessionKey) + def bindParams(gatlingSession: Session)(statement: PreparedStatement): BoundStatement = { + val queryParams: Map[String, DataType] = cqlTypes.getParamsMap(statement) + val initBuilder:BoundStatementBuilder = builderFn(statement.bind()) + val completedBuilder = + queryParams.foldLeft(initBuilder) { + (builder, kv) => + kv match { + case (gatlingSessionKey, valType) => + cqlTypes.bindParamByName(gatlingSession, builder, valType, gatlingSessionKey) + } } - } - boundStatement + completedBuilder.build() } } - /** * Set a custom payload on the statement * * @param statement SimpleStaten * @param payloadRef session variable for custom payload */ -case class DseCqlCustomPayloadStatement(statement: SimpleStatement, payloadRef: String) extends DseCqlStatement { - - def buildFromSession(gatlingSession: Session): Validation[Statement] = { +case class DseCqlCustomPayloadStatement(statement: SimpleStatement, payloadRef: String) + extends DseCqlStatement[SimpleStatement, SimpleStatementBuilder] { + def buildFromSession(gatlingSession: Session): Validation[SimpleStatementBuilder] = { if (!gatlingSession.contains(payloadRef)) { throw new DseCqlStatementException(s"Passed sessionKey: {$payloadRef} does not exist in Session.") } Try { val payload = gatlingSession(payloadRef).as[Map[String, ByteBuffer]].asJava - statement.setOutgoingPayload(payload) + statement.setCustomPayload(payload) } match { - case TrySuccess(stmt) => stmt.success + case TrySuccess(stmt) => SimpleStatement.builder(stmt).success case TryFailure(error) => error.getMessage.failure } } - } /** @@ -209,13 +213,16 @@ case class DseCqlCustomPayloadStatement(statement: SimpleStatement, payloadRef: * * @param sessionKey the session key which is associated to a PreparedStatement */ -case class DseCqlBoundStatementNamedFromSession(cqlTypes: CqlPreparedStatementUtil, sessionKey: String) extends DseCqlStatement { +case class DseCqlBoundStatementNamedFromSession(cqlTypes: CqlPreparedStatementUtil, + sessionKey: String) + (implicit builderFn: (BoundStatement) => BoundStatementBuilder) + extends DseCqlStatement[BoundStatement, BoundStatementBuilder] { - def buildFromSession(gatlingSession: Session): Validation[BoundStatement] = { + def buildFromSession(gatlingSession: Session): Validation[BoundStatementBuilder] = { if (!gatlingSession.contains(sessionKey)) { throw new DseCqlStatementException(s"Passed sessionKey: {$sessionKey} does not exist in Session.") } val preparedStatement = gatlingSession(sessionKey).as[PreparedStatement] - DseCqlBoundStatementNamed(cqlTypes, preparedStatement).buildFromSession(gatlingSession) + DseCqlBoundStatementNamed(cqlTypes, preparedStatement)(builderFn).buildFromSession(gatlingSession) } } diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributes.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributes.scala index ad1e45f..4afbe1d 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributes.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributes.scala @@ -6,9 +6,12 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.core.{ConsistencyLevel, Row} -import com.datastax.driver.dse.graph.{GraphNode, GraphStatement} -import com.datastax.gatling.plugin.checks.{DseGraphCheck, GenericCheck} +import java.time.Duration + +import com.datastax.oss.driver.api.core.ConsistencyLevel +import com.datastax.dse.driver.api.core.graph.{GraphStatement, GraphStatementBuilderBase} +import com.datastax.gatling.plugin.checks.DseGraphCheck +import com.datastax.oss.driver.api.core.metadata.Node /** * Graph Query Attributes to be applied to the current query @@ -19,34 +22,31 @@ import com.datastax.gatling.plugin.checks.{DseGraphCheck, GenericCheck} * @param statement Graph Statement to be sent to Cluster * @param cl Consistency Level to be used * @param graphChecks Data-level checks to be run after response is returned - * @param genericChecks Low-level checks to be run after response is returned - * @param userOrRole User or role to be used when proxy auth is enabled - * @param readTimeout Read timeout to be used * @param idempotent Set request to be idempotent i.e. whether it can be applied multiple times - * @param defaultTimestamp Set default timestamp on request, overriding current system time - * @param readCL Consistency level to use for the read part of the query - * @param writeCL Consistency level to use for the write part of the query - * @param graphName Name of the graph to use if different from the one used when connecting - * @param graphLanguage Language used in the query - * @param graphSource Graph source to use if different from the one used when connecting - * @param isSystemQuery Whether the query is a system one and should be used without any graph name - * @param graphInternalOptions Query-specific options not available in the driver public API - * @param graphTransformResults Function to use in order to transform a row into a Graph node + * @param node Set the node that should handle this query + * @param userOrRole Set the user/role for this query if proxy authentication is used + * @param graphName Name of the graph to use if different from the one used when connecting + * @param readCL Consistency level to use for the read part of the query + * @param subProtocol Name of the graph protocol to use for encoding/decoding + * @param timeout Timeout to use for this request + * @param timestamp Timestamp to use for this request + * @param traversalSource The traversal source for this request + * @param writeCL Consistency level to use for the write part of the query */ -case class DseGraphAttributes(tag: String, - statement: DseStatement[GraphStatement], - cl: Option[ConsistencyLevel] = None, - graphChecks: List[DseGraphCheck] = List.empty, - genericChecks: List[GenericCheck] = List.empty, - userOrRole: Option[String] = None, - readTimeout: Option[Int] = None, - idempotent: Option[Boolean] = None, - defaultTimestamp: Option[Long] = None, - readCL: Option[ConsistencyLevel] = None, - writeCL: Option[ConsistencyLevel] = None, - graphName: Option[String] = None, - graphLanguage: Option[String] = None, - graphSource: Option[String] = None, - isSystemQuery: Option[Boolean] = None, - graphInternalOptions: Option[Seq[(String, String)]] = None, - graphTransformResults: Option[com.google.common.base.Function[Row, GraphNode]] = None) +case class DseGraphAttributes[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]] + (tag: String, + statement: DseGraphStatement[T, B], + graphChecks: List[DseGraphCheck] = List.empty, + /* General attributes */ + cl: Option[ConsistencyLevel] = None, + idempotent: Option[Boolean] = None, + node: Option[Node] = None, + userOrRole: Option[String] = None, + /* Graph-specific attributes */ + graphName: Option[String] = None, + readCL: Option[ConsistencyLevel] = None, + subProtocol: Option[String] = None, + timeout: Option[Duration] = None, + timestamp: Option[Long] = None, + traversalSource: Option[String] = None, + writeCL: Option[ConsistencyLevel] = None) diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributesBuilder.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributesBuilder.scala index 427b7e9..e13f698 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributesBuilder.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphAttributesBuilder.scala @@ -6,25 +6,26 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.core.{ConsistencyLevel, Row} -import com.datastax.driver.dse.graph.GraphNode -import com.datastax.gatling.plugin.checks.{DseGraphCheck, GenericCheck} -import com.datastax.gatling.plugin.request.GraphRequestActionBuilder -import io.gatling.core.action.builder.ActionBuilder +import java.time.Duration +import com.datastax.oss.driver.api.core.ConsistencyLevel +import com.datastax.dse.driver.api.core.graph.{GraphStatement, GraphStatementBuilderBase} +import com.datastax.gatling.plugin.checks.DseGraphCheck +import com.datastax.gatling.plugin.request.GraphRequestActionBuilder +import com.datastax.oss.driver.api.core.metadata.Node /** * Request Builder for Graph Requests * * @param attr Addition Attributes */ -case class DseGraphAttributesBuilder(attr: DseGraphAttributes) { +case class DseGraphAttributesBuilder[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]](attr: DseGraphAttributes[T, B]) { /** * Builds to final action to run * * @return */ - def build(): ActionBuilder = new GraphRequestActionBuilder(attr) + def build(): GraphRequestActionBuilder[T, B] = new GraphRequestActionBuilder(attr) /** * Set Consistency Level @@ -32,54 +33,33 @@ case class DseGraphAttributesBuilder(attr: DseGraphAttributes) { * @param level ConsistencyLevel * @return */ - def withConsistencyLevel(level: ConsistencyLevel) = DseGraphAttributesBuilder(attr.copy(cl = Some(level))) - - /** - * Execute a query as another user or another role, provided the current logged in user has PROXY.EXECUTE permission. - * - * This permission MUST be granted to the currently logged in user using the CQL statement: `GRANT PROXY.EXECUTE ON - * ROLE someRole TO alice`. The user MUST be logged in with - * [[com.datastax.driver.dse.auth.DsePlainTextAuthProvider]] or - * [[com.datastax.driver.dse.auth.DseGSSAPIAuthProvider]] - * - * @param userOrRole String - * @return - */ - def withUserOrRole(userOrRole: String) = DseGraphAttributesBuilder(attr.copy(userOrRole = Some(userOrRole))) - - /** - * Override the current system time for write time of query - * - * @param epochTsInMs timestamp to use - * @return - */ - def withDefaultTimestamp(epochTsInMs: Long) = DseGraphAttributesBuilder(attr.copy(defaultTimestamp = Some(epochTsInMs))) - + def withConsistencyLevel(level: ConsistencyLevel):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(cl = Some(level))) /** * Set query to be idempotent i.e. run only once * * @return */ - def withIdempotency() = DseGraphAttributesBuilder(attr.copy(idempotent = Some(true))) - + def withIdempotency():DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(idempotent = Some(true))) /** - * Set Read timeout of the query - * - * @param readTimeoutInMs time in milliseconds + * Set the node that should handle this query + * @param node Node * @return */ - def withReadTimeout(readTimeoutInMs: Int) = DseGraphAttributesBuilder(attr.copy(readTimeout = Some(readTimeoutInMs))) - + def withNode(node: Node):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(node = Some(node))) /** - * Sets the graph language - * - * @param language graph language to use + * Set the user or role to use for proxy auth + * @param userOrRole String * @return */ - def withLanguage(language: String) = DseGraphAttributesBuilder(attr.copy(graphLanguage = Some(language))) + def executeAs(userOrRole: String):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(userOrRole = Some(userOrRole))) + /** * Sets the graph name to use @@ -87,59 +67,53 @@ case class DseGraphAttributesBuilder(attr: DseGraphAttributes) { * @param name Graph name * @return */ - def withName(name: String) = DseGraphAttributesBuilder(attr.copy(graphName = Some(name))) - + def withName(name: String):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(graphName = Some(name))) /** - * Set the source of the graph - * - * @param source graph source - * @return - */ - def withSource(source: String) = DseGraphAttributesBuilder(attr.copy(graphSource = Some(source))) - - /** - * Set the query to be system level + * Define [[ConsistencyLevel]] to be used for read queries * + * @param readCL Consistency Level to use * @return */ - def withSystemQuery() = DseGraphAttributesBuilder(attr.copy(isSystemQuery = Some(true))) + def withReadConsistency(readCL: ConsistencyLevel):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(readCL = Some(readCL))) /** - * Set Options on graph + * Set the sub-protocol * - * @param options options in key/value par to set against the query + * @param subProtocol the sub-protocol to use * @return */ - def withOptions(options: (String, String)*) = DseGraphAttributesBuilder(attr.copy(graphInternalOptions = Some(options))) - + def withSubProtocol(subProtocol: String):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(subProtocol = Some(subProtocol))) /** - * Set Option on graph + * Set the timeout * - * @param option options in key/value par to set against the query + * @param timeout the timeout to use * @return */ - def withOption(option: (String, String)) = withOptions(option) - + def withTimeout(timeout: Duration):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(timeout = Some(timeout))) /** - * Transform results function + * Set the timestamp * - * @param transform Transform Function + * @param timestamp the timestamp to use * @return */ - def withTransformResults(transform: com.google.common.base.Function[Row, GraphNode]) = { - DseGraphAttributesBuilder(attr.copy(graphTransformResults = Some(transform))) - } + def withTimestamp(timestamp: Long):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(timestamp = Some(timestamp))) /** - * Define [[ConsistencyLevel]] to be used for read queries + * Set the sub-protocol * - * @param readCL Consistency Level to use + * @param traversalSource the traversal source to use * @return */ - def withReadConsistency(readCL: ConsistencyLevel) = DseGraphAttributesBuilder(attr.copy(readCL = Some(readCL))) + def withTraversalSource(traversalSource: String):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(traversalSource = Some(traversalSource))) /** * Define [[ConsistencyLevel]] to be used for write queries @@ -147,30 +121,9 @@ case class DseGraphAttributesBuilder(attr: DseGraphAttributes) { * @param writeCL Consistency Level to use * @return */ - def withWriteConsistency(writeCL: ConsistencyLevel) = DseGraphAttributesBuilder(attr.copy(writeCL = Some(writeCL))) - - - /** - * Backwards compatibility to set consistencyLevel - * - * @see [[DseGraphAttributesBuilder.withConsistencyLevel]] - * @param level Consistency Level to use - * @return - */ - @deprecated("use withConsistencyLevel() instead, will be removed in future version") - def consistencyLevel(level: ConsistencyLevel) = withConsistencyLevel(level) - - - /** - * For Backwards compatibility - * - * @see [[DseGraphAttributesBuilder.executeAs]] - * @param userOrRole User or role to use - * @return - */ - @deprecated("use withUserOrRole() instead, will be removed in future version") - def executeAs(userOrRole: String) = withUserOrRole(userOrRole: String) + def withWriteConsistency(writeCL: ConsistencyLevel):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(writeCL = Some(writeCL))) - def check(check: DseGraphCheck) = DseGraphAttributesBuilder(attr.copy(graphChecks = check :: attr.graphChecks)) - def check(check: GenericCheck) = DseGraphAttributesBuilder(attr.copy(genericChecks = check :: attr.genericChecks)) + def check(check: DseGraphCheck):DseGraphAttributesBuilder[T, B] = + DseGraphAttributesBuilder(attr.copy(graphChecks = check :: attr.graphChecks)) } diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatementBuilders.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatementBuilders.scala index f559562..ce365f5 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatementBuilders.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatementBuilders.scala @@ -6,7 +6,7 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.dse.graph.{GraphStatement, SimpleGraphStatement} +import com.datastax.dse.driver.api.core.graph.{FluentGraphStatement, FluentGraphStatementBuilder, ScriptGraphStatement, ScriptGraphStatementBuilder} import io.gatling.core.session.{Expression, Session} /** @@ -22,8 +22,11 @@ case class DseGraphStatementBuilder(tag: String) { * @param strStatement Graph Query String * @return */ - def executeGraph(strStatement: Expression[String]) = { - DseGraphAttributesBuilder(DseGraphAttributes(tag, GraphStringStatement(strStatement))) + def executeGraph(strStatement: Expression[String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = { + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphStringStatement(strStatement))) } /** @@ -33,8 +36,8 @@ case class DseGraphStatementBuilder(tag: String) { * @param gStatement Simple Graph Statement * @return */ - @deprecated("Replaced by executeGraph(SimpleGraphStatement)") - def executeGraphStatement(gStatement: SimpleGraphStatement) = + @deprecated("Replaced by executeGraph(ScriptGraphStatement)") + def executeGraphStatement(gStatement: ScriptGraphStatement): DseGraphParametrizedStatementBuilder = executeGraph(gStatement) /** @@ -44,8 +47,8 @@ case class DseGraphStatementBuilder(tag: String) { * @param gStatement Simple Graph Statement * @return */ - def executeGraph(gStatement: SimpleGraphStatement) = { - DseGraphParametrizedStatementBuilder(tag, gStatement) + def executeGraph(gStatement: ScriptGraphStatement):DseGraphParametrizedStatementBuilder = { + DseGraphParametrizedStatementBuilder(tag, new ScriptGraphStatementBuilder(gStatement)) } /** @@ -54,8 +57,11 @@ case class DseGraphStatementBuilder(tag: String) { * @param gStatement Graph Statement from a Fluent API builder * @return */ - def executeGraphFluent(gStatement: GraphStatement) = { - DseGraphAttributesBuilder(DseGraphAttributes(tag, GraphFluentStatement(gStatement))) + def executeGraphFluent(gStatement: FluentGraphStatement): DseGraphAttributesBuilder[FluentGraphStatement, FluentGraphStatementBuilder] = { + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphFluentStatement(gStatement))) } /** @@ -71,8 +77,11 @@ case class DseGraphStatementBuilder(tag: String) { * @param gLambda The lambda * @return */ - def executeGraphFluent(gLambda: Session => GraphStatement) = { - DseGraphAttributesBuilder(DseGraphAttributes(tag, GraphFluentStatementFromScalaLambda(gLambda))) + def executeGraphFluent(gLambda: Session => FluentGraphStatement): DseGraphAttributesBuilder[FluentGraphStatement, FluentGraphStatementBuilder] = { + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphFluentStatementFromScalaLambda(gLambda))) } /** @@ -82,8 +91,11 @@ case class DseGraphStatementBuilder(tag: String) { * @return */ @deprecated("Replaced by executeGraphFluent{session => session(feederKey)}") - def executeGraphFeederTraversal(feederKey: String): DseGraphAttributesBuilder = { - DseGraphAttributesBuilder(DseGraphAttributes(tag, GraphFluentSessionKey(feederKey))) + def executeGraphFeederTraversal(feederKey: String): DseGraphAttributesBuilder[FluentGraphStatement, FluentGraphStatementBuilder] = { + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphFluentSessionKey(feederKey))) } } @@ -91,9 +103,9 @@ case class DseGraphStatementBuilder(tag: String) { * Builder for Graph queries that do not have bound parameters yet. * * @param tag Query tag - * @param gStatement Simple Graph Staetment + * @param builder Simple Graph Staetment */ -case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleGraphStatement) { +case class DseGraphParametrizedStatementBuilder(tag: String, builder: ScriptGraphStatementBuilder) { /** * Included for compatibility @@ -102,7 +114,8 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @return */ @deprecated("Replaced by withParams") - def withSetParams(paramNames: Array[String]): DseGraphAttributesBuilder = withParams(paramNames.toList) + def withSetParams(paramNames: Array[String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = + withParams(paramNames.toList) /** * Params to set from strings @@ -110,7 +123,7 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @param paramNames List of strings to use * @return */ - def withParams(paramNames: String*): DseGraphAttributesBuilder = + def withParams(paramNames: String*): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = withParams(paramNames.toList) /** @@ -119,8 +132,13 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @param paramNames List of strings to use * @return */ - def withParams(paramNames: List[String]): DseGraphAttributesBuilder = DseGraphAttributesBuilder( - DseGraphAttributes(tag, GraphBoundStatement(gStatement, paramNames.map(key => key -> key).toMap)) + def withParams(paramNames: List[String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphBoundStatement( + builder, + paramNames.map(key => key -> key).toMap)) ) /** @@ -130,7 +148,7 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @return */ @deprecated("Replaced with withParams") - def withSetParams(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder = + def withSetParams(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = withParams(paramNamesAndOverrides) @@ -142,7 +160,7 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @return */ @deprecated("Replaced with withParams") - def withParamOverrides(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder = + def withParamOverrides(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = withParams(paramNamesAndOverrides) /** @@ -152,8 +170,11 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @param paramNamesAndOverrides a Map of Session parameter names to their GraphStatement parameter names * @return */ - def withParams(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder = { - DseGraphAttributesBuilder(DseGraphAttributes(tag, GraphBoundStatement(gStatement, paramNamesAndOverrides))) + def withParams(paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = { + DseGraphAttributesBuilder( + DseGraphAttributes( + tag, + GraphBoundStatement(builder, paramNamesAndOverrides))) } /** @@ -165,9 +186,9 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @return */ @deprecated("Replaced by withRepeatedParams") - def withRepeatedSetParams(batchSize: Int, paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder = { + def withRepeatedSetParams(batchSize: Int, + paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = withRepeatedParams(batchSize, paramNamesAndOverrides) - } /** * Repeat the parameters given by suffixing their names and overridden names by a number picked from 1 to batchSize. @@ -177,7 +198,8 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG * @param paramNamesAndOverrides a Map of Session parameter names to their GraphStatement parameter names * @return */ - def withRepeatedParams(batchSize: Int, paramNamesAndOverrides: Map[String, String]): DseGraphAttributesBuilder = { + def withRepeatedParams(batchSize: Int, paramNamesAndOverrides: Map[String, String]): + DseGraphAttributesBuilder[ScriptGraphStatement, ScriptGraphStatementBuilder] = { def repeatParameters(params: Map[String, String]): Map[String, String] = batchSize match { // Gatling has a weird behavior when feeding multiple values // Feeding 1 value gives non-suffixed variables whereas feeding more gives suffixed variables starting by the @@ -190,7 +212,11 @@ case class DseGraphParametrizedStatementBuilder(tag: String, gStatement: SimpleG } DseGraphAttributesBuilder( - DseGraphAttributes(tag, GraphBoundStatement(gStatement, repeatParameters(paramNamesAndOverrides))) + DseGraphAttributes( + tag, + GraphBoundStatement( + builder, + repeatParameters(paramNamesAndOverrides))) ) } } diff --git a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatements.scala b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatements.scala index af2be96..d007af3 100644 --- a/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatements.scala +++ b/src/main/scala/com/datastax/gatling/plugin/model/DseGraphStatements.scala @@ -6,8 +6,7 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.dse.graph.{GraphStatement, SimpleGraphStatement} -import com.datastax.dse.graph.api.DseGraph +import com.datastax.dse.driver.api.core.graph._ import com.datastax.gatling.plugin.exceptions.DseGraphStatementException import io.gatling.commons.validation._ import io.gatling.core.session.{Expression, Session} @@ -15,19 +14,18 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal import scala.util.{Try, Failure => TryFailure, Success => TrySuccess} - -trait DseGraphStatement extends DseStatement[GraphStatement] { - def buildFromSession(session: Session): Validation[GraphStatement] -} +trait DseGraphStatement[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]] extends DseStatement[B] /** * Simple DSE Graph Statement from a String * * @param statement the Gremlin String to execute */ -case class GraphStringStatement(statement: Expression[String]) extends DseGraphStatement { - def buildFromSession(gatlingSession: Session): Validation[GraphStatement] = { - statement(gatlingSession).flatMap(stmt => new SimpleGraphStatement(stmt).success) +case class GraphStringStatement(statement: Expression[String]) + extends DseGraphStatement[ScriptGraphStatement, ScriptGraphStatementBuilder] { + + def buildFromSession(gatlingSession: Session): Validation[ScriptGraphStatementBuilder] = { + statement(gatlingSession).flatMap(stmt => ScriptGraphStatement.builder(stmt).success) } } @@ -36,9 +34,11 @@ case class GraphStringStatement(statement: Expression[String]) extends DseGraphS * * @param statement the Fluent Statement */ -case class GraphFluentStatement(statement: GraphStatement) extends DseGraphStatement { - def buildFromSession(gatlingSession: Session): Validation[GraphStatement] = { - statement.success +case class GraphFluentStatement(statement: FluentGraphStatement) + extends DseGraphStatement[FluentGraphStatement, FluentGraphStatementBuilder] { + + def buildFromSession(gatlingSession: Session): Validation[FluentGraphStatementBuilder] = { + FluentGraphStatement.builder(statement).success } } @@ -49,9 +49,11 @@ case class GraphFluentStatement(statement: GraphStatement) extends DseGraphState * @param lambda Scala lambda that takes a Gatling User Session (from which it can retrieve parameters) * and returns a fluent Graph Statement */ -case class GraphFluentStatementFromScalaLambda(lambda: Session => GraphStatement) extends DseGraphStatement { - def buildFromSession(gatlingSession: Session): Validation[GraphStatement] = { - lambda(gatlingSession).success +case class GraphFluentStatementFromScalaLambda(lambda: Session => FluentGraphStatement) + extends DseGraphStatement[FluentGraphStatement, FluentGraphStatementBuilder] { + + def buildFromSession(gatlingSession: Session): Validation[FluentGraphStatementBuilder] = { + FluentGraphStatement.builder(lambda(gatlingSession)).success } } @@ -61,31 +63,32 @@ case class GraphFluentStatementFromScalaLambda(lambda: Session => GraphStatement * * @param sessionKey Place a GraphTraversal in your session with this key name */ -case class GraphFluentSessionKey(sessionKey: String) extends DseGraphStatement { +case class GraphFluentSessionKey(sessionKey: String) + extends DseGraphStatement[FluentGraphStatement, FluentGraphStatementBuilder] { - def buildFromSession(gatlingSession: Session): Validation[GraphStatement] = { + def buildFromSession(gatlingSession: Session): Validation[FluentGraphStatementBuilder] = { if (!gatlingSession.contains(sessionKey)) { throw new DseGraphStatementException(s"Passed sessionKey: {$sessionKey} does not exist in Session.") } Try { - DseGraph.statementFromTraversal(gatlingSession(sessionKey).as[GraphTraversal[_, _]]) + FluentGraphStatement.builder(gatlingSession(sessionKey).as[GraphTraversal[_, _]]) } match { - case TrySuccess(stmt) => stmt.success + case TrySuccess(builder) => builder.success case TryFailure(error) => error.getMessage.failure } } - } /** * Set/Bind Gatling Session key/vals to GraphStatement * - * @param statement SimpleGraphStatement + * @param builder SimpleGraphStatementBuilder * @param sessionKeys Gatling session param keys mapped to their bind name, to allow name override */ -case class GraphBoundStatement(statement: SimpleGraphStatement, sessionKeys: Map[String, String]) extends DseGraphStatement { +case class GraphBoundStatement(builder: ScriptGraphStatementBuilder, sessionKeys: Map[String, String]) + extends DseGraphStatement[ScriptGraphStatement, ScriptGraphStatementBuilder] { /** * Apply the Gatling session params passed to the GraphStatement @@ -93,28 +96,19 @@ case class GraphBoundStatement(statement: SimpleGraphStatement, sessionKeys: Map * @param gatlingSession Gatling Session * @return */ - def buildFromSession(gatlingSession: Session): Validation[GraphStatement] = { + + def buildFromSession(gatlingSession: Session): Validation[ScriptGraphStatementBuilder] = { Try { - sessionKeys.foreach((tuple: (String, String)) => setParam(gatlingSession, tuple._1, tuple._2)).success - statement + sessionKeys foreach { k => + k match { + case (k, v) => builder.setQueryParam(v, gatlingSession(k).as[Object]) + case _ => throw new RuntimeException(s"Observed ${k} instead of expected key-value pair") + } + } + builder } match { - case TrySuccess(stmt) => stmt.success + case TrySuccess(builder) => builder.success case TryFailure(error) => error.getMessage.failure } } - - /** - * Set a parameter to the current gStatement. - * - * The value of this parameter is retrieved by looking up paramName from the gatlingSession. - * It is then bound to overriddenParamName in gStatement. - * - * @param gatlingSession Gatling session - * @param paramName Parameter name as accessible in Gatling session - * @param overriddenParamName Parameter name used to bind the statement - * @return gStatement - */ - private def setParam(gatlingSession: Session, paramName: String, overriddenParamName: String): GraphStatement = { - statement.set(overriddenParamName, gatlingSession(paramName).as[Object]) - } } \ No newline at end of file diff --git a/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestAction.scala b/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestAction.scala index bbf630a..fe98e93 100644 --- a/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestAction.scala +++ b/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestAction.scala @@ -12,18 +12,20 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.MICROSECONDS import akka.actor.ActorSystem +import com.datastax.dse.driver.api.core.auth.ProxyAuthentication import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.metrics.MetricsLogger import com.datastax.gatling.plugin.model.DseCqlAttributes import com.datastax.gatling.plugin.response.CqlResponseHandler import com.datastax.gatling.plugin.utils._ +import com.datastax.oss.driver.api.core.cql.{Statement, StatementBuilder} import io.gatling.commons.stats.KO import io.gatling.commons.validation.safely import io.gatling.core.action.{Action, ExitableAction} import io.gatling.core.session.Session import io.gatling.core.stats.StatsEngine -import scala.collection.JavaConverters._ +import scala.compat.java8.FutureConverters import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} /** @@ -34,22 +36,22 @@ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} * free the Gatling `injector` actor as fast as possible. * * The plugin router (and its actors) execute the driver code in order to locate the best replica that should receive - * each query, through `DseSession.executeAsync()` or `DseSession.executeGraphAsync()`. A driver I/O thread encodes + * each query, through `CqlSession.executeAsync()` or `CqlSession.executeGraphAsync()`. A driver I/O thread encodes * the request and sends it over the wire. * * Once the response is received, a driver I/O thread (Netty) decodes the response into a Java Object. It then - * completes the `Future` that was returned by `DseSession.executeAsync()`. + * completes the `Future` that was returned by `CqlSession.executeAsync()`. * * Completing that future results in immediately delegating the latency recording work to the plugin router. That * work includes recording it in HDR histograms through non-blocking data structures, and forwarding the result to * other Gatling data writers, like the console reporter. */ -class CqlRequestAction(val name: String, +class CqlRequestAction[T <: Statement[T], B <: StatementBuilder[B,T]](val name: String, val next: Action, val system: ActorSystem, val statsEngine: StatsEngine, val protocol: DseProtocol, - val dseAttributes: DseCqlAttributes, + val dseAttributes: DseCqlAttributes[T, B], val metricsLogger: MetricsLogger, val dseExecutorService: ExecutorService, val gatlingTimingSource: GatlingTimingSource) @@ -61,48 +63,57 @@ class CqlRequestAction(val name: String, }) } - def sendQuery(session: Session): Unit = { - val enableCO = Boolean.getBoolean("gatling.dse.plugin.measure_service_time") - val responseTimeBuilder: ResponseTimeBuilder = if (enableCO) { - // The throughput checker is useless in CO affected scenarios since throughput is not known in advance - COAffectedResponseTime.startingNow(gatlingTimingSource) - } else { - ThroughputVerifier.checkForGatlingOverloading(session, gatlingTimingSource) - GatlingResponseTime.startedByGatling(session, gatlingTimingSource) - } - val stmt = safely()(dseAttributes.statement.buildFromSession(session)) - - stmt.onFailure(err => { - handleFailure(session, responseTimeBuilder, err) - }) + private def buildStatement(builder:B):T = { - stmt.onSuccess({ stmt => - // global options - dseAttributes.cl.map(stmt.setConsistencyLevel) - dseAttributes.userOrRole.map(stmt.executingAs) - dseAttributes.readTimeout.map(stmt.setReadTimeoutMillis) - dseAttributes.idempotent.map(stmt.setIdempotent) - dseAttributes.defaultTimestamp.map(stmt.setDefaultTimestamp) + // global options + dseAttributes.cl.foreach(builder.setConsistencyLevel) + dseAttributes.idempotent.foreach(builder.setIdempotence(_)) + dseAttributes.node.foreach(builder.setNode) - // CQL Only Options - dseAttributes.outGoingPayload.map(x => stmt.setOutgoingPayload(x.asJava)) - dseAttributes.serialCl.map(stmt.setSerialConsistencyLevel) - dseAttributes.retryPolicy.map(stmt.setRetryPolicy) - dseAttributes.fetchSize.map(stmt.setFetchSize) - dseAttributes.pagingState.map(stmt.setPagingState) - if (dseAttributes.enableTrace.isDefined && dseAttributes.enableTrace.get) { - stmt.enableTracing + // CQL Only Options + dseAttributes.customPayload.foreach { + _.foreach { + _ match { + case (k, v) => builder.addCustomPayload(k, v) + } } + } + dseAttributes.enableTrace.foreach(builder.setTracing) + dseAttributes.pageSize.foreach(builder.setPageSize) + dseAttributes.pagingState.foreach(builder.setPagingState) + dseAttributes.queryTimestamp.foreach(builder.setQueryTimestamp) + dseAttributes.routingKey.foreach(builder.setRoutingKey) + dseAttributes.routingKeyspace.foreach(builder.setRoutingKeyspace) + dseAttributes.routingToken.foreach(builder.setRoutingToken) + dseAttributes.serialCl.foreach(builder.setSerialConsistencyLevel) + dseAttributes.timeout.foreach(builder.setTimeout) + builder.build + } - val responseHandler = new CqlResponseHandler(next, session, system, statsEngine, responseTimeBuilder, stmt, dseAttributes, metricsLogger) - implicit val sameThreadExecutionContext: ExecutionContextExecutor = ExecutionContext.fromExecutorService(dseExecutorService) - FutureUtils - .toScalaFuture(protocol.session.executeAsync(stmt)) - .onComplete(t => DseRequestActor.recordResult(RecordResult(t, responseHandler))) - }) + private def handleSuccess(session: Session, responseTimeBuilder: ResponseTimeBuilder)(builder:B): Unit = { + + val baseStmt:T = buildStatement(builder) + val stmt:T = + dseAttributes.userOrRole + .map(ProxyAuthentication.executeAs(_,baseStmt)) + .getOrElse(baseStmt) + val responseHandler = + new CqlResponseHandler[T, B]( + next, + session, + system, + statsEngine, + responseTimeBuilder, + stmt, + dseAttributes, + metricsLogger) + implicit val sameThreadExecutionContext: ExecutionContextExecutor = ExecutionContext.fromExecutorService(dseExecutorService) + FutureConverters + .toScala(protocol.session.executeAsync(stmt)) + .onComplete(t => DseRequestActor.recordResult(RecordResult(t, responseHandler))) } - private def handleFailure(session: Session, responseTimeBuilder: ResponseTimeBuilder, err: String) = { + private def handleFailure(session: Session, responseTimeBuilder: ResponseTimeBuilder)(err: String) = { val responseTime: ResponseTime = responseTimeBuilder.build() val logUuid = UUID.randomUUID.toString val tagString = if (session.groupHierarchy.nonEmpty) session.groupHierarchy.mkString("/") + "/" + dseAttributes.tag else dseAttributes.tag @@ -113,4 +124,18 @@ class CqlRequestAction(val name: String, logger.error("[{}] {} - Err: {} - Attrs: {}", logUuid, tagString, err, session.attributes.mkString(",")) next ! session.markAsFailed } + + def sendQuery(session: Session): Unit = { + val enableCO = Boolean.getBoolean("gatling.dse.plugin.measure_service_time") + val responseTimeBuilder: ResponseTimeBuilder = if (enableCO) { + // The throughput checker is useless in CO affected scenarios since throughput is not known in advance + COAffectedResponseTime.startingNow(gatlingTimingSource) + } else { + ThroughputVerifier.checkForGatlingOverloading(session, gatlingTimingSource) + GatlingResponseTime.startedByGatling(session, gatlingTimingSource) + } + val stmtBuilder = safely()(dseAttributes.statement.buildFromSession(session)) + stmtBuilder.onFailure(handleFailure(session,responseTimeBuilder)) + stmtBuilder.onSuccess(handleSuccess(session,responseTimeBuilder)) + } } diff --git a/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestActionBuilder.scala b/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestActionBuilder.scala index 455d1bd..2816be0 100644 --- a/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestActionBuilder.scala +++ b/src/main/scala/com/datastax/gatling/plugin/request/CqlRequestActionBuilder.scala @@ -8,17 +8,19 @@ package com.datastax.gatling.plugin.request import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.model.DseCqlAttributes +import com.datastax.oss.driver.api.core.cql.{Statement, StatementBuilder} import io.gatling.core.action.Action import io.gatling.core.action.builder.ActionBuilder import io.gatling.core.structure.ScenarioContext import io.gatling.core.util.NameGen -class CqlRequestActionBuilder(val dseAttributes: DseCqlAttributes) extends ActionBuilder with - NameGen { +class CqlRequestActionBuilder[T <: Statement[T], B <: StatementBuilder[B,T]](val dseAttributes: DseCqlAttributes[T, B]) + extends ActionBuilder + with NameGen { def build(ctx: ScenarioContext, next: Action): Action = { val dseComponents = ctx.protocolComponentsRegistry.components(DseProtocol.DseProtocolKey) - new CqlRequestAction( + new CqlRequestAction[T, B]( dseAttributes.tag, next, ctx.system, @@ -30,4 +32,3 @@ class CqlRequestActionBuilder(val dseAttributes: DseCqlAttributes) extends Actio dseComponents.gatlingTimingSource) } } - diff --git a/src/main/scala/com/datastax/gatling/plugin/request/DseRequestActor.scala b/src/main/scala/com/datastax/gatling/plugin/request/DseRequestActor.scala index c55c996..789d3bc 100644 --- a/src/main/scala/com/datastax/gatling/plugin/request/DseRequestActor.scala +++ b/src/main/scala/com/datastax/gatling/plugin/request/DseRequestActor.scala @@ -8,19 +8,19 @@ package com.datastax.gatling.plugin.request import akka.actor.Actor -import com.datastax.driver.core.ResultSet -import com.datastax.driver.dse.graph.GraphResultSet -import com.google.common.util.concurrent.FutureCallback +import com.datastax.dse.driver.api.core.graph.{GraphStatement, GraphStatementBuilderBase} +import com.datastax.gatling.plugin.response.DseResponseCallback +import com.datastax.oss.driver.api.core.cql.{Statement, StatementBuilder} import com.typesafe.scalalogging.StrictLogging import io.gatling.core.session.Session import scala.concurrent.ExecutionException import scala.util.{Failure, Success, Try} -case class SendCqlQuery(dseRequestAction: CqlRequestAction, session: Session) -case class SendGraphQuery(dseRequestAction: GraphRequestAction, session: Session) +case class SendCqlQuery[T <: Statement[T], B <: StatementBuilder[B,T]](dseRequestAction: CqlRequestAction[T,B], session: Session) +case class SendGraphQuery[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]](dseRequestAction: GraphRequestAction[T,B], session: Session) -case class RecordResult[T](t: Try[T], callback: FutureCallback[T]) +case class RecordResult[T](t: Try[T], callback: DseResponseCallback[T]) class DseRequestActor extends Actor with StrictLogging { override def receive: Actor.Receive = { diff --git a/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestAction.scala b/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestAction.scala index e4abe66..1bfca0e 100644 --- a/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestAction.scala +++ b/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestAction.scala @@ -12,6 +12,8 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.MICROSECONDS import akka.actor.ActorSystem +import com.datastax.dse.driver.api.core.auth.ProxyAuthentication +import com.datastax.dse.driver.api.core.graph.{GraphStatement, GraphStatementBuilderBase} import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.metrics.MetricsLogger import com.datastax.gatling.plugin.model.DseGraphAttributes @@ -22,6 +24,7 @@ import io.gatling.core.action.{Action, ExitableAction} import io.gatling.core.session.Session import io.gatling.core.stats.StatsEngine +import scala.compat.java8.FutureConverters import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} /** @@ -32,22 +35,22 @@ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} * free the Gatling `injector` actor as fast as possible. * * The plugin router (and its actors) execute the driver code in order to locate the best replica that should receive - * each query, through `DseSession.executeAsync()` or `DseSession.executeGraphAsync()`. A driver I/O thread encodes + * each query, through `CqlSession.executeAsync()` or `CqlSession.executeGraphAsync()`. A driver I/O thread encodes * the request and sends it over the wire. * * Once the response is received, a driver I/O thread (Netty) decodes the response into a Java Object. It then - * completes the `Future` that was returned by `DseSession.executeAsync()`. + * completes the `Future` that was returned by `CqlSession.executeAsync()`. * * Completing that future results in immediately delegating the latency recording work to the plugin router. That * work includes recording it in HDR histograms through non-blocking data structures, and forwarding the result to * other Gatling data writers, like the console reporter. */ -class GraphRequestAction(val name: String, +class GraphRequestAction[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]](val name: String, val next: Action, val system: ActorSystem, val statsEngine: StatsEngine, val protocol: DseProtocol, - val dseAttributes: DseGraphAttributes, + val dseAttributes: DseGraphAttributes[T, B], val metricsLogger: MetricsLogger, val dseExecutorService: ExecutorService, val gatlingTimingSource: GatlingTimingSource) @@ -59,6 +62,60 @@ class GraphRequestAction(val name: String, }) } + private def buildStatement(builder:B):T = { + + // global options + dseAttributes.cl.foreach(builder.setConsistencyLevel) + dseAttributes.idempotent.foreach(builder.setIdempotence(_)) + dseAttributes.node.foreach(builder.setNode) + + // Graph only Options + dseAttributes.graphName.foreach(builder.setGraphName) + dseAttributes.readCL.foreach(builder.setReadConsistencyLevel) + dseAttributes.subProtocol.foreach(builder.setSubProtocol) + dseAttributes.timeout.foreach(builder.setTimeout) + dseAttributes.timestamp.foreach(builder.setTimestamp) + dseAttributes.traversalSource.foreach(builder.setTraversalSource) + dseAttributes.writeCL.foreach(builder.setWriteConsistencyLevel) + + builder.build + } + + private def handleSuccess(session: Session, responseTimeBuilder: ResponseTimeBuilder)(builder:B): Unit = { + + val baseStmt:T = buildStatement(builder) + val stmt:T = + dseAttributes.userOrRole + .map(ProxyAuthentication.executeAs(_,baseStmt)) + .getOrElse(baseStmt) + val responseHandler = + new GraphResponseHandler[T, B]( + next, + session, + system, + statsEngine, + responseTimeBuilder, + stmt, + dseAttributes, + metricsLogger) + implicit val sameThreadExecutionContext: ExecutionContextExecutor = ExecutionContext.fromExecutorService(dseExecutorService) + FutureConverters + .toScala(protocol.session.executeAsync(stmt)) + .onComplete(t => DseRequestActor.recordResult(RecordResult(t, responseHandler))) + } + + private def handleFailure(session: Session, responseTimeBuilder: ResponseTimeBuilder)(err: String) = { + + val responseTime = responseTimeBuilder.build() + val logUuid = UUID.randomUUID.toString + val tagString = if (session.groupHierarchy.nonEmpty) session.groupHierarchy.mkString("/") + "/" + dseAttributes.tag else dseAttributes.tag + + statsEngine.logResponse(session, name, responseTime.toGatlingResponseTimings, KO, None, + Some(s"$tagString - Preparing: ${err.take(50)}"), List(responseTime.latencyIn(MICROSECONDS), "PRE", logUuid)) + + logger.error("[{}] {} - Preparing: {} - Attrs: {}", logUuid, tagString, err, session.attributes.mkString(",")) + next ! session.markAsFailed + } def sendQuery(session: Session): Unit = { val enableCO = Boolean.getBoolean("gatling.dse.plugin.measure_service_time") @@ -69,51 +126,8 @@ class GraphRequestAction(val name: String, ThroughputVerifier.checkForGatlingOverloading(session, gatlingTimingSource) GatlingResponseTime.startedByGatling(session, gatlingTimingSource) } - val stmt = dseAttributes.statement.buildFromSession(session) - - stmt.onFailure(err => { - val responseTime = responseTimeBuilder.build() - val logUuid = UUID.randomUUID.toString - val tagString = if (session.groupHierarchy.nonEmpty) session.groupHierarchy.mkString("/") + "/" + dseAttributes.tag else dseAttributes.tag - - statsEngine.logResponse(session, name, responseTime.toGatlingResponseTimings, KO, None, - Some(s"$tagString - Preparing: ${err.take(50)}"), List(responseTime.latencyIn(MICROSECONDS), "PRE", logUuid)) - - logger.error("[{}] {} - Preparing: {} - Attrs: {}", logUuid, tagString, err, session.attributes.mkString(",")) - next ! session.markAsFailed - }) - - stmt.onSuccess({ gStmt => - // global options - dseAttributes.cl.map(gStmt.setConsistencyLevel) - dseAttributes.defaultTimestamp.map(gStmt.setDefaultTimestamp) - dseAttributes.userOrRole.map(gStmt.executingAs) - dseAttributes.readTimeout.map(gStmt.setReadTimeoutMillis) - dseAttributes.idempotent.map(gStmt.setIdempotent) - - // Graph only Options - dseAttributes.readCL.map(gStmt.setGraphReadConsistencyLevel) - dseAttributes.writeCL.map(gStmt.setGraphWriteConsistencyLevel) - dseAttributes.graphLanguage.map(gStmt.setGraphLanguage) - dseAttributes.graphName.map(gStmt.setGraphName) - dseAttributes.graphSource.map(gStmt.setGraphSource) - dseAttributes.graphTransformResults.map(gStmt.setTransformResultFunction) - - if (dseAttributes.graphInternalOptions.isDefined) { - dseAttributes.graphInternalOptions.get.foreach { t => - gStmt.setGraphInternalOption(t._1, t._2) - } - } - - if (dseAttributes.isSystemQuery.isDefined && dseAttributes.isSystemQuery.get) { - gStmt.setSystemQuery() - } - - val responseHandler = new GraphResponseHandler(next, session, system, statsEngine, responseTimeBuilder, gStmt, dseAttributes, metricsLogger) - implicit val sameThreadExecutionContext: ExecutionContextExecutor = ExecutionContext.fromExecutorService(dseExecutorService) - FutureUtils - .toScalaFuture(protocol.session.executeGraphAsync(gStmt)) - .onComplete(t => DseRequestActor.recordResult(RecordResult(t, responseHandler))) - }) + val stmtBuilder = dseAttributes.statement.buildFromSession(session) + stmtBuilder.onFailure(handleFailure(session, responseTimeBuilder)) + stmtBuilder.onSuccess(handleSuccess(session, responseTimeBuilder)) } } diff --git a/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestActionBuilder.scala b/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestActionBuilder.scala index eb2d367..6f6e7f3 100644 --- a/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestActionBuilder.scala +++ b/src/main/scala/com/datastax/gatling/plugin/request/GraphRequestActionBuilder.scala @@ -6,6 +6,7 @@ package com.datastax.gatling.plugin.request +import com.datastax.dse.driver.api.core.graph.{GraphStatement, GraphStatementBuilderBase} import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.model.DseGraphAttributes import io.gatling.core.action.Action @@ -13,12 +14,13 @@ import io.gatling.core.action.builder.ActionBuilder import io.gatling.core.structure.ScenarioContext import io.gatling.core.util.NameGen -class GraphRequestActionBuilder(dseAttributes: DseGraphAttributes) extends ActionBuilder with - NameGen { +class GraphRequestActionBuilder[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]](dseAttributes: DseGraphAttributes[T, B]) + extends ActionBuilder + with NameGen { def build(ctx: ScenarioContext, next: Action): Action = { val dseComponents = ctx.protocolComponentsRegistry.components(DseProtocol.DseProtocolKey) - new GraphRequestAction( + new GraphRequestAction[T, B]( dseAttributes.tag, next, ctx.system, diff --git a/src/main/scala/com/datastax/gatling/plugin/response/DseResponse.scala b/src/main/scala/com/datastax/gatling/plugin/response/DseResponse.scala index db03c27..b3bcd21 100644 --- a/src/main/scala/com/datastax/gatling/plugin/response/DseResponse.scala +++ b/src/main/scala/com/datastax/gatling/plugin/response/DseResponse.scala @@ -6,147 +6,17 @@ package com.datastax.gatling.plugin.response -import com.datastax.driver.core._ -import com.datastax.driver.dse.graph._ -import com.datastax.gatling.plugin.model.{DseCqlAttributes, DseGraphAttributes} +import com.datastax.gatling.plugin.model.{ DseCqlAttributes, DseGraphAttributes } +import com.datastax.oss.driver.api.core.cql._ +import com.datastax.dse.driver.api.core.graph._ import com.typesafe.scalalogging.LazyLogging -import scala.collection.JavaConverters._ -import scala.util.Try - -abstract class DseResponse { - def executionInfo(): ExecutionInfo - def rowCount(): Int - def applied(): Boolean - def exhausted(): Boolean - def queriedHost(): Host = executionInfo().getQueriedHost - def achievedConsistencyLevel(): ConsistencyLevel = executionInfo().getAchievedConsistencyLevel - def speculativeExecutions(): Int = executionInfo().getSpeculativeExecutions - def pagingState(): PagingState = executionInfo().getPagingState - def triedHosts(): List[Host] = executionInfo().getTriedHosts.asScala.toList - def warnings(): List[String] = executionInfo().getWarnings.asScala.toList - def successFullExecutionIndex(): Int = executionInfo().getSuccessfulExecutionIndex - def schemaInAgreement(): Boolean = executionInfo().isSchemaInAgreement -} - - -class GraphResponse(graphResultSet: GraphResultSet, dseAttributes: DseGraphAttributes) extends DseResponse with LazyLogging { - private lazy val allGraphNodes: Seq[GraphNode] = collection.JavaConverters.asScalaBuffer(graphResultSet.all()) - - override def executionInfo(): ExecutionInfo = graphResultSet.getExecutionInfo() - override def applied(): Boolean = false // graph doesn't support LWTs so always return false - override def exhausted(): Boolean = graphResultSet.isExhausted() - - /** - * Get the number of all rows returned by the query. - * Note: Calling this function fetches all rows from the result set! - */ - override def rowCount(): Int = allGraphNodes.size - - def getGraphResultSet: GraphResultSet = { - graphResultSet - } - - def getAllNodes: Seq[GraphNode] = allGraphNodes - - def getOneNode: GraphNode = { - graphResultSet.one() - } - - - def getGraphResultColumnValues(column: String): Seq[Any] = { - if (allGraphNodes.isEmpty) return Seq.empty - - allGraphNodes.map(node => if (node.get(column) != null) node.get(column)) - } - - - def getEdges(name: String): Seq[Edge] = { - val columnValues = collection.mutable.Buffer[Edge]() - graphResultSet.forEach { n => - Try( - if (n.get(name).isEdge) { - columnValues.append(n.get(name).asEdge()) - } - ) - } - columnValues - } - - - def getVertexes(name: String): Seq[Vertex] = { - val columnValues = collection.mutable.Buffer[Vertex]() - graphResultSet.forEach { n => Try(if (n.get(name).isVertex) columnValues.append(n.get(name).asVertex())) } - columnValues - } - - - def getPaths(name: String): Seq[Path] = { - val columnValues = collection.mutable.Buffer[Path]() - graphResultSet.forEach { n => Try(columnValues.append(n.get(name).asPath())) } - columnValues - } - - - def getProperties(name: String): Seq[Property] = { - val columnValues = collection.mutable.Buffer[Property]() - graphResultSet.forEach { n => Try(columnValues.append(n.get(name).asProperty())) } - columnValues - } - - - def getVertexProperties(name: String): Seq[VertexProperty] = { - val columnValues = collection.mutable.Buffer[VertexProperty]() - graphResultSet.forEach { n => Try(columnValues.append(n.get(name).asVertexProperty())) } - columnValues - } - - def getDseAttributes: DseGraphAttributes = dseAttributes +class CqlResponse(cqlResultSet: AsyncResultSet, dseAttributes: DseCqlAttributes[_, _]) extends LazyLogging { + def attributes:DseCqlAttributes[_,_] = dseAttributes + def resultSet:AsyncResultSet = cqlResultSet } -class CqlResponse(cqlResultSet: ResultSet, dseAttributes: DseCqlAttributes) extends DseResponse with LazyLogging { - private lazy val allCqlRows: Seq[Row] = collection.JavaConverters.asScalaBuffer(cqlResultSet.all()) - - override def executionInfo(): ExecutionInfo = cqlResultSet.getExecutionInfo() - override def applied(): Boolean = cqlResultSet.wasApplied() - override def exhausted(): Boolean = cqlResultSet.isExhausted() - - /** - * Get the number of all rows returned by the query. - * Note: Calling this function fetches all rows from the result set! - */ - def rowCount: Int = allCqlRows.size - - /** - * Get CQL ResultSet - * - * @return - */ - def getCqlResultSet: ResultSet = { - cqlResultSet - } - - - /** - * Get all the rows in a Seq[Row] format - * - * @return - */ - def getAllRows: Seq[Row] = allCqlRows - - - def getOneRow: Row = { - cqlResultSet.one() - } - - - def getCqlResultColumnValues(column: String): Seq[Any] = { - if (allCqlRows.isEmpty || !allCqlRows.head.getColumnDefinitions.contains(column)) { - return Seq.empty - } - - allCqlRows.map(row => row.getObject(column)) - } - - def getDseAttributes: DseCqlAttributes = dseAttributes +class GraphResponse(graphResultSet: AsyncGraphResultSet, dseAttributes: DseGraphAttributes[_,_]) extends LazyLogging { + def attributes:DseGraphAttributes[_,_] = dseAttributes + def resultSet:AsyncGraphResultSet = graphResultSet } diff --git a/src/main/scala/com/datastax/gatling/plugin/response/DseResponseHandler.scala b/src/main/scala/com/datastax/gatling/plugin/response/DseResponseHandler.scala index ebd589e..4b0e145 100644 --- a/src/main/scala/com/datastax/gatling/plugin/response/DseResponseHandler.scala +++ b/src/main/scala/com/datastax/gatling/plugin/response/DseResponseHandler.scala @@ -10,12 +10,13 @@ import java.util.UUID import java.util.concurrent.TimeUnit.MICROSECONDS import akka.actor.ActorSystem -import com.datastax.driver.core._ -import com.datastax.driver.dse.graph.{GraphProtocol, GraphResultSet, GraphStatement} +import com.datastax.oss.driver.api.core.cql._ +import com.datastax.dse.driver.api.core.graph.{AsyncGraphResultSet, GraphStatement, GraphStatementBuilderBase} +import com.datastax.gatling.plugin.checks.{DseCqlCheck, DseGraphCheck} import com.datastax.gatling.plugin.metrics.MetricsLogger import com.datastax.gatling.plugin.model.{DseCqlAttributes, DseGraphAttributes} import com.datastax.gatling.plugin.utils.{ResponseTime, ResponseTimeBuilder} -import com.google.common.util.concurrent.FutureCallback +import com.datastax.oss.driver.api.core.metadata.Node import com.typesafe.scalalogging.StrictLogging import io.gatling.commons.stats._ import io.gatling.commons.validation.Failure @@ -25,7 +26,7 @@ import io.gatling.core.session.Session import io.gatling.core.stats.StatsEngine import io.gatling.core.stats.message.ResponseTimings -import scala.util.Try +import collection.JavaConverters._ object DseResponseHandler { def sanitize(s: String): String = s.replaceAll("""(\r|\n)""", " ") @@ -35,20 +36,25 @@ object DseResponseHandler { .mkString(",") } -abstract class DseResponseHandler[RS, Response <: DseResponse] extends StrictLogging with FutureCallback[RS] { +trait DseResponseCallback[RS] { + def onFailure(t: Throwable): Unit + + def onSuccess(result: RS): Unit +} + +abstract class DseResponseHandler[S, RS, R] extends StrictLogging with DseResponseCallback[RS] { protected def responseTimeBuilder: ResponseTimeBuilder protected def system: ActorSystem protected def statsEngine: StatsEngine protected def metricsLogger: MetricsLogger protected def next: Action protected def session: Session - protected def stmt: Any + protected def stmt: S protected def tag: String protected def queries: Seq[String] - protected def specificChecks: List[Check[Response]] - protected def genericChecks: List[Check[DseResponse]] - protected def newResponse(rs: RS): Response - protected def queriedHost(rs: RS): String + protected def specificChecks: List[Check[R]] + protected def newResponse(rs: RS): R + protected def coordinator(rs: RS): Node private def writeGatlingLog(status: Status, respTimings: ResponseTimings, message: Option[String], extraInfo: List[Any]): Unit = statsEngine.logResponse(session, tag, respTimings, status, None, message, extraInfo) @@ -70,8 +76,8 @@ abstract class DseResponseHandler[RS, Response <: DseResponse] extends StrictLog List(responseTime.latencyIn(MICROSECONDS), "CHK", logUuid) ) - logger.warn("[{}] {} - Check: {}, Query: {}, Host: {}", - logUuid, tagString, checkRes._2.get.message, DseResponseHandler.sanitizeAndJoin(queries), queriedHost(resultSet) + logger.warn("[{}] {} - Check: {}, Query: {}, Coordinator: {}", + logUuid, tagString, checkRes._2.get.message, DseResponseHandler.sanitizeAndJoin(queries), coordinator(resultSet).toString ) } @@ -88,13 +94,9 @@ abstract class DseResponseHandler[RS, Response <: DseResponse] extends StrictLog ) stmt match { - case Some(gs: GraphStatement) => - val unwrap = Try( - DseResponseHandler.sanitize(gs.unwrap(GraphProtocol.GRAPHSON_2_0).toString) - ).getOrElse(DseResponseHandler.sanitize(gs.unwrap(GraphProtocol.GRAPHSON_1_0).toString)) - + case Some(gs: GraphStatement[_]) => logger.warn("[{}] {} - Execute: {} - Attrs: {}", - logUuid, tagString, unwrap, session.attributes.mkString(","), t + logUuid, tagString, gs, session.attributes.mkString(","), t ) case _ => logger.warn("[{}] {} - Execute: {}, Query: {}", @@ -114,58 +116,65 @@ abstract class DseResponseHandler[RS, Response <: DseResponse] extends StrictLog val responseTime = responseTimeBuilder.build() val response = newResponse(result) - val genericResult: (Session => Session, Option[Failure]) = Check.check(response, session, genericChecks) - val genericChecksPassed = genericResult._2.isEmpty - val sessionAfterGenericChecks = genericResult._1(session) - if (genericChecksPassed) { - val specificResult: (Session => Session, Option[Failure]) = Check.check(response, sessionAfterGenericChecks, specificChecks) - val sessionAfterSpecificChecks = genericResult._1(session) - val specificChecksPassed = specificResult._2.isEmpty - if (specificChecksPassed) { - writeSuccess(responseTime) - next ! sessionAfterSpecificChecks.markAsSucceeded - } else { - writeCheckFailure(specificResult, result, responseTime) - next ! sessionAfterSpecificChecks.markAsFailed - } + val specificResult: (Session => Session, Option[Failure]) = Check.check(response, session, specificChecks) + val sessionAfterSpecificChecks = specificResult._1(session) + val specificChecksPassed = specificResult._2.isEmpty + if (specificChecksPassed) { + writeSuccess(responseTime) + next ! sessionAfterSpecificChecks.markAsSucceeded } else { - // Do not run specific checks as the response is already error'ed - writeCheckFailure(genericResult, result, responseTime) - next ! sessionAfterGenericChecks.markAsFailed + writeCheckFailure(specificResult, result, responseTime) + next ! sessionAfterSpecificChecks.markAsFailed } } } -class GraphResponseHandler(val next: Action, +class GraphResponseHandler[T <: GraphStatement[T], B <: GraphStatementBuilderBase[B,T]](val next: Action, val session: Session, val system: ActorSystem, val statsEngine: StatsEngine, val responseTimeBuilder: ResponseTimeBuilder, - val stmt: GraphStatement, - val dseAttributes: DseGraphAttributes, + val stmt: T, + val dseAttributes: DseGraphAttributes[T, B], val metricsLogger: MetricsLogger) - extends DseResponseHandler[GraphResultSet, GraphResponse] { + extends DseResponseHandler[T, AsyncGraphResultSet, GraphResponse] { override protected def tag: String = dseAttributes.tag override protected def queries: Seq[String] = Seq.empty - override protected def specificChecks: List[Check[GraphResponse]] = dseAttributes.graphChecks - override protected def genericChecks: List[Check[DseResponse]] = dseAttributes.genericChecks - override protected def newResponse(rs: GraphResultSet): GraphResponse = new GraphResponse(rs, dseAttributes) - override protected def queriedHost(rs: GraphResultSet): String = rs.getExecutionInfo.getQueriedHost.toString + override protected def specificChecks: List[DseGraphCheck] = dseAttributes.graphChecks + override protected def newResponse(rs: AsyncGraphResultSet): GraphResponse = new GraphResponse(rs, dseAttributes) + override protected def coordinator(rs: AsyncGraphResultSet): Node = rs.getExecutionInfo.getCoordinator } -class CqlResponseHandler(val next: Action, +class CqlResponseHandler[T <: Statement[T], B <: StatementBuilder[B,T]](val next: Action, val session: Session, val system: ActorSystem, val statsEngine: StatsEngine, val responseTimeBuilder: ResponseTimeBuilder, - val stmt: Statement, - val dseAttributes: DseCqlAttributes, + val stmt: T, + val dseAttributes: DseCqlAttributes[T, B], val metricsLogger: MetricsLogger) - extends DseResponseHandler[ResultSet, CqlResponse] { + extends DseResponseHandler[T, AsyncResultSet, CqlResponse] { override protected def tag: String = dseAttributes.tag - override protected def queries: Seq[String] = Seq.empty - override protected def specificChecks: List[Check[CqlResponse]] = dseAttributes.cqlChecks - override protected def genericChecks: List[Check[DseResponse]] = dseAttributes.genericChecks - override protected def newResponse(rs: ResultSet): CqlResponse = new CqlResponse(rs, dseAttributes) - override protected def queriedHost(rs: ResultSet): String = rs.getExecutionInfo.getQueriedHost.toString + override protected def queries: Seq[String] = getQueryStrings(stmt) + override protected def specificChecks: List[DseCqlCheck] = dseAttributes.cqlChecks + override protected def newResponse(rs: AsyncResultSet): CqlResponse = new CqlResponse(rs, dseAttributes) + override protected def coordinator(rs: AsyncResultSet): Node = rs.getExecutionInfo.getCoordinator + + def getQueryString(s:SimpleStatement):String = s.getQuery + + def getQueryString(s:BoundStatement):String = s.getPreparedStatement.getQuery + + def getQueryStrings(stmt:Statement[T]):Seq[String] = { + + stmt match { + case s:SimpleStatement => Seq(getQueryString(s)) + case s:BoundStatement => Seq(getQueryString(s)) + case s:BatchStatement => s.iterator.asScala.map((stmt) => { + stmt match { + case s:SimpleStatement => getQueryString(s) + case s:BoundStatement => getQueryString(s) + } + }).toSeq + } + } } diff --git a/src/main/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtil.scala b/src/main/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtil.scala index 9596de1..6000ccf 100644 --- a/src/main/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtil.scala +++ b/src/main/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtil.scala @@ -9,37 +9,40 @@ package com.datastax.gatling.plugin.utils import java.math.BigInteger import java.net.InetAddress import java.nio.ByteBuffer +import java.time.{Duration, Instant, LocalDate, LocalTime} import java.util -import java.util.Date -import java.util.concurrent.TimeUnit -import com.datastax.driver.core.DataType.Name._ -import com.datastax.driver.core._ -import com.datastax.driver.dse.geometry._ -import com.datastax.driver.dse.geometry.codecs.PointCodec +import com.datastax.dse.driver.api.core.data.geometry._ +import com.datastax.oss.driver.api.core.cql._ +import com.datastax.oss.driver.api.core.`type`._ +import com.datastax.oss.protocol.internal.ProtocolConstants.DataType._ import com.datastax.gatling.plugin.exceptions.CqlTypeException +import com.datastax.oss.driver.api.core.data.{TupleValue, UdtValue} import com.github.nscala_time.time.Imports.DateTime import io.gatling.core.session.Session import scala.collection.JavaConverters._ import scala.util.matching.Regex - trait CqlPreparedStatementUtil { protected val hourMinSecRegEx: Regex = """(\d+):(\d+):(\d+)""".r protected val hourMinSecNanoRegEx: Regex = """(\d+):(\d+):(\d+).(\d+{1,9})""".r - def bindParamByOrder(gatlingSession: Session, - boundStatement: BoundStatement, paramType: DataType.Name, - paramName: String, key: Int): BoundStatement + def bindParamByOrder[T <: Bindable[T]](gatlingSession: Session, + bindable: T, + paramType: DataType, + paramName: String, + key: Int): T - def bindParamByName(gatlingSession: Session, boundStatement: BoundStatement, paramType: DataType.Name, - paramName: String): BoundStatement + def bindParamByName[T <: Bindable[T]](gatlingSession: Session, + bindable: T, + paramType: DataType, + paramName: String): T - def getParamsMap(preparedStatement: PreparedStatement): Map[String, DataType.Name] + def getParamsMap(preparedStatement: PreparedStatement): Map[String, DataType] - def getParamsList(preparedStatement: PreparedStatement): List[DataType.Name] + def getParamsList(preparedStatement: PreparedStatement): List[DataType] } /** @@ -47,89 +50,111 @@ trait CqlPreparedStatementUtil { */ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { - /** * Bind CQL Prepared statement params by key order * * @param gatlingSession Gatling Session - * @param boundStatement CQL BoundStatement + * @param bindable CQL Bindable impl * @param paramType Type of param ie String, int, boolean * @param paramName Gatling Session Attribute Name * @param key Key/Order of param */ - def bindParamByOrder(gatlingSession: Session, boundStatement: BoundStatement, paramType: DataType.Name, - paramName: String, key: Int): BoundStatement = { + def bindParamByOrder[T <: Bindable[T]](gatlingSession: Session, bindable: T, paramType: DataType, + paramName: String, key: Int): T = { if (!gatlingSession.attributes.contains(paramName)) { - if (boundStatement.isSet(paramName)) { - boundStatement.unset(paramName) + return if (bindable.isSet(paramName)) { + bindable.unset(paramName) + } else { + bindable } - return boundStatement } gatlingSession.attributes.get(paramName) match { case Some(null) => - boundStatement.setToNull(paramName) - boundStatement + bindable.setToNull(paramName) case Some(None) => - if (boundStatement.isSet(paramName)) { - boundStatement.unset(paramName) + if (bindable.isSet(paramName)) { + bindable.unset(paramName) + } else { + bindable } - boundStatement case _ => - paramType match { - case (VARCHAR | TEXT | ASCII) => - boundStatement.setString(key, asString(gatlingSession, paramName)) + paramType.getProtocolCode match { + case (VARCHAR | ASCII) => + bindable.setString(key, asString(gatlingSession, paramName)) case INT => - boundStatement.setInt(key, asInteger(gatlingSession, paramName)) + bindable.setInt(key, asInteger(gatlingSession, paramName)) case BOOLEAN => - boundStatement.setBool(key, asBoolean(gatlingSession, paramName)) + bindable.setBoolean(key, asBoolean(gatlingSession, paramName)) case (UUID | TIMEUUID) => - boundStatement.setUUID(key, asUuid(gatlingSession, paramName)) + bindable.setUuid(key, asUuid(gatlingSession, paramName)) case FLOAT => - boundStatement.setFloat(key, asFloat(gatlingSession, paramName)) + bindable.setFloat(key, asFloat(gatlingSession, paramName)) case DOUBLE => - boundStatement.setDouble(key, asDouble(gatlingSession, paramName)) + bindable.setDouble(key, asDouble(gatlingSession, paramName)) case DECIMAL => - boundStatement.setDecimal(key, asDecimal(gatlingSession, paramName)) + bindable.setBigDecimal(key, asDecimal(gatlingSession, paramName)) case INET => - boundStatement.setInet(key, asInet(gatlingSession, paramName)) + bindable.setInetAddress(key, asInet(gatlingSession, paramName)) case TIMESTAMP => - boundStatement.setTimestamp(key, asTimestamp(gatlingSession, paramName)) + bindable.setInstant(key, asInstant(gatlingSession, paramName)) case COUNTER => - boundStatement.setLong(key, asCounter(gatlingSession, paramName)) + bindable.setLong(key, asCounter(gatlingSession, paramName)) case BIGINT => - boundStatement.setLong(key, asBigInt(gatlingSession, paramName)) + bindable.setLong(key, asBigInt(gatlingSession, paramName)) case BLOB => - boundStatement.setBytes(key, asByte(gatlingSession, paramName)) + bindable.setByteBuffer(key, asByte(gatlingSession, paramName)) case VARINT => - boundStatement.setVarint(key, asVarInt(gatlingSession, paramName)) + bindable.setBigInteger(key, asVarInt(gatlingSession, paramName)) case LIST => - boundStatement.setList(key, asList(gatlingSession, paramName)) + val dataType = bindable.getType(key) + dataType match { + case l: ListType => { + val memberClz = clzFromCodec(bindable, l.getElementType) + bindable.setList(key, asList(gatlingSession, paramName, memberClz), memberClz) + } + case _ => throw new IllegalStateException("Observed something other than ListType for a LIST param") + } case SET => - boundStatement.setSet(key, asSet(gatlingSession, paramName)) + val dataType = bindable.getType(key) + dataType match { + case s: SetType => { + val memberClz = clzFromCodec(bindable, s.getElementType) + bindable.setSet(key, asSet(gatlingSession, paramName, memberClz), memberClz) + } + case _ => throw new IllegalStateException("Observed something other than SetType for a SET param") + } case MAP => - boundStatement.setMap(key, asMap(gatlingSession, paramName)) + val dataType = bindable.getType(key) + dataType match { + case m: MapType => { + val keyClz = clzFromCodec(bindable, m.getKeyType) + val valClz = clzFromCodec(bindable, m.getValueType) + bindable.setMap(key, asMap(gatlingSession, paramName, keyClz, valClz), keyClz, valClz) + } + case _ => throw new IllegalStateException("Observed something other than MapType for a MAP param") + } case UDT => - boundStatement.setUDTValue(key, asUdt(gatlingSession, paramName)) + bindable.setUdtValue(key, asUdt(gatlingSession, paramName)) case TUPLE => - boundStatement.setTupleValue(key, asTuple(gatlingSession, paramName)) + bindable.setTupleValue(key, asTuple(gatlingSession, paramName)) case DATE => - boundStatement.setDate(key, asDate(gatlingSession, paramName)) + bindable.setLocalDate(key, asLocalDate(gatlingSession, paramName)) case SMALLINT => - boundStatement.setShort(key, asSmallInt(gatlingSession, paramName)) + bindable.setShort(key, asSmallInt(gatlingSession, paramName)) case TINYINT => - boundStatement.setByte(key, asTinyInt(gatlingSession, paramName)) + bindable.setByte(key, asTinyInt(gatlingSession, paramName)) case TIME => - boundStatement.setTime(key, asTime(gatlingSession, paramName)) + bindable.setLocalTime(key, asTime(gatlingSession, paramName)) case CUSTOM => gatlingSession.attributes.get(paramName) match { case Some(p: Point) => - boundStatement.set(key, asPoint(gatlingSession, paramName), classOf[Point]) + bindable.set(key, asPoint(gatlingSession, paramName), classOf[Point]) case Some(p: LineString) => - boundStatement.set(key, asLineString(gatlingSession, paramName), classOf[LineString]) + bindable.set(key, asLineString(gatlingSession, paramName), classOf[LineString]) case Some(p: Polygon) => - boundStatement.set(key, asPolygon(gatlingSession, paramName), classOf[Polygon]) + bindable.set(key, asPolygon(gatlingSession, paramName), classOf[Polygon]) case _ => throw new UnsupportedOperationException(s"$paramName on unknown CUSTOM type") } @@ -143,83 +168,106 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * Bind CQL Prepared statement params by anem * * @param gatlingSession Gatling Session - * @param boundStatement CQL BoundStatement + * @param bindable CQL Bindable impl * @param paramType Type of param ie String, int, boolean * @param paramName Gatling Session Attribute Value */ - def bindParamByName(gatlingSession: Session, boundStatement: BoundStatement, paramType: DataType.Name, - paramName: String): BoundStatement = { + def bindParamByName[T <: Bindable[T]](gatlingSession: Session, bindable: T, paramType: DataType, + paramName: String): T = { if (!gatlingSession.attributes.contains(paramName)) { - if (boundStatement.isSet(paramName)) { - boundStatement.unset(paramName) + return if (bindable.isSet(paramName)) { + bindable.unset(paramName) + } else { + bindable } - return boundStatement } gatlingSession.attributes.get(paramName) match { case Some(null) => - boundStatement.setToNull(paramName) - boundStatement + bindable.setToNull(paramName) case Some(None) => - if (boundStatement.isSet(paramName)) { - boundStatement.unset(paramName) + if (bindable.isSet(paramName)) { + bindable.unset(paramName) + } else { + bindable } - boundStatement case _ => - paramType match { - case (VARCHAR | TEXT | ASCII) => - boundStatement.setString(paramName, asString(gatlingSession, paramName)) + paramType.getProtocolCode match { + case (VARCHAR | ASCII) => + bindable.setString(paramName, asString(gatlingSession, paramName)) case INT => - boundStatement.setInt(paramName, asInteger(gatlingSession, paramName)) + bindable.setInt(paramName, asInteger(gatlingSession, paramName)) case BOOLEAN => - boundStatement.setBool(paramName, asBoolean(gatlingSession, paramName)) + bindable.setBoolean(paramName, asBoolean(gatlingSession, paramName)) case (UUID | TIMEUUID) => - boundStatement.setUUID(paramName, asUuid(gatlingSession, paramName)) + bindable.setUuid(paramName, asUuid(gatlingSession, paramName)) case FLOAT => - boundStatement.setFloat(paramName, asFloat(gatlingSession, paramName)) + bindable.setFloat(paramName, asFloat(gatlingSession, paramName)) case DOUBLE => - boundStatement.setDouble(paramName, asDouble(gatlingSession, paramName)) + bindable.setDouble(paramName, asDouble(gatlingSession, paramName)) case DECIMAL => - boundStatement.setDecimal(paramName, asDecimal(gatlingSession, paramName)) + bindable.setBigDecimal(paramName, asDecimal(gatlingSession, paramName)) case INET => - boundStatement.setInet(paramName, asInet(gatlingSession, paramName)) + bindable.setInetAddress(paramName, asInet(gatlingSession, paramName)) case TIMESTAMP => - boundStatement.setTimestamp(paramName, asTimestamp(gatlingSession, paramName)) + bindable.setInstant(paramName, asInstant(gatlingSession, paramName)) case BIGINT => - boundStatement.setLong(paramName, asBigInt(gatlingSession, paramName)) + bindable.setLong(paramName, asBigInt(gatlingSession, paramName)) case COUNTER => - boundStatement.setLong(paramName, asCounter(gatlingSession, paramName)) + bindable.setLong(paramName, asCounter(gatlingSession, paramName)) case BLOB => - boundStatement.setBytes(paramName, asByte(gatlingSession, paramName)) + bindable.setByteBuffer(paramName, asByte(gatlingSession, paramName)) case VARINT => - boundStatement.setVarint(paramName, asVarInt(gatlingSession, paramName)) + bindable.setBigInteger(paramName, asVarInt(gatlingSession, paramName)) case LIST => - boundStatement.setList(paramName, asList(gatlingSession, paramName)) + val dataType = bindable.getType(paramName) + dataType match { + case l: ListType => { + val memberClz = clzFromCodec(bindable, l.getElementType) + bindable.setList(paramName, asList(gatlingSession, paramName, memberClz), memberClz) + } + case _ => throw new IllegalStateException("Observed something other than ListType for a LIST param") + } case SET => - boundStatement.setSet(paramName, asSet(gatlingSession, paramName)) + val dataType = bindable.getType(paramName) + dataType match { + case s: SetType => { + val memberClz = clzFromCodec(bindable, s.getElementType) + bindable.setSet(paramName, asSet(gatlingSession, paramName, memberClz), memberClz) + } + case _ => throw new IllegalStateException("Observed something other than SetType for a SET param") + } case MAP => - boundStatement.setMap(paramName, asMap(gatlingSession, paramName)) + val dataType = bindable.getType(paramName) + dataType match { + case m: MapType => { + val keyClz = bindable.codecRegistry().codecFor(m.getKeyType).getJavaType.getRawType.asInstanceOf[Class[Any]] + val valClz = bindable.codecRegistry().codecFor(m.getValueType).getJavaType.getRawType.asInstanceOf[Class[Any]] + bindable.setMap(paramName, asMap(gatlingSession, paramName, keyClz, valClz), keyClz, valClz) + } + case _ => throw new IllegalStateException("Observed something other than MapType for a MAP param") + } case UDT => - boundStatement.setUDTValue(paramName, asUdt(gatlingSession, paramName)) + bindable.setUdtValue(paramName, asUdt(gatlingSession, paramName)) case TUPLE => - boundStatement.setTupleValue(paramName, asTuple(gatlingSession, paramName)) + bindable.setTupleValue(paramName, asTuple(gatlingSession, paramName)) case DATE => - boundStatement.setDate(paramName, asDate(gatlingSession, paramName)) + bindable.setLocalDate(paramName, asLocalDate(gatlingSession, paramName)) case SMALLINT => - boundStatement.setShort(paramName, asSmallInt(gatlingSession, paramName)) + bindable.setShort(paramName, asSmallInt(gatlingSession, paramName)) case TINYINT => - boundStatement.setByte(paramName, asTinyInt(gatlingSession, paramName)) + bindable.setByte(paramName, asTinyInt(gatlingSession, paramName)) case TIME => - boundStatement.setTime(paramName, asTime(gatlingSession, paramName)) + bindable.setLocalTime(paramName, asTime(gatlingSession, paramName)) case CUSTOM => gatlingSession.attributes.get(paramName) match { case Some(p: Point) => - boundStatement.set(paramName, asPoint(gatlingSession, paramName), classOf[Point]) + bindable.set(paramName, asPoint(gatlingSession, paramName), classOf[Point]) case Some(p: LineString) => - boundStatement.set(paramName, asLineString(gatlingSession, paramName), classOf[LineString]) + bindable.set(paramName, asLineString(gatlingSession, paramName), classOf[LineString]) case Some(p: Polygon) => - boundStatement.set(paramName, asPolygon(gatlingSession, paramName), classOf[Polygon]) + bindable.set(paramName, asPolygon(gatlingSession, paramName), classOf[Polygon]) case _ => throw new UnsupportedOperationException(s"$paramName on unknown CUSTOM type") } @@ -236,10 +284,10 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param preparedStatement CQL Prepared Stated * @return */ - def getParamsMap(preparedStatement: PreparedStatement): Map[String, DataType.Name] = { - val paramVariables = preparedStatement.getVariables + def getParamsMap(preparedStatement: PreparedStatement): Map[String, DataType] = { + val paramVariables = preparedStatement.getVariableDefinitions val paramIterator = paramVariables.iterator.asScala - paramIterator.map(p => (p.getName, p.getType.getName)).toMap + paramIterator.map(p => (p.getName.asCql(true), p.getType)).toMap } @@ -249,9 +297,9 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param preparedStatement CQL Prepared Stated * @return */ - def getParamsList(preparedStatement: PreparedStatement): List[DataType.Name] = { - val paramVariables = preparedStatement.getVariables - paramVariables.iterator.asScala.map(p => p.getType.getName).toList + def getParamsList(preparedStatement: PreparedStatement): List[DataType] = { + val paramVariables = preparedStatement.getVariableDefinitions + paramVariables.iterator.asScala.map(_.getType).toList } @@ -484,16 +532,16 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asTimestamp(gatlingSession: Session, paramName: String): java.util.Date = { + def asInstant(gatlingSession: Session, paramName: String): Instant = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { case Some(l: Long) => - new Date(l) + Instant.ofEpochMilli(l) case Some(s: String) => - DateTime.parse(s).toDate - case Some(d: Date) => - d + Instant.ofEpochMilli(DateTime.parse(s).getMillis) + case Some(i: Instant) => + i case _ => - throw new CqlTypeException(s"$paramName expected to be type of Long, String or java.util.Date") + throw new CqlTypeException(s"$paramName expected to be type of Long, String or java.time.Instant") } } @@ -507,13 +555,13 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asSet(gatlingSession: Session, paramName: String): util.Set[Any] = { + def asSet[T](gatlingSession: Session, paramName: String, elementType: Class[T]): util.Set[T] = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { - case Some(m: Set[Any]@unchecked) => + case Some(m: Set[T]@unchecked) => m.asJava - case Some(s: Seq[Any]@unchecked) => + case Some(s: Seq[T]@unchecked) => s.toSet.asJava - case Some(s: util.Set[Any]@unchecked) => + case Some(s: util.Set[T]@unchecked) => s case _ => throw new CqlTypeException(s"$paramName expected to be type of Set") @@ -530,13 +578,13 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asList(gatlingSession: Session, paramName: String): util.List[Any] = { + def asList[T](gatlingSession: Session, paramName: String, elementType: Class[T]): util.List[T] = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { - case Some(m: List[Any]@unchecked) => + case Some(m: List[T]@unchecked) => m.asJava - case Some(s: Seq[Any]@unchecked) => + case Some(s: Seq[T]@unchecked) => s.toList.asJava - case Some(l: util.List[Any]@unchecked) => + case Some(l: util.List[T]@unchecked) => l case _ => throw new CqlTypeException(s"$paramName expected to be type of List") @@ -553,11 +601,11 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asMap(gatlingSession: Session, paramName: String): util.Map[Any, Any] = { + def asMap[K, V](gatlingSession: Session, paramName: String, keyType: Class[K], valType: Class[V]): util.Map[K, V] = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { - case Some(m: Map[Any, Any]@unchecked) => + case Some(m: Map[K, V]@unchecked) => m.asJava - case Some(mj: util.Map[Any, Any]@unchecked) => + case Some(mj: util.Map[K, V]@unchecked) => mj case _ => throw new CqlTypeException(s"$paramName expected to be type of Set") @@ -595,7 +643,7 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { throw new CqlTypeException(s"$paramName expected to be type of LineString") } } - + /** * Returns CQL compatible Polygon type * @@ -695,10 +743,12 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asTime(gatlingSession: Session, paramName: String): Long = { + def asTime(gatlingSession: Session, paramName: String): LocalTime = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { - case Some(l: Long) => + case Some(l: LocalTime) => l + case Some(l: Long) => + LocalTime.ofNanoOfDay(l) case Some(s: String) => s.trim match { case hourMinSecNanoRegEx(hour, min, second, nano) => parseTime(paramName, hour, min, second, nano) @@ -711,7 +761,7 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { } private def parseTime(paramName: String, hourStr: String, minStr: String, - secStr: String, nanoStr: String = null) = { + secStr: String, nanoStr: String = null): LocalTime = { val hour = Integer.parseInt(hourStr) val min = Integer.parseInt(minStr) @@ -740,13 +790,7 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { throw new CqlTypeException(s"$paramName Seconds out of bounds.") } - var rawTime: Long = 0 - rawTime += TimeUnit.HOURS.toNanos(hour) - rawTime += TimeUnit.MINUTES.toNanos(min) - rawTime += TimeUnit.SECONDS.toNanos(sec) - rawTime += nanos - - rawTime + LocalTime.of(hour, min, sec, nanos) } @@ -761,16 +805,16 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asDate(gatlingSession: Session, paramName: String): com.datastax.driver.core.LocalDate = { + def asLocalDate(gatlingSession: Session, paramName: String): LocalDate = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { case Some(s: String) => val dateSplit = s.split("-").toList - LocalDate.fromYearMonthDay(dateSplit.head.toInt, dateSplit(1).toInt, dateSplit(2).toInt) + LocalDate.of(dateSplit.head.toInt, dateSplit(1).toInt, dateSplit(2).toInt) case Some(l: Long) => - LocalDate.fromMillisSinceEpoch(l) + toLocalDate(l) case Some(i: Int) => - LocalDate.fromDaysSinceEpoch(i) - case Some(ld: com.datastax.driver.core.LocalDate) => + toLocalDate(i) + case Some(ld: LocalDate) => ld case _ => throw new CqlTypeException(s"$paramName expected to be type of String, Long, Int or LocalDate") @@ -807,11 +851,12 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { * @param paramName CQL prepared statement parameter name * @return */ - def asUdt(gatlingSession: Session, paramName: String): UDTValue = { + def asUdt(gatlingSession: Session, paramName: String): UdtValue = { gatlingSession.attributes.get(paramName).flatMap(Option(_)) match { - case Some(udt: UDTValue) => + case Some(udt: UdtValue) => udt case _ => + throw new CqlTypeException(s"$paramName expected to be type of UDTValue") } } @@ -857,4 +902,14 @@ object CqlPreparedStatementUtil extends CqlPreparedStatementUtil { throw new CqlTypeException(s"$paramName expected to be type of ByteBuffer, Array[Byte] or Byte") } } + + def toLocalDate(epochMillis: Long): LocalDate = { + val end = Instant.ofEpochMilli(epochMillis) + val d = Duration.between(Instant.EPOCH, end) + LocalDate.ofEpochDay(d.toDays) + } + + def clzFromCodec(bindable: Bindable[_], genType: DataType): Class[Any] = { + bindable.codecRegistry().codecFor(genType).getJavaType.getRawType.asInstanceOf[Class[Any]] + } } diff --git a/src/main/scala/com/datastax/gatling/plugin/utils/FutureUtils.scala b/src/main/scala/com/datastax/gatling/plugin/utils/FutureUtils.scala deleted file mode 100644 index 8111056..0000000 --- a/src/main/scala/com/datastax/gatling/plugin/utils/FutureUtils.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018 Datastax Inc. - * - * This software can be used solely with DataStax products. Please consult the file LICENSE.md. - */ - -package com.datastax.gatling.plugin.utils - -import com.google.common.util.concurrent.{FutureCallback, Futures, ListenableFuture} - -import scala.concurrent.{Future, Promise} - -object FutureUtils { - /** - * Converts a Guava future into a Scala future containing the exact same - * result. - * - * The conversion is operated by the same thread that will eventually - * complete the guava future. It is not an expensive operation, though it - * may involve CAS operations under the hood. - * - * @param guavaFuture the Guava future to convert - * @tparam T the type of object returned by the Guava future - * @return a Scala [[Future]] that will be completed when the Guava future - * completes - */ - def toScalaFuture[T](guavaFuture: ListenableFuture[T]): Future[T] = { - val scalaPromise = Promise[T]() - Futures.addCallback( - guavaFuture, - new FutureCallback[T] { - def onSuccess(result: T): Unit = scalaPromise.success(result) - - def onFailure(exception: Throwable): Unit = scalaPromise.failure(exception) - } - ) - scalaPromise.future - } -} diff --git a/src/main/scala/com/datastax/gatling/plugin/utils/ResultSetUtils.scala b/src/main/scala/com/datastax/gatling/plugin/utils/ResultSetUtils.scala new file mode 100644 index 0000000..264eaf1 --- /dev/null +++ b/src/main/scala/com/datastax/gatling/plugin/utils/ResultSetUtils.scala @@ -0,0 +1,73 @@ +package com.datastax.gatling.plugin.utils + +import java.util.concurrent.CompletionStage + +import com.datastax.dse.driver.api.core.graph.{ AsyncGraphResultSet, GraphNode } +import com.datastax.oss.driver.api.core.cql.{ AsyncResultSet, Row } +import org.apache.tinkerpop.gremlin.process.traversal.Path +import org.apache.tinkerpop.gremlin.structure.{ Edge, Property, Vertex, VertexProperty } + +import collection.JavaConverters._ + +object ResultSetUtils { + + def asyncResultSetToIterator(resultSet: AsyncResultSet): Iterator[Row] = + new IteratorFromAsyncResultSet(new CqlAsyncRS(resultSet)) + + def asyncGraphResultSetToIterator(resultSet: AsyncGraphResultSet): Iterator[GraphNode] = + new IteratorFromAsyncResultSet(new GraphAsyncRS(resultSet)) +} + +object GraphResultSetUtils { + + private def buildFilterAndMapFn[T](filterFn: GraphNode => Boolean, mapFn: GraphNode => T)(resultSet: AsyncGraphResultSet, key: String): Iterator[T] = + ResultSetUtils + .asyncGraphResultSetToIterator(resultSet) + .map(graphNode => graphNode.getByKey(key)) + .filter(filterFn) + .map(mapFn) + + def edges: (AsyncGraphResultSet, String) => Iterator[Edge] = buildFilterAndMapFn(_.isEdge, _.asEdge) + + def vertexes: (AsyncGraphResultSet, String) => Iterator[Vertex] = buildFilterAndMapFn(_.isVertex, _.asVertex) + + def paths: (AsyncGraphResultSet, String) => Iterator[Path] = buildFilterAndMapFn(_.isPath, _.asPath) + + def properties: (AsyncGraphResultSet, String) => Iterator[Property[_]] = + buildFilterAndMapFn(_.isProperty, _.asProperty.asInstanceOf[Property[_]]) + + def vertexProperties: (AsyncGraphResultSet, String) => Iterator[VertexProperty[_]] = + buildFilterAndMapFn(_.isVertexProperty, _.asVertexProperty.asInstanceOf[VertexProperty[_]]) +} + +trait AsyncRS[T] { + def hasMorePages: Boolean + def currentPage: Iterable[T] + def fetchNextPage: CompletionStage[AsyncRS[T]] +} + +class CqlAsyncRS(rs: AsyncResultSet) extends AsyncRS[Row] { + override def hasMorePages: Boolean = rs.hasMorePages + override def currentPage: Iterable[Row] = rs.currentPage.asScala + override def fetchNextPage: CompletionStage[AsyncRS[Row]] = rs.fetchNextPage.thenApplyAsync(new CqlAsyncRS(_)) +} + +class GraphAsyncRS(rs: AsyncGraphResultSet) extends AsyncRS[GraphNode] { + override def hasMorePages: Boolean = rs.hasMorePages + override def currentPage: Iterable[GraphNode] = rs.currentPage.asScala + override def fetchNextPage: CompletionStage[AsyncRS[GraphNode]] = rs.fetchNextPage.thenApplyAsync(new GraphAsyncRS(_)) +} + +/* Note that this iterator isn't thread-safe */ +class IteratorFromAsyncResultSet[T](rs: AsyncRS[T]) extends Iterator[T] { + var working = rs + var iter = rs.currentPage.iterator + override def hasNext: Boolean = iter.hasNext || working.hasMorePages + override def next: T = { + if (!iter.hasNext && working.hasMorePages) { + working = working.fetchNextPage.toCompletableFuture.get + iter = working.currentPage.iterator + } + iter.next + } +} diff --git a/src/test/scala/com/datastax/gatling/plugin/DseCqlStatementSpec.scala b/src/test/scala/com/datastax/gatling/plugin/DseCqlStatementSpec.scala index 632b445..9b58156 100644 --- a/src/test/scala/com/datastax/gatling/plugin/DseCqlStatementSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/DseCqlStatementSpec.scala @@ -2,12 +2,12 @@ package com.datastax.gatling.plugin import java.nio.ByteBuffer -import com.datastax.driver.core.ColumnDefinitions.Definition -import com.datastax.driver.core._ import com.datastax.gatling.plugin.base.BaseSpec import com.datastax.gatling.plugin.exceptions.DseCqlStatementException import com.datastax.gatling.plugin.model._ import com.datastax.gatling.plugin.utils.CqlPreparedStatementUtil +import com.datastax.oss.driver.api.core.`type`.{DataType, DataTypes} +import com.datastax.oss.driver.api.core.cql._ import io.gatling.commons.validation._ import io.gatling.core.session.Session import io.gatling.core.session.el.ElCompiler @@ -15,13 +15,10 @@ import org.easymock.EasyMock._ import scala.collection.JavaConverters._ - class DseCqlStatementSpec extends BaseSpec { val prepared = mock[PreparedStatement] val mockColDefinitions = mock[ColumnDefinitions] - val mockDefinitions = mock[Definition] - val mockDefinitionId = mock[Definition] val mockBoundStatement = mock[BoundStatement] val mockCqlTypes = mock[CqlPreparedStatementUtil] @@ -41,47 +38,46 @@ class DseCqlStatementSpec extends BaseSpec { reset(prepared, mockBoundStatement, mockCqlTypes) } - describe("DseCqlSimpleStatement") { it("should succeed with a passed SimpleStatement", CqlTest) { - val stmt = new SimpleStatement("select * from keyspace.table where id = 5") + val stmt = SimpleStatement.builder("select * from keyspace.table where id = 5").build() val result = DseCqlSimpleStatement(stmt).buildFromSession(validGatlingSession) result shouldBe a[Success[_]] - result.get.toString shouldBe stmt.toString + result.get.build.getQuery shouldBe stmt.getQuery } - } - describe("DseCqlBoundStatementWithPassedParams") { val e1 = ElCompiler.compile[AnyRef]("${foo}") val e2 = ElCompiler.compile[AnyRef]("${bar}") + val mockBuilder = mock[BoundStatementBuilder] + it("correctly bind values to a prepared statement") { expecting { prepared.bind(fooValue, barValue).andReturn(mockBoundStatement) + mockBuilder.build().andReturn(mockBoundStatement) } whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - DseCqlBoundStatementWithPassedParams(mockCqlTypes, prepared, e1, e2) + DseCqlBoundStatementWithPassedParams(mockCqlTypes, prepared, e1, e2)((_) => mockBuilder) .buildFromSession(validGatlingSession) shouldBe a[Success[_]] } } - it("should fail if the expression is wrong and return the 1st error") { expecting { - prepared.getVariables.andStubReturn(mockColDefinitions) + prepared.getVariableDefinitions.andStubReturn(mockColDefinitions) } whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - val r = DseCqlBoundStatementWithPassedParams(mockCqlTypes, prepared, e1, e2) + val r = DseCqlBoundStatementWithPassedParams(mockCqlTypes, prepared, e1, e2)((_) => mockBuilder) .buildFromSession(invalidGatlingSession) r shouldBe a[Failure] r shouldBe "No attribute named 'foo' is defined".failure @@ -89,64 +85,69 @@ class DseCqlStatementSpec extends BaseSpec { } } - describe("DseCqlBoundStatementWithParamList") { val validParamList = Seq("foo", "bar") - val paramsList = List[DataType.Name](DataType.Name.TEXT, DataType.Name.INT) + val paramsList = List(DataTypes.TEXT, DataTypes.INT) + + val mockBuilder = mock[BoundStatementBuilder] it("correctly bind values to a prepared statement") { expecting { prepared.bind().andReturn(mockBoundStatement) mockCqlTypes.getParamsList(prepared).andReturn(paramsList) - mockCqlTypes.bindParamByOrder(validGatlingSession, mockBoundStatement, DataType.Name.TEXT, "foo", 0) - .andReturn(mockBoundStatement) - mockCqlTypes.bindParamByOrder(validGatlingSession, mockBoundStatement, DataType.Name.INT, "bar", 1) - .andReturn(mockBoundStatement) + mockCqlTypes.bindParamByOrder(validGatlingSession, mockBuilder, DataTypes.TEXT, "foo", 0) + .andReturn(mockBuilder) + mockCqlTypes.bindParamByOrder(validGatlingSession, mockBuilder, DataTypes.INT, "bar", 1) + .andReturn(mockBuilder) + mockBuilder.build().andReturn(mockBoundStatement) } whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - DseCqlBoundStatementWithParamList(mockCqlTypes, prepared, validParamList) + DseCqlBoundStatementWithParamList(mockCqlTypes, prepared, validParamList)((_) => mockBuilder) .buildFromSession(validGatlingSession) shouldBe a[Success[_]] } } } - - describe("DseCqlBoundStatementNamed") { + val mockBuilder = mock[BoundStatementBuilder] + it("correctly bind values to a prepared statement") { expecting { prepared.bind().andReturn(mockBoundStatement) - mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataType.Name.INT)) - mockCqlTypes.bindParamByName(validGatlingSession, mockBoundStatement, DataType.Name.INT, "foo") - .andReturn(mockBoundStatement) + mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataTypes.INT)) + mockCqlTypes.bindParamByName(validGatlingSession, mockBuilder, DataTypes.INT, "foo") + .andReturn(mockBuilder) + mockBuilder.build().andReturn(mockBoundStatement) } whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - DseCqlBoundStatementNamed(mockCqlTypes, prepared) + DseCqlBoundStatementNamed(mockCqlTypes, prepared)((_) => mockBuilder) .buildFromSession(validGatlingSession) shouldBe a[Success[_]] } } } + describe("DseCqlBoundStatementNamedFromSession") { + val mockBuilder = mock[BoundStatementBuilder] - - describe("DseCqlBoundStatementNamedFromSession") { it("correctly bind values to a prepared statement in session") { val sessionWithStatement: Session = validGatlingSession.set("statementKey", prepared) expecting { prepared.bind().andReturn(mockBoundStatement) - mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataType.Name.INT)) - mockCqlTypes.bindParamByName(sessionWithStatement, mockBoundStatement, DataType.Name.INT, "foo") - .andReturn(mockBoundStatement) + mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataTypes.INT)) + mockCqlTypes.bindParamByName(sessionWithStatement, mockBuilder, DataTypes.INT, fooKey) + .andReturn(mockBuilder) + mockBuilder.build().andReturn(mockBoundStatement) } + whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - DseCqlBoundStatementNamedFromSession(mockCqlTypes, "statementKey") + DseCqlBoundStatementNamedFromSession(mockCqlTypes, "statementKey")((_) => mockBuilder) .buildFromSession(sessionWithStatement) shouldBe a[Success[_]] } } @@ -156,7 +157,7 @@ class DseCqlStatementSpec extends BaseSpec { } whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { val thrown = intercept[DseCqlStatementException] { - DseCqlBoundStatementNamedFromSession(mockCqlTypes, "statementKey") + DseCqlBoundStatementNamedFromSession(mockCqlTypes, "statementKey")((_) => mockBuilder) .buildFromSession(validGatlingSession) shouldBe a[Failure] } thrown.getMessage shouldBe "Passed sessionKey: {statementKey} does not exist in Session." @@ -164,45 +165,45 @@ class DseCqlStatementSpec extends BaseSpec { } } - - - describe("DseCqlBoundBatchStatement") { + val mockBuilder = mock[BoundStatementBuilder] + it("correctly bind values to a prepared statement") { expecting { - mockBoundStatement.getOutgoingPayload.andReturn(Map("test" -> ByteBuffer.wrap(Array(12.toByte))).asJava) - mockBoundStatement.getOutgoingPayload.andReturn(Map("test" -> ByteBuffer.wrap(Array(12.toByte))).asJava) prepared.bind().andReturn(mockBoundStatement) - mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataType.Name.INT)) - mockCqlTypes.bindParamByName(validGatlingSession, mockBoundStatement, DataType.Name.INT, "foo") - .andReturn(mockBoundStatement) + mockCqlTypes.getParamsMap(prepared).andReturn(Map(fooKey -> DataTypes.INT)) + mockCqlTypes.bindParamByName(validGatlingSession, mockBuilder, DataTypes.INT, fooKey) + .andReturn(mockBuilder).anyTimes() + mockBuilder.build().andReturn(mockBoundStatement) } - whenExecuting(prepared, mockCqlTypes, mockBoundStatement) { - DseCqlBoundBatchStatement(mockCqlTypes, Seq(prepared)) + whenExecuting(prepared, mockCqlTypes, mockBoundStatement, mockBuilder) { + DseCqlBoundBatchStatement(mockCqlTypes, Seq(prepared))((_) => mockBuilder) .buildFromSession(validGatlingSession) shouldBe a[Success[_]] } } } - describe("DseCqlCustomPayloadStatement") { - val stmt = new SimpleStatement("select * from keyspace.table where id = 5") + val stmt = SimpleStatement.builder("select * from keyspace.table where id = 5").build() it("should succeed with a passed SimpleStatement", CqlTest) { + val expectedCustomPayload = Map("test" -> ByteBuffer.wrap(Array(12.toByte))) val payloadGatlingSession = new Session("name", 1, Map( - "payload" -> Map("test" -> ByteBuffer.wrap(Array(12.toByte)))) + "payload" -> expectedCustomPayload) ) val result = DseCqlCustomPayloadStatement(stmt, "payload") .buildFromSession(payloadGatlingSession) result shouldBe a[Success[_]] - result.get.toString shouldBe stmt.toString + val resultStmt = result.get.build + resultStmt.getCustomPayload shouldBe expectedCustomPayload.asJava + resultStmt.getQuery shouldBe stmt.getQuery } it("should fail with non existent sessionKey", CqlTest) { @@ -220,8 +221,6 @@ class DseCqlStatementSpec extends BaseSpec { DseCqlCustomPayloadStatement(stmt, "payload") .buildFromSession(payloadGatlingSession) shouldBe a[Failure] } - } - } diff --git a/src/test/scala/com/datastax/gatling/plugin/DseGraphStatementSpec.scala b/src/test/scala/com/datastax/gatling/plugin/DseGraphStatementSpec.scala index 69c6bd1..63371d4 100644 --- a/src/test/scala/com/datastax/gatling/plugin/DseGraphStatementSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/DseGraphStatementSpec.scala @@ -1,24 +1,23 @@ package com.datastax.gatling.plugin -import com.datastax.driver.dse.DseSession -import com.datastax.driver.dse.graph.SimpleGraphStatement -import com.datastax.dse.graph.api.DseGraph +import com.datastax.dse.driver.api.core.graph.{FluentGraphStatement, ScriptGraphStatement} +import com.datastax.dse.driver.api.core.graph.DseGraph.g import com.datastax.gatling.plugin.base.BaseSpec import com.datastax.gatling.plugin.model.{GraphBoundStatement, GraphFluentStatement, GraphStringStatement} +import com.datastax.oss.driver.api.core.CqlSession import io.gatling.commons.validation.{Failure, Success} import io.gatling.core.session.Session import io.gatling.core.session.el.ElCompiler import org.easymock.EasyMock.reset - class DseGraphStatementSpec extends BaseSpec { - val mockDseSession = mock[DseSession] + val mockCqlSession = mock[CqlSession] val validGatlingSession = new Session("name", 1, Map("test" -> "5")) val invalidGatlingSession = new Session("name", 1, Map("buzz" -> Map("test" -> "this"))) before { - reset(mockDseSession) + reset(mockCqlSession) } @@ -42,8 +41,7 @@ class DseGraphStatementSpec extends BaseSpec { describe("FluentStatement") { - val g = DseGraph.traversal(mockDseSession) - val gStatement = DseGraph.statementFromTraversal(g.V().limit(5)) + val gStatement = FluentGraphStatement.newInstance(g.V().limit(5)) val target = GraphFluentStatement(gStatement) it("should correctly return StringStatement for a valid expression") { @@ -54,7 +52,7 @@ class DseGraphStatementSpec extends BaseSpec { describe("GraphBoundStatement") { - val graphStatement = new SimpleGraphStatement("g.addV(label, vertexLabel).property('type', myType)") + val graphStatement = ScriptGraphStatement.builder("g.addV(label, vertexLabel).property('type', myType)") val target = GraphBoundStatement(graphStatement, Map("test" -> "type")) it("should suceeed with a valid session") { diff --git a/src/test/scala/com/datastax/gatling/plugin/base/BaseCassandraServerSpec.scala b/src/test/scala/com/datastax/gatling/plugin/base/BaseCassandraServerSpec.scala index aee98db..7bcc96e 100644 --- a/src/test/scala/com/datastax/gatling/plugin/base/BaseCassandraServerSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/base/BaseCassandraServerSpec.scala @@ -1,15 +1,16 @@ package com.datastax.gatling.plugin.base import java.nio.file.Files +import java.util.concurrent.atomic.AtomicBoolean -import com.datastax.driver.dse.DseSession +import com.datastax.oss.driver.api.core.CqlSession import org.cassandraunit.utils.EmbeddedCassandraServerHelper /** * Used for Specs that require a running Cassandra instance to run */ class BaseCassandraServerSpec extends BaseSpec { - protected val dseSession: DseSession = BaseCassandraServerSpec.dseSession + protected val cqlSession: CqlSession = BaseCassandraServerSpec.dseSession protected def cleanCassandra(keyspace: String = ""): Unit = { if (keyspace.isEmpty) { @@ -20,14 +21,14 @@ class BaseCassandraServerSpec extends BaseSpec { } protected def createKeyspace(keyspace: String): Boolean = { - dseSession.execute( + cqlSession.execute( s"CREATE KEYSPACE IF NOT EXISTS $keyspace WITH replication = " + "{ 'class' : 'SimpleStrategy', 'replication_factor': '1'}") .wasApplied() } protected def createTable(keyspace: String, name: String, columns: String): Boolean = { - dseSession.execute( + cqlSession.execute( s""" CREATE TABLE IF NOT EXISTS $keyspace.$name ( $columns @@ -36,7 +37,7 @@ class BaseCassandraServerSpec extends BaseSpec { protected def createType(keyspace: String, name: String, columns: String): Boolean = { - dseSession.execute( + cqlSession.execute( s""" CREATE TYPE IF NOT EXISTS $keyspace.$name ( $columns @@ -45,12 +46,11 @@ class BaseCassandraServerSpec extends BaseSpec { } object BaseCassandraServerSpec { - if (EmbeddedCassandraServerHelper.getSession == null) { + EmbeddedCassandraServerHelper.startEmbeddedCassandra( "cassandra.yaml", Files.createTempDirectory("gatling-dse-plugin.").toString, 30000L) - } - private val dseSession: DseSession = GatlingDseSession.getSession + private val dseSession: CqlSession = GatlingCqlSession.getSession } diff --git a/src/test/scala/com/datastax/gatling/plugin/base/BaseCqlSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/base/BaseCqlSimulation.scala index bd6b95e..dfe9d9d 100644 --- a/src/test/scala/com/datastax/gatling/plugin/base/BaseCqlSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/base/BaseCqlSimulation.scala @@ -6,7 +6,7 @@ abstract class BaseCqlSimulation extends Simulation { val testKeyspace = "gatling_cql_unittests" - val session = GatlingDseSession.createDseSession() + val session = GatlingCqlSession.createCqlSession() def createTestKeyspace = { session.execute( diff --git a/src/test/scala/com/datastax/gatling/plugin/base/BaseGraphSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/base/BaseGraphSimulation.scala index fd0563d..0891137 100644 --- a/src/test/scala/com/datastax/gatling/plugin/base/BaseGraphSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/base/BaseGraphSimulation.scala @@ -6,7 +6,7 @@ abstract class BaseGraphSimulation extends Simulation { val testKeyspace = "gatling_cql_unittests" - val session = GatlingDseSession.createDseSession("10.10.10.2", 9042) + val session = GatlingCqlSession.createCqlSession("10.10.10.2", 9042) def createTestKeyspace = { session.execute( diff --git a/src/test/scala/com/datastax/gatling/plugin/base/GatlingCqlSession.scala b/src/test/scala/com/datastax/gatling/plugin/base/GatlingCqlSession.scala new file mode 100644 index 0000000..93c8c7b --- /dev/null +++ b/src/test/scala/com/datastax/gatling/plugin/base/GatlingCqlSession.scala @@ -0,0 +1,58 @@ +package com.datastax.gatling.plugin.base + +import java.net.InetSocketAddress +import java.util.concurrent.atomic.AtomicReference + +import com.datastax.oss.driver.api.core.CqlSession +import org.cassandraunit.utils.EmbeddedCassandraServerHelper + +trait GatlingCqlSession { + + // TODO: Probably should eventually be an actor, using AtomicRef for now to cheat + private val session: AtomicReference[CqlSession] = new AtomicReference[CqlSession](null) + + /** + * Create new Dse Session to either the embedded C* instance or a remote instance + * + * Note: This includes a hack to get around issue with DseCluster builder and Scala + * + * @param cassandraHost Cassandra Server IP + * @param cassandraPort Cassandra Port, default will use Embedded Cassandra's port + * @return + */ + def createCqlSession(cassandraHost: String = "127.0.0.1", + cassandraPort: Int = EmbeddedCassandraServerHelper.getNativeTransportPort, + localDc:String = "datacenter1"): CqlSession = { + + session.updateAndGet((v) => { + if (v != null) { + v + } + else { + val addr = new InetSocketAddress(cassandraHost, cassandraPort) + CqlSession.builder().addContactPoint(addr).withLocalDatacenter(localDc).build() + } + }) + } + + + /** + * Get the session of the current DSE Session + * + * @return + */ + def getSession: CqlSession = { + createCqlSession() + } + + + /** + * Close the current session + */ + def closeSession(): Unit = { + session.getAndSet(null).close() + } + +} + +object GatlingCqlSession extends GatlingCqlSession diff --git a/src/test/scala/com/datastax/gatling/plugin/base/GatlingDseSession.scala b/src/test/scala/com/datastax/gatling/plugin/base/GatlingDseSession.scala deleted file mode 100644 index 8af4d84..0000000 --- a/src/test/scala/com/datastax/gatling/plugin/base/GatlingDseSession.scala +++ /dev/null @@ -1,67 +0,0 @@ -package com.datastax.gatling.plugin.base - -import com.datastax.driver.dse.{DseCluster, DseSession} -import org.cassandraunit.utils.EmbeddedCassandraServerHelper - -trait GatlingDseSession { - - private var dseCluster: DseCluster = _ - - private var session: DseSession = _ - - /** - * Create new Dse Session to either the embedded C* instance or a remote instance - * - * Note: This includes a hack to get around issue with DseCluster builder and Scala - * - * @param cassandraHost Cassandra Server IP - * @param cassandraPort Cassandra Port, default will use Embedded Cassandra's port - * @return - */ - def createDseSession(cassandraHost: String = "127.0.0.1", cassandraPort: Int = -1): DseSession = { - - if (session != null) { - return session - } - - var cPort = cassandraPort - if (cPort == -1) { - cPort = EmbeddedCassandraServerHelper.getNativeTransportPort - } - - dseCluster = - try { - DseCluster.builder().addContactPoint(cassandraHost).withPort(cPort).build() - } - catch { - case _: Exception => DseCluster.builder().addContactPoint(cassandraHost).withPort(cPort).build() - } - - session = dseCluster.connect() - session - } - - - /** - * Get the session of the current DSE Session - * - * @return - */ - def getSession: DseSession = { - if (session == null) { - createDseSession() - } - session - } - - - /** - * Close the current session - */ - def closeSession(): Unit = { - session.close() - } - -} - -object GatlingDseSession extends GatlingDseSession diff --git a/src/test/scala/com/datastax/gatling/plugin/model/CqlStatementBuildersSpec.scala b/src/test/scala/com/datastax/gatling/plugin/model/CqlStatementBuildersSpec.scala index de40a8f..b3b6189 100644 --- a/src/test/scala/com/datastax/gatling/plugin/model/CqlStatementBuildersSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/model/CqlStatementBuildersSpec.scala @@ -1,11 +1,14 @@ package com.datastax.gatling.plugin.model -import com.datastax.driver.core.ConsistencyLevel.{EACH_QUORUM, THREE} -import com.datastax.driver.core._ -import com.datastax.driver.core.policies.FallthroughRetryPolicy +import java.nio.ByteBuffer +import java.time.Duration + import com.datastax.gatling.plugin.DsePredef._ -import com.datastax.gatling.plugin.checks.{CqlChecks, DseCqlCheck, GenericCheck, GenericChecks} -import io.gatling.core.Predef._ +import com.datastax.gatling.plugin.checks.CqlChecks +import com.datastax.oss.driver.api.core.ConsistencyLevel.{EACH_QUORUM, THREE} +import com.datastax.oss.driver.api.core.cql.{AsyncResultSet, SimpleStatement, SimpleStatementBuilder} +import com.datastax.oss.driver.api.core.metadata.Node +import com.datastax.oss.driver.api.core.metadata.token.Token import io.gatling.core.session.{ExpressionSuccessWrapper, Session} import org.scalatest.easymock.EasyMockSugar import org.scalatest.{FlatSpec, Matchers} @@ -15,63 +18,77 @@ class CqlStatementBuildersSpec extends FlatSpec with Matchers with EasyMockSugar it should "build statements from a CQL String" in { - val statementAttributes: DseCqlAttributes = cql("the-tag") - .executeCql("SELECT foo FROM bar.baz LIMIT 1") + val statementAttributes: DseCqlAttributes[SimpleStatement,SimpleStatementBuilder] = cql("the-tag") + .executeStatement("SELECT foo FROM bar.baz LIMIT 1") .build() .dseAttributes - val statement: SimpleStatement = statementAttributes.statement + val statement = statementAttributes.statement .buildFromSession(Session("the-tag", 42)) - .get.asInstanceOf[SimpleStatement] + .get.build statementAttributes.cqlStatements should contain only "SELECT foo FROM bar.baz LIMIT 1" - statement.getQueryString() should be("SELECT foo FROM bar.baz LIMIT 1") + statement.getQuery should be("SELECT foo FROM bar.baz LIMIT 1") } it should "forward all attributs to DseCqlAttributes" in { - val pagingState = mock[PagingState] - val genericCheck = GenericCheck(GenericChecks.exhausted.is(true)) - val cqlCheck = DseCqlCheck(CqlChecks.oneRow.is(mock[Row].expressionSuccess)) - val statementAttributes: DseCqlAttributes = cql("the-session-tag") - .executeCql("FOO") + val node = mock[Node] + val userOrRole = "userOrRole" + val customPayloadKey = "key" + val customPayloadVal = mock[ByteBuffer] + val pagingState = mock[ByteBuffer] + val queryTimestamp = 123L + val routingKey = mock[ByteBuffer] + val routingKeyspace = "some_keyspace" + val routingToken = mock[Token] + val timeout = Duration.ofHours(1) + val cqlCheck = CqlChecks.resultSet.find.is(mock[AsyncResultSet].expressionSuccess).build + val statementAttributes: DseCqlAttributes[_,_] = cql("the-session-tag") + .executeStatement("FOO") .withConsistencyLevel(EACH_QUORUM) - .withUserOrRole("User or role") - .withDefaultTimestamp(-76) + .addCustomPayload(customPayloadKey, customPayloadVal) .withIdempotency() - .withReadTimeout(99) - .withSerialConsistencyLevel(THREE) - .withRetryPolicy(FallthroughRetryPolicy.INSTANCE) - .withFetchSize(3) + .withNode(node) + .executeAs(userOrRole) .withTracingEnabled() + .withPageSize(3) .withPagingState(pagingState) - .check(genericCheck) + .withQueryTimestamp(queryTimestamp) + .withRoutingKey(routingKey) + .withRoutingKeyspace(routingKeyspace) + .withRoutingToken(routingToken) + .withSerialConsistencyLevel(THREE) + .withTimeout(timeout) .check(cqlCheck) .build() .dseAttributes statementAttributes.tag should be("the-session-tag") statementAttributes.cl should be(Some(EACH_QUORUM)) statementAttributes.cqlChecks should contain only cqlCheck - statementAttributes.genericChecks should contain only genericCheck - statementAttributes.userOrRole should be(Some("User or role")) - statementAttributes.readTimeout should be(Some(99)) + statementAttributes.customPayload should be(Some(Map(customPayloadKey -> customPayloadVal))) statementAttributes.idempotent should be(Some(true)) - statementAttributes.defaultTimestamp should be(Some(-76)) + statementAttributes.node should be(Some(node)) + statementAttributes.userOrRole should be(Some(userOrRole)) statementAttributes.enableTrace should be(Some(true)) - statementAttributes.serialCl should be(Some(THREE)) - statementAttributes.fetchSize should be(Some(3)) - statementAttributes.retryPolicy should be(Some(FallthroughRetryPolicy.INSTANCE)) + statementAttributes.pageSize should be(Some(3)) statementAttributes.pagingState should be(Some(pagingState)) + statementAttributes.queryTimestamp should be(Some(queryTimestamp)) + statementAttributes.routingKey should be(Some(routingKey)) + statementAttributes.routingKeyspace should be(Some(routingKeyspace)) + statementAttributes.routingToken should be(Some(routingToken)) + statementAttributes.serialCl should be(Some(THREE)) + statementAttributes.timeout should be(Some(timeout)) statementAttributes.cqlStatements should contain only "FOO" } it should "build statements from a SimpleStatement" in { - val statementAttributes: DseCqlAttributes = cql("the-tag") - .executeStatement(new SimpleStatement("Some CQL")) + val statementAttributes: DseCqlAttributes[SimpleStatement,SimpleStatementBuilder] = cql("the-tag") + .executeStatement(SimpleStatement.newInstance("Some CQL")) .build() .dseAttributes - val statement: SimpleStatement = statementAttributes.statement + val statement = statementAttributes.statement .buildFromSession(Session("the-tag", 42)) - .get.asInstanceOf[SimpleStatement] + .get.build statementAttributes.cqlStatements should contain only "Some CQL" - statement.getQueryString() should be("Some CQL") + statement.getQuery should be("Some CQL") } // it should "build statements from a PreparedStatement" in { diff --git a/src/test/scala/com/datastax/gatling/plugin/request/CqlRequestActionSpec.scala b/src/test/scala/com/datastax/gatling/plugin/request/CqlRequestActionSpec.scala index 553e778..73a7e74 100644 --- a/src/test/scala/com/datastax/gatling/plugin/request/CqlRequestActionSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/request/CqlRequestActionSpec.scala @@ -1,21 +1,23 @@ package com.datastax.gatling.plugin.request -import java.util.concurrent.{Executor, TimeUnit} +import java.nio.ByteBuffer +import java.time.Duration +import java.util.concurrent.{CompletableFuture, CompletionStage} import akka.actor.{ActorSystem, Props} import akka.testkit.TestKitBase import ch.qos.logback.classic.{Level, Logger} import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender -import com.datastax.driver.core._ -import com.datastax.driver.core.policies.FallthroughRetryPolicy -import com.datastax.driver.dse.DseSession import com.datastax.gatling.plugin.base.BaseSpec import com.datastax.gatling.plugin.metrics.NoopMetricsLogger import com.datastax.gatling.plugin.utils.GatlingTimingSource import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.model.{DseCqlAttributes, DseCqlStatement} -import com.google.common.util.concurrent.{Futures, ListenableFuture} +import com.datastax.oss.driver.api.core.{ConsistencyLevel, CqlSession} +import com.datastax.oss.driver.api.core.cql.{AsyncResultSet, SimpleStatement, SimpleStatementBuilder} +import com.datastax.oss.driver.api.core.metadata.Node +import com.datastax.oss.driver.api.core.metadata.token.Token import io.gatling.commons.validation.SuccessWrapper import io.gatling.core.action.Exit import io.gatling.core.config.GatlingConfiguration @@ -28,39 +30,36 @@ import org.slf4j.LoggerFactory class CqlRequestActionSpec extends BaseSpec with TestKitBase { implicit lazy val system:ActorSystem = ActorSystem() val gatlingTestConfig: GatlingConfiguration = GatlingConfiguration.loadForTest() - val dseSession: DseSession = mock[DseSession] - val dseCqlStatement: DseCqlStatement = mock[DseCqlStatement] - val pagingState: PagingState = mock[PagingState] + val cqlSession: CqlSession = mock[CqlSession] + val dseCqlStatement: DseCqlStatement[SimpleStatement,SimpleStatementBuilder] = mock[DseCqlStatement[SimpleStatement,SimpleStatementBuilder]] + val node:Node = mock[Node] + val pageSize = 3 + val pagingState: ByteBuffer = mock[ByteBuffer] + val queryTimestamp = 123L + val routingKey:ByteBuffer = mock[ByteBuffer] + val routingKeyspace = "some_keyspace" + val routingToken:Token = mock[Token] + val timeout:Duration = Duration.ofHours(1) val statsEngine: StatsEngine = mock[StatsEngine] val gatlingSession = Session("scenario", 1) - def getTarget(dseAttributes: DseCqlAttributes): CqlRequestAction = { + def getTarget(dseAttributes: DseCqlAttributes[SimpleStatement,SimpleStatementBuilder]): CqlRequestAction[SimpleStatement,SimpleStatementBuilder] = { new CqlRequestAction( "sample-dse-request", new Exit(system.actorOf(Props[DseRequestActor]), statsEngine), system, statsEngine, - DseProtocol(dseSession), + DseProtocol(cqlSession), dseAttributes, NoopMetricsLogger(), executorServiceForTests(), GatlingTimingSource()) } - private def mockResultSetFuture(): ResultSetFuture = new ResultSetFuture { - val delegate: ListenableFuture[ResultSet] = Futures.immediateFuture(mock[ResultSet]) - override def cancel(b: Boolean): Boolean = false - override def getUninterruptibly: ResultSet = delegate.get() - override def getUninterruptibly(duration: Long, timeUnit: TimeUnit): ResultSet = delegate.get(duration, timeUnit) - override def addListener(listener: Runnable, executor: Executor): Unit = delegate.addListener(listener, executor) - override def isCancelled: Boolean = delegate.isCancelled - override def isDone: Boolean = delegate.isDone - override def get(): ResultSet = delegate.get() - override def get(timeout: Long, unit: TimeUnit): ResultSet = delegate.get(timeout, unit) - } + private def mockAsyncResultSetFuture(): CompletionStage[AsyncResultSet] = CompletableFuture.completedFuture(mock[AsyncResultSet]) before { - reset(dseCqlStatement, dseSession, pagingState, statsEngine) + reset(dseCqlStatement, cqlSession, pagingState, statsEngine) } override protected def afterAll(): Unit = { @@ -68,19 +67,19 @@ class CqlRequestActionSpec extends BaseSpec with TestKitBase { } describe("CQL") { - val statementCapture = EasyMock.newCapture[RegularStatement] + val statementCapture = EasyMock.newCapture[SimpleStatement] it("should have default CQL attributes set if nothing passed") { val cqlAttributesWithDefaults = DseCqlAttributes( "test", dseCqlStatement) expecting { - dseCqlStatement.buildFromSession(gatlingSession).andReturn(new SimpleStatement("select * from test") + dseCqlStatement.buildFromSession(gatlingSession) andReturn(SimpleStatement.builder("select * from test") .success) - dseSession.executeAsync(capture(statementCapture)) andReturn mockResultSetFuture() + cqlSession.executeAsync(capture(statementCapture)) andReturn mockAsyncResultSetFuture() } - whenExecuting(dseCqlStatement, dseSession) { + whenExecuting(dseCqlStatement, cqlSession) { getTarget(cqlAttributesWithDefaults).sendQuery(gatlingSession) } @@ -88,50 +87,53 @@ class CqlRequestActionSpec extends BaseSpec with TestKitBase { capturedStatement shouldBe a[SimpleStatement] capturedStatement.getConsistencyLevel shouldBe null capturedStatement.getSerialConsistencyLevel shouldBe null - capturedStatement.getFetchSize shouldBe 0 - capturedStatement.getDefaultTimestamp shouldBe -9223372036854775808L - capturedStatement.getReadTimeoutMillis shouldBe -2147483648 - capturedStatement.getRetryPolicy shouldBe null + capturedStatement.getPageSize should be <= 0 capturedStatement.isIdempotent shouldBe null capturedStatement.isTracing shouldBe false - capturedStatement.getQueryString should be("select * from test") + capturedStatement.getQuery should be("select * from test") } it("should enable all the CQL Attributes in DseAttributes") { - val cqlAttributes = DseCqlAttributes( + val cqlAttributes = new DseCqlAttributes[SimpleStatement,SimpleStatementBuilder]( "test", dseCqlStatement, cl = Some(ConsistencyLevel.ANY), - userOrRole = Some("test_user"), - readTimeout = Some(12), - defaultTimestamp = Some(1498167845000L), idempotent = Some(true), - fetchSize = Some(50), + node = Some(node), + enableTrace = Some(true), + pagingState = Some(pagingState), + pageSize = Some(pageSize), + queryTimestamp = Some(queryTimestamp), + routingKey = Some(routingKey), + routingKeyspace = Some(routingKeyspace), + routingToken = Some(routingToken), serialCl = Some(ConsistencyLevel.LOCAL_SERIAL), - retryPolicy = Some(FallthroughRetryPolicy.INSTANCE), - enableTrace = Some(true)) + timeout = Some(timeout)) expecting { - dseCqlStatement.buildFromSession(gatlingSession).andReturn(new SimpleStatement("select * from test") + dseCqlStatement.buildFromSession(gatlingSession) andReturn(SimpleStatement.builder("select * from test") .success) - dseSession.executeAsync(capture(statementCapture)) andReturn mockResultSetFuture() + cqlSession.executeAsync(capture(statementCapture)) andReturn mockAsyncResultSetFuture() } - whenExecuting(dseCqlStatement, dseSession) { + whenExecuting(dseCqlStatement, cqlSession) { getTarget(cqlAttributes).sendQuery(gatlingSession) } val capturedStatement = statementCapture.getValue capturedStatement shouldBe a[SimpleStatement] capturedStatement.getConsistencyLevel shouldBe ConsistencyLevel.ANY - capturedStatement.getDefaultTimestamp shouldBe 1498167845000L - capturedStatement.getReadTimeoutMillis shouldBe 12 capturedStatement.isIdempotent shouldBe true - capturedStatement.getFetchSize shouldBe 50 - capturedStatement.getSerialConsistencyLevel shouldBe ConsistencyLevel.LOCAL_SERIAL - capturedStatement.getRetryPolicy shouldBe FallthroughRetryPolicy.INSTANCE - capturedStatement.getQueryString should be("select * from test") + capturedStatement.getNode shouldBe node capturedStatement.isTracing shouldBe true + capturedStatement.getPageSize shouldBe pageSize + capturedStatement.getPagingState shouldBe pagingState + capturedStatement.getQueryTimestamp shouldBe queryTimestamp + capturedStatement.getRoutingKey shouldBe routingKey + capturedStatement.getRoutingKeyspace.toString shouldBe routingKeyspace + capturedStatement.getRoutingToken shouldBe routingToken + capturedStatement.getSerialConsistencyLevel shouldBe ConsistencyLevel.LOCAL_SERIAL + capturedStatement.getTimeout shouldBe timeout } it("should log exceptions encountered") { @@ -147,12 +149,12 @@ class CqlRequestActionSpec extends BaseSpec with TestKitBase { val cqlRequestAction = getTarget(cqlAttributesWithDefaults) - val classLogger = LoggerFactory.getLogger(classOf[CqlRequestAction]).asInstanceOf[Logger] + val classLogger = LoggerFactory.getLogger(classOf[CqlRequestAction[SimpleStatement,SimpleStatementBuilder]]).asInstanceOf[Logger] val listAppender: ListAppender[ILoggingEvent] = new ListAppender[ILoggingEvent] listAppender.start() classLogger.addAppender(listAppender) - whenExecuting(dseCqlStatement, dseSession) { + whenExecuting(dseCqlStatement, cqlSession) { cqlRequestAction.sendQuery(gatlingSession) } diff --git a/src/test/scala/com/datastax/gatling/plugin/request/GraphRequestActionSpec.scala b/src/test/scala/com/datastax/gatling/plugin/request/GraphRequestActionSpec.scala index 2c61336..5d02568 100644 --- a/src/test/scala/com/datastax/gatling/plugin/request/GraphRequestActionSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/request/GraphRequestActionSpec.scala @@ -1,18 +1,20 @@ package com.datastax.gatling.plugin.request -import java.util.concurrent.{Executor, TimeUnit} +import java.nio.ByteBuffer +import java.time.Duration +import java.util.concurrent.{CompletableFuture, CompletionStage} import akka.actor.{ActorSystem, Props} import akka.testkit.TestKitBase -import com.datastax.driver.core._ -import com.datastax.driver.dse.DseSession -import com.datastax.driver.dse.graph.{GraphResultSet, RegularGraphStatement, SimpleGraphStatement} +import com.datastax.dse.driver.api.core.DseSession +import com.datastax.dse.driver.api.core.graph.{AsyncGraphResultSet, ScriptGraphStatement, ScriptGraphStatementBuilder} +import com.datastax.gatling.plugin.DseProtocol import com.datastax.gatling.plugin.base.BaseSpec import com.datastax.gatling.plugin.metrics.NoopMetricsLogger import com.datastax.gatling.plugin.utils.GatlingTimingSource -import com.datastax.gatling.plugin.DseProtocol -import com.datastax.gatling.plugin.model.{DseGraphStatement, DseGraphAttributes} -import com.google.common.util.concurrent.{Futures, ListenableFuture} +import com.datastax.gatling.plugin.model.{DseGraphAttributes, DseGraphStatement} +import com.datastax.oss.driver.api.core.ConsistencyLevel +import com.datastax.oss.driver.api.core.metadata.Node import io.gatling.commons.validation.SuccessWrapper import io.gatling.core.action.Exit import io.gatling.core.config.GatlingConfiguration @@ -24,108 +26,82 @@ import org.easymock.EasyMock._ class GraphRequestActionSpec extends BaseSpec with TestKitBase { implicit lazy val system = ActorSystem() val gatlingTestConfig = GatlingConfiguration.loadForTest() - val dseSession = mock[DseSession] - val dseGraphStatement = mock[DseGraphStatement] - val pagingState = mock[PagingState] + val cqlSession = mock[DseSession] + val dseGraphStatement = mock[DseGraphStatement[ScriptGraphStatement,ScriptGraphStatementBuilder]] + val node:Node = mock[Node] + val readConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM + val subProtocol = "graph-binary-3.0" + val timeout:Duration = Duration.ofHours(1) + val timestamp = 123L + val traversalSource = "g.V()" + val writeConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM + + val pagingState:ByteBuffer = mock[ByteBuffer] val statsEngine: StatsEngine = mock[StatsEngine] val gatlingSession = Session("scenario", 1) - def getTarget(dseAttributes: DseGraphAttributes): GraphRequestAction = { + def getTarget(dseAttributes: DseGraphAttributes[ScriptGraphStatement,ScriptGraphStatementBuilder]): + GraphRequestAction[ScriptGraphStatement,ScriptGraphStatementBuilder] = { new GraphRequestAction( "sample-dse-request", new Exit(system.actorOf(Props[DseRequestActor]), statsEngine), system, statsEngine, - DseProtocol(dseSession), + DseProtocol(cqlSession), dseAttributes, NoopMetricsLogger(), executorServiceForTests(), GatlingTimingSource()) } - private def mockResultSetFuture(): ResultSetFuture = new ResultSetFuture { - val delegate: ListenableFuture[ResultSet] = Futures.immediateFuture(mock[ResultSet]) - override def cancel(b: Boolean): Boolean = false - override def getUninterruptibly: ResultSet = delegate.get() - override def getUninterruptibly(duration: Long, timeUnit: TimeUnit): ResultSet = delegate.get(duration, timeUnit) - override def addListener(listener: Runnable, executor: Executor): Unit = delegate.addListener(listener, executor) - override def isCancelled: Boolean = delegate.isCancelled - override def isDone: Boolean = delegate.isDone - override def get(): ResultSet = delegate.get() - override def get(timeout: Long, unit: TimeUnit): ResultSet = delegate.get(timeout, unit) - } + private def mockAsyncGraphResultSetFuture(): CompletionStage[AsyncGraphResultSet] = + CompletableFuture.completedFuture(mock[AsyncGraphResultSet]) before { - reset(dseGraphStatement, dseSession, pagingState, statsEngine) + reset(dseGraphStatement, cqlSession, pagingState, statsEngine) } override protected def afterAll(): Unit = { shutdown(system) } - + describe("Graph") { - val statementCapture = EasyMock.newCapture[RegularGraphStatement] + val statementCapture = EasyMock.newCapture[ScriptGraphStatement] it("should enable all the Graph Attributes in DseAttributes") { - val graphAttributes = DseGraphAttributes("test", dseGraphStatement, + val graphAttributes = new DseGraphAttributes("test", dseGraphStatement, cl = Some(ConsistencyLevel.ANY), - userOrRole = Some("test_user"), - readTimeout = Some(12), - defaultTimestamp = Some(1498167845000L), idempotent = Some(true), - readCL = Some(ConsistencyLevel.LOCAL_QUORUM), - writeCL = Some(ConsistencyLevel.LOCAL_QUORUM), + node = Some(node), graphName = Some("MyGraph"), - graphLanguage = Some("english"), - graphSource = Some("mysource"), - graphInternalOptions = Some(Seq(("get", "this"))), - graphTransformResults = None + readCL = Some(readConsistencyLevel), + subProtocol = Some(subProtocol), + timeout = Some(timeout), + timestamp = Some(timestamp), + traversalSource = Some(traversalSource), + writeCL = Some(writeConsistencyLevel) ) expecting { - dseGraphStatement.buildFromSession(gatlingSession).andReturn(new SimpleGraphStatement("g.V()").success) - dseSession.executeGraphAsync(capture(statementCapture)) andReturn Futures.immediateFuture(mock[GraphResultSet]) + dseGraphStatement.buildFromSession(gatlingSession) andReturn(ScriptGraphStatement.builder("g.V()").success) + cqlSession.executeAsync(capture(statementCapture)) andReturn mockAsyncGraphResultSetFuture() } - whenExecuting(dseGraphStatement, dseSession) { + whenExecuting(dseGraphStatement, cqlSession) { getTarget(graphAttributes).sendQuery(gatlingSession) } val capturedStatement = statementCapture.getValue - capturedStatement shouldBe a[SimpleGraphStatement] + capturedStatement shouldBe a[ScriptGraphStatement] capturedStatement.getConsistencyLevel shouldBe ConsistencyLevel.ANY - capturedStatement.getDefaultTimestamp shouldBe 1498167845000L - capturedStatement.getReadTimeoutMillis shouldBe 12 capturedStatement.isIdempotent shouldBe true + capturedStatement.getNode shouldBe node capturedStatement.getGraphName shouldBe "MyGraph" - capturedStatement.getGraphLanguage shouldBe "english" - capturedStatement.getGraphReadConsistencyLevel shouldBe ConsistencyLevel.LOCAL_QUORUM - capturedStatement.getGraphWriteConsistencyLevel shouldBe ConsistencyLevel.LOCAL_QUORUM - capturedStatement.getGraphSource shouldBe "mysource" - capturedStatement.isSystemQuery shouldBe false - capturedStatement.getGraphInternalOption("get") shouldBe "this" - } - - it("should override the graph name if system") { - val graphAttributes = DseGraphAttributes( - "test", - dseGraphStatement, - graphName = Some("MyGraph"), - isSystemQuery = Some(true), - ) - - expecting { - dseGraphStatement.buildFromSession(gatlingSession).andReturn(new SimpleGraphStatement("g.V()").success) - dseSession.executeGraphAsync(capture(statementCapture)) andReturn Futures.immediateFuture(mock[GraphResultSet]) - } - - whenExecuting(dseGraphStatement, dseSession) { - getTarget(graphAttributes).sendQuery(gatlingSession) - } - - val capturedStatement = statementCapture.getValue - capturedStatement shouldBe a[SimpleGraphStatement] - capturedStatement.getGraphName shouldBe null - capturedStatement.isSystemQuery shouldBe true + capturedStatement.getReadConsistencyLevel shouldBe readConsistencyLevel + capturedStatement.getSubProtocol shouldBe subProtocol + capturedStatement.getTimeout shouldBe timeout + capturedStatement.getTimestamp shouldBe timestamp + capturedStatement.getTraversalSource shouldBe traversalSource + capturedStatement.getWriteConsistencyLevel shouldBe writeConsistencyLevel } } } diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BatchStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BatchStatementSimulation.scala index e4a7bb9..c9a1d67 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BatchStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BatchStatementSimulation.scala @@ -1,8 +1,8 @@ package com.datastax.gatling.plugin.simulations.cql -import com.datastax.driver.core.ResultSet import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.`type`.UserDefinedType import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -17,7 +17,7 @@ class BatchStatementSimulation extends BaseCqlSimulation { val cqlConfig = cql.session(session) //Initialize Gatling DSL with your session - val addressType = session.getCluster.getMetadata.getKeyspace(testKeyspace).getUserType("fullname") + val addressType:UserDefinedType = session.getMetadata.getKeyspace(testKeyspace).flatMap(_.getUserDefinedType(udt_name)).get val simpleId = 1 val preparedId = 2 @@ -42,8 +42,8 @@ class BatchStatementSimulation extends BaseCqlSimulation { val scn = scenario("BatchStatement") .feed(preparedFeed) .exec(insertPreparedCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) setUp( @@ -53,7 +53,7 @@ class BatchStatementSimulation extends BaseCqlSimulation { ) - def createTable: ResultSet = { + private def createTable = { val udt = s""" diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BoundCqlTypesSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BoundCqlTypesSimulation.scala index 89dcf8a..c742a38 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BoundCqlTypesSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/BoundCqlTypesSimulation.scala @@ -2,11 +2,14 @@ package com.datastax.gatling.plugin.simulations.cql import java.nio.ByteBuffer import java.sql.Timestamp +import java.time.Instant -import com.datastax.driver.core.utils.UUIDs -import com.datastax.driver.core.{DataType, ResultSet} import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.`type`.{DataTypes, UserDefinedType} +import com.datastax.oss.driver.api.core.cql.Row +import com.datastax.oss.driver.api.core.uuid.Uuids +import com.datastax.oss.driver.api.querybuilder.QueryBuilder import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -20,16 +23,16 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { createTable val cqlConfig = cql.session(session) - val udtType = session.getCluster.getMetadata.getKeyspace(testKeyspace).getUserType("fullname") + val addressType:UserDefinedType = session.getMetadata.getKeyspace(testKeyspace).flatMap(_.getUserDefinedType("fullname")).get - val insertFullName = udtType.newValue() + val insertFullName = addressType.newValue() .setString("firstname", "John") .setString("lastname", "Smith") - val tupleType = session.getCluster.getMetadata.newTupleType(DataType.text(), DataType.text()) + val tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.TEXT) val insertTuple = tupleType.newValue("one", "two") - val uuid = UUIDs.random() + val uuid = Uuids.random() val preparedStatementInsert = s"""INSERT INTO $testKeyspace.$table_name ( @@ -44,7 +47,10 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { |:tinyint_type, :time_type, :null_type, :udt_type, :tuple_type, :frozen_set_type, :set_string_type |)""".stripMargin - val preparedStatementSelect = s"""SELECT * FROM $testKeyspace.$table_name WHERE uuid_type = ?""" + val preparedStatementSelect = QueryBuilder.selectFrom(testKeyspace, table_name) + .all() + .whereColumn("uuid_type").isEqualTo(QueryBuilder.bindMarker()) + .build() val preparedInsert = session.prepare(preparedStatementInsert) val preparedSelect = session.prepare(preparedStatementSelect) @@ -58,7 +64,7 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { val preparedFeed = Iterator.continually( Map( "uuid_type" -> uuid, - "timeuuid_type" -> UUIDs.timeBased(), + "timeuuid_type" -> Uuids.timeBased(), "int_type" -> 1, "text_type" -> "text", "float_type" -> 4.50, @@ -110,37 +116,38 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { ) // End counter details + // A predicate function can be useful when we want to do multiple comparisons on data in a single row + private def preparedCqlPredicate(row:Row):Boolean = + row.getBoolean("boolean_type") && row.isNull("null_type") val scn = scenario("BoundCqlStatement") .feed(preparedFeed) .exec(insertPreparedCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(100.millis) .exec(selectPreparedCql .withParams(List("uuid_type")) - .check(rowCount is 1) - .check(columnValue("name") not "") - .check(columnValue("null_type") not "test") - .check(columnValue("boolean_type") is true) + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => preparedCqlPredicate(rs.one)) is true) ) .pause(100.millis) .feed(counterFeed) .exec(insertCounterPreparedCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(100.millis) .exec(selectCounterPreparedCql .withParams(List("uuid_type")) - .check(rowCount is 1) - .check(columnValue("counter_type") is 2) + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => rs.one().getLong("counter_type")) is 2L) ) .pause(100.millis) @@ -151,8 +158,7 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { global.failedRequests.count.is(0) ) - - def createTable: ResultSet = { + private def createTable = { val udt = s""" @@ -206,12 +212,11 @@ class BoundCqlTypesSimulation extends BaseCqlSimulation { } - def getRandomEpoch: Timestamp = { - val offset: Long = Timestamp.valueOf("2012-01-01 00:00:00").getTime + def getRandomEpoch: Instant = { + val offset = Timestamp.valueOf("2012-01-01 00:00:00").getTime val end = Timestamp.valueOf("2017-01-01 00:00:00").getTime val diff = end - offset + 1 - val time: Long = (offset + (Math.random() * diff)).toLong - new Timestamp(time) + val time = (offset + (Math.random() * diff)).toLong + Instant.ofEpochMilli(time) } - } diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/NamedStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/NamedStatementSimulation.scala index cf88192..9000064 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/NamedStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/NamedStatementSimulation.scala @@ -2,6 +2,7 @@ package com.datastax.gatling.plugin.simulations.cql import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.cql.Row import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -39,17 +40,20 @@ class NamedStatementSimulation extends BaseCqlSimulation { val selectCql = cql("NamedParam Select Statement") .executeNamed(preparedSelect) + private def selectCqlExtract(row:Row):String = + row.getString("str") + val scn = scenario("NamedStatement") .feed(feeder) .exec(insertCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(1.seconds) .exec(selectCql - .check(rowCount is 1) - .check(columnValue("str") is insertStr) + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => selectCqlExtract(rs.one)) is insertStr) ) diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/PreparedStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/PreparedStatementSimulation.scala index f634abd..45103e3 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/PreparedStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/PreparedStatementSimulation.scala @@ -2,6 +2,8 @@ package com.datastax.gatling.plugin.simulations.cql import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.cql.Row +import com.datastax.oss.driver.api.querybuilder.QueryBuilder import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -19,8 +21,16 @@ class PreparedStatementSimulation extends BaseCqlSimulation { val insertStr = "two" val insertName = "test" - val statementInsert = s"""INSERT INTO $testKeyspace.$table_name (id, str, name) VALUES (?, ?, ?)""" - val statementSelect = s"""SELECT * FROM $testKeyspace.$table_name WHERE id = ? AND str = ?""" + val statementInsert = QueryBuilder.insertInto(testKeyspace, table_name) + .value("id", QueryBuilder.bindMarker()) + .value("str", QueryBuilder.bindMarker()) + .value("name", QueryBuilder.bindMarker()) + .build() + val statementSelect = QueryBuilder.selectFrom(testKeyspace, table_name) + .all() + .whereColumn("id").isEqualTo(QueryBuilder.bindMarker()) + .whereColumn("str").isEqualTo(QueryBuilder.bindMarker()) + .build() val preparedInsert = session.prepare(statementInsert) val preparedSelect = session.prepare(statementSelect) @@ -36,35 +46,37 @@ class PreparedStatementSimulation extends BaseCqlSimulation { ) val insertCql = cql("Insert_Statement") - .executePrepared(preparedInsert) + .executeStatement(preparedInsert) .withParams("${id}", "${str}", "${name}") val selectCql = cql("Select_Statement") - .executePrepared(preparedSelect) + .executeStatement(preparedSelect) .withParams("${id}", "${str}") val selectCqlSessionParam = cql("Select_Statement_Array") - .executePrepared(preparedSelect) + .executeStatement(preparedSelect) .withParams(List("id", "str")) + private def selectCqlExtract(row:Row):String = + row.getString("name") val scnPassed = scenario("ABCPreparedStatement") .feed(feeder) .exec(insertCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(1.seconds) .exec(selectCql - .check(rowCount is 1) - .check(columnValue("name") is insertName) + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => selectCqlExtract(rs.one)) is insertName) ) .pause(1.seconds) .exec(selectCqlSessionParam - .check(rowCount is 1) - .check(columnValue("name") is insertName) + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => selectCqlExtract(rs.one)) is insertName) ) diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/SimpleStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/SimpleStatementSimulation.scala index a314aad..51fea11 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/SimpleStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/SimpleStatementSimulation.scala @@ -2,6 +2,9 @@ package com.datastax.gatling.plugin.simulations.cql import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.`type`.DataTypes +import com.datastax.oss.driver.api.core.cql.Row +import com.datastax.oss.driver.api.querybuilder.{QueryBuilder, SchemaBuilder} import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -11,33 +14,46 @@ class SimpleStatementSimulation extends BaseCqlSimulation { val table_name = "test_table_simple" createTestKeyspace - createTable + + val createTable = SchemaBuilder.createTable(testKeyspace, table_name) + .ifNotExists + .withPartitionKey("id",DataTypes.INT) + .withClusteringColumn("str", DataTypes.TEXT) + .build + session.execute(createTable) val dseProtocol = dseProtocolBuilder.session(session) //Initialize Gatling DSL with your session val insertId = 1 val insertStr = "one" - val simpleStatementInsert = s"""INSERT INTO $testKeyspace.$table_name (id, str) VALUES ($insertId, '$insertStr')""" - val simpleStatementSelect = s"""SELECT * FROM $testKeyspace.$table_name WHERE id = $insertId""" - - val insertCql = cql("Insert_Statement") - .executeCql(simpleStatementInsert) + val simpleStatementInsert = QueryBuilder.insertInto(testKeyspace, table_name) + .value("id", QueryBuilder.literal(insertId)) + .value("str", QueryBuilder.literal(insertStr)) + .build() + val simpleStatementSelect = QueryBuilder.selectFrom(testKeyspace, table_name) + .all() + .whereColumn("id").isEqualTo(QueryBuilder.literal(insertId)) + .build() - val selectCql = cql("Select_Statement") - .executeCql(simpleStatementSelect) + private def selectCqlExtract(row:Row):String = + row.getString("str") val scn = scenario("SimpleStatement") - .exec(insertCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .exec( + cql("Insert_Statement") + .executeStatement(simpleStatementInsert) + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining()) is 0) // "normal" INSERTs don't return anything ) .pause(1.seconds) .group("TestGroup") { - exec(selectCql - .check(rowCount is 1) - .check(columnValue("str") is insertStr) - ) + exec( + cql("Select_Statement") + .executeStatement(simpleStatementSelect) + .check(resultSet.transform(_.getExecutionInfo.getWarnings.size).is(0)) + .check(resultSet.transform(_.remaining()) is 1) + .check(resultSet.transform(rs => selectCqlExtract(rs.one)) is insertStr)) } setUp( @@ -45,16 +61,4 @@ class SimpleStatementSimulation extends BaseCqlSimulation { ).assertions( global.failedRequests.count.is(0) ) - - def createTable = { - val table = - s""" - CREATE TABLE IF NOT EXISTS $testKeyspace.$table_name ( - id int, - str text, - PRIMARY KEY (id) - );""" - - session.execute(table) - } } diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/UdtStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/UdtStatementSimulation.scala index 1406973..87b7e36 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/cql/UdtStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/cql/UdtStatementSimulation.scala @@ -1,8 +1,10 @@ package com.datastax.gatling.plugin.simulations.cql -import com.datastax.driver.core.ResultSet import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseCqlSimulation +import com.datastax.oss.driver.api.core.`type`.UserDefinedType +import com.datastax.oss.driver.api.core.cql.Row +import com.datastax.oss.driver.api.querybuilder.QueryBuilder import io.gatling.core.Predef._ import scala.concurrent.duration.DurationInt @@ -17,7 +19,7 @@ class UdtStatementSimulation extends BaseCqlSimulation { val cqlConfig = cql.session(session) //Initialize Gatling DSL with your session - val addressType = session.getCluster.getMetadata.getKeyspace(testKeyspace).getUserType("fullname") + val addressType:UserDefinedType = session.getMetadata.getKeyspace(testKeyspace).flatMap(_.getUserDefinedType("fullname")).get val simpleId = 1 val preparedId = 2 @@ -26,15 +28,20 @@ class UdtStatementSimulation extends BaseCqlSimulation { .setString("firstname", "John") .setString("lastname", "Smith") - - val simpleStatementInsert = s"""INSERT INTO $testKeyspace.$table_name (id, name) VALUES ($simpleId, $insertFullName)""" - val simpleStatementSelect = s"""SELECT * FROM $testKeyspace.$table_name WHERE id = $simpleId""" + val simpleStatementInsert = QueryBuilder.insertInto(testKeyspace, table_name) + .value("id", QueryBuilder.literal(simpleId)) + .value("name", QueryBuilder.literal(insertFullName)) + .build() + val simpleStatementSelect = QueryBuilder.selectFrom(testKeyspace, table_name) + .all() + .whereColumn("id").isEqualTo(QueryBuilder.literal(simpleId)) + .build() val insertCql = cql("Insert Simple Statements") - .executeCql(simpleStatementInsert) + .executeStatement(simpleStatementInsert) val selectCql = cql("Select Simple Statement") - .executeCql(simpleStatementSelect) + .executeStatement(simpleStatementSelect) val preparedStatementInsert = s"""INSERT INTO $testKeyspace.$table_name (id, name) VALUES (?, ?)""" val preparedStatementSelect = s"""SELECT * FROM $testKeyspace.$table_name WHERE id = ?""" @@ -74,44 +81,47 @@ class UdtStatementSimulation extends BaseCqlSimulation { ) ) + private def extractFirstNameFromRow(row:Row):String = + row.getUdtValue("name").getString(0) + val scn = scenario("SimpleStatement") .exec(insertCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(100.millis) .exec(selectCql - .check(rowCount is 1) - .check(columnValue("name").find(0) not "") + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => extractFirstNameFromRow(rs.one)) not "") ) .pause(100.millis) .feed(preparedFeed) .exec(insertPreparedCql .withParams(List("id", "fullname")) - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(100.millis) .exec(selectPreparedCql .withParams(List("id")) - .check(rowCount is 1) - .check(columnValue("name").find(0) not "") + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => extractFirstNameFromRow(rs.one)) not "") ) .pause(100.millis) .feed(namedFeed) .exec(insertNamedCql - .check(exhausted is true) - .check(rowCount is 0) // "normal" INSERTs don't return anything + .check(resultSet.transform(_.hasMorePages) is false) + .check(resultSet.transform(_.remaining) is 0) // "normal" INSERTs don't return anything ) .pause(100.millis) .exec(selectNamedCql - .check(rowCount is 1) - .check(columnValue("name").find(0) not "") + .check(resultSet.transform(_.remaining) is 1) + .check(resultSet.transform(rs => extractFirstNameFromRow(rs.one)) not "") ) setUp( @@ -120,8 +130,7 @@ class UdtStatementSimulation extends BaseCqlSimulation { global.failedRequests.count.is(0) ) - - def createTable: ResultSet = { + private def createTable = { val udt = s""" diff --git a/src/test/scala/com/datastax/gatling/plugin/simulations/graph/GraphStatementSimulation.scala b/src/test/scala/com/datastax/gatling/plugin/simulations/graph/GraphStatementSimulation.scala index 8f2fce2..8476488 100644 --- a/src/test/scala/com/datastax/gatling/plugin/simulations/graph/GraphStatementSimulation.scala +++ b/src/test/scala/com/datastax/gatling/plugin/simulations/graph/GraphStatementSimulation.scala @@ -1,12 +1,10 @@ package com.datastax.gatling.plugin.simulations.graph -import com.datastax.driver.core.ConsistencyLevel -import com.datastax.driver.dse.graph.{GraphStatement, SimpleGraphStatement} -import com.datastax.dse.graph.api.DseGraph +import com.datastax.dse.driver.api.core.graph.{DseGraph, FluentGraphStatement, ScriptGraphStatement} import com.datastax.gatling.plugin.DsePredef._ import com.datastax.gatling.plugin.base.BaseGraphSimulation +import com.datastax.oss.driver.api.core.ConsistencyLevel import io.gatling.core.Predef._ -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal import org.scalatest.Ignore import scala.concurrent.duration.DurationInt @@ -15,42 +13,38 @@ import scala.concurrent.duration.DurationInt class GraphStatementSimulation extends BaseGraphSimulation { val table_name = "test_table" - - session.getCluster.getConfiguration.getGraphOptions.setGraphName("demo") + val graph_name = "demo" val graphConfig = graph.session(session) //Initialize Gatling DSL with your session val r = scala.util.Random - val graphStatement = new SimpleGraphStatement("g.addV(label, vertexLabel).property('type', myType)") - def getInt: String = { "test_" + r.nextInt(100).toString } + val insertStatement = ScriptGraphStatement.newInstance("g.addV(label, vertexLabel).property('type', myType)") val insertGraph = graph("Graph Statement") - .executeGraphStatement(graphStatement) - .withSetParams(Array("vertexLabel", "myType")) - .consistencyLevel(ConsistencyLevel.LOCAL_ONE) + .executeGraph(insertStatement) + .withParams("vertexLabel", "myType") + .withConsistencyLevel(ConsistencyLevel.LOCAL_ONE) + .withTraversalSource("g") + .withName(graph_name) val queryGraph = graph("Graph Query") .executeGraph("g.V().limit(5)") + .withTraversalSource("g") + .withName(graph_name) - val g = DseGraph.traversal(session) - val t: GraphTraversal[_,_] = g.V().limit(5) - val st: GraphStatement = DseGraph.statementFromTraversal(t) - + val queryStatement: FluentGraphStatement = FluentGraphStatement.newInstance(DseGraph.g.V().limit(5)) val queryGraphNative = graph("Graph Fluent") - .executeGraphFluent(st) - - val queryGraphFeederTraversal = graph("Graph Feeder") - .executeGraphFeederTraversal("traversal") + .executeGraphFluent(queryStatement) + .withName(graph_name) val feeder = Iterator.continually( Map[String, Any]( "vertexLabel" -> getInt, - "myType" -> r.nextInt(100), - "traversal" -> t + "myType" -> r.nextInt(100) ) ) @@ -60,12 +54,10 @@ class GraphStatementSimulation extends BaseGraphSimulation { .pause(1.seconds) .exec(queryGraph - .check(rowCount greaterThan 1) + .check(graphResultSet.transform(_.remaining) greaterThan 1) ) .pause(1.seconds) .exec(queryGraphNative) - .pause(1.seconds) - .exec(queryGraphFeederTraversal) .exec(session => { // println(session("test").asOption[String].toString) session diff --git a/src/test/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtilSpec.scala b/src/test/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtilSpec.scala index d373c2c..23435c9 100644 --- a/src/test/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtilSpec.scala +++ b/src/test/scala/com/datastax/gatling/plugin/utils/CqlPreparedStatementUtilSpec.scala @@ -3,12 +3,16 @@ package com.datastax.gatling.plugin.utils import java.math.BigInteger import java.net.InetAddress import java.nio.ByteBuffer +import java.time.{Instant, LocalDate, LocalTime} +import java.util.Optional -import com.datastax.driver.core.utils.UUIDs -import com.datastax.driver.core.{DataType, _} -import com.datastax.driver.dse.geometry._ +import com.datastax.dse.driver.api.core.data.geometry._ import com.datastax.gatling.plugin.base.BaseCassandraServerSpec import com.datastax.gatling.plugin.exceptions.CqlTypeException +import com.datastax.oss.driver.api.core.`type`.{DataTypes, UserDefinedType} +import com.datastax.oss.driver.api.core.cql.BoundStatement +import com.datastax.oss.driver.api.core.data.{TupleValue, UdtValue} +import com.datastax.oss.driver.api.core.uuid.Uuids import com.github.nscala_time.time.Imports.DateTime import io.gatling.core.session.Session @@ -39,12 +43,13 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { "floatStr" -> "12.4", "double" -> 12.0, "epoch" -> 1483299340813L, + "epochInstant" -> Instant.ofEpochMilli(1483299340813L), "number" -> 12.asInstanceOf[Number], "inetStr" -> "127.0.0.1", "inet" -> InetAddress.getByName("127.0.0.1"), - "localDate" -> LocalDate.fromMillisSinceEpoch(1483299340813L), + "localDate" -> CqlPreparedStatementUtil.toLocalDate(1483299340813L), "stringDate" -> "2016-10-05", "set" -> Set(1), @@ -68,12 +73,13 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { "javaNumber" -> 12.asInstanceOf[Number], "javaDate" -> new java.util.Date(), + "javaInstant" -> Instant.now(), "isoDateString" -> "2008-03-01T13:00:00Z", "dateString" -> "2016-10-05", - "uuid" -> UUIDs.random(), + "uuid" -> Uuids.random(), "uuidString" -> "252a3806-b8be-42d3-929d-4cbb380a433e", - "timeUuid" -> UUIDs.timeBased(), + "timeUuid" -> Uuids.timeBased(), "byte" -> 12.toByte, "short" -> 12.toShort, @@ -88,9 +94,9 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { "null_type" -> null, - "point_type" -> new Point(1.0, 1.0), - "linestring_type" -> new LineString(new Point(1.0, 1.0), new Point(2.0, 2.0)), - "polygon_type" -> new Polygon(new Point(1.0, 1.0), new Point(2.0, 2.0), new Point(3.0, 3.0), new Point(4.0, 4.0)) + "point_type" -> Point.fromCoordinates(1.0, 1.0), + "linestring_type" -> LineString.fromPoints(Point.fromCoordinates(1.0, 1.0), Point.fromCoordinates(2.0, 2.0)), + "polygon_type" -> Polygon.fromPoints(Point.fromCoordinates(1.0, 1.0), Point.fromCoordinates(2.0, 2.0), Point.fromCoordinates(3.0, 3.0), Point.fromCoordinates(4.0, 4.0)) ) val defaultGatlingSession: Session = gatlingSession.setAll(defaultSessionVars) @@ -105,24 +111,22 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { it("should return a list of types") { - val preparedStatement = dseSession.prepare(s"SELECT * FROM $keyspace.$table where id = ?") + val preparedStatement = cqlSession.prepare(s"SELECT * FROM $keyspace.$table where id = ?") val paramList = CqlPreparedStatementUtil.getParamsList(preparedStatement) - paramList should contain(DataType.Name.INT) + paramList should contain(DataTypes.INT) } - } describe("getParamsMap") { it("should return a map of types") { - val preparedStatement = dseSession.prepare(s"SELECT * FROM $keyspace.$table where id = :id") + val preparedStatement = cqlSession.prepare(s"SELECT * FROM $keyspace.$table where id = :id") val paramsMap = CqlPreparedStatementUtil.getParamsMap(preparedStatement) - paramsMap("id") shouldBe DataType.Name.INT + paramsMap("id") shouldBe DataTypes.INT } - } } @@ -387,28 +391,28 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { describe("asDate") { it("should accept a date string") { - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "stringDate") shouldBe a[LocalDate] - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "stringDate").getDay.equals(5) + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "stringDate") shouldBe a[LocalDate] + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "stringDate").getDayOfMonth.equals(5) } it("should accept a long") { - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "long") shouldBe a[LocalDate] - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "long").getDay.equals(1) + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "long") shouldBe a[LocalDate] + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "long").getDayOfMonth.equals(1) } it("should accept an int") { - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "int") shouldBe a[LocalDate] - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "int").getDay.equals(13) + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "int") shouldBe a[LocalDate] + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "int").getDayOfMonth.equals(13) } it("should accept an native localDate") { - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "localDate") shouldBe a[LocalDate] - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "localDate").getDay.equals(1) + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "localDate") shouldBe a[LocalDate] + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "localDate").getDayOfMonth.equals(1) } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asDate(defaultGatlingSession, "float") shouldBe a[LocalDate] + CqlPreparedStatementUtil.asLocalDate(defaultGatlingSession, "float") shouldBe a[LocalDate] } } @@ -418,83 +422,78 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { describe("asSet") { it("should accept a scala set") { - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "set") shouldBe a[java.util.Set[_]] - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "set") shouldBe + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "set", classOf[Int]) shouldBe a[java.util.Set[_]] + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "set", classOf[Int]) shouldBe defaultSessionVars("set").asInstanceOf[Set[Int]].asJava } it("should accept a java set") { - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "setJava") shouldBe a[java.util.Set[_]] - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "setJava") shouldBe + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "setJava", classOf[Int]) shouldBe a[java.util.Set[_]] + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "setJava", classOf[Int]) shouldBe defaultSessionVars("set").asInstanceOf[Set[Int]].asJava } it("should accept a scala seq of ints") { - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seq") shouldBe a[java.util.Set[_]] - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seq") shouldBe + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seq", classOf[Int]) shouldBe a[java.util.Set[_]] + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seq", classOf[Int]) shouldBe defaultSessionVars("set").asInstanceOf[Set[Int]].asJava } it("should accept a scala seq of strings") { - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seqString") shouldBe a[java.util.Set[_]] - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seqString") shouldBe + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seqString", classOf[String]) shouldBe a[java.util.Set[_]] + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "seqString", classOf[String]) shouldBe defaultSessionVars("setString").asInstanceOf[Set[String]].asJava } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asSet(defaultGatlingSession, "float") shouldBe a[java.util.Set[_]] + CqlPreparedStatementUtil.asSet(defaultGatlingSession, "float", classOf[Float]) shouldBe a[java.util.Set[_]] } } - } - describe("asList") { it("should accept a scala set") { - CqlPreparedStatementUtil.asList(defaultGatlingSession, "list") shouldBe a[java.util.List[_]] - CqlPreparedStatementUtil.asList(defaultGatlingSession, "list") shouldBe + CqlPreparedStatementUtil.asList(defaultGatlingSession, "list", classOf[Int]) shouldBe a[java.util.List[_]] + CqlPreparedStatementUtil.asList(defaultGatlingSession, "list", classOf[Int]) shouldBe defaultSessionVars("list").asInstanceOf[List[Int]].asJava } it("should accept a java set") { - CqlPreparedStatementUtil.asList(defaultGatlingSession, "listJava") shouldBe a[java.util.List[_]] + CqlPreparedStatementUtil.asList(defaultGatlingSession, "listJava", classOf[Int]) shouldBe a[java.util.List[_]] } it("should accept a scala seq") { - CqlPreparedStatementUtil.asList(defaultGatlingSession, "seq") shouldBe a[java.util.List[_]] - CqlPreparedStatementUtil.asList(defaultGatlingSession, "seq") shouldBe + CqlPreparedStatementUtil.asList(defaultGatlingSession, "seq", classOf[Int]) shouldBe a[java.util.List[_]] + CqlPreparedStatementUtil.asList(defaultGatlingSession, "seq", classOf[Int]) shouldBe defaultSessionVars("list").asInstanceOf[List[Int]].asJava } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asList(defaultGatlingSession, "float") shouldBe a[java.util.List[_]] + CqlPreparedStatementUtil.asList(defaultGatlingSession, "float", classOf[Float]) shouldBe a[java.util.List[_]] } } - } - describe("asMap") { - it("should accept a scala set") { - CqlPreparedStatementUtil.asMap(defaultGatlingSession, "map") shouldBe a[java.util.Map[_, _]] - CqlPreparedStatementUtil.asMap(defaultGatlingSession, "map") shouldBe + it("should accept a scala map") { + CqlPreparedStatementUtil.asMap(defaultGatlingSession, "map", classOf[Int], classOf[Int]) shouldBe a[java.util.Map[_,_]] + CqlPreparedStatementUtil.asMap(defaultGatlingSession, "map", classOf[Int], classOf[Int]) shouldBe defaultSessionVars("map").asInstanceOf[Map[Int, Int]].asJava } - it("should accept a java set") { - CqlPreparedStatementUtil.asMap(defaultGatlingSession, "mapJava") shouldBe a[java.util.Map[_, _]] + it("should accept a java map") { + CqlPreparedStatementUtil.asMap(defaultGatlingSession, "mapJava", classOf[Int], classOf[Int]) shouldBe a[java.util.Map[_,_]] } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asMap(defaultGatlingSession, "float").get(1) shouldBe 1 + CqlPreparedStatementUtil.asMap(defaultGatlingSession, "float", classOf[Int], classOf[Int]).get(1) shouldBe 1 } } - } describe("asInet") { @@ -520,21 +519,21 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { describe("asTime") { it("should accept a Long") { - CqlPreparedStatementUtil.asTime(defaultGatlingSession, "long") shouldBe a[java.lang.Long] + CqlPreparedStatementUtil.asTime(defaultGatlingSession, "long") shouldBe a[LocalTime] } describe("should accept a String") { it("should accept a String time w/o nanoseconds") { val validHour = CqlPreparedStatementUtil.asTime(defaultGatlingSession, "hourTime") - validHour shouldBe a[java.lang.Long] - validHour shouldBe 3661000000000L + validHour shouldBe a[LocalTime] + validHour.toNanoOfDay shouldBe 3661000000000L } it("should accept a String time w/ nanoseconds") { val validNano = CqlPreparedStatementUtil.asTime(defaultGatlingSession, "nanoTime") - validNano shouldBe a[java.lang.Long] - validNano shouldBe 3661343000000L + validNano shouldBe a[LocalTime] + validNano.toNanoOfDay shouldBe 3661343000000L } it("should not accept invalid hour and produce a CqlTypeException") { @@ -704,38 +703,37 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { } - describe("asTimestamp") { + describe("asInstant") { it("should accept an epoch long") { - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "epoch") shouldBe a[java.util.Date] - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "epoch") shouldBe - new java.util.Date(defaultSessionVars("epoch").asInstanceOf[Long]) + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "epoch") shouldBe a[Instant] + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "epoch") shouldBe + defaultSessionVars("epochInstant") } - it("should accept a java Date") { - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "javaDate") shouldBe a[java.util.Date] - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "javaDate") shouldBe - defaultSessionVars("javaDate").asInstanceOf[java.util.Date] + it("should accept a java Instant") { + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "javaInstant") shouldBe a[Instant] + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "javaInstant") shouldBe + defaultSessionVars("javaInstant") } it("should accept a date string") { - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "dateString") shouldBe a[java.util.Date] - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "dateString") shouldBe - DateTime.parse(defaultSessionVars("dateString").asInstanceOf[String]).toDate + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "dateString") shouldBe a[Instant] + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "dateString") shouldBe + Instant.ofEpochMilli(DateTime.parse(defaultSessionVars("dateString").toString).getMillis) } it("should accept a isoDateString string") { - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "isoDateString") shouldBe a[java.util.Date] - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "isoDateString") shouldBe - DateTime.parse(defaultSessionVars("isoDateString").asInstanceOf[String]).toDate + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "isoDateString") shouldBe a[Instant] + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "isoDateString") shouldBe + Instant.ofEpochMilli(DateTime.parse(defaultSessionVars("isoDateString").toString).getMillis) } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asTimestamp(defaultGatlingSession, "float") shouldBe a[java.util.Date] + CqlPreparedStatementUtil.asInstant(defaultGatlingSession, "float") shouldBe a[Instant] } } - } describe("asByte") { @@ -770,9 +768,10 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { createType(keyspace, typeName, "firstname text, lastname text") createTable(keyspace, table, "id int, name frozen, PRIMARY KEY(id)") - val addressType = dseSession.getCluster.getMetadata.getKeyspace(keyspace).getUserType(typeName) + val addressType:Optional[UserDefinedType] = cqlSession.getMetadata.getKeyspace(keyspace).flatMap(_.getUserDefinedType(typeName)) + addressType should not be Optional.empty - val insertFullName = addressType.newValue() + val insertFullName = addressType.get.newValue() .setString("firstname", "John") .setString("lastname", "Smith") @@ -781,23 +780,19 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val udtSession: Session = gatlingSession.setAll(newSessionVars) it("should accept a UDTValue") { - CqlPreparedStatementUtil.asUdt(udtSession, "fullname") shouldBe a[UDTValue] + CqlPreparedStatementUtil.asUdt(udtSession, "fullname") shouldBe a[UdtValue] } it("should not accept a float and produce a CqlTypeException") { intercept[CqlTypeException] { - CqlPreparedStatementUtil.asUdt(udtSession, "invalid") shouldBe a[UDTValue] + CqlPreparedStatementUtil.asUdt(udtSession, "invalid") shouldBe a[UdtValue] } } - } describe("asTuple") { - val table = "tuple_test" - createTable(keyspace, table, "id int, tuple_type tuple, PRIMARY KEY (id)") - - val tupleType = dseSession.getCluster.getMetadata.newTupleType(DataType.varchar(), DataType.varchar()) + val tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.TEXT) val insertTuple = tupleType.newValue("test", "test2") val newSessionVars = Map("tuple_type" -> insertTuple, "invalid" -> "string") val tupleSession: Session = gatlingSession.setAll(newSessionVars) @@ -811,21 +806,17 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { CqlPreparedStatementUtil.asTuple(tupleSession, "invalid") shouldBe a[TupleValue] } } - } - } - describe("boundStatementFunctions") { - val typeName = "fullname2" createType(keyspace, typeName, "firstname text, lastname text") val tableName = "type_table" createTable(keyspace, tableName, "uuid_type uuid, timeuuid_type timeuuid, int_type int, text_type text, " + - "varchar_type varchar, ascii_type ascii, float_type float, double_type double, decimal_type decimal, " + + "ascii_type ascii, float_type float, double_type double, decimal_type decimal, " + "boolean_type boolean, inet_type inet, timestamp_type timestamp, bigint_type bigint, blob_type blob, " + "varint_type varint, list_type list, set_type set, map_type map, date_type date, " + "smallint_type smallint, tinyint_type tinyint, time_type time, tuple_type tuple, " + @@ -843,7 +834,6 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { |timeuuid_type, |int_type, |text_type, - |varchar_type, |ascii_type, |float_type, |double_type, @@ -866,168 +856,165 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { |null_type, |none_type) |VALUES - |(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""".stripMargin + |(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""".stripMargin - val boundStatementKeys = dseSession.prepare(preparedStatementInsert).bind() + val boundStatementKeys = cqlSession.prepare(preparedStatementInsert).bind() it("should bind with a UUID") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.UUID, "uuid", 0) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.UUID, "uuid", 0) result shouldBe a[BoundStatement] result.isSet(0) shouldBe true } it("should bind with a timeUuid") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TIMEUUID, "timeUuid", 1) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TIMEUUID, "timeUuid", 1) result shouldBe a[BoundStatement] result.isSet(1) shouldBe true } it("should bind with a int") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.INT, "int", 2) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.INT, "int", 2) result shouldBe a[BoundStatement] result.isSet(2) shouldBe true } it("should bind with a text") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TEXT, "string", 3) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TEXT, "string", 3) result shouldBe a[BoundStatement] result.isSet(3) shouldBe true } - it("should bind with a varchar") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.VARCHAR, "string", 4) - result shouldBe a[BoundStatement] - result.isSet(4) shouldBe true - } - it("should bind with a ascii") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.ASCII, "string", 5) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.ASCII, "string", 4) result shouldBe a[BoundStatement] - result.isSet(5) shouldBe true + result.isSet(4) shouldBe true } it("should bind with a float") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.FLOAT, "float", 6) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.FLOAT, "float", 5) result shouldBe a[BoundStatement] - result.isSet(6) shouldBe true + result.isSet(5) shouldBe true } it("should bind with a double") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.DOUBLE, "double", 7) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.DOUBLE, "double", 6) result shouldBe a[BoundStatement] - result.isSet(7) shouldBe true + result.isSet(6) shouldBe true } it("should bind with a decimal") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.DECIMAL, "double", 8) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.DECIMAL, "double", 7) result shouldBe a[BoundStatement] - result.isSet(8) shouldBe true + result.isSet(7) shouldBe true } it("should bind with a boolean") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.BOOLEAN, "boolean", 9) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.BOOLEAN, "boolean", 8) result shouldBe a[BoundStatement] - result.isSet(9) shouldBe true + result.isSet(8) shouldBe true } it("should bind with a inetAddress") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.INET, "inetStr", 10) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.INET, "inetStr", 9) result shouldBe a[BoundStatement] - result.isSet(10) shouldBe true + result.isSet(9) shouldBe true } it("should bind with a timestamp") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TIMESTAMP, "epoch", 11) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TIMESTAMP, "epochInstant", 10) result shouldBe a[BoundStatement] - result.isSet(11) shouldBe true + result.isSet(10) shouldBe true } it("should bind with a bigInt") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.BIGINT, "bigInteger", 12) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.BIGINT, "bigInteger", 11) result shouldBe a[BoundStatement] - result.isSet(12) shouldBe true + result.isSet(11) shouldBe true } it("should bind with a blob_type") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.BLOB, "byteArray", 13) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.BLOB, "byteArray", 12) result shouldBe a[BoundStatement] - result.isSet(13) shouldBe true + result.isSet(12) shouldBe true } it("should bind with a varint") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.VARINT, "int", 14) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.VARINT, "int", 13) result shouldBe a[BoundStatement] - result.isSet(14) shouldBe true + result.isSet(13) shouldBe true } it("should bind with a list") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.LIST, "list", 15) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.listOf(DataTypes.INT), "list", 14) result shouldBe a[BoundStatement] - result.isSet(15) shouldBe true + result.isSet(14) shouldBe true } it("should bind with a set") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.SET, "set", 16) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.setOf(DataTypes.INT), "set", 15) result shouldBe a[BoundStatement] - result.isSet(16) shouldBe true + result.isSet(15) shouldBe true } it("should bind with a map") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.MAP, "map", 17) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.mapOf(DataTypes.INT, DataTypes.INT), "map", 16) result shouldBe a[BoundStatement] - result.isSet(17) shouldBe true + result.isSet(16) shouldBe true } it("should bind with a date") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.DATE, "epoch", 18) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.DATE, "epoch", 17) result shouldBe a[BoundStatement] - result.isSet(18) shouldBe true + result.isSet(17) shouldBe true } it("should bind with a smallInt") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.SMALLINT, "int", 19) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.SMALLINT, "int", 18) result shouldBe a[BoundStatement] - result.isSet(19) shouldBe true + result.isSet(18) shouldBe true } it("should bind with a tinyint") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TINYINT, "int", 20) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TINYINT, "int", 19) result shouldBe a[BoundStatement] - result.isSet(20) shouldBe true + result.isSet(19) shouldBe true } it("should bind with a time") { - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TIME, "epoch", 21) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TIME, "epoch", 20) result shouldBe a[BoundStatement] - result.isSet(21) shouldBe true + result.isSet(20) shouldBe true } it("should bind with a tuple") { - val tupleType = dseSession.getCluster.getMetadata.newTupleType(DataType.varchar(), DataType.varchar()) + val tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.TEXT) val insertTuple = tupleType.newValue("test", "test2") val newSessionVars = Map("tuple_type" -> insertTuple, "invalid" -> "string") val tupleSession: Session = gatlingSession.setAll(newSessionVars) - val result = CqlPreparedStatementUtil.bindParamByOrder(tupleSession, boundStatementKeys, DataType.Name.TUPLE, "tuple_type", 22) + val result = CqlPreparedStatementUtil.bindParamByOrder(tupleSession, boundStatementKeys, tupleType, "tuple_type", 21) result shouldBe a[BoundStatement] - result.isSet(22) shouldBe true + result.isSet(21) shouldBe true } it("should bind with a udt") { - val addressType = dseSession.getCluster.getMetadata.getKeyspace(keyspace).getUserType("fullname2") - val insertFullName = addressType.newValue() - .setString("firstname", "John") - .setString("lastname", "Smith") + val addressType:Optional[UserDefinedType] = cqlSession.getMetadata.getKeyspace(keyspace).flatMap(_.getUserDefinedType("fullname2")) + addressType should not be Optional.empty + + val insertFullName = addressType.get.newValue() + .setString("firstname", "John") + .setString("lastname", "Smith") + val newSessionVars = Map("fullname2" -> insertFullName, "invalid" -> "string") val udtSession: Session = gatlingSession.setAll(newSessionVars) - val result = CqlPreparedStatementUtil.bindParamByOrder(udtSession, boundStatementKeys, DataType.Name.UDT, "fullname2", 23) + val result = CqlPreparedStatementUtil.bindParamByOrder(udtSession, boundStatementKeys, addressType.get, "fullname2", 22) result shouldBe a[BoundStatement] - result.isSet(23) shouldBe true + result.isSet(22) shouldBe true } @@ -1036,9 +1023,9 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val preparedStatementInsertCounter = s"""UPDATE $keyspace.$counterTableName SET counter_type = counter_type + ? WHERE uuid_type = ?""" - val boundStatementCounter = dseSession.prepare(preparedStatementInsertCounter).bind() + val boundStatementCounter = cqlSession.prepare(preparedStatementInsertCounter).bind() - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementCounter, DataType.Name.COUNTER, "int", 0) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementCounter, DataTypes.COUNTER, "int", 0) result shouldBe a[BoundStatement] result.isSet(0) shouldBe true } @@ -1047,29 +1034,33 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { boundStatementKeys.isSet("null_type") shouldBe false - val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TINYINT, "null_type", 24) + val result = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TINYINT, "null_type", 23) result shouldBe a[BoundStatement] - boundStatementKeys.isSet("null_type") shouldBe true - boundStatementKeys.isNull("null_type") shouldBe true + result.isSet("null_type") shouldBe true + result.isNull("null_type") shouldBe true } it("should not set and unset a None value") { val field = "none_type" - - CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TEXT, field, 25) shouldBe a[BoundStatement] boundStatementKeys.isSet(field) shouldBe false + val result1 = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataTypes.TEXT, field, 24) + result1 shouldBe a[BoundStatement] + result1.isSet(field) shouldBe false + val newSessionVars = Map(field -> "test") val newSession: Session = gatlingSession.setAll(newSessionVars) - CqlPreparedStatementUtil.bindParamByOrder(newSession, boundStatementKeys, DataType.Name.TEXT, field, 25) shouldBe a[BoundStatement] - boundStatementKeys.isSet(field) shouldBe true - boundStatementKeys.getString(field) shouldBe "test" + val result2 = CqlPreparedStatementUtil.bindParamByOrder(newSession, result1, DataTypes.TEXT, field, 24) + result2 shouldBe a[BoundStatement] + result2.isSet(field) shouldBe true + result2.getString(field) shouldBe "test" - CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, boundStatementKeys, DataType.Name.TEXT, field, 25) shouldBe a[BoundStatement] - boundStatementKeys.isSet(field) shouldBe false + val result3 = CqlPreparedStatementUtil.bindParamByOrder(defaultGatlingSession, result2, DataTypes.TEXT, field, 24) + result3 shouldBe a[BoundStatement] + result3.isSet(field) shouldBe false } it("should not set a missing session value") { @@ -1078,7 +1069,7 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val newSessionVars = Map("missing" -> "test") val newSession: Session = gatlingSession.setAll(newSessionVars) - CqlPreparedStatementUtil.bindParamByOrder(newSession, boundStatementKeys, DataType.Name.TEXT, field, 25) shouldBe a[BoundStatement] + CqlPreparedStatementUtil.bindParamByOrder(newSession, boundStatementKeys, DataTypes.TEXT, field, 25) shouldBe a[BoundStatement] boundStatementKeys.isSet(field) shouldBe false } } @@ -1092,7 +1083,6 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { |timeuuid_type, |int_type, |text_type, - |varchar_type, |ascii_type, |float_type, |double_type, @@ -1119,7 +1109,6 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { |:timeuuid_type, |:int_type, |:text_type, - |:varchar_type, |:ascii_type, |:float_type, |:double_type, @@ -1143,14 +1132,13 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { |:none_type |)""".stripMargin - val boundStatementNames = dseSession.prepare(preparedStatementInsert).bind() + val boundStatementNames = cqlSession.prepare(preparedStatementInsert).bind() val defaultSessionVars = Map( "uuid_type" -> java.util.UUID.randomUUID(), - "timeuuid_type" -> UUIDs.timeBased(), + "timeuuid_type" -> Uuids.timeBased(), "int_type" -> 12, "text_type" -> "string", - "varchar_type" -> "string", "ascii_type" -> "string", "float_type" -> 12.0.toFloat, "double_type" -> 12.0, @@ -1177,184 +1165,179 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { it("should bind with a UUID") { val paramName = "uuid_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.UUID, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.UUID, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a timeUuid") { val paramName = "timeuuid_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TIMEUUID, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TIMEUUID, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a int") { val paramName = "int_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.INT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.INT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a text") { val paramName = "text_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TEXT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TEXT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true - } - - it("should bind with a varchar") { - val paramName = "varchar_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.VARCHAR, paramName) - result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a ascii") { val paramName = "ascii_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.ASCII, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.ASCII, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a float") { val paramName = "float_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.FLOAT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.FLOAT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a double") { val paramName = "double_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.DOUBLE, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.DOUBLE, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a decimal") { val paramName = "decimal_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.DECIMAL, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.DECIMAL, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a boolean") { val paramName = "boolean_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.BOOLEAN, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.BOOLEAN, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a inetAddress") { val paramName = "inet_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.INET, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.INET, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a timestamp") { val paramName = "timestamp_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TIMESTAMP, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TIMESTAMP, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true - + result.isSet(paramName) shouldBe true } it("should bind with a bigInt") { val paramName = "bigint_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.BIGINT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.BIGINT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a blob_type") { val paramName = "blob_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.BLOB, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.BLOB, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a varint") { val paramName = "varint_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.VARINT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.VARINT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a list") { val paramName = "list_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.LIST, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.listOf(DataTypes.INT), paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a set") { val paramName = "set_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.SET, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.setOf(DataTypes.INT), paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a map") { val paramName = "map_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.MAP, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.mapOf(DataTypes.INT, DataTypes.INT), paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a date") { val paramName = "date_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.DATE, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.DATE, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a smallInt") { val paramName = "smallint_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.SMALLINT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.SMALLINT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a tinyint") { val paramName = "tinyint_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TINYINT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TINYINT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a time") { val paramName = "time_type" - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TIME, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TIME, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } it("should bind with a tuple") { val paramName = "tuple_type" - val tupleType = dseSession.getCluster.getMetadata.newTupleType(DataType.varchar(), DataType.varchar()) + val tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.TEXT) val insertTuple = tupleType.newValue("test", "test2") - val newSessionVars = Map("tuple_type" -> insertTuple, "invalid" -> "string") + val newSessionVars = Map(paramName -> insertTuple, "invalid" -> "string") val tupleSession: Session = gatlingSession.setAll(newSessionVars) - val result = CqlPreparedStatementUtil.bindParamByName(tupleSession, boundStatementNames, DataType.Name.TUPLE, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(tupleSession, boundStatementNames, tupleType, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } - it("should bind with a udt") { + val paramName = "udt_type" - val addressType = dseSession.getCluster.getMetadata.getKeyspace(keyspace).getUserType("fullname2") - val insertFullName = addressType.newValue() - .setString("firstname", "John") - .setString("lastname", "Smith") - val newSessionVars = Map("udt_type" -> insertFullName, "invalid" -> "string") + + val addressType:Optional[UserDefinedType] = cqlSession.getMetadata.getKeyspace(keyspace).flatMap(_.getUserDefinedType("fullname2")) + addressType should not be Optional.empty + + val insertFullName = addressType.get.newValue() + .setString("firstname", "John") + .setString("lastname", "Smith") + val newSessionVars = Map(paramName -> insertFullName, "invalid" -> "string") val udtSession: Session = gatlingSession.setAll(newSessionVars) - val result = CqlPreparedStatementUtil.bindParamByName(udtSession, boundStatementNames, DataType.Name.UDT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(udtSession, boundStatementNames, addressType.get, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true + result.isSet(paramName) shouldBe true } @@ -1363,9 +1346,9 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val preparedStatementInsertCounter = s"""UPDATE $keyspace.$counterTableName SET counter_type = counter_type + :counter_type WHERE uuid_type = :uuid_type""" - val boundNamedStatementCounter = dseSession.prepare(preparedStatementInsertCounter).bind() + val boundNamedStatementCounter = cqlSession.prepare(preparedStatementInsertCounter).bind() - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundNamedStatementCounter, DataType.Name.COUNTER, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundNamedStatementCounter, DataTypes.COUNTER, paramName) result shouldBe a[BoundStatement] } @@ -1374,32 +1357,33 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val paramName = "null_type" boundStatementNames.isSet(paramName) shouldBe false - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TINYINT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TINYINT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true - boundStatementNames.isNull(paramName) shouldBe true + result.isSet(paramName) shouldBe true + result.isNull(paramName) shouldBe true } it("should not set and unset a None value") { val paramName = "none_type" + boundStatementNames.isSet(paramName) shouldBe false - val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TEXT, paramName) + val result = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataTypes.TEXT, paramName) result shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe false + result.isSet(paramName) shouldBe false val newSessionVars = Map(paramName -> "test") val newSession: Session = gatlingSession.setAll(newSessionVars) - val result2 = CqlPreparedStatementUtil.bindParamByName(newSession, boundStatementNames, DataType.Name.TEXT, paramName) + val result2 = CqlPreparedStatementUtil.bindParamByName(newSession, result, DataTypes.TEXT, paramName) result2 shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe true - boundStatementNames.getString(paramName) shouldBe "test" + result2.isSet(paramName) shouldBe true + result2.getString(paramName) shouldBe "test" - val result3 = CqlPreparedStatementUtil.bindParamByName(typeSession, boundStatementNames, DataType.Name.TEXT, paramName) + val result3 = CqlPreparedStatementUtil.bindParamByName(typeSession, result2, DataTypes.TEXT, paramName) result3 shouldBe a[BoundStatement] - boundStatementNames.isSet(paramName) shouldBe false + result3.isSet(paramName) shouldBe false } it("should not set a missing session value") { @@ -1408,11 +1392,10 @@ class CqlPreparedStatementUtilSpec extends BaseCassandraServerSpec { val newSessionVars = Map("missing" -> "test") val newSession: Session = gatlingSession.setAll(newSessionVars) - val result = CqlPreparedStatementUtil.bindParamByName(newSession, boundStatementNames, DataType.Name.TEXT, field) + val result = CqlPreparedStatementUtil.bindParamByName(newSession, boundStatementNames, DataTypes.TEXT, field) result shouldBe a[BoundStatement] - boundStatementNames.isSet(field) shouldBe false + result.isSet(field) shouldBe false } - } } }