Skip to content

Commit 02548dc

Browse files
committed
experiment with pre-computing tuple type
evidence not to go with plain named tuples further wip work with projecting named tupke type into the record
1 parent 5e6edca commit 02548dc

11 files changed

+200
-79
lines changed

build.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ trait Common extends CommonBase with CrossScalaModule
7676
object `scalasql-namedtuples` extends CommonBase {
7777
def scalaVersion: T[String] = scala3NamedTuples
7878
def millSourcePath: os.Path = scalasql(scala3).millSourcePath / "namedtuples"
79-
def moduleDeps: Seq[PublishModule] = Seq(scalasql(scala3).query)
79+
def moduleDeps: Seq[PublishModule] = Seq(scalasql(scala3))
8080

8181
// override def scalacOptions: Target[Seq[String]] = T {
8282
// super.scalacOptions() :+ "-Xprint:inlining"
Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,89 @@
11
package scalasql.namedtuples
22

3-
import scala.NamedTuple.AnyNamedTuple
4-
5-
import scalasql.query.Table
3+
import scalasql.core.DbApi.Impl
64
import scalasql.core.DialectTypeMappers
5+
import scalasql.core.Expr
76
import scalasql.core.Queryable
8-
import scalasql.query.Column
7+
import scalasql.core.Queryable.ResultSetIterator
98
import scalasql.core.Sc
10-
import scalasql.core.Expr
9+
import scalasql.dialects.Dialect
10+
import scalasql.dialects.TableOps
11+
import scalasql.namedtuples.SimpleTable.Internal.Tombstone
12+
import scalasql.query.Column
13+
import scalasql.query.Query
14+
import scalasql.query.Table
15+
import scalasql.query.Table.ImplicitMetadata
16+
import sourcecode.Name
17+
18+
import scala.NamedTuple.AnyNamedTuple
19+
import scala.language.implicitConversions
20+
21+
class SimpleTable[C]()(
22+
using val name0: sourcecode.Name,
23+
metadata0: => SimpleTable.Metadata[C]
24+
) extends Table.Base
25+
with SimpleTable.LowPri[C] {
26+
27+
lazy val metadata: SimpleTable.Metadata[C] = metadata0
28+
29+
override protected[scalasql] def tableName: String = name0.value
30+
31+
override protected[scalasql] def schemaName: String = ""
32+
33+
override protected[scalasql] def tableLabels: Seq[String] = {
34+
metadata.metadata0.walkLabels0()
35+
}
36+
37+
override protected[scalasql] def escape: Boolean = false
1138

12-
class SimpleTable[C <: SimpleTable.Source]()(
13-
using name: sourcecode.Name,
14-
metadata0: SimpleTable.Metadata[C]
15-
) extends Table[SimpleTable.Lift[C]](using name, metadata0.metadata0) {
1639
given simpleTableImplicitMetadata: SimpleTable.WrappedMetadata[C] =
17-
SimpleTable.WrappedMetadata(metadata0)
40+
SimpleTable.WrappedMetadata(metadata)
41+
42+
implicit def containerQr(
43+
implicit dialect: DialectTypeMappers,
44+
f: SimpleTableMacros.Mask[C]
45+
): Queryable.Row[f.Result[Expr], C] =
46+
val tableMetadata = metadata.metadata0
47+
tableMetadata
48+
.queryable(
49+
tableMetadata.walkLabels0,
50+
dialect,
51+
new Table.Metadata.QueryableProxy(tableMetadata.queryables(dialect, _))
52+
)
53+
.asInstanceOf[Queryable.Row[f.Result[Expr], C]]
1854
}
1955

2056
object SimpleTable {
2157

22-
/**
23-
* Marker class that signals that a data type is convertable to an SQL table row.
24-
* @note this must be a class to convince the match type reducer that it provably can't be mixed
25-
* into various column types such as java.util.Date, geny.Bytes, or scala.Option.
26-
*/
27-
abstract class Source
28-
29-
type Lift[C] = [T[_]] =>> T[Internal.Tombstone.type] match {
30-
case Expr[?] => Record[C, T]
31-
case _ => C
58+
trait LowPri[C] { this: SimpleTable[C] =>
59+
implicit def containerQr2(
60+
implicit dialect: DialectTypeMappers,
61+
f: SimpleTableMacros.Mask[C]
62+
): Queryable.Row[f.Result[Column], C] =
63+
containerQr.asInstanceOf[Queryable.Row[f.Result[Column], C]]
3264
}
3365

34-
final class Record[C, T[_]](data: IArray[AnyRef]) extends Selectable:
35-
type Fields = NamedTuple.Map[
36-
NamedTuple.From[C],
37-
[X] =>> X match {
38-
case Source => Record[X, T]
39-
case _ => T[X]
40-
}
41-
]
66+
implicit def TableOpsConv[C: {SimpleTableMacros.Mask as f}](
67+
t: SimpleTable[C]
68+
)(using dialect: Dialect): TableOps[f.Result] =
69+
// assume types in f.Result matches
70+
val tableMetadata = t.metadata.metadata0.asInstanceOf[Table.Metadata[f.Result]]
71+
dialect.TableOpsConv(new Table[f.Result](using t.name0, tableMetadata) {
72+
override protected[scalasql] def tableName: String = t.tableName
73+
74+
override protected[scalasql] def schemaName: String = t.schemaName
75+
76+
override protected[scalasql] def tableLabels: Seq[String] = t.tableLabels
77+
78+
override protected[scalasql] def escape: Boolean = t.escape
79+
})
80+
81+
final class Record[C, Mask <: AnyNamedTuple](data: IArray[AnyRef]) extends Selectable:
82+
type Fields = Mask
4283
def recordIterator: Iterator[Any] = data.iterator.asInstanceOf[Iterator[Any]]
4384
def apply(i: Int): AnyRef = data(i)
44-
def updates(fs: ((u: RecordUpdater[C, T]) => u.Patch)*): Record[C, T] =
45-
val u = recordUpdater[C, T]
85+
def updates(fs: ((u: RecordUpdater[C, Mask]) => u.Patch)*): Record[C, Mask] =
86+
val u = recordUpdater[C, Mask]
4687
val arr = IArray.genericWrapArray(data).toArray
4788
fs.foreach: f =>
4889
val patch = f(u)
@@ -53,17 +94,14 @@ object SimpleTable {
5394
inline def selectDynamic(name: String): AnyRef =
5495
apply(compiletime.constValue[Record.IndexOf[name.type, Record.Names[C], 0]])
5596

56-
private object RecordUpdaterImpl extends RecordUpdater[Any, [T] =>> Any]
57-
def recordUpdater[C, T[_]]: RecordUpdater[C, T] =
58-
RecordUpdaterImpl.asInstanceOf[RecordUpdater[C, T]]
59-
sealed trait RecordUpdater[C, T[_]] extends Selectable:
97+
private object RecordUpdaterImpl extends RecordUpdater[Any, AnyNamedTuple]
98+
def recordUpdater[C, Mask <: AnyNamedTuple]: RecordUpdater[C, Mask] =
99+
RecordUpdaterImpl.asInstanceOf[RecordUpdater[C, Mask]]
100+
sealed trait RecordUpdater[C, Mask <: AnyNamedTuple] extends Selectable:
60101
final case class Patch(idx: Int, f: AnyRef => AnyRef)
61102
type Fields = NamedTuple.Map[
62-
NamedTuple.From[C],
63-
[X] =>> X match {
64-
case Source => (Record[X, T] => Record[X, T]) => Patch
65-
case _ => (T[X] => T[X]) => Patch
66-
}
103+
Mask,
104+
[X] =>> (X => X) => Patch
67105
]
68106
def apply(i: Int): (AnyRef => AnyRef) => Patch =
69107
f => Patch(i, f)
@@ -78,7 +116,7 @@ object SimpleTable {
78116
case N *: _ => Acc
79117
case _ *: t => IndexOf[N, t, S[Acc]]
80118
}
81-
def fromIArray(data: IArray[AnyRef]): Record[Any, [T] =>> Any] =
119+
def fromIArray(data: IArray[AnyRef]): Record[Any, AnyNamedTuple] =
82120
Record(data)
83121

84122
object Internal {
@@ -92,17 +130,19 @@ object SimpleTable {
92130
def metadata: Metadata[C] = m
93131
}
94132
}
95-
class Metadata[C](val metadata0: Table.Metadata[Lift[C]]):
133+
class Metadata[C](val f: SimpleTableMacros.Mask[C])(
134+
val metadata0: Table.Metadata[f.Result]
135+
):
96136
def rowExpr(
97137
mappers: DialectTypeMappers
98-
): Queryable.Row[Record[C, Expr], C] =
138+
): Queryable.Row[f.Result[Expr], C] =
99139
metadata0
100140
.queryable(
101141
metadata0.walkLabels0,
102142
mappers,
103143
new Table.Metadata.QueryableProxy(metadata0.queryables(mappers, _))
104144
)
105-
.asInstanceOf[Queryable.Row[Record[C, Expr], C]]
145+
.asInstanceOf[Queryable.Row[f.Result[Expr], C]]
106146

107147
object Metadata extends SimpleTableMacros
108148
}

scalasql/namedtuples/src/SimpleTableMacros.scala

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,78 @@ import java.util.function.UnaryOperator
1515
import scala.annotation.nowarn
1616
import scalasql.namedtuples.SimpleTableMacros.BaseLabels
1717
import scalasql.core.TypeMapper
18+
import scala.annotation.tailrec
1819

1920
object SimpleTableMacros {
21+
22+
trait Mask[C]:
23+
type Result[T[_]] <: SimpleTable.Record[C, ?] | C
24+
25+
object Mask:
26+
import scala.quoted.{Expr as QExpr, *}
27+
object Impl extends Mask[Any]:
28+
type Result[T[_]] = Any
29+
30+
transparent inline given [C]: Mask[C] = ${ compute[C, NamedTuple.From[C]] }
31+
32+
def compute[C: Type, A <: AnyNamedTuple: Type](using Quotes): QExpr[Mask[C]] =
33+
computeRoot[C, NamedTuple.Names[A], NamedTuple.DropNames[A]]
34+
35+
def computeRoot[C: Type, N <: Tuple: Type, V <: Tuple: Type](
36+
using Quotes
37+
): QExpr[Mask[C]] =
38+
compute1[C, N, V](Nil) match
39+
case Some('[type res[T[_]] <: SimpleTable.Record[C, ?]; res]) =>
40+
'{
41+
Impl.asInstanceOf[
42+
Mask[
43+
C
44+
] {
45+
type Result[T[_]] = T[SimpleTable.Internal.Tombstone.type] match
46+
case Expr[?] => res[T]
47+
case _ => C
48+
}
49+
]
50+
}
51+
case _ =>
52+
'{
53+
compiletime.error(
54+
"Cannot find a Mask instance for the given type. Please ensure that the type is a case class or a named tuple."
55+
)
56+
}
57+
58+
@tailrec
59+
def compute1[C: Type, N <: Tuple: Type, V <: Tuple: Type](acc: List[Type[?]])(
60+
using Quotes
61+
): Option[Type[?]] =
62+
Type.of[V] match
63+
case '[type vs <: Tuple; (v *: `vs`)] =>
64+
QExpr.summon[SimpleTable.WrappedMetadata[v]] match
65+
case Some(_) =>
66+
computeRec[v] match
67+
case Some(res) =>
68+
compute1[C, N, vs](res :: acc)
69+
case _ =>
70+
None
71+
case _ =>
72+
compute1[C, N, vs](Type.of[[T[_]] =>> T[v]] :: acc)
73+
case '[EmptyTuple] =>
74+
val tpes =
75+
acc.reverse.foldRight(Type.of[[T[_]] =>> EmptyTuple].asInstanceOf[Type[?]])(
76+
(tpe, acc) =>
77+
((tpe, acc): @unchecked) match {
78+
case ('[type tpe[T[_]]; `tpe`], '[type acc[T[_]] <: Tuple; `acc`]) =>
79+
Type.of[[T[_]] =>> tpe[T] *: acc[T]]
80+
}
81+
)
82+
tpes match
83+
case '[type tpes[T[_]] <: Tuple; `tpes`] =>
84+
Some(Type.of[[T[_]] =>> SimpleTable.Record[C, NamedTuple.NamedTuple[N, tpes[T]]]])
85+
86+
def computeRec[V: Type](using Quotes): Option[Type[?]] =
87+
type vNT = NamedTuple.From[V]
88+
compute1[V, NamedTuple.Names[vNT], NamedTuple.DropNames[vNT]](Nil)
89+
2090
def asIArray[T: ClassTag](t: Tuple): IArray[T] = {
2191
IArray.from(t.productIterator.asInstanceOf[Iterator[T]])
2292
}
@@ -143,9 +213,9 @@ object SimpleTableMacros {
143213
factory(buf.result())
144214
}
145215

146-
def deconstruct[R <: SimpleTable.Record[?, ?]](
216+
def deconstruct(
147217
queryable: Table.Metadata.QueryableProxy
148-
)(c: Product): R = {
218+
)(c: Product): SimpleTable.Record[?, ?] = {
149219
var i = 0
150220
val buf = IArray.newBuilder[AnyRef]
151221
val fields = c.productIterator
@@ -156,15 +226,16 @@ object SimpleTableMacros {
156226
val row = queryable[Field, T](i)
157227
buf += row.deconstruct(field).asInstanceOf[AnyRef]
158228
i += 1
159-
SimpleTable.Record.fromIArray(buf.result()).asInstanceOf[R]
229+
SimpleTable.Record.fromIArray(buf.result())
160230
}
161231

162232
}
163233

164234
trait SimpleTableMacros {
165-
inline given initTableMetadata[C <: Product & SimpleTable.Source]: SimpleTable.Metadata[C] =
235+
inline given initTableMetadata: [C <: Product]
236+
=> (f: SimpleTableMacros.Mask[C]) => SimpleTable.Metadata[C] =
166237
def m = compiletime.summonInline[Mirror.ProductOf[C]]
167-
type Impl = SimpleTable.Lift[C]
238+
type Impl = f.Result
168239
type Labels = NamedTuple.Names[NamedTuple.From[C]]
169240
type Values = NamedTuple.DropNames[NamedTuple.From[C]]
170241
type Size = NamedTuple.Size[NamedTuple.From[C]]
@@ -197,17 +268,19 @@ trait SimpleTableMacros {
197268
walkLabels0: () => Seq[String],
198269
@nowarn("msg=unused") mappers: DialectTypeMappers,
199270
queryable: Table.Metadata.QueryableProxy
200-
): Queryable[Impl[Expr], Impl[Sc]] = Table.Internal.TableQueryable(
201-
walkLabels0,
202-
walkExprs0 = SimpleTableMacros.walkAllExprs(queryable),
203-
construct0 = args =>
204-
SimpleTableMacros.construct(queryable)(
205-
size = compiletime.constValue[Size],
206-
args = args,
207-
factory = SimpleTableMacros.make(m, _)
208-
),
209-
deconstruct0 = values => SimpleTableMacros.deconstruct[Impl[Expr]](queryable)(values)
210-
)
271+
): Queryable[Impl[Expr], Impl[Sc]] = Table.Internal
272+
.TableQueryable(
273+
walkLabels0,
274+
walkExprs0 = SimpleTableMacros.walkAllExprs(queryable),
275+
construct0 = args =>
276+
SimpleTableMacros.construct(queryable)(
277+
size = compiletime.constValue[Size],
278+
args = args,
279+
factory = SimpleTableMacros.make(m, _)
280+
),
281+
deconstruct0 = values => SimpleTableMacros.deconstruct(queryable)(values)
282+
)
283+
.asInstanceOf[Queryable[Impl[Expr], Impl[Sc]]]
211284

212285
def vExpr0(
213286
tableRef: TableRef,
@@ -221,5 +294,5 @@ trait SimpleTableMacros {
221294

222295
val metadata0 = Table.Metadata[Impl](queryables, walkLabels0, queryable, vExpr0)
223296

224-
SimpleTable.Metadata(metadata0)
297+
SimpleTable.Metadata(f)(metadata0)
225298
}

scalasql/namedtuples/test/src/datatypes/LargeObjectTest.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ case class LargeObjectA(
220220
a198: Int,
221221
a199: Int,
222222
b: LargeObjectB
223-
) extends SimpleTable.Source
223+
)
224224
object LargeObjectA extends SimpleTable[LargeObjectA]
225225

226226
case class LargeObjectB(
@@ -424,7 +424,7 @@ case class LargeObjectB(
424424
b197: Int,
425425
b198: Int,
426426
b199: Int
427-
) extends SimpleTable.Source
427+
)
428428
object LargeObjectB extends SimpleTable[LargeObjectB]
429429

430430
case class LargeObjectC(
@@ -628,5 +628,5 @@ case class LargeObjectC(
628628
c197: Int,
629629
c198: Int,
630630
c199: Int
631-
) extends SimpleTable.Source
631+
)
632632
object LargeObjectC extends SimpleTable[LargeObjectC]

0 commit comments

Comments
 (0)