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 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 = Bson(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 long usecs = ts.fracSec.total!"usecs"; 148 res = Bson(["time": Bson(time), "usecs": Bson(usecs)]); 149 break; 150 151 case Json: 152 case Jsonb: 153 vibe.data.json.Json json = binaryValueAs!PGjson(v); 154 res = Bson(json); 155 break; 156 157 default: 158 throw new AnswerConvException( 159 ConvExceptionType.NOT_IMPLEMENTED, 160 "Format of the column ("~to!(immutable(char)[])(v.oidType)~") doesn't supported by Value to Bson converter", 161 __FILE__, __LINE__ 162 ); 163 } 164 165 return res; 166 } 167 168 public void _integration_test( string connParam ) 169 { 170 import std.uuid; 171 import std.datetime: SysTime, DateTime, UTC; 172 173 auto conn = new Connection(connParam); 174 175 // text answer tests 176 { 177 auto a = conn.exec( 178 "SELECT 123::int8 as int_num_value,"~ 179 "'text string'::text as text_value,"~ 180 "'123.456'::json as json_numeric_value,"~ 181 "'\"json_value_string\"'::json as json_text_value" 182 ); 183 184 auto r = a[0]; // first row 185 186 assert(r["int_num_value"].as!Bson == Bson("123")); 187 assert(r["text_value"].as!Bson == Bson("text string")); 188 assert(r["json_numeric_value"].as!Bson == Bson(123.456)); 189 assert(r["json_text_value"].as!Bson == Bson("json_value_string")); 190 } 191 192 // binary answer tests 193 QueryParams params; 194 params.resultFormat = ValueFormat.BINARY; 195 196 { 197 void testIt(Bson bsonValue, string pgType, string pgValue) 198 { 199 params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as bson_test_value"; 200 auto answer = conn.execParams(params); 201 202 immutable Value v = answer[0][0]; 203 Bson bsonRes = v.as!Bson(UTC()); 204 205 if(v.isNull || !v.isSupportedArray) // standalone 206 { 207 if(pgType == "numeric") pgType = "string"; // bypass for numeric values represented as strings 208 209 assert(bsonRes == bsonValue, "Received unexpected value\nreceived bsonType="~to!string(bsonValue.type)~"\nexpected nativeType="~pgType~ 210 "\nsent pgValue="~pgValue~"\nexpected bsonValue="~to!string(bsonValue)~"\nresult="~to!string(bsonRes)); 211 } 212 else // arrays 213 { 214 assert(bsonRes.type == Bson.Type.array && bsonRes.toString == bsonValue.toString, 215 "pgType="~pgType~" pgValue="~pgValue~" bsonValue="~to!string(bsonValue)); 216 } 217 } 218 219 alias C = testIt; // "C" means "case" 220 221 C(Bson(null), "text", "null"); 222 C(Bson(null), "integer", "null"); 223 C(Bson(true), "boolean", "true"); 224 C(Bson(false), "boolean", "false"); 225 C(Bson(-32_761), "smallint", "-32761"); 226 C(Bson(-2_147_483_646), "integer", "-2147483646"); 227 C(Bson(-9_223_372_036_854_775_806), "bigint", "-9223372036854775806"); 228 C(Bson(-1234.56789012345), "double precision", "-1234.56789012345"); 229 C(Bson("first line\nsecond line"), "text", "'first line\nsecond line'"); 230 C(Bson("12345 "), "char(6)", "'12345'"); 231 C(Bson("-487778762.918209326"), "numeric", "-487778762.918209326"); 232 233 C(Bson(BsonBinData( 234 BsonBinData.Type.userDefined, 235 [0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21] 236 )), 237 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII) 238 239 C(Bson(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")), 240 "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 241 242 C(Bson([ 243 Bson([Bson([Bson("1")]),Bson([Bson("22")]),Bson([Bson("333")])]), 244 Bson([Bson([Bson("4")]),Bson([Bson(null)]),Bson([Bson("6")])]) 245 ]), "text[]", "'{{{1},{22},{333}},{{4},{null},{6}}}'"); 246 247 C(Bson.emptyArray, "text[]", "'{}'"); 248 249 C(Bson(["time": Bson(BsonDate(SysTime(DateTime(1997, 12, 17, 7, 37, 16), UTC()))), "usecs": Bson(cast(long) 12)]), "timestamp without time zone", "'1997-12-17 07:37:16.000012'"); 250 251 C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'"); 252 253 C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "jsonb", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'"); 254 } 255 }