@@ -98,27 +98,42 @@ public struct PostgresCopyFromWriter: Sendable {
9898 }
9999}
100100
101-
101+ // PostgresBinaryCopyFromWriter relies on non-Escapable types, which were only introduced in Swift 6.2
102+ #if compiler(>=6.2)
102103/// Handle to send binary data for a `COPY ... FROM STDIN` query to the backend.
103104///
104105/// It takes care of serializing `PostgresEncodable` column types into the binary format that Postgres expects.
105106public struct PostgresBinaryCopyFromWriter : ~ Copyable {
106107 /// Handle to serialize columns into a row that is being written by `PostgresBinaryCopyFromWriter`.
107- public struct ColumnWriter : ~ Copyable {
108- /// The `PostgresBinaryCopyFromWriter` that is gathering the serialized data.
109- ///
110- /// We need to model this as `UnsafeMutablePointer` because we can't express in the Swift type system that
111- /// `ColumnWriter` never exceeds the lifetime of `PostgresBinaryCopyFromWriter`.
108+ public struct ColumnWriter : ~ Escapable, ~ Copyable {
109+ /// Pointer to the `PostgresBinaryCopyFromWriter` that is gathering the serialized data.
112110 @usableFromInline
113111 let underlying : UnsafeMutablePointer < PostgresBinaryCopyFromWriter >
114112
115113 /// The number of columns that have been written by this `ColumnWriter`.
116114 @usableFromInline
117115 var columns : UInt16 = 0
118116
117+ /// - Warning: Do not call directly, call `withColumnWriter` instead
119118 @usableFromInline
120- init ( underlying: UnsafeMutablePointer < PostgresBinaryCopyFromWriter > ) {
121- self . underlying = underlying
119+ init ( _underlying: UnsafeMutablePointer < PostgresBinaryCopyFromWriter > ) {
120+ self . underlying = _underlying
121+ }
122+
123+ @usableFromInline
124+ static func withColumnWriter< T> (
125+ writingTo underlying: inout PostgresBinaryCopyFromWriter ,
126+ body: ( inout ColumnWriter ) throws -> T
127+ ) rethrows -> T {
128+ return try withUnsafeMutablePointer ( to: & underlying) { pointerToUnderlying in
129+ // We can guarantee that `ColumWriter` never outlives `underlying` because `ColumnWriter` is
130+ // `~Escapable` and thus cannot escape the context of the closure to `withUnsafeMutablePointer`.
131+ // To model this without resorting to unsafe pointers, we would need to be able to declare an `inout`
132+ // reference to `PostgresBinaryCopyFromWriter` as a member of `ColumnWriter`, which isn't possible at
133+ // the moment (https://github.com/swiftlang/swift/issues/85832).
134+ var columnWriter = ColumnWriter ( _underlying: pointerToUnderlying)
135+ return try body ( & columnWriter)
136+ }
122137 }
123138
124139 /// Serialize a single column to a row.
@@ -128,16 +143,19 @@ public struct PostgresBinaryCopyFromWriter: ~Copyable {
128143 /// be called with an `Int32`. Serializing an integer of a different width will cause a deserialization
129144 /// failure in the backend.
130145 @inlinable
146+ #if compiler(<6.3)
147+ @_lifetime ( & self )
148+ #endif
131149 public mutating func writeColumn( _ column: ( some PostgresEncodable ) ? ) throws {
132150 columns += 1
133151 try invokeWriteColumn ( on: underlying, column)
134152 }
135153
136- // Needed to work around https://github.com/swiftlang/swift/issues/83309, copying the implementation into
154+ // Needed to work around https://github.com/swiftlang/swift/issues/83309, copying the implementation into
137155 // `writeColumn` causes an assertion failure when thread sanitizer is enabled.
138- @inlinable
156+ @inlinable
139157 func invokeWriteColumn(
140- on writer: UnsafeMutablePointer < PostgresBinaryCopyFromWriter > ,
158+ on writer: UnsafeMutablePointer < PostgresBinaryCopyFromWriter > ,
141159 _ column: ( some PostgresEncodable ) ?
142160 ) throws {
143161 try writer. pointee. writeColumn ( column)
@@ -169,17 +187,8 @@ public struct PostgresBinaryCopyFromWriter: ~Copyable {
169187 let columnIndex = buffer. writerIndex
170188 buffer. writeInteger ( UInt16 ( 0 ) )
171189
172- let columns = try withUnsafeMutablePointer ( to: & self ) { pointerToSelf in
173- // Important: We need to ensure that `pointerToSelf` (and thus `ColumnWriter`) does not exceed the lifetime
174- // of `self` because it is holding an unsafe reference to it.
175- //
176- // We achieve this because `ColumnWriter` is non-Copyable and thus the client can't store a copy to it.
177- // Furthermore, `columnWriter` is destroyed before the end of `withUnsafeMutablePointer`, which holds `self`
178- // alive.
179- var columnWriter = ColumnWriter ( underlying: pointerToSelf)
180-
190+ let columns = try ColumnWriter . withColumnWriter ( writingTo: & self ) { columnWriter in
181191 try body ( & columnWriter)
182-
183192 return columnWriter. columns
184193 }
185194
@@ -212,6 +221,7 @@ public struct PostgresBinaryCopyFromWriter: ~Copyable {
212221 buffer. clear ( )
213222 }
214223}
224+ #endif
215225
216226/// Specifies the format in which data is transferred to the backend in a COPY operation.
217227///
@@ -289,6 +299,7 @@ private func buildCopyFromQuery(
289299}
290300
291301extension PostgresConnection {
302+ #if compiler(>=6.2)
292303 /// Copy data into a table using a `COPY <table name> FROM STDIN` query, transferring data in a binary format.
293304 ///
294305 /// - Parameters:
@@ -331,6 +342,7 @@ extension PostgresConnection {
331342 try await binaryWriter. flush ( )
332343 }
333344 }
345+ #endif
334346
335347 /// Copy data into a table using a `COPY <table name> FROM STDIN` query.
336348 ///
0 commit comments