/* Copyright (c) 2014, 2015, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */

// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gtest/gtest.h>

#include <vector>
#include <sstream>
#include <string>

#include "my_global.h"
#include "item_cmpfunc.h"
#include "fake_table.h"
#include "test_utils.h"

#include "parse_tree_helpers.h"

namespace item_filter_unittest {

using my_testing::Server_initializer;

/**
  Helper class to print which line a failing test was called from.
*/
class TestFailLinePrinter
{
public:
  explicit TestFailLinePrinter(int line) : m_line(line) {}
  int m_line;
};
std::ostream &operator<< (std::ostream &s, const TestFailLinePrinter &v)
{
  return s << "called from line " << v.m_line;
}


class ItemFilterTest : public ::testing::Test
{
protected:
  ItemFilterTest() : rows_in_table(200) {}

  virtual void SetUp()
  {
    initializer.SetUp();
    init_sql_alloc(PSI_NOT_INSTRUMENTED,
                   &m_alloc, thd()->variables.range_alloc_block_size, 0);
  }

  virtual void TearDown()
  {
    delete m_table;

    initializer.TearDown();
    free_root(&m_alloc, MYF(0));
  }

  THD *thd() { return initializer.thd(); }

  /**
    Create a table with the requested number of columns without
    creating indexes.

    @param  nbr_columns       The number of columns in the table
    @param  columns_nullable  Whether or not columns in the table can be NULL
  */
  void create_table(int nbr_columns, bool columns_nullable)
  {
    m_table= new Fake_TABLE(nbr_columns, columns_nullable);
    m_field= m_table->field;
    m_table_list= m_table->pos_in_table_list;
  }

  /**
    Create a table with the requested number of non-nullable columns
    without creating indexes.

    @param  nbr_columns       The number of columns in the table
    @param  columns_nullable  Whether or not columns in the table can be NULL
  */
  void create_table(int nbr_columns) { create_table(nbr_columns, false); }

  /**
    Utility funtion used to simplify creation of func items used as
    range predicates.

    @param type            The type of range predicate operator requested
    @param fld             The field used in the range predicate
    @param val1            Value used in the range predicate (if the predicate
                           takes two or more arguments)
    @param val2            Value used in the range predicate (if the predicate
                           takes three or more arguments)

    @return Item for the specified range predicate
  */
  Item_func *create_item(Item_func::Functype type, Field *fld,
                         int val1, int val2);

// Undefined at end of this file
#define create_item_check_filter(er, op, f1, v1, v2, ut, fti)            \
  do_create_item_check_filter(er, op, f1, v1, v2, ut, fti, TestFailLinePrinter(__LINE__))
  Item* do_create_item_check_filter(const float expected_result,
                                    const Item_func::Functype type,
                                    Field *fld,
                                    const int val1,
                                    const int val2,
                                    const table_map used_tables,
                                    MY_BITMAP *fields_to_ignore,
                                    TestFailLinePrinter called_from_line)
  {
    SCOPED_TRACE(called_from_line);
    Item *item= create_item(type, fld, val1, val2);

    const float filter= item->get_filtering_effect(m_table_list->map(),
                                                   used_tables,
                                                   fields_to_ignore,
                                                   rows_in_table);
    EXPECT_FLOAT_EQ(expected_result, filter);
    return item;
  }

#define create_anditem_check_filter(er, lst, ut, fti)  \
  do_create_anditem_check_filter(er, lst, ut, fti,               \
                                 TestFailLinePrinter(__LINE__))
  Item_cond_and*
  do_create_anditem_check_filter(const float expected_result,
                                 List<Item> &lst,
                                 const table_map used_tables,
                                 MY_BITMAP *fields_to_ignore,
                                 TestFailLinePrinter called_from_line)
  {
    SCOPED_TRACE(called_from_line);

    Item_cond_and *and_item= new Item_cond_and(lst);

    Item *itm= static_cast<Item*>(and_item);
    and_item->fix_fields(thd(), &itm);

    const float filter= and_item->get_filtering_effect(m_table_list->map(),
                                                       used_tables,
                                                       fields_to_ignore,
                                                       rows_in_table);
    EXPECT_FLOAT_EQ(expected_result, filter);

    return and_item;
  }

#define create_oritem_check_filter(er, lst, ut, fti)  \
  do_create_oritem_check_filter(er, lst, ut, fti,               \
                                TestFailLinePrinter(__LINE__))
  Item_cond_or*
  do_create_oritem_check_filter(const float expected_result,
                                List<Item> &lst,
                                const table_map used_tables,
                                MY_BITMAP *fields_to_ignore,
                                TestFailLinePrinter called_from_line)
  {
    SCOPED_TRACE(called_from_line);

    Item_cond_or *or_item= new Item_cond_or(lst);

    Item *itm= static_cast<Item*>(or_item);
    or_item->fix_fields(thd(), &itm);

    const float filter= or_item->get_filtering_effect(m_table_list->map(),
                                                      used_tables,
                                                      fields_to_ignore,
                                                      rows_in_table);
    EXPECT_FLOAT_EQ(expected_result, filter);

    return or_item;
  }

#define create_initem_check_filter(er, lst, ut, fti)  \
  do_create_initem_check_filter(er, lst, ut, fti,               \
                                TestFailLinePrinter(__LINE__))
  Item_func_in*
  do_create_initem_check_filter(const float expected_result,
                                List<Item> &lst,
                                const table_map used_tables,
                                MY_BITMAP *fields_to_ignore,
                                TestFailLinePrinter called_from_line)
  {
    SCOPED_TRACE(called_from_line);

    PT_item_list *list= new (thd()->mem_root) PT_item_list;
    list->value= lst;
    Item_func_in *in_item= new Item_func_in(POS(), list, false);
    Parse_context pc(thd(), thd()->lex->current_select());
    EXPECT_FALSE(in_item->itemize(&pc, (Item **) &in_item));

    Item *itm= static_cast<Item*>(in_item);
    in_item->fix_fields(thd(), &itm);

    const float filter= in_item->get_filtering_effect(m_table_list->map(),
                                                      used_tables,
                                                      fields_to_ignore,
                                                      rows_in_table);
    EXPECT_FLOAT_EQ(expected_result, filter);

    return in_item;
  }

  /*
    Many filtering estimates depends on the number of rows in the
    table. For example, P("col = <const>") = max(0.005, 1/rows_in_table)

    This variable determines the number of rows in the table.
  */
  double rows_in_table;
  Server_initializer initializer;
  MEM_ROOT           m_alloc;

  Fake_TABLE *m_table;
  TABLE_LIST *m_table_list;
  /*
    Pointer to m_table->field. Only valid if the table was
    created by calling one of ItemFilterTest::create_table*()
   */
  Field **m_field;
};

Item_func* ItemFilterTest::create_item(Item_func::Functype type,
                                       Field *fld, int val1, int val2)
{
  Item_func *result;
  switch (type)
  {
  case Item_func::GT_FUNC:
    result= new Item_func_gt(new Item_field(fld), new Item_int(val1));
    break;
  case Item_func::GE_FUNC:
    result= new Item_func_ge(new Item_field(fld), new Item_int(val1));
    break;
  case Item_func::LT_FUNC:
    result= new Item_func_lt(new Item_field(fld), new Item_int(val1));
    break;
  case Item_func::LE_FUNC:
    result= new Item_func_le(new Item_field(fld), new Item_int(val1));
    break;
  case Item_func::MULT_EQUAL_FUNC:
    result= new Item_equal(new Item_int(val1), new Item_field(fld));
    break;
  case Item_func::XOR_FUNC:
    result= new Item_func_xor(new Item_field(fld), new Item_int(val1));
    break;
  case Item_func::ISNULL_FUNC:
    result= new Item_func_isnull(new Item_field(fld));
    break;
  case Item_func::ISNOTNULL_FUNC:
    result= new Item_func_isnotnull(new Item_field(fld));
    break;
  case Item_func::BETWEEN:
    {
      Parse_context pc(thd(), thd()->lex->current_select());
      result= new Item_func_between(POS(), new Item_field(fld),
                                    new Item_int(val1), new Item_int(val2),
                                    false);
      EXPECT_FALSE(result->itemize(&pc, (Item **) &result));
      break;
    }
  default:
    result= NULL;
    DBUG_ASSERT(false);
    return result;
  }
  Item *itm= static_cast<Item*>(result);
  result->fix_fields(thd(), &itm);
  return result;
}

/*
  Basic single predicate filter tests
*/
TEST_F(ItemFilterTest, BasicDefaultRows)
{
  create_table(1, true);

  const int unused_int= 0;
  const table_map used_tables= 0;

  MY_BITMAP no_ignore_flds;
  bitmap_init(&no_ignore_flds, 0, m_table->s->fields, false);

  // Check filtering for predicate: field0 = 10
  create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 > 10
  create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::GT_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 >= 10
  create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::GE_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 < 10
  create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::LT_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 <= 10
  create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::LE_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 IS NULL
  create_item_check_filter(COND_FILTER_EQUALITY, Item_func::ISNULL_FUNC,
                           m_field[0], unused_int, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 IS NOT NULL
  create_item_check_filter(1.0-COND_FILTER_EQUALITY, Item_func::ISNOTNULL_FUNC,
                           m_field[0], unused_int, unused_int,
                           used_tables, &no_ignore_flds);
  // Check filtering for predicate: field0 BETWEEN 10 AND 12
  create_item_check_filter(COND_FILTER_BETWEEN, Item_func::BETWEEN,
                           m_field[0], 10, 12, used_tables, &no_ignore_flds);

  bitmap_free(&no_ignore_flds);
}

/*
  Basic single predicate filter tests when field should be ignored
*/
TEST_F(ItemFilterTest, BasicIgnoreField)
{
  create_table(2, true);

  const int unused_int= 0;
  const table_map used_tables= 0;

  // Predicates on m_field[0] should be ignored
  MY_BITMAP ignore_fld0;
  bitmap_init(&ignore_fld0, 0, m_table->s->fields, false);
  bitmap_set_bit(&ignore_fld0, m_field[0]->field_index);

  // Check filtering for predicate on ignored field: field0 = 10
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::MULT_EQUAL_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 > 10
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::GT_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 >= 10
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::GE_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 < 10
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::LT_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 <= 10
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::LE_FUNC,
                           m_field[0], 10, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 IS NULL
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::ISNULL_FUNC,
                           m_field[0], unused_int, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 IS NOT NULL
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::ISNOTNULL_FUNC,
                           m_field[0], unused_int, unused_int,
                           used_tables, &ignore_fld0);
  // Check filtering for predicate on ignored field: field0 BETWEEN 10 AND 12
  create_item_check_filter(COND_FILTER_ALLPASS, Item_func::BETWEEN,
                           m_field[0], 10, 12, used_tables, &ignore_fld0);

  // Verifying that predicates on other fields are not ignored
  create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                           m_field[1], 10, unused_int,
                           used_tables, &ignore_fld0);

  bitmap_free(&ignore_fld0);
}

/*
  Basic condition test: AND of const predicates
*/
TEST_F(ItemFilterTest, BasicConstAnd)
{
  create_table(3, true);

  const int unused_int= 0;
  const table_map used_tables= 0;

  // Do not ignore predicates on any fields
  MY_BITMAP ignore_flds;
  bitmap_init(&ignore_flds, 0, m_table->s->fields, false);

  // Create predicate: field0 = 10
  Item *eq_item1=
    create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                             m_field[0], 10, unused_int,
                             used_tables, &ignore_flds);
  // Create predicate: field1 = 10
  Item *eq_item2=
    create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                             m_field[1], 10, unused_int,
                             used_tables, &ignore_flds);

  // Create predicate: field2 < 99
  Item *lt_item=
    create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::LT_FUNC,
                             m_field[2], 99, unused_int,
                             used_tables, &ignore_flds);


  List<Item> and_lst;
  and_lst.push_back(eq_item1);
  and_lst.push_back(eq_item2);

  // Calculate filtering of (field0=10 AND field1=10) 
  create_anditem_check_filter(COND_FILTER_EQUALITY * COND_FILTER_EQUALITY,
                              and_lst, used_tables, &ignore_flds);


  and_lst.push_back(lt_item);

  // Calculate filtering of (field0=10 AND field1=10 AND field2<99) 
  Item *and_it= create_anditem_check_filter(COND_FILTER_EQUALITY *
                                            COND_FILTER_EQUALITY *
                                            COND_FILTER_INEQUALITY,
                                            and_lst, used_tables, &ignore_flds);

  /*
    Calculate filtering of (field0=10 AND field1=10 AND field2<99)
    while ignoring predicates on field0
  */
  bitmap_set_bit(&ignore_flds, m_field[0]->field_index);
  
  float filter= and_it->get_filtering_effect(m_table_list->map(),
                                             used_tables,
                                             &ignore_flds,
                                             rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_EQUALITY * COND_FILTER_INEQUALITY, filter);

  /*
    Calculate filtering of (field0=10 AND field1=10 AND field2<99)
    while ignoring predicates on field0 and field1
  */
  bitmap_set_bit(&ignore_flds, m_field[1]->field_index);
  filter= and_it->get_filtering_effect(m_table_list->map(),
                                       used_tables,
                                       &ignore_flds,
                                       rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_INEQUALITY, filter);

  /*
    Calculate filtering of (field0=10 AND field1=10 AND field2<99)
    while ignoring predicates on field0, field1 and field2
  */
  bitmap_set_bit(&ignore_flds, m_field[2]->field_index);
  filter= and_it->get_filtering_effect(m_table_list->map(),
                                       used_tables,
                                       &ignore_flds,
                                       rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_ALLPASS, filter);

  bitmap_free(&ignore_flds);
}

/*
  Basic condition test: OR of const predicates
*/
TEST_F(ItemFilterTest, BasicConstOr)
{
  create_table(3, true);

  const int unused_int= 0;
  const table_map used_tables= 0;

  // Do not ignore predicates on any fields
  MY_BITMAP ignore_flds;
  bitmap_init(&ignore_flds, 0, m_table->s->fields, false);

  // Create predicate: field0 = 10
  Item *eq_item1=
    create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                             m_field[0], 10, unused_int,
                             used_tables, &ignore_flds);
  // Create predicate: field1 = 10
  Item *eq_item2=
    create_item_check_filter(COND_FILTER_EQUALITY, Item_func::MULT_EQUAL_FUNC,
                             m_field[1], 10, unused_int,
                             used_tables, &ignore_flds);

  // Create predicate: field2 < 99
  Item *lt_item=
    create_item_check_filter(COND_FILTER_INEQUALITY, Item_func::LT_FUNC,
                             m_field[2], 99, unused_int,
                             used_tables, &ignore_flds);

  List<Item> or_lst;
  or_lst.push_back(eq_item1);
  or_lst.push_back(eq_item2);

  // Calculate filtering of (field0=10 OR field1=10)
  const float expected1= 
    2 * COND_FILTER_EQUALITY - (COND_FILTER_EQUALITY * COND_FILTER_EQUALITY);
  create_oritem_check_filter(expected1, or_lst, used_tables, &ignore_flds);

  or_lst.push_back(lt_item);

  // Calculate filtering of (field0=10 OR field1=10 OR field2<99)
  const float expected2=
    expected1 + COND_FILTER_INEQUALITY - (expected1 * COND_FILTER_INEQUALITY);
  Item *or_it=
    create_oritem_check_filter(expected2, or_lst, used_tables, &ignore_flds);

  /*
    Calculate filtering of (field0=10 OR field1=10 OR field2<99) while
    ignoring predicates on m_field[0]

    A predicate on an ignored field has filtering effect 1.0 (no
    filtering). The filtering effect for "A or B" is calculated as
    "Disjunction of independent events":

    P(A or B) =  P(A) + P(B) - P(A and B)

    The result is always 1.0 if any of the ORed predicates is 1.0.

  */
  bitmap_set_bit(&ignore_flds, m_field[0]->field_index);
  const float filt_ign0= or_it->get_filtering_effect(m_table_list->map(),
                                                     used_tables,
                                                     &ignore_flds,
                                                     rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_ALLPASS, filt_ign0);

  /*
    Calculate filtering of (field0=10 OR field1=10 OR field2<99) while
    ignoring predicates on m_field[0] and m_field[1]
  */
  bitmap_set_bit(&ignore_flds, m_field[1]->field_index);
  const float filt_ign1= or_it->get_filtering_effect(m_table_list->map(),
                                                     used_tables,
                                                     &ignore_flds,
                                                     rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_ALLPASS, filt_ign1);

  /*
    Calculate filtering of (field0=10 OR field1=10 OR field2<99) while
    ignoring predicates on m_field[2]
  */
  bitmap_clear_all(&ignore_flds);
  bitmap_set_bit(&ignore_flds, m_field[2]->field_index);
  const float filt_ign2= or_it->get_filtering_effect(m_table_list->map(),
                                                     used_tables,
                                                     &ignore_flds,
                                                     rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_ALLPASS, filt_ign2);

  bitmap_free(&ignore_flds);
}

/*
  Test filtering for IN predicates
*/
TEST_F(ItemFilterTest, InPredicate)
{
  create_table(1, true);

  const table_map used_tables= 0;

  // Do not ignore predicates on any fields
  MY_BITMAP ignore_flds;
  bitmap_init(&ignore_flds, 0, m_table->s->fields, false);

  // Calculate filtering effect of "col IN (1)"
  List<Item> in_lst1;
  in_lst1.push_back(new Item_field(m_field[0]));
  in_lst1.push_back(new Item_int(1));

  create_initem_check_filter(COND_FILTER_EQUALITY, in_lst1,
                             used_tables, &ignore_flds);

  // Calculate filtering effect of "col IN (1, ..., 4)"
  List<Item> in_lst2;
  in_lst2.push_back(new Item_field(m_field[0]));
  in_lst2.push_back(new Item_int(1));
  in_lst2.push_back(new Item_int(2));
  in_lst2.push_back(new Item_int(3));
  in_lst2.push_back(new Item_int(4));

  const float filter= 4 * COND_FILTER_EQUALITY;
  Item_func_in *in_it=
    create_initem_check_filter(filter, in_lst2,
                               used_tables, &ignore_flds);

  /*
    Calculate filtering effect of "col IN (1, ..., 110)"

    "col IN (<110 values>)" would mean a filtering effect of 
    110 * COND_FILTER_EQUALITY = 0.6, but the filtering effect
    of IN has an upper limit of 0.5.
  */
  List<Item> in_lst3;
  in_lst3.push_back(new Item_field(m_field[0]));
  for (int i= 1; i <= 110; i++)
    in_lst3.push_back(new Item_int(i));

  const float capped_filter= 0.5;
  create_initem_check_filter(capped_filter, in_lst3,
                             used_tables, &ignore_flds);

  /*
    There is no filtering effect for "col IN (1,..,6)" if 'col' is
    ignored.
  */
  bitmap_set_bit(&ignore_flds, m_field[0]->field_index);
  const float filt_ign= in_it->get_filtering_effect(m_table_list->map(),
                                                    used_tables,
                                                    &ignore_flds,
                                                    rows_in_table);
  EXPECT_FLOAT_EQ(COND_FILTER_ALLPASS, filt_ign);
  bitmap_free(&ignore_flds);
}

}
#undef create_item_check_filter
#undef create_anditem_check_filter
#undef create_oritem_check_filter
#undef create_initem_check_filter
