Skip to content
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
63 changes: 62 additions & 1 deletion src/main/scala/usql/dao/QueryBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ trait QueryBuilder[T] extends Query[T] {
project(f(columnRootPath))
}

protected def columnRootPath: ColumnRootPath[T] = ColumnPath.make(using structure)
private[usql] def columnRootPath: ColumnRootPath[T] = ColumnPath.make(using structure)

/** Project using a Column Path */
def project[P](p: ColumnPath[T, P]): QueryBuilder[P]
Expand Down Expand Up @@ -74,4 +74,65 @@ object QueryBuilder {
def make[T](using tabular: SqlTabular[T]): QueryBuilderForTable[T] = {
SimpleTableSelect(tabular)
}

extension [A, B](qb: QueryBuilder[(A, B)])
def map[P](
f: (ColumnPath[(A, B), A], ColumnPath[(A, B), B]) => ColumnPath[(A, B), P]
): QueryBuilder[P] = {
val root = qb.columnRootPath
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B), A]]
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B), B]]
qb.project(f(a, b))
}

extension [A, B, C](qb: QueryBuilder[(A, B, C)])
def map[P](
f: (
ColumnPath[(A, B, C), A],
ColumnPath[(A, B, C), B],
ColumnPath[(A, B, C), C]
) => ColumnPath[(A, B, C), P]
): QueryBuilder[P] = {
val root = qb.columnRootPath
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C), A]]
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C), B]]
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C), C]]
qb.project(f(a, b, c))
}

extension [A, B, C, D](qb: QueryBuilder[(A, B, C, D)])
def map[P](
f: (
ColumnPath[(A, B, C, D), A],
ColumnPath[(A, B, C, D), B],
ColumnPath[(A, B, C, D), C],
ColumnPath[(A, B, C, D), D]
) => ColumnPath[(A, B, C, D), P]
): QueryBuilder[P] = {
val root = qb.columnRootPath
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C, D), A]]
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C, D), B]]
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C, D), C]]
val d = root.selectDynamic("_4").asInstanceOf[ColumnPath[(A, B, C, D), D]]
qb.project(f(a, b, c, d))
}

extension [A, B, C, D, E](qb: QueryBuilder[(A, B, C, D, E)])
def map[P](
f: (
ColumnPath[(A, B, C, D, E), A],
ColumnPath[(A, B, C, D, E), B],
ColumnPath[(A, B, C, D, E), C],
ColumnPath[(A, B, C, D, E), D],
ColumnPath[(A, B, C, D, E), E]
) => ColumnPath[(A, B, C, D, E), P]
): QueryBuilder[P] = {
val root = qb.columnRootPath
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C, D, E), A]]
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C, D, E), B]]
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C, D, E), C]]
val d = root.selectDynamic("_4").asInstanceOf[ColumnPath[(A, B, C, D, E), D]]
val e = root.selectDynamic("_5").asInstanceOf[ColumnPath[(A, B, C, D, E), E]]
qb.project(f(a, b, c, d, e))
}
}
27 changes: 27 additions & 0 deletions src/test/scala/usql/dao/QueryBuilderTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,33 @@ class QueryBuilderTest extends TestBaseWithH2 {
foo.count() shouldBe 3
}

it should "support tuple-destructured map after a single join" in new EnvWithSamples {
val foo: QueryBuilder[(Person, Int)] = Person.query
.join(PersonPermission.query)(_.id === _.personId)
.map((p, pp) => (p, pp.permissionId))
foo.all() should contain theSameElementsAs Seq(
alice -> read.id,
alice -> write.id,
bob -> read.id
)

foo.count() shouldBe 3
}

it should "support tuple-destructured map after a left join" in new EnvWithSamples {
val q = Person.query
.leftJoin(PersonPermission.query)(_.id === _.personId)
.leftJoin(Permission.query)(_._2.permissionId === _.id)
.map((pp, perm) => (pp._1.name, perm.name))

q.all() should contain theSameElementsAs Seq(
("Alice", Some("Read")),
("Alice", Some("Write")),
("Bob", Some("Read")),
("Charly", None)
)
}

it should "with simple joins" in new EnvWithSamples {
val foo = Person.query
.join(PersonPermission.query)(_.id === _.personId)
Expand Down
Loading