@@ -60,8 +60,8 @@ public enum SQLCellValue: Codable, Sendable, Equatable {
6060 case uuid( UUID )
6161 case object( String ) // JSON-encoded fallback
6262
63- /// Underlying value as Any ? for compatibility with existing code.
64- public var anyValue : Any ? {
63+ /// Underlying value as Sendable ? for compatibility with existing code.
64+ public var anyValue : ( any Sendable ) ? {
6565 switch self {
6666 case . null: return nil
6767 case . string( let v) : return v
@@ -178,8 +178,10 @@ public enum SQLCellValue: Codable, Sendable, Equatable {
178178 return ( raw as? NSNumber ) . map { . double( $0. doubleValue) } ?? . null
179179 case 50 , 104 : // SYBBIT / SYBBITN
180180 return ( raw as? NSNumber ) . map { . bool( $0. boolValue) } ?? . null
181- case 55 , 63 , 60 , 122 , 110 : // decimal / numeric / money
182- return ( raw as? NSDecimalNumber ) . map { . decimal( $0. decimalValue) } ?? . null
181+ case 55 , 63 , 60 , 122 , 110 , 106 , 108 : // decimal / numeric / money
182+ if let dec = raw as? NSDecimalNumber { return . decimal( dec. decimalValue) }
183+ if let data = raw as? Data { return . bytes( data) }
184+ return . null
183185 case 36 : // SYBUNIQUE
184186 return ( raw as? UUID ) . map { . uuid( $0) } ?? . null
185187 case 45 , 37 , 34 , 173 , 174 , 167 : // binary types
@@ -201,13 +203,31 @@ public enum SQLCellValue: Codable, Sendable, Equatable {
201203 case 59 : return . float
202204 case 62 : return . double
203205 case 50 , 104 : return . boolean
204- case 55 , 63 , 60 , 122 , 110 : return . decimal
206+ case 55 , 63 , 60 , 122 , 110 , 106 , 108 : return . decimal
205207 case 36 : return . guid
206208 case 45 , 37 , 34 , 173 , 174 , 167 : return . byteArray
207209 case 61 , 58 , 111 , 40 , 41 , 42 , 43 , 187 , 188 : return . dateTime
208210 default : return . string
209211 }
210212 }
213+
214+ /// Reverse mapping of SQLColumnType to a representative FreeTDS type code.
215+ internal static func freeTDSType( for type: SQLColumnType ) -> Int32 {
216+ switch type {
217+ case . byte: return 48
218+ case . int16: return 52
219+ case . int32: return 56
220+ case . int64: return 127
221+ case . float: return 59
222+ case . double: return 62
223+ case . boolean: return 50
224+ case . decimal: return 55
225+ case . guid: return 36
226+ case . byteArray: return 45
227+ case . dateTime: return 61
228+ default : return 47 // SYBCHAR
229+ }
230+ }
211231}
212232
213233// MARK: - SQLDataTable
@@ -286,29 +306,29 @@ public struct SQLDataTable: Codable, Sendable {
286306
287307 /// Decodes each row into a `Decodable` type using column names as coding keys.
288308 public func decode< T: Decodable > ( as type: T . Type = T . self) throws -> [ T ] {
289- return try rows. map { rowCells in
290- // Map column names exactly as returned from SQL
291- var dict : [ String : SQLCellValue ] = [ : ]
292- for (ci, col) in columns. enumerated ( ) where ci < rowCells. count {
293- dict [ col. name] = rowCells [ ci]
309+ let colTypes = Dictionary ( uniqueKeysWithValues: columns. map { ( $0. name, SQLCellValue . freeTDSType ( for: $0. type) ) } )
310+ return try rows. map { rowCells in
311+ let storage : [ ( key: String , value: Sendable ) ] = rowCells. enumerated ( ) . compactMap { ( ci, cell) in
312+ guard ci < columns. count else { return nil }
313+ let value : Sendable = cell. anyValue ?? NSNull ( )
314+ return ( key: columns [ ci] . name, value: value)
315+ }
316+ return try T ( from: SQLRowDecoder ( row: SQLRow ( storage, columnTypes: colTypes) ) )
294317 }
295- let row = SQLRow ( dict)
296- // Use CodingKeys matching struct properties
297- return try T ( from: SQLRowDecoder ( row: row) )
298318 }
299- }
300319
301320 // MARK: SQLRow compatibility
302321
303322 /// Converts to `[SQLRow]` for compatibility with existing query methods.
304323 public func toSQLRows( ) -> [ SQLRow ] {
305- rows. map { rowCells in
324+ let colTypes = Dictionary ( uniqueKeysWithValues: columns. map { ( $0. name, SQLCellValue . freeTDSType ( for: $0. type) ) } )
325+ return rows. map { rowCells in
306326 let storage : [ ( key: String , value: Sendable ) ] = rowCells. enumerated ( ) . compactMap { ( ci, cell) in
307327 guard ci < columns. count else { return nil }
308- let value : Sendable = cell. anyValue. map { $0 as AnyObject } ?? NSNull ( )
328+ let value : Sendable = cell. anyValue ?? NSNull ( )
309329 return ( key: columns [ ci] . name, value: value)
310330 }
311- return SQLRow ( storage)
331+ return SQLRow ( storage, columnTypes : colTypes )
312332 }
313333 }
314334}
@@ -331,11 +351,7 @@ public struct SQLDataSet: Codable, Sendable {
331351
332352 internal init ( tables: [ SQLDataTable ] ) { self . tables = tables }
333353}
334- extension String {
335- func caseInsensitiveCompare( _ other: String ) -> Bool {
336- return self . lowercased ( ) == other. lowercased ( )
337- }
338- }
354+
339355// MARK: - SQLClientResult extension
340356
341357extension SQLClientResult {
@@ -354,13 +370,22 @@ extension SQLClientResult {
354370 return SQLDataTable ( name: " Table \( idx + 1 ) " , columns: [ ] , rows: [ ] )
355371 }
356372 let cols = first. columns. map { name in
357- // Infer type from first non-null value in the column
373+ // Use FreeTDS type if available in SQLRow
374+ if let tdsType = first. columnTypes [ name] {
375+ return SQLDataColumn ( name: name, type: SQLCellValue . columnType ( for: tdsType) )
376+ }
377+ // Infer type from first non-null value in the column (fallback)
358378 let sample = sqlRows. compactMap ( { $0 [ name] } ) . first ( where: { !( $0 is NSNull ) } )
359379 return SQLDataColumn ( name: name, type: inferColumnType ( from: sample) )
360380 }
361381 let dataRows : [ [ SQLCellValue ] ] = sqlRows. map { sqlRow in
362382 cols. map { col in
363383 guard let raw = sqlRow [ col. name] else { return . null }
384+ // If we have FreeTDS type for this row/column, use it
385+ if let tdsType = sqlRow. columnTypes [ col. name] {
386+ let val : Sendable = ( raw as AnyObject ) as! Sendable
387+ return SQLCellValue . from ( raw: val, freeTDSType: tdsType)
388+ }
364389 return cellValueFromAny ( raw, columnType: col. type)
365390 }
366391 }
0 commit comments