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