diff --git a/phantom-dsl/src/main/scala/com/newzly/phantom/query/InsertQuery.scala b/phantom-dsl/src/main/scala/com/newzly/phantom/query/InsertQuery.scala index 7281a1166..e4236586f 100644 --- a/phantom-dsl/src/main/scala/com/newzly/phantom/query/InsertQuery.scala +++ b/phantom-dsl/src/main/scala/com/newzly/phantom/query/InsertQuery.scala @@ -24,16 +24,18 @@ class InsertQuery[T <: CassandraTable[T, R], R](table: T, val qb: Insert) extend final def value[RR](c: T => AbstractColumn[RR], value: RR): InsertQuery[T, R] = { val col = c(table) - qb.value(col.name, col.toCType(value)) - this + new InsertQuery[T, R](table, qb.value(col.name, col.toCType(value))) } final def valueOrNull[RR](c: T => AbstractColumn[RR], value: RR): InsertQuery[T, R] = { val col = c(table) - qb.value(col.name, Try { + new InsertQuery[T, R](table, qb.value(col.name, Try { col.toCType(value) - } getOrElse null.asInstanceOf[T]) - this + } getOrElse null.asInstanceOf[T])) + } + + final def ifNotExists[RR] = { + new InsertQuery[T, R](table, qb.ifNotExists()) } /** diff --git a/phantom-dsl/src/main/scala/com/newzly/phantom/query/UpdateQuery.scala b/phantom-dsl/src/main/scala/com/newzly/phantom/query/UpdateQuery.scala index 6577594d0..c60284e68 100644 --- a/phantom-dsl/src/main/scala/com/newzly/phantom/query/UpdateQuery.scala +++ b/phantom-dsl/src/main/scala/com/newzly/phantom/query/UpdateQuery.scala @@ -48,13 +48,17 @@ class AssignmentOptionQuery[T <: CassandraTable[T, R], R](table: T, val qb: Upda class ConditionalUpdateQuery[T <: CassandraTable[T, R], R](table: T, val qb: Update.Conditions) extends SharedQueryMethods[ConditionalUpdateQuery[T, R], Update.Conditions](qb) { - def where[RR](condition: T => QueryCondition): UpdateWhere[T, R] = { - new UpdateWhere[T, R](table, qb.where(condition(table).clause)) + def and[RR](condition: T => SecondaryQueryCondition): ConditionalUpdateQuery[T, R] = { + new ConditionalUpdateQuery[T, R](table, qb.and(condition(table).clause)) } } class ConditionalUpdateWhereQuery[T <: CassandraTable[T, R], R](table: T, val qb: Update.Conditions) extends SharedQueryMethods[ConditionalUpdateWhereQuery[T, R], Update.Conditions](qb) { + + def and[RR](condition: T => SecondaryQueryCondition): ConditionalUpdateQuery[T, R] = { + new ConditionalUpdateQuery[T, R](table, qb.and(condition(table).clause)) + } } class UpdateQuery[T <: CassandraTable[T, R], R](table: T, val qb: Update) diff --git a/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/CASConditionalQueriesTest.scala b/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/CASConditionalQueriesTest.scala new file mode 100644 index 000000000..30ed3b3d5 --- /dev/null +++ b/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/CASConditionalQueriesTest.scala @@ -0,0 +1,57 @@ +package com.newzly.phantom.dsl.query + +import org.scalatest.{ FlatSpec, Matchers, ParallelTestExecution } +import com.datastax.driver.core.utils.UUIDs +import com.newzly.phantom.Implicits._ +import com.newzly.phantom.tables.{ Primitives, SecondaryIndexTable, TimeSeriesTable } +import com.newzly.util.testing.Sampler + +class CASConditionalQueriesTest extends FlatSpec with Matchers { + it should "allow using a non-index column in a conditional update clause" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L)" should compile + } + + it should " not allow using a PartitionKey in a conditional clause" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.pkey eqs Sampler.getARandomString)" shouldNot compile + } + + it should " not allow using a PrimaryKey in a conditional clause " in { + "TwoKeys.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.intColumn1 eqs 5)" shouldNot compile + } + + it should " not allow using an Index in a conditional clause " in { + "SecondaryIndexTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.secondary eqs UUIDs.timeBased())" shouldNot compile + } + + it should " not allow using an Index in the second part of a conditional clause " in { + "SecondaryIndexTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.name eqs Sampler.getARandomString).and(_.secondary eqs UUIDs.timeBased())" shouldNot compile + } + + it should " allow using a non Clustering column from a TimeSeries table in a conditional clause" in { + "TimeSeriesTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.name eqs Sampler.getARandomString)" should compile + } + + it should " not allow using a ClusteringColumn in a conditional clause" in { + "TimeSeriesTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.timestamp eqs new DateTime)" shouldNot compile + } + + it should " not allow using a ClusteringColumn in the second part of a conditional clause" in { + "TimeSeriesTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.name eqs Sampler.getARandomString).and(_.timestamp eqs new DateTime)" shouldNot compile + } + + it should "allow using multiple non-primary conditions in a CAS clase" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L).and(_.boolean eqs false)" should compile + } + + it should "not allow using an index column condition in the AND part of a CAS clause" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L).and(_.pkey eqs Sampler.getARandomString)" shouldNot compile + } + + it should "allow using 3 separate CAS conditions in an update query" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L).and(_.boolean eqs false).and(_.int eqs 10)" should compile + } + + it should "not allow using 3 separate CAS conditions in an update query with the 3rd condition on an indexed column" in { + "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L).and(_.boolean eqs false).and(_.pkey eqs Sampler.getARandomString)" shouldNot compile + } +} diff --git a/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/ConditionalQueryRestrictions.scala b/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/ConditionalQueryRestrictions.scala deleted file mode 100644 index 996c49da1..000000000 --- a/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/ConditionalQueryRestrictions.scala +++ /dev/null @@ -1,34 +0,0 @@ -package com.newzly.phantom.dsl.query - -import org.scalatest.{ FlatSpec, Matchers, ParallelTestExecution } -import com.datastax.driver.core.utils.UUIDs -import com.newzly.phantom.Implicits._ -import com.newzly.phantom.tables.{ Primitives, TimeSeriesTable } -import com.newzly.util.testing.Sampler - -class ConditionalQueryRestrictions extends FlatSpec with Matchers { - - it should "allow using a non-index column in a conditional update clause" in { - "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.long eqs 5L)" should compile - } - - it should " not allow using a PartitionKey in a conditional clause" in { - "Primitives.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.pkey eqs Sampler.getARandomString)" shouldNot compile - } - - it should " not allow using a PrimaryKey in a conditional clause " in { - "TwoKeys.update.where(_.pkey eqs Sampler.getARandomString).onlyIf(_.intColumn1 eqs 5)" shouldNot compile - } - - it should " not allow using an Index in a conditional clause " in { - "SecondaryIndexTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.secondary eqs UUIDs.timeBased())" shouldNot compile - } - - it should " allow using a non Clustering column from a TimeSeries table in a conditional clause" in { - "TimeSeriesTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.name eqs Sampler.getARandomString)" should compile - } - - it should " not allow using a ClusteringColumn in a conditional clause" in { - "TimeSeriesTable.update.where(_.id eqs UUIDs.timeBased()).onlyIf(_.timestamp eqs new DateTime)" shouldNot compile - } -} diff --git a/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/QuerySerializationTest.scala b/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/QuerySerializationTest.scala index c9311d245..4d8a73192 100644 --- a/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/QuerySerializationTest.scala +++ b/phantom-test/src/test/scala/com/newzly/phantom/dsl/query/QuerySerializationTest.scala @@ -18,7 +18,7 @@ package com.newzly.phantom.dsl.query import org.scalatest.{ FlatSpec, Matchers, ParallelTestExecution } import com.datastax.driver.core.utils.UUIDs import com.newzly.phantom.Implicits._ -import com.newzly.phantom.tables.{ Articles, Recipes } +import com.newzly.phantom.tables.{ Articles, Primitives, Recipes } import com.newzly.util.testing.Sampler class QuerySerializationTest extends FlatSpec with Matchers { @@ -65,4 +65,33 @@ class QuerySerializationTest extends FlatSpec with Matchers { ).where(_.url eqs someId).qb.toString shouldBe s"SELECT url,description,ingredients FROM Recipes WHERE url='$someId';" } + it should "corectly serialise a simple conditional update query" in { + val qb = Primitives.update.where(_.pkey eqs "test").onlyIf(_.boolean eqs false).qb.toString + qb shouldEqual s"UPDATE Primitives WHERE pkey='test' IF boolean=false;" + } + + it should "corectly serialise a multi-part conditional update query" in { + val qb = Primitives.update.where(_.pkey eqs "test").onlyIf(_.boolean eqs false).and(_.long eqs 5L).qb.toString + qb shouldEqual s"UPDATE Primitives WHERE pkey='test' IF boolean=false AND long=5;" + } + + it should "corectly serialise a conditional update query with a single List column based clause" in { + val qb = Recipes.update.where(_.url eqs "test") + .modify(_.description setTo Some("blabla")) + .onlyIf(_.ingredients eqs List("1", "2", "3")) + .qb.toString + + qb shouldEqual "UPDATE Recipes SET description='blabla' WHERE url='test' IF ingredients=['1','2','3'];" + } + + it should "corectly serialise a multi-part conditional update query with a List column part" in { + val qb = Recipes.update.where(_.url eqs "test") + .modify(_.description setTo Some("blabla")) + .onlyIf(_.ingredients eqs List("1", "2", "3")) + .and(_.description eqs Some("test")) + .qb.toString + + qb shouldEqual "UPDATE Recipes SET description='blabla' WHERE url='test' IF ingredients=['1','2','3'] AND description='test';" + } + } diff --git a/phantom-test/src/test/scala/com/newzly/phantom/dsl/specialized/ConditionalQueries.scala b/phantom-test/src/test/scala/com/newzly/phantom/dsl/specialized/ConditionalQueries.scala index 223c8f606..5863e64b7 100644 --- a/phantom-test/src/test/scala/com/newzly/phantom/dsl/specialized/ConditionalQueries.scala +++ b/phantom-test/src/test/scala/com/newzly/phantom/dsl/specialized/ConditionalQueries.scala @@ -1,7 +1,21 @@ +/* + * Copyright 2013 newzly ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.newzly.phantom.dsl.specialized import scala.concurrent.blocking -import com.datastax.driver.core.exceptions.InvalidQueryException import com.datastax.driver.core.utils.UUIDs import com.newzly.phantom.Implicits._ import com.newzly.phantom.tables.{ Recipe, Recipes } @@ -104,7 +118,7 @@ class ConditionalQueries extends BaseTest { } } - it should "throw an error when a list column is used a conditional clause" in { + it should "execute an update when a list column is used a conditional clause" in { val recipe = Recipe.sample val id = UUIDs.timeBased() @@ -141,12 +155,55 @@ class ConditionalQueries extends BaseTest { second.isDefined shouldEqual true info("And it should contain the updated value of the uid") - second.get.description shouldEqual(updated) + second.get.description shouldEqual updated } } } - it should "throw an error when a list column is used a conditional clause with Twitter Futures" in { + it should "not execute the update when the list column in a conditional clause doesn't match" in { + val recipe = Recipe.sample + val id = UUIDs.timeBased() + + val invalidMatch = List("invalid1", "invalid2") + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .future() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).one() + update <- Recipes.update.where(_.url eqs recipe.url).modify(_.description setTo updated).onlyIf(_.ingredients eqs invalidMatch).future() + select2 <- Recipes.select.where(_.url eqs recipe.url).one() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it shouldn't have updated the value") + second.get.description shouldNot equal(updated) + } + } + } + + it should "execute an update when a list column is used a conditional clause with Twitter Futures" in { val recipe = Recipe.sample val id = UUIDs.timeBased() @@ -183,7 +240,50 @@ class ConditionalQueries extends BaseTest { second.isDefined shouldEqual true info("And it should contain the updated value of the uid") - second.get.description shouldEqual(updated) + second.get.description shouldEqual updated + } + } + } + + it should "not execute the update when the list column in a conditional clause doesn't match with Twitter Futures" in { + val recipe = Recipe.sample + val id = UUIDs.timeBased() + + val invalidMatch = List("invalid1", "invalid2") + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .execute() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).get() + update <- Recipes.update.where(_.url eqs recipe.url).modify(_.description setTo updated).onlyIf(_.ingredients eqs invalidMatch).execute() + select2 <- Recipes.select.where(_.url eqs recipe.url).get() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it shouldn't have updated the value") + second.get.description shouldNot equal(updated) } } } @@ -271,4 +371,369 @@ class ConditionalQueries extends BaseTest { } } } + + it should "execute an update with a multi-part CAS conditional query with no collection columns in the CAS part" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .future() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).one() + update <- Recipes.update.where(_.url eqs recipe.url).modify(_.description setTo updated).onlyIf(_.description eqs recipe.description).and(_.uid eqs id).future() + select2 <- Recipes.select.where(_.url eqs recipe.url).one() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a multi-part CAS conditional query with no collection columns in the CAS part with Twitter Futures" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .execute() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).get() + update <- Recipes.update.where(_.url eqs recipe.url).modify(_.description setTo updated).onlyIf(_.description eqs recipe.description).and(_.uid eqs id).execute() + select2 <- Recipes.select.where(_.url eqs recipe.url).get() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a tri-part CAS conditional query with no collection columns in the CAS part" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .future() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).one() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.description eqs recipe.description) + .and(_.last_checked_at eqs recipe.lastCheckedAt) + .and(_.uid eqs id).future() + select2 <- Recipes.select.where(_.url eqs recipe.url).one() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a tri-part CAS conditional query with no collection columns in the CAS part with Twitter Futures" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .execute() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).get() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.description eqs recipe.description) + .and(_.last_checked_at eqs recipe.lastCheckedAt) + .and(_.uid eqs id).execute() + + select2 <- Recipes.select.where(_.url eqs recipe.url).get() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a dual-part CAS conditional query with a mixture of collection columns in the CAS part" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .future() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).one() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.props eqs recipe.props) + .and(_.ingredients eqs recipe.ingredients) + .future() + select2 <- Recipes.select.where(_.url eqs recipe.url).one() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a dual-part CAS conditional query with a mixture of collection columns in the CAS part with Twitter Futures" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .execute() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).get() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.props eqs recipe.props) + .and(_.ingredients eqs recipe.ingredients) + .execute() + select2 <- Recipes.select.where(_.url eqs recipe.url).get() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + it should "execute an update with a dual-part CAS conditional query with a mixture of collection columns and simple comparisons in the CAS part" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .future() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).one() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.props eqs recipe.props) + .and(_.uid eqs id) + .and(_.ingredients eqs recipe.ingredients) + .future() + select2 <- Recipes.select.where(_.url eqs recipe.url).one() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + + + it should "execute an update with a dual-part CAS query with a mixture of columns with Twitter Futures" in { + + val recipe = Recipe.sample + val id = UUIDs.timeBased() + val updated = Some(Sampler.getARandomString) + + val insert = Recipes.insert + .value(_.uid, id) + .value(_.url, recipe.url) + .value(_.description, recipe.description) + .value(_.ingredients, recipe.ingredients) + .value(_.last_checked_at, recipe.lastCheckedAt) + .value(_.props, recipe.props) + .execute() + + val chain = for { + insert <- insert + select1 <- Recipes.select.where(_.url eqs recipe.url).get() + update <- Recipes.update.where(_.url eqs recipe.url) + .modify(_.description setTo updated) + .onlyIf(_.props eqs recipe.props) + .and(_.uid eqs id) + .and(_.ingredients eqs recipe.ingredients) + .execute() + select2 <- Recipes.select.where(_.url eqs recipe.url).get() + } yield (select1, select2) + + chain.successful { + res => { + val initial = res._1 + val second = res._2 + + info("The first record should not be empty") + initial.isDefined shouldEqual true + + info("And it should match the inserted values") + initial.get.url shouldEqual recipe.url + + info("The updated record should not be empty") + second.isDefined shouldEqual true + + info("And it should contain the updated value of the uid") + second.get.description shouldEqual updated + } + } + } + }