docs: public final void add(VoltTableRow row) {
          assert(verifyTableInvariants());
          if (m_readOnly) {
              throw new IllegalStateException("Table is read-only. Make a copy before changing.");
          }
          if (m_colCount == 0) {
              throw new IllegalStateException("Table has no columns defined");
          }
          if (row.getColumnCount() != m_colCount) {
              throw new IllegalArgumentException(row.getColumnCount() + " arguments but table has " + m_colCount + " columns");
          }
  
          // memoize the start of this row in case we roll back
          final int pos = m_buffer.position();
  
          try {
              // Allow the buffer to grow to max capacity
              m_buffer.limit(m_buffer.capacity());
  
              byte[] inboundSchemaString = row.getSchemaString();
              byte[] mySchemaString = getSchemaString();
  
              // The way this works is that when two schema strings are found to have
              // the same value, the target table's reference is pointed at the source
              // table's reference. This allows the copying of multiple rows from one
              // table to another to only do a deep comparison once, and to do reference
              // equivalence checks for subsequent rows.
              boolean canDoRawCopy = (inboundSchemaString == mySchemaString) ||
                      Arrays.equals(inboundSchemaString, mySchemaString);
  
              if (canDoRawCopy) {
                  // make them the same object if equal for faster comparison next time
                  m_schemaString = inboundSchemaString;
  
                  // raw blit the row (assume the row is valid with proper length)
                  ByteBuffer rawRow = row.getRawRow();
                  m_buffer.put(rawRow);
              }
              else {
                  // advance the row size value
                  m_buffer.position(pos + 4);
  
                  for (int i = 0; i < m_colCount; i++) {
                      VoltType inboundType = row.getColumnType(i);
                      VoltType outboundType = getColumnType(i);
  
                      if (inboundType == outboundType) {
                          byte[] raw = row.getRaw(i);
                          m_buffer.put(raw);
                      }
                      else {
                          Object inboundValue = row.get(i, inboundType);
                          addColumnValue(inboundValue, outboundType, i);
                      }
                  }
  
                  final int rowsize = m_buffer.position() - pos - 4;
                  assert(rowsize >= 0);
  
                  // check for too big rows
                  if (rowsize > VoltTableRow.MAX_TUPLE_LENGTH) {
                      throw new VoltOverflowException(
                              "Table row total length larger than allowed max " + VoltTableRow.MAX_TUPLE_LENGTH_STR);
                  }
  
                  // buffer overflow is caught and handled below.
                  m_buffer.putInt(pos, rowsize);
              }
  
              // constrain buffer limit back to the new position
              m_buffer.limit(m_buffer.position());
  
              // increment the rowcount in the member var and in the buffer
              m_rowCount++;
              m_buffer.putInt(m_rowStart, m_rowCount);
          }
          catch (VoltTypeException vte) {
              // revert the row size advance and any other
              // buffer additions
              m_buffer.position(pos);
              throw vte;
          }
          catch (BufferOverflowException e) {
              m_buffer.position(pos);
              expandBuffer();
              add(row);
          }
          // row was too big, reset and rethrow
          catch (VoltOverflowException e) {
              m_buffer.position(pos);
              throw e;
          }
          catch (IllegalArgumentException e) {
              // if this was thrown because of a lack of space
              // then grow the buffer
              // the number 32 was picked out of a hat ( maybe a bug if str > 32 )
              if (m_buffer.limit() - m_buffer.position() < 32) {
                  m_buffer.position(pos);
                  expandBuffer();
                  add(row);
              } else {
                  throw e;
              }
          }
          assert(verifyTableInvariants());
      }