Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic pagination based on seek #99

Merged
merged 3 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class PersonRepoImpl extends PersonRepo {
}
override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = {
val fs = List(
Some((Fragment.const(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPutOption(Meta.StringMeta.put))}")),
unsaved.one match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""one""""), fr"${fromWrite(value: Long)(Write.fromPut(Meta.LongMeta.put))}::int8"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""one""""), fr"${fromWrite(value: Long)(Write.fromPut(Meta.LongMeta.put))}::int8"))
},
unsaved.two match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""two""""), fr"${fromWrite(value: Option[String])(Write.fromPutOption(Meta.StringMeta.put))}"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""two""""), fr"${fromWrite(value: Option[String])(Write.fromPutOption(Meta.StringMeta.put))}"))
}
).flatten

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ class PersonRepoImpl extends PersonRepo {
}
override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = {
val fs = List(
Some((Fragment.const(s""""favourite_football_club_id""""), fr"${fromWrite(unsaved.favouriteFootballClubId)(Write.fromPut(FootballClubId.put))}")),
Some((Fragment.const(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""nick_name""""), fr"${fromWrite(unsaved.nickName)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""blog_url""""), fr"${fromWrite(unsaved.blogUrl)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""email""""), fr"${fromWrite(unsaved.email)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""phone""""), fr"${fromWrite(unsaved.phone)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""likes_pizza""""), fr"${fromWrite(unsaved.likesPizza)(Write.fromPut(Meta.BooleanMeta.put))}")),
Some((Fragment.const(s""""work_email""""), fr"${fromWrite(unsaved.workEmail)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""favourite_football_club_id""""), fr"${fromWrite(unsaved.favouriteFootballClubId)(Write.fromPut(FootballClubId.put))}")),
Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""nick_name""""), fr"${fromWrite(unsaved.nickName)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""blog_url""""), fr"${fromWrite(unsaved.blogUrl)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""email""""), fr"${fromWrite(unsaved.email)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""phone""""), fr"${fromWrite(unsaved.phone)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""likes_pizza""""), fr"${fromWrite(unsaved.likesPizza)(Write.fromPut(Meta.BooleanMeta.put))}")),
Some((Fragment.const0(s""""work_email""""), fr"${fromWrite(unsaved.workEmail)(Write.fromPutOption(Meta.StringMeta.put))}")),
unsaved.id match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""id""""), fr"${fromWrite(value: PersonId)(Write.fromPut(PersonId.put))}::int8"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: PersonId)(Write.fromPut(PersonId.put))}::int8"))
},
unsaved.maritalStatusId match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""marital_status_id""""), fr"${fromWrite(value: MaritalStatusId)(Write.fromPut(MaritalStatusId.put))}"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""marital_status_id""""), fr"${fromWrite(value: MaritalStatusId)(Write.fromPut(MaritalStatusId.put))}"))
},
unsaved.sector match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""sector""""), fr"${fromWrite(value: Sector)(Write.fromPut(Sector.put))}::myschema.sector"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""sector""""), fr"${fromWrite(value: Sector)(Write.fromPut(Sector.put))}::myschema.sector"))
},
unsaved.favoriteNumber match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""favorite_number""""), fr"${fromWrite(value: Number)(Write.fromPut(Number.put))}::myschema.number"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""favorite_number""""), fr"${fromWrite(value: Number)(Write.fromPut(Number.put))}::myschema.number"))
}
).flatten

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class PersonRepoImpl extends PersonRepo {
}
override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = {
val fs = List(
Some((Fragment.const(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPutOption(Meta.StringMeta.put))}")),
unsaved.one match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""one""""), fr"${fromWrite(value: Long)(Write.fromPut(Meta.LongMeta.put))}::int8"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""one""""), fr"${fromWrite(value: Long)(Write.fromPut(Meta.LongMeta.put))}::int8"))
},
unsaved.two match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""two""""), fr"${fromWrite(value: Option[String])(Write.fromPutOption(Meta.StringMeta.put))}"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""two""""), fr"${fromWrite(value: Option[String])(Write.fromPutOption(Meta.StringMeta.put))}"))
}
).flatten

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ class PersonRepoImpl extends PersonRepo {
}
override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = {
val fs = List(
Some((Fragment.const(s""""favourite_football_club_id""""), fr"${fromWrite(unsaved.favouriteFootballClubId)(Write.fromPut(FootballClubId.put))}")),
Some((Fragment.const(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""nick_name""""), fr"${fromWrite(unsaved.nickName)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""blog_url""""), fr"${fromWrite(unsaved.blogUrl)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""email""""), fr"${fromWrite(unsaved.email)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""phone""""), fr"${fromWrite(unsaved.phone)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const(s""""likes_pizza""""), fr"${fromWrite(unsaved.likesPizza)(Write.fromPut(Meta.BooleanMeta.put))}")),
Some((Fragment.const(s""""work_email""""), fr"${fromWrite(unsaved.workEmail)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""favourite_football_club_id""""), fr"${fromWrite(unsaved.favouriteFootballClubId)(Write.fromPut(FootballClubId.put))}")),
Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""nick_name""""), fr"${fromWrite(unsaved.nickName)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""blog_url""""), fr"${fromWrite(unsaved.blogUrl)(Write.fromPutOption(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""email""""), fr"${fromWrite(unsaved.email)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""phone""""), fr"${fromWrite(unsaved.phone)(Write.fromPut(Meta.StringMeta.put))}")),
Some((Fragment.const0(s""""likes_pizza""""), fr"${fromWrite(unsaved.likesPizza)(Write.fromPut(Meta.BooleanMeta.put))}")),
Some((Fragment.const0(s""""work_email""""), fr"${fromWrite(unsaved.workEmail)(Write.fromPutOption(Meta.StringMeta.put))}")),
unsaved.id match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""id""""), fr"${fromWrite(value: PersonId)(Write.fromPut(PersonId.put))}::int8"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: PersonId)(Write.fromPut(PersonId.put))}::int8"))
},
unsaved.maritalStatusId match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""marital_status_id""""), fr"${fromWrite(value: MaritalStatusId)(Write.fromPut(MaritalStatusId.put))}"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""marital_status_id""""), fr"${fromWrite(value: MaritalStatusId)(Write.fromPut(MaritalStatusId.put))}"))
},
unsaved.sector match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""sector""""), fr"${fromWrite(value: Sector)(Write.fromPut(Sector.put))}::myschema.sector"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""sector""""), fr"${fromWrite(value: Sector)(Write.fromPut(Sector.put))}::myschema.sector"))
},
unsaved.favoriteNumber match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""favorite_number""""), fr"${fromWrite(value: Number)(Write.fromPut(Number.put))}::myschema.number"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""favorite_number""""), fr"${fromWrite(value: Number)(Write.fromPut(Number.put))}::myschema.number"))
}
).flatten

Expand Down
10 changes: 5 additions & 5 deletions site/blog/emailaddress/EmailaddressRepoImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ class EmailaddressRepoImpl extends EmailaddressRepo {
}
override def insert(unsaved: EmailaddressRowUnsaved): ConnectionIO[EmailaddressRow] = {
val fs = List(
Some((Fragment.const(s""""businessentityid""""), fr"${unsaved.businessentityid}::int4")),
Some((Fragment.const(s""""emailaddress""""), fr"${unsaved.emailaddress}")),
Some((Fragment.const0(s""""businessentityid""""), fr"${unsaved.businessentityid}::int4")),
Some((Fragment.const0(s""""emailaddress""""), fr"${unsaved.emailaddress}")),
unsaved.emailaddressid match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""emailaddressid""""), fr"${value: Int}::int4"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""emailaddressid""""), fr"${value: Int}::int4"))
},
unsaved.rowguid match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""rowguid""""), fr"${value: TypoUUID}::uuid"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""rowguid""""), fr"${value: TypoUUID}::uuid"))
},
unsaved.modifieddate match {
case Defaulted.UseDefault => None
case Defaulted.Provided(value) => Some((Fragment.const(s""""modifieddate""""), fr"${value: TypoLocalDateTime}::timestamp"))
case Defaulted.Provided(value) => Some((Fragment.const0(s""""modifieddate""""), fr"${value: TypoLocalDateTime}::timestamp"))
}
).flatten

Expand Down
11 changes: 11 additions & 0 deletions typo-dsl-anorm/src/scala/typo/dsl/SqlExpr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ object SqlExpr {
}
}

object Const {
trait As[T, N[_], R] {
def apply(value: N[T]): SqlExpr.Const[T, N, R]
}

object As {
implicit def as[T, N[_], R](implicit T: ToParameterValue[N[T]], P: ParameterMetaData[T]): As[T, N, R] =
(value: N[T]) => SqlExpr.Const(value, T, P)
}
}

case class ArrayIndex[T, N1[_], N2[_], R](arr: SqlExpr[Array[T], N1, R], idx: SqlExpr[Int, N2, R], N: Nullability2[N1, N2, Option]) extends SqlExpr[T, Option, R] {
override def eval(row: R): Option[T] = {
N.mapN(arr.eval(row), idx.eval(row)) { (arr, idx) =>
Expand Down
24 changes: 24 additions & 0 deletions typo-dsl-anorm/src/scala/typo/dsl/pagination/SortOrderRepr.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package typo.dsl.pagination

import typo.dsl.SortOrderNoHkt

import java.util.concurrent.atomic.AtomicInteger

/** A client cursor is inherently tied to a set of sort orderings.
*
* As such we encode them in the cursor itself, and verify them on the way in.
*/
case class SortOrderRepr(expr: String) extends AnyVal

object SortOrderRepr {
def from[NT, R](x: SortOrderNoHkt[NT, R]): SortOrderRepr = {
// note `x.expr`! the value is independent of ascending/descending and nulls first/last
val fragment = x.expr.render(new AtomicInteger(0))
// todo: deconstructing the sql string and replacing `?` with the value would yield a more readable result
val sql = fragment.params match {
case Nil => fragment.sql
case nonEmpty => fragment.sql + ":" + nonEmpty.map(_.value.show).mkString(",")
}
SortOrderRepr(sql.trim)
}
}
2 changes: 1 addition & 1 deletion typo-dsl-doobie/src/scala/typo/dsl/DeleteBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object DeleteBuilder {

def mkSql(counter: AtomicInteger): Fragment = {
List[Iterable[Fragment]](
Some(fr"delete from ${Fragment.const(name)}"),
Some(fr"delete from ${Fragment.const0(name)}"),
params.where
.map(w => w(structure.fields))
.reduceLeftOption(_.and(_))
Expand Down
18 changes: 9 additions & 9 deletions typo-dsl-doobie/src/scala/typo/dsl/SelectBuilderSql.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ object SelectBuilderSql {

private def sql(counter: AtomicInteger): Fragment = {
val cols = structure.columns
.map(x => Fragment.const(x.sqlReadCast.foldLeft("\"" + x.value + "\"") { case (acc, cast) => s"$acc::$cast" }))
.intercalate(Fragment.const(", "))
val baseSql = fr"select $cols from ${Fragment.const(name)}"
.map(x => Fragment.const0(x.sqlReadCast.foldLeft("\"" + x.value + "\"") { case (acc, cast) => s"$acc::$cast" }))
.intercalate(Fragment.const0(", "))
val baseSql = fr"select $cols from ${Fragment.const0(name)}"
SelectParams.render(structure.fields, baseSql, counter, params)
}

Expand Down Expand Up @@ -108,16 +108,16 @@ object SelectBuilderSql {
case NonEmptyList(one, Nil) => one.sqlFrag
case NonEmptyList(first, rest) =>
val prelude =
fr"""|select ${instance.columns.map(c => Fragment.const(c.value)).intercalate(Fragment.const(", "))}
fr"""|select ${instance.columns.map(c => Fragment.const0(c.value)).intercalate(Fragment.const0(", "))}
|from (
|${first.sqlFrag}
|) ${Fragment.const(first.alias)}
|) ${Fragment.const0(first.alias)}
|""".stripMargin

val joins = rest.map { case SelectBuilderSql.InstantiatedPart(alias, _, sqlFrag, joinFrag) =>
fr"""|join (
|$sqlFrag
|) ${Fragment.const(alias)} on $joinFrag
|) ${Fragment.const0(alias)} on $joinFrag
|""".stripMargin
}
prelude ++ joins.reduce(_ ++ _)
Expand Down Expand Up @@ -167,16 +167,16 @@ object SelectBuilderSql {
case NonEmptyList(one, Nil) => one.sqlFrag
case NonEmptyList(first, rest) =>
val prelude =
fr"""|select ${fragments.comma(instance.columns.map(c => Fragment.const(c.value)))}
fr"""|select ${fragments.comma(instance.columns.map(c => Fragment.const0(c.value)))}
|from (
| ${first.sqlFrag}
|) ${Fragment.const(first.alias)}
|) ${Fragment.const0(first.alias)}
|""".stripMargin

val joins = rest.map { case SelectBuilderSql.InstantiatedPart(alias, _, sqlFrag, joinFrag) =>
fr"""|left join (
|${sqlFrag}
|) ${Fragment.const(alias)} on $joinFrag
|) ${Fragment.const0(alias)} on $joinFrag
|""".stripMargin
}
prelude ++ joins.reduce(_ ++ _)
Expand Down
22 changes: 16 additions & 6 deletions typo-dsl-doobie/src/scala/typo/dsl/SqlExpr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ object SqlExpr {
) extends SqlExpr[T, N, R]
with FieldLikeNoHkt[N[T], R] {
override def eval(row: R): N[T] = get(row)
override def render(counter: AtomicInteger): Fragment = Fragment.const(value)
override def render(counter: AtomicInteger): Fragment = Fragment.const0(value)
}

sealed trait FieldLikeNotId[T, N[_], R] extends FieldLike[T, N, R] with FieldLikeNotIdNoHkt[N[T], R]
Expand All @@ -146,11 +146,21 @@ object SqlExpr {
override def render(counter: AtomicInteger): Fragment = {
val cast = P match {
case _: Put.Basic[_] => Fragment.empty
case p: Put.Advanced[_] => Fragment.const(s"::${p.schemaTypes.head}")
case p: Put.Advanced[_] => Fragment.const0(s"::${p.schemaTypes.head}")
}
fr"${W.toFragment(value)}$cast"
}
}
object Const {
trait As[T, N[_], R] {
def apply(value: N[T]): SqlExpr.Const[T, N, R]
}

object As {
implicit def as[T, N[_], R](implicit P: Put[T], W: Write[N[T]]): As[T, N, R] =
(value: N[T]) => SqlExpr.Const(value, P, W)
}
}

case class ArrayIndex[T, N1[_], N2[_], R](arr: SqlExpr[Array[T], N1, R], idx: SqlExpr[Int, N2, R], N: Nullability2[N1, N2, Option]) extends SqlExpr[T, Option, R] {
override def eval(row: R): Option[T] = {
Expand All @@ -168,14 +178,14 @@ object SqlExpr {
override def eval(row: R): N[O] =
N.mapN(arg1.eval(row))(f.eval)
override def render(counter: AtomicInteger): Fragment =
fr"${Fragment.const(f.name)}(${arg1.render(counter)})"
fr"${Fragment.const0(f.name)}(${arg1.render(counter)})"
}

case class Apply2[T1, T2, O, N1[_], N2[_], N[_], R](f: SqlFunction2[T1, T2, O], arg1: SqlExpr[T1, N1, R], arg2: SqlExpr[T2, N2, R], N: Nullability2[N1, N2, N]) extends SqlExpr[O, N, R] {
override def eval(row: R): N[O] =
N.mapN(arg1.eval(row), arg2.eval(row))(f.eval)
override def render(counter: AtomicInteger): Fragment =
fr"${Fragment.const(f.name)}(${arg1.render(counter)}, ${arg2.render(counter)})"
fr"${Fragment.const0(f.name)}(${arg1.render(counter)}, ${arg2.render(counter)})"
}

case class Apply3[T1, T2, T3, N1[_], N2[_], N3[_], N[_], O, R](
Expand All @@ -188,14 +198,14 @@ object SqlExpr {
override def eval(row: R): N[O] =
N.mapN(arg1.eval(row), arg2.eval(row), arg3.eval(row))(f.eval)
override def render(counter: AtomicInteger): Fragment =
fr"${Fragment.const(f.name)}(${arg1.render(counter)}, ${arg2.render(counter)}, ${arg3.render(counter)})"
fr"${Fragment.const0(f.name)}(${arg1.render(counter)}, ${arg2.render(counter)}, ${arg3.render(counter)})"
}

case class Binary[T1, T2, O, N1[_], N2[_], N[_], R](left: SqlExpr[T1, N1, R], op: SqlOperator[T1, T2, O], right: SqlExpr[T2, N2, R], N: Nullability2[N1, N2, N]) extends SqlExpr[O, N, R] {
override def eval(row: R): N[O] =
N.mapN(left.eval(row), right.eval(row))(op.eval)
override def render(counter: AtomicInteger): Fragment =
fr"(${left.render(counter)} ${Fragment.const(op.name)} ${right.render(counter)})"
fr"(${left.render(counter)} ${Fragment.const0(op.name)} ${right.render(counter)})"
}

case class Underlying[T, TT, N[_], R](expr: SqlExpr[T, N, R], bijection: Bijection[T, TT], N: Nullability[N]) extends SqlExpr[TT, N, R] {
Expand Down
Loading
Loading