@@ -4,77 +4,56 @@ import FluentSQL
44import Foundation
55
66/// Adds ability to do basic Fluent queries using a `PostgreSQLDatabase`.
7- extension PostgreSQLDatabase : QuerySupporting {
7+ extension PostgreSQLDatabase : QuerySupporting , CustomSQLSupporting {
88 /// See `QuerySupporting.execute`
9- public static func execute< I, D> ( query: DatabaseQuery < PostgreSQLDatabase > , into stream: I , on connection: PostgreSQLConnection )
10- where I: Async . InputStream , D: Decodable , D == I . Input
11- {
12- let future = Future < Void > . flatMap {
9+ public static func execute(
10+ query: DatabaseQuery < PostgreSQLDatabase > ,
11+ into handler: @escaping ( [ QueryField : PostgreSQLData ] , PostgreSQLConnection ) throws -> ( ) ,
12+ on connection: PostgreSQLConnection
13+ ) -> EventLoopFuture < Void > {
14+ /// wait for the table name cache before continuing
15+ return PostgreSQLTableNameCache . get ( for: connection) . flatMap ( to: Void . self) { tableNameCache in
1316 // Convert Fluent `DatabaseQuery` to generic FluentSQL `DataQuery`
1417 var ( sqlQuery, bindValues) = query. makeDataQuery ( )
1518
1619 // If the query has an Encodable model attached serialize it.
1720 // Dictionary keys should be added to the DataQuery as columns.
1821 // Dictionary values should be added to the parameterized array.
19- let modelData : [ PostgreSQLData ]
20- if let model = query. data {
21- let encoder = PostgreSQLRowEncoder ( )
22- try model. encode ( to: encoder)
23- sqlQuery. columns += encoder. data. keys. map { key in
24- return DataColumn ( table: query. entity, name: key)
25- }
26- modelData = . init( encoder. data. values)
27- } else {
28- modelData = [ ]
22+ var modelData : [ PostgreSQLData ] = [ ]
23+ modelData. reserveCapacity ( query. data. count)
24+ for (field, data) in query. data {
25+ if case . create = query. action, data. isNull && field. name == " id " { continue } // bad hack
26+ sqlQuery. columns. append ( DataColumn ( table: field. entity, name: field. name) )
27+ modelData. append ( data)
28+ }
29+
30+ /// Apply custom sql transformations
31+ for customSQL in query. customSQL {
32+ customSQL. closure ( & sqlQuery)
2933 }
3034
3135 // Create a PostgreSQL-flavored SQL serializer to create a SQL string
3236 let sqlSerializer = PostgreSQLSQLSerializer ( )
3337 let sqlString = sqlSerializer. serialize ( data: sqlQuery)
3438
35- // Combine the query data with bind values from filters.
36- // All bind values must come _after_ the columns section of the query.
37- let parameters = try modelData + bindValues. map { bind in
38- let encodable = bind. encodable
39- guard let convertible = encodable as? PostgreSQLDataCustomConvertible else {
40- let type = Swift . type ( of: encodable)
41- throw PostgreSQLError (
42- identifier: " convertible " ,
43- reason: " Unsupported encodable type: \( type) " ,
44- suggestedFixes: [
45- " Conform \( type) to PostgreSQLDataCustomConvertible "
46- ] ,
47- source: . capture( )
48- )
49- }
50- return try convertible. convertToPostgreSQLData ( )
51- }
39+ /// Convert params
40+ let parameters : [ PostgreSQLData ] = modelData + bindValues
5241
53- // Create a push stream to accept the psql output
54- // FIXME: connect streams directly instead?
55- let pushStream = PushStream < D > ( )
56- pushStream . output ( to : stream )
42+ /// Log supporting
43+ if let logger = connection . logger {
44+ logger . log ( query : sqlString , parameters : parameters )
45+ }
5746
5847 // Run the query
5948 return try connection. query ( sqlString, parameters) { row in
60- do {
61- let decoded = try D . init ( from: PostgreSQLRowDecoder ( row: row) )
62- pushStream. push ( decoded)
63- } catch {
64- pushStream. error ( error)
49+ var res : [ QueryField : PostgreSQLData ] = [ : ]
50+ for (col, data) in row {
51+ let field = QueryField ( entity: tableNameCache. storage [ col. tableOID] , name: col. name)
52+ res [ field] = data
6553 }
54+ try handler ( res, connection)
6655 }
6756 }
68-
69- /// Convert Future completion / error to stream
70- future. do {
71- // Query is complete
72- stream. close ( )
73- } . catch { error in
74- // Query failed
75- stream. error ( error)
76- stream. close ( )
77- }
7857 }
7958
8059 /// See `QuerySupporting.modelEvent`
@@ -86,19 +65,54 @@ extension PostgreSQLDatabase: QuerySupporting {
8665 if M . ID. self == UUID . self {
8766 var model = model
8867 model. fluentID = UUID ( ) as? M . ID
89- return Future ( model )
68+ return Future . map ( on : connection ) { model }
9069 }
9170 case . didCreate:
92- if M . ID. self == Int . self {
71+ if M . ID. self == Int . self, model . fluentID == nil {
9372 return connection. simpleQuery ( " SELECT LASTVAL(); " ) . map ( to: M . self) { row in
9473 var model = model
95- try model. fluentID = row [ 0 ] [ " lastval " ] ? . decode ( Int . self) as? M . ID
74+ try model. fluentID = row [ 0 ] . firstValue ( forColumn : " lastval " ) ? . decode ( Int . self) as? M . ID
9675 return model
9776 }
9877 }
9978 default : break
10079 }
101-
102- return Future ( model)
80+
81+ return Future . map ( on: connection) { model }
82+ }
83+
84+ /// See `QuerySupporting.QueryDataConvertible`
85+ public typealias QueryDataConvertible = PostgreSQLDataConvertible
86+
87+ /// See `QuerySupporting.queryDataParse(_:from:)`
88+ public static func queryDataParse< T> ( _ type: T . Type , from data: PostgreSQLData ) throws -> T ? {
89+ if data. isNull {
90+ return nil
91+ }
92+ guard let convertibleType = T . self as? PostgreSQLDataConvertible . Type else {
93+ throw PostgreSQLError ( identifier: " queryDataParse " , reason: " Cannot parse \( T . self) from PostgreSQLData " , source: . capture( ) )
94+ }
95+ let t : T = try convertibleType. convertFromPostgreSQLData ( data) as! T
96+ return t
10397 }
98+
99+ /// See `QuerySupporting.queryDataSerialize(data:)`
100+ public static func queryDataSerialize< T> ( data: T ? ) throws -> PostgreSQLData {
101+ if let data = data {
102+ guard let convertible = data as? PostgreSQLDataConvertible else {
103+ throw PostgreSQLError ( identifier: " queryDataSerialize " , reason: " Cannot serialize \( T . self) to PostgreSQLData " , source: . capture( ) )
104+ }
105+ return try convertible. convertToPostgreSQLData ( )
106+ } else {
107+ guard let convertibleType = T . self as? PostgreSQLDataConvertible . Type else {
108+ throw PostgreSQLError ( identifier: " queryDataParse " , reason: " Cannot parse \( T . self) from PostgreSQLData " , source: . capture( ) )
109+ }
110+ return PostgreSQLData ( type: convertibleType. postgreSQLDataType, format: . binary, data: nil )
111+ }
112+ }
113+
114+ /// See `QuerySupporting.QueryFilter`
115+ public typealias QueryFilter = DataPredicateComparison
104116}
117+
118+ extension PostgreSQLData : FluentData { }
0 commit comments