Skip to content

Commit

Permalink
Replace Quill examples with ScalaSql (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi authored Jan 1, 2024
1 parent 7bd5f2d commit 2a51b8c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 82 deletions.
4 changes: 2 additions & 2 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ object example extends Module{
object staticFiles2 extends Cross[StaticFiles2Module](scalaVersions)

trait TodoModule extends millbuild.example.todo.build.AppModule with LocalModule
object todo extends Cross[TodoModule](scala212, scala213) // uses quill, can't enable for Dotty yet
object todo extends Cross[TodoModule](scala213) // uses quill, can't enable for Dotty yet

trait TodoApiModule extends millbuild.example.todoApi.build.AppModule with LocalModule
object todoApi extends Cross[TodoApiModule](scalaVersions)

trait TodoDbModule extends millbuild.example.todoDb.build.AppModule with LocalModule
object todoDb extends Cross[TodoDbModule](scala212, scala213) // uses quill, can't enable for Dotty yet
object todoDb extends Cross[TodoDbModule](scala213) // uses quill, can't enable for Dotty yet

trait TwirlModule extends millbuild.example.twirl.build.AppModule with LocalModule
object twirl extends Cross[TwirlModule](scalaVersions)
Expand Down
78 changes: 36 additions & 42 deletions example/todo/app/src/TodoServer.scala
Original file line number Diff line number Diff line change
@@ -1,102 +1,96 @@
package app
import com.typesafe.config.ConfigFactory
import io.getquill.{SnakeCase, SqliteJdbcContext}
import io.getquill.context.ExecutionInfo
import scalasql.DbApi.Txn
import scalasql.Sc
import scalasql.SqliteDialect._
import scalatags.Text.all._
import scalatags.Text.tags2

object TodoServer extends cask.MainRoutes{
val tmpDb = java.nio.file.Files.createTempDirectory("todo-cask-sqlite")

object ctx extends SqliteJdbcContext(
SnakeCase,
ConfigFactory.parseString(
s"""{"driverClassName":"org.sqlite.JDBC","jdbcUrl":"jdbc:sqlite:$tmpDb/file.db"}"""
)
val sqliteDataSource = new org.sqlite.SQLiteDataSource()
sqliteDataSource.setUrl(s"jdbc:sqlite:$tmpDb/file.db")
lazy val sqliteClient = new scalasql.DbClient.DataSource(
sqliteDataSource,
config = new scalasql.Config {}
)

class transactional extends cask.RawDecorator{
class TransactionFailed(val value: cask.router.Result.Error) extends Exception
def wrapFunction(pctx: cask.Request, delegate: Delegate) = {
try ctx.transaction(
delegate(Map()) match{
try sqliteClient.transaction( txn =>
delegate(Map("txn" -> txn)) match{
case cask.router.Result.Success(t) => cask.router.Result.Success(t)
case e: cask.router.Result.Error => throw new TransactionFailed(e)
}
)
catch{case e: TransactionFailed => e.value}

}
}

case class Todo(id: Int, checked: Boolean, text: String)
case class Todo[T[_]](id: T[Int], checked: T[Boolean], text: T[String])
object Todo extends scalasql.Table[Todo]

ctx.executeAction(
sqliteClient.getAutoCommitClientConnection.updateRaw(
"""CREATE TABLE todo (
| id INTEGER PRIMARY KEY AUTOINCREMENT,
| checked BOOLEAN,
| text TEXT
|);
|""".stripMargin
)(ExecutionInfo.unknown, ())
ctx.executeAction(
"""INSERT INTO todo (checked, text) VALUES
|
|INSERT INTO todo (checked, text) VALUES
|(1, 'Get started with Cask'),
|(0, 'Profit!');
|""".stripMargin
)(ExecutionInfo.unknown, ())

import ctx._
)

@transactional
@cask.post("/list/:state")
def list(state: String) = renderBody(state).render
def list(state: String)(txn: Txn) = renderBody(state)(txn).render

@transactional
@cask.post("/add/:state")
def add(state: String, request: cask.Request) = {
def add(state: String, request: cask.Request)(implicit txn: Txn) = {
val body = request.text()
run(
query[Todo]
.insert(_.checked -> lift(false), _.text -> lift(body))
.returningGenerated(_.id)
)
txn.run(Todo.insert.columns(_.checked := false, _.text := body))
renderBody(state).render
}

@transactional
@cask.post("/delete/:state/:index")
def delete(state: String, index: Int) = {
run(query[Todo].filter(_.id == lift(index)).delete)
def delete(state: String, index: Int)(implicit txn: Txn) = {
txn.run(Todo.delete(_.id === index))
renderBody(state).render
}

@transactional
@cask.post("/toggle/:state/:index")
def toggle(state: String, index: Int) = {
run(query[Todo].filter(_.id == lift(index)).update(p => p.checked -> !p.checked))
def toggle(state: String, index: Int)(implicit txn: Txn) = {
txn.run(Todo.update(_.id === index).set(p => p.checked := !p.checked))
renderBody(state).render
}

@transactional
@cask.post("/clear-completed/:state")
def clearCompleted(state: String) = {
run(query[Todo].filter(_.checked).delete)
def clearCompleted(state: String)(implicit txn: Txn) = {
txn.run(Todo.delete(_.checked))
renderBody(state).render
}

@transactional
@cask.post("/toggle-all/:state")
def toggleAll(state: String) = {
val next = run(query[Todo].filter(_.checked).size) != 0
run(query[Todo].update(_.checked -> !lift(next)))
def toggleAll(state: String)(implicit txn: Txn) = {
val next = txn.run(Todo.select.filter(_.checked).size) != 0
txn.run(Todo.update(_ => true).set(_.checked := !next))
renderBody(state).render
}

def renderBody(state: String) = {
def renderBody(state: String)(implicit txn: Txn) = {
val filteredTodos = state match{
case "all" => run(query[Todo]).sortBy(-_.id)
case "active" => run(query[Todo].filter(!_.checked)).sortBy(-_.id)
case "completed" => run(query[Todo].filter(_.checked)).sortBy(-_.id)
case "all" => txn.run(Todo.select).sortBy(-_.id)
case "active" => txn.run(Todo.select.filter(!_.checked)).sortBy(-_.id)
case "completed" => txn.run(Todo.select.filter(_.checked)).sortBy(-_.id)
}
frag(
header(cls := "header",
Expand All @@ -108,7 +102,7 @@ object TodoServer extends cask.MainRoutes{
id := "toggle-all",
cls := "toggle-all",
`type` := "checkbox",
if (run(query[Todo].filter(_.checked).size != 0)) checked else ()
if (txn.run(Todo.select.filter(_.checked).size !== 0)) checked else ()
),
label(`for` := "toggle-all","Mark all as complete"),
ul(cls := "todo-list",
Expand All @@ -130,7 +124,7 @@ object TodoServer extends cask.MainRoutes{
),
footer(cls := "footer",
span(cls := "todo-count",
strong(run(query[Todo].filter(!_.checked).size).toInt),
strong(txn.run(Todo.select.filter(!_.checked).size).toInt),
" items left"
),
ul(cls := "filters",
Expand All @@ -151,7 +145,7 @@ object TodoServer extends cask.MainRoutes{

@transactional
@cask.get("/")
def index() = {
def index()(implicit txn: Txn) = {
doctype("html")(
html(lang := "en",
head(
Expand Down
2 changes: 1 addition & 1 deletion example/todo/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ trait AppModule extends CrossScalaModule{

def ivyDeps = Agg[Dep](
ivy"org.xerial:sqlite-jdbc:3.42.0.0",
ivy"io.getquill::quill-jdbc:4.6.1",
ivy"com.lihaoyi::scalasql:0.1.0",
ivy"com.lihaoyi::scalatags:0.12.0",
ivy"org.slf4j:slf4j-simple:1.7.30",
)
Expand Down
67 changes: 32 additions & 35 deletions example/todoDb/app/src/TodoMvcDb.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package app
import com.typesafe.config.ConfigFactory
import io.getquill.{SqliteJdbcContext, SnakeCase}
import io.getquill.context.ExecutionInfo
import scalasql.DbApi.Txn
import scalasql.Sc
import scalasql.SqliteDialect._

object TodoMvcDb extends cask.MainRoutes{
val tmpDb = java.nio.file.Files.createTempDirectory("todo-cask-sqlite")

object ctx extends SqliteJdbcContext(
SnakeCase,
ConfigFactory.parseString(
s"""{"driverClassName":"org.sqlite.JDBC","jdbcUrl":"jdbc:sqlite:$tmpDb/file.db"}"""
)
val sqliteDataSource = new org.sqlite.SQLiteDataSource()
sqliteDataSource.setUrl(s"jdbc:sqlite:$tmpDb/file.db")
lazy val sqliteClient = new scalasql.DbClient.DataSource(
sqliteDataSource,
config = new scalasql.Config {}
)

class transactional extends cask.RawDecorator{
class TransactionFailed(val value: cask.router.Result.Error) extends Exception
def wrapFunction(pctx: cask.Request, delegate: Delegate) = {
try ctx.transaction(
delegate(Map()) match{
try sqliteClient.transaction( txn =>
delegate(Map("txn" -> txn)) match{
case cask.router.Result.Success(t) => cask.router.Result.Success(t)
case e: cask.router.Result.Error => throw new TransactionFailed(e)
}
Expand All @@ -27,60 +26,58 @@ object TodoMvcDb extends cask.MainRoutes{
}
}

case class Todo(id: Int, checked: Boolean, text: String)
object Todo{
implicit def todoRW = upickle.default.macroRW[Todo]
case class Todo[T[_]](id: T[Int], checked: T[Boolean], text: T[String])
object Todo extends scalasql.Table[Todo]{
implicit def todoRW = upickle.default.macroRW[Todo[Sc]]
}

ctx.executeAction(
sqliteClient.getAutoCommitClientConnection.updateRaw(
"""CREATE TABLE todo (
| id INTEGER PRIMARY KEY AUTOINCREMENT,
| checked BOOLEAN,
| text TEXT
|);
|""".stripMargin
)(ExecutionInfo.unknown, ())
ctx.executeAction(
"""INSERT INTO todo (checked, text) VALUES
|
|INSERT INTO todo (checked, text) VALUES
|(1, 'Get started with Cask'),
|(0, 'Profit!');
|""".stripMargin
)(ExecutionInfo.unknown, ())

import ctx._
)

@transactional
@cask.get("/list/:state")
def list(state: String) = {
def list(state: String)(txn: Txn) = {
val filteredTodos = state match{
case "all" => run(query[Todo])
case "active" => run(query[Todo].filter(!_.checked))
case "completed" => run(query[Todo].filter(_.checked))
case "all" => txn.run(Todo.select)
case "active" => txn.run(Todo.select.filter(!_.checked))
case "completed" => txn.run(Todo.select.filter(_.checked))
}
upickle.default.write(filteredTodos)
}

@transactional
@cask.post("/add")
def add(request: cask.Request) = {
def add(request: cask.Request)(txn: Txn) = {
val body = request.text()
run(
query[Todo]
.insert(_.checked -> lift(false), _.text -> lift(body))
.returningGenerated(_.id)
txn.run(
Todo
.insert
.columns(_.checked := false, _.text := body)
.returning(_.id)
.single
)
}

@transactional
@cask.post("/toggle/:index")
def toggle(index: Int) = {
run(query[Todo].filter(_.id == lift(index)).update(p => p.checked -> !p.checked))
def toggle(index: Int)(txn: Txn) = {
txn.run(Todo.update(_.id === index).set(p => p.checked := !p.checked))
}

@transactional
@cask.post("/delete/:index")
def delete(index: Int) = {
run(query[Todo].filter(_.id == lift(index)).delete)
def delete(index: Int)(txn: Txn) = {
txn.run(Todo.delete(_.id === index))
}

initialize()
Expand Down
3 changes: 1 addition & 2 deletions example/todoDb/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ trait AppModule extends CrossScalaModule{

def ivyDeps = Agg[Dep](
ivy"org.xerial:sqlite-jdbc:3.42.0.0",
ivy"io.getquill::quill-jdbc:4.6.1",
ivy"org.slf4j:slf4j-simple:1.7.30"
ivy"com.lihaoyi::scalasql:0.1.0",
)

object test extends ScalaTests with TestModule.Utest{
Expand Down

0 comments on commit 2a51b8c

Please sign in to comment.