1 module dpq2.types.from_bson;
2 
3 @trusted:
4 
5 import dpq2;
6 import vibe.data.bson;
7 import std.bitmanip: nativeToBigEndian;
8 
9 /// Default type will be used for NULL value and for array without detected type
10 @property Value bsonToValue(Bson v)
11 {
12     if(v.type == Bson.Type.array)
13         return bsonArrayToValue(v);
14     else
15         return bsonValueToValue(v);
16 }
17 
18 private:
19 
20 Value bsonValueToValue(Bson v)
21 {
22     Value ret;
23 
24     with(Bson.Type)
25     switch(v.type)
26     {
27         case null_:
28             ret = Value(ValueFormat.BINARY, OidType.Unknown);
29             break;
30 
31         case int_:
32             ret = v.get!int.toValue;
33             break;
34 
35         case long_:
36             ret = v.get!long.toValue;
37             break;
38 
39         case double_:
40             ret = v.get!double.toValue;
41             break;
42 
43         case Bson.Type..string:
44             ret = v.get!(immutable(char)[]).toValue;
45             break;
46 
47         default:
48             throw new AnswerConvException(
49                     ConvExceptionType.NOT_IMPLEMENTED,
50                     "Format "~v.type.to!(immutable(char)[])~" doesn't supported by Bson to Value converter",
51                     __FILE__, __LINE__
52                 );
53     }
54 
55     return ret;
56 }
57 
58 unittest
59 {
60     {
61         Value v1 = bsonToValue(Bson(123));
62         Value v2 = (123).toValue;
63 
64         assert(v1.as!int == v2.as!int);
65     }
66 
67     {
68         Value v1 = bsonToValue(Bson("Test string"));
69         Value v2 = ("Test string").toValue;
70 
71         assert(v1.as!string == v2.as!string);
72     }
73 }
74 
75 Value bsonArrayToValue(ref Bson bsonArr)
76 {
77     ubyte[] nullValue() pure
78     {
79         ubyte[] ret = [0xff, 0xff, 0xff, 0xff]; //NULL magic number
80         return ret;
81     }
82 
83     ubyte[] rawValue(Value v) pure
84     {
85         if(v.isNull)
86         {
87             return nullValue();
88         }
89         else
90         {
91             return v._data.length.to!uint.nativeToBigEndian ~ v._data;
92         }
93     }
94 
95     ArrayProperties ap;
96     ubyte[] rawValues;
97 
98     void recursive(ref Bson bsonArr, int dimension)
99     {
100         if(dimension == ap.dimsSize.length)
101         {
102             ap.dimsSize ~= bsonArr.length.to!int;
103         }
104         else
105         {
106             if(ap.dimsSize[dimension] != bsonArr.length)
107                 throw new AnswerConvException(ConvExceptionType.NOT_ARRAY, "Jagged arrays are unsupported", __FILE__, __LINE__);
108         }
109 
110         foreach(bElem; bsonArr)
111         {
112             ap.nElems++;
113 
114             switch(bElem.type)
115             {
116                 case Bson.Type.array:
117                     recursive(bElem, dimension + 1);
118                     break;
119 
120                 case Bson.Type.null_:
121                     rawValues ~= nullValue();
122                     break;
123 
124                 default:
125                     Value v = bsonValueToValue(bElem);
126 
127                     if(ap.OID == OidType.Unknown)
128                     {
129                         ap.OID = v.oidType;
130                     }
131                     else
132                     {
133                         if(ap.OID != v.oidType)
134                             throw new AnswerConvException(
135                                     ConvExceptionType.NOT_ARRAY,
136                                     "Bson (which used for creating "~ap.OID.to!string~" array) also contains value of type "~v.oidType.to!string,
137                                     __FILE__, __LINE__
138                                 );                    
139                     }
140 
141                     rawValues ~= rawValue(v);
142             }
143         }
144     }
145 
146     recursive(bsonArr, 0);
147 
148     // If array empty or contains only NULL values this allows to read it using ::text cast
149     if(ap.OID == OidType.Unknown) ap.OID = OidType.Text;
150 
151     ArrayHeader_net h;
152     h.ndims = nativeToBigEndian(ap.dimsSize.length.to!int);
153     h.OID = nativeToBigEndian(ap.OID.to!Oid);
154 
155     ubyte[] ret;
156     ret ~= (cast(ubyte*) &h)[0 .. h.sizeof];
157 
158     foreach(i; 0 .. ap.dimsSize.length)
159     {
160         Dim_net dim;
161         dim.dim_size = nativeToBigEndian(ap.dimsSize[i]);
162         dim.lbound = nativeToBigEndian!int(1);
163 
164         ret ~= (cast(ubyte*) &dim)[0 .. dim.sizeof];
165     }
166 
167     ret ~= rawValues;
168 
169     return Value(ret, ap.OID.oidType2arrayType, false, ValueFormat.BINARY);
170 }
171 
172 unittest
173 {
174     {
175         Bson bsonArray = Bson(
176             [Bson(123), Bson(155), Bson(null), Bson(0), Bson(null)]
177         );
178 
179         Value v = bsonToValue(bsonArray);
180 
181         assert(v.isSupportedArray);
182         assert(v.toBson == bsonArray);
183     }
184 
185     {
186         Bson bsonArray = Bson([
187             Bson([Bson(123), Bson(155), Bson(null)]),
188             Bson([Bson(0), Bson(null), Bson(155)])
189         ]);
190 
191         Value v = bsonToValue(bsonArray);
192 
193         assert(v.isSupportedArray);
194         assert(v.toBson == bsonArray);
195     }
196 
197     {
198         Bson bsonArray = Bson([
199             Bson([Bson(123), Bson(155)]),
200             Bson([Bson(0)])
201         ]);
202 
203         bool exceptionFlag = false;
204 
205         try
206             bsonToValue(bsonArray);
207         catch(AnswerConvException e)
208         {
209             if(e.type == ConvExceptionType.NOT_ARRAY)
210                 exceptionFlag = true;
211         }
212 
213         assert(exceptionFlag);
214     }
215 }
216 
217 OidType oidType2arrayType(OidType type)
218 {
219     with(OidType)
220     switch(type)
221     {
222         case Text:
223             return TextArray;
224 
225         case Int2:
226             return Int2Array;
227 
228         case Int4:
229             return Int4Array;
230 
231         case Int8:
232             return Int8Array;
233 
234         case Float4:
235             return Float4Array;
236 
237         case Float8:
238             return Float8Array;
239 
240         default:
241             throw new AnswerConvException( // TODO: rename it to ValueConvException and move to value.d
242                     ConvExceptionType.NOT_IMPLEMENTED,
243                     "Format "~type.to!(immutable(char)[])~" doesn't supported by Bson array to Value converter",
244                     __FILE__, __LINE__
245                 );
246     }
247 }