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