denizzzka / dpq2
1
///
2
module dpq2.conv.to_d_types;
3

4
@safe:
5

6
import dpq2.value;
7
import dpq2.oids: OidType, isNativeInteger, isNativeFloat;
8
import dpq2.connection: Connection;
9
import dpq2.query: QueryParams;
10
import dpq2.result: msg_NOT_BINARY;
11
import dpq2.conv.from_d_types;
12
import dpq2.conv.numeric: rawValueToNumeric;
13
import dpq2.conv.time: binaryValueAs, TimeStamp, TimeStampUTC;
14
import dpq2.conv.geometric: binaryValueAs, Line;
15
import dpq2.conv.arrays : binaryValueAs;
16

17
import vibe.data.json: Json, parseJsonString;
18
import vibe.data.bson: Bson;
19
import std.traits;
20
import std.uuid;
21
import std.datetime;
22
import std.traits: isScalarType;
23
import std.typecons : Nullable;
24
import std.bitmanip: bigEndianToNative, BitArray;
25
import std.conv: to;
26
version (unittest) import std.exception : assertThrown;
27

28
// Supported PostgreSQL binary types
29
alias PGboolean =       bool; /// boolean
30
alias PGsmallint =      short; /// smallint
31
alias PGinteger =       int; /// integer
32
alias PGbigint =        long; /// bigint
33
alias PGreal =          float; /// real
34
alias PGdouble_precision = double; /// double precision
35
alias PGtext =          string; /// text
36
alias PGnumeric =       string; /// numeric represented as string
37
alias PGbytea =         immutable(ubyte)[]; /// bytea
38
alias PGuuid =          UUID; /// UUID
39
alias PGdate =          Date; /// Date (no time of day)
40
alias PGtime_without_time_zone = TimeOfDay; /// Time of day (no date)
41
alias PGtimestamp = TimeStamp; /// Both date and time without time zone
42
alias PGtimestamptz = TimeStampUTC; /// Both date and time stored in UTC time zone
43
alias PGjson =          Json; /// json or jsonb
44
alias PGline =          Line; /// Line (geometric type)
45
alias PGvarbit =        BitArray; /// BitArray
46

47
private alias VF = ValueFormat;
48
private alias AE = ValueConvException;
49
private alias ET = ConvExceptionType;
50

51
/**
52
    Returns cell value as a native string based type from text or binary formatted field.
53
    Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value
54
*/
55
T as(T)(in Value v) pure @trusted
56
if(is(T : const(char)[]))
57
{
58 1
    if(v.format == VF.BINARY)
59
    {
60 1
        if(!(
61
            v.oidType == OidType.Text ||
62 0
            v.oidType == OidType.FixedString ||
63 0
            v.oidType == OidType.VariableString ||
64 0
            v.oidType == OidType.Numeric ||
65 0
            v.oidType == OidType.Json ||
66 0
            v.oidType == OidType.Jsonb ||
67 0
            v.oidType == OidType.Name
68
        ))
69 0
            throwTypeComplaint(v.oidType, "Text, FixedString, VariableString, Name, Numeric, Json or Jsonb", __FILE__, __LINE__);
70
    }
71

72
    static if(is(T == Nullable!R, R))
73
    {
74
        alias Ret = R;
75

76 1
        if (v.isNull)
77 1
            return T.init;
78
    }
79
    else
80
        alias Ret = T;
81

82 1
    Ret r;
83

84 1
    if(v.format == VF.BINARY && v.oidType == OidType.Numeric)
85 0
        r = rawValueToNumeric(v.data); // special case for 'numeric' which represented in dpq2 as string
86
    else
87 1
        r = v.valueAsString;
88

89
    static if(is(T == Nullable!R2, R2))
90 0
        return T(r);
91
    else
92 1
        return r;
93
}
94

95
@system unittest
96
{
97
    import core.exception: AssertError;
98

99 1
    auto v = Value(ValueFormat.BINARY, OidType.Text);
100

101 1
    assert(v.isNull);
102 1
    assertThrown!AssertError(v.as!string == "");
103 1
    assert(v.as!(Nullable!string).isNull == true);
104
}
105

106
/**
107
    Returns value as D type value from binary formatted field.
108
    Throws: AssertError if the db value is NULL and Nullable is not used to retrieve the value
109
*/
110
T as(T)(in Value v)
111
if(!is(T : const(char)[]) && !is(T == Bson))
112
{
113 1
    if(!(v.format == VF.BINARY))
114 1
        throw new AE(ET.NOT_BINARY,
115
            msg_NOT_BINARY, __FILE__, __LINE__);
116

117
    static if (is(T == Nullable!R, R))
118
    {
119 1
        if (v.isNull)
120 1
            return T.init;
121
        else
122 1
            return T(binaryValueAs!R(v));
123
    }
124
    else
125 1
        return binaryValueAs!T(v);
126
}
127

128
@system unittest
129
{
130 1
    auto v = Value([1], OidType.Int4, false, ValueFormat.TEXT);
131 1
    assertThrown!AE(v.as!int);
132
}
133

134
package:
135

136
/*
137
 * Something was broken in DMD64 D Compiler v2.079.0-rc.1 so I made this "tunnel"
138
 * TODO: remove it and replace by direct binaryValueAs calls
139
 */
140
auto tunnelForBinaryValueAsCalls(T)(in Value v)
141
{
142 1
    return binaryValueAs!T(v);
143
}
144

145
char[] valueAsString(in Value v) pure
146
{
147 1
    return (cast(const(char[])) v.data).to!(char[]);
148
}
149

150
/// Returns value as bytes from binary formatted field
151
T binaryValueAs(T)(in Value v)
152
if(is(T : const ubyte[]))
153
{
154 1
    if(!(v.oidType == OidType.ByteArray))
155 1
        throwTypeComplaint(v.oidType, "immutable ubyte[]", __FILE__, __LINE__);
156

157 1
    return v.data;
158
}
159

160
@system unittest
161
{
162 1
    auto v = Value([1], OidType.Bool);
163 1
    assertThrown!ValueConvException(v.binaryValueAs!(const ubyte[]));
164
}
165

166
/// Returns cell value as native integer or decimal values
167
///
168
/// Postgres type "numeric" is oversized and not supported by now
169
T binaryValueAs(T)(in Value v)
170
if( isNumeric!(T) )
171
{
172
    static if(isIntegral!(T))
173 1
        if(!isNativeInteger(v.oidType))
174 1
            throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__);
175

176
    static if(isFloatingPoint!(T))
177 1
        if(!isNativeFloat(v.oidType))
178 1
            throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__);
179

180 1
    if(!(v.data.length == T.sizeof))
181 1
        throw new AE(ET.SIZE_MISMATCH,
182
            to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~
183
                to!string(typeid(T))~" size ("~to!string(T.sizeof)~")",
184
            __FILE__, __LINE__);
185

186 1
    ubyte[T.sizeof] s = v.data[0..T.sizeof];
187 1
    return bigEndianToNative!(T)(s);
188
}
189

190
@system unittest
191
{
192 1
    auto v = Value([1], OidType.Bool);
193 1
    assertThrown!ValueConvException(v.binaryValueAs!int);
194 1
    assertThrown!ValueConvException(v.binaryValueAs!float);
195

196 1
    v = Value([1], OidType.Int4);
197 1
    assertThrown!ValueConvException(v.binaryValueAs!int);
198
}
199

200
/// Returns UUID as native UUID value
201
UUID binaryValueAs(T)(in Value v)
202
if( is( T == UUID ) )
203
{
204 1
    if(!(v.oidType == OidType.UUID))
205 1
        throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__);
206

207 1
    if(!(v.data.length == 16))
208 1
        throw new AE(ET.SIZE_MISMATCH,
209
            "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__);
210

211 0
    UUID r;
212 0
    r.data = v.data;
213 0
    return r;
214
}
215

216
@system unittest
217
{
218 1
    auto v = Value([1], OidType.Int4);
219 1
    assertThrown!ValueConvException(v.binaryValueAs!UUID);
220

221 1
    v = Value([1], OidType.UUID);
222 1
    assertThrown!ValueConvException(v.binaryValueAs!UUID);
223
}
224

225
/// Returns boolean as native bool value
226
bool binaryValueAs(T : bool)(in Value v)
227
if (!is(T == Nullable!R, R))
228
{
229 1
    if(!(v.oidType == OidType.Bool))
230 1
        throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__);
231

232 1
    if(!(v.data.length == 1))
233 1
        throw new AE(ET.SIZE_MISMATCH,
234
            "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__);
235

236 1
    return v.data[0] != 0;
237
}
238

239
@system unittest
240
{
241 1
    auto v = Value([1], OidType.Int4);
242 1
    assertThrown!ValueConvException(v.binaryValueAs!bool);
243

244 1
    v = Value([1,2], OidType.Bool);
245 1
    assertThrown!ValueConvException(v.binaryValueAs!bool);
246
}
247

248
/// Returns Vibe.d's Json
249
Json binaryValueAs(T)(in Value v) @trusted
250
if( is( T == Json ) )
251
{
252
    import dpq2.conv.jsonb: jsonbValueToJson;
253

254 1
    Json res;
255

256 1
    switch(v.oidType)
257
    {
258 1
        case OidType.Json:
259
            // represent value as text and parse it into Json
260 1
            string t = v.valueAsString;
261 1
            res = parseJsonString(t);
262 1
            break;
263

264 0
        case OidType.Jsonb:
265 0
            res = v.jsonbValueToJson;
266 0
            break;
267

268 1
        default:
269 1
            throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__);
270
    }
271

272 1
    return res;
273
}
274

275
@system unittest
276
{
277 1
    auto v = Value([1], OidType.Int4);
278 1
    assertThrown!ValueConvException(v.binaryValueAs!Json);
279
}
280

281
import money: currency, roundingMode;
282

283
/// Returns money type
284
///
285
/// Caution: here is no check of fractional precision while conversion!
286
/// See also: PostgreSQL's "lc_monetary" description and "money" package description
287
T binaryValueAs(T)(in Value v) @trusted
288
if( isInstanceOf!(currency, T) &&  T.amount.sizeof == 8 )
289
{
290
    import std.format: format;
291

292 1
    if(v.data.length != T.amount.sizeof)
293 1
        throw new AE(
294
            ET.SIZE_MISMATCH,
295
            format(
296
                "%s length (%d) isn't equal to D money type %s size (%d)",
297
                v.oidType.to!string,
298
                v.data.length,
299
                typeid(T).to!string,
300
                T.amount.sizeof
301
            )
302
        );
303

304 1
    T r;
305

306 1
    r.amount = v.data[0 .. T.amount.sizeof].bigEndianToNative!long;
307

308 1
    return r;
309
}
310

311
package alias PGTestMoney = currency!("TEST_CURR", 2); //TODO: roundingMode.UNNECESSARY
312

313
unittest
314
{
315 1
    auto v = Value([1], OidType.Money);
316 1
    assertThrown!ValueConvException(v.binaryValueAs!PGTestMoney);
317
}
318

319
T binaryValueAs(T)(in Value v) @trusted
320
if( is(T == BitArray) )
321
{
322
    import core.bitop : bitswap;
323
    import std.bitmanip;
324
    import std.format: format;
325
    import std.range : chunks;
326

327 1
    if(v.data.length < int.sizeof)
328 1
        throw new AE(
329
            ET.SIZE_MISMATCH,
330
            format(
331
                "%s length (%d) is less than minimum int type size (%d)",
332
                v.oidType.to!string,
333
                v.data.length,
334
                int.sizeof
335
            )
336
        );
337

338 1
    auto data = v.data;
339 1
    size_t len = data.read!int;
340 1
    size_t[] newData;
341 1
    foreach (ch; data.chunks(size_t.sizeof))
342
    {
343 1
        ubyte[size_t.sizeof] tmpData;
344 1
        tmpData[0 .. ch.length] = ch[];
345

346
        // DMD Issue 19693
347
        version(DigitalMars)
348 1
            auto re = softBitswap(bigEndianToNative!size_t(tmpData));
349
        else
350
            auto re = bitswap(bigEndianToNative!size_t(tmpData));
351 1
        newData ~= re;
352
    }
353 1
    return T(newData, len);
354
}
355

356
unittest
357
{
358 1
    auto v = Value([1], OidType.VariableBitString);
359 1
    assertThrown!ValueConvException(v.binaryValueAs!BitArray);
360
}

Read our documentation on viewing source code .

Loading