Skip to content

Commit 2f75019

Browse files
committed
Option.map
1 parent 32ae965 commit 2f75019

File tree

4 files changed

+52
-47
lines changed

4 files changed

+52
-47
lines changed

src/main/scala/tyql/DB.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,8 @@ def driverMain(): Unit = {
212212

213213
println(q.toQueryIR.toSQLString())
214214
println(q2.toQueryIR.toSQLString())
215+
216+
val mq = t.map(u => (hello = u.name.map(a => a.length).map(a => a + 12)))
217+
218+
println(mq.toQueryIR.toSQLString())
215219
}

src/main/scala/tyql/expr/Expr.scala

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,17 @@ object Expr:
217217
def isDefined: Expr[Boolean, S1] = Not(Expr.IsNull(x))
218218
def get: Expr[T, S1] = x.asInstanceOf[Expr[T, S1]] // TODO should this error silently?
219219
def getOrElse(default: Expr[T, S1]): Expr[T, S1] = coalesce(x.asInstanceOf[Expr[T, S1]], default)
220-
def map[U: ResultTag, S2 <: ExprShape](f: Ref[T, NonScalarExpr] => Expr[U, NonScalarExpr]): Expr[Option[U], S1] =
220+
/* ABSTRACTION: To abstract over expressions (not relations) in the DSL, the DSL needs some kind of abstraction/application operation.
221+
Option 1: (already supported) use host-level abstraction e.g. define a lambda.
222+
Option 2: Use a macro to do substitution, but then lose the macro-free claim.
223+
Option 3: You could define an expression-level substitution, but since the case classes in this file use type parameters
224+
constrained by e.g. Numeric, it would be very annoying to write a type-safe substitution that replaces the expression reference
225+
by another expression in some larger expression.
226+
Because we do not want to use macros and the Option.map is expected to have little code, we pick the lambda.
227+
*/
228+
def map[U](f: Expr[T, NonScalarExpr] => Expr[U, NonScalarExpr])(using ResultTag[U]): Expr[Option[U], S1] =
221229
OptionMap(x, f)
222-
// TODO unclear how to implement flatMap
230+
223231
// TODO somehow use options in aggregations
224232

225233
extension [S1 <: ExprShape](x: Expr[Array[Byte], S1])
@@ -333,31 +341,6 @@ object Expr:
333341
def lastValue[R, S <: ExprShape](e: Expr[R, S])(using ResultTag[R]): ExprInWindowPosition[R] = LastValue(e)
334342
def nthValue[R, S <: ExprShape](e: Expr[R, S], n: Int)(using ResultTag[R]): ExprInWindowPosition[R] = NthValue(e, n)
335343

336-
// TODO aren't these types too restrictive?
337-
def cases[T: ResultTag, SC <: ExprShape, SV <: ExprShape]
338-
(
339-
firstCase: (Expr[Boolean, SC] | true | ElseToken, Expr[T, SV]),
340-
restOfCases: (Expr[Boolean, SC] | true | ElseToken, Expr[T, SV])*
341-
)
342-
: Expr[T, SV] =
343-
var mainCases: collection.mutable.ArrayBuffer[(Expr[Boolean, SC], Expr[T, SV])] =
344-
collection.mutable.ArrayBuffer.empty
345-
var elseCase: Option[Expr[T, SV]] = None
346-
val cases = Seq(firstCase) ++ restOfCases
347-
for (((condition, value), index) <- cases.zipWithIndex) {
348-
condition match
349-
case _: ElseToken =>
350-
assert(index == cases.size - 1, "The default condition must be last")
351-
elseCase = Some(value)
352-
case true =>
353-
assert(index == cases.size - 1, "The default condition must be last")
354-
elseCase = Some(value)
355-
case false => assert(false, "what do you mean, false?")
356-
case _: Expr[?, ?] =>
357-
mainCases += ((condition.asInstanceOf[Expr[Boolean, SC]], value))
358-
}
359-
SearchedCase(mainCases.toList, elseCase)
360-
361344
// Note: All field names of constructors in the query language are prefixed with `$`
362345
// so that we don't accidentally pick a field name of a constructor class where we want
363346
// a name in the domain model instead.
@@ -551,7 +534,7 @@ object Expr:
551534
(using ResultTag[TE], ResultTag[TR]) extends Expr[TR, SR]
552535

553536
case class OptionMap[A, B, S <: ExprShape]
554-
($x: Expr[Option[A], S], $f: Ref[A, NonScalarExpr] => Expr[B, NonScalarExpr])
537+
($x: Expr[Option[A], S], $f: Expr[A, NonScalarExpr] => Expr[B, NonScalarExpr])
555538
(using ResultTag[A], ResultTag[B]) extends Expr[Option[B], S]
556539

557540
case class Cast[A, B, S <: ExprShape]($x: Expr[A, S], resultType: CastTarget)(using ResultTag[B]) extends Expr[B, S]
@@ -595,7 +578,6 @@ object Expr:
595578
(using r: DialectFeature.RandomIntegerInInclusiveRange)
596579
: Expr[Int, CalculatedShape[S1, S2]] =
597580
// TODO maybe add a check for (a <= b) if we know both components at generation time?
598-
// TODO what about parentheses? Do we really not need them?
599581
RandomInt(a, b)
600582

601583
/** Should be able to rely on the implicit conversions, but not always. One approach is to overload, another is to
@@ -607,22 +589,6 @@ object Expr:
607589
// case t:String => StringLit(t)
608590
// case t:Boolean => BooleanLit(t)
609591

610-
/* ABSTRACTION: if we want to abstract over expressions (not relations) in the DSL, to enable better composability,
611-
then the DSL needs some kind of abstraction/application operation.
612-
Option 1: (already supported) use host-level abstraction e.g. define a lambda.
613-
Option 2: (below) define a substitution method, WIP.
614-
Option 3: Use a macro to do substitution, but then lose the macro-free claim.
615-
*/
616-
case class RefExpr[A: ResultTag, S <: ExprShape]() extends Expr[A, S]:
617-
private val id = exprRefCount
618-
exprRefCount += 1
619-
def stringRef() = s"exprRef$id"
620-
override def toString: String = s"ExprRef[${stringRef()}]"
621-
622-
case class AbstractedExpr[A, B, S <: ExprShape]($param: RefExpr[A, S], $body: Expr[B, S]):
623-
def apply(exprArg: Expr[A, S]): Expr[B, S] =
624-
substitute($body, $param, exprArg)
625-
private def substitute[C](expr: Expr[B, S], formalP: RefExpr[A, S], actualP: Expr[A, S]): Expr[B, S] = ???
626592
type Pred[A, S <: ExprShape] = Fun[A, Expr[Boolean, S], S]
627593

628594
type IsTupleOfExpr[A <: AnyNamedTuple] = Tuple.Union[NamedTuple.DropNames[A]] <:< Expr[?, NonScalarExpr]
@@ -644,6 +610,31 @@ object Expr:
644610

645611
end Expr
646612

613+
// TODO aren't these types too restrictive?
614+
def cases[T: ResultTag, SC <: ExprShape, SV <: ExprShape]
615+
(
616+
firstCase: (Expr[Boolean, SC] | true | ElseToken, Expr[T, SV]),
617+
restOfCases: (Expr[Boolean, SC] | true | ElseToken, Expr[T, SV])*
618+
)
619+
: Expr[T, SV] =
620+
var mainCases: collection.mutable.ArrayBuffer[(Expr[Boolean, SC], Expr[T, SV])] =
621+
collection.mutable.ArrayBuffer.empty
622+
var elseCase: Option[Expr[T, SV]] = None
623+
val cases = Seq(firstCase) ++ restOfCases
624+
for (((condition, value), index) <- cases.zipWithIndex) {
625+
condition match
626+
case _: ElseToken =>
627+
assert(index == cases.size - 1, "The default condition must be last")
628+
elseCase = Some(value)
629+
case true =>
630+
assert(index == cases.size - 1, "The default condition must be last")
631+
elseCase = Some(value)
632+
case false => assert(false, "what do you mean, false?")
633+
case _: Expr[?, ?] =>
634+
mainCases += ((condition.asInstanceOf[Expr[Boolean, SC]], value))
635+
}
636+
Expr.SearchedCase(mainCases.toList, elseCase)
637+
647638
inline def lit(x: () => java.io.InputStream): Expr[() => java.io.InputStream, NonScalarExpr] = Expr.ByteStreamLit(x)
648639
inline def lit(x: java.io.InputStream): Expr[() => java.io.InputStream, NonScalarExpr] = Expr.ByteStreamLit(() => x)
649640
inline def lit(x: Array[Byte]): Expr[Array[Byte], NonScalarExpr] = Expr.BytesLit(x)

src/main/scala/tyql/ir/QueryIRTree.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,17 @@ object QueryIRTree:
527527
case Right(windowExpr) => generateExprInWindowPosition(windowExpr, symbols)
528528
val partitionBy = w.partitionBy.map(generateExpr(_, symbols))
529529
WindowFunctionOp(ae, partitionBy, Seq(), w)
530+
case om: Expr.OptionMap[?, ?, ?] =>
531+
val mapExpr = om.$f.asInstanceOf[Expr[?, NonScalarExpr] => Expr[?, NonScalarExpr]](om.$x.asInstanceOf[Expr[
532+
?,
533+
NonScalarExpr
534+
]])
535+
val wholeExpr = tyql.cases(
536+
om.$x.isNull -> tyql.Null,
537+
tyql.Else -> mapExpr.asInstanceOf[Expr[Null, NonScalarExpr]]
538+
)
539+
val compiledExpr = generateExpr(wholeExpr, symbols)
540+
compiledExpr
530541
case g: Expr.Gt[?, ?, ?, ?] =>
531542
BinExprOp("", generateExpr(g.$x, symbols), " > ", generateExpr(g.$y, symbols), "", Precedence.Comparison, g)
532543
case g: Expr.Lt[?, ?, ?, ?] =>

src/test/scala/test/integration/CaseTests.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package test.integration.cases
22

33
import munit.FunSuite
44
import test.{withDB, checkExprDialect}
5-
import tyql.Expr.cases
6-
import tyql.{lit, True, False, Else}
5+
import tyql._
76
import java.sql.ResultSet
87
import tyql.NonScalarExpr
98

0 commit comments

Comments
 (0)