1 /**
2 Structures that describe query results and notifications, received from backend.
3 
4 Copyright: Copyright Boris-Barboris 2017.
5 License: MIT
6 Authors: Boris-Barboris
7 */
8 
9 module dpeq.result;
10 
11 import std.exception: enforce;
12 import std.conv: to;
13 import std.traits;
14 import std.meta;
15 import std.range;
16 
17 import dpeq.exceptions;
18 import dpeq.connection;
19 import dpeq.constants;
20 import dpeq.serialize;
21 
22 
23 /// Message, received from backend.
24 struct Message
25 {
26     BackendMessageType type;
27     /// Raw unprocessed message byte array excluding message type byte and
28     /// first 4 bytes that represent message body length.
29     immutable(ubyte)[] data;
30 }
31 
32 
33 /// Lazily-deserialized field (column) description
34 struct FieldDescription
35 {
36     @safe pure:
37 
38     @property string name() const
39     {
40         // from c-string to d-string, no allocation
41         return deserializeString(m_buf[0..nameLength-1]);
42     }
43 
44     /// If the field can be identified as a column of a specific table,
45     /// the object ID of the table; otherwise zero.
46     @property OID table() const
47     {
48         return deserializeNumber(m_buf[nameLength .. nameLength + 4]);
49     }
50 
51     /// If the field can be identified as a column of a specific table,
52     /// the attribute number of the column; otherwise zero.
53     @property short columnId() const
54     {
55         return deserializeNumber!short(m_buf[nameLength + 4 .. nameLength + 6]);
56     }
57 
58     /// The object ID of the field's data type.
59     @property OID type() const
60     {
61         return deserializeNumber(m_buf[nameLength + 6 .. nameLength + 10]);
62     }
63 
64     /// The data type size (see pg_type.typlen).
65     /// Note that negative values denote variable-width types.
66     @property short typeLen() const
67     {
68         return deserializeNumber!short(m_buf[nameLength + 10 .. nameLength + 12]);
69     }
70 
71     /// The type modifier (see pg_attribute.atttypmod).
72     /// The meaning of the modifier is type-specific.
73     @property int typeModifier() const
74     {
75         return deserializeNumber(m_buf[nameLength + 12 .. nameLength + 16]);
76     }
77 
78     /// The format code being used for the field. Currently will be zero (text)
79     /// or one (binary). In a RowDescription returned from the statement variant
80     /// of Describe, the format code is not yet known and will always be zero.
81     @property FormatCode formatCode() const
82     {
83         return cast(FormatCode)
84             deserializeNumber!short(m_buf[nameLength + 16 .. nameLength + 18]);
85     }
86 
87     /// backing buffer, owned by Message
88     immutable(ubyte)[] m_buf;
89 
90     /// length of name C-string wich spans the head of backing buffer
91     private int nameLength;
92 
93     static FieldDescription deserialize(immutable(ubyte)[] buf, out int bytesDiscarded)
94     {
95         int bytesRead = 0;
96         while (buf[bytesRead])  // name is C-string, so it ends with zero
97             bytesRead++;
98         int nameLength = bytesRead + 1;
99         bytesDiscarded = (nameLength + 2 * OID.sizeof + 2 * short.sizeof +
100             int.sizeof + FormatCode.sizeof).to!int;
101         return FieldDescription(buf[0 .. bytesDiscarded], nameLength);
102     }
103 }
104 
105 
106 struct RowDescription
107 {
108     @safe pure:
109 
110     /// number of fields (columns) in a row
111     @property short fieldCount() const
112     {
113         return deserializeNumber!short(m_buf[0 .. 2]);
114     }
115 
116     /// buffer owned by Message
117     immutable(ubyte)[] m_buf;
118 
119     /// true when row description of this row block was received
120     @property bool isSet() const { return m_buf !is null; }
121 
122     /// Slice operator, wich returns ForwardRange of FieldDescriptions.
123     auto opIndex() const
124     {
125         assert(isSet(), "RowDescription is not set");
126 
127         static struct FieldDescrRange
128         {
129             private immutable(ubyte)[] buf;
130 
131             this(immutable(ubyte)[] backing)
132             {
133                 buf = backing;
134             }
135 
136             @property bool empty() const
137             {
138                 return buf.length == 0 && !frontDeserialized;
139             }
140 
141             private bool frontDeserialized = false;
142             private FieldDescription _front;
143 
144             @property FieldDescription front()
145             {
146                 if (frontDeserialized)
147                     return _front;
148                 assert(buf.length > 0);
149                 int shift = 0;
150                 _front = FieldDescription.deserialize(buf, shift);
151                 buf = buf[shift .. $];
152                 frontDeserialized = true;
153                 return _front;
154             }
155 
156             void popFront()
157             {
158                 frontDeserialized = false;
159             }
160 
161             FieldDescrRange save() const
162             {
163                 return this;
164             }
165         }
166 
167         static assert (isForwardRange!FieldDescrRange);
168 
169         return FieldDescrRange(m_buf[2..$]);
170     }
171 }
172 
173 
174 /// Row block data rows are in one of these states
175 enum RowBlockState: byte
176 {
177     invalid = 0,    /// Default state, wich means it was never set.
178     complete,       /// Last data row was succeeded with CommandComplete.
179     emptyQuery,     /// EmptyQueryResponse was issued from backend.
180     /** Happens when the server has sent PortalSuspended due to reaching nonzero
181     result-row count limit, requested in Execute message. The appearance of
182     this message tells the frontend that another Execute should be issued
183     against the same portal to complete the operation. Keep in mind, that all
184     portals are destroyed at the end of transaction, wich means that you
185     should not carelessly send Sync message before receiving CommandComplete
186     when you use portal suspension functionality and implicit transaction scope
187     (no explicit BEGIN\COMMIT). */
188     suspended,
189     /** Polling stopped early on the client side, for example 'getOneRowBlock'
190     stopped because of rowCountLimit. */
191     incomplete
192 }
193 
194 
195 /** Array of rows, returned by the server, wich all share one row
196 description. Simple queries may include multiple SQL statements, each
197 returning a row block. In extended query protocol flow, row block
198 is retuned for each "Execute" message. */
199 struct RowBlock
200 {
201     RowDescription rowDesc;
202     immutable(ubyte)[][] dataRows;
203     RowBlockState state;
204 
205     /**
206     The command tag. Present when CommandComplete was received.
207     This is usually a single word that identifies which SQL command was completed.
208     For an INSERT command, the tag is INSERT oid rows, where rows is the number of rows inserted.
209         oid is the object ID of the inserted row if rows is 1 and the target table
210         has OIDs; otherwise oid is 0.
211     For a DELETE command, the tag is DELETE rows where rows is the number of rows deleted.
212     For an UPDATE command, the tag is UPDATE rows where rows is the number of rows updated.
213     For a SELECT or CREATE TABLE AS command, the tag is SELECT rows where rows is the number of rows retrieved.
214     For a MOVE command, the tag is MOVE rows where rows is the number of rows the cursor's position has been changed by.
215     For a FETCH command, the tag is FETCH rows where rows is the number of rows that have been retrieved from the cursor.
216     For a COPY command, the tag is COPY rows where rows is the number of rows copied. (Note:
217         the row count appears only in PostgreSQL 8.2 and later.) */
218     string commandTag;
219 }
220 
221 
222 /// Generic query result, returned by getQueryResults
223 struct QueryResult
224 {
225     /** Data blocks, each block being an array of rows sharing one row
226     description. Each sql statement in simple query protocol
227     creates one block. Each portal execution in EQ protocol creates one block. */
228     RowBlock[] blocks;
229 
230     /// returns true if there is not a single data row in the response.
231     bool noDataRows() const pure @safe
232     {
233         foreach (block; blocks)
234             if (block.dataRows.length > 0)
235                 return false;
236         return true;
237     }
238 }
239 
240 
241 /// NotificationResponse message received from the backend.
242 /// https://www.postgresql.org/docs/current/static/sql-notify.html
243 struct Notification
244 {
245     /// The process ID of the notifying backend process.
246     int procId;
247 
248     /// The name of the channel that the notify has been raised on.
249     string channel;
250 
251     /// The "payload" string passed from the notifying process.
252     string payload;
253 }
254 
255 
256 /// Contents of NoticeResponse or ErrorResponse messages.
257 /// https://www.postgresql.org/docs/current/static/protocol-error-fields.html
258 struct Notice
259 {
260     /** Field contents are ERROR, FATAL, or PANIC (in an error message), or
261     WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a localized
262     translation of one of these. Always present. */
263     string severity;
264 
265     /** Field contents are ERROR, FATAL, or PANIC (in an error
266     message), or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message).
267     This is identical to the 'severity' field except that the contents are never
268     localized. This is present only in messages generated by PostgreSQL
269     versions 9.6 and later. */
270     string severityV;
271 
272     /// https://www.postgresql.org/docs/current/static/errcodes-appendix.html
273     char[5] code;
274 
275     /// Primary human-readable error message. This should be accurate
276     /// but terse (typically one line). Always present.
277     string message;
278 
279     /// Optional secondary error message carrying more detail about the
280     /// problem. Might run to multiple lines.
281     string detail;
282 
283     /** Optional suggestion what to do about the problem. This is intended to
284     differ from Detail in that it offers advice (potentially inappropriate)
285     rather than hard facts. Might run to multiple lines. */
286     string hint;
287 
288     /** Decimal ASCII integer, indicating an error cursor position as an index
289     into the original query string. The first character has index 1, and
290     positions are measured in characters not bytes. */
291     string position;
292 
293     /** this is defined the same as the position field, but it is used when
294     the cursor position refers to an internally generated command rather than
295     the one submitted by the client. The q field will always appear when this
296     field appears.*/
297     string internalPos;
298 
299     /// Text of a failed internally-generated command. This could be, for
300     /// example, a SQL query issued by a PL/pgSQL function.
301     string internalQuery;
302 
303     /** Context in which the error occurred. Presently this includes a call
304     stack traceback of active procedural language functions and
305     internally-generated queries. The trace is one entry per line, most
306     recent first. */
307     string where;
308 
309     string schema;
310     string table;
311     string column;
312 
313     /// If the error was associated with a specific data type, the name of
314     /// the data type.
315     string dataType;
316 
317     /** If the error was associated with a specific constraint, the name of the
318     constraint. Refer to fields listed above for the associated table or domain.
319     (For this purpose, indexes are treated as constraints, even if they weren't
320     created with constraint syntax.) */
321     string constraint;
322 
323     /// File name of the source-code location where the error was reported.
324     string file;
325 
326     /// Line number of the source-code location where the error was reported.
327     string line;
328 
329     /// Name of the source-code routine reporting the error.
330     string routine;
331 }
332 
333 
334 void parseNoticeMessage(immutable(ubyte)[] data, ref Notice n) @safe pure
335 {
336     void copyTillZero(ref string dest)
337     {
338         size_t length;
339         dest = deserializeProtocolString(data, length);
340         data = data[length..$];
341     }
342 
343     void discardTillZero()
344     {
345         size_t idx = 0;
346         while (idx < data.length && data[idx])
347             idx++;
348         data = data[idx+1..$];
349     }
350 
351     while (data.length > 1)
352     {
353         char fieldType = cast(char) data[0];
354         data = data[1..$];
355         // https://www.postgresql.org/docs/current/static/protocol-error-fields.html
356         switch (fieldType)
357         {
358             case 'S':
359                 copyTillZero(n.severity);
360                 break;
361             case 'V':
362                 copyTillZero(n.severityV);
363                 break;
364             case 'C':
365                 assert(data.length >= 6, "Expected 5 bytes of SQLSTATE code.");
366                 n.code[] = cast(immutable(char)[]) data[0..5];
367                 data = data[6..$];
368                 break;
369             case 'M':
370                 copyTillZero(n.message);
371                 break;
372             case 'D':
373                 copyTillZero(n.detail);
374                 break;
375             case 'H':
376                 copyTillZero(n.hint);
377                 break;
378             case 'P':
379                 copyTillZero(n.position);
380                 break;
381             case 'p':
382                 copyTillZero(n.internalPos);
383                 break;
384             case 'q':
385                 copyTillZero(n.internalQuery);
386                 break;
387             case 'W':
388                 copyTillZero(n.where);
389                 break;
390             case 's':
391                 copyTillZero(n.schema);
392                 break;
393             case 't':
394                 copyTillZero(n.table);
395                 break;
396             case 'c':
397                 copyTillZero(n.column);
398                 break;
399             case 'd':
400                 copyTillZero(n.dataType);
401                 break;
402             case 'n':
403                 copyTillZero(n.constraint);
404                 break;
405             case 'F':
406                 copyTillZero(n.file);
407                 break;
408             case 'L':
409                 copyTillZero(n.line);
410                 break;
411             case 'R':
412                 copyTillZero(n.routine);
413                 break;
414             default:
415                 discardTillZero();
416         }
417     }
418 }