From 54c23232c0fdf728ac6aea5b1730f26a2beb7ba7 Mon Sep 17 00:00:00 2001 From: voropaevp Date: Tue, 31 Aug 2021 23:14:37 +0100 Subject: [PATCH] Cats Effect 3 context --- build.sbt | 43 +++++++++- .../io/getquill/CassandraCeContext.scala | 85 +++++++++++++++++++ .../io/getquill/CassandraCeContext.scala | 83 ++++++++++++++++++ .../src/test/resources/application.conf | 12 +++ .../cassandra/catEffect/DecodeNullSpec.scala | 36 ++++++++ .../cassandra/catEffect/EncodingSpec.scala | 44 ++++++++++ .../QueryResultTypeCassandraCeSpec.scala | 54 ++++++++++++ .../UdtEncodingSessionContextSpec.scala | 78 +++++++++++++++++ .../context/cassandra/catEffect/package.scala | 15 ++++ .../cassandra/catEffect/DecodeNullSpec.scala | 35 ++++++++ .../cassandra/catEffect/EncodingSpec.scala | 44 ++++++++++ .../QueryResultTypeCassandraCeSpec.scala | 54 ++++++++++++ .../UdtEncodingSessionContextSpec.scala | 78 +++++++++++++++++ .../context/cassandra/catEffect/package.scala | 15 ++++ .../io/getquill/context/ce/CeContext.scala | 18 ++++ .../io/getquill/context/ce/CeContext.scala | 18 ++++ 16 files changed, 709 insertions(+), 3 deletions(-) create mode 100644 quill-cassandra-ce/src/main/scala-2.12/io/getquill/CassandraCeContext.scala create mode 100644 quill-cassandra-ce/src/main/scala-2.13/io/getquill/CassandraCeContext.scala create mode 100644 quill-cassandra-ce/src/test/resources/application.conf create mode 100644 quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/EncodingSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/package.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/EncodingSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala create mode 100644 quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/package.scala create mode 100644 quill-ce/src/main/scala-2.12/io/getquill/context/ce/CeContext.scala create mode 100644 quill-ce/src/main/scala-2.13/io/getquill/context/ce/CeContext.scala diff --git a/build.sbt b/build.sbt index 5b669813a0..7f64481cda 100644 --- a/build.sbt +++ b/build.sbt @@ -64,7 +64,7 @@ lazy val codegenModules = Seq[sbt.ClasspathDep[sbt.ProjectReference]]( ) lazy val bigdataModules = Seq[sbt.ClasspathDep[sbt.ProjectReference]]( - `quill-cassandra`, `quill-cassandra-lagom`, `quill-cassandra-monix`, `quill-cassandra-zio`, `quill-orientdb`, `quill-spark` + `quill-cassandra`, `quill-cassandra-lagom`, `quill-cassandra-monix`, `quill-cassandra-zio`, `quill-orientdb`, `quill-spark`, `quill-cassandra-ce` ) lazy val allModules = @@ -76,6 +76,7 @@ lazy val scala213Modules = baseModules ++ jsModules ++ dbModules ++ codegenModul `quill-async-postgres`, `quill-finagle-mysql`, `quill-cassandra`, + `quill-cassandra-ce`, `quill-cassandra-lagom`, `quill-cassandra-monix`, `quill-cassandra-zio`, @@ -391,8 +392,8 @@ lazy val `quill-monix` = .settings( Test / fork := true, libraryDependencies ++= Seq( - "io.monix" %% "monix-eval" % "3.0.0", - "io.monix" %% "monix-reactive" % "3.0.0" + "io.monix" %% "monix-eval" % "3.0.0" excludeAll(ExclusionRule(organization = "org.typelevel")), + "io.monix" %% "monix-reactive" % "3.0.0" excludeAll(ExclusionRule(organization = "org.typelevel")), ) ) .dependsOn(`quill-core-jvm` % "compile->compile;test->test") @@ -434,6 +435,31 @@ lazy val `quill-zio` = .dependsOn(`quill-core-jvm` % "compile->compile;test->test") .enablePlugins(MimaPlugin) +lazy val `quill-ce` = + (project in file("quill-ce")) + .settings(commonSettings: _*) + .settings(mimaSettings: _*) + .settings( + scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, major)) if major <= 12 => Seq("-Ypartial-unification") + case _ => Seq.empty + } + }, + libraryDependencies ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, x)) if x >= 12 => Seq( + "org.typelevel" %% "cats-core" % "2.3.0", + "org.typelevel" %% "cats-effect" % "3.1.1", + "co.fs2" %% "fs2-core" % "3.0.4", + ) + case _ => Seq.empty + } + } + ) + .dependsOn(`quill-core-jvm` % "compile->compile;test->test") + .enablePlugins(MimaPlugin) + lazy val `quill-jdbc-zio` = (project in file("quill-jdbc-zio")) .settings(commonSettings: _*) @@ -666,6 +692,16 @@ lazy val `quill-cassandra-zio` = .dependsOn(`quill-zio` % "compile->compile;test->test") .enablePlugins(MimaPlugin) +lazy val `quill-cassandra-ce` = + (project in file("quill-cassandra-ce")) + .settings(commonSettings: _*) + .settings(mimaSettings: _*) + .settings( + Test / fork := true + ) + .dependsOn(`quill-cassandra` % "compile->compile;test->test") + .dependsOn(`quill-ce` % "compile->compile;test->test") + .enablePlugins(MimaPlugin) lazy val `quill-cassandra-lagom` = (project in file("quill-cassandra-lagom")) @@ -686,6 +722,7 @@ lazy val `quill-cassandra-lagom` = ) ++ versionSpecificDependencies } ) + .dependsOn(`quill-cassandra` % "compile->compile;test->test") .enablePlugins(MimaPlugin) diff --git a/quill-cassandra-ce/src/main/scala-2.12/io/getquill/CassandraCeContext.scala b/quill-cassandra-ce/src/main/scala-2.12/io/getquill/CassandraCeContext.scala new file mode 100644 index 0000000000..f5faef086c --- /dev/null +++ b/quill-cassandra-ce/src/main/scala-2.12/io/getquill/CassandraCeContext.scala @@ -0,0 +1,85 @@ +package io.getquill + +import cats._ +import cats.effect._ +import com.datastax.oss.driver.api.core.cql.Row +import cats.syntax.all._ +import com.datastax.oss.driver.api.core.CqlSession +import com.datastax.oss.driver.api.core.cql.AsyncResultSet +import fs2.{ Chunk, Stream } +import com.typesafe.config.Config +import io.getquill.context.cassandra.CqlIdiom +import io.getquill.util.{ ContextLogger, LoadConfig } +import io.getquill.context.ExecutionInfo +import io.getquill.context.ce.CeContext + +import scala.jdk.CollectionConverters._ +import scala.language.higherKinds + +class CassandraCeContext[N <: NamingStrategy, F[_]]( + naming: N, + session: CqlSession, + preparedStatementCacheSize: Long +)(implicit val af: Async[F]) + extends CassandraCqlSessionContext[N](naming, session, preparedStatementCacheSize) + with CeContext[CqlIdiom, N, F] { + + private val logger = ContextLogger(classOf[CassandraCeContext[_, F]]) + + private[getquill] def prepareRowAndLog(cql: String, prepare: Prepare = identityPrepare): F[PrepareRow] = for { + ec <- Async[F].executionContext + futureStatement = Sync[F].delay(prepareAsync(cql)(ec)) + prepStatement <- Async[F].fromFuture(futureStatement) + (params, bs) = prepare(prepStatement, this) + _ <- Sync[F].delay(logger.logQuery(cql, params)) + } yield bs + + protected def page(rs: AsyncResultSet): Stream[F, Row] = + Stream.unfoldChunkEval(rs.remaining())(rem => + if (rem > 0) + af.delay[Option[(Chunk[Row], Int)]] { + val chunk: Chunk[Row] = Chunk.iterable(rs.currentPage().asScala) + Some((chunk, rs.remaining())) + } + else + af.pure[Option[(Chunk[Row], Int)]](None)) + + def streamQuery[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): StreamResult[T] = { + Stream + .eval(prepareRowAndLog(cql, prepare)) + .evalMap(p => af.fromCompletableFuture(af.delay(session.executeAsync(p).toCompletableFuture))) + .flatMap(page) + .map(it => extractor(it, this)) + } + + def executeQuery[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): Result[RunQueryResult[T]] = + streamQuery[T](cql, prepare, extractor)(info, dc).compile.toList + + def executeQuerySingle[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): Result[RunQuerySingleResult[T]] = + Functor[F].map(executeQuery(cql, prepare, extractor)(info, dc))(handleSingleResult) + + def executeAction(cql: String, prepare: Prepare = identityPrepare)(info: ExecutionInfo, dc: Runner): Result[RunActionResult] = { + prepareRowAndLog(cql, prepare) + .flatMap(r => af.fromCompletableFuture(af.delay(session.executeAsync(r).toCompletableFuture))) + .map(_ => ()) + } + + def executeBatchAction(groups: List[BatchGroup])(info: ExecutionInfo, dc: Runner): Result[RunBatchActionResult] = + groups.traverse_ { + case BatchGroup(cql, prepare) => + prepare.traverse_(executeAction(cql, _)(info, dc)) + } +} + +object CassandraCeContext { + + def apply[N <: NamingStrategy, F[_]: Async: FlatMap](naming: N, config: CassandraContextConfig): CassandraCeContext[N, F] = + new CassandraCeContext(naming, config.session, config.preparedStatementCacheSize) + + def apply[N <: NamingStrategy, F[_]: Async: FlatMap](naming: N, config: Config): CassandraCeContext[N, F] = + CassandraCeContext(naming, CassandraContextConfig(config)) + + def apply[N <: NamingStrategy, F[_]: Async: FlatMap](naming: N, configPrefix: String): CassandraCeContext[N, F] = + CassandraCeContext(naming, LoadConfig(configPrefix)) + +} diff --git a/quill-cassandra-ce/src/main/scala-2.13/io/getquill/CassandraCeContext.scala b/quill-cassandra-ce/src/main/scala-2.13/io/getquill/CassandraCeContext.scala new file mode 100644 index 0000000000..0018679bff --- /dev/null +++ b/quill-cassandra-ce/src/main/scala-2.13/io/getquill/CassandraCeContext.scala @@ -0,0 +1,83 @@ +package io.getquill + +import cats._ +import cats.effect._ +import cats.syntax.all._ +import fs2.{Chunk, Stream} +import com.typesafe.config.Config +import com.datastax.oss.driver.api.core.cql.Row +import com.datastax.oss.driver.api.core.CqlSession +import com.datastax.oss.driver.api.core.cql.AsyncResultSet +import io.getquill.context.cassandra.CqlIdiom +import io.getquill.util.{ContextLogger, LoadConfig} +import io.getquill.context.ExecutionInfo +import io.getquill.context.ce.CeContext + +import scala.jdk.CollectionConverters._ +import scala.language.higherKinds + +class CassandraCeContext[N <: NamingStrategy, F[_]]( + naming: N, + session: CqlSession, + preparedStatementCacheSize: Long + )(implicit val af: Async[F]) + extends CassandraCqlSessionContext[N](naming, session, preparedStatementCacheSize) + with CeContext[CqlIdiom, N, F] { + + private val logger = ContextLogger(classOf[CassandraCeContext[_, F]]) + + private[getquill] def prepareRowAndLog(cql: String, prepare: Prepare = identityPrepare): F[PrepareRow] = for { + ec <- Async[F].executionContext + futureStatement = Sync[F].delay(prepareAsync(cql)(ec)) + prepStatement <- Async[F].fromFuture(futureStatement) + (params, bs) = prepare(prepStatement, this) + _ <- Sync[F].delay(logger.logQuery(cql, params)) + } yield bs + + protected def page(rs: AsyncResultSet): Stream[F, Row] = + Stream.unfoldChunkEval(rs.remaining())(rem => + if (rem > 0) + af.delay(Some(Chunk.iterable(rs.currentPage().asScala), rs.remaining())) + else + af.pure(None) + ) + + def streamQuery[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): StreamResult[T] = { + Stream + .eval(prepareRowAndLog(cql, prepare)) + .evalMap(p => af.fromCompletableFuture(af.delay(session.executeAsync(p).toCompletableFuture))) + .flatMap(page) + .map(it => extractor(it, this)) + } + + def executeQuery[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): Result[RunQueryResult[T]] = + streamQuery[T](cql, prepare, extractor)(info, dc).compile.toList + + def executeQuerySingle[T](cql: String, prepare: Prepare = identityPrepare, extractor: Extractor[T] = identityExtractor)(info: ExecutionInfo, dc: Runner): Result[RunQuerySingleResult[T]] = + Functor[F].map(executeQuery(cql, prepare, extractor)(info, dc))(handleSingleResult) + + def executeAction(cql: String, prepare: Prepare = identityPrepare)(info: ExecutionInfo, dc: Runner): Result[RunActionResult] = { + prepareRowAndLog(cql, prepare) + .flatMap(r => af.fromCompletableFuture(af.delay(session.executeAsync(r).toCompletableFuture))) + .map(_ => ()) + } + + def executeBatchAction(groups: List[BatchGroup])(info: ExecutionInfo, dc: Runner): Result[RunBatchActionResult] = + groups.traverse_ { + case BatchGroup(cql, prepare) => + prepare.traverse_(executeAction(cql, _)(info, dc)) + } +} + +object CassandraCeContext { + + def apply[N <: NamingStrategy, F[_] : Async : FlatMap](naming: N, config: CassandraContextConfig): CassandraCeContext[N, F] = + new CassandraCeContext(naming, config.session, config.preparedStatementCacheSize) + + def apply[N <: NamingStrategy, F[_] : Async : FlatMap](naming: N, config: Config): CassandraCeContext[N, F] = + CassandraCeContext(naming, CassandraContextConfig(config)) + + def apply[N <: NamingStrategy, F[_] : Async : FlatMap](naming: N, configPrefix: String): CassandraCeContext[N, F] = + CassandraCeContext(naming, LoadConfig(configPrefix)) + +} diff --git a/quill-cassandra-ce/src/test/resources/application.conf b/quill-cassandra-ce/src/test/resources/application.conf new file mode 100644 index 0000000000..a5773c34ef --- /dev/null +++ b/quill-cassandra-ce/src/test/resources/application.conf @@ -0,0 +1,12 @@ +testStreamDB { + preparedStatementCacheSize=1 + keyspace=quill_test + + session { + basic.contact-points = [ ${?CASSANDRA_CONTACT_POINT_0}, ${?CASSANDRA_CONTACT_POINT_1} ] + basic.load-balancing-policy.local-datacenter = ${?CASSANDRA_DC} + basic.request.consistency = LOCAL_QUORUM + basic.request.page-size = 999 + } + +} \ No newline at end of file diff --git a/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala new file mode 100644 index 0000000000..37477cf860 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala @@ -0,0 +1,36 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill._ + +class DecodeNullSpec extends Spec { + + "no default values when reading null" - { + "stream" in { + import io.getquill.context.cassandra.catsEffect.testCeDB._ + import io.getquill.context.cassandra.catsEffect.testCeDB + import cats.effect.unsafe.implicits.global + + val writeEntities = quote(querySchema[DecodeNullTestWriteEntity]("DecodeNullTestEntity")) + + val result = + for { + _ <- testCeDB.run(writeEntities.delete) + _ <- testCeDB.run(writeEntities.insert(lift(insertValue))) + result <- testCeDB.run(query[DecodeNullTestEntity]) + } yield { + result + } + intercept[IllegalStateException] { + await { + result.unsafeToFuture() + } + } + } + } + + case class DecodeNullTestEntity(id: Int, value: Int) + + case class DecodeNullTestWriteEntity(id: Int, value: Option[Int]) + + val insertValue = DecodeNullTestWriteEntity(0, None) +} diff --git a/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/EncodingSpec.scala b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/EncodingSpec.scala new file mode 100644 index 0000000000..77ccaaad9c --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/EncodingSpec.scala @@ -0,0 +1,44 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.Query +import io.getquill.context.cassandra.EncodingSpecHelper +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global + +class EncodingSpec extends EncodingSpecHelper { + "encodes and decodes types" - { + "stream" in { + val result = + for { + _ <- testCeDB.run(query[EncodingTestEntity].delete) + _ <- testCeDB.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e))) + result <- testCeDB.run(query[EncodingTestEntity]) + } yield { + result + } + val f = result.unsafeToFuture() + val r = await(f) + verify(r) + } + } + + "encodes collections" - { + "stream" in { + val q = quote { + (list: Query[Int]) => + query[EncodingTestEntity].filter(t => list.contains(t.id)) + } + val result = + for { + _ <- testCeDB.run(query[EncodingTestEntity].delete) + _ <- testCeDB.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e))) + result <- testCeDB.run(q(liftQuery(insertValues.map(_.id)))) + } yield { + result + } + val f = result.unsafeToFuture() + verify(await(f)) + } + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala new file mode 100644 index 0000000000..6537d85543 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala @@ -0,0 +1,54 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.context.cassandra.QueryResultTypeCassandraSpec +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global +import cats.effect.IO +import io.getquill.{ Ord, Spec } + +class QueryResultTypeCassandraCeSpec extends Spec { + + def result[A](fa: IO[A]): A = fa.unsafeRunSync() + case class OrderTestEntity(id: Int, i: Int) + + val entries = List( + OrderTestEntity(1, 1), + OrderTestEntity(2, 2), + OrderTestEntity(3, 3) + ) + + val insert = quote((e: OrderTestEntity) => query[OrderTestEntity].insert(e)) + val deleteAll = quote(query[OrderTestEntity].delete) + val selectAll = quote(query[OrderTestEntity]) + val map = quote(query[OrderTestEntity].map(_.id)) + val filter = quote(query[OrderTestEntity].filter(_.id == 1)) + val withFilter = quote(query[OrderTestEntity].withFilter(_.id == 1)) + val sortBy = quote(query[OrderTestEntity].filter(_.id == 1).sortBy(_.i)(Ord.asc)) + val take = quote(query[OrderTestEntity].take(10)) + val entitySize = quote(query[OrderTestEntity].size) + val parametrizedSize = quote { (id: Int) => + query[OrderTestEntity].filter(_.id == id).size + } + val distinct = quote(query[OrderTestEntity].map(_.id).distinct) + + override def beforeAll: Unit = { + testCeDB.run(deleteAll).unsafeRunSync() + testCeDB.run(liftQuery(entries).foreach(e => insert(e))).unsafeRunSync() + () + } + + "query" in { + result(testCeDB.run(selectAll)) mustEqual entries + } + + "querySingle" - { + "size" in { + result(testCeDB.run(entitySize)) mustEqual 3 + } + + "parametrized size" in { + result(testCeDB.run(parametrizedSize(lift(10000)))) mustEqual 0 + } + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala new file mode 100644 index 0000000000..c2c1cf5444 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala @@ -0,0 +1,78 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.Udt +import io.getquill.context.cassandra.udt.UdtSpec +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global + +class UdtEncodingSessionContextSpec extends UdtSpec { + + val ctx = testCeDB + import ctx._ + + "Provide encoding for UDT" - { + "raw" in { + implicitly[Decoder[Name]] + implicitly[Encoder[Name]] + } + "collections" in { + 4 + implicitly[Decoder[List[Name]]] + implicitly[Decoder[Set[Name]]] + implicitly[Decoder[Map[String, Name]]] + implicitly[Encoder[List[Name]]] + implicitly[Encoder[Set[Name]]] + implicitly[Encoder[Map[String, Name]]] + } + "nested" in { + implicitly[Decoder[Personal]] + implicitly[Encoder[Personal]] + implicitly[Decoder[List[Personal]]] + implicitly[Encoder[List[Personal]]] + } + "MappedEncoding" in { + case class FirstName(name: String) + case class MyName(firstName: FirstName) extends Udt + + implicit val encodeFirstName = MappedEncoding[FirstName, String](_.name) + implicit val decodeFirstName = MappedEncoding[String, FirstName](FirstName) + + implicitly[Encoder[MyName]] + implicitly[Decoder[MyName]] + implicitly[Encoder[List[MyName]]] + implicitly[Decoder[List[MyName]]] + } + } + + "Complete examples" - { + "without meta" in { + case class WithEverything(id: Int, personal: Personal, nameList: List[Name]) + + val e = WithEverything(1, Personal(1, "strt", + Name("first", Some("last")), + Some(Name("f", None)), + List("e"), + Set(1, 2), + Map(1 -> "1", 2 -> "2")), + List(Name("first", None))) + ctx.run(query[WithEverything].insert(lift(e))).unsafeRunSync() + ctx.run(query[WithEverything].filter(_.id == 1)).unsafeRunSync().headOption must contain(e) + } + "with meta" in { + case class MyName(first: String) extends Udt + case class WithEverything(id: Int, name: MyName, nameList: List[MyName]) + implicit val myNameMeta = udtMeta[MyName]("Name", _.first -> "firstName") + + val e = WithEverything(2, MyName("first"), List(MyName("first"))) + ctx.run(query[WithEverything].insert(lift(e))).unsafeRunSync() + ctx.run(query[WithEverything].filter(_.id == 2)).unsafeRunSync().headOption must contain(e) + } + } + + override def beforeAll: Unit = { + ctx.run(querySchema[Name]("WithEverything").delete).unsafeRunSync() + super.beforeAll() + () + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/package.scala b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/package.scala new file mode 100644 index 0000000000..5bc96ca8ce --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.12/io/getquill/context/cassandra/catEffect/package.scala @@ -0,0 +1,15 @@ +package io.getquill.context.cassandra + +import io.getquill.util.LoadConfig +import io.getquill.{ CassandraCeContext, CassandraContextConfig, Literal } +import cats.effect.{ Async, IO } +import io.getquill.context.cassandra.encoding.{ Decoders, Encoders } + +package object catsEffect { + + lazy val testCeDB: CassandraCeContext[Literal.type, IO] = { + val c = CassandraContextConfig(LoadConfig("testStreamDB")) + new CassandraCeContext(Literal, c.session, c.preparedStatementCacheSize)(Async[IO]) with CassandraTestEntities with Encoders with Decoders + } + +} diff --git a/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala new file mode 100644 index 0000000000..2af822f4d1 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/DecodeNullSpec.scala @@ -0,0 +1,35 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill._ +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global + +class DecodeNullSpec extends Spec { + + "no default values when reading null" - { + "stream" in { + val writeEntities = quote(querySchema[DecodeNullTestWriteEntity]("DecodeNullTestEntity")) + + val result = + for { + _ <- testCeDB.run(writeEntities.delete) + _ <- testCeDB.run(writeEntities.insert(lift(insertValue))) + result <- testCeDB.run(query[DecodeNullTestEntity]) + } yield { + result + } + intercept[IllegalStateException] { + await { + result.unsafeToFuture() + } + } + } + } + + case class DecodeNullTestEntity(id: Int, value: Int) + + case class DecodeNullTestWriteEntity(id: Int, value: Option[Int]) + + val insertValue = DecodeNullTestWriteEntity(0, None) +} diff --git a/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/EncodingSpec.scala b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/EncodingSpec.scala new file mode 100644 index 0000000000..77ccaaad9c --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/EncodingSpec.scala @@ -0,0 +1,44 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.Query +import io.getquill.context.cassandra.EncodingSpecHelper +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global + +class EncodingSpec extends EncodingSpecHelper { + "encodes and decodes types" - { + "stream" in { + val result = + for { + _ <- testCeDB.run(query[EncodingTestEntity].delete) + _ <- testCeDB.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e))) + result <- testCeDB.run(query[EncodingTestEntity]) + } yield { + result + } + val f = result.unsafeToFuture() + val r = await(f) + verify(r) + } + } + + "encodes collections" - { + "stream" in { + val q = quote { + (list: Query[Int]) => + query[EncodingTestEntity].filter(t => list.contains(t.id)) + } + val result = + for { + _ <- testCeDB.run(query[EncodingTestEntity].delete) + _ <- testCeDB.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e))) + result <- testCeDB.run(q(liftQuery(insertValues.map(_.id)))) + } yield { + result + } + val f = result.unsafeToFuture() + verify(await(f)) + } + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala new file mode 100644 index 0000000000..6537d85543 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/QueryResultTypeCassandraCeSpec.scala @@ -0,0 +1,54 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.context.cassandra.QueryResultTypeCassandraSpec +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global +import cats.effect.IO +import io.getquill.{ Ord, Spec } + +class QueryResultTypeCassandraCeSpec extends Spec { + + def result[A](fa: IO[A]): A = fa.unsafeRunSync() + case class OrderTestEntity(id: Int, i: Int) + + val entries = List( + OrderTestEntity(1, 1), + OrderTestEntity(2, 2), + OrderTestEntity(3, 3) + ) + + val insert = quote((e: OrderTestEntity) => query[OrderTestEntity].insert(e)) + val deleteAll = quote(query[OrderTestEntity].delete) + val selectAll = quote(query[OrderTestEntity]) + val map = quote(query[OrderTestEntity].map(_.id)) + val filter = quote(query[OrderTestEntity].filter(_.id == 1)) + val withFilter = quote(query[OrderTestEntity].withFilter(_.id == 1)) + val sortBy = quote(query[OrderTestEntity].filter(_.id == 1).sortBy(_.i)(Ord.asc)) + val take = quote(query[OrderTestEntity].take(10)) + val entitySize = quote(query[OrderTestEntity].size) + val parametrizedSize = quote { (id: Int) => + query[OrderTestEntity].filter(_.id == id).size + } + val distinct = quote(query[OrderTestEntity].map(_.id).distinct) + + override def beforeAll: Unit = { + testCeDB.run(deleteAll).unsafeRunSync() + testCeDB.run(liftQuery(entries).foreach(e => insert(e))).unsafeRunSync() + () + } + + "query" in { + result(testCeDB.run(selectAll)) mustEqual entries + } + + "querySingle" - { + "size" in { + result(testCeDB.run(entitySize)) mustEqual 3 + } + + "parametrized size" in { + result(testCeDB.run(parametrizedSize(lift(10000)))) mustEqual 0 + } + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala new file mode 100644 index 0000000000..c2c1cf5444 --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/UdtEncodingSessionContextSpec.scala @@ -0,0 +1,78 @@ +package io.getquill.context.cassandra.catEffect + +import io.getquill.Udt +import io.getquill.context.cassandra.udt.UdtSpec +import io.getquill.context.cassandra.catsEffect.testCeDB._ +import io.getquill.context.cassandra.catsEffect.testCeDB +import cats.effect.unsafe.implicits.global + +class UdtEncodingSessionContextSpec extends UdtSpec { + + val ctx = testCeDB + import ctx._ + + "Provide encoding for UDT" - { + "raw" in { + implicitly[Decoder[Name]] + implicitly[Encoder[Name]] + } + "collections" in { + 4 + implicitly[Decoder[List[Name]]] + implicitly[Decoder[Set[Name]]] + implicitly[Decoder[Map[String, Name]]] + implicitly[Encoder[List[Name]]] + implicitly[Encoder[Set[Name]]] + implicitly[Encoder[Map[String, Name]]] + } + "nested" in { + implicitly[Decoder[Personal]] + implicitly[Encoder[Personal]] + implicitly[Decoder[List[Personal]]] + implicitly[Encoder[List[Personal]]] + } + "MappedEncoding" in { + case class FirstName(name: String) + case class MyName(firstName: FirstName) extends Udt + + implicit val encodeFirstName = MappedEncoding[FirstName, String](_.name) + implicit val decodeFirstName = MappedEncoding[String, FirstName](FirstName) + + implicitly[Encoder[MyName]] + implicitly[Decoder[MyName]] + implicitly[Encoder[List[MyName]]] + implicitly[Decoder[List[MyName]]] + } + } + + "Complete examples" - { + "without meta" in { + case class WithEverything(id: Int, personal: Personal, nameList: List[Name]) + + val e = WithEverything(1, Personal(1, "strt", + Name("first", Some("last")), + Some(Name("f", None)), + List("e"), + Set(1, 2), + Map(1 -> "1", 2 -> "2")), + List(Name("first", None))) + ctx.run(query[WithEverything].insert(lift(e))).unsafeRunSync() + ctx.run(query[WithEverything].filter(_.id == 1)).unsafeRunSync().headOption must contain(e) + } + "with meta" in { + case class MyName(first: String) extends Udt + case class WithEverything(id: Int, name: MyName, nameList: List[MyName]) + implicit val myNameMeta = udtMeta[MyName]("Name", _.first -> "firstName") + + val e = WithEverything(2, MyName("first"), List(MyName("first"))) + ctx.run(query[WithEverything].insert(lift(e))).unsafeRunSync() + ctx.run(query[WithEverything].filter(_.id == 2)).unsafeRunSync().headOption must contain(e) + } + } + + override def beforeAll: Unit = { + ctx.run(querySchema[Name]("WithEverything").delete).unsafeRunSync() + super.beforeAll() + () + } +} diff --git a/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/package.scala b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/package.scala new file mode 100644 index 0000000000..5bc96ca8ce --- /dev/null +++ b/quill-cassandra-ce/src/test/scala-2.13/io/getquill/context/cassandra/catEffect/package.scala @@ -0,0 +1,15 @@ +package io.getquill.context.cassandra + +import io.getquill.util.LoadConfig +import io.getquill.{ CassandraCeContext, CassandraContextConfig, Literal } +import cats.effect.{ Async, IO } +import io.getquill.context.cassandra.encoding.{ Decoders, Encoders } + +package object catsEffect { + + lazy val testCeDB: CassandraCeContext[Literal.type, IO] = { + val c = CassandraContextConfig(LoadConfig("testStreamDB")) + new CassandraCeContext(Literal, c.session, c.preparedStatementCacheSize)(Async[IO]) with CassandraTestEntities with Encoders with Decoders + } + +} diff --git a/quill-ce/src/main/scala-2.12/io/getquill/context/ce/CeContext.scala b/quill-ce/src/main/scala-2.12/io/getquill/context/ce/CeContext.scala new file mode 100644 index 0000000000..e1807477dc --- /dev/null +++ b/quill-ce/src/main/scala-2.12/io/getquill/context/ce/CeContext.scala @@ -0,0 +1,18 @@ +package io.getquill.context.ce + +import io.getquill.NamingStrategy +import io.getquill.context.{ Context, ExecutionInfo, StreamingContext } +import fs2.{ Stream => FStream } +import scala.language.higherKinds + +trait CeContext[Idiom <: io.getquill.idiom.Idiom, Naming <: NamingStrategy, F[_]] + extends Context[Idiom, Naming] + with StreamingContext[Idiom, Naming] { + + override type StreamResult[T] = FStream[F, T] + override type Result[T] = F[T] + override type RunActionResult = Unit + override type RunBatchActionResult = Unit + override type RunQueryResult[T] = List[T] + override type RunQuerySingleResult[T] = T +} diff --git a/quill-ce/src/main/scala-2.13/io/getquill/context/ce/CeContext.scala b/quill-ce/src/main/scala-2.13/io/getquill/context/ce/CeContext.scala new file mode 100644 index 0000000000..e1807477dc --- /dev/null +++ b/quill-ce/src/main/scala-2.13/io/getquill/context/ce/CeContext.scala @@ -0,0 +1,18 @@ +package io.getquill.context.ce + +import io.getquill.NamingStrategy +import io.getquill.context.{ Context, ExecutionInfo, StreamingContext } +import fs2.{ Stream => FStream } +import scala.language.higherKinds + +trait CeContext[Idiom <: io.getquill.idiom.Idiom, Naming <: NamingStrategy, F[_]] + extends Context[Idiom, Naming] + with StreamingContext[Idiom, Naming] { + + override type StreamResult[T] = FStream[F, T] + override type Result[T] = F[T] + override type RunActionResult = Unit + override type RunBatchActionResult = Unit + override type RunQueryResult[T] = List[T] + override type RunQuerySingleResult[T] = T +}