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