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