text: //--------------------------------------------------------------------------------------------------------------------//
  //                                                                                                                    //
  //                                      Tuplex: Blazing Fast Python Data Science                                      //
  //                                                                                                                    //
  //                                                                                                                    //
  //  (c) 2017 - 2021, Tuplex team                                                                                      //
  //  Created by Leonhard Spiegelberg first on 1/1/2021                                                                 //
  //  License: Apache 2.0                                                                                               //
  //--------------------------------------------------------------------------------------------------------------------//
  
  #include "Row.h"
  
  namespace tuplex {
  
      Row Row::fromMemory(const Schema &schema, const void *ptr, size_t size) {
          Deserializer ds(schema);
          return fromMemory(ds, ptr, size);
      }
  
      Row Row::fromMemory(tuplex::Deserializer &ds, const void *ptr, size_t size) {
          assert(ptr);
          // size may be 0 when only zeros are deserialized.
  
          Row row;
          row._schema = ds.getSchema();
          ds.deserialize(ptr, size);
          // deserialize as Tuple structure & then unpack for easier access
          Tuple t = ds.getTuple();
          for(int i = 0; i < t.numElements(); i++)
              row._values.push_back(t.getField(i));
          // needs to be called after values are set!
          row._serializedLength = ds.rowSize();
          assert(ds.inferLength(ptr) == row._serializedLength);
          return row;
      }
  
      Row Row::fromExceptionMemory(const tuplex::Schema &schema, tuplex::ExceptionCode ec, const void *ptr,
                                   size_t size) {
          Deserializer ds(schema);
          return fromExceptionMemory(ds, ec, ptr, size);
      }
  
      Row Row::fromExceptionMemory(tuplex::Deserializer &ds, tuplex::ExceptionCode ec, const void *ptr, size_t size) {
          switch(ec) {
              case ExceptionCode::BADPARSE_STRING_INPUT: {
  
                  // get string length
                  int64_t size = *((const int64_t*)ptr);
                  auto str = (const char*)ptr + 8;
                  assert(size >= 1);
                  return Row(fromCharPointers(str, str + size - 1));
              }
              // @TODO: add here special code for null-value opt violation, i.e. efficient in-memory format is saved!
  
              default:
                  return fromMemory(ds, ptr, size);
          }
      }
  
      bool Row::getBoolean(const int col) const {
          return get(col).getInt() > 0;
      }
  
      int64_t Row::getInt(const int col) const {
          return get(col).getInt();
      }
  
      double Row::getDouble(const int col) const {
          return get(col).getDouble();
      }
  
      std::string Row::getString(const int col) const {
          return std::string((const char*)get(col).getPtr());
      }
  
      Tuple Row::getTuple(const int col) const {
          // copy constructor invoked to return deep copied tuple
          return Tuple(*((Tuple*)get(col).getPtr()));
      }
  
      Schema Row::getSchema() const {
          return _schema;
      }
  
      python::Type Row::getType(const int col) const {
          return _schema.getRowType().parameters()[col];
      }
  
      std::string Row::toPythonString() const {
          std::string s = "(";
          for(int i = 0; i < getNumColumns(); ++i) {
              s += _values[i].desc();
  
              if(i != getNumColumns() - 1)
                  s += ",";
          }
  
          // special case: single element tuple, add , so it becomes a tuple
          // and not a expression in parentheses
          if(getNumColumns() == 1)
              s +=",";
  
          s += ")";
  
          return s;
      }
  
      python::Type Row::getRowType() const {
          if(_values.empty())
              return python::Type::EMPTYTUPLE; // bad case!
  
          // get types of rows & return then tuple type
          std::vector<python::Type> types;
          for(const auto& el: _values)
              types.push_back(el.getType());
  
          return python::Type::makeTupleType(types);
      }
  
      size_t Row::serializeToMemory(uint8_t *buffer, const size_t capacity) const {
          return getSerializer().serialize(buffer, capacity);
      }
  
      size_t Row::getSerializedLength() const {
          return getSerializer().length();
      }
  
      Serializer Row::getSerializer() const {
          Serializer serializer;
  
          if(_values.empty()) {
              // basically serialize an empty tuple here
              serializer.append(Tuple()); // use {} here to make empty tuple. default constructor yields unknown
              return serializer;
          }
  
          for(const auto& el : _values) {
              if(python::Type::BOOLEAN == el.getType())
                  serializer.append(el.getInt() > 0);
              else if(python::Type::I64 == el.getType())
                  serializer.append(el.getInt());
              else if(python::Type::F64 == el.getType())
                  serializer.append(el.getDouble());
              else if(python::Type::STRING == el.getType())
                  serializer.append(std::string((char*)el.getPtr()));
              else if(python::Type::GENERICDICT == el.getType())
                  serializer.append(std::string((char*)el.getPtr()), python::Type::GENERICDICT);
              else if(el.getType().isDictionaryType())
                  serializer.append(std::string((char*)el.getPtr()), el.getType());
              else if(el.getType() == python::Type::NULLVALUE) {
                  serializer.appendNull();
              } else if(el.getType().isListType()) {
                  serializer.append(*(List*)el.getPtr());
              }
              else {
                  if(el.getType().isTupleType()) {
                      serializer.append(*((Tuple*)el.getPtr()));
                  } else if(el.getType().isOptionType()) {
  
                      // quick hack, all types again
                      auto rt = el.getType().getReturnType();
  
                      if(python::Type::BOOLEAN == rt)
                          serializer.append(el.isNull() ? option<bool>::none : option<bool>(el.getInt() > 0));
                      else if(python::Type::I64 == rt)
                          serializer.append(el.isNull() ? option<int64_t>::none : option<int64_t>(el.getInt()));
                      else if(python::Type::F64 == rt)
                          serializer.append(el.isNull() ? option<double>::none : option<double>(el.getDouble()));
                      else if(python::Type::STRING == rt)
                          serializer.append(el.isNull() ? option<std::string>::none : option<std::string>(std::string((const char*)el.getPtr())));
                      else if(python::Type::EMPTYTUPLE == rt) {
                          serializer.appendEmptyTupleOption(el);
                      }
                      else if(python::Type::GENERICDICT == rt)
                          serializer.append(el.isNull() ? option<std::string>::none : option<std::string>(std::string((const char*)el.getPtr())), python::Type::GENERICDICT);
                      else if(rt.isDictionaryType())
                          serializer.append(el.isNull() ? option<std::string>::none : option<std::string>(std::string((const char*)el.getPtr())), el.getType());
                      else if(rt.isListType())
                          serializer.append(el.isNull() ? option<List>::none : option<List>(*(List*)el.getPtr()), el.getType());
                      else if (rt.isTupleType()) {
                          if (el.isNull()) {
                              serializer.append(option<Tuple>::none, el.getType());
                          } else {
                              auto ptrToTuple = (Tuple*)el.getPtr();
                              assert(ptrToTuple);
                              serializer.append(option<Tuple>(*ptrToTuple), el.getType());
                          }
                      } else {
                          throw std::runtime_error("option underlying type " + rt.desc() + " not known");
                      }
                  } else if(el.getType() == python::Type::NULLVALUE) {
                      serializer.append(NullType());
                  } else if(el.getType() == python::Type::PYOBJECT) {
                      serializer.appendObject(static_cast<const uint8_t *>(el.getPtr()), el.getPtrSize());
                  } else {
                      throw std::runtime_error("unsupported type " + el.getType().desc() + " found");
                  }
              }
          }
          return serializer;
      }
  
      bool operator == (const Row& lhs, const Row& rhs) {
          // first check if number of values match
          if(lhs._values.size() != rhs._values.size())
              return false;
  
          // special case: empty rows
          if(lhs._values.size() == 0)
              return true;
  
          // check whether type matches
          if(lhs.getRowType() != rhs.getRowType())
              return false;
  
          // types match, so do now check contents
          auto numElements = lhs._values.size();
          for(unsigned i = 0; i < numElements; ++i) {
              if(lhs.get(i) != rhs.get(i))
                  return false;
          }
  
  
          return true;
      }
  
  
      Row Row::upcastedRow(const python::Type &targetType) const {
          using namespace std;
          assert(canUpcastToRowType(getRowType(), targetType));
  
          auto num_fields = _values.size();
          auto targetTypes = targetType.parameters();
          vector<Field> fields;
          for(unsigned i = 0; i < num_fields; ++i) {
              auto t = _values[i].getType();
              auto tt = targetTypes[i];
              if(t == tt) {
                  fields.push_back(_values[i]);
              } else {
                  // should be able to upcast!
                  assert(canUpcastType(t, tt));
                  fields.push_back(Field::upcastTo_unsafe(_values[i], tt));
              }
          }
  
          return Row::from_vector(fields);
      }
  
      Tuple Row::getAsTuple() const {
          return Tuple::from_vector(_values);
      }
  
  
      // print table based on rows and headers
      void printTable(std::ostream& os, const std::vector<std::string>& header,
                      const std::vector<Row>& rows, bool quoteStrings) {
          using namespace std;
  
          // retrieve max columns from headers and rows
          auto numColumns = header.size();
          for(const auto& row : rows)
              numColumns = std::max(numColumns, (size_t)row.getNumColumns());
  
  
          // get column width (maximum width within each column)
          std::vector<int> columnWidths(numColumns, 0);
          for (int i = 0; i < numColumns; ++i) {
              if (header[i].length() > columnWidths[i]) {
                  columnWidths[i] = getMaxLineLength(replaceLineBreaks(header[i]));
              }
          }
          for (const auto &row: rows) {
              std::vector<std::string> strs = row.getAsStrings();
  
              // remove '...' quotes
              if(!quoteStrings) {
                  for(int i = 0; i < row.getNumColumns(); ++i)
                      if(row.getType(i) == python::Type::STRING)
                          strs[i] = strs[i].substr(1, strs[i].length() - 2);
              }
  
              for (int i = 0; i < strs.size(); ++i) {
                  if (strs[i].length() > columnWidths[i]) {
                      columnWidths[i] = getMaxLineLength(replaceLineBreaks(strs[i]));
                  }
              }
          }
  
          // assert column widths are at least 1
          for (auto col_width : columnWidths)
              assert(col_width > 0);
  
          // print headers
          tuplex::helper::printSeparatingLine(os, columnWidths);
          tuplex::helper::printRow(os, columnWidths, header);
          tuplex::helper::printSeparatingLine(os, columnWidths);
  
          // print all rows
          for (const auto &row: rows) {
              std::vector<std::string> strs = row.getAsStrings();
  
              // remove '...' quotes
              if(!quoteStrings) {
                  for(int i = 0; i < row.getNumColumns(); ++i)
                      if(row.getType(i) == python::Type::STRING)
                          strs[i] = strs[i].substr(1, strs[i].length() - 2);
              }
  
              tuplex::helper::printRow(os, columnWidths, strs);
              tuplex::helper::printSeparatingLine(os, columnWidths);
          }
  
          os.flush();
      }
  }