1 ///
2 module dpq2.conv.to_d_types;
3 
4 @safe:
5 
6 import dpq2.value;
7 import dpq2.oids: OidType, isNativeInteger, isNativeFloat;
8 import dpq2.connection: Connection;
9 import dpq2.query: QueryParams;
10 import dpq2.result: msg_NOT_BINARY;
11 import dpq2.conv.from_d_types;
12 import dpq2.conv.numeric: rawValueToNumeric;
13 import dpq2.conv.time: binaryValueAs, TimeStamp, TimeStampUTC;
14 import dpq2.conv.geometric: binaryValueAs, Line;
15 
16 import vibe.data.json: Json, parseJsonString;
17 import vibe.data.bson: Bson;
18 import std.traits;
19 import std.uuid;
20 import std.datetime;
21 import std.traits: isScalarType;
22 import std.typecons : Nullable;
23 import std.bitmanip: bigEndianToNative;
24 import std.conv: to;
25 version (unittest) import std.exception : assertThrown;
26 
27 // Supported PostgreSQL binary types
28 alias PGboolean =       bool; /// boolean
29 alias PGsmallint =      short; /// smallint
30 alias PGinteger =       int; /// integer
31 alias PGbigint =        long; /// bigint
32 alias PGreal =          float; /// real
33 alias PGdouble_precision = double; /// double precision
34 alias PGtext =          string; /// text
35 alias PGnumeric =       string; /// numeric represented as string
36 alias PGbytea =         immutable(ubyte)[]; /// bytea
37 alias PGuuid =          UUID; /// UUID
38 alias PGdate =          Date; /// Date (no time of day)
39 alias PGtime_without_time_zone = TimeOfDay; /// Time of day (no date)
40 alias PGtimestamp = TimeStamp; /// Both date and time without time zone
41 alias PGtimestamptz = TimeStampUTC; /// Both date and time stored in UTC time zone
42 alias PGjson =          Json; /// json or jsonb
43 alias PGline =          Line; /// Line (geometric type)
44 
45 private alias VF = ValueFormat;
46 private alias AE = ValueConvException;
47 private alias ET = ConvExceptionType;
48 
49 /**
50     Returns cell value as a native string based type from text or binary formatted field.
51     Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value
52 */
53 T as(T)(in Value v) pure @trusted
54 if(is(T : string))
55 {
56     if(v.format == VF.BINARY)
57     {
58         if(!(
59             v.oidType == OidType.Text ||
60             v.oidType == OidType.FixedString ||
61             v.oidType == OidType.VariableString ||
62             v.oidType == OidType.Numeric ||
63             v.oidType == OidType.Json ||
64             v.oidType == OidType.Jsonb ||
65             v.oidType == OidType.Name
66         ))
67             throwTypeComplaint(v.oidType, "Text, FixedString, VariableString, Name, Numeric, Json or Jsonb", __FILE__, __LINE__);
68     }
69 
70     static if(is(T == Nullable!R, R))
71     {
72         alias Ret = R;
73 
74         if (v.isNull)
75             return T.init;
76     }
77     else
78         alias Ret = T;
79 
80     Ret r;
81 
82     if(v.format == VF.BINARY && v.oidType == OidType.Numeric)
83         r = rawValueToNumeric(v.data); // special case for 'numeric' which represented in dpq2 as string
84     else
85         r = v.valueAsString;
86 
87     static if(is(T == Nullable!R2, R2))
88         return T(r);
89     else
90         return r;
91 }
92 
93 @system unittest
94 {
95     import core.exception: AssertError;
96 
97     auto v = Value(ValueFormat.BINARY, OidType.Text);
98 
99     assert(v.isNull);
100     assertThrown!AssertError(v.as!string == "");
101     assert(v.as!(Nullable!string).isNull == true);
102 }
103 
104 /**
105     Returns value as D type value from binary formatted field.
106     Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value
107 */
108 T as(T)(in Value v)
109 if(!is(T : string) && !is(T == Bson))
110 {
111     if(!(v.format == VF.BINARY))
112         throw new AE(ET.NOT_BINARY,
113             msg_NOT_BINARY, __FILE__, __LINE__);
114 
115     static if (is(T == Nullable!R, R))
116     {
117         if (v.isNull)
118             return T.init;
119         else
120             return T(binaryValueAs!R(v));
121     }
122     else
123         return binaryValueAs!T(v);
124 }
125 
126 @system unittest
127 {
128     auto v = Value([1], OidType.Int4, false, ValueFormat.TEXT);
129     assertThrown!AE(v.as!int);
130 }
131 
132 package:
133 
134 /*
135  * Something was broken in DMD64 D Compiler v2.079.0-rc.1 so I made this "tunnel"
136  * TODO: remove it and replace by direct binaryValueAs calls
137  */
138 auto tunnelForBinaryValueAsCalls(T)(in Value v)
139 {
140     return binaryValueAs!T(v);
141 }
142 
143 string valueAsString(in Value v) pure
144 {
145     return (cast(const(char[])) v.data).to!string;
146 }
147 
148 /// Returns value as bytes from binary formatted field
149 T binaryValueAs(T)(in Value v)
150 if(is(T : const ubyte[]))
151 {
152     if(!(v.oidType == OidType.ByteArray))
153         throwTypeComplaint(v.oidType, "immutable ubyte[]", __FILE__, __LINE__);
154 
155     return v.data;
156 }
157 
158 @system unittest
159 {
160     auto v = Value([1], OidType.Bool);
161     assertThrown!ValueConvException(v.binaryValueAs!(const ubyte[]));
162 }
163 
164 /// Returns cell value as native integer or decimal values
165 ///
166 /// Postgres type "numeric" is oversized and not supported by now
167 T binaryValueAs(T)(in Value v)
168 if( isNumeric!(T) )
169 {
170     static if(isIntegral!(T))
171         if(!isNativeInteger(v.oidType))
172             throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__);
173 
174     static if(isFloatingPoint!(T))
175         if(!isNativeFloat(v.oidType))
176             throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__);
177 
178     if(!(v.data.length == T.sizeof))
179         throw new AE(ET.SIZE_MISMATCH,
180             to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~
181                 to!string(typeid(T))~" size ("~to!string(T.sizeof)~")",
182             __FILE__, __LINE__);
183 
184     ubyte[T.sizeof] s = v.data[0..T.sizeof];
185     return bigEndianToNative!(T)(s);
186 }
187 
188 @system unittest
189 {
190     auto v = Value([1], OidType.Bool);
191     assertThrown!ValueConvException(v.binaryValueAs!int);
192     assertThrown!ValueConvException(v.binaryValueAs!float);
193 
194     v = Value([1], OidType.Int4);
195     assertThrown!ValueConvException(v.binaryValueAs!int);
196 }
197 
198 /// Returns UUID as native UUID value
199 UUID binaryValueAs(T)(in Value v)
200 if( is( T == UUID ) )
201 {
202     if(!(v.oidType == OidType.UUID))
203         throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__);
204 
205     if(!(v.data.length == 16))
206         throw new AE(ET.SIZE_MISMATCH,
207             "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__);
208 
209     UUID r;
210     r.data = v.data;
211     return r;
212 }
213 
214 @system unittest
215 {
216     auto v = Value([1], OidType.Int4);
217     assertThrown!ValueConvException(v.binaryValueAs!UUID);
218 
219     v = Value([1], OidType.UUID);
220     assertThrown!ValueConvException(v.binaryValueAs!UUID);
221 }
222 
223 /// Returns boolean as native bool value
224 bool binaryValueAs(T : bool)(in Value v)
225 if (!is(T == Nullable!R, R))
226 {
227     if(!(v.oidType == OidType.Bool))
228         throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__);
229 
230     if(!(v.data.length == 1))
231         throw new AE(ET.SIZE_MISMATCH,
232             "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__);
233 
234     return v.data[0] != 0;
235 }
236 
237 @system unittest
238 {
239     auto v = Value([1], OidType.Int4);
240     assertThrown!ValueConvException(v.binaryValueAs!bool);
241 
242     v = Value([1,2], OidType.Bool);
243     assertThrown!ValueConvException(v.binaryValueAs!bool);
244 }
245 
246 /// Returns Vibe.d's Json
247 Json binaryValueAs(T)(in Value v) @trusted
248 if( is( T == Json ) )
249 {
250     import dpq2.conv.jsonb: jsonbValueToJson;
251 
252     Json res;
253 
254     switch(v.oidType)
255     {
256         case OidType.Json:
257             // represent value as text and parse it into Json
258             string t = v.valueAsString;
259             res = parseJsonString(t);
260             break;
261 
262         case OidType.Jsonb:
263             res = v.jsonbValueToJson;
264             break;
265 
266         default:
267             throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__);
268     }
269 
270     return res;
271 }
272 
273 @system unittest
274 {
275     auto v = Value([1], OidType.Int4);
276     assertThrown!ValueConvException(v.binaryValueAs!Json);
277 }