1 module dpq2.types.to_bson;
2 
3 import dpq2.answer;
4 import dpq2.oids;
5 
6 import vibe.data.bson;
7 import std.uuid;
8 import std.datetime: SysTime, dur, TimeZone;
9 
10 @property
11 Bson toBson(immutable (Nullable!Value) v, immutable TimeZone tz = null)
12 {
13     if(v.isNull)
14         return Bson(null);
15     else
16         return toBson(v.get, tz);
17 }
18 
19 @property
20 Bson toBson(in Value v, immutable TimeZone tz = null)
21 {
22     if(v.isSupportedArray)
23         return arrayValueToBson(v, tz);
24     else
25         return rawValueToBson(v, tz);
26 }
27 
28 private Bson arrayValueToBson(in Value cell, immutable TimeZone tz) // FIXME: ref Value should be used
29 {
30     const ap = ArrayProperties(cell);
31 
32     size_t curr_offset = ap.dataOffset;
33 
34     Bson recursive(size_t dimNum)
35     {
36         const dimSize = ap.dimsSize[dimNum];
37         Bson[] res = new Bson[dimSize];
38 
39         foreach(elemNum; 0..dimSize)
40         {
41             if(dimNum < ap.nDims - 1)
42             {
43                 res[elemNum] = recursive(dimNum + 1);
44             }
45             else
46             {
47                 ubyte[int.sizeof] size_net; // network byte order
48                 size_net[] = cell.value[ curr_offset .. curr_offset + size_net.sizeof ];
49                 uint size = bigEndianToNative!uint( size_net );
50 
51                 curr_offset += size_net.sizeof;
52 
53                 Bson b;
54                 if(size == size.max) // NULL magic number
55                 {
56                     b = Bson(null);
57                     size = 0;
58                 }
59                 else
60                 {
61                     auto v = Value(cast(ubyte[]) cell.value[curr_offset .. curr_offset + size], ap.OID);
62                     b = v.toBson(tz);
63                 }
64 
65                 curr_offset += size;
66                 res[elemNum] = b;
67             }
68         }
69 
70         return Bson(res);
71     }
72 
73     return recursive(0);
74 }
75 
76 private Bson rawValueToBson(in Value v, immutable TimeZone tz = null)
77 {
78     if(v.format == ValueFormat.TEXT)
79     {
80         const text = v.valueAsString;
81 
82         if(v.oidType == OidType.Json)
83         {
84             return Bson(text.parseJsonString);
85         }
86 
87         return Bson(text);
88     }
89 
90     Bson res;
91 
92     with(OidType)
93     with(Bson.Type)
94     switch(v.oidType)
95     {
96         case OidType.Bool:
97             bool n = v.binaryValueAs!PGboolean;
98             res = Bson(n);
99             break;
100 
101         case Int2:
102             auto n = to!int(v.binaryValueAs!PGsmallint);
103             res = Bson(n);
104             break;
105 
106         case Int4:
107             int n = v.binaryValueAs!PGinteger;
108             res = Bson(n);
109             break;
110 
111         case Int8:
112             long n = v.binaryValueAs!PGbigint;
113             res = Bson(n);
114             break;
115 
116         case Float8:
117             double n = v.binaryValueAs!PGdouble_precision;
118             res = Bson(n);
119             break;
120 
121         case Numeric:
122             res = Bson(rawValueToNumeric(v.value));
123             break;
124 
125         case Text:
126             res = Bson(v.valueAsString);
127             break;
128 
129         case ByteArray:
130             auto b = BsonBinData(BsonBinData.Type.userDefined, v.value.idup);
131             res = Bson(b);
132             break;
133 
134         case UUID:
135             res = Uuid2Bson(v.binaryValueAs!PGuuid);
136             break;
137 
138         case TimeStamp:
139             auto ts = v.binaryValueAs!PGtimestamp_without_time_zone;
140             auto time = BsonDate(SysTime(ts.dateTime, tz));
141             auto usecs = ts.fracSec.usecs;
142             res = Bson(["time": Bson(time), "usecs": Bson(usecs)]);
143             break;
144 
145         case Json:
146             vibe.data.json.Json json = binaryValueAs!PGjson(v);
147             res = Bson(json);
148             break;
149 
150         default:
151             throw new AnswerException(
152                     ExceptionType.NOT_IMPLEMENTED,
153                     "Format of the column ("~to!(immutable(char)[])(v.oidType)~") doesn't supported by Bson converter",
154                     __FILE__, __LINE__
155                 );
156     }
157 
158     return res;
159 }
160 
161 void _integration_test( string connParam )
162 {
163     import std.uuid;
164     import std.datetime: SysTime, DateTime, UTC;
165 
166     auto conn = new Connection;
167 	conn.connString = connParam;
168     conn.connect();
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.exec(params);
196 
197             immutable (Nullable!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(["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'");
242 
243         C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'");
244     }
245 }
246 
247 private Bson Uuid2Bson(in UUID uuid)
248 {
249     return Bson(BsonBinData(BsonBinData.Type.uuid, uuid.data.idup));
250 }
251 
252 private UUID Bson2Uuid(in Bson bson)
253 {
254     const ubyte[16] b = bson.get!BsonBinData().rawData;
255 
256     return UUID(b);
257 }
258 
259 unittest
260 {
261     auto srcUuid = UUID("00010203-0405-0607-0809-0a0b0c0d0e0f");
262 
263     auto b = Uuid2Bson(srcUuid);
264     auto u = Bson2Uuid(b);
265 
266     assert(b.type == Bson.Type.binData);
267     assert(b.get!BsonBinData().type == BsonBinData.Type.uuid);
268     assert(u == srcUuid);
269 }