/* 
 * Copyright (c) 2012, 2013 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 St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "tut_stdafx.h"

#include "connection_helpers.h"
#include "base/file_utilities.h"

#include "grtdb/db_object_helpers.h"
#include "sqlide/wb_sql_editor_form.h"
#include "sqlide/autocomplete_object_name_cache.h"
#include "sqlide/sql_editor_be.h"

using namespace bec;
using namespace wb;

using namespace boost::assign;

// Helper to wait for cache operations to finish.
static GMutex *cache_mutex;
static GCond *cache_condition;

BEGIN_TEST_DATA_CLASS(sql_editor_be_autocomplete_tests)
protected:
  WBTester _tester;
  Sql_editor::Ref _sql_editor;
  GrtVersionRef _version;

  GMutex *_connection_mutex;
  sql::Dbc_connection_handler::Ref _conn;
  AutoCompleteCache *_cache;

public:
TEST_DATA_CONSTRUCTOR(sql_editor_be_autocomplete_tests)
  : _cache(NULL), _conn(new sql::Dbc_connection_handler())
{
  populate_grt(_tester.grt, NULL, _tester);

  // Auto completion needs a cache for object name look up, so we have to set up one
  // with all bells and whistles.
  _connection_mutex = g_mutex_new();

  cache_mutex = g_mutex_new();
  cache_condition = g_cond_new();
}

TEST_DATA_DESTRUCTOR(sql_editor_be_autocomplete_tests)
{
  _cache->shutdown();
  delete _cache;
  g_mutex_free(_connection_mutex);
  g_mutex_free(cache_mutex);
  g_cond_free(cache_condition);
}

base::GMutexLock get_connection(sql::Dbc_connection_handler::Ref &conn)
{
  base::GMutexLock lock(_connection_mutex);
  conn = _conn;
  return lock;
}

END_TEST_DATA_CLASS;

TEST_MODULE(sql_editor_be_autocomplete_tests, "SQL code completion tests");

void cache_callback(bool working)
{
  if (!working)
  {
    g_mutex_lock(cache_mutex);
    g_cond_signal(cache_condition);
    g_mutex_unlock(cache_mutex);
  }
}

/**
 * Setup for the cache.
 */
TEST_FUNCTION(5)
{
  db_mgmt_ConnectionRef connectionProperties(_tester.grt);
  setup_env(_tester.grt, connectionProperties);

  sql::DriverManager *dm = sql::DriverManager::getDriverManager();
  _conn->ref = dm->getConnection(connectionProperties);

  base::remove("testconn.cache");
  _cache = new AutoCompleteCache("testconn", boost::bind(&Test_object_base<sql_editor_be_autocomplete_tests>::get_connection, this, _1),
    ".", cache_callback);

  std::auto_ptr<sql::Statement> stmt(_conn->ref->createStatement());

  sql::ResultSet *res = stmt->executeQuery("SELECT VERSION() as VERSION");
  if (res && res->next())
  {
    std::string version = res->getString("VERSION");
    _version = CatalogHelper::parse_version(_tester.grt, version);
  }
  delete res;

  ensure("Server version is invalid", _version.is_valid());

  _tester.get_rdbms()->version(_version);

  // Copy a current version of the code editor configuration file to the test data folder.
  gchar *contents;
  gsize length;
  GError *error = NULL;
  if (g_file_get_contents("../../res/wbdata/code_editor.xml", &contents, &length, &error))
  {
    ensure("Could not write editor configuration to target file",
      g_file_set_contents("data/code_editor.xml", contents, length, &error) == TRUE);
    g_free(contents);
  }
  else
    fail("Could not copy code editor configuration");

  _sql_editor = Sql_editor::create(_tester.get_rdbms(), _version);
  _sql_editor->set_current_schema("sakila");
  _sql_editor->set_auto_completion_cache(_cache);

  // We don't set up the sakila schema. This is needed in so many places, it should simply exist.
  std::vector<std::string> list = _cache->get_matching_schema_names("sakila");

  // The loops are not necessary, but there can be spurious condition signals,
  // as the glib docs say.
  while (!_cache->is_schema_list_fetch_done())
    g_cond_wait(cache_condition, cache_mutex);

  _cache->get_matching_table_names("sakila"); // Cache all tables and columns.

  while (!_cache->is_schema_table_columns_fetch_done("sakila", "store"))
    g_cond_wait(cache_condition, cache_mutex);
}

/**
 * Another prerequisites test. See that the cache contains needed objects.
 */
TEST_FUNCTION(10)
{
  std::vector<std::string> list = _cache->get_matching_schema_names("sakila");
  int found = 0;
  for (std::vector<std::string>::const_iterator i = list.begin(); i != list.end(); ++i)
  {
    if (*i == "sakila" || *i == "mysql")
      found++;
  }
  ensure_equals("Sakila schema missing. Is the DB set up properly?", found, 1);
}

//--------------------------------------------------------------------------------------------------

/**
 * Checks that exactly the given set of AC parts are set.
 */
void match_included_parts(const std::string msg, const Sql_editor::AutoCompletionContext &context,
  int parts)
{
  ensure_equals(msg + ", parts differ", context.wanted_parts, parts);
}

//--------------------------------------------------------------------------------------------------

/**
 * Checks that the auto completion list contains all entries in the correct order. Additional
 * entries are ignored. Both lists must be alphabetically sorted.
 */
void check_ac_entries(const std::string msg, const std::vector<std::pair<int, std::string> > &ac_list,
  const std::vector<std::string> &expected)
{
  size_t i = 0;
  size_t j = 0;
  while (i < expected.size() && j < ac_list.size())
  {
    while (j < ac_list.size() && expected[i] > ac_list[j].second) // Sakila doesn't use Unicode, so we don't need Unicode aware comparisons.
      j++;
    
    if (j < ac_list.size())
    {
      if (expected[i] != ac_list[j].second)
        break;

      i++;
      j++;
    }
  }
  ensure(msg + ", entries are missing", i == expected.size());
}

//--------------------------------------------------------------------------------------------------

struct ac_test_entry
{
  std::string query;
  std::string typed_part;
  int line;
  int offset;

  bool check_entries;
  std::string entries;
  int parts;
};

static ac_test_entry simple_valid_line1[] = {
  {"select count(distinct a.actor_id), phone, first_name, a.last_name, country.country \n"
   "from sakila.actor a, address aa, country\n"
   "where (a.actor_id = 0 and country_id > 0)\n"
   "group by actor_id", "", 1, 0, false,
    "",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "s", 1, 1, true,
    "savepoint select set show start stop",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "se", 1, 2, true,
    "select set",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "sel", 1, 3, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "sele", 1, 4, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "selec", 1, 5, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "select", 1, 6, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"", "", 1, 7, false,
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "c", 1, 8, true,
    "cast() category category_id ceil() ceiling() char_length() character_length() city city_id "
    "coalesce() concat() concat_ws() connection_id() conv() convert() cos() cot() count() country "
    "country_id create_date curdate() current_user() curtime() customer customer_id customer_list",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "co", 1, 9, true,
    "coalesce() concat() concat_ws() connection_id() conv() convert() cos() cot() count() country "
    "country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "cou", 1, 10, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "coun", 1, 11, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "count", 1, 12, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "", 1, 13, false,
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "d", 1, 14, true,
    "data database databases datafile date date_add() date_format() date_sub() datetime day day_hour "
    "day_microsecond day_minute day_second dayname() dayofmonth() dayofweek() dayofyear() dec "
    "decimal declare decode() default definer degrees() delay_key_write delayed delimiter "
    "des_decrypt() des_encrypt() des_key_file description deterministic directory "
    "disable discard disk distinct distinctrow district div double dual dumpfile duplicate dynamic",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "di", 1, 15, true,
    "directory disable discard disk distinct distinctrow district div",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "dis", 1, 16, true,
    "disable discard disk distinct distinctrow district",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "dist", 1, 17, true,
    "distinct distinctrow district",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "disti", 1, 18, true,
    "distinct distinctrow",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "distin", 1, 19, true,
    "distinct distinctrow",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "distinc", 1, 20, true,
    "distinct distinctrow",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "distinct", 1, 21, true,
    "distinct distinctrow",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 1, 22, false,
    "",
    Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns
  },
  {"", "a", 1, 23, true,
    "a aa actor actor_info address",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 1, 24, true,
    "actor_id first_name last_name last_update",
    Sql_editor::CompletionWantColumns | Sql_editor::CompletionWantTables
  },
  {"", "a", 1, 25, true,
    "actor_id",
    Sql_editor::CompletionWantColumns | Sql_editor::CompletionWantTables
  },
  {"", "ac", 1, 26, true, // This continues for all of the chars in actor_id.
    "actor_id",
    Sql_editor::CompletionWantColumns | Sql_editor::CompletionWantTables
  },
  {"", "", 1, 33, false, // Continue with the closing parenthesis.
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 1, 34, false,
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "", 1, 35, false,
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "p", 1, 36, true, // step 30
    "password payment payment_date payment_id performance_schema period_add() period_diff() phone pi() "
    "picture position() postal_code pow() power() price",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "ph", 1, 37, true,
    "phone",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "", 1, 41, false, // After the next comma.
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "f", 1, 43, true,
    "FID field() film film_actor film_category film_id film_info film_list film_text find_in_set() "
    "first_name floor() format() found_rows() from_days() from_unixtime()",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "fi", 1, 44, true,
    "FID field() film film_actor film_category film_id film_info film_list film_text find_in_set() "
    "first_name",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "fir", 1, 45, true, // Step 35.
    "first_name",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "", 1, 53, false, // After the next comma.
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "a", 1, 55, true,
    "a aa actor actor_info address",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 1, 56, true,
    "actor_id first_name last_name last_update",
    Sql_editor::CompletionWantColumns | Sql_editor::CompletionWantTables
  },
  {"", "l", 1, 57, true,
    "last_name",
    Sql_editor::CompletionWantColumns | Sql_editor::CompletionWantTables
  },
  {"", "", 1, 66, false, // After the next comma. Step 40.
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"", "c", 1, 68, true,
    "category city country customer customer_list",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "co", 1, 69, true,
    "country",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 1, 75, true, // After the dot.
    "country country_id",
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns
  },
  {"", "co", 1, 77, true,
    "country country_id",
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns
  },
  {"", "", 1, 83, false, // Line end. Step 45.
    "",
    Sql_editor::CompletionWantKeywords
  },
};

static ac_test_entry simple_valid_line2[] = {
  {"select count(distinct a.actor_id), phone, first_name, a.last_name, country.country \n"
    "from sakila.actor a, address aa, country \n"
    "where (a.actor_id = 0 and country_id > 0) \n"
    "group by actor_id", "", 2, 0, false,
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "f", 2, 1, true,
    "false fast faults fetch fields file first fixed float float4 float8 for force foreign "
    "format found from full fulltext function",
    Sql_editor::CompletionWantKeywords
  },
  {"", "fr", 2, 2, true,
    "from",
    Sql_editor::CompletionWantKeywords
  },
  {"", "fro", 2, 3, true,
    "from",
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 2, 5, true,
    "actor actor_info address category city country customer customer_list film film_actor "
    "film_category film_list film_text information_schema inventory language mysql "
    "nicer_but_slower_film_list payment performance_schema rental sakila sales_by_film_category "
    "sales_by_store staff staff_list store",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "s", 2, 6, true, // Step 5.
    "sakila sales_by_film_category sales_by_store staff staff_list store",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "sa", 2, 7, true,
    "sakila sales_by_film_category sales_by_store",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 2, 12, true, // Continue after dot.
    "actor actor_info address category city country customer customer_list film film_actor "
    "film_category film_list film_text inventory language "
    "nicer_but_slower_film_list payment rental sales_by_film_category "
    "sales_by_store staff staff_list store",
    Sql_editor::CompletionWantTables
  },
  {"", "a", 2, 13, true,
    "actor actor_info address",
    Sql_editor::CompletionWantTables
  },
  {"", "ac", 2, 14, true,
    "actor actor_info",
    Sql_editor::CompletionWantTables
  },
  {"", "", 2, 18, false, // Continue at the alias (after the space). Step 10.
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "a", 2, 19, true,
    "accessible action add after against aggregate algorithm all and any as asc asensitive at "
    "authors auto_increment autoextend_size avg avg_row_length",
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 2, 21, true, // Second table. Here we do similar steps as for the first table.
    "actor actor_info address category city country customer customer_list film film_actor "
    "film_category film_list film_text information_schema inventory language mysql "
    "nicer_but_slower_film_list payment performance_schema rental sakila sales_by_film_category "
    "sales_by_store staff staff_list store",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "a", 2, 22, true,
    "actor actor_info address",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "ad", 2, 23, true,
    "address",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 2, 29, false, // Continue at the alias (after the space). Step 15.
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "a", 2, 30, true,
    "accessible action add after against aggregate algorithm all and any as asc asensitive at "
    "authors auto_increment autoextend_size avg avg_row_length",
    Sql_editor::CompletionWantKeywords
  },
  {"", "aa", 2, 31, false,
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 2, 33, true, // Third table. Similar steps again.
    "actor actor_info address category city country customer customer_list film film_actor "
    "film_category film_list film_text information_schema inventory language mysql "
    "nicer_but_slower_film_list payment performance_schema rental sakila sales_by_film_category "
    "sales_by_store staff staff_list store",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "c", 2, 34, true,
    "category city country customer customer_list",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "co", 2, 35, true, // Step 20.
    "country",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
  {"", "", 2, 41, false, // Continue after the space.
    "",
    Sql_editor::CompletionWantKeywords
  },
};
 
static ac_test_entry simple_valid_line3[] = {
  {"select count(distinct a.actor_id), phone, first_name, a.last_name, country.country \n"
    "from sakila.actor a, address aa, country\n"
    "where (a.actor_id = 0 and country_id > 0) \n"
    "group by actor_id", "", 3, 0, false,
    "",
    Sql_editor::CompletionWantKeywords
  },
  {"", "w", 3, 1, true,
    "wait warnings when where while with work wrapper write",
    Sql_editor::CompletionWantKeywords
  },
  {"", "wh", 3, 2, true,
    "when where while",
    Sql_editor::CompletionWantKeywords
  },
  {"", "whe", 3, 3, true,
    "when where",
    Sql_editor::CompletionWantKeywords
  },
  {"", "", 3, 6, false, // Continue at the open parenthesis (after the space).
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantKeywords // Keywords possible after where: interval, exists, row, match, case, cast etc.
  },
  {"", "", 3, 7, false, // Step 5.
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords | Sql_editor::CompletionWantSelect // Select for sub queries.
  },
  {"", "a", 3, 8, true,
    "a aa actor actor_info address",
    Sql_editor::CompletionWantSchemas | Sql_editor::CompletionWantTables
  },
};

/**
 * Runs tests from a list of ac test entries in the given range.
 */
void run_simple_query_tests(Sql_editor::Ref editor, int part, ac_test_entry *test_input, size_t start, size_t count)
{
  for (size_t i = start; i < count; i++)
  {
    Sql_editor::AutoCompletionContext context;
    std::vector<std::string> expected_entries = base::split(test_input[i].entries, " ");

    // In order to avoid duplicating the test query we use an empty string as indicator that
    // the start query should be used instead. However, if we really need an empty query we mark
    // this accordingly.
    context.statement = test_input[i].query.empty() ? test_input[0].query : test_input[i].query;
    if (context.statement == "§")
      context.statement = "";
    context.typed_part = test_input[i].typed_part;
    context.line = test_input[i].line;
    context.offset = test_input[i].offset;

    std::string message = base::strfmt("Part %u, step %u", part, i);
    editor->create_auto_completion_list(context);
    match_included_parts(message, context, test_input[i].parts);
    if (test_input[i].check_entries)
    {
      std::vector<std::pair<int, std::string> > entries = editor->update_auto_completion(context.typed_part);
      check_ac_entries(message, entries, expected_entries);
    }
  }
}

/**
 * Collecting AC info for each position in a simple but typical statement.
 * Since this statement is valid we don't need to consider error conditions here.
 */
TEST_FUNCTION(15)
{
  run_simple_query_tests(_sql_editor, 1, simple_valid_line1, 0, sizeof(simple_valid_line1) / sizeof(simple_valid_line1[0]));
  run_simple_query_tests(_sql_editor, 2, simple_valid_line2, 0, sizeof(simple_valid_line2) / sizeof(simple_valid_line2[0]));
  run_simple_query_tests(_sql_editor, 3, simple_valid_line3, 0, sizeof(simple_valid_line3) / sizeof(simple_valid_line3[0]));
}

//--------------------------------------------------------------------------------------------------

static ac_test_entry query_typing_test_data[] = {
  {"§", "", 1, 0, false,
    "",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"s", "s", 1, 1, true,
    "savepoint select set show start stop",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"se", "se", 1, 2, true,
    "select set",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"sel", "sel", 1, 3, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"sele", "sele", 1, 4, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"selec", "selec", 1, 5, true, // 5
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"select", "select", 1, 6, true,
    "select",
    Sql_editor::CompletionWantMajorKeywords
  },
  {"select ", "", 1, 7, false,
    "",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"select c", "c", 1, 8, true,
    "cast() category category_id ceil() ceiling() char_length() character_length() city city_id "
    "coalesce() concat() concat_ws() connection_id() conv() convert() cos() cot() count() country "
    "country_id create_date curdate() current_user() curtime() customer customer_id customer_list",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"select co", "co", 1, 9, true,
    "coalesce() concat() concat_ws() connection_id() conv() convert() cos() cot() count() country "
    "country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"select cou", "cou", 1, 10, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"select coun", "coun", 1, 11, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
  {"select count", "count", 1, 12, true,
    "count() country country_id",
    Sql_editor::CompletionWantFunctions | Sql_editor::CompletionWantSchemas |
    Sql_editor::CompletionWantTables | Sql_editor::CompletionWantColumns |
    Sql_editor::CompletionWantExprStartKeywords
  },
};

/**
 * Collecting AC info for the same statement as in TC 15, but this time as if we were writing
 * each letter. This usually causes various parse errors to appear, but we want essentially the same
 * output as for the valid statement (except for not-yet-written references).
 */
TEST_FUNCTION(20)
{
  run_simple_query_tests(_sql_editor, 1, query_typing_test_data, 0, sizeof(query_typing_test_data) / sizeof(query_typing_test_data[0]));
}

END_TESTS