Closed oyvindberg closed 3 months ago
This is not entirely done - polish will come later.
To use this you now need to copy one of the PaginationQueryXXX
files from the tests, and adapt it to your chosen database and json libraries. It'll be easier to use if you enable JSON support in codegen so you have instances for all types lying around. Otherwise you may have to define local instances for column types you use.
Further work is to polish the story with codegen for a combination of json and database library, and output working boilerplate. then documentation and blog post later.
I'll paste an example integration file PaginationQuery
for doobie and circe here:
package adventureworks
import doobie.free.connection.{ConnectionIO, raiseError}
import io.circe.*
import typo.dsl.pagination.*
import typo.dsl.{SelectBuilder, SortOrder, SqlExpr}
class PaginationQueryCirce[Fields[_], Row](underlying: PaginationQuery[Fields, Row, Json]) {
def andOn[T, N[_]](v: Fields[Row] => SortOrder[T, N, Row])(implicit
e: Encoder[N[T]],
d: Decoder[N[T]],
asConst: SqlExpr.Const.As[T, N, Row]
): PaginationQueryCirce[Fields, Row] =
new PaginationQueryCirce(underlying.andOn(v)(PaginationQueryCirce.abstractCodec)(asConst))
def done(limit: Int, continueFrom: Option[ClientCursor[Json]]): Either[String, (SelectBuilder[Fields, Row], ServerCursor[Fields, Row, Json])] =
underlying.done(limit, continueFrom)
def toList(limit: Int, continueFrom: Option[ClientCursor[Json]]): ConnectionIO[(List[Row], Option[ClientCursor[Json]])] =
underlying.done(limit, continueFrom) match {
case Left(msg) =>
raiseError(new IllegalArgumentException(msg))
case Right((newQuery, cursor)) =>
newQuery.toList.map(rows => (rows, cursor.withNewResults(rows).map(_.clientCursor)))
}
}
object PaginationQueryCirce {
implicit val clientCursorEncoder: Encoder[ClientCursor[Json]] =
Encoder[Map[String, Json]].contramap(_.parts.map { case (k, v) => (k.expr, v) }.toMap)
implicit val clientCursorDecoder: Decoder[ClientCursor[Json]] =
Decoder[Map[String, Json]].map(parts => ClientCursor(parts.map { case (k, v) => (SortOrderRepr(k), v) }))
implicit class PaginationQuerySyntax[Fields[_], Row](private val query: SelectBuilder[Fields, Row]) extends AnyVal {
def seekPaginationOn[T, N[_]](v: Fields[Row] => SortOrder[T, N, Row])(implicit
e: Encoder[N[T]],
d: Decoder[N[T]],
asConst: SqlExpr.Const.As[T, N, Row]
): PaginationQueryCirce[Fields, Row] =
new PaginationQueryCirce(new PaginationQuery(query, Nil).andOn(v)(PaginationQueryCirce.abstractCodec))
}
def abstractCodec[N[_], T](implicit e: Encoder[N[T]], d: Decoder[N[T]]): AbstractJsonCodec[N[T], Json] =
new AbstractJsonCodec[N[T], Json](e.apply, json => d.decodeJson(json).left.map(_.message))
}
Builds on
seek
-support added in #100Pagination with
ClientCursor
On top of
seek
I managed to write a completely generic pagination mechanism. It works for any query, including when you join many tables together. It also works with most SQL expressions, as long as they can be compared in PG. There is a new DSL for this, built on top of the normal one. It's not built directly in, because JSON is used, and the SQL DSL shouldn't have a dependency on a JSON library.It looks like this:
prints:
Uses
seek
In the implementation you can see that it's built on top of
seek
:Properties
I'll copy in the documentation for
ClientCursor
to describe the properties of the cursor:todo: