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 }