Line data Source code
1 : /*
2 : * Text:
3 : * libsnapwebsites/src/libdbproxy/order.cpp
4 : *
5 : * Description:
6 : * Manager an order to be sent to the snapdbproxy daemon.
7 : *
8 : * Documentation:
9 : * See each function below.
10 : *
11 : * License:
12 : * Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
13 : *
14 : * https://snapwebsites.org/
15 : * contact@m2osw.com
16 : *
17 : * Permission is hereby granted, free of charge, to any person obtaining a
18 : * copy of this software and associated documentation files (the
19 : * "Software"), to deal in the Software without restriction, including
20 : * without limitation the rights to use, copy, modify, merge, publish,
21 : * distribute, sublicense, and/or sell copies of the Software, and to
22 : * permit persons to whom the Software is furnished to do so, subject to
23 : * the following conditions:
24 : *
25 : * The above copyright notice and this permission notice shall be included
26 : * in all copies or substantial portions of the Software.
27 : *
28 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
29 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
31 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
32 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
33 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
34 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 : */
36 :
37 : #include "libdbproxy/order.h"
38 :
39 : #include "libdbproxy/exception.h"
40 : #include "libdbproxy/value.h"
41 :
42 : #include "snapwebsites/log.h"
43 :
44 : #include <QtCore>
45 :
46 : #include <iostream>
47 : #include <sstream>
48 :
49 : namespace libdbproxy
50 : {
51 :
52 :
53 :
54 0 : order::type_of_result_t order::get_type_of_result() const
55 : {
56 0 : return f_type_of_result;
57 : }
58 :
59 :
60 :
61 : /** \brief Get the CQL command.
62 : *
63 : * This function returns the UTF-8 encoded CQL order in a QString.
64 : *
65 : * \return The CQL order converted to a QString.
66 : */
67 0 : QString order::cql() const
68 : {
69 0 : return f_cql;
70 : }
71 :
72 :
73 0 : void order::setCql(QString const & cql_string, type_of_result_t const result_type)
74 : {
75 0 : f_cql = cql_string;
76 0 : f_type_of_result = result_type;
77 0 : }
78 :
79 :
80 : /** \brief Check whether the order is considered valid.
81 : *
82 : * By default, an order is considered valid. It may be marked as invalid
83 : * to avoid sending it or on receipt to know that the order could not be
84 : * properly parsed back in the structure.
85 : *
86 : * \return true if the order is considered valid, false otherwise.
87 : */
88 0 : bool order::validOrder() const
89 : {
90 0 : return f_valid;
91 : }
92 :
93 :
94 : /** \brief Set whether the order is valid.
95 : *
96 : * Orders are considered valid by default. It is possible to change that
97 : * value to false to mark them as invalid.
98 : *
99 : * The decodeOrder() function makes use of this function mark the order
100 : * result as invalid up until the entire order was parsed from the
101 : * source.
102 : *
103 : * \return true if the order is considered valid, false otherwise.
104 : */
105 0 : void order::setValidOrder(bool const valid)
106 : {
107 0 : f_valid = valid;
108 0 : }
109 :
110 :
111 : /** \brief Retrieve the consistency level for this order.
112 : *
113 : * This function returns the consistency level to be used with this
114 : * CQL order.
115 : *
116 : * \return The order consistency level.
117 : */
118 0 : consistency_level_t order::consistencyLevel() const
119 : {
120 0 : return f_consistency_level;
121 : }
122 :
123 :
124 : /** \brief Change the consistency level for this order.
125 : *
126 : * This function sets the consistency level of this order.
127 : *
128 : * \param[in] consistency_level The new consistency level.
129 : */
130 0 : void order::setConsistencyLevel(consistency_level_t consistency_level)
131 : {
132 0 : f_consistency_level = consistency_level;
133 0 : }
134 :
135 :
136 0 : int64_t order::timestamp() const
137 : {
138 0 : return f_timestamp;
139 : }
140 :
141 :
142 0 : void order::setTimestamp(int64_t const user_timestamp)
143 : {
144 0 : f_timestamp = user_timestamp;
145 0 : }
146 :
147 :
148 0 : int32_t order::timeout() const
149 : {
150 0 : return f_timeout_ms;
151 : }
152 :
153 :
154 0 : void order::setTimeout(int32_t const statement_timeout_ms)
155 : {
156 0 : f_timeout_ms = statement_timeout_ms;
157 0 : }
158 :
159 :
160 0 : int8_t order::columnCount() const
161 : {
162 0 : return f_column_count;
163 : }
164 :
165 :
166 0 : void order::setColumnCount(int8_t const column_count)
167 : {
168 0 : f_column_count = column_count;
169 0 : }
170 :
171 :
172 0 : int32_t order::pagingSize() const
173 : {
174 0 : return f_paging_size;
175 : }
176 :
177 :
178 0 : void order::setPagingSize(int32_t const paging_size)
179 : {
180 0 : f_paging_size = paging_size;
181 0 : }
182 :
183 :
184 0 : int32_t order::cursorIndex() const
185 : {
186 0 : return f_cursor_index;
187 : }
188 :
189 :
190 0 : void order::setCursorIndex(int32_t const cursor_index)
191 : {
192 0 : f_cursor_index = cursor_index;
193 0 : }
194 :
195 :
196 0 : int32_t order::batchIndex() const
197 : {
198 0 : return f_batch_index;
199 : }
200 :
201 :
202 0 : void order::setBatchIndex(int32_t const batch_index)
203 : {
204 0 : f_batch_index = batch_index;
205 0 : }
206 :
207 :
208 0 : bool order::clearClusterDescription() const
209 : {
210 0 : return f_clear_cluster_description;
211 : }
212 :
213 :
214 0 : void order::setClearClusterDescription(bool const clear)
215 : {
216 0 : f_clear_cluster_description = clear;
217 0 : }
218 :
219 :
220 0 : bool order::blocking() const
221 : {
222 0 : return f_blocking;
223 : }
224 :
225 :
226 0 : void order::setBlocking(bool const block)
227 : {
228 0 : f_blocking = block;
229 0 : }
230 :
231 :
232 0 : size_t order::parameterCount() const
233 : {
234 0 : return f_parameter.size();
235 : }
236 :
237 :
238 0 : QByteArray const & order::parameter(int index) const
239 : {
240 0 : if(static_cast<size_t>(index) >= f_parameter.size())
241 : {
242 0 : throw overflow_exception("order::parameter() called with an index too large.");
243 : }
244 0 : return f_parameter[index];
245 : }
246 :
247 :
248 0 : void order::addParameter(QByteArray const & data)
249 : {
250 0 : f_parameter.push_back(data);
251 0 : }
252 :
253 :
254 : /** \brief Encode the order so it can be sent to snapdbproxy.
255 : *
256 : * The function transforms the order in a blob that we can send over
257 : * the wire.
258 : *
259 : * The format is as follow:
260 : *
261 : * \li flags -- one byte with the type of result expected; whether this is
262 : * a blocking call; and whether timestamp is included
263 : *
264 : * \li consistency level -- one byte, since these are still very small numbers
265 : *
266 : * \li CQL order -- size on 2 bytes (uint16_t) and then the string itself
267 : *
268 : * \li timestamp -- if bit 5 of the flags is set, 8 bytes with a timestamp
269 : * (at this time we use a timestamp only when deleting
270 : * something because it does not always delete otherwise...)
271 : *
272 : * \li number of parameters -- count on 2 bytes (uint16_t); this size may be
273 : * zero (i.e. no additional parameters)
274 : *
275 : * \li parameters -- a sequence of size (uint32_t) followed by parameter data
276 : * repeated for each parameter; if the number of parameters
277 : * is zero, then none of this exists
278 : *
279 : * \return A blob one can send to the snapdbproxy daemon.
280 : */
281 0 : QByteArray order::encodeOrder() const
282 : {
283 : // Size of an order:
284 : // 4 tag (CQLP)
285 : // 4 size
286 : // 2 flags
287 : // 1 consistency level
288 : // 2 CQL length
289 : // ... length of CQL string
290 : // 8 timestamp
291 : // 4 timeout
292 : // 1 column count
293 : // 4 paging size
294 : // 2 cursor index
295 : // 2 number of parameters
296 : // {
297 : // 4 parameter size
298 : // ... length of parameter
299 : // }*
300 : //
301 : // Byte providing the expected size we get a single malloc()
302 : // instead of 1 malloc + X realloc()
303 : //
304 0 : uint32_t expected_size(4 + 4 + 2 + 1 + 2 + f_cql.length() + 8 + 4 + 1 + 4 + 2 + 2);
305 0 : for(auto param : f_parameter)
306 : {
307 0 : expected_size += 4 + param.size();
308 : }
309 0 : QCassandraEncoder encoder(expected_size);
310 :
311 : // Sending plain CQL (P for plain). We may later support CQLZ to send
312 : // a compressed QByteArray. (i.e. right now all snapdbproxies are
313 : // expected to be local so compression is not that useful, especially
314 : // for orders that are generally small except when uploading a file.)
315 : //
316 0 : encoder.appendSignedCharValue('C');
317 0 : encoder.appendSignedCharValue('Q');
318 0 : encoder.appendSignedCharValue('L');
319 0 : encoder.appendSignedCharValue('P');
320 :
321 0 : encoder.appendUInt32Value(expected_size - 8);
322 :
323 : // TBD: should we err if the f_valid flag is false?
324 :
325 : // flags
326 : // f_type_of_result (bit 0 to 3)
327 : // f_blocking (bit 4)
328 : // f_timestamp included (bit 5)
329 : // f_timeout_ms included (bit 6)
330 : // f_column_count included (bit 7)
331 : // f_paging_size included (bit 8)
332 : // f_cursor_index included (bit 9)
333 : // f_clear_cluster_description (bit 10)
334 : //
335 0 : uint16_t const flags(
336 0 : (static_cast<uint16_t>(f_type_of_result) & 15)
337 0 : | (f_blocking ? 0x0010 : 0)
338 0 : | (f_timestamp != 0 ? 0x0020 : 0)
339 0 : | (f_timeout_ms != 0 ? 0x0040 : 0)
340 0 : | (f_column_count != 1 ? 0x0080 : 0)
341 0 : | (f_paging_size != 0 ? 0x0100 : 0)
342 0 : | (f_cursor_index != -1 ? 0x0200 : 0)
343 0 : | (f_clear_cluster_description ? 0x0400 : 0)
344 0 : | (f_batch_index != -1 ? 0x0800 : 0)
345 : );
346 0 : encoder.appendUInt16Value(flags);
347 :
348 : // consistency level (saved as one byte, signed)
349 : //
350 0 : signed char consistency_level(static_cast<signed char>(f_consistency_level));
351 0 : encoder.appendSignedCharValue(consistency_level);
352 :
353 : // CQL command as a PSTR (size is 2 bytes, max. 64Kb)
354 : //
355 0 : encoder.appendP16StringValue(f_cql);
356 :
357 : // the timestamp if not zero (save as 8 bytes, time in microseconds)
358 : //
359 0 : if(f_timestamp != 0)
360 : {
361 0 : encoder.appendInt64Value(f_timestamp);
362 : }
363 :
364 : // the timeout if not zero (save as 4 bytes, time in seconds)
365 : //
366 0 : if(f_timeout_ms != 0)
367 : {
368 0 : encoder.appendInt32Value(f_timeout_ms);
369 : }
370 :
371 : // the column count if not 1 (save as 1 bytes, 0 to 255 column in a select...)
372 : //
373 0 : if(f_column_count != 1)
374 : {
375 0 : encoder.appendSignedCharValue(f_column_count);
376 : }
377 :
378 : // the paging size if not zero (save as 4 bytes, 1 to 4 billion...)
379 : //
380 0 : if(f_paging_size != 0)
381 : {
382 0 : encoder.appendInt32Value(f_paging_size);
383 : }
384 :
385 : // the cursor index if not -1 (save as 2 bytes)
386 : //
387 0 : if(f_cursor_index != -1)
388 : {
389 0 : encoder.appendUInt16Value(f_cursor_index);
390 : }
391 :
392 : // the batch index if not -1 (save as 2 bytes)
393 : //
394 0 : if(f_batch_index != -1)
395 : {
396 0 : encoder.appendUInt16Value(f_batch_index);
397 : }
398 :
399 : // parameters, if any
400 : //
401 : // here we first save the number of parameters, possibly zero
402 : // (maximum of 64Kb); then we save the parameters as size
403 : // (up to 4Gb) and then the data
404 : //
405 0 : encoder.appendUInt16Value(f_parameter.size());
406 0 : for(auto param : f_parameter)
407 : {
408 0 : encoder.appendBinaryValue(param);
409 : }
410 :
411 : // adjust the size to match the buffer size exactly
412 : //
413 0 : encoder.replaceUInt32Value(encoder.size() - 8, 4);
414 :
415 0 : return encoder.result();
416 : }
417 :
418 :
419 : /** \brief Decode an order that was encoded with encodeOrder().
420 : *
421 : * snapdbproxy calls this function to get a order from
422 : * data received from a client.
423 : *
424 : * \exception runtime_error
425 : * If the buffer is of the wrong size, the reading of the data will
426 : * fail raising this exception. We may later add a try/catch within
427 : * this function to return false instead. Yet, if the order is wrong
428 : * we are going to have a hard time reading the next buffer. Plus,
429 : * if things work as expected, synchronizing the input should never
430 : * be required.
431 : *
432 : * \param[in] encoded_order The order received from a client.
433 : * \param[in] size The number of bytes in the specified buffer.
434 : *
435 : * \return true if the buffer looked proper and can be processed further.
436 : */
437 0 : bool order::decodeOrder(unsigned char const * encoded_order, size_t size)
438 : {
439 : // WARNING: Here I use the horrible fromRawData() function which does
440 : // NOT copy the data to be checked here. However, that gives
441 : // me full access to the value functions against
442 : // QByteArray which is practical.
443 : //
444 : // Just make sure to only use that 'encoded' buffer in this
445 : // function. Do not pass it anywhere, or worst, return it!
446 : //
447 0 : QByteArray const encoded(QByteArray::fromRawData(reinterpret_cast<char const *>(encoded_order), size));
448 0 : QCassandraDecoder const decoder(encoded);
449 :
450 : try
451 : {
452 : // get the flags
453 : //
454 0 : int const flags(decoder.uint16Value());
455 :
456 0 : f_type_of_result = static_cast<type_of_result_t>(flags & 15);
457 0 : f_blocking = (flags & 0x10) != 0;
458 0 : f_clear_cluster_description = (flags & 0x400) != 0;
459 :
460 : // get the consistency level
461 : //
462 0 : f_consistency_level = static_cast<consistency_level_t>(decoder.signedCharValue());
463 :
464 : // get the CQL string (expected to be in UTF-8)
465 : //
466 0 : f_cql = decoder.p16StringValue();
467 :
468 : // if the timestamp was included, read it
469 : //
470 0 : if((flags & 0x20) != 0)
471 : {
472 0 : f_timestamp = decoder.int64Value();
473 : }
474 : else
475 : {
476 : // not included means we do not need it, i.e. zero
477 0 : f_timestamp = 0;
478 : }
479 :
480 : // if the timeout was included, read it
481 : //
482 0 : if((flags & 0x40) != 0)
483 : {
484 0 : f_timeout_ms = decoder.int32Value();
485 : }
486 : else
487 : {
488 : // not included means we do not need it, i.e. zero
489 0 : f_timeout_ms = 0;
490 : }
491 :
492 : // if the paging size was included, read it
493 : //
494 0 : if((flags & 0x80) != 0)
495 : {
496 0 : f_column_count = decoder.signedCharValue();
497 : }
498 : else
499 : {
500 : // not included means we do not need it, i.e. one
501 0 : f_column_count = 1;
502 : }
503 :
504 : // if the paging size was included, read it
505 : //
506 0 : if((flags & 0x100) != 0)
507 : {
508 0 : f_paging_size = decoder.int32Value();
509 : }
510 : else
511 : {
512 : // not included means we do not need it, i.e. zero
513 0 : f_paging_size = 0;
514 : }
515 :
516 : // if the cursor index was included, read it
517 : //
518 0 : if((flags & 0x200) != 0)
519 : {
520 0 : f_cursor_index = static_cast<int32_t>(decoder.uint16Value());
521 : }
522 : else
523 : {
524 : // not included means we do not need it, i.e. -1
525 0 : f_cursor_index = -1;
526 : }
527 :
528 : // if the batch index was included, read it
529 : //
530 0 : if((flags & 0x800) != 0)
531 : {
532 0 : f_batch_index = static_cast<int32_t>(decoder.uint16Value());
533 : }
534 : else
535 : {
536 : // not included means we do not need it, i.e. -1
537 0 : f_batch_index = -1;
538 : }
539 :
540 : // read the number of parameters that were included
541 : // this may be zero
542 : //
543 0 : size_t const param_count(decoder.uint16Value());
544 0 : for(size_t idx(0); idx < param_count; ++idx)
545 : {
546 : // read this parameter data and immediately push it in the
547 : // list of parameters; the binaryValue() function knows
548 : // to read the size first
549 : //
550 0 : f_parameter.push_back(decoder.binaryValue());
551 : }
552 : }
553 0 : catch( std::exception const & x )
554 : {
555 0 : SNAP_LOG_ERROR("decodeOrder(): exception! what=")(x.what());
556 0 : throw;
557 : }
558 0 : catch( ... )
559 : {
560 0 : SNAP_LOG_ERROR("unknown exception caught!");
561 0 : throw;
562 : }
563 :
564 0 : return true;
565 : }
566 :
567 :
568 :
569 6 : } // namespace libdbproxy
570 : // vim: ts=4 sw=4 et
|