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