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