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