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