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