1 /*
2 * Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2.0,
6 * as published by the Free Software Foundation.
7 *
8 * This program is also distributed with certain software (including
9 * but not limited to OpenSSL) that is licensed under separate terms,
10 * as designated in a particular file or component or in included license
11 * documentation. The authors of MySQL hereby grant you an additional
12 * permission to link the program and your derivative works with the
13 * separately licensed software that they have included with MySQL.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License, version 2.0, for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23 * 02110-1301 USA
24 */
25
26 // Avoid warnings from includes of other project and protobuf
27 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
28 #pragma GCC diagnostic push
29 #pragma GCC diagnostic ignored "-Wshadow"
30 #pragma GCC diagnostic ignored "-Wunused-parameter"
31 #elif defined _MSC_VER
32 #pragma warning (push)
33 #pragma warning (disable : 4018 4996)
34 #endif
35
36 #include "ngs_common/protocol_protobuf.h"
37 #include "mysqlx_resultset.h"
38 #include "mysqlx_protocol.h"
39 #include "mysqlx_row.h"
40 #include "mysqlx_error.h"
41
42 #include "my_config.h"
43
44 #include "ngs_common/bind.h"
45 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
46 #pragma GCC diagnostic pop
47 #elif defined _MSC_VER
48 #pragma warning (pop)
49 #endif
50
51 #ifdef WIN32
52 #pragma warning(push, 0)
53 #endif
54 #ifdef WIN32
55 #pragma warning(pop)
56 #endif
57 #include <string>
58 #include <iostream>
59 #include <limits>
60 #include "ngs_common/xdecimal.h"
61
62 #ifdef WIN32
63 # pragma push_macro("ERROR")
64 # undef ERROR
65 #endif
66
67
68 using namespace mysqlx;
69
throw_server_error(const Mysqlx::Error & error)70 static void throw_server_error(const Mysqlx::Error &error)
71 {
72 throw Error(error.code(), error.msg());
73 }
74
Result(ngs::shared_ptr<XProtocol> owner,bool expect_data,bool expect_ok)75 Result::Result(ngs::shared_ptr<XProtocol>owner, bool expect_data, bool expect_ok)
76 : current_message(NULL), m_owner(owner), m_last_insert_id(-1), m_affected_rows(-1),
77 m_result_index(0), m_state(expect_data ? ReadMetadataI : expect_ok ? ReadStmtOkI : ReadDone), m_buffered(false), m_buffering(false)
78 {
79 }
80
Result()81 Result::Result()
82 : current_message(NULL), m_state(ReadDone), m_buffered(false), m_buffering(false)
83 {
84 }
85
~Result()86 Result::~Result()
87 {
88 // flush the resultset from the pipe
89 while (m_state != ReadError && m_state != ReadDone)
90 nextDataSet();
91
92 delete current_message;
93 }
94
columnMetadata()95 ngs::shared_ptr<std::vector<ColumnMetadata> > Result::columnMetadata()
96 {
97 // If cached, works with the cache data
98 if (m_buffered)
99 return m_current_result->columnMetadata();
100 else
101 {
102 if (m_state == ReadMetadataI)
103 read_metadata();
104 }
105 return m_columns;
106 }
107
ready()108 bool Result::ready()
109 {
110 // if we've read something (ie not on initial state), then we're ready
111 return m_state != ReadMetadataI && m_state != ReadStmtOkI;
112 }
113
wait()114 void Result::wait()
115 {
116 if (m_state == ReadMetadataI)
117 read_metadata();
118 if (m_state == ReadStmtOkI)
119 read_stmt_ok();
120 }
121
mark_error()122 void Result::mark_error()
123 {
124 m_state = ReadError;
125 }
126
handle_notice(int32_t type,const std::string & data)127 bool Result::handle_notice(int32_t type, const std::string &data)
128 {
129 switch (type)
130 {
131 case 1: // warning
132 {
133 Mysqlx::Notice::Warning warning;
134 warning.ParseFromString(data);
135 if (!warning.IsInitialized())
136 std::cerr << "Invalid notice received from server " << warning.InitializationErrorString() << "\n";
137 else
138 {
139 Warning w;
140 w.code = warning.code();
141 w.text = warning.msg();
142 w.is_note = warning.level() == Mysqlx::Notice::Warning::NOTE;
143 m_warnings.push_back(w);
144 }
145 return true;
146 }
147
148 case 2: // session variable changed
149 break;
150
151 case 3: //session state changed
152 {
153 Mysqlx::Notice::SessionStateChanged change;
154 change.ParseFromString(data);
155 if (!change.IsInitialized())
156 std::cerr << "Invalid notice received from server " << change.InitializationErrorString() << "\n";
157 else
158 {
159 switch (change.param())
160 {
161 case Mysqlx::Notice::SessionStateChanged::GENERATED_INSERT_ID:
162 if (change.value().type() == Mysqlx::Datatypes::Scalar::V_UINT)
163 m_last_insert_id = change.value().v_unsigned_int();
164 else
165 std::cerr << "Invalid notice value received from server: " << data << "\n";
166 break;
167
168 case Mysqlx::Notice::SessionStateChanged::ROWS_AFFECTED:
169 if (change.value().type() == Mysqlx::Datatypes::Scalar::V_UINT)
170 m_affected_rows = change.value().v_unsigned_int();
171 else
172 std::cerr << "Invalid notice value received from server: " << data << "\n";
173 break;
174
175 case Mysqlx::Notice::SessionStateChanged::PRODUCED_MESSAGE:
176 if (change.value().type() == Mysqlx::Datatypes::Scalar::V_STRING)
177 m_info_message = change.value().v_string().value();
178 else
179 std::cerr << "Invalid notice value received from server: " << data << "\n";
180 break;
181
182 default:
183 return false;
184 }
185 }
186 return true;
187 }
188 default:
189 std::cerr << "Unexpected notice type received " << type << "\n";
190 return false;
191 }
192 return false;
193 }
194
get_message_id()195 int Result::get_message_id()
196 {
197 if (NULL != current_message)
198 {
199 return current_message_id;
200 }
201
202 ngs::shared_ptr<XProtocol>owner = m_owner.lock();
203
204 if (owner)
205 {
206 owner->push_local_notice_handler(ngs::bind(&Result::handle_notice, this, ngs::placeholders::_1, ngs::placeholders::_2));
207
208 try
209 {
210 current_message = owner->recv_next(current_message_id);
211 }
212 catch (...)
213 {
214 m_state = ReadError;
215 owner->pop_local_notice_handler();
216 throw;
217 }
218
219 owner->pop_local_notice_handler();
220 }
221
222 // error messages that can be received in any state
223 if (current_message_id == Mysqlx::ServerMessages::ERROR)
224 {
225 m_state = ReadError;
226 throw_server_error(static_cast<const Mysqlx::Error&>(*current_message));
227 }
228
229 switch (m_state)
230 {
231 case ReadMetadataI:
232 {
233 switch (current_message_id)
234 {
235 case Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK:
236 m_state = ReadDone;
237 return current_message_id;
238
239 case Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA:
240 m_state = ReadMetadata;
241 return current_message_id;
242 }
243 break;
244 }
245 case ReadMetadata:
246 {
247 // while reading metadata, we can either get more metadata
248 // start getting rows (which also signals end of metadata)
249 // or EORows, which signals end of metadata AND empty resultset
250 switch (current_message_id)
251 {
252 case Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA:
253 m_state = ReadMetadata;
254 return current_message_id;
255
256 case Mysqlx::ServerMessages::RESULTSET_ROW:
257 m_state = ReadRows;
258 return current_message_id;
259
260 case Mysqlx::ServerMessages::RESULTSET_FETCH_DONE:
261 // empty resultset
262 m_state = ReadStmtOk;
263 return current_message_id;
264 }
265 break;
266 }
267 case ReadRows:
268 {
269 switch (current_message_id)
270 {
271 case Mysqlx::ServerMessages::RESULTSET_ROW:
272 return current_message_id;
273
274 case Mysqlx::ServerMessages::RESULTSET_FETCH_DONE:
275 m_state = ReadStmtOk;
276 return current_message_id;
277
278 case Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS:
279 m_state = ReadMetadata;
280 return current_message_id;
281 }
282 break;
283 }
284 case ReadStmtOkI:
285 case ReadStmtOk:
286 {
287 switch (current_message_id)
288 {
289 case Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK:
290 m_state = ReadDone;
291 return current_message_id;
292 }
293 break;
294 }
295 case ReadError:
296 case ReadDone:
297 // not supposed to reach here
298 throw std::logic_error("attempt to read data at wrong time");
299 }
300
301 if (getenv("MYSQLX_DEBUG"))
302 {
303 std::string out;
304 google::protobuf::TextFormat::PrintToString(*current_message, &out);
305 std::cout << out << "\n";
306 }
307 m_state = ReadError;
308 throw Error(CR_COMMANDS_OUT_OF_SYNC, "Unexpected message received from server reading results");
309 }
310
pop_message()311 mysqlx::Message* Result::pop_message()
312 {
313 mysqlx::Message *result = current_message;
314
315 current_message = NULL;
316
317 return result;
318 }
319
lastDocumentId()320 std::string Result::lastDocumentId()
321 {
322 // Last document id is only available on collection add operations
323 // and only if a single document is added (MY-139 Spec, Req 4, 6)
324 if (!m_has_doc_ids || m_last_document_ids.size() != 1)
325 throw std::logic_error("document id is not available.");
326
327 return m_last_document_ids.at(0);
328 }
329
lastDocumentIds()330 const std::vector<std::string>& Result::lastDocumentIds()
331 {
332 // Last document ids are available on any collection add operation (MY-139 Spec, Req 1-5)
333 if (!m_has_doc_ids)
334 throw std::logic_error("document ids are not available.");
335
336 return m_last_document_ids;
337 }
338
setLastDocumentIDs(const std::vector<std::string> & document_ids)339 void Result::setLastDocumentIDs(const std::vector<std::string>& document_ids)
340 {
341 m_has_doc_ids = true;
342 m_last_document_ids.reserve(document_ids.size());
343 std::copy(document_ids.begin(), document_ids.end(), std::back_inserter(m_last_document_ids));
344 }
345
unwrap_column_metadata(const Mysqlx::Resultset::ColumnMetaData & column_data)346 static ColumnMetadata unwrap_column_metadata(const Mysqlx::Resultset::ColumnMetaData &column_data)
347 {
348 ColumnMetadata column;
349
350 switch (column_data.type())
351 {
352 case Mysqlx::Resultset::ColumnMetaData::SINT:
353 column.type = mysqlx::SINT;
354 break;
355 case Mysqlx::Resultset::ColumnMetaData::UINT:
356 column.type = mysqlx::UINT;
357 break;
358 case Mysqlx::Resultset::ColumnMetaData::DOUBLE:
359 column.type = mysqlx::DOUBLE;
360 break;
361 case Mysqlx::Resultset::ColumnMetaData::FLOAT:
362 column.type = mysqlx::FLOAT;
363 break;
364 case Mysqlx::Resultset::ColumnMetaData::BYTES:
365 column.type = mysqlx::BYTES;
366 break;
367 case Mysqlx::Resultset::ColumnMetaData::TIME:
368 column.type = mysqlx::TIME;
369 break;
370 case Mysqlx::Resultset::ColumnMetaData::DATETIME:
371 column.type = mysqlx::DATETIME;
372 break;
373 case Mysqlx::Resultset::ColumnMetaData::SET:
374 column.type = mysqlx::SET;
375 break;
376 case Mysqlx::Resultset::ColumnMetaData::ENUM:
377 column.type = mysqlx::ENUM;
378 break;
379 case Mysqlx::Resultset::ColumnMetaData::BIT:
380 column.type = mysqlx::BIT;
381 break;
382 case Mysqlx::Resultset::ColumnMetaData::DECIMAL:
383 column.type = mysqlx::DECIMAL;
384 break;
385 }
386 column.name = column_data.name();
387 column.original_name = column_data.original_name();
388
389 column.table = column_data.table();
390 column.original_table = column_data.original_table();
391
392 column.schema = column_data.schema();
393 column.catalog = column_data.catalog();
394
395 column.collation = column_data.has_collation() ? column_data.collation() : 0;
396
397 column.fractional_digits = column_data.fractional_digits();
398
399 column.length = column_data.length();
400
401 column.flags = column_data.flags();
402 column.content_type = column_data.content_type();
403 return column;
404 }
405
read_metadata()406 void Result::read_metadata()
407 {
408 if (m_state != ReadMetadata && m_state != ReadMetadataI)
409 throw std::logic_error("read_metadata() called at wrong time");
410
411 // msgs we can get in this state:
412 // CURSOR_OK
413 // META_DATA
414
415 int msgid = -1;
416 m_columns.reset(new std::vector<ColumnMetadata>());
417 while (m_state == ReadMetadata || m_state == ReadMetadataI)
418 {
419 if (-1 != msgid)
420 {
421 delete pop_message();
422 }
423
424 msgid = get_message_id();
425
426 if (msgid == Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA)
427 {
428 msgid = -1;
429 ngs::unique_ptr<Mysqlx::Resultset::ColumnMetaData> column_data(static_cast<Mysqlx::Resultset::ColumnMetaData*>(pop_message()));
430
431 m_columns->push_back(unwrap_column_metadata(*column_data));
432 }
433 }
434 }
435
read_row()436 ngs::shared_ptr<Row> Result::read_row()
437 {
438 ngs::shared_ptr<Row> ret_val;
439
440 if (m_state != ReadRows)
441 throw std::logic_error("read_row() called at wrong time");
442
443 // msgs we can get in this state:
444 // RESULTSET_ROW
445 // RESULTSET_FETCH_DONE
446 // RESULTSET_FETCH_DONE_MORE_RESULTSETS
447 int mid = get_message_id();
448
449 if (mid == Mysqlx::ServerMessages::RESULTSET_ROW)
450 {
451 ret_val.reset(new Row(m_columns, static_cast<Mysqlx::Resultset::Row*>(pop_message())));
452
453 // If caching adds it to the cache instead
454 if (m_buffering)
455 m_current_result->add_row(ret_val);
456 }
457
458 return ret_val;
459 }
460
read_stmt_ok()461 void Result::read_stmt_ok()
462 {
463 if (m_state != ReadStmtOk && m_state != ReadStmtOkI)
464 throw std::logic_error("read_stmt_ok() called at wrong time");
465
466 // msgs we can get in this state:
467 // STMT_EXEC_OK
468
469 if (Mysqlx::ServerMessages::RESULTSET_FETCH_DONE == get_message_id())
470 delete pop_message();
471
472 if (Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK != get_message_id())
473 throw std::runtime_error("Unexpected message id");
474
475 ngs::unique_ptr<mysqlx::Message> msg(pop_message());
476 }
477
rewind()478 bool Result::rewind()
479 {
480 bool ret_val = false;
481 if (m_buffered)
482 {
483 for (m_result_index = 0; m_result_index < m_result_cache.size(); m_result_index++)
484 m_result_cache[m_result_index]->rewind();
485
486 m_result_index = 0;
487 nextDataSet();
488
489 ret_val = true;
490 }
491
492 return ret_val;
493 }
494
tell(size_t & dataset,size_t & record)495 bool Result::tell(size_t &dataset, size_t&record)
496 {
497 bool ret_val = false;
498
499 if (m_buffered && m_current_result)
500 {
501 dataset = m_result_index;
502 m_current_result->tell(record);
503 ret_val = true;
504 }
505
506 return ret_val;
507 }
508
seek(size_t dataset,size_t record)509 bool Result::seek(size_t dataset, size_t record)
510 {
511 bool ret_val = false;
512
513 if (m_buffered)
514 {
515 rewind();
516
517 while (dataset < m_result_index)
518 nextDataSet();
519
520 m_current_result->seek(record);
521
522 ret_val = true;
523 }
524
525 return ret_val;
526 }
527
has_data()528 bool Result::has_data()
529 {
530 bool ret_val = false;
531
532 if (m_buffered)
533 ret_val = m_current_result->columnMetadata() && m_current_result->columnMetadata()->size() > 0;
534 else
535 ret_val = m_columns && m_columns->size() > 0;
536
537 return ret_val;
538 }
539
nextDataSet()540 bool Result::nextDataSet()
541 {
542 if (m_buffered)
543 {
544 if (m_result_index < m_result_cache.size())
545 m_current_result = m_result_cache[m_result_index++];
546 else
547 m_current_result.reset();
548
549 return m_current_result ? true : false;
550 }
551 else
552 {
553 // flush left over rows
554 while (m_state == ReadRows)
555 read_row();
556
557 if (m_state == ReadMetadata)
558 {
559 read_metadata();
560 if (m_state == ReadRows)
561 {
562 // If caching adds this new resultset to the cache
563 if (m_buffering)
564 {
565 m_current_result.reset(new ResultData(m_columns));
566 m_result_cache.push_back(m_current_result);
567 }
568 return true;
569 }
570 }
571 if (m_state == ReadStmtOk)
572 read_stmt_ok();
573 }
574 return false;
575 }
576
next()577 ngs::shared_ptr<Row> Result::next()
578 {
579 ngs::shared_ptr<Row> ret_val;
580
581 if (m_buffered)
582 ret_val = m_current_result->next();
583 else
584 {
585 if (!ready())
586 wait();
587
588 if (m_state == ReadStmtOk)
589 read_stmt_ok();
590
591 if (m_state != ReadDone)
592 {
593 ret_val = read_row();
594
595 if (m_state == ReadStmtOk)
596 read_stmt_ok();
597 }
598 }
599
600 return ret_val;
601 }
602
603 // Flush will read all the messages from the IO
604 // If caching is enabled the data will be cached, if not
605 // it will be just discarded
flush()606 void Result::flush()
607 {
608 // Flushes the leftover data only if it was not previously cached
609 wait();
610 while (nextDataSet());
611 }
612
buffer()613 Result& Result::buffer()
614 {
615 if (!ready())
616 wait();
617
618 // The buffer makes sense ONLY if there's something else
619 // to be buffered
620 if (m_state != ReadDone)
621 {
622 m_buffering = true;
623
624 // This will enable data caching
625 m_current_result.reset(new ResultData(m_columns));
626 m_result_cache.push_back(m_current_result);
627
628 // This will actually cache the data
629 while (nextDataSet())
630 ;
631
632 m_buffering = false;
633 m_buffered = true;
634
635 m_result_index = 1;
636 }
637
638 return *this;
639 }
640
ResultData(ngs::shared_ptr<std::vector<ColumnMetadata>> columns)641 ResultData::ResultData(ngs::shared_ptr<std::vector<ColumnMetadata> > columns) :
642 m_columns(columns), m_row_index(0)
643 {
644 }
645
add_row(ngs::shared_ptr<Row> row)646 void ResultData::add_row(ngs::shared_ptr<Row> row)
647 {
648 m_rows.push_back(row);
649 }
650
next()651 ngs::shared_ptr<Row> ResultData::next()
652 {
653 ngs::shared_ptr<Row> ret_val;
654
655 if (m_row_index < m_rows.size())
656 ret_val = m_rows[m_row_index++];
657
658 return ret_val;
659 }
660
rewind()661 void ResultData::rewind()
662 {
663 m_row_index = 0;
664 }
665
tell(size_t & record)666 void ResultData::tell(size_t &record)
667 {
668 record = m_row_index;
669 }
670
seek(size_t record)671 void ResultData::seek(size_t record)
672 {
673 m_row_index = m_rows.size();
674
675 if (record < m_row_index)
676 m_row_index = record;
677 }
678
Row(ngs::shared_ptr<std::vector<ColumnMetadata>> columns,Mysqlx::Resultset::Row * data)679 Row::Row(ngs::shared_ptr<std::vector<ColumnMetadata> > columns, Mysqlx::Resultset::Row *data)
680 : m_columns(columns), m_data(data)
681 {
682 }
683
~Row()684 Row::~Row()
685 {
686 delete m_data;
687 }
688
check_field(int field,FieldType type) const689 void Row::check_field(int field, FieldType type) const
690 {
691 if (field < 0 || field >= (int)m_columns->size())
692 throw std::range_error("invalid field index");
693
694 if (m_columns->at(field).type != type)
695 throw std::range_error("invalid field type");
696 }
697
isNullField(int field) const698 bool Row::isNullField(int field) const
699 {
700 if (field < 0 || field >= (int)m_columns->size())
701 throw std::range_error("invalid field index");
702
703 if (m_data->field(field).empty())
704 return true;
705 return false;
706 }
707
sIntField(int field) const708 int32_t Row::sIntField(int field) const
709 {
710 int64_t t = sInt64Field(field);
711 if (t > std::numeric_limits<int32_t>::max() || t < std::numeric_limits<int32_t>::min())
712 throw std::invalid_argument("field of wrong type");
713
714 return (int32_t)t;
715 }
716
uIntField(int field) const717 uint32_t Row::uIntField(int field) const
718 {
719 uint64_t t = uInt64Field(field);
720 if (t > std::numeric_limits<uint32_t>::max())
721 throw std::invalid_argument("field of wrong type");
722
723 return (uint32_t)t;
724 }
725
sInt64Field(int field) const726 int64_t Row::sInt64Field(int field) const
727 {
728 check_field(field, SINT);
729 const std::string& field_val = m_data->field(field);
730
731 return Row_decoder::s64_from_buffer(field_val);
732 }
733
uInt64Field(int field) const734 uint64_t Row::uInt64Field(int field) const
735 {
736 check_field(field, UINT);
737 const std::string& field_val = m_data->field(field);
738
739 return Row_decoder::u64_from_buffer(field_val);
740 }
741
bitField(int field) const742 uint64_t Row::bitField(int field) const
743 {
744 check_field(field, BIT);
745 const std::string& field_val = m_data->field(field);
746
747 return Row_decoder::u64_from_buffer(field_val);
748 }
749
stringField(int field) const750 std::string Row::stringField(int field) const
751 {
752 size_t length;
753 check_field(field, BYTES);
754
755 const std::string& field_val = m_data->field(field);
756
757 const char* res = Row_decoder::string_from_buffer(field_val, length);
758 return std::string(res, length);
759 }
760
decimalField(int field) const761 std::string Row::decimalField(int field) const
762 {
763 check_field(field, DECIMAL);
764
765 const std::string& field_val = m_data->field(field);
766
767 mysqlx::Decimal decimal = Row_decoder::decimal_from_buffer(field_val);
768
769 return std::string(decimal.str());
770 }
771
setFieldStr(int field) const772 std::string Row::setFieldStr(int field) const
773 {
774 check_field(field, SET);
775
776 const std::string& field_val = m_data->field(field);
777
778 return Row_decoder::set_from_buffer_as_str(field_val);
779 }
780
setField(int field) const781 std::set<std::string> Row::setField(int field) const
782 {
783 std::set<std::string> result;
784 check_field(field, SET);
785
786 const std::string& field_val = m_data->field(field);
787 Row_decoder::set_from_buffer(field_val, result);
788
789 return result;
790 }
791
enumField(int field) const792 std::string Row::enumField(int field) const
793 {
794 size_t length;
795 check_field(field, ENUM);
796
797 const std::string& field_val = m_data->field(field);
798
799 const char* res = Row_decoder::string_from_buffer(field_val, length);
800 return std::string(res, length);
801 }
802
stringField(int field,size_t & rlength) const803 const char *Row::stringField(int field, size_t &rlength) const
804 {
805 check_field(field, BYTES);
806
807 const std::string& field_val = m_data->field(field);
808
809 return Row_decoder::string_from_buffer(field_val, rlength);
810 }
811
floatField(int field) const812 float Row::floatField(int field) const
813 {
814 check_field(field, FLOAT);
815
816 const std::string& field_val = m_data->field(field);
817
818 return Row_decoder::float_from_buffer(field_val);
819 }
820
doubleField(int field) const821 double Row::doubleField(int field) const
822 {
823 check_field(field, DOUBLE);
824
825 const std::string& field_val = m_data->field(field);
826
827 return Row_decoder::double_from_buffer(field_val);
828 }
829
dateTimeField(int field) const830 DateTime Row::dateTimeField(int field) const
831 {
832 check_field(field, DATETIME);
833
834 const std::string& field_val = m_data->field(field);
835
836 return Row_decoder::datetime_from_buffer(field_val);
837 }
838
timeField(int field) const839 Time Row::timeField(int field) const
840 {
841 check_field(field, TIME);
842
843 const std::string& field_val = m_data->field(field);
844
845 return Row_decoder::time_from_buffer(field_val);
846 }
847
numFields() const848 int Row::numFields() const
849 {
850 return m_data->field_size();
851 }
852
853 #ifdef WIN32
854 # pragma pop_macro("ERROR")
855 #endif
856