1 /**
2 Structures that describe the schema of query results.
3 
4 Copyright: Copyright Boris-Barboris 2017.
5 License: MIT
6 Authors: Boris-Barboris
7 */
8 
9 module dpeq.schema;
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.marshalling;
21 
22 
23 
24 /// Lazily-demarshalled field (column) description
25 struct FieldDescription
26 {
27     @property string name()
28     {
29         // from c-string to d-string, no allocation
30         return demarshalString(m_buf[0..nameLength-1]);
31     }
32 
33     /// If the field can be identified as a column of a specific table,
34     /// the object ID of the table; otherwise zero.
35     @property ObjectID table()
36     {
37         return demarshalNumber(m_buf[nameLength .. nameLength + 4]);
38     }
39 
40     /// If the field can be identified as a column of a specific table,
41     /// the attribute number of the column; otherwise zero.
42     @property short columnId()
43     {
44         return demarshalNumber!short(m_buf[nameLength + 4 .. nameLength + 6]);
45     }
46 
47     /// The object ID of the field's data type.
48     @property ObjectID type()
49     {
50         return demarshalNumber(m_buf[nameLength + 6 .. nameLength + 10]);
51     }
52 
53     /// The data type size (see pg_type.typlen).
54     /// Note that negative values denote variable-width types.
55     @property short typeLen()
56     {
57         return demarshalNumber!short(m_buf[nameLength + 10 .. nameLength + 12]);
58     }
59 
60     /// The type modifier (see pg_attribute.atttypmod).
61     /// The meaning of the modifier is type-specific.
62     @property int typeModifier()
63     {
64         return demarshalNumber(m_buf[nameLength + 12 .. nameLength + 16]);
65     }
66 
67     /// The format code being used for the field. Currently will be zero (text)
68     /// or one (binary). In a RowDescription returned from the statement variant
69     /// of Describe, the format code is not yet known and will always be zero.
70     @property FormatCode formatCode()
71     {
72         return cast(FormatCode)
73             demarshalNumber!short(m_buf[nameLength + 16 .. nameLength + 18]);
74     }
75 
76     /// backing buffer, owned by Message
77     const(ubyte)[] m_buf;
78 
79     /// length of name C-string wich spans the head of backing buffer
80     private int nameLength;
81 
82     static FieldDescription demarshal(const(ubyte)[] buf, out int bytesDiscarded)
83     {
84         int bytesRead = 0;
85         while (buf[bytesRead])  // name is C-string, so it ends with zero
86             bytesRead++;
87         int nameLength = bytesRead + 1;
88         bytesDiscarded = (nameLength + 2 * ObjectID.sizeof + 2 * short.sizeof +
89             int.sizeof + FormatCode.sizeof).to!int;
90         return FieldDescription(buf[0 .. bytesDiscarded], nameLength);
91     }
92 }
93 
94 
95 struct RowDescription
96 {
97     /// number of fields (columns) in a row
98     @property short fieldCount()
99     {
100         return demarshalNumber!short(m_buf[0 .. 2]);
101     }
102 
103     /// buffer owned by Message
104     const(ubyte)[] m_buf;
105 
106     /// true when row description of this row block was received
107     @property bool isSet() const { return m_buf !is null; }
108 
109     /// Slice operator, wich returns InputRange of lazily-demarshalled FieldDescriptions.
110     auto opIndex()
111     {
112         static struct FieldDescrRange
113         {
114             private const(ubyte)[] buf;
115 
116             this(const(ubyte)[] backing)
117             {
118                 buf = backing;
119             }
120 
121             @property bool empty() const
122             {
123                 return buf.length == 0 && !frontDemarshalled;
124             }
125 
126             private bool frontDemarshalled = false;
127             private FieldDescription _front;
128 
129             @property FieldDescription front()
130             {
131                 if (frontDemarshalled)
132                     return _front;
133                 assert(buf.length > 0);
134                 int shift = 0;
135                 _front = FieldDescription.demarshal(buf, shift);
136                 buf = buf[shift .. $];
137                 frontDemarshalled = true;
138                 return _front;
139             }
140 
141             void popFront()
142             {
143                 frontDemarshalled = false;
144             }
145         }
146 
147         static assert (isInputRange!FieldDescrRange);
148 
149         return FieldDescrRange(m_buf[2..$]);
150     }
151 }
152 
153 
154 /** Array of rows, returned by the server, wich all share one row
155 description. Simple queries may include multiple SQL statements, each
156 corresponding to row block. In extended query protocol flow, row block
157 is retuned for each "Execute" message. */
158 struct RowBlock
159 {
160     RowDescription rowDesc;
161     Message[] dataRows;
162 
163     /// set when the server responded with EmptyQueryResponse to sql query
164     /// this row block represents.
165     bool emptyQuery;
166 
167     /** Set when the server has sent PortalSuspended due to reaching nonzero
168     result-row count limit, requested in Execute message. The appearance of
169     this message tells the frontend that another Execute should be issued
170     against the same portal to complete the operation. Keep in mind, that all
171     portals are destroyed at the end of transaction, wich means that you
172     should not carelessly send Sync message before receiving CommandComplete
173     when you use portal suspension functionality. */
174     bool suspended;
175 }
176 
177 
178 /// Generic query result, returned by getQueryResults
179 struct QueryResult
180 {
181     /// Number of CommandComplete\EmptyQueryResponse\PortalSuspended messages received.
182     short commandsComplete;
183 
184     /// Data blocks, each block being an array of rows sharing one row
185     /// description (schema). Each sql statement in simple query protocol
186     /// creates one block. Each portal execution in EQ protocol creates
187     /// one block.
188     RowBlock[] blocks;
189 
190     /// returns true if there is not a single data row in the response.
191     @property bool noDataRows() const
192     {
193         foreach (block; blocks)
194             if (block.dataRows.length > 0)
195                 return false;
196         return true;
197     }
198 }
199 
200 
201 /// NotificationResponse message received from the backend.
202 /// https://www.postgresql.org/docs/current/static/sql-notify.html
203 struct Notification
204 {
205     /// The process ID of the notifying backend process.
206     int procId;
207 
208     /// The name of the channel that the notify has been raised on.
209     string channel;
210 
211     /// The "payload" string passed from the notifying process.
212     string payload;
213 }
214 
215 
216 /// Contents of NoticeResponse or ErrorResponse messages.
217 /// https://www.postgresql.org/docs/current/static/protocol-error-fields.html
218 struct Notice
219 {
220     /** Field contents are ERROR, FATAL, or PANIC (in an error message), or
221     WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a localized
222     translation of one of these. Always present. */
223     string severity;
224 
225     /** Field contents are ERROR, FATAL, or PANIC (in an error
226     message), or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message).
227     This is identical to the 'severity' field except that the contents are never
228     localized. This is present only in messages generated by PostgreSQL
229     versions 9.6 and later. */
230     string severityV;
231 
232     /// https://www.postgresql.org/docs/current/static/errcodes-appendix.html
233     char[5] code;
234 
235     /// Primary human-readable error message. This should be accurate
236     /// but terse (typically one line). Always present.
237     string message;
238 
239     /// Optional secondary error message carrying more detail about the
240     /// problem. Might run to multiple lines.
241     string detail;
242 
243     /** Optional suggestion what to do about the problem. This is intended to
244     differ from Detail in that it offers advice (potentially inappropriate)
245     rather than hard facts. Might run to multiple lines. */
246     string hint;
247 
248     /** Decimal ASCII integer, indicating an error cursor position as an index
249     into the original query string. The first character has index 1, and
250     positions are measured in characters not bytes. */
251     string position;
252 
253     /** this is defined the same as the position field, but it is used when
254     the cursor position refers to an internally generated command rather than
255     the one submitted by the client. The q field will always appear when this
256     field appears.*/
257     string internalPos;
258 
259     /// Text of a failed internally-generated command. This could be, for
260     /// example, a SQL query issued by a PL/pgSQL function.
261     string internalQuery;
262 
263     /** Context in which the error occurred. Presently this includes a call
264     stack traceback of active procedural language functions and
265     internally-generated queries. The trace is one entry per line, most
266     recent first. */
267     string where;
268 
269     string schema;
270     string table;
271     string column;
272 
273     /// If the error was associated with a specific data type, the name of
274     /// the data type.
275     string dataType;
276 
277     /** If the error was associated with a specific constraint, the name of the
278     constraint. Refer to fields listed above for the associated table or domain.
279     (For this purpose, indexes are treated as constraints, even if they weren't
280     created with constraint syntax.) */
281     string constraint;
282 
283     /// File name of the source-code location where the error was reported.
284     string file;
285 
286     /// Line number of the source-code location where the error was reported.
287     string line;
288 
289     /// Name of the source-code routine reporting the error.
290     string routine;
291 }