/* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include <NdbDictionary.hpp>

/*
 * Move data between "compatible" tables.
 *
 * Uses batches of insert-into-target / delete-from-source.
 * Compared to copying alter table, the advantages are
 * 1) does not need double storage 2) can be restarted
 * after temporary failure.
 *
 * Use init() to define source / target and then move_data().
 * Methods return -1 on error and 0 on success.  On temporary
 * error call move_data() again to continue.
 *
 * Use get_error() for details.  Negative error code means
 * non-recoverable error.  Positive error code is ndb error
 * which may be temporary.
 *
 * Like ndb_restore, remaps columns based on name.
 *
 * Used from ndb_restore for char<->text conversion.  Here
 * user pre-creates the new table and then loads data into it
 * via ndb_restore -r.  This first loads data into a temporary
 * "staging table" which has same structure as table in backup.
 * Then move_data() is called to move data into the new table.
 *
 * Current version handles data conversions between all char,
 * binary, text, blob types.
 *
 * The conversion methods should be unified with ndb_restore.
 * Missing cases (date and numeric types) should be added.
 */

struct Ndb_move_data {
  struct Opts {
    enum {
      MD_ABORT_ON_ERROR = 0x1,
      MD_EXCLUDE_MISSING_COLUMNS = 0x2,
      MD_ATTRIBUTE_PROMOTION = 0x4,
      MD_ATTRIBUTE_DEMOTION = 0x8
    };
    int flags;
    // for parsing --staging-tries (but caller handles retries)
    struct Tries {
      int maxtries; // 0 = no limit
      int mindelay;
      int maxdelay;
      Tries() : maxtries(0), mindelay(1000), maxdelay(60*1000) {}
    };
    Opts();
  };
  struct Stat {
    Uint64 rows_moved; // in current move_data() call
    Uint64 rows_total; // total moved so far
    Uint64 truncated; // truncated attributes so far
    Stat();
  };
  struct Error {
    enum {
      InvalidState = -101,
      InvalidSource = -102,
      InvalidTarget = -103,
      UnsupportedConversion = -201,
      NoExcludeMissingFlag = -202,
      NoPromotionFlag = -203,
      NoDemotionFlag = -204,
      DataTruncated = -301
    };
    int line;
    int code;
    char message[512];
    NdbError ndberror; // valid if code > 0
    bool is_temporary() const;
    Error();
  };
  Ndb_move_data();
  ~Ndb_move_data();
  int init(const NdbDictionary::Table* source,
           const NdbDictionary::Table* target);
  void set_opts_flags(int flags);
  static void unparse_opts_tries(char* opt, const Opts::Tries& ot);
  static int parse_opts_tries(const char* opt, Opts::Tries& retry);
  int move_data(Ndb* ndb);
  void error_insert(); // insert random temporary error
  const Stat& get_stat();
  const Error& get_error();

private:
  const NdbDictionary::Table* m_source; // source rows moved from
  const NdbDictionary::Table* m_target; // target rows moved to
  struct Attr {
    enum {
      TypeNone = 0,
      TypeArray,
      TypeBlob,
      TypeOther
    };
    const NdbDictionary::Column* column;
    const char* name;
    int id; // own id (array index)
    int map_id; // column id in other table
    int type;
    Uint32 size_in_bytes;
    Uint32 length_bytes;
    Uint32 data_size; // size_in_bytes - length_bytes
    Uint32 tiny_bytes;
    int pad_char;
    bool equal; // attr1,attr2 equal non-blobs
    Attr();
  };
  Attr* m_sourceattr;
  Attr* m_targetattr;
  void set_type(Attr& attr, const NdbDictionary::Column* c);
  Uint32 calc_str_len_truncated(CHARSET_INFO *cs, const char *data, uint32 maxlen);
  int check_nopk(const Attr& attr1, const Attr& attr2);
  int check_promotion(const Attr& attr1, const Attr& attr2);
  int check_demotion(const Attr& attr1, const Attr& attr2);
  int check_sizes(const Attr& attr1, const Attr& attr2);
  int check_unsupported(const Attr& attr1, const Attr& attr2);
  int check_tables();
  struct Data {
    char* data;
    Data* next;
    Data();
    ~Data();
  };
  Data* m_data; // blob data for the batch, used for blob2blob
  char* alloc_data(Uint32 n);
  void release_data();
  struct Op {
    Ndb* ndb;
    NdbTransaction* scantrans;
    NdbScanOperation* scanop;
    NdbTransaction* updatetrans;
    NdbOperation* updateop;
    union Value {
      NdbRecAttr* ra;
      NdbBlob* bh;
    };
    Value* values;
    int buflen;
    char* buf1;
    char* buf2;
    Uint32 rows_in_batch;
    Uint32 truncated_in_batch;
    bool end_of_scan;
    Op();
    ~Op();
  };
  Op m_op;
  int start_scan();
  int copy_other_to_other(const Attr& attr1, const Attr& attr2);
  int copy_data_to_array(const char* data1, const Attr& attr2,
                         Uint32 length1, Uint32 length1x);
  int copy_array_to_array(const Attr& attr1, const Attr& attr2);
  int copy_array_to_blob(const Attr& attr1, const Attr& attr2);
  int copy_blob_to_array(const Attr& attr1, const Attr& attr2);
  int copy_blob_to_blob(const Attr& attr1, const Attr& attr2);
  int copy_attr(const Attr& attr1, const Attr& attr2);
  int move_row();
  int move_batch();
  void close_op(Ndb* ndb, int ret);
  Opts m_opts;
  Stat m_stat;
  Error m_error;
  void set_error_line(int line);
  void set_error_code(int code, const char* fmt, ...);
  void set_error_code(const NdbError& ndberror);
  void reset_error();
  friend class NdbOut& operator<<(NdbOut&, const Error&);
  bool m_error_insert;
  void invoke_error_insert();
  void abort_on_error();
};
