@@ -35,41 +35,61 @@ object Query:
35
35
Query (fab.sql, fab.encoder.contramap(f), fab.decoder.map(g))
36
36
37
37
final class Fragment [A ](
38
- val parts : List [ Either [ String , Int ]] ,
38
+ val part : Fragment . Part ,
39
39
val encoder : Encoder [A ],
40
40
):
41
- def sql : String = parts.foldMap {
42
- case Left (s) => s
43
- case Right (i) => (" ?, " * (i - 1 )) ++ " ?"
44
- }
41
+ def sql : String = part.compile.runA(1 ).value
45
42
46
43
def command : Query [A , Unit ] = Query (sql, encoder, Codec .unit)
47
44
48
45
def query [B ](decoder : Decoder [B ]): Query [A , B ] = Query (sql, encoder, decoder)
49
46
50
- def apply (a : A ): Fragment [Unit ] = Fragment (parts , encoder.contramap(_ => a))
47
+ def apply (a : A ): Fragment [Unit ] = Fragment (part , encoder.contramap(_ => a))
51
48
52
49
def stripMargin : Fragment [A ] = stripMargin('|' )
53
50
54
51
def stripMargin (marginChar : Char ): Fragment [A ] =
55
- val head = parts.headOption
56
- val tail = parts.tail
57
- val ps = head.map {
58
- _.leftMap(_.stripMargin(marginChar))
59
- }.toList ++ tail.map {
60
- _.leftMap(str =>
61
- str.takeWhile(_ != '\n ' ) + str.dropWhile(_ != '\n ' ).stripMargin(marginChar),
62
- )
63
- }
64
- Fragment (ps, encoder)
52
+ Fragment (part.stripMargin(true , marginChar), encoder)
65
53
66
54
object Fragment :
55
+ sealed trait Part :
56
+ def compile : State [Int , String ]
57
+ def concatenate (other : Part ): Part = other match {
58
+ case Part .Concatenate (values) => Part .Concatenate (this :: values)
59
+ case _ => Part .Concatenate (List (this , other))
60
+ }
61
+ def stripMargin (head : Boolean , marginChar : Char ): Part
62
+
63
+ object Part :
64
+ final case class Literal (x : String ) extends Part :
65
+ def compile = State .pure(x)
66
+ def stripMargin (head : Boolean , marginChar : Char ) =
67
+ if (head) Literal (x.stripMargin(marginChar))
68
+ else Literal (x.takeWhile(_ != '\n ' ) ++ x.dropWhile(_ != '\n ' ).stripMargin(marginChar))
69
+ final case class Concatenate (values : List [Part ]) extends Part :
70
+ def compile = values.traverse(_.compile).map(_.combineAll)
71
+ override def concatenate (other : Part ) = other match {
72
+ case Concatenate (values) => Concatenate (this .values ++ values)
73
+ case _ => Concatenate (this .values :+ other)
74
+ }
75
+ def stripMargin (head : Boolean , marginChar : Char ) =
76
+ values match {
77
+ case h :: t =>
78
+ Concatenate (
79
+ h.stripMargin(head, marginChar) :: t.map(_.stripMargin(false , marginChar)),
80
+ )
81
+ case other => this
82
+ }
83
+ final case class Parameters (advance : State [Int , List [Int ]]) extends Part :
84
+ def compile = advance.map(_.map(idx => s " ? $idx" ).mkString(" , " ))
85
+ def stripMargin (head : Boolean , marginChar : Char ) = this
86
+
67
87
given ContravariantMonoidal [Fragment ] = new :
68
- val unit = Fragment (List .empty, Codec .unit)
88
+ val unit = Fragment (Part . Concatenate ( List .empty) , Codec .unit)
69
89
def product [A , B ](fa : Fragment [A ], fb : Fragment [B ]) =
70
- Fragment (fa.parts ++ fb.parts , (fa.encoder, fb.encoder).tupled)
90
+ Fragment (fa.part.concatenate( fb.part) , (fa.encoder, fb.encoder).tupled)
71
91
def contramap [A , B ](fa : Fragment [A ])(f : B => A ) =
72
- Fragment (fa.parts , fa.encoder.contramap(f))
92
+ Fragment (fa.part , fa.encoder.contramap(f))
73
93
74
94
given Monoid [Fragment [Unit ]] = new :
75
95
def empty = ContravariantMonoidal [Fragment ].unit
@@ -93,13 +113,13 @@ private def sqlImpl(
93
113
94
114
// TODO appending to `List` is slow
95
115
val fragment =
96
- parts.zipAll(args, ' { " " }, ' { " " }).foldLeft(' { List .empty[Either [ String , Int ] ] }) {
97
- case (' { $acc : List [Either [ String , Int ] ] }, (' { $p : String }, ' { $s : String })) =>
98
- ' { $acc :+ Left ($p) :+ Left ($s) }
99
- case (' { $acc : List [Either [ String , Int ] ] }, (' { $p : String }, ' { $e : Encoder [t] })) =>
100
- ' { $acc :+ Left ($p) :+ Right ($e.parameters) }
101
- case (' { $acc : List [Either [ String , Int ] ] }, (' { $p : String }, ' { $f : Fragment [t] })) =>
102
- ' { $acc :+ Left ($p) :++ $f.parts }
116
+ parts.zipAll(args, ' { " " }, ' { " " }).foldLeft(' { List .empty[Fragment . Part ] }) {
117
+ case (' { $acc : List [Fragment . Part ] }, (' { $p : String }, ' { $s : String })) =>
118
+ ' { $acc :+ Fragment . Part . Literal ($p) :+ Fragment . Part . Literal ($s) }
119
+ case (' { $acc : List [Fragment . Part ] }, (' { $p : String }, ' { $e : Encoder [t] })) =>
120
+ ' { $acc :+ Fragment . Part . Literal ($p) :+ Fragment . Part . Parameters ($e.parameters) }
121
+ case (' { $acc : List [Fragment . Part ] }, (' { $p : String }, ' { $f : Fragment [t] })) =>
122
+ ' { $acc :+ Fragment . Part . Literal ($p) :+ $f.part }
103
123
}
104
124
105
125
val encoder = args.collect {
@@ -125,5 +145,6 @@ private def sqlImpl(
125
145
}
126
146
127
147
(fragment, encoder) match
128
- case (' { $s : List [Either [String , Int ]] }, ' { $e : Encoder [a] }) => ' { Fragment [a]($s, $e) }
148
+ case (' { $s : List [Fragment .Part ] }, ' { $e : Encoder [a] }) =>
149
+ ' { Fragment [a](Fragment .Part .Concatenate ($s), $e) }
129
150
case _ => sys.error(" porcupine pricked itself" )
0 commit comments