|
| 1 | +#if canImport(TabularData) |
| 2 | + |
| 3 | +import TabularData |
| 4 | +import Foundation |
| 5 | + |
| 6 | +// MARK: - PreparedStatement + DataFrame |
| 7 | + |
| 8 | +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) |
| 9 | +extension PreparedStatement { |
| 10 | + /// Creates a new data frame from a prepared statement. |
| 11 | + /// |
| 12 | + /// ```swift |
| 13 | + /// let df = try db.prepare("SELECT * FROM contacts;").dataFrame() |
| 14 | + /// print(df) |
| 15 | + /// ``` |
| 16 | + /// ``` |
| 17 | + /// ┏━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓ |
| 18 | + /// ┃ ┃ id ┃ name ┃ rating ┃ image ┃ |
| 19 | + /// ┃ ┃ <Int> ┃ <String> ┃ <Double> ┃ <Data> ┃ |
| 20 | + /// ┡━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩ |
| 21 | + /// │ 0 │ 5 │ A │ 2,0 │ nil │ |
| 22 | + /// │ 1 │ 6 │ B │ nil │ 3 bytes │ |
| 23 | + /// └───┴───────┴──────────┴──────────┴─────────┘ |
| 24 | + /// ``` |
| 25 | + /// - Parameters: |
| 26 | + /// - capacity: An integer that represents the number of elements the columns can initially store. |
| 27 | + /// - transformers: SQLite column value transformers. |
| 28 | + /// - Returns: The data frame that can print a table. |
| 29 | + /// - Throws: ``DatabaseError`` |
| 30 | + public func dataFrame( |
| 31 | + capacity: Int = 0, |
| 32 | + transformers: [String: ColumnValueTransformer] = ColumnValueTransformer.defaults |
| 33 | + ) throws -> TabularData.DataFrame { |
| 34 | + |
| 35 | + let valueTransformers: [ColumnValueTransformer] = (0..<columnCount).map { index in |
| 36 | + columnDeclaration(at: index).flatMap { transformers[$0] } ?? .string |
| 37 | + } |
| 38 | + let columns: [TabularData.AnyColumn] = (0..<columnCount).map { index in |
| 39 | + let name = columnName(at: index) ?? "N/A" |
| 40 | + return valueTransformers[index].column(name, capacity) |
| 41 | + } |
| 42 | + var df = DataFrame(columns: columns) |
| 43 | + var count = 0 |
| 44 | + while let row = try row() { |
| 45 | + df.appendEmptyRow() |
| 46 | + for index in (0..<columnCount) { |
| 47 | + df.rows[count][index] = row[index].flatMap { valueTransformers[index].transform($0) } |
| 48 | + } |
| 49 | + count += 1 |
| 50 | + } |
| 51 | + return df |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) |
| 56 | +public struct ColumnValueTransformer: Sendable { |
| 57 | + public static let string = ColumnValueTransformer(transform: \.string) |
| 58 | + public static let int = ColumnValueTransformer(transform: \.int) |
| 59 | + public static let double = ColumnValueTransformer(transform: \.double) |
| 60 | + public static let blob = ColumnValueTransformer(transform: \.blob) |
| 61 | + |
| 62 | + public static let defaults: [String: ColumnValueTransformer] = [ |
| 63 | + "INT": .int, |
| 64 | + "INTEGER": .int, |
| 65 | + "NUM": .double, |
| 66 | + "REAL": .double, |
| 67 | + "FLOAT": .double, |
| 68 | + "TEXT": .string, |
| 69 | + "BLOB": .blob, |
| 70 | + ] |
| 71 | + |
| 72 | + @usableFromInline |
| 73 | + let column: @Sendable (_ name: String, _ capacity: Int) -> AnyColumn |
| 74 | + @usableFromInline |
| 75 | + let transform: @Sendable (PreparedStatement.Value) -> Any? |
| 76 | + |
| 77 | + @inlinable |
| 78 | + public init<T>(transform: @escaping @Sendable (PreparedStatement.Value) -> T?) { |
| 79 | + self.column = { name, capacity in |
| 80 | + TabularData.Column<T>(name: name, capacity: capacity).eraseToAnyColumn() |
| 81 | + } |
| 82 | + self.transform = transform |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +#endif // TabularData |
0 commit comments