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 }