Skip to content

Commit b1a40fb

Browse files
authored
Expose PostgresColumns metadata on PostgresRowSequence (#624)
1 parent 9e9bde9 commit b1a40fb

3 files changed

Lines changed: 144 additions & 3 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
extension PostgresRowSequence {
2+
/// A ``PostgresColumns`` collection containing metadata about the columns in the query result.
3+
public var columns: PostgresColumns {
4+
PostgresColumns(underlying: self._columns)
5+
}
6+
}
7+
8+
/// A collection of ``PostgresColumn`` column metadata for a PostgreSQL query result.
9+
///
10+
/// You can access metadata about the columns in a query result from ``PostgresRowSequence/columns``.
11+
public struct PostgresColumns: Sequence, Sendable {
12+
public typealias Element = PostgresColumn
13+
14+
var underlying: [RowDescription.Column]
15+
16+
public func makeIterator() -> Iterator {
17+
Iterator(underlying: self.underlying.makeIterator())
18+
}
19+
20+
public struct Iterator: IteratorProtocol {
21+
var underlying: [RowDescription.Column].Iterator
22+
23+
public mutating func next() -> PostgresColumn? {
24+
guard let next = self.underlying.next() else {
25+
return nil
26+
}
27+
return PostgresColumn(underlying: next)
28+
}
29+
}
30+
}
31+
32+
extension PostgresColumns: Collection, Equatable {
33+
public typealias Index = Int
34+
35+
public var startIndex: Index { self.underlying.startIndex }
36+
public var endIndex: Index { self.underlying.endIndex }
37+
38+
public subscript(position: Index) -> PostgresColumn {
39+
PostgresColumn(underlying: self.underlying[position])
40+
}
41+
42+
public func index(after i: Int) -> Int {
43+
self.underlying.index(after: i)
44+
}
45+
}
46+
47+
/// Metadata for a single column in a PostgreSQL query result.
48+
public struct PostgresColumn: Hashable, Sendable {
49+
let underlying: RowDescription.Column
50+
51+
/// The field name.
52+
public var name: String {
53+
self.underlying.name
54+
}
55+
56+
/// If the field can be identified as a column of a specific table, the object ID of the table; otherwise zero.
57+
public var tableOID: Int32 {
58+
self.underlying.tableOID
59+
}
60+
61+
/// If the field can be identified as a column of a specific table, the attribute number of the column; otherwise zero.
62+
public var columnAttributeNumber: Int16 {
63+
self.underlying.columnAttributeNumber
64+
}
65+
66+
/// The object ID of the field's data type.
67+
public var dataType: PostgresDataType {
68+
self.underlying.dataType
69+
}
70+
71+
/// The data type size (see pg_type.typlen). Note that negative values denote variable-width types.
72+
public var dataTypeSize: Int16 {
73+
self.underlying.dataTypeSize
74+
}
75+
76+
/// The type modifier (see pg_attribute.atttypmod). The meaning of the modifier is type-specific.
77+
public var dataTypeModifier: Int32 {
78+
self.underlying.dataTypeModifier
79+
}
80+
81+
/// The format being used for the field. Currently will be text or binary.
82+
/// In a RowDescription returned from the statement variant of Describe, the format code is not yet known and will always be text.
83+
public var format: PostgresFormat {
84+
self.underlying.format
85+
}
86+
}

Sources/PostgresNIO/New/PostgresRowSequence.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ public struct PostgresRowSequence: AsyncSequence, Sendable {
1111

1212
let backing: BackingSequence
1313
let lookupTable: [String: Int]
14-
let columns: [RowDescription.Column]
14+
let _columns: [RowDescription.Column]
1515

1616
init(_ backing: BackingSequence, lookupTable: [String: Int], columns: [RowDescription.Column]) {
1717
self.backing = backing
1818
self.lookupTable = lookupTable
19-
self.columns = columns
19+
self._columns = columns
2020
}
2121

2222
public func makeAsyncIterator() -> AsyncIterator {
2323
AsyncIterator(
2424
backing: self.backing.makeAsyncIterator(),
2525
lookupTable: self.lookupTable,
26-
columns: self.columns
26+
columns: self._columns
2727
)
2828
}
2929
}

Tests/PostgresNIOTests/New/PostgresRowSequenceTests.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,61 @@ import Logging
433433
let emptyRow = try await rowIterator.next()
434434
#expect(emptyRow == nil)
435435
}
436+
437+
@Test func testGettingColumnsReturnsCorrectColumnInformation() {
438+
let dataSource = MockRowDataSource()
439+
let embeddedEventLoop = EmbeddedEventLoop()
440+
441+
let sourceColumns = [
442+
RowDescription.Column(
443+
name: "id",
444+
tableOID: 12345,
445+
columnAttributeNumber: 1,
446+
dataType: .int8,
447+
dataTypeSize: 8,
448+
dataTypeModifier: -1,
449+
format: .binary
450+
),
451+
RowDescription.Column(
452+
name: "name",
453+
tableOID: 12345,
454+
columnAttributeNumber: 2,
455+
dataType: .text,
456+
dataTypeSize: -1,
457+
dataTypeModifier: -1,
458+
format: .text
459+
)
460+
]
461+
462+
let expectedColumns = PostgresColumns(underlying: sourceColumns)
463+
464+
let stream = PSQLRowStream(
465+
source: .stream(sourceColumns, dataSource),
466+
eventLoop: embeddedEventLoop,
467+
logger: self.logger
468+
)
469+
470+
let rowSequence = stream.asyncSequence()
471+
let actualColumns = rowSequence.columns
472+
473+
#expect(actualColumns == expectedColumns)
474+
}
475+
476+
@Test func testGettingColumnsWithEmptyColumns() {
477+
let dataSource = MockRowDataSource()
478+
let embeddedEventLoop = EmbeddedEventLoop()
479+
480+
let stream = PSQLRowStream(
481+
source: .stream([], dataSource),
482+
eventLoop: embeddedEventLoop,
483+
logger: self.logger
484+
)
485+
486+
let rowSequence = stream.asyncSequence()
487+
let columns = rowSequence.columns
488+
489+
#expect(columns.isEmpty)
490+
}
436491
}
437492

438493
final class MockRowDataSource: PSQLRowsDataSource {

0 commit comments

Comments
 (0)