-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from davenverse/enablePureCross
Enable Pure Cross Sqlite
- Loading branch information
Showing
10 changed files
with
293 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
cross/js/src/main/scala/io/chrisdavenport/sqlitesjs/cross/PackagePlatform.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
// import io.chrisdavenport.sqlitesjs.Sqlite | ||
|
||
trait PackagePlatform { | ||
// Warning: SJS encodes Booleans as Ints. If using boolean it will fail, use IntBoolean directly | ||
// for automatic or contramap from Decoder[IntBoolean] to build your decoder for use with booleans. | ||
type Write[A] = io.circe.Encoder[A] | ||
type Read[A] = io.circe.Decoder[A] | ||
|
||
} |
44 changes: 44 additions & 0 deletions
44
cross/js/src/main/scala/io/chrisdavenport/sqlitesjs/cross/SqliteCrossCompanionPlatform.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
import cats.effect.kernel._ | ||
import cats.syntax.all._ | ||
import io.chrisdavenport.sqlitesjs.Sqlite | ||
import io.circe.{Json, JsonNumber, JsonObject} | ||
|
||
trait SqliteCrossCompanionPlatform { | ||
def impl[F[_]: Async](path: String): Resource[F, SqliteCross[F]] = { | ||
Sqlite.fromFile(path).map(new SqliteCrossJSImpl[F](_)) | ||
} | ||
|
||
private class SqliteCrossJSImpl[F[_]: Async](sqlite: Sqlite[F]) extends SqliteCross[F]{ | ||
|
||
private def encode[A: Write](a: A): List[Json] = { | ||
val json = io.circe.Encoder[A].apply(a) | ||
def toList(json: Json): List[Json] = { | ||
json.fold( | ||
jsonNull = List(Json.Null), | ||
jsonBoolean = {(bool: Boolean) => List({if (bool) Json.fromInt(1) else Json.fromInt(0)})}, // Throw maybe | ||
jsonNumber = {(number: JsonNumber) => List(Json.fromJsonNumber(number))}, | ||
jsonString = {(s: String) => List(Json.fromString(s))}, | ||
jsonArray = {(v: Vector[Json]) => v.toList}, // Throw Maybe | ||
jsonObject = {(obj: JsonObject) => obj.toList.flatMap(a => toList(a._2))} // Throw maybe? | ||
) | ||
} | ||
toList(json) | ||
} | ||
|
||
def exec(sql: String): F[Unit] = sqlite.exec(sql) | ||
def get[B: Read](sql: String): F[Option[B]] = sqlite.get(sql).flatMap(_.traverse(_.as[B]).liftTo[F]) | ||
def get[A: Write, B: Read](sql: String, write: A): F[Option[B]] = { | ||
sqlite.get(sql, encode(write)).flatMap(_.traverse(_.as[B]).liftTo[F]) | ||
} | ||
|
||
def all[B: Read](sql: String): F[List[B]] = | ||
sqlite.all(sql).flatMap(_.traverse(_.as[B]).liftTo[F]) | ||
def all[A: Write, B: Read](sql: String, write: A): F[List[B]] = | ||
sqlite.all(sql, encode(write)).flatMap(_.traverse(_.as[B]).liftTo[F]) | ||
|
||
def run(sql: String): F[Int] = sqlite.run(sql) | ||
def run[A: Write](sql: String, write: A): F[Int] = sqlite.run(sql, encode(write)) | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
cross/jvm/src/main/scala/io/chrisdavenport/sqlitesjs/cross/PackagePlatform.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
|
||
|
||
trait PackagePlatform { | ||
// type SqliteConnection[F[_]] = doobie.Transactor[F] | ||
type Write[A] = doobie.Write[A] | ||
type Read[A] = doobie.Read[A] | ||
} |
28 changes: 28 additions & 0 deletions
28
...s/jvm/src/main/scala/io/chrisdavenport/sqlitesjs/cross/SqliteCrossCompanionPlatform.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
import doobie.{Write => _, Read => _, _} | ||
import doobie.syntax.all._ | ||
import cats.effect.kernel._ | ||
import cats.syntax.all._ | ||
|
||
trait SqliteCrossCompanionPlatform { | ||
def impl[F[_]: Async](path: String): Resource[F, SqliteCross[F]] = Resource.pure[F, SqliteCross[F]]{ | ||
val ts = Transactor.fromDriverManager[F]("org.sqlite.JDBC", s"jdbc:sqlite:${path.toString}", "", "") | ||
new SqliteCrossJVMImpl[F](ts) | ||
} | ||
|
||
private class SqliteCrossJVMImpl[F[_]: MonadCancelThrow](ts: Transactor[F]) extends SqliteCross[F]{ | ||
def exec(sql: String): F[Unit] = Update(sql).run(()).transact(ts).void | ||
def get[B: Read](sql: String): F[Option[B]] = Query0(sql).option.transact(ts) | ||
def get[A: Write, B: Read](sql: String, write: A): F[Option[B]] = | ||
Query[A, B](sql).option(write).transact(ts) | ||
|
||
def all[B: Read](sql: String): F[List[B]] = | ||
Query0(sql).to[List].transact(ts) | ||
def all[A: Write, B: Read](sql: String, write: A): F[List[B]] = | ||
Query[A, B](sql).to[List](write).transact(ts) | ||
|
||
def run(sql: String): F[Int] = Update(sql).run(()).transact(ts) | ||
def run[A: Write](sql: String, write: A): F[Int] = Update[A](sql).run(write).transact(ts) | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
cross/shared/src/main/scala/io/chrisdavenport/sqlitesjs/cross/IntBoolean.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
import io.circe._ | ||
import cats.syntax.all._ | ||
|
||
// Use this rather than boolean in models, otherwise javascript platform will | ||
// die. | ||
case class IntBoolean(boolean: Boolean) | ||
object IntBoolean { | ||
implicit val decoder: Decoder[IntBoolean] = new Decoder[IntBoolean]{ | ||
def apply(c: HCursor): Decoder.Result[IntBoolean] = | ||
Decoder[Int].emap{ | ||
case 0 => IntBoolean(false).asRight | ||
case 1 => IntBoolean(true).asRight | ||
case other => s"Invalid IntBoolean: $other".asLeft | ||
}.or(Decoder.decodeBoolean.map(IntBoolean(_)))(c) | ||
} | ||
|
||
// implicit val decoder: Decoder[IntBoolean] = Decoder[Int].emap{ | ||
// case 0 => IntBoolean(false).asRight | ||
// case 1 => IntBoolean(true).asRight | ||
// case other => s"Invalid IntBoolean: $other".asLeft | ||
// } | ||
|
||
implicit val encoder: Encoder[IntBoolean] = Encoder[Int].contramap[IntBoolean]{ | ||
case IntBoolean(true) => 1 | ||
case IntBoolean(false) => 0 | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
cross/shared/src/main/scala/io/chrisdavenport/sqlitesjs/cross/SqliteCross.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package io.chrisdavenport.sqlitesjs.cross | ||
|
||
|
||
trait SqliteCross[F[_]]{ | ||
def exec(sql: String): F[Unit] | ||
// Uses Positional Encoding | ||
def get[B: Read](sql: String): F[Option[B]] | ||
def get[A: Write, B: Read](sql: String, write: A): F[Option[B]] | ||
|
||
|
||
def all[B: Read](sql: String): F[List[B]] | ||
def all[A: Write, B: Read](sql: String, write: A): F[List[B]] | ||
|
||
def run(sql: String): F[Int] | ||
def run[A: Write](sql: String, write: A): F[Int] | ||
} | ||
|
||
object SqliteCross extends SqliteCrossCompanionPlatform {} | ||
|
||
|
5 changes: 5 additions & 0 deletions
5
cross/shared/src/main/scala/io/chrisdavenport/sqlitesjs/cross/package.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package io.chrisdavenport.sqlitesjs | ||
|
||
package object cross extends PackagePlatform { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import io.chrisdavenport.sqlitesjs.cross._ | ||
|
||
import cats.syntax.all._ | ||
import cats.effect._ | ||
|
||
object Example extends IOApp.Simple { | ||
|
||
def run: IO[Unit] = SqliteCross.impl[IO]("testing/testCrossExample.sqlite").use{ cross => | ||
|
||
for { | ||
_ <- cross.exec(createTableStatement) | ||
init <- cross.all[Hello](select) | ||
_ <- if (init.isEmpty) IO.unit else IO.println("Initial State:") | ||
_ <- init.traverse(h => IO.println(h)) | ||
_ <- IO.println("Please Say Hello To Someone or say CLEAR:") | ||
// Hangs on js | ||
// who <- fs2.io.stdin[IO](4096).through(fs2.text.utf8.decode).takeThrough(_.contains("\n")).compile.string.map(_.trim()) | ||
who <- std.Console[IO].readLine // BROKEN | ||
_ <- if (who === "CLEAR") clear(cross) else sayHello(cross, who) | ||
} yield () | ||
} | ||
|
||
def sayHello(cross: SqliteCross[IO], name: String): IO[Unit] = for { | ||
now <- Clock[IO].realTime.map(_.toMillis) | ||
record = Hello(name, now, IntBoolean(name.toUpperCase() === name)) | ||
_ <- cross.run(upsert, record) | ||
_ <- IO.println("") >> IO.println("Current State:") | ||
after <- cross.all[Hello](select) | ||
_ <- after.traverse(h => IO.println(h)) | ||
} yield () | ||
|
||
def clear(cross: SqliteCross[IO]): IO[Unit] = { | ||
cross.run(del).flatMap(i => IO.println(s"Cleared $i rows")) | ||
} | ||
|
||
case class Hello(name: String, lastAccessed: Long, allCaps: IntBoolean) | ||
// IntBoolean is used to get booleans in a compatible fashion without custom encoding/generic logic. | ||
// You can use a custom decoder which maps to Boolean from IntBoolean if you do want to prevent | ||
// intermediates appearing in your code. | ||
object Hello { | ||
// Can use anything to get the decoder for this | ||
// Generic for 2.12 | ||
// derives Codec.AsObject for 3 | ||
// implicit val codec: io.circe.Codec[Hello] = ??? | ||
// In this example I use semi-auto derivation | ||
implicit val codec: io.circe.Codec[Hello] = io.circe.generic.semiauto.deriveCodec[Hello] | ||
|
||
// An example of how to do this without use of custom type, but explicitly encoding/decoding | ||
// implicit val decoder: Decoder[Hello] = new Decoder[Hello]{ | ||
// def apply(h: HCursor) = for { | ||
// name <- h.downField("name").as[String] | ||
// accessed <- h.downField("lastAccessed").as[Long] | ||
// allCaps <- h.downField("allCaps").as[IntBoolean].map(_.boolean) | ||
// } yield Hello(name, accessed, allCaps) | ||
// } | ||
|
||
// implicit val encoder: Encoder[Hello] = new Encoder[Hello]{ | ||
// def apply(a: Hello): Json = Json.obj( | ||
// "name" -> a.name.asJson, | ||
// "lastAccessed" -> a.lastAccessed.asJson, | ||
// "allCaps" -> IntBoolean(a.allCaps).asJson | ||
// ) | ||
// } | ||
} | ||
|
||
val createTableStatement = { | ||
"""CREATE TABLE IF NOT EXISTS hello ( | ||
|name TEXT NOT NULL PRIMARY KEY, | ||
|lastAccessed INTEGER NOT NULL, | ||
|allCaps INTEGER NOT NULL)""".stripMargin | ||
} | ||
|
||
val select = { | ||
"SELECT name,lastAccessed,allCaps FROM hello" | ||
} | ||
|
||
val upsert = { | ||
"""INSERT OR REPLACE INTO hello (name, lastAccessed, allCaps) VALUES (?, ?, ?)""" | ||
} | ||
|
||
val del = """DELETE FROM hello""" | ||
|
||
} |