diff --git a/Playgrounds/README.playground/Contents.swift b/Playgrounds/README.playground/Contents.swift index 9a13b6f..41f8708 100644 --- a/Playgrounds/README.playground/Contents.swift +++ b/Playgrounds/README.playground/Contents.swift @@ -53,3 +53,26 @@ struct Contact: Codable { let select = try database.prepare("SELECT * FROM contacts;") let contacts = try select.array(Contact.self) +/*: + ## DataFrame + + The [DataFrame](https://developer.apple.com/documentation/tabulardata/dataframe) from the [TabularData](https://developer.apple.com/documentation/tabulardata) framework is supported. + + It can help to print the table. + */ +let df = try database.prepare("SELECT * FROM contacts;").dataFrame() +print(df) +/*: + ``` + ┏━━━┳━━━━━━━┳━━━━━━━━━━┓ + ┃ ┃ id ┃ name ┃ + ┃ ┃ ┃ + ┡━━━╇━━━━━━━╇━━━━━━━━━━┩ + │ 0 │ 1 │ Paul │ + │ 1 │ 2 │ John │ + └───┴───────┴──────────┘ + ``` + ## License + + MIT + */ diff --git a/README.md b/README.md index 391411a..e148f4c 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,24 @@ struct Contact: Codable { let select = try database.prepare("SELECT * FROM contacts;") let contacts = try select.array(Contact.self) ``` +## DataFrame + +The [DataFrame](https://developer.apple.com/documentation/tabulardata/dataframe) from the [TabularData](https://developer.apple.com/documentation/tabulardata) framework is supported. + +It can help to print the table. +```swift +let df = try database.prepare("SELECT * FROM contacts;").dataFrame() +print(df) +``` +``` +┏━━━┳━━━━━━━┳━━━━━━━━━━┓ +┃ ┃ id ┃ name ┃ +┃ ┃ ┃ +┡━━━╇━━━━━━━╇━━━━━━━━━━┩ +│ 0 │ 1 │ Paul │ +│ 1 │ 2 │ John │ +└───┴───────┴──────────┘ +``` +## License + +MIT diff --git a/Sources/SQLyra/DataFrame.swift b/Sources/SQLyra/DataFrame.swift new file mode 100644 index 0000000..4b827b1 --- /dev/null +++ b/Sources/SQLyra/DataFrame.swift @@ -0,0 +1,86 @@ +#if canImport(TabularData) + +import TabularData +import Foundation + +// MARK: - PreparedStatement + DataFrame + +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension PreparedStatement { + /// Creates a new data frame from a prepared statement. + /// + /// ```swift + /// let df = try db.prepare("SELECT * FROM contacts;").dataFrame() + /// print(df) + /// ``` + /// ``` + /// ┏━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓ + /// ┃ ┃ id ┃ name ┃ rating ┃ image ┃ + /// ┃ ┃ ┃ + /// ┡━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩ + /// │ 0 │ 5 │ A │ 2,0 │ nil │ + /// │ 1 │ 6 │ B │ nil │ 3 bytes │ + /// └───┴───────┴──────────┴──────────┴─────────┘ + /// ``` + /// - Parameters: + /// - capacity: An integer that represents the number of elements the columns can initially store. + /// - transformers: SQLite column value transformers. + /// - Returns: The data frame that can print a table. + /// - Throws: ``DatabaseError`` + public func dataFrame( + capacity: Int = 0, + transformers: [String: ColumnValueTransformer] = ColumnValueTransformer.defaults + ) throws -> TabularData.DataFrame { + + let valueTransformers: [ColumnValueTransformer] = (0.. AnyColumn + @usableFromInline + let transform: @Sendable (PreparedStatement.Value) -> Any? + + @inlinable + public init(transform: @escaping @Sendable (PreparedStatement.Value) -> T?) { + self.column = { name, capacity in + TabularData.Column(name: name, capacity: capacity).eraseToAnyColumn() + } + self.transform = transform + } +} + +#endif // TabularData diff --git a/Sources/SQLyra/PreparedStatement.swift b/Sources/SQLyra/PreparedStatement.swift index 7dba370..57bfd16 100644 --- a/Sources/SQLyra/PreparedStatement.swift +++ b/Sources/SQLyra/PreparedStatement.swift @@ -149,6 +149,10 @@ extension PreparedStatement { public func columnName(at index: Int) -> String? { sqlite3_column_name(stmt, Int32(index)).string } + + func columnDeclaration(at index: Int) -> String? { + sqlite3_column_decltype(stmt, Int32(index)).string + } } // MARK: - Result values from a Query diff --git a/Tests/SQLyraTests/PreparedStatementTests.swift b/Tests/SQLyraTests/PreparedStatementTests.swift index 4872345..f75d3ec 100644 --- a/Tests/SQLyraTests/PreparedStatementTests.swift +++ b/Tests/SQLyraTests/PreparedStatementTests.swift @@ -93,4 +93,25 @@ struct PreparedStatementTests { ] #expect(contracts == expected) } + + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + @Test func dataFrame() throws { + let insert = try db.prepare(Contact.insert) + + try insert.bind(parameters: 5, "A").execute().reset() + try insert.bind(parameters: 6, "B").execute() + + let df = try db.prepare("SELECT * FROM contacts;").dataFrame() + let expected = """ + ┏━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┓ + ┃ ┃ id ┃ name ┃ rating ┃ image ┃ + ┃ ┃ ┃ + ┡━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━┩ + │ 0 │ 5 │ A │ nil │ nil │ + │ 1 │ 6 │ B │ nil │ nil │ + └───┴───────┴──────────┴──────────┴────────┘ + 2 rows, 4 columns + """ + #expect(df.description == expected + "\n") + } }