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