Skip to content

Commit

Permalink
Merge 903e45b into d7443f0
Browse files Browse the repository at this point in the history
  • Loading branch information
alexflav23 committed Apr 2, 2019
2 parents d7443f0 + 903e45b commit 450813a
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 26 deletions.
Expand Up @@ -121,6 +121,7 @@ object UpdateClause extends Clause {

object OperatorClause extends Clause {
class Condition(override val qb: CQLQuery) extends QueryCondition[HNil](qb, Nil)
class Prepared[T](override val qb: CQLQuery) extends QueryCondition[T :: HNil](qb, Nil)
}

object TypedClause extends Clause {
Expand Down
Expand Up @@ -23,6 +23,7 @@ import com.outworkers.phantom.builder.clauses.OperatorClause.Condition
import com.outworkers.phantom.builder.clauses.{OperatorClause, TypedClause, WhereClause}
import com.outworkers.phantom.builder.primitives.Primitive
import com.outworkers.phantom.builder.query.engine.CQLQuery
import com.outworkers.phantom.builder.query.prepared.PrepareMark
import com.outworkers.phantom.builder.syntax.CQLSyntax
import com.outworkers.phantom.column.{AbstractColumn, Column, TimeUUIDColumn}
import com.outworkers.phantom.connectors.SessionAugmenterImplicits
Expand Down Expand Up @@ -157,6 +158,10 @@ private[phantom] trait TimeUUIDOperator {
new Condition(fn(CQLQuery.escape(new DateTime(date).toString())))
}

def apply(mark: PrepareMark): OperatorClause.Prepared[Long] = {
new OperatorClause.Prepared[Long](fn(mark.qb.queryString))
}

def fn: String => CQLQuery
}

Expand Down
Expand Up @@ -19,6 +19,7 @@ import com.outworkers.phantom.builder.QueryBuilder
import com.outworkers.phantom.builder.clauses._
import com.outworkers.phantom.builder.primitives.Primitive
import com.outworkers.phantom.builder.query.prepared.{ListValue, PrepareMark}
import shapeless.HList

/**
* A class enforcing columns used in where clauses to be indexed.
Expand All @@ -34,10 +35,14 @@ abstract class RootQueryColumn[RR](val name: String)(implicit p: Primitive[RR])
new WhereClause.Condition(QueryBuilder.Where.eqs(name, p.asCql(value)))
}

def eqs(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.eqs(name, value.qb.queryString))
def eqs[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = {
new WhereClause.HListCondition[HL](
QueryBuilder.Where.eqs(name, value.qb.queryString)
)
}


// LT Clauses
def lt(value: RR): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lt(name, p.asCql(value)))
}
Expand All @@ -46,14 +51,15 @@ abstract class RootQueryColumn[RR](val name: String)(implicit p: Primitive[RR])
new WhereClause.Condition(QueryBuilder.Where.lt(name, p.asCql(value)))
}

def lt(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lt(name, value.qb.queryString))
def lt[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = {
new WhereClause.HListCondition[HL](
QueryBuilder.Where.lt(name, value.qb.queryString)
)
}

def <(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lt(name, value.qb.queryString))
}
def <[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = lt(value)

// LTE clauses
def lte(value: RR): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lte(name, implicitly[Primitive[RR]].asCql(value)))
}
Expand All @@ -62,14 +68,16 @@ abstract class RootQueryColumn[RR](val name: String)(implicit p: Primitive[RR])
new WhereClause.Condition(QueryBuilder.Where.lte(name, implicitly[Primitive[RR]].asCql(value)))
}

def lte(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lte(name, value.qb.queryString))
def lte[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = {
new WhereClause.HListCondition[HL](
QueryBuilder.Where.lte(name, value.qb.queryString)
)
}

def <=(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.lte(name, value.qb.queryString))
}
def <=[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = lte(value)


// GT Clauses
def gt(value: RR): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gt(name, p.asCql(value)))
}
Expand All @@ -78,14 +86,15 @@ abstract class RootQueryColumn[RR](val name: String)(implicit p: Primitive[RR])
new WhereClause.Condition(QueryBuilder.Where.gt(name, p.asCql(value)))
}

def gt(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gt(name, value.qb.queryString))
def gt[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = {
new WhereClause.HListCondition[HL](
QueryBuilder.Where.gt(name, value.qb.queryString)
)
}

def >(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gt(name, value.qb.queryString))
}
def >[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = gt(value)

// GTE clauses
def gte(value: RR): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gte(name, p.asCql(value)))
}
Expand All @@ -94,13 +103,13 @@ abstract class RootQueryColumn[RR](val name: String)(implicit p: Primitive[RR])
new WhereClause.Condition(QueryBuilder.Where.gte(name, p.asCql(value)))
}

def gte(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gte(name, value.qb.queryString))
def gte[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = {
new WhereClause.HListCondition[HL](
QueryBuilder.Where.gte(name, value.qb.queryString)
)
}

def >=(value: OperatorClause.Condition): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.gte(name, value.qb.queryString))
}
def >=[HL <: HList](value: QueryCondition[HL]): WhereClause.HListCondition[HL] = gte(value)

def in(values: List[RR]): WhereClause.Condition = {
new WhereClause.Condition(QueryBuilder.Where.in(name, values.map(p.asCql)))
Expand Down
Expand Up @@ -147,8 +147,8 @@ class Jdk8TimeUUIDTests extends PhantomSuite {
_ <- database.timeuuidTable.store(record2).future()
one <- database.timeuuidTable.select
.where(_.user eqs record.user)
.and(_.id <= maxTimeuuid(end))
.and(_.id >= minTimeuuid(start))
.and(_.id <= maxTimeuuid(end))
.fetch()

one2 <- database.timeuuidTable.select
Expand All @@ -175,6 +175,142 @@ class Jdk8TimeUUIDTests extends PhantomSuite {
}
}

it should "be able to store and retrieve a fixed time slice of records with prepared statements" in {

val interval = 60
val now = ZonedDateTime.now()
val start = now.plusMinutes(-interval)
val end = now.plusMinutes(interval)
val user = UUIDs.random()

val record = TimeUUIDRecord(
user,
UUIDs.timeBased(),
gen[String]
)

val minuteOffset = start.plusMinutes(-1).timeuuid
val secondOffset = start.plusSeconds(-15).timeuuid

val record1 = TimeUUIDRecord(
user,
minuteOffset,
gen[String]
)

val record2 = TimeUUIDRecord(
user,
secondOffset,
gen[String]
)

val query = database.timeuuidTable.select
.where(_.user eqs ?)
.and(_.id > minTimeuuid(?))
.and(_.id < maxTimeuuid(?))
.prepareAsync()

val chain = for {
_ <- database.timeuuidTable.store(record).future()
_ <- database.timeuuidTable.store(record1).future()
_ <- database.timeuuidTable.store(record2).future()
one <- query.flatMap(_.bind(record.user, start.toInstant.toEpochMilli, end.toInstant.toEpochMilli).fetch())

one2 <- query.flatMap(_.bind(
record.user,
start.plusMinutes(-2).toInstant.toEpochMilli,
end.toInstant.toEpochMilli
).fetch())
} yield (one, one2)

whenReady(chain) { case (res, res2) =>
info("At least one timestamp value, including potential time skews, should be included here")
res should contain (record)

info("Should not contain record with a timestamp 1 minute before the selection window")
res should not contain record1

info("Should not contain record with a timestamp 15 seconds before the selection window")
res should not contain record2

info("Should contain all elements if we expand the selection window by 1 minute")
res2.find(_.id == record1.id) shouldBe defined
}
}

it should "be able to store and retrieve a time slice of records with prepared statements" in {

val interval = 60
val now = ZonedDateTime.now()
val start = now.plusMinutes(-interval)
val end = now.plusMinutes(interval)
val user = UUIDs.random()

val record = TimeUUIDRecord(
user,
UUIDs.timeBased(),
gen[String]
)

/**
* Cassandra sometimes skews the timestamp of this date by exactly 1 milliseconds
* for reasons beyond our understanding, which means the test is flaky unless this
* list is added to make sure at least one of t, t minus 1 millisecond, or t plus 1 millisecond
* is found in the expected list of records.
*/
val recordList = record :: Nil

val minuteOffset = start.plusMinutes(-1).timeuuid
val secondOffset = start.plusSeconds(-15).timeuuid

val record1 = TimeUUIDRecord(
user,
minuteOffset,
gen[String]
)

val record2 = TimeUUIDRecord(
user,
secondOffset,
gen[String]
)

val query = database.timeuuidTable.select
.where(_.user eqs ?)
.and(_.id >= minTimeuuid(?))
.and(_.id <= maxTimeuuid(?))
.prepareAsync()

val chain = for {
_ <- database.timeuuidTable.store(record).future()
_ <- database.timeuuidTable.store(record1).future()
_ <- database.timeuuidTable.store(record2).future()
one <- query.flatMap(_.bind(record.user, start.toInstant.toEpochMilli, end.toInstant.toEpochMilli).fetch())

one2 <- query.flatMap(_.bind(
record.user,
start.plusMinutes(-2).toInstant.toEpochMilli,
end.toInstant.toEpochMilli
).fetch())
} yield (one, one2)

whenReady(chain) { case (res, res2) =>
info("At least one timestamp value, including potential time skewes, should be included here")
recordList exists(res contains) shouldEqual true

info("Should not contain record with a timestamp 1 minute before the selection window")
res should not contain record1

info("Should not contain record with a timestamp 15 seconds before the selection window")
res should not contain record2

info("Should contain all elements if we expand the selection window by 1 minute")
res2.find(_.id == record.id) shouldBe defined
res2.find(_.id == record1.id) shouldBe defined
res2.find(_.id == record2.id) shouldBe defined
}
}

it should "not retrieve anything for a mismatched selection time window using ZonedDateTime" in {

val intervalOffset = 60
Expand Down Expand Up @@ -223,18 +359,18 @@ class Jdk8TimeUUIDTests extends PhantomSuite {
Gen.choose(
-intervalOffset,
intervalOffset
).sample.get
).sample.value
).timeuuid)
)

val chain = for {
_ <- database.timeuuidTable.storeRecords(records)
get <- database.timeuuidTable.select
records <- database.timeuuidTable.select
.where(_.user eqs user)
.and(_.id >= minTimeuuid(start.plusSeconds(-3 * intervalOffset)))
.and(_.id <= maxTimeuuid(start.plusSeconds(-2 * intervalOffset)))
.fetch()
} yield get
} yield records

whenReady(chain) { res =>
res.size shouldEqual 0
Expand Down
1 change: 1 addition & 0 deletions readme/src/main/tut/basics/tables.md
Expand Up @@ -122,6 +122,7 @@ abstract class ExampleRecord extends Table[ExampleRecord, ExampleModel] {
object name extends StringColumn
object props extends MapColumn[String, String]
object test extends OptionalIntColumn
}
```


Expand Down

0 comments on commit 450813a

Please sign in to comment.