1 // Written in D programming language
2 /**
3 *   Utilities for conversion from PostgreSQL binary format.
4 *
5 *   Copyright: © 2014 DSoftOut
6 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
7 *   Authors: NCrashed <ncrashed@gmail.com>
8 */
9 module pgator.db.pq.types.conv;
10 
11 import pgator.db.pq.types.oids;
12 import pgator.db.connection;
13 import vibe.data.bson;
14 import dlogg.log;
15 import std.conv;
16 import std.traits;
17 import std.typetuple;
18 import std.datetime : SysTime;
19 //import util;
20 
21 import pgator.db.pq.types.all;
22 
23 bool nonConvertable(PQType type)
24 {
25     switch(type)
26     {
27         case PQType.RegProc: return true;
28         case PQType.RegProcArray: return true;
29         case PQType.TypeCatalog: return true;
30         case PQType.AttributeCatalog: return true;
31         case PQType.ProcCatalog: return true;
32         case PQType.ClassCatalog: return true; 
33         case PQType.StorageManager: return true;
34         case PQType.Tid: return true;
35         case PQType.TidArray: return true;
36         case PQType.Line: return true;
37         case PQType.AccessControlList: return true;
38         case PQType.AccessControlListArray: return true;
39         
40         // awaiting implementation
41         case PQType.FixedBitString: return true;
42         case PQType.FixedBitStringArray: return true;
43         case PQType.VariableBitString: return true;
44         case PQType.VariableBitStringArray: return true;
45         
46         case PQType.RefCursor: return true;
47         case PQType.RefCursorArray: return true;
48         case PQType.RegProcWithArgs: return true;
49         case PQType.RegProcWithArgsArray: return true;
50         case PQType.RegOperator: return true;
51         case PQType.RegOperatorArray: return true;
52         case PQType.RegOperatorWithArgs: return true;
53         case PQType.RegOperatorWithArgsArray: return true;
54         case PQType.RegClass: return true;
55         case PQType.RegClassArray: return true;
56         case PQType.RegType: return true;
57         case PQType.RegTypeArray: return true;
58         
59         case PQType.UUID: return true;
60         case PQType.UUIDArray: return true;
61         case PQType.TSVector: return true;
62         case PQType.TSVectorArray: return true;
63         case PQType.GTSVector: return true;
64         case PQType.GTSVectorArray: return true;
65         case PQType.TSQuery: return true;
66         case PQType.TSQueryArray: return true;
67         case PQType.RegConfig: return true;
68         case PQType.RegConfigArray: return true;
69         case PQType.RegDictionary: return true;
70         case PQType.RegDictionaryArray: return true;
71         case PQType.TXidSnapshot: return true;
72         case PQType.TXidSnapshotArray: return true;
73         
74         case PQType.Int4Range: return true;
75         case PQType.Int4RangeArray: return true;
76         case PQType.NumRange: return true;
77         case PQType.NumRangeArray: return true;
78         case PQType.TimeStampRange: return true;
79         case PQType.TimeStampRangeArray: return true;
80         case PQType.TimeStampWithZoneRange: return true;
81         case PQType.TimeStampWithZoneRangeArray: return true;
82         case PQType.DateRange: return true;
83         case PQType.DateRangeArray: return true;
84         case PQType.Int8Range: return true;
85         case PQType.Int8RangeArray: return true;
86         
87         // Pseudo types
88         case PQType.CString: return true;
89         case PQType.Record: return true;
90         case PQType.RecordArray: return true;
91         case PQType.AnyVoid: return true;
92         case PQType.AnyArray: return true;
93         case PQType.Trigger: return true;
94         case PQType.EventTrigger: return true;
95         case PQType.LanguageHandler: return true;
96         case PQType.Internal: return true;
97         case PQType.Opaque: return true;
98         case PQType.AnyElement: return true;
99         case PQType.AnyNoArray: return true;
100         case PQType.AnyEnum: return true;
101         case PQType.FDWHandler: return true;
102         case PQType.AnyRange: return true;
103         default: return false;
104     }
105 }
106 
107 Bson toBson(PQType type)(ubyte[] val, shared IConnection conn)
108 {
109     template IsNativeSupport(T)
110     {  
111         import std.range;
112         
113         static if (is(T == string) || is(T == ubyte[]) || is(T == Json))
114         {
115             enum IsNativeSupport = true;
116         }
117         else static if(isArray!T)
118         {
119             enum IsNativeSupport = IsNativeSupport!(ElementType!T);
120         }
121         else
122         {
123             enum IsNativeSupport = 
124                    is(T == bool)
125                 || is(T == float)
126                 || is(T == double)
127                 || is(T == short)
128                 || is(T == ushort)
129                 || is(T == int)
130                 || is(T == uint)
131                 || is(T == long)
132                 || is(T == ulong)
133                 || is(T == PGNumeric);
134         }
135     }
136     
137     bool checkNullValues(T)(out Bson bson)
138     {
139         static if(isSomeString!T)
140         {
141             if(val.length == 0) 
142             {
143                 bson = serializeToBson("");
144                 return true;
145             }
146         }
147         else static if(isArray!T)
148         {
149             if(val.length == 0) 
150             {
151                 bson = serializeToBson(cast(T)[]);
152                 return true;
153             }
154         } else
155         {
156             if(val.length == 0)
157             {
158                 bson = Bson(null);
159                 return true;
160             }
161         }
162         return false;
163     }
164     
165     // Checking if the convert function needs connection for reverse link
166     static if(is(ParameterTypeTuple!(convert!type) == TypeTuple!(ubyte[])))
167     {
168         alias typeof(convert!type(val)) T;
169         
170         Bson retBson; if(checkNullValues!T(retBson)) return retBson;
171     
172         auto convVal = convert!type(val);
173     } else static if(is(ParameterTypeTuple!(convert!type) == TypeTuple!(ubyte[], shared IConnection)))
174     {
175         alias typeof(convert!type(val, conn)) T;
176     
177         Bson retBson; if(checkNullValues!T(retBson)) return retBson;
178         
179         auto convVal = convert!type(val, conn);
180     } else
181     {
182         static assert(false, text("Doesn't support '",ParameterTypeTuple!(convert!type),"' signature of converting function"));
183     }
184     
185     static if(is(T == ubyte[]))
186     {
187         return serializeToBson(new BsonBinData(BsonBinData.Type.generic, convVal.idup));
188     }
189     else static if(IsNativeSupport!T)
190     {
191         return serializeToBson(convVal); 
192     } 
193     else static if(is(T == SysTime))
194     {
195         return serializeToBson(convVal.stdTime);
196     } 
197     else static if(is(T == PGNumeric))
198     {
199         double store;
200         if(convVal.canBeNative(store))
201             return serializeToBson(store);
202         else
203             return serializeToBson(convVal.to!string);
204     }
205     else static if(is(T == struct) || is(T == class))
206     {
207         return serializeToBson(convVal);
208     }
209     else
210     {
211         return serializeToBson(convVal.to!string);   
212     }
213 }
214 
215 Bson pqToBson(PQType type, ubyte[] val, shared IConnection conn, shared ILogger logger)
216 {
217     try
218     {
219         foreach(ts; __traits(allMembers, PQType))
220         {
221             enum t = mixin("PQType."~ts);
222             if(type == t)
223             {
224                 static if(nonConvertable(t))
225                 {
226                     enum errMsg = ts ~ " is not supported!";
227                     pragma(msg, errMsg);
228                     assert(false,errMsg);
229                 } else
230                 {
231                     return toBson!t(val, conn); 
232                 }
233             }
234         }
235     }
236     catch(Exception e)
237     {
238         logger.logError(text("Binary protocol exception: ", e.msg));
239         logger.logError(text("Converting from: ", type));
240         logger.logError(text("Payload: ", val));
241         logger.logError(text("Stack trace: ", e));
242         throw e;
243     }
244     catch(Error err)
245     {
246         logger.logError(text("Binary protocol error (logic error): ", err.msg));
247         logger.logError(text("Converting from: ", type));
248         logger.logError(text("Payload: ", val));
249         logger.logError(text("Stack trace: ", err));
250         throw err;
251     }
252     
253     debug assert(false, "Unknown type "~to!string(type)~"!");
254     else
255     {
256         throw new Exception(text("pgator doesn't support typeid ", type," at the moment! Please, visit "
257                 "https://github.com/DSoftOut/pgator and open an issue."));
258     }
259 }
260 
261 version(IntegrationTest2)
262 {
263     import pgator.db.pool;
264     import dlogg.log;
265     
266     void testConvertions(shared ILogger logger, shared IConnectionPool pool)
267     {
268         foreach(t; __traits(allMembers, PQType))
269         {
270             enum type = mixin("PQType."~t);
271             static if(!nonConvertable(type)) 
272             {
273                 test!type(logger, pool);
274             }
275         }
276     }
277 }