From 47da18690f7b42fbb97358b8ba63001d823f80ac Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 11 Apr 2023 10:11:57 +0200 Subject: [PATCH 1/5] Variadic Generics Reduce Run tests Even better Use variadic generics Keep stuff --- Package@swift-5.9.swift | 60 +++++++ .../New/PostgresRow-multi-decode.swift | 2 + .../PostgresRowSequence-multi-decode.swift | 2 +- .../PostgresNIO/New/VariadicGenerics.swift | 160 ++++++++++++++++++ Tests/IntegrationTests/AsyncTests.swift | 6 +- .../New/PostgresRowSequenceTests.swift | 12 +- 6 files changed, 233 insertions(+), 9 deletions(-) create mode 100644 Package@swift-5.9.swift create mode 100644 Sources/PostgresNIO/New/VariadicGenerics.swift diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift new file mode 100644 index 00000000..f24a71d1 --- /dev/null +++ b/Package@swift-5.9.swift @@ -0,0 +1,60 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "postgres-nio", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .watchOS(.v6), + .tvOS(.v13), + ], + products: [ + .library(name: "PostgresNIO", targets: ["PostgresNIO"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.44.0"), + .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.13.1"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.1"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.0.0"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"), + ], + targets: [ + .target( + name: "PostgresNIO", + dependencies: [ + .product(name: "Atomics", package: "swift-atomics"), + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "NIO", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), + .product(name: "NIOTLS", package: "swift-nio"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ], + swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] + ), + .testTarget( + name: "PostgresNIOTests", + dependencies: [ + .target(name: "PostgresNIO"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOTestUtils", package: "swift-nio"), + ], + swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] + ), + .testTarget( + name: "IntegrationTests", + dependencies: [ + .target(name: "PostgresNIO"), + .product(name: "NIOTestUtils", package: "swift-nio"), + ], + swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] + ), + ] +) diff --git a/Sources/PostgresNIO/New/PostgresRow-multi-decode.swift b/Sources/PostgresNIO/New/PostgresRow-multi-decode.swift index cb62c325..71aa04dc 100644 --- a/Sources/PostgresNIO/New/PostgresRow-multi-decode.swift +++ b/Sources/PostgresNIO/New/PostgresRow-multi-decode.swift @@ -1,5 +1,6 @@ /// NOTE: THIS FILE IS AUTO-GENERATED BY dev/generate-postgresrow-multi-decode.sh +#if compiler(<5.9) extension PostgresRow { @inlinable @_alwaysEmitIntoClient @@ -1171,3 +1172,4 @@ extension PostgresRow { try self.decode((T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14).self, context: .default, file: file, line: line) } } +#endif diff --git a/Sources/PostgresNIO/New/PostgresRowSequence-multi-decode.swift b/Sources/PostgresNIO/New/PostgresRowSequence-multi-decode.swift index 53d9a7ea..f45357d8 100644 --- a/Sources/PostgresNIO/New/PostgresRowSequence-multi-decode.swift +++ b/Sources/PostgresNIO/New/PostgresRowSequence-multi-decode.swift @@ -1,6 +1,6 @@ /// NOTE: THIS FILE IS AUTO-GENERATED BY dev/generate-postgresrowsequence-multi-decode.sh -#if canImport(_Concurrency) +#if compiler(<5.9) extension AsyncSequence where Element == PostgresRow { @inlinable @_alwaysEmitIntoClient diff --git a/Sources/PostgresNIO/New/VariadicGenerics.swift b/Sources/PostgresNIO/New/VariadicGenerics.swift new file mode 100644 index 00000000..5a780f13 --- /dev/null +++ b/Sources/PostgresNIO/New/VariadicGenerics.swift @@ -0,0 +1,160 @@ +#if compiler(>=5.9) +#if hasFeature(VariadicGenerics) +extension PostgresRow { + + // --- snip TODO: Remove once bug is fixed, that disallows tuples of one + @inlinable + public func decode( + _: Column.Type, + file: String = #fileID, + line: Int = #line + ) throws -> (Column) { + try self.decode(Column.self, context: .default, file: file, line: line) + } + + @inlinable + public func decode( + _: Column.Type, + context: PostgresDecodingContext, + file: String = #fileID, + line: Int = #line + ) throws -> (Column) { + precondition(self.columns.count >= 1) + let columnIndex = 0 + var cellIterator = self.data.makeIterator() + var cellData = cellIterator.next().unsafelyUnwrapped + var columnIterator = self.columns.makeIterator() + let column = columnIterator.next().unsafelyUnwrapped + let swiftTargetType: Any.Type = Column.self + + do { + let r0 = try Column._decodeRaw(from: &cellData, type: column.dataType, format: column.format, context: context) + + return (r0) + } catch let code as PostgresDecodingError.Code { + throw PostgresDecodingError( + code: code, + columnName: column.name, + columnIndex: columnIndex, + targetType: swiftTargetType, + postgresType: column.dataType, + postgresFormat: column.format, + postgresData: cellData, + file: file, + line: line + ) + } + } + // --- snap TODO: Remove once bug is fixed, that disallows tuples of one + +// @inlinable // <--- commenting this in, crashes the compiler + public func decode( + _ columnType: (repeat each Column).Type, + context: PostgresDecodingContext, + file: String = #fileID, + line: Int = #line + ) throws -> (repeat each Column) { + var columnIndex = 0 + var cellIterator = self.data.makeIterator() + var columnIterator = self.columns.makeIterator() + + return ( + repeat try Self.decodeNextColumn( + (each Column).self, + cellIterator: &cellIterator, + columnIterator: &columnIterator, + columnIndex: &columnIndex, + context: context, + file: file, + line: line + ) + ) + } + + @inlinable + static func decodeNextColumn( + _ columnType: Column.Type, + cellIterator: inout IndexingIterator, + columnIterator: inout IndexingIterator<[RowDescription.Column]>, + columnIndex: inout Int, + context: PostgresDecodingContext, + file: String, + line: Int + ) throws -> Column { + defer { columnIndex += 1 } + + let column = columnIterator.next().unsafelyUnwrapped + var cellData = cellIterator.next().unsafelyUnwrapped + do { + return try Column._decodeRaw(from: &cellData, type: column.dataType, format: column.format, context: context) + } catch let code as PostgresDecodingError.Code { + throw PostgresDecodingError( + code: code, + columnName: column.name, + columnIndex: columnIndex, + targetType: Column.self, + postgresType: column.dataType, + postgresFormat: column.format, + postgresData: cellData, + file: file, + line: line + ) + } + } + +// @inlinable // <--- commenting this in, crashes the compiler + public func decode( + _ columnType: (repeat each Column).Type, + file: String = #fileID, + line: Int = #line + ) throws -> (repeat each Column) { + try self.decode(columnType, context: .default, file: file, line: line) + } +} + +extension AsyncSequence where Element == PostgresRow { + @inlinable + public func decode( + _: Column.Type, + context: PostgresDecodingContext, + file: String = #fileID, + line: Int = #line + ) -> AsyncThrowingMapSequence { + self.map { row in + try row.decode(Column.self, context: context, file: file, line: line) + } + } + + @inlinable + public func decode( + _: Column.Type, + file: String = #fileID, + line: Int = #line + ) -> AsyncThrowingMapSequence { + self.decode(Column.self, context: .default, file: file, line: line) + } + + #if false // commented out since, the AsyncSequence map currently crashes the compiler + public func decode( + _ columnType: (repeat each Column).Type, + context: PostgresDecodingContext, + file: String = #fileID, + line: Int = #line + ) throws -> AsyncThrowingMapSequence { + self.map { row in + try row.decode(columnType, context: context, file: file, line: line) + } + } + + public func decode( + _ columnType: (repeat each Column).Type, + file: String = #fileID, + line: Int = #line + ) throws -> AsyncThrowingMapSequence { + try self.decode(columnType, context: .default, file: file, line: line) + } + #endif // AsyncSequence extension +} + +#endif // hasFeature +#endif // compiler(>=5.9) diff --git a/Tests/IntegrationTests/AsyncTests.swift b/Tests/IntegrationTests/AsyncTests.swift index 7a45c5c0..fd7cd70d 100644 --- a/Tests/IntegrationTests/AsyncTests.swift +++ b/Tests/IntegrationTests/AsyncTests.swift @@ -37,7 +37,8 @@ final class AsyncPostgresConnectionTests: XCTestCase { try await withTestConnection(on: eventLoop) { connection in let rows = try await connection.query("SELECT generate_series(\(start), \(end));", logger: .psqlTest) var counter = 0 - for try await element in rows.decode(Int.self, context: .default) { + for try await row in rows { + let element = try row.decode(Int.self) XCTAssertEqual(element, counter + 1) counter += 1 } @@ -236,7 +237,8 @@ final class AsyncPostgresConnectionTests: XCTestCase { try await withTestConnection(on: eventLoop) { connection in let rows = try await connection.query("SELECT generate_series(\(start), \(end));", logger: .psqlTest) var counter = 1 - for try await element in rows.decode(Int.self, context: .default) { + for try await row in rows { + let element = try row.decode(Int.self, context: .default) XCTAssertEqual(element, counter) counter += 1 } diff --git a/Tests/PostgresNIOTests/New/PostgresRowSequenceTests.swift b/Tests/PostgresNIOTests/New/PostgresRowSequenceTests.swift index e1fdad11..fe1c2b5a 100644 --- a/Tests/PostgresNIOTests/New/PostgresRowSequenceTests.swift +++ b/Tests/PostgresNIOTests/New/PostgresRowSequenceTests.swift @@ -60,7 +60,7 @@ final class PostgresRowSequenceTests: XCTestCase { var counter = 0 for try await row in rowSequence { - XCTAssertEqual(try row.decode(Int.self, context: .default), counter) + XCTAssertEqual(try row.decode(Int.self), counter) counter += 1 if counter == 64 { @@ -142,7 +142,7 @@ final class PostgresRowSequenceTests: XCTestCase { var counter = 0 for try await row in rowSequence { - XCTAssertEqual(try row.decode(Int.self, context: .default), counter) + XCTAssertEqual(try row.decode(Int.self), counter) counter += 1 } @@ -172,7 +172,7 @@ final class PostgresRowSequenceTests: XCTestCase { var counter = 0 for try await row in rowSequence { - XCTAssertEqual(try row.decode(Int.self, context: .default), counter) + XCTAssertEqual(try row.decode(Int.self), counter) counter += 1 } @@ -233,7 +233,7 @@ final class PostgresRowSequenceTests: XCTestCase { } let row1 = try await rowIterator.next() - XCTAssertEqual(try row1?.decode(Int.self, context: .default), 0) + XCTAssertEqual(try row1?.decode(Int.self), 0) DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { stream.receive(completion: .success("SELECT 1")) @@ -267,7 +267,7 @@ final class PostgresRowSequenceTests: XCTestCase { } let row1 = try await rowIterator.next() - XCTAssertEqual(try row1?.decode(Int.self, context: .default), 0) + XCTAssertEqual(try row1?.decode(Int.self), 0) DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { stream.receive(completion: .failure(PSQLError.connectionClosed)) @@ -436,7 +436,7 @@ final class PostgresRowSequenceTests: XCTestCase { var counter = 1 for _ in 0..<(2 * messagePerChunk - 1) { let row = try await rowIterator.next() - XCTAssertEqual(try row?.decode(Int.self, context: .default), counter) + XCTAssertEqual(try row?.decode(Int.self), counter) counter += 1 } From 6115358457f07aa5cd046956b440445697e74679 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Wed, 10 May 2023 18:30:20 +0200 Subject: [PATCH 2/5] move forward --- Sources/PostgresNIO/New/VariadicGenerics.swift | 4 ++-- Tests/IntegrationTests/AsyncTests.swift | 1 - Tests/IntegrationTests/PostgresNIOTests.swift | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/PostgresNIO/New/VariadicGenerics.swift b/Sources/PostgresNIO/New/VariadicGenerics.swift index 5a780f13..97167e26 100644 --- a/Sources/PostgresNIO/New/VariadicGenerics.swift +++ b/Sources/PostgresNIO/New/VariadicGenerics.swift @@ -47,7 +47,7 @@ extension PostgresRow { } // --- snap TODO: Remove once bug is fixed, that disallows tuples of one -// @inlinable // <--- commenting this in, crashes the compiler + @inlinable public func decode( _ columnType: (repeat each Column).Type, context: PostgresDecodingContext, @@ -102,7 +102,7 @@ extension PostgresRow { } } -// @inlinable // <--- commenting this in, crashes the compiler + @inlinable public func decode( _ columnType: (repeat each Column).Type, file: String = #fileID, diff --git a/Tests/IntegrationTests/AsyncTests.swift b/Tests/IntegrationTests/AsyncTests.swift index fd7cd70d..2ac95b75 100644 --- a/Tests/IntegrationTests/AsyncTests.swift +++ b/Tests/IntegrationTests/AsyncTests.swift @@ -8,7 +8,6 @@ import NIOPosix import NIOCore final class AsyncPostgresConnectionTests: XCTestCase { - func test1kRoundTrips() async throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } diff --git a/Tests/IntegrationTests/PostgresNIOTests.swift b/Tests/IntegrationTests/PostgresNIOTests.swift index 19c4e167..ea4d8d05 100644 --- a/Tests/IntegrationTests/PostgresNIOTests.swift +++ b/Tests/IntegrationTests/PostgresNIOTests.swift @@ -1246,10 +1246,10 @@ final class PostgresNIOTests: XCTestCase { return EventLoopFuture.whenAllSucceed([a, b, c], on: self.eventLoop) }).wait()) XCTAssertEqual(queries?.count, 3) - var resutIterator = queries?.makeIterator() - XCTAssertEqual(try resutIterator?.next()?.first?.decode(String.self, context: .default), "a") - XCTAssertEqual(try resutIterator?.next()?.first?.decode(String.self, context: .default), "b") - XCTAssertEqual(try resutIterator?.next()?.first?.decode(String.self, context: .default), "c") + var resultIterator = queries?.makeIterator() + XCTAssertEqual(try resultIterator?.next()?.first?.decode(String.self, context: .default), "a") + XCTAssertEqual(try resultIterator?.next()?.first?.decode(String.self, context: .default), "b") + XCTAssertEqual(try resultIterator?.next()?.first?.decode(String.self, context: .default), "c") } // https://github.com/vapor/postgres-nio/issues/122 From c177ae955e200d8a4bf2cee44dde37ec509bbd93 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 1 Jun 2023 08:41:14 +0200 Subject: [PATCH 3/5] Use more features! --- Sources/PostgresNIO/New/VariadicGenerics.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/PostgresNIO/New/VariadicGenerics.swift b/Sources/PostgresNIO/New/VariadicGenerics.swift index 97167e26..2690e465 100644 --- a/Sources/PostgresNIO/New/VariadicGenerics.swift +++ b/Sources/PostgresNIO/New/VariadicGenerics.swift @@ -1,7 +1,6 @@ #if compiler(>=5.9) #if hasFeature(VariadicGenerics) extension PostgresRow { - // --- snip TODO: Remove once bug is fixed, that disallows tuples of one @inlinable public func decode( @@ -113,6 +112,7 @@ extension PostgresRow { } extension AsyncSequence where Element == PostgresRow { + // --- snip TODO: Remove once bug is fixed, that disallows tuples of one @inlinable public func decode( _: Column.Type, @@ -133,14 +133,14 @@ extension AsyncSequence where Element == PostgresRow { ) -> AsyncThrowingMapSequence { self.decode(Column.self, context: .default, file: file, line: line) } + // --- snap TODO: Remove once bug is fixed, that disallows tuples of one - #if false // commented out since, the AsyncSequence map currently crashes the compiler public func decode( _ columnType: (repeat each Column).Type, context: PostgresDecodingContext, file: String = #fileID, line: Int = #line - ) throws -> AsyncThrowingMapSequence { + ) -> AsyncThrowingMapSequence { self.map { row in try row.decode(columnType, context: context, file: file, line: line) } @@ -150,10 +150,9 @@ extension AsyncSequence where Element == PostgresRow { _ columnType: (repeat each Column).Type, file: String = #fileID, line: Int = #line - ) throws -> AsyncThrowingMapSequence { - try self.decode(columnType, context: .default, file: file, line: line) + ) -> AsyncThrowingMapSequence { + self.decode(columnType, context: .default, file: file, line: line) } - #endif // AsyncSequence extension } #endif // hasFeature From 0b176b05b64ce1756fe3df9d09fa8efa4b1c52eb Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 8 Jun 2023 14:47:31 +0200 Subject: [PATCH 4/5] We don't need requirement `.enableExperimentalFeature("VariadicGenerics")` anymore --- Package@swift-5.9.swift | 60 ------------------- .../PostgresNIO/New/VariadicGenerics.swift | 3 - 2 files changed, 63 deletions(-) delete mode 100644 Package@swift-5.9.swift diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift deleted file mode 100644 index f24a71d1..00000000 --- a/Package@swift-5.9.swift +++ /dev/null @@ -1,60 +0,0 @@ -// swift-tools-version:5.9 -import PackageDescription - -let package = Package( - name: "postgres-nio", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .watchOS(.v6), - .tvOS(.v13), - ], - products: [ - .library(name: "PostgresNIO", targets: ["PostgresNIO"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.44.0"), - .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.13.1"), - .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.1"), - .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), - .package(url: "https://github.com/apple/swift-metrics.git", from: "2.0.0"), - .package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"), - ], - targets: [ - .target( - name: "PostgresNIO", - dependencies: [ - .product(name: "Atomics", package: "swift-atomics"), - .product(name: "Crypto", package: "swift-crypto"), - .product(name: "Logging", package: "swift-log"), - .product(name: "Metrics", package: "swift-metrics"), - .product(name: "NIO", package: "swift-nio"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), - .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), - .product(name: "NIOTLS", package: "swift-nio"), - .product(name: "NIOSSL", package: "swift-nio-ssl"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ], - swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] - ), - .testTarget( - name: "PostgresNIOTests", - dependencies: [ - .target(name: "PostgresNIO"), - .product(name: "NIOEmbedded", package: "swift-nio"), - .product(name: "NIOTestUtils", package: "swift-nio"), - ], - swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] - ), - .testTarget( - name: "IntegrationTests", - dependencies: [ - .target(name: "PostgresNIO"), - .product(name: "NIOTestUtils", package: "swift-nio"), - ], - swiftSettings: [.enableExperimentalFeature("VariadicGenerics")] - ), - ] -) diff --git a/Sources/PostgresNIO/New/VariadicGenerics.swift b/Sources/PostgresNIO/New/VariadicGenerics.swift index 2690e465..37012b8f 100644 --- a/Sources/PostgresNIO/New/VariadicGenerics.swift +++ b/Sources/PostgresNIO/New/VariadicGenerics.swift @@ -1,5 +1,4 @@ #if compiler(>=5.9) -#if hasFeature(VariadicGenerics) extension PostgresRow { // --- snip TODO: Remove once bug is fixed, that disallows tuples of one @inlinable @@ -154,6 +153,4 @@ extension AsyncSequence where Element == PostgresRow { self.decode(columnType, context: .default, file: file, line: line) } } - -#endif // hasFeature #endif // compiler(>=5.9) From da907fbfc2b3b1bb8540ab5071fc47aca81eec4a Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Wed, 12 Jul 2023 08:34:22 +0200 Subject: [PATCH 5/5] Variadic Generic decoding: Ensure PostgresRow length check --- Sources/PostgresNIO/New/VariadicGenerics.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/PostgresNIO/New/VariadicGenerics.swift b/Sources/PostgresNIO/New/VariadicGenerics.swift index 37012b8f..312d36dc 100644 --- a/Sources/PostgresNIO/New/VariadicGenerics.swift +++ b/Sources/PostgresNIO/New/VariadicGenerics.swift @@ -52,6 +52,9 @@ extension PostgresRow { file: String = #fileID, line: Int = #line ) throws -> (repeat each Column) { + let packCount = ComputeParameterPackLength.count(ofPack: repeat (each Column).self) + precondition(self.columns.count >= packCount) + var columnIndex = 0 var cellIterator = self.data.makeIterator() var columnIterator = self.columns.makeIterator() @@ -153,4 +156,19 @@ extension AsyncSequence where Element == PostgresRow { self.decode(columnType, context: .default, file: file, line: line) } } + +@usableFromInline +enum ComputeParameterPackLength { + @usableFromInline + enum BoolConverter { + @usableFromInline + typealias Bool = Swift.Bool + } + + @inlinable + static func count(ofPack t: repeat each T) -> Int { + MemoryLayout<(repeat BoolConverter.Bool)>.size / MemoryLayout.stride + } +} #endif // compiler(>=5.9) +