@@ -66,13 +66,16 @@ public enum SQLCellValue: Sendable, Equatable {
6666 case . string( let v) : return v
6767 case . bytes( let v) : return Data ( v) . base64EncodedString ( )
6868 case . uuid( let v) : return v. uuidString
69- case . date( let v) :
70- let f = ISO8601DateFormatter ( )
71- f. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
72- return f. string ( from: v)
69+ case . date( let v) : return SQLCellValue . _isoFormatter. string ( from: v)
7370 }
7471 }
7572
73+ private nonisolated ( unsafe) static let _isoFormatter : ISO8601DateFormatter = {
74+ let f = ISO8601DateFormatter ( )
75+ f. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
76+ return f
77+ } ( )
78+
7679 /// Human-readable string for display / Markdown rendering.
7780 public var displayString : String {
7881 switch self {
@@ -85,10 +88,7 @@ public enum SQLCellValue: Sendable, Equatable {
8588 case . string( let v) : return v
8689 case . bytes( let v) : return " 0x " + v. map { String ( format: " %02X " , $0) } . joined ( )
8790 case . uuid( let v) : return v. uuidString
88- case . date( let v) :
89- let f = ISO8601DateFormatter ( )
90- f. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
91- return f. string ( from: v)
91+ case . date( let v) : return SQLCellValue . _isoFormatter. string ( from: v)
9292 }
9393 }
9494}
@@ -223,23 +223,33 @@ public struct SQLDataTable: Sendable {
223223
224224 /// Decode all rows into an array of the given `Decodable` type.
225225 public func decode< T: Decodable > ( as type: T . Type = T . self) throws -> [ T ] {
226+ // Build the shared column-name → index map once for all rows.
227+ // This avoids O(columns) linear search per field per row in _RowDecoder.
228+ let colIndex = Dictionary (
229+ columns. enumerated ( ) . map { ( i, col) in ( col. name. lowercased ( ) , i) } ,
230+ uniquingKeysWith: { first, _ in first }
231+ )
226232 let decoder = SQLRowDecoder ( )
227- return try toSQLRows ( ) . map { try decoder. decode ( T . self, from: $0) }
233+ return try rows. map { cells in
234+ try decoder. decode ( T . self, cells: cells, columns: columns, columnIndex: colIndex)
235+ }
228236 }
229237
230238 // MARK: JSON rendering
231239
232240 /// Renders the table as a JSON array of objects (column name → native value).
233241 /// SQL NULL becomes JSON `null`. Dates are ISO-8601 strings.
234242 public func toJson( pretty: Bool = true ) -> String {
235- let array = rows. map { row -> [ String : Any ? ] in
236- Dictionary ( uniqueKeysWithValues: zip ( columns. map ( \. name) , row. map ( \. jsonValue) ) )
237- }
238- // JSONSerialization needs [String: Any] with NSNull for nulls
239- let sanitized = array. map { dict in
240- dict. mapValues { $0 ?? NSNull ( ) as Any }
243+ // Pre-compute column names once — not per row.
244+ let names = columns. map ( \. name)
245+ let sanitized = rows. map { row -> [ String : Any ] in
246+ var dict = [ String: Any] ( minimumCapacity: names. count)
247+ for (i, cell) in row. enumerated ( ) where i < names. count {
248+ dict [ names [ i] ] = cell. jsonValue ?? NSNull ( )
249+ }
250+ return dict
241251 }
242- let opts : JSONSerialization . WritingOptions = pretty ? [ . prettyPrinted, . sortedKeys ] : [ ]
252+ let opts : JSONSerialization . WritingOptions = pretty ? [ . prettyPrinted] : [ ]
243253 let data = ( try ? JSONSerialization . data ( withJSONObject: sanitized, options: opts) ) ?? Data ( )
244254 return String ( data: data, encoding: . utf8) ?? " [] "
245255 }
0 commit comments