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