1 ///
2 module dpq2.value;
3 
4 import dpq2.oids;
5 
6 @safe:
7 
8 /**
9 Represents table cell or argument value
10 
11 Internally it is a ubyte[].
12 If it returned by Answer methods it contains a reference to the data of
13 the server answer and it can not be accessed after Answer is destroyed.
14 */
15 struct Value
16 {
17     private
18     {
19         bool _isNull = true;
20         OidType _oidType = OidType.Undefined;
21 
22         ValueFormat _format;
23     }
24 
25     package immutable(ubyte)[] _data;
26 
27     // FIXME:
28     // The pointer returned by PQgetvalue points to storage that is part of the PGresult structure.
29     // One should not modify the data it points to, and one must explicitly copy the data into other
30     // storage if it is to be used past the lifetime of the PGresult structure itself.
31     // Thus, it is need to store reference to Answer here to ensure that result is still available.
32     // (Also see DIP1000)
33     /// ctor
34     this(immutable(ubyte)[] data, in OidType oidType, bool isNull = false, in ValueFormat format = ValueFormat.BINARY) inout pure
35     {
36         import std.exception: enforce;
37 
38         //TODO: it is possible to skip this check for fixed-size values?
39         enforce(data.length <= int.max, `data.length is too big for use as Postgres value`);
40 
41         this._data = data;
42         this._format = format;
43         this._oidType = oidType;
44         this._isNull = isNull;
45     }
46 
47     /// Null Value constructor
48     this(in ValueFormat format, in OidType oidType) pure
49     {
50         this._format = format;
51         this._oidType = oidType;
52     }
53 
54     @safe const pure nothrow @nogc
55     {
56         /// Indicates if the value is NULL
57         bool isNull()
58         {
59             return _isNull;
60         }
61 
62         /// Indicates if the value is array type
63         bool isArray()
64         {
65             return dpq2.oids.isSupportedArray(oidType);
66         }
67         alias isSupportedArray = isArray; //TODO: deprecate
68 
69         /// Returns Oid of the value
70         OidType oidType()
71         {
72             return _oidType;
73         }
74 
75         /// Returns ValueFormat representation (text or binary)
76         ValueFormat format()
77         {
78             return _format;
79         }
80     }
81 
82     package void oidType(OidType type) @safe pure nothrow @nogc
83     {
84         _oidType = type;
85     }
86 
87     immutable(ubyte)[] data() pure const
88     {
89         import std.exception;
90         import core.exception;
91 
92         enforce!AssertError(!isNull, "Attempt to read NULL value", __FILE__, __LINE__);
93 
94         return _data;
95     }
96 
97     ///
98     string toString() const @trusted
99     {
100         import vibe.data.bson: Bson;
101         import dpq2.conv.to_bson;
102         import std.conv: to;
103 
104         return this.as!Bson.toString~"::"~oidType.to!string~"("~(format == ValueFormat.TEXT? "t" : "b")~")";
105     }
106 }
107 
108 @system unittest
109 {
110     import dpq2.conv.to_d_types;
111     import core.exception: AssertError;
112 
113     Value v = Value(ValueFormat.BINARY, OidType.Int4);
114 
115     bool exceptionFlag = false;
116 
117     try
118         cast(void) v.as!int;
119     catch(AssertError e)
120         exceptionFlag = true;
121 
122     assert(exceptionFlag);
123 }
124 
125 ///
126 enum ValueFormat : int {
127     TEXT, ///
128     BINARY ///
129 }
130 
131 import std.conv: to, ConvException;
132 
133 /// Conversion exception types
134 enum ConvExceptionType
135 {
136     NOT_ARRAY, /// Format of the value isn't array
137     NOT_BINARY, /// Format of the column isn't binary
138     NOT_TEXT, /// Format of the column isn't text string
139     NOT_IMPLEMENTED, /// Support of this type isn't implemented (or format isn't matches to specified D type)
140     SIZE_MISMATCH, /// Value size is not matched to the Postgres value or vice versa
141     CORRUPTED_JSONB, /// Corrupted JSONB value
142     DATE_VALUE_OVERFLOW, /// Date value isn't fits to Postgres binary Date value
143     DIMENSION_MISMATCH, /// Array dimension size is not matched to the Postgres array
144     CORRUPTED_ARRAY, /// Corrupted array value
145     OUT_OF_RANGE, /// Index is out of range
146 }
147 
148 /// Value conversion exception
149 class ValueConvException : ConvException
150 {
151     const ConvExceptionType type; /// Exception type
152 
153     this(ConvExceptionType t, string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) pure @safe
154     {
155         type = t;
156         super(msg, file, line, next);
157     }
158 }
159 
160 package void throwTypeComplaint(OidType receivedType, string expectedType, string file = __FILE__, size_t line = __LINE__) pure
161 {
162     throw new ValueConvException(
163             ConvExceptionType.NOT_IMPLEMENTED,
164             "Format of the column ("~to!string(receivedType)~") doesn't match to D native "~expectedType,
165             file, line
166         );
167 }