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