1 module dpq2.conv.to_bson;
2 
3 import dpq2;
4 import vibe.data.bson;
5 import std.uuid;
6 import std.datetime: SysTime, dur, TimeZone;
7 
8 @property Bson as(T)(in Value v, immutable TimeZone tz = null)
9 if(is(T == Bson))
10 {
11     if(v.isNull)
12     {
13         return Bson(null);
14     }
15     else
16     {
17         if(v.isSupportedArray && ValueFormat.BINARY)
18             return arrayValueToBson(v, tz);
19         else
20             return rawValueToBson(v, tz);
21     }
22 }
23 
24 private:
25 
26 Bson arrayValueToBson(in Value cell, immutable TimeZone tz)
27 {
28     const ap = ArrayProperties(cell);
29 
30     // empty array
31     if(ap.dimsSize.length == 0) return Bson.emptyArray;
32 
33     size_t curr_offset = ap.dataOffset;
34 
35     Bson recursive(size_t dimNum)
36     {
37         const dimSize = ap.dimsSize[dimNum];
38         Bson[] res = new Bson[dimSize];
39 
40         foreach(elemNum; 0..dimSize)
41         {
42             if(dimNum < ap.dimsSize.length - 1)
43             {
44                 res[elemNum] = recursive(dimNum + 1);
45             }
46             else
47             {
48                 ubyte[int.sizeof] size_net; // network byte order
49                 size_net[] = cell.data[ curr_offset .. curr_offset + size_net.sizeof ];
50                 uint size = bigEndianToNative!uint( size_net );
51 
52                 curr_offset += size_net.sizeof;
53 
54                 Bson b;
55                 if(size == size.max) // NULL magic number
56                 {
57                     b = Bson(null);
58                     size = 0;
59                 }
60                 else
61                 {
62                     auto v = Value(cast(ubyte[]) cell.data[curr_offset .. curr_offset + size], ap.OID, false);
63                     b = v.as!Bson(tz);
64                 }
65 
66                 curr_offset += size;
67                 res[elemNum] = b;
68             }
69         }
70 
71         return Bson(res);
72     }
73 
74     return recursive(0);
75 }
76 
77 Bson rawValueToBson(in Value v, immutable TimeZone tz = null)
78 {
79     if(v.format == ValueFormat.TEXT)
80     {
81         const text = v.valueAsString;
82 
83         if(v.oidType == OidType.Json)
84         {
85             return Bson(text.parseJsonString);
86         }
87 
88         return Bson(text);
89     }
90 
91     Bson res;
92 
93     with(OidType)
94     with(Bson.Type)
95     switch(v.oidType)
96     {
97         case OidType.Bool:
98             bool n = v.binaryValueAs!PGboolean;
99             res = Bson(n);
100             break;
101 
102         case Int2:
103             auto n = to!int(v.binaryValueAs!PGsmallint);
104             res = Bson(n);
105             break;
106 
107         case Int4:
108             int n = v.binaryValueAs!PGinteger;
109             res = Bson(n);
110             break;
111 
112         case Int8:
113             long n = v.binaryValueAs!PGbigint;
114             res = Bson(n);
115             break;
116 
117         case Float8:
118             double n = v.binaryValueAs!PGdouble_precision;
119             res = Bson(n);
120             break;
121 
122         case Numeric:
123             res = Bson(rawValueToNumeric(v.data));
124             break;
125 
126         case Text:
127             res = Bson(v.valueAsString);
128             break;
129 
130         case ByteArray:
131             auto b = BsonBinData(BsonBinData.Type.userDefined, v.data.idup);
132             res = Bson(b);
133             break;
134 
135         case UUID:
136             res = Uuid2Bson(v.binaryValueAs!PGuuid);
137             break;
138 
139         case TimeStamp:
140             auto ts = v.binaryValueAs!PGtimestamp_without_time_zone;
141             auto time = BsonDate(SysTime(ts.dateTime, tz));
142             auto usecs = ts.fracSec.usecs;
143             res = Bson(["time": Bson(time), "usecs": Bson(usecs)]);
144             break;
145 
146         case Json:
147             vibe.data.json.Json json = binaryValueAs!PGjson(v);
148             res = Bson(json);
149             break;
150 
151         default:
152             throw new AnswerConvException(
153                     ConvExceptionType.NOT_IMPLEMENTED,
154                     "Format of the column ("~to!(immutable(char)[])(v.oidType)~") doesn't supported by Value to Bson converter",
155                     __FILE__, __LINE__
156                 );
157     }
158 
159     return res;
160 }
161 
162 public void _integration_test( string connParam )
163 {
164     import std.uuid;
165     import std.datetime: SysTime, DateTime, UTC;
166 
167     auto conn = new Connection(connParam);
168 
169     // text answer tests
170     {
171         auto a = conn.exec(
172                 "SELECT 123::int8 as int_num_value,"~
173                        "'text string'::text as text_value,"~
174                        "'123.456'::json as json_numeric_value,"~
175                        "'\"json_value_string\"'::json as json_text_value"
176             );
177 
178         auto r = a[0]; // first row
179 
180         assert(r["int_num_value"].as!Bson == Bson("123"));
181         assert(r["text_value"].as!Bson == Bson("text string"));
182         assert(r["json_numeric_value"].as!Bson == Bson(123.456));
183         assert(r["json_text_value"].as!Bson == Bson("json_value_string"));
184     }
185 
186     // binary answer tests
187     QueryParams params;
188     params.resultFormat = ValueFormat.BINARY;
189 
190     {
191         void testIt(Bson bsonValue, string pgType, string pgValue)
192         {
193             params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as bson_test_value";
194             auto answer = conn.execParams(params);
195 
196             immutable Value v = answer[0][0];
197             Bson bsonRes = v.as!Bson(UTC());
198 
199             if(v.isNull || !v.isSupportedArray) // standalone
200             {
201                 if(pgType == "numeric") pgType = "string"; // bypass for numeric values represented as strings
202 
203                 assert(bsonRes == bsonValue, "Received unexpected value\nreceived bsonType="~to!string(bsonValue.type)~"\nexpected nativeType="~pgType~
204                     "\nsent pgValue="~pgValue~"\nexpected bsonValue="~to!string(bsonValue)~"\nresult="~to!string(bsonRes));
205             }
206             else // arrays
207             {
208                 assert(bsonRes.type == Bson.Type.array && bsonRes.toString == bsonValue.toString,
209                     "pgType="~pgType~" pgValue="~pgValue~" bsonValue="~to!string(bsonValue));
210             }
211         }
212 
213         alias C = testIt; // "C" means "case"
214 
215         C(Bson(null), "text", "null");
216         C(Bson(null), "integer", "null");
217         C(Bson(true), "boolean", "true");
218         C(Bson(false), "boolean", "false");
219         C(Bson(-32_761), "smallint", "-32761");
220         C(Bson(-2_147_483_646), "integer", "-2147483646");
221         C(Bson(-9_223_372_036_854_775_806), "bigint", "-9223372036854775806");
222         C(Bson(-1234.56789012345), "double precision", "-1234.56789012345");
223         C(Bson("first line\nsecond line"), "text", "'first line\nsecond line'");
224         C(Bson("-487778762.918209326"), "numeric", "-487778762.918209326");
225 
226         C(Bson(BsonBinData(
227                     BsonBinData.Type.userDefined,
228                     [0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21]
229                 )),
230                 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII)
231 
232         C(Uuid2Bson(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")),
233                 "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'");
234 
235         C(Bson([
236                 Bson([Bson([Bson("1")]),Bson([Bson("22")]),Bson([Bson("333")])]),
237                 Bson([Bson([Bson("4")]),Bson([Bson(null)]),Bson([Bson("6")])])
238             ]), "text[]", "'{{{1},{22},{333}},{{4},{null},{6}}}'");
239 
240         C(Bson.emptyArray, "text[]", "'{}'");
241 
242         C(Bson(["time": Bson(BsonDate(SysTime(DateTime(1997, 12, 17, 7, 37, 16), UTC()))), "usecs": Bson(12)]), "timestamp without time zone", "'1997-12-17 07:37:16.000012'");
243 
244         C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'");
245     }
246 }
247 
248 Bson Uuid2Bson(in UUID uuid)
249 {
250     return Bson(BsonBinData(BsonBinData.Type.uuid, uuid.data.idup));
251 }
252 
253 UUID Bson2Uuid(in Bson bson)
254 {
255     const ubyte[16] b = bson.get!BsonBinData().rawData;
256 
257     return UUID(b);
258 }
259 
260 unittest
261 {
262     auto srcUuid = UUID("00010203-0405-0607-0809-0a0b0c0d0e0f");
263 
264     auto b = Uuid2Bson(srcUuid);
265     auto u = Bson2Uuid(b);
266 
267     assert(b.type == Bson.Type.binData);
268     assert(b.get!BsonBinData().type == BsonBinData.Type.uuid);
269     assert(u == srcUuid);
270 }