Skip to content

Commit dc58cdb

Browse files
DataFrame (#11)
1 parent 7b284da commit dc58cdb

File tree

5 files changed

+155
-0
lines changed

5 files changed

+155
-0
lines changed

Playgrounds/README.playground/Contents.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,26 @@ struct Contact: Codable {
5353

5454
let select = try database.prepare("SELECT * FROM contacts;")
5555
let contacts = try select.array(Contact.self)
56+
/*:
57+
## DataFrame
58+
59+
The [DataFrame](https://developer.apple.com/documentation/tabulardata/dataframe) from the [TabularData](https://developer.apple.com/documentation/tabulardata) framework is supported.
60+
61+
It can help to print the table.
62+
*/
63+
let df = try database.prepare("SELECT * FROM contacts;").dataFrame()
64+
print(df)
65+
/*:
66+
```
67+
┏━━━┳━━━━━━━┳━━━━━━━━━━┓
68+
┃ ┃ id ┃ name ┃
69+
┃ ┃ <Int> ┃ <String> ┃
70+
┡━━━╇━━━━━━━╇━━━━━━━━━━┩
71+
│ 0 │ 1 │ Paul │
72+
│ 1 │ 2 │ John │
73+
└───┴───────┴──────────┘
74+
```
75+
## License
76+
77+
MIT
78+
*/

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,24 @@ struct Contact: Codable {
5353
let select = try database.prepare("SELECT * FROM contacts;")
5454
let contacts = try select.array(Contact.self)
5555
```
56+
## DataFrame
57+
58+
The [DataFrame](https://developer.apple.com/documentation/tabulardata/dataframe) from the [TabularData](https://developer.apple.com/documentation/tabulardata) framework is supported.
59+
60+
It can help to print the table.
61+
```swift
62+
let df = try database.prepare("SELECT * FROM contacts;").dataFrame()
63+
print(df)
64+
```
65+
```
66+
┏━━━┳━━━━━━━┳━━━━━━━━━━┓
67+
┃ ┃ id ┃ name ┃
68+
┃ ┃ <Int> ┃ <String> ┃
69+
┡━━━╇━━━━━━━╇━━━━━━━━━━┩
70+
│ 0 │ 1 │ Paul │
71+
│ 1 │ 2 │ John │
72+
└───┴───────┴──────────┘
73+
```
74+
## License
75+
76+
MIT

Sources/SQLyra/DataFrame.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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

Sources/SQLyra/PreparedStatement.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ extension PreparedStatement {
149149
public func columnName(at index: Int) -> String? {
150150
sqlite3_column_name(stmt, Int32(index)).string
151151
}
152+
153+
func columnDeclaration(at index: Int) -> String? {
154+
sqlite3_column_decltype(stmt, Int32(index)).string
155+
}
152156
}
153157

154158
// MARK: - Result values from a Query

Tests/SQLyraTests/PreparedStatementTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,25 @@ struct PreparedStatementTests {
9393
]
9494
#expect(contracts == expected)
9595
}
96+
97+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
98+
@Test func dataFrame() throws {
99+
let insert = try db.prepare(Contact.insert)
100+
101+
try insert.bind(parameters: 5, "A").execute().reset()
102+
try insert.bind(parameters: 6, "B").execute()
103+
104+
let df = try db.prepare("SELECT * FROM contacts;").dataFrame()
105+
let expected = """
106+
┏━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┓
107+
┃ ┃ id ┃ name ┃ rating ┃ image ┃
108+
┃ ┃ <Int> ┃ <String> ┃ <Double> ┃ <Data> ┃
109+
┡━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━┩
110+
│ 0 │ 5 │ A │ nil │ nil │
111+
│ 1 │ 6 │ B │ nil │ nil │
112+
└───┴───────┴──────────┴──────────┴────────┘
113+
2 rows, 4 columns
114+
"""
115+
#expect(df.description == expected + "\n")
116+
}
96117
}

0 commit comments

Comments
 (0)