import Foundation /// Representable by a `T[]` column on the PostgreSQL database. public protocol PostgreSQLArrayCustomConvertible: PostgreSQLDataConvertible { /// The associated array element type associatedtype PostgreSQLArrayElement // : PostgreSQLDataCustomConvertible /// Convert an array of elements to self. static func convertFromPostgreSQLArray(_ data: [PostgreSQLArrayElement]) -> Self /// Convert self to an array of elements. func convertToPostgreSQLArray() -> [PostgreSQLArrayElement] } extension PostgreSQLArrayCustomConvertible { /// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)` public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self { guard var value = data.data else { throw PostgreSQLError(identifier: "nullArray", reason: "Unable to decode PostgreSQL array from `null` data.", source: .capture()) } /// Extract and convert each element. var array: [PostgreSQLArrayElement] = [] let hasData = value.extract(Int32.self).bigEndian if hasData == 1 { /// grab the array metadata from the beginning of the data let metadata = value.extract(PostgreSQLArrayMetadata.self) for _ in 0..<metadata.count { let count = Int(value.extract(Int32.self).bigEndian) let subValue = value.extract(count: count) let psqlData = PostgreSQLData(type: metadata.type, format: data.format, data: subValue) let element = try requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).convertFromPostgreSQLData(psqlData) array.append(element as! PostgreSQLArrayElement) } } else { array = [] } return convertFromPostgreSQLArray(array) } /// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()` public func convertToPostgreSQLData() throws -> PostgreSQLData { let elements = try convertToPostgreSQLArray().map { try requirePostgreSQLDataCustomConvertible($0).convertToPostgreSQLData() } var data = Data() data += Int32(1).data // non-null data += Int32(0).data // b data += requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).postgreSQLDataType.raw.data // type data += Int32(elements.count).data // length data += Int32(1).data // dimensions for element in elements { if let value = element.data { data += Int32(value.count).data data += value } else { data += Int32(0).data } } return PostgreSQLData(type: requirePostgreSQLDataCustomConvertible(PostgreSQLArrayElement.self).postgreSQLDataArrayType, format: .binary, data: data) } } fileprivate struct PostgreSQLArrayMetadata { /// Unknown private let _b: Int32 /// The big-endian array element type private let _type: Int32 /// The big-endian length of the array private let _count: Int32 /// The big-endian number of dimensions private let _dimensions: Int32 /// Converts the raw array elemetn type to DataType var type: PostgreSQLDataType { return .init(_type.bigEndian) } /// The length of the array var count: Int32 { return _count.bigEndian } /// The number of dimensions var dimensions: Int32 { return _dimensions.bigEndian } } extension PostgreSQLArrayMetadata: CustomStringConvertible { /// See `CustomStringConvertible.description` var description: String { return "\(type)[\(count)]" } } extension Array: PostgreSQLArrayCustomConvertible { /// See `PostgreSQLArrayCustomConvertible.postgreSQLDataArrayType` public static var postgreSQLDataArrayType: PostgreSQLDataType { fatalError("Multi-dimensional arrays are not yet supported.") } /// See `PostgreSQLDataCustomConvertible.postgreSQLDataType` public static var postgreSQLDataType: PostgreSQLDataType { return requirePostgreSQLDataCustomConvertible(Element.self).postgreSQLDataArrayType } /// See `PostgreSQLArrayCustomConvertible.PostgreSQLArrayElement` public typealias PostgreSQLArrayElement = Element /// See `PostgreSQLArrayCustomConvertible.convertFromPostgreSQLArray(_:)` public static func convertFromPostgreSQLArray(_ data: [Element]) -> Array<Element> { return data } /// See `PostgreSQLArrayCustomConvertible.convertToPostgreSQLArray(_:)` public func convertToPostgreSQLArray() -> [Element] { return self } } func requirePostgreSQLDataCustomConvertible<T>(_ type: T.Type) -> PostgreSQLDataConvertible.Type { guard let custom = T.self as? PostgreSQLDataConvertible.Type else { fatalError("`\(T.self)` does not conform to `PostgreSQLDataCustomConvertible`") } return custom } func requirePostgreSQLDataCustomConvertible<T>(_ type: T) -> PostgreSQLDataConvertible { guard let custom = type as? PostgreSQLDataConvertible else { fatalError("`\(T.self)` does not conform to `PostgreSQLDataCustomConvertible`") } return custom }