denizzzka / dpq2
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 1
    if(v.isNull)
20
    {
21 0
        return Bson(null);
22
    }
23
    else
24
    {
25 1
        if(v.isSupportedArray && ValueFormat.BINARY)
26 1
            return arrayValueToBson(v);
27
        else
28 1
            return rawValueToBson(v);
29
    }
30
}
31

32
private:
33

34
Bson arrayValueToBson(in Value cell)
35
{
36 1
    const ap = ArrayProperties(cell);
37

38
    // empty array
39 1
    if(ap.dimsSize.length == 0) return Bson.emptyArray;
40

41 1
    size_t curr_offset = ap.dataOffset;
42

43
    Bson recursive(size_t dimNum)
44
    {
45 1
        const dimSize = ap.dimsSize[dimNum];
46 1
        Bson[] res = new Bson[dimSize];
47

48 1
        foreach(elemNum; 0..dimSize)
49
        {
50 1
            if(dimNum < ap.dimsSize.length - 1)
51
            {
52 1
                res[elemNum] = recursive(dimNum + 1);
53
            }
54
            else
55
            {
56 1
                ubyte[int.sizeof] size_net; // network byte order
57 1
                size_net[] = cell.data[ curr_offset .. curr_offset + size_net.sizeof ];
58 1
                uint size = bigEndianToNative!uint( size_net );
59

60 1
                curr_offset += size_net.sizeof;
61

62 1
                Bson b;
63 1
                if(size == size.max) // NULL magic number
64
                {
65 1
                    b = Bson(null);
66 1
                    size = 0;
67
                }
68
                else
69
                {
70 1
                    auto v = Value(cast(ubyte[]) cell.data[curr_offset .. curr_offset + size], ap.OID, false);
71 1
                    b = v.as!Bson;
72
                }
73

74 1
                curr_offset += size;
75 1
                res[elemNum] = b;
76
            }
77
        }
78

79 1
        return Bson(res);
80
    }
81

82 1
    return recursive(0);
83
}
84

85
Bson rawValueToBson(in Value v)
86
{
87 1
    if(v.format == ValueFormat.TEXT)
88
    {
89 0
        immutable text = v.valueAsString;
90

91 0
        if(v.oidType == OidType.Json)
92
        {
93 0
            return Bson(text.parseJsonString);
94
        }
95

96 0
        return Bson(text);
97
    }
98

99 1
    Bson res;
100

101
    with(OidType)
102
    with(Bson.Type)
103 1
    switch(v.oidType)
104
    {
105 0
        case OidType.Bool:
106 0
            bool n = v.tunnelForBinaryValueAsCalls!PGboolean;
107 0
            res = Bson(n);
108 0
            break;
109

110 0
        case Int2:
111 0
            auto n = v.tunnelForBinaryValueAsCalls!PGsmallint.to!int;
112 0
            res = Bson(n);
113 0
            break;
114

115 1
        case Int4:
116 1
            int n = v.tunnelForBinaryValueAsCalls!PGinteger;
117 1
            res = Bson(n);
118 1
            break;
119

120 0
        case Int8:
121 0
            long n = v.tunnelForBinaryValueAsCalls!PGbigint;
122 0
            res = Bson(n);
123 0
            break;
124

125 0
        case Float8:
126 0
            double n = v.tunnelForBinaryValueAsCalls!PGdouble_precision;
127 0
            res = Bson(n);
128 0
            break;
129

130 0
        case Numeric:
131 0
            res = Bson(rawValueToNumeric(v.data));
132 0
            break;
133

134 0
        case Text:
135 0
        case FixedString:
136 0
        case VariableString:
137 0
            res = Bson(v.valueAsString);
138 0
            break;
139

140 0
        case ByteArray:
141 0
            auto b = BsonBinData(BsonBinData.Type.userDefined, v.data.idup);
142 0
            res = Bson(b);
143 0
            break;
144

145 0
        case UUID:
146
            // See: https://github.com/vibe-d/vibe.d/issues/2161
147
            // res = Bson(v.tunnelForBinaryValueAsCalls!PGuuid);
148 0
            res = serializeToBson(v.tunnelForBinaryValueAsCalls!PGuuid);
149 0
            break;
150

151 0
        case TimeStampWithZone:
152 0
            auto ts = v.tunnelForBinaryValueAsCalls!(dpq2.conv.time.TimeStampUTC);
153 0
            auto time = BsonDate(SysTime(ts.dateTime, UTC()));
154 0
            long usecs = ts.fracSec.total!"usecs";
155 0
            res = Bson(["time": Bson(time), "usecs": Bson(usecs)]);
156 0
            break;
157

158 0
        case Json:
159 0
        case Jsonb:
160 0
            vibe.data.json.Json json = v.tunnelForBinaryValueAsCalls!PGjson;
161 0
            res = Bson(json);
162 0
            break;
163

164 0
        default:
165 0
            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 1
    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 0
    auto conn = new Connection(connParam);
184

185
    // text answer tests
186
    {
187 0
        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 0
        auto r = a[0]; // first row
195

196 0
        assert(r["int_num_value"].as!Bson == Bson("123"));
197 0
        assert(r["text_value"].as!Bson == Bson("text string"));
198 0
        assert(r["json_numeric_value"].as!Bson == Bson(123.456));
199 0
        assert(r["json_text_value"].as!Bson == Bson("json_value_string"));
200
    }
201

202
    // binary answer tests
203 0
    QueryParams params;
204 0
    params.resultFormat = ValueFormat.BINARY;
205

206
    {
207
        void testIt(Bson bsonValue, string pgType, string pgValue)
208
        {
209 0
            params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as bson_test_value";
210 0
            auto answer = conn.execParams(params);
211

212 0
            immutable Value v = answer[0][0];
213 0
            Bson bsonRes = v.as!Bson;
214

215 0
            if(v.isNull || !v.isSupportedArray) // standalone
216
            {
217 0
                if(pgType == "numeric") pgType = "string"; // bypass for numeric values represented as strings
218

219 0
                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 0
                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 0
        C(Bson(null), "text", "null");
232 0
        C(Bson(null), "integer", "null");
233 0
        C(Bson(true), "boolean", "true");
234 0
        C(Bson(false), "boolean", "false");
235 0
        C(Bson(-32_761), "smallint", "-32761");
236 0
        C(Bson(-2_147_483_646), "integer", "-2147483646");
237 0
        C(Bson(-9_223_372_036_854_775_806), "bigint", "-9223372036854775806");
238 0
        C(Bson(-1234.56789012345), "double precision", "-1234.56789012345");
239 0
        C(Bson("first line\nsecond line"), "text", "'first line\nsecond line'");
240 0
        C(Bson("12345 "), "char(6)", "'12345'");
241 0
        C(Bson("-487778762.918209326"), "numeric", "-487778762.918209326");
242

243 0
        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 0
        C(serializeToBson(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")),
253
                "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'");
254

255 0
        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 0
        C(Bson.emptyArray, "text[]", "'{}'");
261

262 0
        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 0
        C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'");
265

266 0
        C(Bson(Json(["float_value": Json(123.456), "text_str": Json("text string")])), "jsonb", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'");
267
    }
268
}

Read our documentation on viewing source code .

Loading