@@ -5,39 +5,44 @@ import StructuredQueriesSupport
55/// You will typically create instances of this type using string literals, where bindings are
66/// directly interpolated into the string. This most commonly occurs when using the `#sql` macro,
77/// which takes values of this type.
8- public struct QueryFragment : Hashable , Sendable , CustomDebugStringConvertible {
9- #if DEBUG
10- /// The underlying SQL string.
11- public var string : String
12- #else
13- /// The underlying SQL string.
14- public package ( set) var string : String
15- #endif
8+ public struct QueryFragment : Hashable , Sendable {
9+ /// A segment of a query fragment.
10+ public enum Segment : Hashable , Sendable {
11+ /// A raw SQL fragment.
12+ case sql( String )
1613
17- #if DEBUG
18- /// An array of parameterized statement bindings.
19- public var bindings : [ QueryBinding ]
20- #else
21- /// An array of parameterized statement bindings.
22- public package ( set) var bindings : [ QueryBinding ]
23- #endif
14+ /// A binding.
15+ case binding( QueryBinding )
16+ }
17+
18+ /// An array of segments backing this query fragment.
19+ public internal( set) var segments : [ Segment ] = [ ]
20+
21+ fileprivate init ( segments: [ Segment ] ) {
22+ self . segments = segments
23+ }
2424
25- init ( _ string: String = " " , _ bindings: [ QueryBinding ] = [ ] ) {
26- self . string = string
27- self . bindings = bindings
25+ init ( _ string: String = " " ) {
26+ self . init ( segments: [ . sql( string) ] )
2827 }
2928
3029 /// A Boolean value indicating whether the query fragment is empty.
3130 public var isEmpty : Bool {
32- return string. isEmpty && bindings. isEmpty
31+ segments. allSatisfy {
32+ switch $0 {
33+ case . sql( let sql) :
34+ sql. isEmpty
35+ case . binding:
36+ false
37+ }
38+ }
3339 }
3440
3541 /// Appends the given fragment to this query fragment.
3642 ///
3743 /// - Parameter other: Another query fragment.
3844 public mutating func append( _ other: Self ) {
39- string. append ( other. string)
40- bindings. append ( contentsOf: other. bindings)
45+ segments. append ( contentsOf: other. segments)
4146 }
4247
4348 /// Appends a given query fragment to another fragment.
@@ -52,35 +57,35 @@ public struct QueryFragment: Hashable, Sendable, CustomDebugStringConvertible {
5257 return query
5358 }
5459
60+ /// Returns a prepared SQL string and associated bindings for this query.
61+ ///
62+ /// - Parameter template: Prepare a template string for a binding at a given 1-based offset.
63+ /// - Returns: A SQL string and array of associated bindings.
64+ public func prepare(
65+ _ template: ( _ offset: Int ) -> String
66+ ) -> ( sql: String , bindings: [ QueryBinding ] ) {
67+ segments. enumerated ( ) . reduce ( into: ( sql: " " , bindings: [ QueryBinding] ( ) ) ) {
68+ switch $1. element {
69+ case . sql( let sql) :
70+ $0. sql. append ( sql)
71+ case . binding( let binding) :
72+ $0. sql. append ( template ( $1. offset + 1 ) )
73+ $0. bindings. append ( binding)
74+ }
75+ }
76+ }
77+ }
78+
79+ extension QueryFragment : CustomDebugStringConvertible {
5580 public var debugDescription : String {
56- var compiled = " "
57- var bindings = bindings
58- var currentDelimiter : Character ?
59- compiled. reserveCapacity ( string. count)
60- let delimiters : [ Character : Character ] = [
61- #"""# : #"""# ,
62- " ' " : " ' " ,
63- " ` " : " ` " ,
64- " [ " : " ] " ,
65- ]
66- for character in string {
67- if let delimiter = currentDelimiter {
68- if delimiter == character,
69- compiled. last != character || compiled. last == delimiters [ delimiter]
70- {
71- currentDelimiter = nil
72- }
73- compiled. append ( character)
74- } else if delimiters. keys. contains ( character) {
75- currentDelimiter = character
76- compiled. append ( character)
77- } else if character == " ? " {
78- compiled. append ( bindings. removeFirst ( ) . debugDescription)
79- } else {
80- compiled. append ( character)
81+ segments. reduce ( into: " " ) { debugDescription, segment in
82+ switch segment {
83+ case . sql( let sql) :
84+ debugDescription. append ( sql)
85+ case . binding( let binding) :
86+ debugDescription. append ( binding. debugDescription)
8187 }
8288 }
83- return compiled
8489 }
8590}
8691
@@ -103,7 +108,7 @@ extension [QueryFragment] {
103108
104109extension QueryFragment : ExpressibleByStringInterpolation {
105110 public init ( stringInterpolation: StringInterpolation ) {
106- self . init ( stringInterpolation . string , stringInterpolation. bindings )
111+ self . init ( segments : stringInterpolation. segments )
107112 }
108113
109114 public init ( stringLiteral value: String ) {
@@ -132,16 +137,14 @@ extension QueryFragment: ExpressibleByStringInterpolation {
132137 }
133138
134139 public struct StringInterpolation : StringInterpolationProtocol {
135- public var string = " "
136- public var bindings : [ QueryBinding ] = [ ]
140+ fileprivate var segments : [ Segment ] = [ ]
137141
138142 public init ( literalCapacity: Int , interpolationCount: Int ) {
139- string. reserveCapacity ( literalCapacity)
140- bindings. reserveCapacity ( interpolationCount)
143+ segments. reserveCapacity ( interpolationCount)
141144 }
142145
143146 public mutating func appendLiteral( _ literal: String ) {
144- string . append ( literal)
147+ segments . append ( . sql ( literal) )
145148 }
146149
147150 /// Append a quoted fragment to the interpolation.
@@ -162,7 +165,7 @@ extension QueryFragment: ExpressibleByStringInterpolation {
162165 quote sql: String ,
163166 delimiter: QuoteDelimiter = . identifier
164167 ) {
165- string . append ( sql. quoted ( delimiter) )
168+ segments . append ( . sql( sql . quoted ( delimiter) ) )
166169 }
167170
168171 /// Append a raw SQL string to the interpolation.
@@ -174,7 +177,7 @@ extension QueryFragment: ExpressibleByStringInterpolation {
174177 ///
175178 /// - Parameter sql: A raw query string.
176179 public mutating func appendInterpolation( raw sql: String ) {
177- string . append ( sql)
180+ segments . append ( . sql( sql ) )
178181 }
179182
180183 /// Append a raw lossless string to the interpolation.
@@ -191,23 +194,21 @@ extension QueryFragment: ExpressibleByStringInterpolation {
191194 ///
192195 /// - Parameter sql: A raw query string.
193196 public mutating func appendInterpolation( raw sql: some LosslessStringConvertible ) {
194- string . append ( sql. description)
197+ segments . append ( . sql( sql . description) )
195198 }
196199
197200 /// Append a query binding to the interpolation.
198201 ///
199202 /// - Parameter binding: A query binding.
200203 public mutating func appendInterpolation( _ binding: QueryBinding ) {
201- string. append ( " ? " )
202- bindings. append ( binding)
204+ segments. append ( . binding( binding) )
203205 }
204206
205207 /// Append a query fragment to the interpolation.
206208 ///
207209 /// - Parameter fragment: A query fragment.
208210 public mutating func appendInterpolation( _ fragment: QueryFragment ) {
209- string. append ( fragment. string)
210- bindings. append ( contentsOf: fragment. bindings)
211+ segments. append ( contentsOf: fragment. segments)
211212 }
212213
213214 /// Append a query expression to the interpolation.
0 commit comments