Skip to content

Commit 059b90f

Browse files
authored
Merge pull request #91 from nob13/nob/unapply
#73 Deconstructing on Joins
2 parents f9bdafa + d745746 commit 059b90f

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

src/main/scala/usql/dao/QueryBuilder.scala

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ trait QueryBuilder[T] extends Query[T] {
1818
project(f(columnRootPath))
1919
}
2020

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

2323
/** Project using a Column Path */
2424
def project[P](p: ColumnPath[T, P]): QueryBuilder[P]
@@ -74,4 +74,65 @@ object QueryBuilder {
7474
def make[T](using tabular: SqlTabular[T]): QueryBuilderForTable[T] = {
7575
SimpleTableSelect(tabular)
7676
}
77+
78+
extension [A, B](qb: QueryBuilder[(A, B)])
79+
def map[P](
80+
f: (ColumnPath[(A, B), A], ColumnPath[(A, B), B]) => ColumnPath[(A, B), P]
81+
): QueryBuilder[P] = {
82+
val root = qb.columnRootPath
83+
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B), A]]
84+
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B), B]]
85+
qb.project(f(a, b))
86+
}
87+
88+
extension [A, B, C](qb: QueryBuilder[(A, B, C)])
89+
def map[P](
90+
f: (
91+
ColumnPath[(A, B, C), A],
92+
ColumnPath[(A, B, C), B],
93+
ColumnPath[(A, B, C), C]
94+
) => ColumnPath[(A, B, C), P]
95+
): QueryBuilder[P] = {
96+
val root = qb.columnRootPath
97+
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C), A]]
98+
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C), B]]
99+
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C), C]]
100+
qb.project(f(a, b, c))
101+
}
102+
103+
extension [A, B, C, D](qb: QueryBuilder[(A, B, C, D)])
104+
def map[P](
105+
f: (
106+
ColumnPath[(A, B, C, D), A],
107+
ColumnPath[(A, B, C, D), B],
108+
ColumnPath[(A, B, C, D), C],
109+
ColumnPath[(A, B, C, D), D]
110+
) => ColumnPath[(A, B, C, D), P]
111+
): QueryBuilder[P] = {
112+
val root = qb.columnRootPath
113+
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C, D), A]]
114+
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C, D), B]]
115+
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C, D), C]]
116+
val d = root.selectDynamic("_4").asInstanceOf[ColumnPath[(A, B, C, D), D]]
117+
qb.project(f(a, b, c, d))
118+
}
119+
120+
extension [A, B, C, D, E](qb: QueryBuilder[(A, B, C, D, E)])
121+
def map[P](
122+
f: (
123+
ColumnPath[(A, B, C, D, E), A],
124+
ColumnPath[(A, B, C, D, E), B],
125+
ColumnPath[(A, B, C, D, E), C],
126+
ColumnPath[(A, B, C, D, E), D],
127+
ColumnPath[(A, B, C, D, E), E]
128+
) => ColumnPath[(A, B, C, D, E), P]
129+
): QueryBuilder[P] = {
130+
val root = qb.columnRootPath
131+
val a = root.selectDynamic("_1").asInstanceOf[ColumnPath[(A, B, C, D, E), A]]
132+
val b = root.selectDynamic("_2").asInstanceOf[ColumnPath[(A, B, C, D, E), B]]
133+
val c = root.selectDynamic("_3").asInstanceOf[ColumnPath[(A, B, C, D, E), C]]
134+
val d = root.selectDynamic("_4").asInstanceOf[ColumnPath[(A, B, C, D, E), D]]
135+
val e = root.selectDynamic("_5").asInstanceOf[ColumnPath[(A, B, C, D, E), E]]
136+
qb.project(f(a, b, c, d, e))
137+
}
77138
}

src/test/scala/usql/dao/QueryBuilderTest.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,33 @@ class QueryBuilderTest extends TestBaseWithH2 {
129129
foo.count() shouldBe 3
130130
}
131131

132+
it should "support tuple-destructured map after a single join" in new EnvWithSamples {
133+
val foo: QueryBuilder[(Person, Int)] = Person.query
134+
.join(PersonPermission.query)(_.id === _.personId)
135+
.map((p, pp) => (p, pp.permissionId))
136+
foo.all() should contain theSameElementsAs Seq(
137+
alice -> read.id,
138+
alice -> write.id,
139+
bob -> read.id
140+
)
141+
142+
foo.count() shouldBe 3
143+
}
144+
145+
it should "support tuple-destructured map after a left join" in new EnvWithSamples {
146+
val q = Person.query
147+
.leftJoin(PersonPermission.query)(_.id === _.personId)
148+
.leftJoin(Permission.query)(_._2.permissionId === _.id)
149+
.map((pp, perm) => (pp._1.name, perm.name))
150+
151+
q.all() should contain theSameElementsAs Seq(
152+
("Alice", Some("Read")),
153+
("Alice", Some("Write")),
154+
("Bob", Some("Read")),
155+
("Charly", None)
156+
)
157+
}
158+
132159
it should "with simple joins" in new EnvWithSamples {
133160
val foo = Person.query
134161
.join(PersonPermission.query)(_.id === _.personId)

0 commit comments

Comments
 (0)