1 // Written in D programming language
2 /**
3 *   PostgreSQL common types 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.plain;
10 
11 import pgator.db.pq.types.oids;
12 import vibe.data.json;
13 import std.array;
14 import std.bitmanip;
15 import std.format;
16 import std.conv;
17 
18 alias uint RegProc;
19 alias uint Oid;
20 alias uint Xid;
21 alias uint Cid;
22 
23 struct PQTid
24 {
25     uint blockId, blockNumber;
26     
27     string toString() const
28     {
29         return text("'(",blockId, ",", blockNumber, ")'");
30     }
31 }
32 
33 bool convert(PQType type)(ubyte[] val)
34     if(type == PQType.Bool)
35 {
36     assert(val.length == 1);
37     return val[0] != 0;
38 }
39 
40 /**
41 *   Converts byte array into hex escaped SQL byte array.
42 */
43 string escapeBytea(const ubyte[] arr)
44 {
45     auto builder = appender!string;
46     foreach(b; arr)
47         formattedWrite(builder, "%02X", b);
48     
49     return `E'\\x`~builder.data~"'"; 
50 }
51 
52 ubyte[] convert(PQType type)(ubyte[] val)
53     if(type == PQType.ByteArray)
54 {
55     return val.dup;
56 }
57 
58 char convert(PQType type)(ubyte[] val)
59     if(type == PQType.Char)
60 {
61     assert(val.length == 1, text("Expected 1 bytes, but got ", val.length));
62     return val.read!char;
63 }
64 
65 string convert(PQType type)(ubyte[] val)
66     if(type == PQType.Name)
67 {
68     assert(val.length == 63, text("Expected 63 bytes for name, but there are ", val.length, " bytes!"));
69     return cast(string)val.idup;
70 }
71 
72 long convert(PQType type)(ubyte[] val)
73     if(type == PQType.Int8)
74 {
75     assert(val.length == 8, text("Expected 8 bytes, but got ", val.length));
76     return val.read!long;
77 }
78 
79 short convert(PQType type)(ubyte[] val)
80     if(type == PQType.Int2)
81 {
82     assert(val.length == 2, text("Expected 2 bytes, but got ", val.length));
83     return val.read!short;
84 }
85 
86 int convert(PQType type)(ubyte[] val)
87     if(type == PQType.Int4)
88 {
89     assert(val.length == 4, text("Expected 4 bytes, but got ", val.length));
90     return val.read!int;
91 }
92 
93 RegProc convert(PQType type)(ubyte[] val)
94     if(type == PQType.RegProc)
95 {
96     assert(val.length == 4, text("Expected 4 bytes, but got ", val.length));
97     return val.read!RegProc;
98 }
99 
100 string convert(PQType type)(ubyte[] val)
101     if(type == PQType.Text || type == PQType.FixedString || type == PQType.VariableString)
102 {
103     return cast(string)val.idup;
104 }
105 
106 Oid convert(PQType type)(ubyte[] val)
107     if(type == PQType.Oid)
108 {
109     assert(val.length == Oid.sizeof);
110     return val.read!Oid;
111 }
112 
113 PQTid convert(PQType type)(ubyte[] val)
114     if(type == PQType.Tid)
115 {
116     assert(val.length == 8, text("Expected 8 bytes, but got ", val.length));
117     PQTid res;
118     res.blockId = val.read!uint;
119     res.blockNumber = val.read!uint;
120     return res;
121 }
122 
123 Xid convert(PQType type)(ubyte[] val)
124     if(type == PQType.Xid)
125 {
126     assert(val.length == 4, text("Expected 4 bytes, but got ", val.length));
127     return val.read!uint;
128 }
129 
130 Cid convert(PQType type)(ubyte[] val)
131     if(type == PQType.Cid)
132 {
133     assert(val.length == 4, text("Expected 4 bytes, but got ", val.length));
134     return val.read!uint;
135 }
136 
137 Json convert(PQType type)(ubyte[] val)
138     if(type == PQType.Json)
139 {
140     return parseJsonString(cast(string)val.idup);
141 }
142 
143 string convert(PQType type)(ubyte[] val)
144     if(type == PQType.Xml)
145 {
146     return cast(string)val.idup;
147 }
148 
149 string convert(PQType type)(ubyte[] val)
150     if(type == PQType.NodeTree)
151 {
152     assert(val.length > 0);
153     return cast(string)val.idup;
154 }
155 
156 float convert(PQType type)(ubyte[] val)
157     if(type == PQType.Float4)
158 {
159     assert(val.length == 4);
160     return val.read!float;
161 }
162 
163 float convert(PQType type)(ubyte[] val)
164     if(type == PQType.Float8)
165 {
166     assert(val.length == 8);
167     return val.read!double;
168 }
169 
170 string convert(PQType type)(ubyte[] val)
171     if(type == PQType.Unknown)
172 {
173     return convert!(PQType.Text)(val);
174 }
175 
176 long convert(PQType type)(ubyte[] val)
177     if(type == PQType.Money)
178 {
179     assert(val.length == 8);
180     return val.read!long;
181 }
182 
183 string convert(PQType type)(ubyte[] val)
184     if(type == PQType.Void)
185 {
186     return "";
187 }
188 
189 version(IntegrationTest2)
190 {
191     import pgator.db.pq.types.test;
192     import pgator.db.pool;
193     import std.random;
194     import std.algorithm;
195     import std.encoding;
196     import std.math;
197     import vibe.data.bson;
198     import dlogg.log;
199     import dlogg.buffered;
200     
201      void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
202          if(type == PQType.Bool)
203      {
204          logger.logInfo("Testing Bool...");
205          testValue!bool(logger, pool, true, "boolean");
206          testValue!bool(logger, pool, false, "boolean");
207      }
208 
209     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
210         if(type == PQType.ByteArray)
211     {
212         ubyte[] genRand(size_t n)
213         {
214             auto builder = appender!(ubyte[]);
215             foreach(i; 0..n)
216                 builder.put(cast(ubyte)uniform(0u, 255u));
217             return builder.data; 
218         }
219         
220         logger.logInfo("Testing ByteArray...");
221         foreach(i; 0..100)
222             testValue!(ubyte[], escapeBytea)(logger, pool, genRand(i), "bytea");
223 
224     }
225     
226     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
227         if(type == PQType.Char)
228     {
229         logger.logInfo("Testing Char...");
230         alias testValue!(string, to!string, (str) {
231                 str = str.strip('\'');
232                 return str == `\` ? `\0` : str;}) test;
233         
234         test(logger, pool, `'\0'`, `"char"`);
235         test(logger, pool, `''''`, `"char"`);
236         foreach(char c; char.min .. char.max)
237             if((['\0', '\'']).find(c).empty && isValid(`'`~c~`'`))
238                 test(logger, pool, `'`~c~`'`, `"char"`);
239         
240     }
241     
242     string genRandString(size_t n)
243     {
244         auto builder = appender!string;
245         immutable aphs = "1234567890qwertyuiopasdfghjklzxcvbnm";
246         foreach(i; 0..n)
247             builder.put(aphs[uniform(0, aphs.length)]);
248         return builder.data;    
249     }
250         
251     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
252         if(type == PQType.Name)
253     {
254         logger.logInfo("Testing Name...");
255         
256         foreach(i; 0..100)
257             testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(63)~`'`, "name");
258     }
259     
260     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
261         if(type == PQType.Text)
262     {
263         logger.logInfo("Testing Text...");
264         
265         testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "text");      
266         foreach(i; 0..100)
267             testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "text");
268     }
269     
270     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
271         if(type == PQType.VariableString)
272     {
273         logger.logInfo("Testing varchar[n]...");
274         
275         testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "varchar");      
276         foreach(i; 0..100)
277             testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "varchar(50)");
278     }
279     
280     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
281         if(type == PQType.FixedString)
282     {
283         logger.logInfo("Testing char[n]...");
284           
285         foreach(i; 0..100)
286             testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "char(50)");
287     }
288     
289     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
290         if(type == PQType.CString)
291     {
292         logger.logInfo("Testing cstring...");
293         
294         testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "cstring");   
295         foreach(i; 0..100)
296             testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "cstring");
297     }
298     
299     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
300         if(type == PQType.Unknown)
301     {
302         logger.logInfo("Testing Unknown...");
303         
304         testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'Unknown'`, "unknown");   
305     }
306     
307     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
308         if(type == PQType.Int8)
309     {
310         logger.logInfo("Testing Int8...");
311         foreach(i; 0..100)
312             testValue!long(logger, pool, uniform(long.min, long.max), "Int8");
313     }
314     
315     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
316         if(type == PQType.Money)
317     {
318         logger.logInfo("Testing Money...");
319         foreach(i; 0..100)
320             testValue!long(logger, pool, uniform(long.min, long.max), "Int8");
321     }
322     
323     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
324         if(type == PQType.Int4)
325     {
326         logger.logInfo("Testing Int4...");
327         foreach(i; 0..100)
328             testValue!int(logger, pool, uniform(int.min, int.max), "Int4");
329     }
330     
331     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
332         if(type == PQType.Int2)
333     {
334         logger.logInfo("Testing Int2...");
335         foreach(i; 0..100)
336             testValue!int(logger, pool, uniform(short.min, short.max), "Int2");
337     }
338     
339     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
340         if(type == PQType.Oid)
341     {
342         logger.logInfo("Testing Oid...");
343         foreach(i; 0..100)
344             testValue!Oid(logger, pool, uniform(Oid.min, Oid.max), "Oid");
345     }
346     
347     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
348         if(type == PQType.RegProc)
349     {
350         logger.logInfo("Testing RegProc...");
351         foreach(i; 0..100)
352             testValue!RegProc(logger, pool, uniform(RegProc.min, RegProc.max), "regproc");
353     }
354     
355     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
356         if(type == PQType.Tid)
357     {
358         logger.logInfo("Testing Tid...");
359         foreach(i; 0..100)
360         {
361             auto testTid = PQTid(uniform(uint.min, uint.max), uniform(uint.min, uint.max));
362             testValue!PQTid(logger, pool, testTid, "tid");
363         }
364     }
365     
366     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
367         if(type == PQType.Xid)
368     {
369         logger.logInfo("Testing Xid...");
370         foreach(i; 0..100)
371         {
372             testValue!(Xid, (v) => "'"~v.to!string~"'")(logger, pool, uniform(Xid.min, Xid.max), "xid");
373         }
374     }
375     
376     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
377         if(type == PQType.Cid)
378     {
379         logger.logInfo("Testing Cid...");
380         foreach(i; 0..100)
381         {   // postgres trims large cid values
382             testValue!(Cid, (v) => "'"~v.to!string~"'")(logger, pool, uniform(Cid.min/4, Cid.max/4), "cid");
383         }
384     }
385     
386     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
387         if(type == PQType.Json)
388     {
389         logger.logInfo("Testing Json...");
390 
391         auto json = Json.emptyObject;
392         json.str = genRandString(10);
393         json.arr = serializeToJson([4,8,15,16,23,42]);
394         json.boolean = uniform(0,1) != 0;
395         //json.floating = cast(double)42.0; hard to compare properly
396         json.integer  = cast(long)42;
397         json.nullable = null;
398         json.mapping = ["1":Json(4), "2":Json(8), "3":Json(15)];
399         
400         testValue!(Json, (v) => "'"~v.to!string~"'")(logger, pool, json, "json");
401     }
402     
403     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
404         if(type == PQType.Xml)
405     {
406         logger.logInfo("Testing Xml...");
407         
408         testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'‹?xml version= "1.0"›'`, "xml");   
409     }
410     
411     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
412         if(type == PQType.NodeTree)
413     {
414         logger.logInfo("Testing NodeTree...");
415         logger.logInfo("Not testable");
416     }
417     
418     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
419         if(type == PQType.Float4)
420     {
421         logger.logInfo("Testing Float4...");
422         string convFloat(float t)
423         {
424            if(t == float.infinity) return "'Infinity'";
425            else if(t == -float.infinity) return "'-Infinity'";
426            else if(isNaN(t)) return "'NaN'";
427            else return t.to!string;
428         }
429         testValue!(float, convFloat)(logger, pool, float.infinity, "Float4");
430         testValue!(float, convFloat)(logger, pool, -float.infinity, "Float4");
431         testValue!(float, convFloat)(logger, pool, -float.nan, "Float4");
432         
433         foreach(i; 0..100)
434             testValue!(float, convFloat)(logger, pool, uniform(-100.0, 100.0), "Float4");
435     }
436     
437     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
438         if(type == PQType.Float8)
439     {
440         logger.logInfo("Testing Float8...");
441         string convFloat(double t)
442         {
443            if(t == double.infinity) return "'Infinity'";
444            else if(t == -double.infinity) return "'-Infinity'";
445            else if(isNaN(t)) return "'NaN'";
446            else return t.to!string;
447         }
448         testValue!(double, convFloat)(logger, pool, double.infinity, "Float8");
449         testValue!(double, convFloat)(logger, pool, -double.infinity, "Float8");
450         testValue!(double, convFloat)(logger, pool, -double.nan, "Float8");
451         
452         foreach(i; 0..100)
453             testValue!(double, convFloat)(logger, pool, uniform(-100.0, 100.0), "Float8");
454     }
455     
456     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
457         if(type == PQType.Void)
458     {
459         logger.logInfo("Testing Void...");
460         
461         pool.execTransaction(["drop function if exists pgator_test_void();"]);
462         pool.execTransaction(["CREATE FUNCTION pgator_test_void() RETURNS void AS $$ DECLARE BEGIN END; $$ LANGUAGE plpgsql;"]);
463         
464         auto blogger = new shared BufferedLogger(logger);
465         auto res = queryValue(blogger, pool, "pgator_test_void()").deserializeBson!string;
466         assert(res == "");
467         
468         pool.execTransaction(["drop function if exists pgator_test_void();"]);
469     }
470 }