From b1c13e207ba6651b3351fcf0bc214551f3323632 Mon Sep 17 00:00:00 2001 From: Donovan Levinson Date: Thu, 10 Mar 2016 14:46:31 -0800 Subject: [PATCH 1/5] Added support for relational operators (less than, less than or equal, greater than, greater than or equal) in prepared statements. Added new tests for prepared statements with relational operators using new test table 'Clicks'. --- .../phantom/builder/ops/QueryColumn.scala | 24 +++ .../PreparedRelationalOperatorQueryTest.scala | 178 ++++++++++++++++++ .../websudos/phantom/db/TestDatabase.scala | 3 +- .../com/websudos/phantom/tables/Clicks.scala | 53 ++++++ .../phantom/tables/TestDatabase.scala | 2 + 5 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala create mode 100644 phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala diff --git a/phantom-dsl/src/main/scala/com/websudos/phantom/builder/ops/QueryColumn.scala b/phantom-dsl/src/main/scala/com/websudos/phantom/builder/ops/QueryColumn.scala index fb106cdd0..36107b261 100644 --- a/phantom-dsl/src/main/scala/com/websudos/phantom/builder/ops/QueryColumn.scala +++ b/phantom-dsl/src/main/scala/com/websudos/phantom/builder/ops/QueryColumn.scala @@ -145,6 +145,30 @@ sealed class QueryColumn[RR : Primitive](val col: AbstractColumn[RR]) { final def eqs(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = { new PreparedWhereClause.ParametricCondition[RR](QueryBuilder.Where.eqs(col.name, value.symbol)) } + + final def lt(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = { + new PreparedWhereClause.ParametricCondition[RR](QueryBuilder.Where.lt(col.name, value.symbol)) + } + + final def <(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = lt(value) + + final def lte(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = { + new PreparedWhereClause.ParametricCondition[RR](QueryBuilder.Where.lte(col.name, value.symbol)) + } + + final def <=(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = lte(value) + + final def gt(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = { + new PreparedWhereClause.ParametricCondition[RR](QueryBuilder.Where.gt(col.name, value.symbol)) + } + + final def >(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = gt(value) + + final def gte(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = { + new PreparedWhereClause.ParametricCondition[RR](QueryBuilder.Where.gte(col.name, value.symbol)) + } + + final def >=(value: PrepareMark): PreparedWhereClause.ParametricCondition[RR] = gte(value) } /** diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala new file mode 100644 index 000000000..1023f5ac8 --- /dev/null +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala @@ -0,0 +1,178 @@ +/* + * Copyright 2013-2016 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.builder.query.prepared + +import com.websudos.phantom.PhantomSuite +import com.websudos.phantom.dsl._ +import com.websudos.phantom.tables._ +import com.websudos.util.testing._ +import org.slf4j.LoggerFactory + +import scala.concurrent.Future + +class PreparedRelationalOperatorQueryTest extends PhantomSuite { + + val logger = LoggerFactory.getLogger(this.getClass) + + val url = "index.html" + + val clicks = for (timestamp <- 1 to 100) yield { + Click(url, timestamp) + } + + override def beforeAll(): Unit = { + super.beforeAll() + + database.clicks.insertSchema() + + val insertFutures = clicks.map { click => + database.clicks.store(click).future() + } + + val futureInserts = Future.sequence(insertFutures) + + futureInserts.successful { inserts => + logger.debug(s"Added ${inserts.size} clicks") + } + } + + it should "support prepared statement with less than operator" in { + val max: Long = 50 + + val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp < ?).prepare() + + val futureResults = query.bind(url, max).fetch() + + whenReady(futureResults) { results => + results.foreach { result => + result.url shouldEqual url + result.timestamp should be < max + } + + val lessThanClicks = clicks.filter(_.timestamp < max) + + lessThanClicks.foreach { click => + results should contain (click) + } + } + } + + it should "support prepared statement with less than or equal operator" in { + val max: Long = 40 + + val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp <= ?).prepare() + + val futureResults = query.bind(url, max).fetch() + + futureResults.successful { results => + results.foreach { result => + result.url shouldEqual url + result.timestamp should be <= max + } + + val lessThanOrEqualClicks = clicks.filter(_.timestamp <= max) + + lessThanOrEqualClicks.foreach { recipe => + results should contain (recipe) + } + } + } + + it should "support prepared statement with greater than operator" in { + val min: Long = 60 + + val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp > ?).prepare() + + val futureResults = query.bind(url, min).fetch() + + futureResults.successful { results => + results.foreach { result => + result.url shouldEqual url + result.timestamp should be > min + } + + val greaterThanClicks = clicks.filter(_.timestamp > min) + + greaterThanClicks.foreach { click => + results should contain (click) + } + } + } + + it should "support prepared statement with greater than or equal operator" in { + val min: Long = 75 + + val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp >= ?).prepare() + + val futureResults = query.bind(url, min).fetch() + + futureResults.successful { results => + results.foreach { result => + result.url shouldEqual url + result.timestamp should be >= min + } + + val greaterThanOrEqualClicks = clicks.filter(_.timestamp >= min) + + greaterThanOrEqualClicks.foreach { click => + results should contain (click) + } + } + } + + it should "support prepared statement with less than and greater than operators" in { + val min: Long = 10 + val max: Long = 40 + + val query = database.clicks.select + .p_where(_.url eqs ?) + .p_and(_.timestamp > ?) + .p_and(_.timestamp < ?) + .prepare() + + val operation = for { + select <- query.bind(url, min, max).fetch() + } yield select + + operation.successful { results => + results.foreach { result => + result.url shouldEqual url + result.timestamp should be > min + result.timestamp should be < max + } + + val rangeClicks = clicks.filter(r => r.timestamp > min && r.timestamp < max) + + rangeClicks.foreach { click => + results should contain (click) + } + } + } +} diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala index 50275a407..0138e0edc 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala @@ -30,7 +30,7 @@ package com.websudos.phantom.db import com.websudos.phantom.connectors.ContactPoint -import com.websudos.phantom.tables.{Recipes, JsonTable, EnumTable, BasicTable} +import com.websudos.phantom.tables._ private[this] object DefaultKeyspace { @@ -50,4 +50,5 @@ class ValueInitDatabase extends DatabaseImpl(DefaultKeyspace.local) { val enumTable = new EnumTable with connector.Connector val jsonTable = new JsonTable with connector.Connector val recipes = new Recipes with connector.Connector + val clicks = new Clicks with connector.Connector } \ No newline at end of file diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala new file mode 100644 index 000000000..3bfd95a3b --- /dev/null +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.tables + +import com.websudos.phantom.builder.query.InsertQuery +import com.websudos.phantom.dsl._ + +case class Click(url: String, timestamp: Long) + +class Clicks extends CassandraTable[ConcreteClicks, Click] { + + object url extends StringColumn(this) with PartitionKey[String] + + object timestamp extends LongColumn(this) with PrimaryKey[Long] with ClusteringOrder[Long] with Descending + + def fromRow(r: Row): Click = Click(url(r), timestamp(r)) +} + +abstract class ConcreteClicks extends Clicks with RootConnector { + + def store(click: Click): InsertQuery.Default[ConcreteClicks, Click] = { + insert + .value(_.url, click.url) + .value(_.timestamp, click.timestamp) + } +} diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala index f145a6800..b96212648 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala @@ -51,6 +51,8 @@ class TestDatabase(override val connector: KeySpaceDef) extends DatabaseImpl(con object byteBufferTable extends ConcreteByteBufferTable with connector.Connector + object clicks extends ConcreteClicks with connector.Connector + object counterTableTest extends ConcreteCounterTableTest with connector.Connector object secondaryCounterTable extends ConcreteSecondaryCounterTable with connector.Connector object brokenCounterCounterTable extends ConcreteBrokenCounterTableTest with connector.Connector From 7b4ea8c4deb195e8d98db980ae4e108274013612 Mon Sep 17 00:00:00 2001 From: Flavian Alexandru Date: Mon, 14 Mar 2016 11:00:17 +0000 Subject: [PATCH 2/5] Adding modules for JDK8 date API support. --- .../phantom/jdk8/DefaultJava8Primitives.scala | 104 ++++++++++++++++++ .../websudos/phantom/jdk8/dsl/package.scala | 48 ++++++++ .../phantom/jdk8/Jdk8TimeColumnsTest.scala | 74 +++++++++++++ .../jdk8/tables/ConcretePrimitivesJdk8.scala | 77 +++++++++++++ .../jdk8/tables/OptionalPrimitivesJdk8.scala | 77 +++++++++++++ .../phantom/jdk8/tables/TestDatabase.scala | 44 ++++++++ .../phantom/jdk8/tables/package.scala | 60 ++++++++++ project/Build.scala | 41 +++++-- 8 files changed, 518 insertions(+), 7 deletions(-) create mode 100644 phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/DefaultJava8Primitives.scala create mode 100644 phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/dsl/package.scala create mode 100644 phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/Jdk8TimeColumnsTest.scala create mode 100644 phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/ConcretePrimitivesJdk8.scala create mode 100644 phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/OptionalPrimitivesJdk8.scala create mode 100644 phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/TestDatabase.scala create mode 100644 phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/package.scala diff --git a/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/DefaultJava8Primitives.scala b/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/DefaultJava8Primitives.scala new file mode 100644 index 000000000..791aca59c --- /dev/null +++ b/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/DefaultJava8Primitives.scala @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8 + +import java.time._ +import java.util.Date + +import com.datastax.driver.core.Row +import com.websudos.phantom.builder.primitives.Primitive +import com.websudos.phantom.builder.query.CQLQuery +import com.websudos.phantom.builder.syntax.CQLSyntax +import com.websudos.phantom.jdk8.dsl.{JdkLocalDate, OffsetDateTime, ZonedDateTime} + +import scala.util.Try + +trait DefaultJava8Primitives { + + implicit object OffsetDateTimeIsPrimitive extends Primitive[OffsetDateTime] { + + override type PrimitiveType = java.util.Date + + val cassandraType = CQLSyntax.Types.Timestamp + + override def asCql(value: OffsetDateTime): String = { + value.toInstant.toEpochMilli.toString + } + + override def fromRow(column: String, row: Row): Try[OffsetDateTime] = nullCheck(column, row) { + r => OffsetDateTime.ofInstant(r.getTimestamp(column).toInstant, ZoneOffset.UTC) + } + + override def fromString(value: String): OffsetDateTime = OffsetDateTime.parse(value) + + override def clz: Class[Date] = classOf[Date] + } + + implicit object ZonedDateTimeIsPrimitive extends Primitive[ZonedDateTime] { + + override type PrimitiveType = java.util.Date + + val cassandraType = CQLSyntax.Types.Timestamp + + override def asCql(value: ZonedDateTime): String = { + value.toInstant.toEpochMilli.toString + } + + override def fromRow(column: String, row: Row): Try[ZonedDateTime] = nullCheck(column, row) { + r => ZonedDateTime.ofInstant(r.getTimestamp(column).toInstant, ZoneOffset.UTC) + } + + override def fromString(value: String): ZonedDateTime = ZonedDateTime.parse(value) + + override def clz: Class[Date] = classOf[Date] + } + + implicit object JdkLocalDateIsPrimitive extends Primitive[JdkLocalDate] { + + override type PrimitiveType = com.datastax.driver.core.LocalDate + + val cassandraType = CQLSyntax.Types.Date + + override def asCql(value: JdkLocalDate): String = { + CQLQuery.empty.singleQuote(value.toString) + } + + override def fromRow(column: String, row: Row): Try[JdkLocalDate] = nullCheck(column, row) { + r => LocalDate.ofEpochDay(r.getDate(column).getDaysSinceEpoch) + } + + override def fromString(value: String): JdkLocalDate = { + Instant.ofEpochMilli(value.toLong).atOffset(ZoneOffset.UTC).toLocalDate + } + + override def clz: Class[com.datastax.driver.core.LocalDate] = classOf[com.datastax.driver.core.LocalDate] + } + +} diff --git a/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/dsl/package.scala b/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/dsl/package.scala new file mode 100644 index 000000000..25af8de8b --- /dev/null +++ b/phantom-jdk8/src/main/scala/com/websudos/phantom/jdk8/dsl/package.scala @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8 + +import com.websudos.phantom.dsl.CassandraTable + +package object dsl extends DefaultJava8Primitives { + + type OffsetDateTimeColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.PrimitiveColumn[Owner, Record, OffsetDateTime] + type ZonedDateTimeColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.PrimitiveColumn[Owner, Record, ZonedDateTime] + type JdkLocalDateColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.PrimitiveColumn[Owner, Record, JdkLocalDate] + + type OptionalOffsetDateTimeColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.OptionalPrimitiveColumn[Owner, Record, OffsetDateTime] + type OptionalZonedDateTimeColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.OptionalPrimitiveColumn[Owner, Record, ZonedDateTime] + type OptionalJdkLocalDateColumn[Owner <: CassandraTable[Owner, Record], Record] = com.websudos.phantom.column.OptionalPrimitiveColumn[Owner, Record, JdkLocalDate] + + type OffsetDateTime = java.time.OffsetDateTime + type ZonedDateTime = java.time.ZonedDateTime + type JdkLocalDate = java.time.LocalDate + +} diff --git a/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/Jdk8TimeColumnsTest.scala b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/Jdk8TimeColumnsTest.scala new file mode 100644 index 000000000..83ecef912 --- /dev/null +++ b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/Jdk8TimeColumnsTest.scala @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8 + +import com.websudos.phantom.PhantomSuite +import com.websudos.phantom.dsl._ +import com.websudos.phantom.jdk8.tables.{Jdk8Row, TestDatabase, _} +import com.websudos.util.testing._ + +class Jdk8TimeColumnsTest extends PhantomSuite { + + override def beforeAll(): Unit = { + super.beforeAll() + if (session.v4orNewer) { + TestDatabase.primitivesJdk8.insertSchema() + TestDatabase.optionalPrimitivesJdk8.insertSchema() + } + } + + if (session.v4orNewer) { + it should "correctly insert and extract java.time columns" in { + val row = gen[Jdk8Row] + + val chain = for { + store <- TestDatabase.primitivesJdk8.store(row).future() + select <- TestDatabase.primitivesJdk8.select.where(_.pkey eqs row.pkey).one() + } yield select + + chain successful { + res => res.value shouldEqual row + } + } + + it should "correctly insert and extract optional java.time columns" in { + val row = gen[OptionalJdk8Row] + + val chain = for { + store <- TestDatabase.optionalPrimitivesJdk8.store(row).future() + select <- TestDatabase.optionalPrimitivesJdk8.select.where(_.pkey eqs row.pkey).one() + } yield select + + chain successful { + res => res.value shouldEqual row + } + } + } +} diff --git a/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/ConcretePrimitivesJdk8.scala b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/ConcretePrimitivesJdk8.scala new file mode 100644 index 000000000..daee6821f --- /dev/null +++ b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/ConcretePrimitivesJdk8.scala @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8.tables + +import java.time.{LocalDate, OffsetDateTime} + +import com.websudos.phantom.CassandraTable +import com.websudos.phantom.builder.query.InsertQuery +import com.websudos.phantom.dsl._ +import com.websudos.phantom.jdk8.dsl._ + +case class Jdk8Row( + pkey: String, + offsetDateTime: OffsetDateTime, + zonedDateTime: ZonedDateTime, + localDate: LocalDate +) + +sealed class PrimitivesJdk8 extends CassandraTable[ConcretePrimitivesJdk8, Jdk8Row] { + + object pkey extends StringColumn(this) with PartitionKey[String] + + object offsetDateTime extends OffsetDateTimeColumn(this) + + object zonedDateTime extends ZonedDateTimeColumn(this) + + object localDate extends JdkLocalDateColumn(this) + + override def fromRow(r: Row): Jdk8Row = { + Jdk8Row( + pkey = pkey(r), + offsetDateTime = offsetDateTime(r), + zonedDateTime = zonedDateTime(r), + localDate = localDate(r) + ) + } +} + +abstract class ConcretePrimitivesJdk8 extends PrimitivesJdk8 with RootConnector { + + def store(primitive: Jdk8Row): InsertQuery.Default[ConcretePrimitivesJdk8, Jdk8Row] = { + insert.value(_.pkey, primitive.pkey) + .value(_.offsetDateTime, primitive.offsetDateTime) + .value(_.zonedDateTime, primitive.zonedDateTime) + .value(_.localDate, primitive.localDate) + } + + override val tableName = "PrimitivesJdk8" + +} \ No newline at end of file diff --git a/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/OptionalPrimitivesJdk8.scala b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/OptionalPrimitivesJdk8.scala new file mode 100644 index 000000000..b8a0c76df --- /dev/null +++ b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/OptionalPrimitivesJdk8.scala @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8.tables + +import java.time.{LocalDate, OffsetDateTime} + +import com.websudos.phantom.CassandraTable +import com.websudos.phantom.builder.query.InsertQuery +import com.websudos.phantom.dsl._ +import com.websudos.phantom.jdk8.dsl._ + +case class OptionalJdk8Row( + pkey: String, + offsetDateTime: Option[OffsetDateTime], + zonedDateTime: Option[ZonedDateTime], + localDate: Option[LocalDate] +) + +sealed class OptionalPrimitivesJdk8 extends CassandraTable[ConcreteOptionalPrimitivesJdk8, OptionalJdk8Row] { + + object pkey extends StringColumn(this) with PartitionKey[String] + + object offsetDateTime extends OptionalOffsetDateTimeColumn(this) + + object zonedDateTime extends OptionalZonedDateTimeColumn(this) + + object localDate extends OptionalJdkLocalDateColumn(this) + + override def fromRow(r: Row): OptionalJdk8Row = { + OptionalJdk8Row( + pkey = pkey(r), + offsetDateTime = offsetDateTime(r), + zonedDateTime = zonedDateTime(r), + localDate = localDate(r) + ) + } +} + +abstract class ConcreteOptionalPrimitivesJdk8 extends OptionalPrimitivesJdk8 with RootConnector { + + def store(primitive: OptionalJdk8Row): InsertQuery.Default[ConcreteOptionalPrimitivesJdk8, OptionalJdk8Row] = { + insert.value(_.pkey, primitive.pkey) + .value(_.offsetDateTime, primitive.offsetDateTime) + .value(_.zonedDateTime, primitive.zonedDateTime) + .value(_.localDate, primitive.localDate) + } + + override val tableName = "OptionalPrimitivesJdk8" + +} \ No newline at end of file diff --git a/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/TestDatabase.scala b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/TestDatabase.scala new file mode 100644 index 000000000..a34a2d427 --- /dev/null +++ b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/TestDatabase.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8.tables + +import com.websudos.phantom.connectors.{ContactPoint, KeySpaceDef} +import com.websudos.phantom.db.DatabaseImpl + + +class TestDatabase(override val connector: KeySpaceDef) extends DatabaseImpl(connector) { + + object primitivesJdk8 extends ConcretePrimitivesJdk8 with connector.Connector + + object optionalPrimitivesJdk8 extends ConcreteOptionalPrimitivesJdk8 with connector.Connector + +} + +object TestDatabase extends TestDatabase(ContactPoint.local.keySpace("phantom")) \ No newline at end of file diff --git a/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/package.scala b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/package.scala new file mode 100644 index 000000000..e8e850d7f --- /dev/null +++ b/phantom-jdk8/src/test/scala/com/websudos/phantom/jdk8/tables/package.scala @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2015 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.jdk8 + +import java.time.{LocalDate, OffsetDateTime, ZoneOffset, ZonedDateTime} + +import com.websudos.util.testing.{Sample, _} + +package object tables { + + implicit object Jdk8RowSampler extends Sample[Jdk8Row] { + def sample: Jdk8Row = { + Jdk8Row( + gen[String], + OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(gen[Long]), + ZonedDateTime.now(ZoneOffset.UTC).plusSeconds(gen[Long]), + LocalDate.now().plusDays(gen[Long]) + ) + } + } + + implicit object OptionalJdk8RowSampler extends Sample[OptionalJdk8Row] { + def sample: OptionalJdk8Row = { + OptionalJdk8Row( + gen[String], + Some(OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(gen[Long])), + Some(ZonedDateTime.now(ZoneOffset.UTC).plusSeconds(gen[Long])), + Some(LocalDate.now().plusDays(gen[Long])) + ) + } + } + +} diff --git a/project/Build.scala b/project/Build.scala index 50fff0265..be702ed52 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -152,6 +152,24 @@ object Build extends Build { parallelExecution in ThisBuild := false ) ++ net.virtualvoid.sbt.graph.Plugin.graphSettings ++ VersionManagement.newSettings ++ PublishTasks.bintrayPublishSettings + + private[this] def isJdk8: Boolean = sys.props("java.specification.version") == "1.8" + + private[this] def addOnCondition(condition: Boolean, projectReference: ProjectReference): Seq[ProjectReference] = + if (condition) projectReference :: Nil else Nil + + lazy val baseProjectList: Seq[ProjectReference] = Seq( + phantomDsl, + phantomExample, + phantomConnectors, + phantomReactiveStreams, + phantomThrift, + phantomUdt, + phantomZookeeper + ) + + lazy val fullProjectList = baseProjectList ++ addOnCondition(isJdk8, phantomJdk8) + lazy val phantom = Project( id = "phantom", base = file("."), @@ -163,13 +181,7 @@ object Build extends Build { ).settings( name := "phantom" ).aggregate( - phantomDsl, - phantomExample, - phantomConnectors, - phantomReactiveStreams, - phantomThrift, - phantomUdt, - phantomZookeeper + fullProjectList: _* ) lazy val phantomDsl = Project( @@ -209,6 +221,21 @@ object Build extends Build { phantomConnectors ) + lazy val phantomJdk8 = Project( + id = "phantom-jdk8", + base = file("phantom-jdk8"), + settings = Defaults.coreDefaultSettings ++ sharedSettings + ).settings( + name := "phantom-jdk8", + testOptions in Test += Tests.Argument("-oF"), + logBuffered in Test := false, + concurrentRestrictions in Test := Seq( + Tags.limit(Tags.ForkedTestGroup, 4) + ) + ).dependsOn( + phantomDsl % "compile->compile;test->test" + ) + lazy val phantomConnectors = Project( id = "phantom-connectors", base = file("phantom-connectors"), From f0260a705da135a7c9c2123ba72d785745b902eb Mon Sep 17 00:00:00 2001 From: Flavian Alexandru Date: Mon, 14 Mar 2016 10:46:23 +0000 Subject: [PATCH 3/5] Fixing credentials problem. --- README.md | 2 +- build/publish_develop.sh | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a96107080..d2e9a7d6b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -phantom [![Build Status](https://travis-ci.org/outworkers/phantom.svg?branch=develop)](https://travis-ci.org/outworkers/phantom) [![Coverage Status](https://coveralls.io/repos/outworkers/phantom/badge.svg)](https://coveralls.io/r/outworkers/phantom) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.websudos/phantom_2.10/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.websudos/phantom_2.10) [![Bintray](https://api.bintray.com/packages/websudos/oss-releases/phantom/images/download.svg) ](https://bintray.com/websudos/oss-releases/phantom/_latestVersion) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/outworkers/phantom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +phantom [![Build Status](https://travis-ci.org/outworkers/phantom.svg?branch=develop)](https://travis-ci.org/outworkers/phantom) [![Coverage Status](https://coveralls.io/repos/outworkers/phantom/badge.svg)](https://coveralls.io/r/outworkers/phantom) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.websudos/phantom_2.10/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.websudos/phantom_2.10) [![Bintray](https://api.bintray.com/packages/websudos/oss-releases/phantom/images/download.svg) ](https://bintray.com/websudos/oss-releases/phantom/_latestVersion) [![Codacy Rating](https://api.codacy.com/project/badge/grade/25bee222a7d142ff8151e6ceb39151b4)](https://www.codacy.com/app/flavian/phantom_2) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/outworkers/phantom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ================================================================================================== Reactive type-safe Scala DSL for Cassandra diff --git a/build/publish_develop.sh b/build/publish_develop.sh index 1ce1cf338..ce3ebdfb5 100755 --- a/build/publish_develop.sh +++ b/build/publish_develop.sh @@ -5,6 +5,24 @@ then echo "The current JDK version is ${TRAVIS_JDK_VERSION}" echo "The current Scala version is ${TRAVIS_SCALA_VERSION}" + echo "Creating credentials file" + if [ -e "$HOME/.bintray/.credentials" ]; then + echo "Bintray redentials file already exists" + else + touch "$HOME/.bintray/.credentials" + echo "realm = Bintray API Realm" >> "$HOME/.bintray/.credentials" + echo "host = api.bintray.com" >> "$HOME/.bintray/.credentials" + echo "user = $bintray_user" >> "$HOME/.bintray/.credentials" + echo "password = $bintray_password" >> "$HOME/.bintray/.credentials" + fi + + if [ -e "$HOME/.bintray/.credentials" ]; then + echo "Bintray credentials file succesfully created" + else + echo "Bintray credentials still not found" + fi + + CURRENT_VERSION = "$(sbt version)" echo "Bumping release version with a patch increment from $CURRENT_VERSION" sbt version-bump-patch From 404a60560b6fabd8bfc5afbd08ff5eb25765a750 Mon Sep 17 00:00:00 2001 From: Flavian Alexandru Date: Mon, 14 Mar 2016 12:53:32 +0000 Subject: [PATCH 4/5] Updating publish script. --- build/publish_develop.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/publish_develop.sh b/build/publish_develop.sh index ce3ebdfb5..bd4248c18 100755 --- a/build/publish_develop.sh +++ b/build/publish_develop.sh @@ -9,6 +9,7 @@ then if [ -e "$HOME/.bintray/.credentials" ]; then echo "Bintray redentials file already exists" else + mkdir -p "$HOME/.bintray/" touch "$HOME/.bintray/.credentials" echo "realm = Bintray API Realm" >> "$HOME/.bintray/.credentials" echo "host = api.bintray.com" >> "$HOME/.bintray/.credentials" @@ -23,12 +24,11 @@ then fi - CURRENT_VERSION = "$(sbt version)" - echo "Bumping release version with a patch increment from $CURRENT_VERSION" + echo "Bumping release version with a patch increment from $(sbt version)" sbt version-bump-patch - NEW_VERSION = "$(sbt version)" - echo "Creating Git tag for version $NEW_VERSION" + SET NEW_VERSION = "$(sbt version)" + echo "Creating Git tag for version $(sbt version)" echo "Pushing tag to GitHub." git push --tags "https://${github_token}@${GH_REF}" > /dev/null 2>&1 @@ -37,8 +37,8 @@ then if [ "${TRAVIS_SCALA_VERSION}" == "2.11.7" ] && [ "${TRAVIS_JDK_VERSION}" == "oraclejdk8" ]; then - "Publishing $NEW_VERSION to bintray" - sbt bintray:publish + "Publishing $(sbt version) to bintray" + sbt +publish else echo "Only publishing version for Scala 2.11.7 and Oracle JDK 8 to prevent multiple artifacts" fi @@ -46,7 +46,7 @@ then git checkout master git merge develop - git push --all "https://${github_token}@${GH_REF}" master > /dev/null 2>&1 + git push --all "https://${github_token}@${GH_REF}" develop:master > /dev/null 2>&1 else From 40f01bf48fd7e906fac00d6dd61358bcd6636c0b Mon Sep 17 00:00:00 2001 From: Donovan Levinson Date: Thu, 10 Mar 2016 14:46:31 -0800 Subject: [PATCH 5/5] Added test for prepared statement with constant TTL to TTLTest. Added test for prepared statement with constant limit to TimeSeriesTest, and refactored test file to avoid duplication. Added a RelationalOperatorsTest file with queries using relational operators with Scala and Twitter Futures. Merged in tests from PreparedRelationalOperatorQueryTest into RelationalOperatorsTest, using the timeSeriesTable. Removed the clicks table since it was no longer needed. --- .../db/crud/RelationalOperatorsTest.scala | 283 ++++++++++++++++ .../builder/query/db/crud/TTLTest.scala | 98 ++++-- .../query/db/ordering/TimeSeriesTest.scala | 315 ++++++++---------- .../PreparedRelationalOperatorQueryTest.scala | 178 ---------- .../websudos/phantom/db/TestDatabase.scala | 1 - .../com/websudos/phantom/tables/Clicks.scala | 53 --- .../phantom/tables/TestDatabase.scala | 2 - 7 files changed, 500 insertions(+), 430 deletions(-) create mode 100644 phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/RelationalOperatorsTest.scala delete mode 100644 phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala delete mode 100644 phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/RelationalOperatorsTest.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/RelationalOperatorsTest.scala new file mode 100644 index 000000000..a4fedf450 --- /dev/null +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/RelationalOperatorsTest.scala @@ -0,0 +1,283 @@ +/* + * Copyright 2013-2016 Websudos, Limited. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.websudos.phantom.builder.query.db.crud + +import com.twitter.util.{Future => TwitterFuture} +import com.websudos.phantom.PhantomSuite +import com.websudos.phantom.builder.query.db.ordering.TimeSeriesTest +import com.websudos.phantom.builder.query.prepared._ +import com.websudos.phantom.dsl._ +import com.websudos.phantom.tables._ +import com.websudos.util.testing._ +import org.slf4j.LoggerFactory +import scala.concurrent.{Future => ScalaFuture} + +class RelationalOperatorsTest extends PhantomSuite { + val logger = LoggerFactory.getLogger(this.getClass) + + val numRecords = 100 + val records: Seq[TimeSeriesRecord] = TimeSeriesTest.genSequentialRecords(numRecords) + + override def beforeAll(): Unit = { + super.beforeAll() + + TestDatabase.timeSeriesTable.insertSchema() + + val chain = for { + truncate <- TestDatabase.timeSeriesTable.truncate.future() + inserts <- TimeSeriesTest.addRecordsToBatch(records).future() + } yield inserts + + chain.successful { inserts => + logger.debug(s"Initialized table with $numRecords records") + } + } + + it should "fetch records using less than operator" in { + val maxIndex = 50 + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp < maxTimestamp) + .allowFiltering() + .fetch() + + val expected = records.filter(_.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than operator with Twitter Futures" in { + val maxIndex = 50 + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp < maxTimestamp) + .allowFiltering() + .collect() + + val expected = records.filter(_.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than operator with prepared statement" in { + val maxIndex = 50 + val maxTimestamp = records(maxIndex).timestamp + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.timestamp < ?) + .allowFiltering() + .prepare() + + val futureResults = query.bind(maxTimestamp).fetch() + val expected = records.filter(_.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than or equal operator" in { + val maxIndex = 40 + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp <= maxTimestamp) + .allowFiltering() + .fetch() + + val expected = records.filter(!_.timestamp.isAfter(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than or equal operator with Twitter Futures" in { + val maxIndex = 40 + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp <= maxTimestamp) + .allowFiltering() + .collect() + + val expected = records.filter(!_.timestamp.isAfter(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than or equal operator with prepared statement" in { + val maxIndex = 40 + val maxTimestamp = records(maxIndex).timestamp + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.timestamp <= ?) + .allowFiltering() + .prepare() + + val futureResults = query.bind(maxTimestamp).fetch() + val expected = records.filter(!_.timestamp.isAfter(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than operator" in { + val minIndex = 60 + val minTimestamp = records(minIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp > minTimestamp) + .allowFiltering() + .fetch() + + val expected = records.filter(_.timestamp.isAfter(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than operator with Twitter Futures" in { + val minIndex = 60 + val minTimestamp = records(minIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp > minTimestamp) + .allowFiltering() + .collect() + + val expected = records.filter(_.timestamp.isAfter(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than operator with prepared statement" in { + val minIndex = 60 + val minTimestamp = records(minIndex).timestamp + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.timestamp > ?) + .allowFiltering() + .prepare() + + val futureResults = query.bind(minTimestamp).fetch() + val expected = records.filter(_.timestamp.isAfter(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than or equal operator" in { + val minIndex = 75 + val minTimestamp = records(minIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp >= minTimestamp) + .allowFiltering() + .fetch() + + val expected = records.filter(!_.timestamp.isBefore(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than or equal operator with Twitter Futures" in { + val minIndex = 75 + val minTimestamp = records(minIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp >= minTimestamp) + .allowFiltering() + .collect() + + val expected = records.filter(!_.timestamp.isBefore(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using greater than or equal operator with prepared statement" in { + val minIndex = 75 + val minTimestamp = records(minIndex).timestamp + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.timestamp >= ?) + .allowFiltering() + .prepare() + + val futureResults = query.bind(minTimestamp).fetch() + val expected = records.filter(!_.timestamp.isBefore(minTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than and greater than operators" in { + val minIndex = 10 + val maxIndex = 40 + val minTimestamp = records(minIndex).timestamp + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp > minTimestamp) + .and(_.timestamp < maxTimestamp) + .allowFiltering() + .fetch() + + val expected = records.filter(r => r.timestamp.isAfter(minTimestamp) && r.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than and greater than operators with Twitter Futures" in { + val minIndex = 10 + val maxIndex = 40 + val minTimestamp = records(minIndex).timestamp + val maxTimestamp = records(maxIndex).timestamp + + val futureResults = TestDatabase.timeSeriesTable.select + .where(_.timestamp > minTimestamp) + .and(_.timestamp < maxTimestamp) + .allowFiltering() + .collect() + + val expected = records.filter(r => r.timestamp.isAfter(minTimestamp) && r.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + it should "fetch records using less than and greater than operators with prepared statement" in { + val minIndex = 10 + val maxIndex = 40 + val minTimestamp = records(minIndex).timestamp + val maxTimestamp = records(maxIndex).timestamp + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.timestamp > ?) + .p_and(_.timestamp < ?) + .allowFiltering() + .prepare() + + val futureResults = query.bind(minTimestamp, maxTimestamp).fetch() + val expected = records.filter(r => r.timestamp.isAfter(minTimestamp) && r.timestamp.isBefore(maxTimestamp)) + verifyResults(futureResults, expected) + } + + def verifyResults(futureResults: ScalaFuture[Seq[TimeSeriesRecord]], expected: Seq[TimeSeriesRecord]): Unit = { + futureResults.successful { results => + results.toSet shouldEqual expected.toSet + } + } + + def verifyResults(futureResults: TwitterFuture[Seq[TimeSeriesRecord]], expected: Seq[TimeSeriesRecord]): Unit = { + futureResults.successful { results => + results.toSet shouldEqual expected.toSet + } + } +} diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/TTLTest.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/TTLTest.scala index 119d7c41d..b638b7f89 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/TTLTest.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/crud/TTLTest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 Websudos, Limited. + * Copyright 2013-2016 Websudos, Limited. * * All rights reserved. * @@ -30,12 +30,13 @@ package com.websudos.phantom.builder.query.db.crud import com.websudos.phantom.PhantomSuite +import com.websudos.phantom.builder.query.prepared._ import com.websudos.phantom.dsl._ import com.websudos.phantom.tables.{TestDatabase, Primitive} import com.websudos.util.testing._ import org.scalatest.concurrent.Eventually import org.scalatest.time.{Milliseconds, Seconds, Span} -import org.scalatest.time.SpanSugar._ +import scala.concurrent.duration._ class TTLTest extends PhantomSuite with Eventually { @@ -46,42 +47,99 @@ class TTLTest extends PhantomSuite with Eventually { TestDatabase.primitives.insertSchema() } - it should "expire inserted records after 2 seconds" in { + private val ttl = 2 seconds + private val granularity = 1 second + + it should "expire inserted records after TTL" in { val row = gen[Primitive] val chain = for { - store <- TestDatabase.primitives.store(row).ttl(2).future() + store <- TestDatabase.primitives.store(row).ttl(ttl).future() get <- TestDatabase.primitives.select.where(_.pkey eqs row.pkey).one() } yield get - whenReady(chain) { - record => { - record.value shouldEqual row + chain.successful { record => + record shouldEqual Some(row) + } - eventually { - val record = TestDatabase.primitives.select.where(_.pkey eqs row.pkey).one().block(3.seconds) - record shouldBe empty - } + eventually(timeout(ttl + granularity)) { + val futureRecord = TestDatabase.primitives.select.where(_.pkey eqs row.pkey).one() + futureRecord.successful { record => + record shouldBe empty } } } - it should "expire inserted records after 2 seconds with Twitter Futures" in { + it should "expire inserted records after TTL with Twitter Futures" in { val row = gen[Primitive] val chain = for { - store <- TestDatabase.primitives.store(row).ttl(2).execute() + store <- TestDatabase.primitives.store(row).ttl(ttl).execute() get <- TestDatabase.primitives.select.where(_.pkey eqs row.pkey).get() } yield get - chain.successful { - record => { - record.value shouldEqual row + chain.successful { record => + record shouldEqual Some(row) + } + + eventually(timeout(ttl + granularity)) { + val futureRecord = TestDatabase.primitives.select.where(_.pkey eqs row.pkey).get() + futureRecord.successful { record => + record shouldBe empty + } + } + } + + it should "expire inserted records after TTL with prepared statement" in { + val row = gen[Primitive] + + val fetchQuery = TestDatabase.primitives.select + .p_where(_.pkey eqs ?) + .prepare() + + val insertQuery = TestDatabase.primitives.insert + .p_value(_.pkey, ?) + .p_value(_.long, ?) + .p_value(_.boolean, ?) + .p_value(_.bDecimal, ?) + .p_value(_.double, ?) + .p_value(_.float, ?) + .p_value(_.inet, ?) + .p_value(_.int, ?) + .p_value(_.date, ?) + .p_value(_.uuid, ?) + .p_value(_.bi, ?) + .ttl(ttl) + .prepare() + + def preparedInsert(row: Primitive): ExecutablePreparedQuery = { + insertQuery.bind( + row.pkey, + row.long, + row.boolean, + row.bDecimal, + row.double, + row.float, + row.inet, + row.int, + row.date, + row.uuid, + row.bi) + } + + val chain = for { + store <- preparedInsert(row).future() + get <- fetchQuery.bind(row.pkey).one() + } yield get + + chain.successful { result => + result shouldEqual Some(row) + } - eventually { - val record = TestDatabase.primitives.select.where(_.pkey eqs row.pkey).one().block(3.seconds) - record shouldBe empty - } + eventually(timeout(ttl + granularity)) { + val futureResults = fetchQuery.bind(row.pkey).one() + futureResults.successful { results => + results.isEmpty shouldBe true } } } diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/ordering/TimeSeriesTest.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/ordering/TimeSeriesTest.scala index 3faa865fd..ce3925011 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/ordering/TimeSeriesTest.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/db/ordering/TimeSeriesTest.scala @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 Websudos, Limited. + * Copyright 2013-2016 Websudos, Limited. * * All rights reserved. * @@ -29,15 +29,20 @@ */ package com.websudos.phantom.builder.query.db.ordering +import com.datastax.driver.core.Session +import com.twitter.util.{Future => TwitterFuture} import com.websudos.phantom.PhantomSuite - -import scala.concurrent.duration._ - -import org.scalatest.concurrent.PatienceConfiguration - +import com.websudos.phantom.batch.BatchQuery +import com.websudos.phantom.builder.Unspecified +import com.websudos.phantom.builder.query.prepared._ +import com.websudos.phantom.connectors.KeySpace import com.websudos.phantom.dsl._ import com.websudos.phantom.tables._ import com.websudos.util.testing._ +import org.scalatest.concurrent.PatienceConfiguration +import scala.concurrent.duration._ +import scala.concurrent.{Future => ScalaFuture} +import TimeSeriesTest._ class TimeSeriesTest extends PhantomSuite { @@ -48,234 +53,192 @@ class TimeSeriesTest extends PhantomSuite { TestDatabase.timeSeriesTable.insertSchema() } - protected[this] final val durationOffset = 1000 + it should "fetch records in natural order for a descending clustering order" in { + val number = 10 + val limit = 5 - it should "allow using naturally fetch the records in descending order for a descending clustering order" in { + val records = genSequentialRecords(number) - var i = 0 - val number = 5 + val chain = for { + truncate <- TestDatabase.timeSeriesTable.truncate.future() + insert <- addRecordsToBatch(records).future() + chunks <- TestDatabase.timeSeriesTable.select.limit(limit).fetch() + } yield chunks - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset, i) - ) - }) + verifyResults(chain, records.reverse.take(limit)) + } - val ts = recordList.map(_.timestamp.getSecondOfDay) + it should "fetch records in natural order for a descending clustering order with Twitter Futures" in { + val number = 10 + val limit = 5 - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => { - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } - } + val records = genSequentialRecords(number) val chain = for { - truncate <- TestDatabase.timeSeriesTable.truncate.future() - insert <- batch.future() - chunks <- TestDatabase.timeSeriesTable.select.limit(number).fetch() + truncate <- TestDatabase.timeSeriesTable.truncate.execute() + insert <- addRecordsToBatch(records).execute() + chunks <- TestDatabase.timeSeriesTable.select.limit(limit).collect() } yield chunks - - chain.successful { - res => - val mapped = res.map(_.timestamp.getSecondOfDay) - mapped.toList shouldEqual ts.reverse - } + verifyResults(chain, records.reverse.take(limit)) } - it should "allow using naturally fetch the records in descending order for a descending clustering order with Twitter Futures" in { - var i = 0 - val number = 5 - - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset, i) - ) - }) + it should "fetch records in natural order for a descending clustering order with prepared statements" in { + val number = 10 + val limit = 5 - val ts = recordList.map(_.timestamp.getSecondOfDay) + val records = genSequentialRecords(number) - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => { - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } - } + val query = TestDatabase.timeSeriesTable.select + .p_where(_.id eqs ?) + .limit(limit) + .prepare() val chain = for { - truncate <- TestDatabase.timeSeriesTable.truncate.execute() - insert <- batch.execute() - chunks <- TestDatabase.timeSeriesTable.select.limit(number).collect() + truncate <- TestDatabase.timeSeriesTable.truncate.future() + insert <- addRecordsToBatch(records).future() + chunks <- query.bind(TestDatabase.timeSeriesTable.testUUID).fetch() } yield chunks - chain.successful { - res => - val mapped = res.map(_.timestamp.getSecondOfDay) - mapped.toList shouldEqual ts.reverse - } + verifyResults(chain, records.reverse.take(limit)) } - it should "allow fetching the records in ascending order for a descending clustering order using order by clause" in { - var i = 0 - val number = 5 + it should "fetch records in ascending order for a descending clustering order using order by clause" in { + val number = 10 + val limit = 5 - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset / 2, i) - ) - }) + val records = genSequentialRecords(number) - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => { - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } - } val chain = for { truncate <- TestDatabase.timeSeriesTable.truncate.future() - insert <- batch.future() + insert <- addRecordsToBatch(records).future() chunks <- { - TestDatabase.timeSeriesTable.select.where(_.id eqs TestDatabase.timeSeriesTable.testUUID) + TestDatabase.timeSeriesTable.select + .where(_.id eqs TestDatabase.timeSeriesTable.testUUID) .orderBy(_.timestamp.asc) - .limit(number) + .limit(limit) .fetch() } } yield chunks - chain.successful { - res => - val ts = recordList.map(_.timestamp.getSecondOfDay) - - res.map(_.timestamp.getSecondOfDay) shouldEqual ts - } + verifyResults(chain, records.take(limit)) } - it should "allow fetching the records in ascending order for a descending clustering order using order by clause with Twitter Futures" in { - var i = 0 - val number = 5 + it should "fetch records in ascending order for a descending clustering order using order by clause with Twitter Futures" in { + val number = 10 + val limit = 5 - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset, i) - ) - }) + val records = genSequentialRecords(number) - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => { - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } - } val chain = for { truncate <- TestDatabase.timeSeriesTable.truncate.execute() - insert <- batch.execute() - chunks <- TestDatabase.timeSeriesTable - .select + insert <- addRecordsToBatch(records).execute() + chunks <- TestDatabase.timeSeriesTable.select .where(_.id eqs TestDatabase.timeSeriesTable.testUUID) - .orderBy(_.timestamp.asc).limit(number) + .orderBy(_.timestamp.asc) + .limit(limit) .collect() } yield chunks - chain.successful { - res => - val ts = recordList.map(_.timestamp.getSecondOfDay) - res.map(_.timestamp.getSecondOfDay) shouldEqual ts - } + verifyResults(chain, records.take(limit)) } - it should "allow fetching the records in descending order for a descending clustering order using order by clause" in { - var i = 0 - val number = 5 - - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset / 2, i) - ) - }) - - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } + it should "fetch records in ascending order for a descending clustering order using prepared statements" in { + val number = 10 + val limit = 5 + + val records = genSequentialRecords(number) + + val query = TestDatabase.timeSeriesTable.select + .p_where(_.id eqs ?) + .orderBy(_.timestamp.asc) + .limit(limit) + .prepare() + + val chain = for { + truncate <- TestDatabase.timeSeriesTable.truncate.future() + insert <- addRecordsToBatch(records).future() + chunks <- query.bind(TestDatabase.timeSeriesTable.testUUID).fetch() + } yield chunks + + verifyResults(chain, records.take(limit)) + } + + it should "fetch records in descending order for a descending clustering order using order by clause" in { + val number = 10 + val limit = 5 + + val records = genSequentialRecords(number) + val chain = for { truncate <- TestDatabase.timeSeriesTable.truncate.future() - insert <- batch.future() - chunks <- TestDatabase.timeSeriesTable - .select + insert <- addRecordsToBatch(records).future() + chunks <- TestDatabase.timeSeriesTable.select .where(_.id eqs TestDatabase.timeSeriesTable.testUUID) .orderBy(_.timestamp.descending) - .limit(number) + .limit(limit) .fetch() } yield chunks - chain.successful { - res => - val ts = recordList.map(_.timestamp.getSecondOfDay) - res.map(_.timestamp.getSecondOfDay) shouldEqual ts.reverse - } + verifyResults(chain, records.reverse.take(limit)) } - it should "allow fetching the records in descending order for a descending clustering order using order by clause with Twitter Futures" in { - var i = 0 - val number = 5 - - val recordList = genList[TimeSeriesRecord](number).map( - item => { - i += 1 - item.copy( - id = TestDatabase.timeSeriesTable.testUUID, - timestamp = item.timestamp.withDurationAdded(durationOffset, i) - ) - }) - - val batch = recordList.foldLeft(Batch.unlogged) { - (b, record) => - b.add(TestDatabase.timeSeriesTable.insert - .value(_.id, record.id) - .value(_.name, record.name) - .value(_.timestamp, record.timestamp)) - } + it should "fetch records in descending order for a descending clustering order using order by clause with Twitter Futures" in { + val number = 10 + val limit = 5 + + val records = genSequentialRecords(number) + val chain = for { truncate <- TestDatabase.timeSeriesTable.truncate.execute() - insert <- batch.execute() + insert <- addRecordsToBatch(records).execute() chunks <- TestDatabase.timeSeriesTable.select .where(_.id eqs TestDatabase.timeSeriesTable.testUUID) - .orderBy(_.timestamp.desc).limit(number) + .orderBy(_.timestamp.desc) + .limit(limit) .collect() } yield chunks - chain.successful { - res => - val ts = recordList.map(_.timestamp.getSecondOfDay) - res.map(_.timestamp.getSecondOfDay) shouldEqual ts.reverse + verifyResults(chain, records.reverse.take(limit)) + } + + def verifyResults(futureResults: ScalaFuture[Seq[TimeSeriesRecord]], expected: Seq[TimeSeriesRecord]): Unit = { + futureResults.successful { results => + results shouldEqual expected } } + def verifyResults(futureResults: TwitterFuture[Seq[TimeSeriesRecord]], expected: Seq[TimeSeriesRecord]): Unit = { + futureResults.successful { results => + results shouldEqual expected + } + } +} + +object TimeSeriesTest { + def genSequentialRecords(number: Int): Seq[TimeSeriesRecord] = { + val durationOffset = 1000 + + (1 to number).map { i => + val record = gen[TimeSeriesRecord] + record.copy( + id = TestDatabase.timeSeriesTable.testUUID, + timestamp = record.timestamp.withDurationAdded(durationOffset, i)) + } + } + + def addRecordsToBatch( + records: Seq[TimeSeriesRecord])( + implicit space: KeySpace, + session: Session): BatchQuery[Unspecified] = { + + records.foldLeft(Batch.unlogged) { + (b, record) => { + b.add(TestDatabase.timeSeriesTable.insert + .value(_.id, record.id) + .value(_.name, record.name) + .value(_.timestamp, record.timestamp)) + } + } + } } diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala deleted file mode 100644 index 1023f5ac8..000000000 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/builder/query/prepared/PreparedRelationalOperatorQueryTest.scala +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2013-2016 Websudos, Limited. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.websudos.phantom.builder.query.prepared - -import com.websudos.phantom.PhantomSuite -import com.websudos.phantom.dsl._ -import com.websudos.phantom.tables._ -import com.websudos.util.testing._ -import org.slf4j.LoggerFactory - -import scala.concurrent.Future - -class PreparedRelationalOperatorQueryTest extends PhantomSuite { - - val logger = LoggerFactory.getLogger(this.getClass) - - val url = "index.html" - - val clicks = for (timestamp <- 1 to 100) yield { - Click(url, timestamp) - } - - override def beforeAll(): Unit = { - super.beforeAll() - - database.clicks.insertSchema() - - val insertFutures = clicks.map { click => - database.clicks.store(click).future() - } - - val futureInserts = Future.sequence(insertFutures) - - futureInserts.successful { inserts => - logger.debug(s"Added ${inserts.size} clicks") - } - } - - it should "support prepared statement with less than operator" in { - val max: Long = 50 - - val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp < ?).prepare() - - val futureResults = query.bind(url, max).fetch() - - whenReady(futureResults) { results => - results.foreach { result => - result.url shouldEqual url - result.timestamp should be < max - } - - val lessThanClicks = clicks.filter(_.timestamp < max) - - lessThanClicks.foreach { click => - results should contain (click) - } - } - } - - it should "support prepared statement with less than or equal operator" in { - val max: Long = 40 - - val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp <= ?).prepare() - - val futureResults = query.bind(url, max).fetch() - - futureResults.successful { results => - results.foreach { result => - result.url shouldEqual url - result.timestamp should be <= max - } - - val lessThanOrEqualClicks = clicks.filter(_.timestamp <= max) - - lessThanOrEqualClicks.foreach { recipe => - results should contain (recipe) - } - } - } - - it should "support prepared statement with greater than operator" in { - val min: Long = 60 - - val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp > ?).prepare() - - val futureResults = query.bind(url, min).fetch() - - futureResults.successful { results => - results.foreach { result => - result.url shouldEqual url - result.timestamp should be > min - } - - val greaterThanClicks = clicks.filter(_.timestamp > min) - - greaterThanClicks.foreach { click => - results should contain (click) - } - } - } - - it should "support prepared statement with greater than or equal operator" in { - val min: Long = 75 - - val query = database.clicks.select.p_where(_.url eqs ?).p_and(_.timestamp >= ?).prepare() - - val futureResults = query.bind(url, min).fetch() - - futureResults.successful { results => - results.foreach { result => - result.url shouldEqual url - result.timestamp should be >= min - } - - val greaterThanOrEqualClicks = clicks.filter(_.timestamp >= min) - - greaterThanOrEqualClicks.foreach { click => - results should contain (click) - } - } - } - - it should "support prepared statement with less than and greater than operators" in { - val min: Long = 10 - val max: Long = 40 - - val query = database.clicks.select - .p_where(_.url eqs ?) - .p_and(_.timestamp > ?) - .p_and(_.timestamp < ?) - .prepare() - - val operation = for { - select <- query.bind(url, min, max).fetch() - } yield select - - operation.successful { results => - results.foreach { result => - result.url shouldEqual url - result.timestamp should be > min - result.timestamp should be < max - } - - val rangeClicks = clicks.filter(r => r.timestamp > min && r.timestamp < max) - - rangeClicks.foreach { click => - results should contain (click) - } - } - } -} diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala index 0138e0edc..2d5dd152d 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/db/TestDatabase.scala @@ -50,5 +50,4 @@ class ValueInitDatabase extends DatabaseImpl(DefaultKeyspace.local) { val enumTable = new EnumTable with connector.Connector val jsonTable = new JsonTable with connector.Connector val recipes = new Recipes with connector.Connector - val clicks = new Clicks with connector.Connector } \ No newline at end of file diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala deleted file mode 100644 index 3bfd95a3b..000000000 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/Clicks.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2015 Websudos, Limited. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Explicit consent must be obtained from the copyright owner, Websudos Limited before any redistribution is made. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.websudos.phantom.tables - -import com.websudos.phantom.builder.query.InsertQuery -import com.websudos.phantom.dsl._ - -case class Click(url: String, timestamp: Long) - -class Clicks extends CassandraTable[ConcreteClicks, Click] { - - object url extends StringColumn(this) with PartitionKey[String] - - object timestamp extends LongColumn(this) with PrimaryKey[Long] with ClusteringOrder[Long] with Descending - - def fromRow(r: Row): Click = Click(url(r), timestamp(r)) -} - -abstract class ConcreteClicks extends Clicks with RootConnector { - - def store(click: Click): InsertQuery.Default[ConcreteClicks, Click] = { - insert - .value(_.url, click.url) - .value(_.timestamp, click.timestamp) - } -} diff --git a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala index b96212648..f145a6800 100644 --- a/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala +++ b/phantom-dsl/src/test/scala/com/websudos/phantom/tables/TestDatabase.scala @@ -51,8 +51,6 @@ class TestDatabase(override val connector: KeySpaceDef) extends DatabaseImpl(con object byteBufferTable extends ConcreteByteBufferTable with connector.Connector - object clicks extends ConcreteClicks with connector.Connector - object counterTableTest extends ConcreteCounterTableTest with connector.Connector object secondaryCounterTable extends ConcreteSecondaryCounterTable with connector.Connector object brokenCounterCounterTable extends ConcreteBrokenCounterTableTest with connector.Connector