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