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