1 // Written in D programming language
2 /**
3 *   PostgreSQL typed arrays binary data 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.array;
10 
11 import pgator.db.pq.types.oids;
12 import pgator.db.connection;
13 import derelict.pq.pq;
14 import std.array;
15 import std.bitmanip;
16 import std.conv;
17 import std.traits;
18 
19 import std.datetime;
20 import pgator.db.pq.types.all;
21 
22 private struct Vector(T)
23 {
24     int     ndim;
25     int     dataoffset;
26     Oid     elemtype;
27     int     dim1;
28     int     lbound1;
29     T[]     values;
30 }
31 
32 private Vector!T readVec(T, PQType type)(ubyte[] arr, shared IConnection conn)
33 {
34     if(arr.length == 0) return Vector!T();
35     
36     assert(arr.length >= 2*int.sizeof + Oid.sizeof, text(
37             "Expected min array size ", 2*int.sizeof + Oid.sizeof, ", but got ", arr.length));
38     Vector!T vec;
39     
40     vec.ndim    = arr.read!int;
41     vec.dataoffset = arr.read!int;
42     vec.elemtype   = arr.read!Oid;
43     if(arr.length == 0) return vec;
44     vec.dim1       = arr.read!int;
45     vec.lbound1    = arr.read!int;
46     
47     auto builder = appender!(T[]);
48     while(arr.length > 0)
49     {
50         auto maybeLength  = arr.read!uint;
51         // if got [255, 255, 255, 255] - there is special case for NULL in array
52         if(maybeLength == uint.max)
53         {
54             static if(is(T == class) || is(T == interface))
55             {
56                builder.put(null);
57             } 
58             else
59             {
60                 builder.put(T.init);
61             }
62             continue;
63         }
64         
65         // can cast to array size
66         auto length = cast(size_t)maybeLength; 
67         
68         static if(__traits(compiles, pgator.db.pq.types.all.convert!type(arr)))
69         {
70             auto value = pgator.db.pq.types.all.convert!type(arr[0..length]); 
71             builder.put(value);
72             arr = arr[length..$];
73         } else static if(__traits(compiles, pgator.db.pq.types.all.convert!type(arr, conn)))
74         { 
75             auto value = pgator.db.pq.types.all.convert!type(arr[0..length], conn); 
76             builder.put(value); 
77             arr = arr[length..$];
78         } else
79         {
80             static assert(false, "There is no convert function for libpq type: "~type.to!text);
81         }
82     }
83     vec.values = builder.data;
84     return vec;    
85 }
86 
87 mixin ArraySupport!(
88     PQType.Int2Vector,              short[],                PQType.Int2,
89     PQType.Int2Array,               short[],                PQType.Int2,
90     PQType.Int4Array,               int[],                  PQType.Int4,
91     PQType.OidVector,               Oid[],                  PQType.Oid,
92     PQType.OidArray,                Oid[],                  PQType.Oid,
93     PQType.TextArray,               string[],               PQType.Text,
94     PQType.CStringArray,            string[],               PQType.Text,
95     PQType.Float4Array,             float[],                PQType.Float4,
96     PQType.BoolArray,               bool[],                 PQType.Bool,
97     PQType.ByteArrayArray,          ubyte[][],              PQType.ByteArray,
98     PQType.CharArray,               char[],                 PQType.Char,
99     PQType.NameArray,               string[],               PQType.Name,
100     PQType.Int2VectorArray,         short[][],              PQType.Int2VectorArray,
101     PQType.XidArray,                Xid[],                  PQType.Xid,
102     PQType.CidArray,                Cid[],                  PQType.Cid,
103     PQType.OidVectorArray,          Oid[][],                PQType.OidVector,
104     PQType.FixedStringArray,        string[],               PQType.FixedString,
105     PQType.VariableStringArray,     string[],               PQType.VariableString,
106     PQType.Int8Array,               long[],                 PQType.Int8,
107     PQType.PointArray,              Point[],                PQType.Point,
108     PQType.LineSegmentArray,        LineSegment[],          PQType.LineSegment,
109     PQType.PathArray,               Path[],                 PQType.Path,
110     PQType.BoxArray,                Box[],                  PQType.Box,
111     PQType.Float8Array,             double[],               PQType.Float8,
112     PQType.AbsTimeArray,            PGAbsTime[],            PQType.AbsTime,
113     PQType.RelTimeArray,            PGRelTime[],            PQType.RelTime,
114     PQType.IntervalArray,           PGInterval[],           PQType.Interval,
115     PQType.PolygonArray,            Polygon[],              PQType.Polygon,
116     PQType.MacAddressArray,         PQMacAddress[],         PQType.MacAddress,
117     PQType.HostAdressArray,         PQInetAddress[],        PQType.HostAddress,
118     PQType.NetworkAdressArray,      PQInetAddress[],        PQType.NetworkAddress,
119     PQType.TimeStampArray,          PGTimeStamp[],          PQType.TimeStamp,
120     PQType.DateArray,               Date[],                 PQType.Date,
121     PQType.TimeArray,               PGTime[],               PQType.Time,
122     PQType.TimeStampWithZoneArray,  PGTimeStampWithZone[],  PQType.TimeStampWithZone,
123     PQType.TimeIntervalArray,       TimeInterval[],         PQType.TimeInterval,
124     PQType.NumericArray,            PGNumeric[],            PQType.Numeric,
125     PQType.TimeWithZoneArray,       PGTimeWithZone[],       PQType.TimeWithZone,
126     );
127 
128 /**
129 *   Template magic lives here! Generates templates and functions to work with
130 *   templates. Expects input argument as triples of PQType value, corresponding
131 *   D type and corresponding element type of libpq type.
132 */    
133 private mixin template ArraySupport(PairsRaw...)
134 {
135     import std.range;
136     import std.traits;
137     
138     /// To work with D tuples
139     private template Tuple(E...)
140     {
141         alias Tuple = E;
142     }
143     
144     /// Custom tuple for a pair
145     private template ArrayTuple(TS...)
146     {
147         static assert(TS.length == 3);
148         enum id = TS[0];
149         alias TS[1] type;
150         enum elementType = TS[2];
151     } 
152     
153     /// Converts unstructured pairs in ArrayTuple tuple
154     private template ConvertPairs(TS...)
155     {
156         static assert(TS.length % 3 == 0, "ArraySupport expected even count of arguments!");
157         static if(TS.length == 3)
158         {
159             alias Tuple!(ArrayTuple!(TS[0], TS[1], TS[2])) ConvertPairs;
160         } else
161         {
162             static assert(is(typeof(TS[0]) == PQType), "ArraySupport expected PQType value as first triple argument!");
163             static assert(is(TS[1]), "ArraySupport expected a type as second triple argument!"); 
164             static assert(is(typeof(TS[0]) == PQType), "ArraySupport expected PQType value as third triple argument!");
165             
166             alias Tuple!(ArrayTuple!(TS[0], TS[1], TS[2]), ConvertPairs!(TS[3..$])) ConvertPairs;
167         }
168     }
169     
170     /// Structured pairs
171     alias ConvertPairs!PairsRaw Pairs;
172     
173     /// Checks if PQType value is actually array type
174     template IsArrayType(TS...)
175     {
176         private template genCompareExpr(US...)
177         {
178             static if(US.length == 0)
179             {
180                 enum genCompareExpr = "";
181             } else static if(US.length == 1)
182             {
183                 enum genCompareExpr = "T == PQType."~US[0].id.to!string;
184             } else
185             {
186                 enum genCompareExpr = "T == PQType."~US[0].id.to!string~" || "
187                     ~ genCompareExpr!(US[1..$]);
188             }
189         }
190         
191         static assert(TS.length > 0, "IsArrayType expected argument count > 0!");
192         alias TS[0] T;
193         enum IsArrayType = mixin(genCompareExpr!(Pairs));
194     }
195     
196     /// Returns oid of provided element type or generates error if it is not a libpq array
197     template ArrayElementType(TS...)
198     {
199         static assert(TS.length > 0, "ArrayElementType expected argument!");
200         
201         private enum T = TS[0];
202         
203         static assert(is(typeof(TS[0]) == PQType), "ArrayElementType expected PQType value as argument!");
204         static assert(IsArrayType!(TS[0]), TS[0].to!string~" is not a libpq array!");
205         
206         template FindArrayTuple(TS...)
207         {
208             static if(TS.length == 0)
209             {
210                 static assert("Cannot find "~T.to!string~" in array types!");
211             } else
212             {
213                 static if(TS[0] == T)
214                 {
215                     enum FindArrayTuple = TS[3];
216                 } else
217                 {
218                     enum FindArrayTuple = FindArrayTuple!(TS[1..$]);
219                 }
220             }
221         }
222         
223         enum ArrayElementType = FindArrayTuple!TS;
224     }
225     
226     /// Generates set of converting functions from ubyte[] to types in triples
227     private template genConvertFunctions(TS...)
228     {
229         private template genConvertFunction(TS...)
230         {
231             alias TS[0] T;
232             
233             enum genConvertFunction = T.type.stringof ~ " convert(PQType type)(ubyte[] val, shared IConnection conn)\n"
234                 "\t if(type == PQType."~T.id.to!string~")\n{\n"
235                 "\t return val.readVec!("~ForeachType!(T.type).stringof~", PQType."~T.elementType.to!string~")(conn).values;\n}";
236         }
237            
238         static if(TS.length == 0)
239         {
240             enum genConvertFunctions = "";
241         } else
242         {
243             enum genConvertFunctions = genConvertFunction!(TS[0]) ~"\n"~genConvertFunctions!(TS[1..$]);
244         }
245     }
246 
247     mixin(genConvertFunctions!Pairs);
248 }   
249 
250 version(IntegrationTest2)
251 {
252     import pgator.db.pq.types.test;
253     import pgator.db.pq.types.plain;
254     import pgator.db.pool;
255     import std.array;
256     version(LDC){} else import std.algorithm.iteration;
257     import std.random;
258     import std.math;
259     import std.traits;
260     import dlogg.log;
261 
262     string convertArray(T, bool wrapQuotes = true)(T[] ts)
263     {
264         auto builder = appender!string;
265         foreach(i,t; ts)
266         {
267             enum needQuotes = (isSomeString!T || isSomeChar!T || is(T == Xid) || is(T == Cid)) && wrapQuotes;
268             static if(needQuotes) builder.put("'");
269             static if(isFloatingPoint!T)
270             {
271                if(t == T.infinity) builder.put("'Infinity'");
272                else if(t == -T.infinity) builder.put("'-Infinity'");
273                else if(isNaN(t)) builder.put("'NaN'");
274                else builder.put(t.to!string);
275             } else
276             {
277                 builder.put(t.to!string);
278             }
279             static if(needQuotes) builder.put("'");
280             if(i != ts.length-1)
281                 builder.put(", ");
282         }
283         return "ARRAY["~builder.data~"]";
284     } 
285     
286     struct Name {};
287     
288     U[] randArray(T, U=T)(size_t n)
289     {
290         auto builder = appender!(U[]);
291         foreach(i; 0..n)
292         {
293             static if(isSomeChar!T)
294             {
295                 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|";
296                 builder.put(alph[uniform(0,alph.length)]);
297             }
298             else static if(is(T == string))
299             {
300                 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|";
301                 auto zbuilder = appender!string;
302                 foreach(j; 0..n)
303                     zbuilder.put(alph[uniform(0,alph.length)]);
304                 builder.put(zbuilder.data);
305             }  
306             else static if(is(T == Name))
307             {
308                 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|";
309                 auto zbuilder = appender!string;
310                 foreach(j; 0..63)
311                     zbuilder.put(alph[uniform(0,alph.length)]);
312                 builder.put(zbuilder.data);
313             }
314             else static if(is(T == float))
315             {
316                 builder.put(uniform(-1000.0, 1000.0));
317             } 
318             else static if(is(T == bool))
319             {
320                 builder.put(uniform!"[]"(0,1) != 0);
321             }
322             else static if(isArray!T)
323             {
324                 builder.put(randArray!(ElementType!T)(n));
325             }
326             else static if(is(T == Cid))
327             {
328                 builder.put(uniform(Cid.min/4, Cid.max/4));
329             }
330             else static if(isFloatingPoint!T)
331             {
332                 builder.put(uniform(-T.max, T.max));
333             }
334             else
335             {
336                 builder.put(uniform(T.min, T.max));
337             }
338         }
339         return builder.data;    
340     }
341     
342     void testArray(T, alias typeTrans = (n, name) => name)(shared ILogger logger, shared IConnectionPool pool, string tname, size_t bn = 0, size_t en = 100)
343     {
344         logger.logInfo("Testing "~tname~"...");
345         foreach(i; bn..en)
346         {
347             static if(is(T == ubyte[]))
348             {
349                 testValue!(T[], (a) => a.map!(a => escapeBytea(a)).array.convertArray!(string, false), id)(logger, pool, randArray!T(i), typeTrans(i, tname));
350             } else static if(isSomeChar!T)
351             {
352                 testValue!(string[], convertArray)(logger, pool, randArray!T(i).map!(a => [cast(char)a].idup).array, typeTrans(i, tname));
353             } else static if(is(T == Name))
354             {
355                 testValue!(string[], convertArray)(logger, pool, randArray!(T, string)(i), typeTrans(i, tname));
356             } else static if(isArray!T && !isSomeString!T)
357             {
358                 testValue!(T[], (a) 
359                     {
360                         auto builder = appender!(string[]);
361                         foreach(b; a)
362                         {
363                             builder.put(b.convertArray);
364                         }
365                         return builder.data.convertArray!(string, false);
366                     }
367                     )(logger, pool, randArray!T(i), typeTrans(i, tname));
368             }
369             else
370             {
371                 testValue!(T[], convertArray)(logger, pool, randArray!T(i), typeTrans(i, tname));
372             }
373         }
374     }
375         
376     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
377         if(type == PQType.Int2Vector)
378     { 
379         testArray!short(logger, pool, "int2vector");
380     }
381     
382     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
383         if(type == PQType.OidVector)
384     {       
385         testArray!Oid(logger, pool, "oidvector");
386     }
387     
388     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
389         if(type == PQType.OidArray)
390     {       
391         testArray!Oid(logger, pool, "oid[]");
392     }
393     
394     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
395         if(type == PQType.Int2Array)
396     {
397         testArray!short(logger, pool, "int2[]");
398     }
399     
400     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
401         if(type == PQType.Int4Array)
402     {
403         testArray!int(logger, pool, "int4[]");
404     }
405     
406     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
407         if(type == PQType.TextArray)
408     {
409         testArray!string(logger, pool, "text[]");
410     }
411     
412     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
413         if(type == PQType.CStringArray)
414     {
415         testArray!string(logger, pool, "cstring[]");
416     }
417     
418     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
419         if(type == PQType.BoolArray)
420     {
421         testArray!bool(logger, pool, "bool[]");
422     }
423     
424     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
425         if(type == PQType.ByteArrayArray)
426     {
427         testArray!(ubyte[])(logger, pool, "bytea[]");
428     }
429     
430     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
431         if(type == PQType.CharArray)
432     {
433         testArray!char(logger, pool, "char[]");
434     }
435     
436     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
437         if(type == PQType.NameArray)
438     {
439         testArray!Name(logger, pool, "name[]");
440     }
441     
442     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
443         if(type == PQType.Int2VectorArray)
444     {
445         logger.logInfo("Not testable");
446         //testArray!(short[])(logger, pool, "int2vector[]");
447     }
448     
449     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
450         if(type == PQType.XidArray)
451     {
452         testArray!Xid(logger, pool, "xid[]");
453     }
454     
455     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
456         if(type == PQType.CidArray)
457     {
458         testArray!Cid(logger, pool, "cid[]");
459     }
460     
461     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
462         if(type == PQType.OidVectorArray)
463     {
464         logger.logInfo("Not testable");
465         //testArray!(Oid[])(logger, pool, "oidvector[]");
466     }
467     
468     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
469         if(type == PQType.FixedStringArray)
470     {
471         testArray!(string, (n, name) => text("char(",n,")[]"))(logger, pool, "char(n)[]", 1, 100);
472     }
473     
474     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
475         if(type == PQType.VariableStringArray)
476     {
477         testArray!(string, (n, name) => text("varchar(",n,")[]"))(logger, pool, "varchar(n)[]", 1, 100);
478     }
479     
480     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
481         if(type == PQType.Int8Array)
482     {
483         testArray!long(logger, pool, "int8[]");
484     }
485         
486     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
487         if(type == PQType.PointArray)
488     {
489         //testArray!string(logger, pool, "point[]");
490     }
491 
492     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
493         if(type == PQType.LineSegmentArray)
494     {
495         //testArray!string(logger, pool, "lseg[]");
496     }
497 
498     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
499         if(type == PQType.PathArray)
500     {
501         //testArray!string(logger, pool, "path[]");
502     }
503 
504     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
505         if(type == PQType.BoxArray)
506     {
507         //testArray!string(logger, pool, "box[]");
508     }
509 
510     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
511         if(type == PQType.Float8Array)
512     {
513         testArray!double(logger, pool, "float8[]");
514     }
515 
516     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
517         if(type == PQType.AbsTimeArray)
518     {
519         //testArray!string(logger, pool, "abstime[]");
520     }
521 
522     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
523         if(type == PQType.RelTimeArray)
524     {
525         //testArray!string(logger, pool, "reltime[]");
526     }
527 
528     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
529         if(type == PQType.IntervalArray)
530     {
531         //testArray!string(logger, pool, "interval[]");
532     }
533 
534     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
535         if(type == PQType.PolygonArray)
536     {
537         //testArray!string(logger, pool, "polygon[]");
538     }
539 
540     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
541         if(type == PQType.MacAddressArray)
542     {
543         //testArray!string(logger, pool, "macaddress[]");
544     }
545 
546     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
547         if(type == PQType.HostAdressArray)
548     {
549         //testArray!string(logger, pool, "inet[]");
550     }
551 
552     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
553         if(type == PQType.NetworkAdressArray)
554     {
555         //testArray!string(logger, pool, "cidr[]");
556     }
557 
558     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
559         if(type == PQType.TimeStampArray)
560     {
561         //testArray!string(logger, pool, "timestamp[]");
562     }
563 
564     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
565         if(type == PQType.DateArray)
566     {
567         //testArray!string(logger, pool, "date[]");
568     }
569 
570     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
571         if(type == PQType.TimeArray)
572     {
573         //testArray!string(logger, pool, "time[]");
574     }
575 
576     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
577         if(type == PQType.TimeStampWithZoneArray)
578     {
579         //testArray!string(logger, pool, "timestamp with zone[]");
580     }
581 
582     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
583         if(type == PQType.TimeIntervalArray)
584     {
585         //testArray!string(logger, pool, "TimeInterval[]");
586     }
587 
588     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
589         if(type == PQType.NumericArray)
590     {
591         //testArray!string(logger, pool, "numeric[]");
592     }
593     
594     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
595         if(type == PQType.TimeWithZoneArray)
596     {
597         //testArray!string(logger, pool, "time with zone[]");
598     }
599 
600 
601     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
602         if(type == PQType.Float4Array)
603     {
604         testArray!float(logger, pool, "float4[]"); 
605         testValue!(float[], convertArray)(logger, pool, [float.infinity], "float4[]");
606         testValue!(float[], convertArray)(logger, pool, [-float.infinity], "float4[]");
607         testValue!(float[], convertArray)(logger, pool, [float.nan], "float4[]");
608     }
609 }