/* Copyright (C) Olivier Bertrand 2004 - 2014

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

  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 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 */

/** @file ha_connect.h

    @brief
  The ha_connect engine is a prototype storage engine to access external data.

   @see
  /sql/handler.h and /storage/connect/ha_connect.cc
*/

#ifdef USE_PRAGMA_INTERFACE
#pragma interface     /* gcc class implementation */
#endif

static char *strz(PGLOBAL g, LEX_STRING &ls);

/****************************************************************************/
/*  Structures used to pass info between CONNECT and ha_connect.            */
/****************************************************************************/
typedef struct _create_xinfo {
  char *Type;                               /* Retrieved from table comment */
  char *Filename;                           /* Set if not standard          */
  char *IndexFN;                            /* Set if not standard          */
  ulonglong Maxrows;                        /* Estimated max nb of rows     */
  ulong Lrecl;                              /* Set if not default           */
  ulong Elements;                           /* Number of lines in blocks    */
  bool  Fixed;                              /* False for DOS type           */
  void *Pcf;                                /* To list of columns           */
  void *Pxdf;                               /* To list of indexes           */
} CRXINFO, *PCXF;

typedef struct _xinfo {
  ulonglong data_file_length;               /* Length of data file          */
  ha_rows   records;                        /* Records in table             */
  ulong     mean_rec_length;                /* Physical record length       */
  char     *data_file_name;                 /* Physical file name           */
} XINFO, *PXF;

class XCHK : public BLOCK {
public:
  XCHK(void) {oldsep= newsep= false;
              oldopn= newopn= NULL;
              oldpix= newpix= NULL;}

  inline char *SetName(PGLOBAL g, char *name) {
    char *nm= NULL;
    if (name) {nm= (char*)PlugSubAlloc(g, NULL, strlen(name) + 1);
               strcpy(nm, name);}
    return nm;}

  bool         oldsep;              // Sepindex before create/alter
  bool         newsep;              // Sepindex after create/alter
  char        *oldopn;              // Optname before create/alter
  char        *newopn;              // Optname after create/alter
  PIXDEF       oldpix;              // The indexes before create/alter
  PIXDEF       newpix;              // The indexes after create/alter
}; // end of class XCHK

typedef class XCHK *PCHK;
typedef class user_connect *PCONNECT;
typedef struct ha_table_option_struct TOS, *PTOS;
typedef struct ha_field_option_struct FOS, *PFOS;
typedef struct ha_index_option_struct XOS, *PXOS;

extern handlerton *connect_hton;

/**
  structure for CREATE TABLE options (table options)

  These can be specified in the CREATE TABLE:
  CREATE TABLE ( ... ) {...here...}
*/
struct ha_table_option_struct {
  const char *type;
  const char *filename;
  const char *optname;
  const char *tabname;
  const char *tablist;
  const char *dbname;
  const char *separator;
//const char *connect;
  const char *qchar;
  const char *module;
  const char *subtype;
  const char *catfunc;
  const char *srcdef;
  const char *colist;
  const char *oplist;
  const char *data_charset;
  ulonglong lrecl;
  ulonglong elements;
//ulonglong estimate;
  ulonglong multiple;
  ulonglong header;
  ulonglong quoted;
  ulonglong ending;
  ulonglong compressed;
  bool mapped;
  bool huge;
  bool split;
  bool readonly;
  bool sepindex;
  };

/**
  structure for CREATE TABLE options (field options)

  These can be specified in the CREATE TABLE per field:
  CREATE TABLE ( field ... {...here...}, ... )
*/
struct ha_field_option_struct
{
  ulonglong offset;
  ulonglong freq;
  ulonglong fldlen;
  uint opt;
  const char *dateformat;
  const char *fieldformat;
  char *special;
};

/*
  index options can be declared similarly
  using the ha_index_option_struct structure.

  Their values can be specified in the CREATE TABLE per index:
  CREATE TABLE ( field ..., .., INDEX .... *here*, ... )
*/
struct ha_index_option_struct
{
  bool dynamic;
  bool mapped;
};

/** @brief
  CONNECT_SHARE is a structure that will be shared among all open handlers.
  This example implements the minimum of what you will probably need.
*/
class CONNECT_SHARE : public Handler_share {
public:
  mysql_mutex_t mutex;
  THR_LOCK lock;
  CONNECT_SHARE()
  {
    thr_lock_init(&lock);
  }
  ~CONNECT_SHARE()
  {
    thr_lock_delete(&lock);
    mysql_mutex_destroy(&mutex);
  }
};

typedef class ha_connect *PHC;

/** @brief
  Class definition for the storage engine
*/
class ha_connect: public handler
{
  THR_LOCK_DATA lock;      ///< MySQL lock
  CONNECT_SHARE *share;        ///< Shared lock info
  CONNECT_SHARE *get_share();

protected:
  char *PlugSubAllocStr(PGLOBAL g, void *memp, const char *str, size_t length)
  {
    char *ptr= (char*)PlgDBSubAlloc(g, memp, length + 1);

    if (ptr) {
      memcpy(ptr, str, length);
      ptr[length]= '\0';
      } // endif ptr

    return ptr;
  } // end of PlugSubAllocStr

public:
  ha_connect(handlerton *hton, TABLE_SHARE *table_arg);
  ~ha_connect();

  // CONNECT Implementation
  static   bool connect_init(void);
  static   bool connect_end(void);
  TABTYPE  GetRealType(PTOS pos= NULL);
  char    *GetRealString(const char *s);
  char    *GetStringOption(char *opname, char *sdef= NULL);
  PTOS     GetTableOptionStruct(TABLE_SHARE *s= NULL);
  bool     GetBooleanOption(char *opname, bool bdef);
  bool     SetBooleanOption(char *opname, bool b);
  int      GetIntegerOption(char *opname);
  bool     GetIndexOption(KEY *kp, char *opname);
  bool     CheckString(const char *str1, const char *str2);
  bool     SameString(TABLE *tab, char *opn);
  bool     SetIntegerOption(char *opname, int n);
  bool     SameInt(TABLE *tab, char *opn);
  bool     SameBool(TABLE *tab, char *opn);
  bool     FileExists(const char *fn, bool bf);
  bool     NoFieldOptionChange(TABLE *tab);
  PFOS     GetFieldOptionStruct(Field *fp);
  void    *GetColumnOption(PGLOBAL g, void *field, PCOLINFO pcf);
  PXOS     GetIndexOptionStruct(KEY *kp);
  PIXDEF   GetIndexInfo(TABLE_SHARE *s= NULL);
  bool     CheckVirtualIndex(TABLE_SHARE *s);
  const char *GetDBName(const char *name);
  const char *GetTableName(void);
  char    *GetPartName(void);
//int      GetColNameLen(Field *fp);
//char    *GetColName(Field *fp);
//void     AddColName(char *cp, Field *fp);
  TABLE   *GetTable(void) {return table;}
  bool     IsSameIndex(PIXDEF xp1, PIXDEF xp2);
  bool     IsPartitioned(void);
  bool     IsUnique(uint n);
  char    *GetDataPath(void) {return (char*)datapath;}

  void     SetDataPath(PGLOBAL g, const char *path); 
  PTDB     GetTDB(PGLOBAL g);
  int      OpenTable(PGLOBAL g, bool del= false);
  bool     CheckColumnList(PGLOBAL g);
  bool     IsOpened(void);
  int      CloseTable(PGLOBAL g);
  int      MakeRecord(char *buf);
  int      ScanRecord(PGLOBAL g, uchar *buf);
  int      CheckRecord(PGLOBAL g, const uchar *oldbuf, uchar *newbuf);
  int      ReadIndexed(uchar *buf, OPVAL op, const uchar* key= NULL,
                                             uint key_len= 0);
  bool     MakeKeyWhere(PGLOBAL g, char *qry, OPVAL op, char *q,
                                   const void *key, int klen);
  inline char *Strz(LEX_STRING &ls); 


  /** @brief
    The name that will be used for display purposes.
   */
  const char *table_type() const {return "CONNECT";}

  /** @brief
    The name of the index type that will be used for display.
    Don't implement this method unless you really have indexes.
   */
  const char *index_type(uint inx);

  /** @brief
    The file extensions.
   */
  const char **bas_ext() const;

 /**
    Check if a storage engine supports a particular alter table in-place
    @note Called without holding thr_lock.c lock.
 */
 virtual enum_alter_inplace_result
 check_if_supported_inplace_alter(TABLE *altered_table,
                                  Alter_inplace_info *ha_alter_info);

  /** @brief
    This is a list of flags that indicate what functionality the storage engine
    implements. The current table flags are documented in handler.h
  */
  ulonglong table_flags() const;

  /** @brief
    This is a bitmap of flags that indicates how the storage engine
    implements indexes. The current index flags are documented in
    handler.h. If you do not implement indexes, just return zero here.

      @details
    part is the key part to check. First key part is 0.
    If all_parts is set, MySQL wants to know the flags for the combined
    index, up to and including 'part'.
  */
  ulong index_flags(uint inx, uint part, bool all_parts) const;

  /** @brief
    unireg.cc will call max_supported_record_length(), max_supported_keys(),
    max_supported_key_parts(), uint max_supported_key_length()
    to make sure that the storage engine can handle the data it is about to
    send. Return *real* limits of your storage engine here; MySQL will do
    min(your_limits, MySQL_limits) automatically.
   */
  uint max_supported_record_length() const { return HA_MAX_REC_LENGTH; }

  /** @brief
    unireg.cc will call this to make sure that the storage engine can handle
    the data it is about to send. Return *real* limits of your storage engine
    here; MySQL will do min(your_limits, MySQL_limits) automatically.

      @details
    There is no need to implement ..._key_... methods if your engine doesn't
    support indexes.
   */
  uint max_supported_keys()          const { return 10; }

  /** @brief
    unireg.cc will call this to make sure that the storage engine can handle
    the data it is about to send. Return *real* limits of your storage engine
    here; MySQL will do min(your_limits, MySQL_limits) automatically.

      @details
    There is no need to implement ..._key_... methods if your engine doesn't
    support indexes.
   */
  uint max_supported_key_parts()     const { return 10; }

  /** @brief
    unireg.cc will call this to make sure that the storage engine can handle
    the data it is about to send. Return *real* limits of your storage engine
    here; MySQL will do min(your_limits, MySQL_limits) automatically.

      @details
    There is no need to implement ..._key_... methods if your engine doesn't
    support indexes.
   */
  uint max_supported_key_length()    const { return 255; }

  /** @brief
    Called in test_quick_select to determine if indexes should be used.
  */
  virtual double scan_time() { return (double) (stats.records+stats.deleted) / 20.0+10; }

  /** @brief
    This method will never be called if you do not implement indexes.
  */
  virtual double read_time(uint, uint, ha_rows rows)
    { return (double) rows /  20.0+1; }

  /*
    Everything below are methods that we implement in ha_connect.cc.

    Most of these methods are not obligatory, skip them and
    MySQL will treat them as not implemented
  */
  virtual bool get_error_message(int error, String *buf);

 /**
   Push condition down to the table handler.

   @param  cond   Condition to be pushed. The condition tree must not be
                  modified by the by the caller.

   @return
     The 'remainder' condition that caller must use to filter out records.
     NULL means the handler will not return rows that do not match the
     passed condition.

   @note
   The pushed conditions form a stack (from which one can remove the
   last pushed condition using cond_pop).
   The table handler filters out rows using (pushed_cond1 AND pushed_cond2
   AND ... AND pushed_condN)
   or less restrictive condition, depending on handler's capabilities.

   handler->ha_reset() call empties the condition stack.
   Calls to rnd_init/rnd_end, index_init/index_end etc do not affect the
   condition stack.
 */
virtual const COND *cond_push(const COND *cond);
PCFIL CheckCond(PGLOBAL g, PCFIL filp, AMT tty, Item *cond);
const char *GetValStr(OPVAL vop, bool neg);
PFIL  CondFilter(PGLOBAL g, Item *cond);
//PFIL  CheckFilter(PGLOBAL g);

 /**
   Number of rows in table. It will only be called if
   (table_flags() & (HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT)) != 0
 */
 virtual ha_rows records();

 /**
   Type of table for caching query
   CONNECT should not use caching because its tables are external
   data prone to me modified out of MariaDB
 */
 virtual uint8 table_cache_type(void)
 {
#if defined(MEMORY_TRACE)
   // Temporary until bug MDEV-4771 is fixed
   return HA_CACHE_TBL_NONTRANSACT;
#else
   return HA_CACHE_TBL_NOCACHE;
#endif
 }

 /** @brief
    We implement this in ha_connect.cc; it's a required method.
  */
  int open(const char *name, int mode, uint test_if_locked);    // required

  /** @brief
    We implement this in ha_connect.cc; it's a required method.
  */
  int close(void);                                              // required

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int write_row(uchar *buf);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int update_row(const uchar *old_data, uchar *new_data);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int delete_row(const uchar *buf);

  // Added to the connect handler
  int index_init(uint idx, bool sorted);
  int index_end();
  int index_read(uchar * buf, const uchar * key, uint key_len,
                              enum ha_rkey_function find_flag);
  int index_next_same(uchar *buf, const uchar *key, uint keylen);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
//int index_read_map(uchar *buf, const uchar *key,
//                   key_part_map keypart_map, enum ha_rkey_function find_flag);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int index_next(uchar *buf);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
int index_prev(uchar *buf);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int index_first(uchar *buf);

  /** @brief
    We implement this in ha_connect.cc. It's not an obligatory method;
    skip it and and MySQL will treat it as not implemented.
  */
  int index_last(uchar *buf);

  /* Index condition pushdown implementation */
//Item *idx_cond_push(uint keyno, Item* idx_cond);

  /** @brief
    Unlike index_init(), rnd_init() can be called two consecutive times
    without rnd_end() in between (it only makes sense if scan=1). In this
    case, the second call should prepare for the new table scan (e.g if
    rnd_init() allocates the cursor, the second call should position the
    cursor to the start of the table; no need to deallocate and allocate
    it again. This is a required method.
  */
  int rnd_init(bool scan);                                      //required
  int rnd_end();
  int rnd_next(uchar *buf);                                     ///< required
  int rnd_pos(uchar *buf, uchar *pos);                          ///< required
  void position(const uchar *record);                           ///< required
  int info(uint);                                               ///< required
  int extra(enum ha_extra_function operation);
  int start_stmt(THD *thd, thr_lock_type lock_type);
  int external_lock(THD *thd, int lock_type);                   ///< required
  int delete_all_rows(void);
  ha_rows records_in_range(uint inx, key_range *min_key,
                           key_range *max_key);
  /**
    These methods can be overridden, but their default implementation
    provide useful functionality.
  */
  int rename_table(const char *from, const char *to);
  /**
    Delete a table in the engine. Called for base as well as temporary
    tables.
  */
  int delete_table(const char *name);
  /**
    Called by delete_table and rename_table
  */
  int delete_or_rename_table(const char *from, const char *to);
  int create(const char *name, TABLE *form,
             HA_CREATE_INFO *create_info);                      ///< required
  bool check_if_incompatible_data(HA_CREATE_INFO *info,
                                  uint table_changes);

  THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
                             enum thr_lock_type lock_type);     ///< required
  int optimize(THD* thd, HA_CHECK_OPT* check_opt);

  /**
   * Multi Range Read interface
   */
  int multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param,
                            uint n_ranges, uint mode, HANDLER_BUFFER *buf);
  int multi_range_read_next(range_id_t *range_info);
  ha_rows multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
                                      void *seq_init_param,
                                      uint n_ranges, uint *bufsz,
                                      uint *flags, Cost_estimate *cost);
  ha_rows multi_range_read_info(uint keyno, uint n_ranges, uint keys,
                                uint key_parts, uint *bufsz,
                                uint *flags, Cost_estimate *cost);
  int multi_range_read_explain_info(uint mrr_mode, char *str, size_t size);

  int reset(void) {ds_mrr.dsmrr_close(); return 0;}

  /* Index condition pushdown implementation */
//  Item *idx_cond_push(uint keyno, Item* idx_cond);
private:
  DsMrr_impl ds_mrr;

protected:
  bool check_privileges(THD *thd, PTOS options, char *dbn);
  MODE CheckMode(PGLOBAL g, THD *thd, MODE newmode, bool *chk, bool *cras);
  char *GetDBfromName(const char *name);

  // Members
  static ulong  num;                  // Tracable handler number
  PCONNECT      xp;                   // To user_connect associated class
  ulong         hnum;                 // The number of this handler
  query_id_t    valid_query_id;       // The one when tdbp was allocated
  query_id_t    creat_query_id;       // The one when handler was allocated
  char         *datapath;             // Is the Path of DB data directory
  PTDB          tdbp;                 // To table class object
  PVAL          sdvalin1;             // Used to convert date values
  PVAL          sdvalin2;             // Used to convert date values
  PVAL          sdvalin3;             // Used to convert date values
  PVAL          sdvalin4;             // Used to convert date values
  PVAL          sdvalout;             // Used to convert date values
  bool          istable;              // True for table handler
  char          partname[64];         // The partition name
  MODE          xmod;                 // Table mode
  XINFO         xinfo;                // The table info structure
  bool          valid_info;           // True if xinfo is valid
  bool          stop;                 // Used when creating index
  bool          alter;                // True when converting to other engine
  bool          mrr;                  // True when getting index positions
  bool          nox;                  // True when index should not be made
  bool          abort;                // True after error in UPDATE/DELETE
  int           indexing;             // Type of indexing for CONNECT
  int           locked;               // Table lock
  MY_BITMAP    *part_id;              // Columns used for partition func
  THR_LOCK_DATA lock_data;

public:
  TABLE_SHARE  *tshp;                 // Used by called tables
  char   *data_file_name;
  char   *index_file_name;
  uint    int_table_flags;            // Inherited from MyISAM
  bool    enable_activate_all_index;  // Inherited from MyISAM
};  // end of ha_connect class definition