#pragma once

#include <string>
#include <sstream>
#include <stdexcept>
#include <iostream>
#include <functional>

#include <util/util.hh>
#include <util/enum_text.hh>
#include <parser/mysql_type_metadata.hh>

template<class T>
std::string stringify_ptr(T * x) {
    if (x == NULL ) {
        return "NULL";
    } else {
        return x->stringify();
    }
}

static inline std::ostream&
operator<<(std::ostream &out, const String &s)
{
    return out << std::string(s.ptr(), s.length());
}

static inline std::ostream&
operator<<(std::ostream &out, const Item &i)
{
    String s;
    const_cast<Item &>(i).print(&s, QT_ORDINARY);

    return out << s;
}

static inline std::ostream&
operator<<(std::ostream &out, Alter_drop &adrop)
{
    // FIXME: Needs to support escapes.
    return out << adrop.name;
}

template<class T>
class List_noparen: public List<T> {};

template<class T>
static inline List_noparen<T>&
noparen(List<T> &l)
{
    return *(List_noparen<T>*) (&l);
}

template<class T>
static inline std::ostream&
operator<<(std::ostream &out, List_noparen<T> &l)
{
    bool first = true;
    for (auto it = List_iterator<T>(l);;) {
        T *i = it++;
        if (!i)
            break;

        if (!first)
            out << ", ";
        out << *i;
        first = false;
    }
    return out;
}

//Strings needs apostrophes
//template<>
static inline std::ostream&
operator<<(std::ostream &out, List_noparen<String> &l)
{
    bool first = true;
    for (auto it = List_iterator<String>(l);;) {
        String *i = it++;
        if (!i)
            break;

        if (!first) {
            out << ", ";
        }
        out << "'" << *i << "'";
        first = false;
    }
    return out;
}

template<class T>
static inline std::ostream&
operator<<(std::ostream &out, List<T> &l)
{
    return out << "(" << noparen(l) << ")";
}

static inline std::ostream&
operator<<(std::ostream &out, const SELECT_LEX &select_lex)
{
    // TODO(stephentu): mysql's select print is
    // missing some parts, like procedure, into outfile,
    // for update, and lock in share mode
    String s;
    const_cast<SELECT_LEX &>(select_lex).print(current_thd, &s,
                                               QT_ORDINARY);
    return out << s;
}

static inline std::ostream&
operator<<(std::ostream &out, SELECT_LEX_UNIT &select_lex_unit)
{
    String s;
    select_lex_unit.print(&s, QT_ORDINARY);
    return out << s;
}

// FIXME: Combine with vector_join.
template <typename T>
std::string ListJoin(List<T> lst, std::string delim,
                     std::function<std::string(T)> finalize)
{
    std::ostringstream accum;

    auto it = List_iterator<T>(lst);
    for (T *element = it++; element; element = it++) {
        std::string finalized_element = finalize(*element);
        accum << finalized_element;
        accum << delim;
    }

    std::string output, str_accum = accum.str();
    if (str_accum.length() > 0) {
        output = str_accum.substr(0, str_accum.length() - delim.length());
    } else {
        output = str_accum;
    }

    return output;
}

static std::ostream&
operator<<(std::ostream &out, CHARSET_INFO & ci) {
    out << ci.csname;
    return out;
}

static std::ostream&
operator<<(std::ostream &out, Create_field &f)
{

    // emit field name + type definition
    out << f.field_name << " " << MySQLTypeToText(f);

    // emit extra length info if necessary
    switch (f.sql_type) {

    // optional (length) cases
    case MYSQL_TYPE_BIT:
    case MYSQL_TYPE_TINY:
    case MYSQL_TYPE_SHORT:
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_LONGLONG:
    case MYSQL_TYPE_STRING:
        if (f.length)
            out << "(" << f.length << ")";
        break;

    // optional (length, decimal) cases
    case MYSQL_TYPE_FLOAT:
    case MYSQL_TYPE_DOUBLE:
        if (isRealEncoded(f)) {
            break;
        }

        if (f.length && f.decimals) {
            out << "(" << f.length << ", " << f.decimals << ")";
        }
        break;

    // mandatory (length) cases
    case MYSQL_TYPE_VARCHAR:
    case MYSQL_TYPE_VAR_STRING:
        // mysql accepts zero length fields
        if (0 == f.length) {
            std::cerr << "WARN: Creating zero length field!" << std::endl;
        }
        out << "(" << f.length << ")";
        break;

    // optional (length [, decimal]) cases
    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
        if (f.length) {
            out << "(" << f.length;
            if (f.decimals) {
                out << ", " << f.decimals << ")";
            } else {
                out << ")";
            }
        }
        break;

    // (val1, val2, ...) cases
    case MYSQL_TYPE_ENUM:
    case MYSQL_TYPE_SET:
        out << f.interval_list;
        break;

    default:
        break;
    }

    // emit extra metadata
    switch (f.sql_type) {
    // optional unsigned zerofill cases
    case MYSQL_TYPE_TINY:
    case MYSQL_TYPE_SHORT:
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_LONGLONG:
    case MYSQL_TYPE_DOUBLE:
    case MYSQL_TYPE_FLOAT:
    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
        if (f.flags & UNSIGNED_FLAG) {
            out << " unsigned";
            if (f.flags & ZEROFILL_FLAG) {
                out << " zerofill";
            }
        }
        break;

    // optional character set and collate parameters
    case MYSQL_TYPE_STRING:
    case MYSQL_TYPE_VARCHAR:
    case MYSQL_TYPE_TINY_BLOB:
    case MYSQL_TYPE_BLOB:
    case MYSQL_TYPE_MEDIUM_BLOB:
    case MYSQL_TYPE_LONG_BLOB:
        break;
    case MYSQL_TYPE_ENUM:
    case MYSQL_TYPE_SET:
        if (f.charset) {
            if (f.charset->csname) {
                out << " character set " << f.charset->csname;
            }
            if (f.charset->name) {
                out << " collate '" << f.charset->name << "'";
            }
        }

        break;

    default:
        break;
    }

    // not null or null
    if (f.flags & NOT_NULL_FLAG) {
        out << " not null";
    }

    // default value
    if (f.def) {
        out << " default " << *f.def;
    }

    // auto increment
    if (f.flags & AUTO_INCREMENT_FLAG) {
        out << " auto_increment";
    }

    // ignore comments

    // TODO(stephentu): column_format?

    // reference_definition

    return out;
}

static std::ostream&
operator<<(std::ostream &out, Key_part_spec &k)
{
    // field name
    std::string field_name(k.field_name.str, k.field_name.length);

    out << field_name;

    // length
    if (k.length) {
        out << " (" << k.length << ")";
    }

    // TODO: asc/desc

    return out;
}

inline std::string
convert_lex_str(const LEX_STRING &l)
{
    return std::string(l.str, l.length);
}

inline std::string
convert_lex_str(const LEX_CSTRING &l)
{
    return std::string(l.str, l.length);
}
inline std::string
nullAccomodationConvertLexStr(const LEX_STRING &l)
{
    if (NULL == l.str) {
        return "";
    }

    return convert_lex_str(l);
}

inline LEX_STRING
string_to_lex_str(const std::string &s)
{
    char *const cstr = current_thd->strdup(s.c_str());
    return LEX_STRING({cstr, s.length()});
}

inline std::string
String_to_std_string(const String &s)
{
    std::cout << s.ptr() << std::endl;
    return std::string(s.ptr(), s.length());
}

inline std::string
printItem(const Item &i)
{
    std::ostringstream o;
    o << i;
    return o.str();
}

static std::ostream&
operator<<(std::ostream &out, enum legacy_db_type db_type) {
    out << "InnoDB";
    /*
    switch (db_type) {
    case DB_TYPE_INNODB: {out << "InnoDB"; break;}
    case DB_TYPE_ISAM: {out << "ISAM"; break;}
    case DB_TYPE_MYISAM: {out << "MYISAM"; break;}
    case DB_TYPE_CSV_DB: {out << "CSV"; break;}
    default:
        assert_s(false, "stringify does not know how to print db_type "
                        + strFromVal((uint)db_type));
    }
    */

    return out;
}
static std::ostream&
operator<<(std::ostream &out, Key &k)
{
    // TODO: constraint

    // key type
    const char *kname = NULL;
    switch (k.type) {
    case Key::PRIMARY     : kname = "PRIMARY KEY"; break;
    case Key::UNIQUE      : kname = "UNIQUE KEY";      break;
    case Key::MULTIPLE    : kname = "INDEX";       break;
    case Key::FULLTEXT    : kname = "FULLTEXT";    break;
    case Key::SPATIAL     : kname = "SPATIAL";     break;
    case Key::FOREIGN_KEY : kname = "FOREIGN KEY"; break;
    default:
        thrower() << "Unsupported key type " << std::to_string(k.type);
    }
    out << kname;

    // index_name
    const std::string key_name(k.name.str, k.name.length);
    if (!key_name.empty()) {
        out << " " << key_name;
    }

    // column list
    out << " " << k.columns;

    // TODO: index_option

    // foreign key references
    if (k.type == Key::FOREIGN_KEY) {
        auto fk = static_cast< Foreign_key& >(k);
        out << " references ";
        const std::string &db_str =
            nullAccomodationConvertLexStr(fk.ref_table->db);
        const std::string &tl_str(convert_lex_str(fk.ref_table->table));
        if (!db_str.empty()) {
            out << "" << db_str << "." << tl_str << "";
        } else {
            out << "" << tl_str << "";
        }
        out << " " << fk.ref_columns;
    }

    return out;
}

static void
do_create_table(std::ostream &out, LEX &lex)
{
    assert(lex.sql_command == SQLCOM_CREATE_TABLE);

    THD *t = current_thd;

    // table name
    TABLE_LIST *tl = lex.select_lex.table_list.first;
    {
        String s;
        tl->print(t, &s, QT_ORDINARY);
        out << "create ";

        // temporary
        if (lex.create_info.options & HA_LEX_CREATE_TMP_TABLE) {
            out << "temporary ";
        }

        out << "table ";

        // if not exists
        if (lex.create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) {
            out << "if not exists ";
        }

        out << tl->table_name << " ";
    }

    if (lex.create_info.options & HA_LEX_CREATE_TABLE_LIKE) {
        // create table ... like tbl_name
        out << " like ";
        TABLE_LIST *select_tables=
          lex.create_last_non_select_table->next_global;
        out << select_tables->alias;
    } else {
        auto cl = lex.alter_info.create_list;
        auto kl = lex.alter_info.key_list;

        // columns
        out << "(" << noparen(cl);

        if (cl.elements && kl.elements) out << ", ";

        // keys
        out << noparen(kl) << ")";

        // TODO(stephentu): table options
        //if (lex.create_info.used_fields) {
        //    mysql_thrower() << "WARNING: table options currently unsupported";
        //}

        // create table ... select ...
        // this strange test for this condition comes from:
        //   create_table_set_open_action_and_adjust_tables()
        if (lex.select_lex.item_list.elements) {
            out << " " << lex.select_lex;
        }

        out << " AUTO_INCREMENT=" << lex.create_info.auto_increment_value;
        if (lex.create_info.db_type) {
            out << " ENGINE=" << lex.create_info.db_type->db_type;
        }
        if (lex.create_info.table_charset) {
            out << " CHARSET=" << *lex.create_info.table_charset;
        }
        if (lex.create_info.default_table_charset) {
            out << " DEFAULT CHARSET="
                << *lex.create_info.default_table_charset;
        }
    }
}

static std::string prefix_drop_column(Alter_drop adrop) {
    std::ostringstream ss;
    ss << "DROP COLUMN " << adrop;
    return ss.str();
}

static std::string prefix_drop_index(Alter_drop adrop) {
    std::ostringstream ss;
    if (equalsIgnoreCase(adrop.name, "PRIMARY")) {
        ss << "DROP PRIMARY KEY";
    } else {
        ss << "DROP INDEX " << adrop;
    }
    return ss.str();
}

static std::string prefix_add_column(Create_field cf) {
    std::ostringstream ss;
    ss << "ADD COLUMN " << cf;
    return ss.str();
}

static std::function<std::string(Key_part_spec)>
do_prefix_add_index()
{
    std::function<std::string(Key_part_spec key_part)> fn =
        [] (Key_part_spec key_part)
        {
            std::ostringstream ss;
            ss << key_part;
            return ss.str();
        };

    return fn;
}

static std::string
prefix_add_index(Key key)
{
    const std::string index_name = convert_lex_str(key.name);
    std::ostringstream key_output;
    if (Key::PRIMARY == key.type) {
        key_output << " ADD PRIMARY KEY (";
    } else {
        key_output << " ADD INDEX " << index_name << " (";
    }
    key_output << ListJoin<Key_part_spec>(key.columns, ",",
                                          do_prefix_add_index())
               << ")";

    return key_output.str();
}

static std::string
enableOrDisableKeysOutput(const LEX &lex)
{
    auto key_status = lex.alter_info.keys_onoff;
    const std::string &out =
        TypeText<enum_enable_or_disable>::toText(key_status) +
        " KEYS";

    return out;
}

/*
static std::string
prettyLockType(enum thr_lock_type lock_type)
{
    switch (lock_type) {
        case TL_READ:
        case TL_READ_NO_INSERT:
            return "READ";
        case TL_WRITE:
        case TL_WRITE_DEFAULT:
            return "WRITE";
        default:
            // FIXME: Use TEST_TextMessageError
            std::cerr << "Unsupported lock type: " << lock_type
                      << std::endl;
            assert(false);
    }
}
*/

static inline std::ostream&
operator<<(std::ostream &out, LEX &lex)
{
    String s;
    THD *t = current_thd;

    switch (lex.sql_command) {
    case SQLCOM_SELECT:
        out << lex.unit;
        break;

    case SQLCOM_UPDATE:
    case SQLCOM_UPDATE_MULTI:
        {
            TABLE_LIST tl;
            st_nested_join nj;
            tl.nested_join = &nj;
            nj.join_list = lex.select_lex.top_join_list;
            tl.print(t, &s, QT_ORDINARY);
            out << "update " << s;
        }

        if (lex.query_tables->lock_type == TL_WRITE_LOW_PRIORITY) {
            out << " low_priority ";
        }

        if (lex.ignore) {
            out << " ignore ";
        }

        {
            auto ii = List_iterator<Item>(lex.select_lex.item_list);
            auto iv = List_iterator<Item>(lex.value_list);
            for (bool first = true;; first = false) {
                Item *i = ii++;
                Item *v = iv++;
                if (!i || !v)
                    break;
                if (first)
                    out << " set ";
                else
                    out << ", ";
                out << *i << "=" << *v;
            }
        }

        if (lex.select_lex.where)
            out << " where " << *lex.select_lex.where;

        if (lex.sql_command == SQLCOM_UPDATE) {
            if (lex.select_lex.order_list.elements) {
                String s0;
                lex.select_lex.print_order(&s0, lex.select_lex.order_list.first,
                                           QT_ORDINARY);
                out << " order by " << s0;
            }

            {
                String s0;
                lex.select_lex.print_limit(t, &s0, QT_ORDINARY);
                out << s0;
            }
        }
        break;

    case SQLCOM_INSERT:
    case SQLCOM_INSERT_SELECT:
    case SQLCOM_REPLACE:
    case SQLCOM_REPLACE_SELECT:
        {
            bool is_insert =
                lex.sql_command == SQLCOM_INSERT ||
                lex.sql_command == SQLCOM_INSERT_SELECT;
            bool no_select =
                lex.sql_command == SQLCOM_INSERT ||
                lex.sql_command == SQLCOM_REPLACE;
            const char *cmd = is_insert ? "insert" : "replace";
            out << cmd << " ";

            switch (lex.query_tables->lock_type) {
            case TL_WRITE_LOW_PRIORITY:
                out << "low_priority ";
                break;
            case TL_WRITE:
                out << "high_priority ";
                break;
            case TL_WRITE_DELAYED:
                out << "delayed ";
                break;
            default:
                ; // no-op
                break;
            }

            if (lex.ignore) {
                out << "ignore ";
            }

            lex.select_lex.table_list.first->print(t, &s, QT_ORDINARY);
            out << "into " << s;
            if (lex.field_list.head())
                out << " " << lex.field_list;
            if (no_select) {
                if (lex.many_values.head())
                    out << " values " << noparen(lex.many_values);
            } else {
                out << " " << lex.select_lex;
            }
            if (is_insert && lex.duplicates == DUP_UPDATE) {
                out << " on duplicate key update ";
                auto ii = List_iterator<Item>(lex.update_list);
                auto iv = List_iterator<Item>(lex.value_list);
                for (bool first = true;; first = false) {
                    Item *i = ii++;
                    Item *v = iv++;
                    if (!i || !v)
                        break;
                    if (!first)
                        out << ", ";
                    out << *i << "=" << *v;
                }
            }
        }
        break;

    case SQLCOM_DELETE:
    case SQLCOM_DELETE_MULTI:
        out << "delete ";

        if (lex.query_tables->lock_type == TL_WRITE_LOW_PRIORITY) {
            out << "low_priority ";
        }

        if (lex.select_lex.options & OPTION_QUICK) {
            out << "quick ";
        }

        if (lex.ignore) {
            out << "ignore ";
        }

        if (lex.sql_command == SQLCOM_DELETE) {
            lex.query_tables->print(t, &s, QT_ORDINARY);
            out << "from " << s;
            if (lex.select_lex.where)
                out << " where " << *lex.select_lex.where;
            if (lex.select_lex.order_list.elements) {
                String s0;
                lex.select_lex.print_order(&s0, lex.select_lex.order_list.first,
                                           QT_ORDINARY);
                out << " order by " << s0;
            }
            {
                String s0;
                lex.select_lex.print_limit(t, &s0, QT_ORDINARY);
                out << s0;
            }
        } else {
            assert(SQLCOM_DELETE_MULTI == lex.sql_command);

            // the alias hackery is necesary because we don't want to
            // print a table with it's alias; we want to print _either_
            // the real table name or the alias;
            // TABLE_LIST::print(...) will print both together
            {
                TABLE_LIST *tbl = lex.auxiliary_table_list.first;
                for (bool f = true; tbl; tbl = tbl->next_local,f = false) {
                    out << (f ? "" : ", ");
                    if (true == tbl->is_alias) {
                        out << quoteText(tbl->alias);
                    } else {
                        String s0;
                        tbl->print(t, &s0, QT_ORDINARY);
                        out << s0;
                    }
                }
            }
            out << " from ";

            {
                String s0;

                // code ripped then mutilated from print_join(...)
                // in sql/sql_select.cc
                TABLE_LIST *table = lex.query_tables;
                // print the first table
                table->print(current_thd, &s0, QT_ORDINARY);
                while ((table = table->next_local)) {
                    // print join type
                    if (table->outer_join)
                        s0.append(STRING_WITH_LEN(" left join "));
                    else if (table->straight)
                        s0.append(STRING_WITH_LEN(" straight_join "));
                    else
                        s0.append(STRING_WITH_LEN(" join "));

                    table->print(current_thd, &s0, QT_ORDINARY);

                    // print on clause
                    if (table->on_expr)  {
                      s0.append(STRING_WITH_LEN(" on("));
                      table->on_expr->print(&s0, QT_ORDINARY);
                      s0.append(')');
                    }
                }

               out << s0;
            }

            if (lex.select_lex.where)
                out << " where " << *lex.select_lex.where;
        }
        break;

    case SQLCOM_CREATE_TABLE:
        do_create_table(out, lex);
        break;

    case SQLCOM_DROP_TABLE:
        out << "drop ";
        if (lex.drop_temporary) {
            out << "temporary ";
        }
        out << "table ";
        if (lex.drop_if_exists) {
            out << "if exists ";
        }
        // table list
        {
            TABLE_LIST *tbl = lex.select_lex.table_list.first;
            for (bool f = true; tbl; tbl = tbl->next_local, f = false) {
                String s0;
                tbl->print(t, &s0, QT_ORDINARY);
                out << (f ? "" : ", ") << s0;
            }
        }
        if (lex.drop_mode == DROP_RESTRICT) {
          out << " restrict";
        } else if (lex.drop_mode == DROP_CASCADE) {
          out << " cascade";
        }
        break;

    case SQLCOM_CREATE_DB:
        out << "create database ";
        if (lex.create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) {
            out << "if not exists ";
        }

        out << quoteText(convert_lex_str(lex.name));
        break;

    case SQLCOM_CHANGE_DB:
        out << "USE " << quoteText(lex.select_lex.db);
        break;

    case SQLCOM_DROP_DB:
        out << "drop database ";
        if (lex.drop_if_exists) {
            out << "if exists ";
        }

        out << quoteText(convert_lex_str(lex.name));
        break;

    case SQLCOM_BEGIN:
        out << "START TRANSACTION";
        if (lex.start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
            out << " WITH CONSISTENT SNAPSHOT";
        break;

    case SQLCOM_COMMIT:
        out << "COMMIT";
        if (lex.tx_chain != TVL_UNKNOWN)
            out << " AND" << (lex.tx_chain == TVL_NO ? " NO" : "") << " CHAIN";
        if (lex.tx_release != TVL_UNKNOWN)
            out << (lex.tx_release == TVL_NO ? " NO" : "") << " RELEASE";
        break;

    case SQLCOM_ROLLBACK:
        out << "ROLLBACK";
        if (lex.tx_chain != TVL_UNKNOWN)
            out << " AND" << (lex.tx_chain == TVL_NO ? " NO" : "") << " CHAIN";
        if (lex.tx_release != TVL_UNKNOWN)
            out << (lex.tx_release == TVL_NO ? " NO" : "") << " RELEASE";
        break;

    /*
     * TODO: Support mixed operations.
     * > Will likely require a filter.
     *
     * You can issue multiple ADD, ALTER, DROP, and CHANGE clauses in a
     * single ALTER TABLE statement seperated by columns.  This is a MySQL
     * extension to standard SQL, which permits only one of each clause
     * per ALTER TABLE statement.
     *
     * ALTER TABLE t DROP COLUMN c, DROP COLUMN d;
     */
    case SQLCOM_CREATE_INDEX:
    case SQLCOM_ALTER_TABLE: {
        out << "ALTER TABLE";
        lex.select_lex.table_list.first->print(t, &s, QT_ORDINARY);
        out << " " << s;

        bool prev = false;
        // TODO: Support other flags.
        //  > ALTER_CHANGE_COLUMN, ALTER_FOREIGN_KEY
        if (lex.alter_info.flags & ALTER_DROP_COLUMN) {
            out << " " << ListJoin<Alter_drop>(lex.alter_info.drop_list,
                                               ",", prefix_drop_column);
            prev = true;
        }

        if (lex.alter_info.flags & ALTER_ADD_COLUMN) {
            if (true == prev) {
                out << ", ";
            }
            out << " " <<ListJoin<Create_field>(lex.alter_info.create_list,
                                                 ",", prefix_add_column);
            prev = true;
        }

        if (lex.alter_info.flags & ALTER_ADD_INDEX) {
            if (true == prev) {
                out << ", ";
            }
            out << " " << ListJoin<Key>(lex.alter_info.key_list, ",",
                                        prefix_add_index);
            prev = true;
        }

        if (lex.alter_info.flags & ALTER_DROP_INDEX) {
            if (true == prev) {
                out << ", ";
            }
            out << " " << ListJoin<Alter_drop>(lex.alter_info.drop_list,
                                               ",", prefix_drop_index);
            prev = true;
        }

        if (lex.alter_info.flags & ALTER_KEYS_ONOFF) {
            if (true == prev) {
                out << ", ";
            }
            out << " " << enableOrDisableKeysOutput(lex);
            prev = true;
        }

        break;
    }

    case SQLCOM_LOCK_TABLES:
        // HACK: prettyLockType(...) should be used.
        out << "do 0";
        break;

    case SQLCOM_UNLOCK_TABLES:
        out << " UNLOCK TABLES";
        break;

    case SQLCOM_SHOW_COLLATIONS:
        out << "SHOW COLLATION";
        break;

    case SQLCOM_SET_OPTION:
    case SQLCOM_SHOW_DATABASES:
    case SQLCOM_SHOW_TABLES:
    case SQLCOM_SHOW_FIELDS:
    case SQLCOM_SHOW_KEYS:
    case SQLCOM_SHOW_VARIABLES:
    case SQLCOM_SHOW_STATUS:
    case SQLCOM_SHOW_STORAGE_ENGINES:
        /* placeholders to make analysis work.. */
        out << ".. type " << lex.sql_command << " query ..";
        break;

    default:
        thrower() << "unhandled sql command " << lex.sql_command;
    }

    return out;
}

