/*
 * Copyright (c) 2017-2024 OARC, Inc.
 * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
 * All rights reserved.
 *
 * This file is part of PacketQ.
 *
 * PacketQ 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PacketQ 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 PacketQ.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "sql.h"
#include "output.h"
#include "packet_handler.h"
#include "packetq.h"
#include "reader.h"

#include <unordered_map>
#include <utility>
#include <vector>
#ifdef WIN32
#include <windows.h>
#endif
#ifdef HAVE_LIBMAXMINDDB
#include <maxminddb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static MMDB_s* __cc_mmdb  = 0;
static MMDB_s* __asn_mmdb = 0;
#endif

namespace packetq {

bool verbose = false;

int g_allocs = 0;

Column* Table::add_column(const char* name, const char* type, int id, bool hidden)
{
    if (!type)
        return add_column(name, Coltype::_text, id, hidden);
    else if (strcmp(type, "bool") == 0)
        return add_column(name, Coltype::_bool, id, hidden);
    else if (strcmp(type, "int") == 0)
        return add_column(name, Coltype::_int, id, hidden);
    else if (strcmp(type, "float") == 0)
        return add_column(name, Coltype::_float, id, hidden);
    else
        return add_column(name, Coltype::_text, id, hidden);
}

Column* Table::add_column(const char* name, Coltype::Type type, int id, bool hidden)
{
    Column* col   = new Column(name, type, id, hidden);
    col->m_offset = Table::align(m_curpos, col->m_def.m_align);
    m_curpos      = col->m_offset + col->m_def.m_size;
    if (type == Coltype::_text)
        m_text_column_offsets.push_back(col->m_offset);
    m_cols.push_back(col);
    return col;
}

void Table::delete_row(Row* row)
{
    row->decref_text_columns(m_text_column_offsets);
    m_row_allocator->deallocate(row);
}

Row* Table::create_row()
{
    if (!m_row_allocator) {
        m_rsize         = sizeof(Row) - ROW_DUMMY_SIZE; // exclude the dummy
        m_dsize         = m_curpos;
        m_row_allocator = new Allocator<Row>(m_rsize + m_dsize, 10000);
    }

    Row* r = m_row_allocator->allocate();
    r->zero_text_columns(m_text_column_offsets);
    return r;
}

void Table::add_row(Row* row)
{
    m_rows.push_back(row);
}

int g_comp = 0;

void Ordering_terms::compile(const std::vector<Table*>& tables, const std::vector<int>& search_order, Query& q)
{
    for (auto it = m_terms.begin(); it != m_terms.end(); it++) {
        OP* op   = it->m_op;
        it->m_op = op->compile(tables, search_order, q);
    }
}

class Sorter {
public:
    Sorter(Ordering_terms& order)
        : m_order(order)
    {
    }
    bool operator()(Row* ia, Row* ib)
    {
        // this works under the assumption that the ordering terms have
        // been compiled with only one table so the row index i is 0
        Row** ia_rows = &ia;
        Row** ib_rows = &ib;

        for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
            g_comp++;

            OP* op = it->m_op;
            op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
            op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
            int res = m_a.cmp(m_b);
            if (res < 0)
                return true;
            if (res > 0)
                return false;
        }
        return false;
    }
    bool eq(Row* ia, Row* ib)
    {
        Row** ia_rows = &ia;
        Row** ib_rows = &ib;

        for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
            g_comp++;

            OP* op = it->m_op;
            op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
            op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
            int res = m_a.cmp(m_b);
            if (res != 0)
                return false;
        }
        return true;
    }
    int cmp(Row* ia, Row* ib)
    {
        Row** ia_rows = &ia;
        Row** ib_rows = &ib;

        for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
            g_comp++;

            OP* op = it->m_op;
            op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
            op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
            int res = m_a.cmp(m_b);
            if (res != 0)
                return res;
        }
        return 0;
    }
    Ordering_terms& m_order;
    Variant         m_a, m_b;
};

struct Stki {
    int s;
    int l;
    int b;
};

struct Spkt {
    Variant cache;
    int     row;
};

class Per_sort {
public:
    struct Tlink {
        Tlink()
            : m_next(0)
            , row(0)
            , m_eq(0)
        {
        }

        Tlink* get_eq() { return m_eq; }
        void   reset()
        {
            m_next = 0;
            row    = 0;
            m_eq   = 0;
        }
        void add_eq(Tlink* o)
        {
            if (!o->m_eq) {
                // add single
                if (!m_eq) {
                    // as first
                    m_eq         = o;
                    o->m_eq_last = o;
                    return;
                }
                // to list
                m_eq->m_eq_last->m_eq = o;
                m_eq->m_eq_last       = o;
                return;
            } else {
                // add list
                if (!m_eq) {
                    // as first
                    m_eq         = o;
                    o->m_eq_last = o->m_eq->m_eq_last;
                    return;
                }
                // to list
                m_eq->m_eq_last->m_eq = o;
                m_eq->m_eq_last       = o->m_eq->m_eq_last;
            }
        }
        union {
            Tlink* m_next;
            Tlink* m_eq_last;
        };
        Row*    row;
        Variant cache;

    private:
        Tlink* m_eq;
    };
    struct List {
    public:
        void reset()
        {
            m_size  = 0;
            m_fl[0] = 0;
            m_fl[1] = 0;
        }
        int    m_size;
        Tlink* m_fl[2];
    };
    Per_sort(Table& table, Ordering_terms& order)
        : m_sorter(order)
        , m_table(table)
    {
        m_escalate_sort = order.m_terms.size() > 1;
        m_asc           = order.m_terms.begin()->m_asc ? 1 : -1;
        m_op            = order.m_terms.begin()->m_op;
        memset(m_groups, 0, sizeof(m_groups));
        m_current.reset();
    }
    inline bool add_to_list(List& list, Tlink* t)
    {
        if (list.m_fl[0] == 0) {
            list.m_fl[0] = list.m_fl[1] = t;
            list.m_size                 = 1;
            return true;
        }
        list.m_fl[1]->m_next = t;
        list.m_fl[1]         = t;
        list.m_size++;
        return true;
    }
    inline bool insert_into_list(List& list, Tlink* t)
    {
        if (list.m_fl[0] == 0) {
            list.m_fl[0] = list.m_fl[1] = t;
            list.m_size                 = 1;
            return true;
        }
        int cmp0 = cmp(t, list.m_fl[0]);
        if (cmp0 == 0) {
            list.m_fl[0]->add_eq(t);
            return true;
        }
        if (cmp0 < 0) {
            t->m_next    = list.m_fl[0];
            list.m_fl[0] = t;
            list.m_size++;
            return true;
        }
        int cmp1 = cmp(t, list.m_fl[1]);
        if (cmp1 == 0) {
            list.m_fl[1]->add_eq(t);
            return true;
        }
        if (cmp1 > 0) {
            list.m_fl[1]->m_next = t;
            list.m_fl[1]         = t;
            list.m_size++;
            return true;
        }
        return false;
    }
    int cmp(Tlink* a, Tlink* b)
    {
        int cmp = a->cache.cmp(b->cache) * m_asc;
        if (cmp != 0 || !m_escalate_sort)
            return cmp;
        return m_sorter.cmp(a->row, b->row);
    }
    inline void add(Tlink* t)
    {
        if (!insert_into_list(m_current, t)) {
            insert_list(m_current);
            m_current.reset();
            insert_into_list(m_current, t);
        }
    }
    inline void insert_list(List& l)
    {
        unsigned int size = l.m_size;
        int          offs = 0;
        size >>= 1;
        while (size != 0) {
            offs++;
            size >>= 1;
        }
        if (m_groups[offs].m_size != 0) {
            List m = merge(m_groups[offs], l);
            m_groups[offs].reset();
            insert_list(m);
        } else
            m_groups[offs] = l;
    }
    List merge(List& l1, List& l2)
    {
        List r;
        r.reset();
        Tlink* a = l1.m_fl[0];
        Tlink* b = l2.m_fl[0];
        if (!a)
            return l2;
        if (!b)
            return l1;

        int size = l1.m_size + l2.m_size;

        while (a && b) {
            int c = cmp(a, b);
            if (c == 0) {
                Tlink* a2  = a;
                Tlink* b2  = b;
                a          = a->m_next;
                b          = b->m_next;
                a2->m_next = 0;
                b2->m_next = 0;
                a2->add_eq(b2);
                add_to_list(r, a2);
                size--;
            } else if (c < 0) {
                Tlink* a2  = a;
                a          = a->m_next;
                a2->m_next = 0;
                add_to_list(r, a2);
            } else {
                Tlink* b2  = b;
                b          = b->m_next;
                b2->m_next = 0;
                add_to_list(r, b2);
            }
        }
        if (a) {
            r.m_fl[1]->m_next = a;
            r.m_fl[1]         = l1.m_fl[1];
        }
        if (b) {
            r.m_fl[1]->m_next = b;
            r.m_fl[1]         = l2.m_fl[1];
        }

        l1.reset();
        l2.reset();
        r.m_size = size;
        return r;
    }

    void sort()
    {
        int table_size = (int)m_table.m_rows.size();
        if (table_size <= 1)
            return;
        auto   it    = m_table.m_rows.begin();
        Tlink* links = new Tlink[table_size];

        int i;
        for (i = 0; i < table_size; i++) {
            Tlink& r = links[i];
            r.reset();
            r.row = *it;
            // &row works under the assumption that m_op has been compiled with
            // this table only so row index is 0
            m_op->evaluate(&r.row, r.cache);
            it++;
            add(&r);
        }
        if (m_current.m_size)
            insert_list(m_current);
        List result;
        result.reset();
        for (i = 0; i < sizeof(m_groups) / sizeof(List); i++) {
            if (m_groups[i].m_size > 0) {
                result = merge(result, m_groups[i]);
                m_groups[i].reset();
            }
        }
        Tlink* p = result.m_fl[0];
        it       = m_table.m_rows.begin();
        while (p) {
            *it++    = p->row;
            Tlink* e = p->get_eq();
            while (e) {
                *it++ = e->row;
                e     = e->get_eq();
            }
            p = p->m_next;
        };

        delete[] links;
    }

    OP*    m_op;
    Sorter m_sorter;
    bool   m_escalate_sort;
    int    m_asc;
    Table& m_table;
    List   m_groups[32];
    List   m_current;
};

void Table::per_sort(Ordering_terms& order)
{
    Per_sort sort(*this, order);

    sort.sort();

    return;
}

void Table::merge_sort(Ordering_terms& order)
{
    Sorter sorter(order);

    bool escalate_sort = order.m_terms.size() > 1;
    int  asc           = order.m_terms.begin()->m_asc ? 1 : -1;
    OP*  op            = order.m_terms.begin()->m_op;

    int table_size = (int)m_rows.size();
    if (table_size <= 1)
        return;
    Row** row_ptrs = new Row*[table_size];
    Spkt* spktpool = new Spkt[table_size * 2];
    Spkt* rows[2];
    rows[0] = spktpool;
    rows[1] = &spktpool[table_size];
    auto it = m_rows.begin();

    int   i = 0;
    Spkt* r = rows[0];
    for (i = 0; i < table_size; i += 2) {

        row_ptrs[i] = *it++;
        r[i].row    = i;
        // &row works under the assumption that m_op has been compiled with
        // this table only so row index is 0
        op->evaluate(&row_ptrs[r[i].row], r[i].cache);
        if (i + 1 < table_size) {
            row_ptrs[i + 1] = *it++;
            r[i + 1].row    = i + 1;
            op->evaluate(&row_ptrs[r[i + 1].row], r[i + 1].cache);
        }
    }
    int swap = 0;

    Stki  stack[64];
    Stki* sp   = stack;
    sp->s      = 0;
    sp->l      = 2;
    sp->b      = 1;
    rows[1][0] = rows[0][1];
    rows[1][1] = rows[0][0];

    sp--;

    int npos = 0;
    while (true) {
        int start, len;
        if (sp > stack && sp->l == sp[-1].l) {
            // two equal size -> merge

            len   = sp->l <<= 1;
            start = sp[-1].s;
            swap  = sp[-1].b;
            sp--;
            sp--;
        } else {
            start = npos;
            npos += 2;
            swap = 0;
            len  = 2;
        }

        int   cnt = start + len > table_size ? table_size - start : len;
        Spkt* s   = rows[swap];
        Spkt* d   = rows[1 - swap];
        if (cnt > 0) {
            int p1 = start;
            int p2 = start + (len >> 1);
            int l1 = len >> 1;
            int l2 = l1;
            if (p1 + l1 > table_size)
                l1 = table_size - p1;
            if (p2 + l2 > table_size)
                l2 = table_size - p2;

            i = start;
            while (cnt-- > 0) {
                if (l1 <= 0) {
                    d[i++] = s[p2++];
                } else if (l2 <= 0) {
                    d[i++] = s[p1++];
                } else {
                    int cmp = s[p1].cache.cmp(s[p2].cache) * asc;
                    if (cmp < 0 || (cmp == 0 && escalate_sort && sorter(row_ptrs[s[p1].row], row_ptrs[s[p2].row]))) {
                        l1--;
                        d[i++] = s[p1++];
                    } else {
                        l2--;
                        d[i++] = s[p2++];
                    }
                }
            }
        }
        ++sp;
        sp->l = len;
        sp->s = start;
        sp->b = 1 - swap;
        if (len > table_size) {
            for (it = m_rows.begin(); it != m_rows.end(); it++) {
                *it = row_ptrs[(d++)->row];
            }

            break;
        }
    }
    delete[] row_ptrs;
    delete[] spktpool;
    return;
}

void Table::limit(int limit, int offset)
{
    int  count = 0;
    auto e     = m_rows.end();
    for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
        if (e != m_rows.end()) {
            delete_row(*e);
            m_rows.erase(e);
            e = m_rows.end();
        }

        int l = count++;
        if (l < offset || l >= offset + limit) {
            e = it;
        }
    }
    if (e != m_rows.end()) {
        delete_row(*e);
        m_rows.erase(e);
    }
}

void printrep(int n, char c)
{
    if (n >= 3000)
        return;
    char buf[3000];
    int  i;
    for (i = 0; i < n; i++)
        buf[i] = c;
    buf[i] = 0;
    printf("%s", buf);
}

void Table::xml()
{
    g_output.reset();
    int cols = (int)m_cols.size();

    g_output.add_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    g_output.add_string("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
    g_output.add_string("<html  xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n  <title>");
    g_output.add_string(m_name.c_str());
    g_output.add_string("</title>\n");
    g_output.add_string("<style type=\"text/css\">\n");
    g_output.add_string("    th.int   { color: #0F0C00; }\n");
    g_output.add_string("    th.float { color: #0F0900; }\n");
    g_output.add_string("    th.text  { color: #0F0600; }\n");
    g_output.add_string("    th.bool  { color: #0C0900; }\n");
    g_output.add_string("</style>\n");
    g_output.add_string("</head>\n");
    g_output.add_string("<body>\n");
    g_output.add_string("<table>\n");

    g_output.add_string("<tr>");

    for (int i = 0; i < cols; i++) {
        if (m_cols[i]->m_hidden)
            continue;

        const char* t = "";
        switch (m_cols[i]->m_type) {
        case (Coltype::_float):
            t = "float";
            break;
        case (Coltype::_int):
            t = "int";
            break;
        case (Coltype::_text):
            t = "text";
            break;
        case (Coltype::_bool):
            t = "bool";
            break;
        }
        g_output.add_string("<th class=\"");
        g_output.add_string(t);
        g_output.add_string("\">");
        g_output.add_string(m_cols[i]->m_name.c_str());
        g_output.add_string("</th>");
    }
    g_output.add_string("</tr>\n");
    for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
        g_output.add_string("<tr>");
        Row* r = *it;

        Variant v;
        for (int i = 0; i < cols; i++) {
            Column* c = m_cols[i];

            if (c->m_hidden)
                continue;

            int offset = c->m_offset;

            static const int bufsize = 100;
            char             buf[bufsize];

            g_output.add_string("<td>");
            switch (c->m_type) {
            case Coltype::_bool:
                g_output.add_string(r->access_column<bool_column>(offset) ? "1" : "0");
                break;
            case Coltype::_int:
                snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
                g_output.add_string(buf);
                break;
            case Coltype::_float:
                snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
                g_output.add_string(buf);
                break;
            case Coltype::_text:
                g_output.add_string(r->access_column<text_column>(offset)->data);
                break;
            }
            g_output.add_string("</td> ");
        }

        g_output.add_string("</tr>\n");
    }
    g_output.add_string("</table>\n");
    g_output.add_string("</body>\n");
    g_output.add_string("</html>\n");
    g_output.print();
}

void Table::json(bool trailing_comma)
{
    g_output.reset();
    int cols = (int)m_cols.size();

    g_output.add_string("  {\n    ");

    g_output.add_q_string("table_name");
    g_output.add_string(": ");
    g_output.add_q_string(m_name.c_str());
    g_output.add_string(",\n    ");

    g_output.add_q_string("query");
    g_output.add_string(": ");
    g_output.add_q_string(m_qstring.c_str());
    g_output.add_string(",\n    ");

    g_output.add_q_string("head");
    g_output.add_string(": [");

    bool append_comma = false;
    for (int i = 0; i < cols; i++) {
        if (m_cols[i]->m_hidden)
            continue;

        if (append_comma)
            g_output.add_string(",\n");
        else
            g_output.add_string("\n");
        append_comma = true;
        g_output.add_string("      { ");
        g_output.add_q_string("name");
        g_output.add_string(": ");
        g_output.add_q_string(m_cols[i]->m_name.c_str());
        g_output.add_string(",");
        g_output.add_q_string("type");
        g_output.add_string(": ");
        const char* t = "";
        switch (m_cols[i]->m_type) {
        case (Coltype::_float):
            t = "float";
            break;
        case (Coltype::_int):
            t = "int";
            break;
        case (Coltype::_text):
            t = "text";
            break;
        case (Coltype::_bool):
            t = "bool";
            break;
        }
        g_output.add_q_string(t);
        g_output.add_string(" }");
    }
    g_output.add_string("\n    ],\n    ");
    g_output.add_q_string("data");
    g_output.add_string(": [");
    bool outer_comma = false;
    for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
        if (outer_comma)
            g_output.add_string(",\n      [");
        else
            g_output.add_string("\n      [");
        outer_comma = true;
        bool comma  = false;
        Row* r      = *it;

        for (int i = 0; i < cols; i++) {
            Column* c = m_cols[i];

            if (c->m_hidden)
                continue;

            if (comma)
                g_output.add_string(",");

            comma = true;

            int              offset  = c->m_offset;
            static const int bufsize = 100;
            char             buf[bufsize];

            switch (c->m_type) {
            case Coltype::_bool:
                g_output.add_string(r->access_column<bool_column>(offset) ? "1" : "0");
                break;
            case Coltype::_int:
                snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
                g_output.add_string(buf);
                break;
            case Coltype::_float:
                snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
                g_output.add_string(buf);
                break;
            case Coltype::_text:
                g_output.add_q_string(r->access_column<text_column>(offset)->data);
                break;
            }
        }

        g_output.add_string("]");
    }
    g_output.add_string("\n    ]\n");
    if (trailing_comma) {
        g_output.add_string("  },\n");
    } else {
        g_output.add_string("  }\n");
    }
    g_output.print();
}

std::string csv_qoute_string(const std::string& s)
{
    std::string r   = "\"";
    int         len = s.length();
    for (int i = 0; i < len; i++) {
        if (s[i] == '"') {
            r += '"';
        }
        r += s[i];
    }
    r += '"';
    return r;
}

void Table::csv(bool format)
{
    int              cols = (int)m_cols.size();
    std::vector<int> col_len(cols);

    for (int i = 0; i < cols; i++)
        col_len[i] = 0;
    int   max = 0;
    char* tmp = 0;
    if (format) {
        for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
            Row* r = *it;

            for (int i = 0; i < cols; i++) {
                Column* c = m_cols[i];

                if (c->m_hidden)
                    continue;

                int len = 0;

                int              offset  = c->m_offset;
                static const int bufsize = 100;
                char             buf[bufsize];

                switch (c->m_type) {
                case Coltype::_bool:
                    len = 1;
                    break;
                case Coltype::_int:
                    snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
                    len = strlen(buf);
                    break;
                case Coltype::_float:
                    snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
                    len = strlen(buf);
                    break;
                case Coltype::_text:
                    len = csv_qoute_string(r->access_column<text_column>(offset)->data).length();
                    break;
                }
                len++;
                if (len > col_len[i])
                    col_len[i] = len;
                if (len > max)
                    max = len;
            }
        }
        for (int i = 0; i < cols; i++) {
            if (m_cols[i]->m_hidden)
                continue;

            int l = csv_qoute_string(m_cols[i]->m_name).length();
            l++;
            if (l > col_len[i])
                col_len[i] = l;
            if (l > max)
                max = l;
        }

        tmp = new char[max + 1];
        for (int i = 0; i < max; i++)
            tmp[i] = 32;
        tmp[max] = 0;
    }

    for (int i = 0; i < cols; i++) {
        if (m_cols[i]->m_hidden)
            continue;

        printf("%s", csv_qoute_string(m_cols[i]->m_name).c_str());
        if (i < cols - 1) {
            size_t pos = csv_qoute_string(m_cols[i]->m_name).length() + max - col_len[i] + 1;
            if (format && pos < max) {
                printf("%s,", &tmp[pos]);
            } else {
                printf(",");
            }
        }
    }
    printf("\n");
    for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
        Row* r = *it;

        for (int i = 0; i < cols; i++) {
            Column* c = m_cols[i];

            if (c->m_hidden)
                continue;

            int              offset  = c->m_offset;
            static const int bufsize = 100;
            char             buf[bufsize];

            std::string out;

            switch (c->m_type) {
            case Coltype::_bool:
                out = r->access_column<bool_column>(offset) ? "1" : "0";
                break;
            case Coltype::_int:
                snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
                out = buf;
                break;
            case Coltype::_float:
                snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
                out = buf;
                break;
            case Coltype::_text:
                out = csv_qoute_string(r->access_column<text_column>(offset)->data);
                break;
            }

            fputs(out.c_str(), stdout);
            if (i < cols - 1) {
                if (format) {
                    printf("%s,", &tmp[out.length() + max - col_len[i] + 1]);
                } else {
                    printf(",");
                }
            }
        }

        printf("\n");
    }
    delete[] tmp;
}

void Table::dump()
{
    int  cols  = (int)m_cols.size();
    int  width = 25;
    char fmti[40];
    snprintf(fmti, sizeof(fmti) - 1, "%%%dd |", width);
    fmti[39] = 0;
    char fmtd[40];
    snprintf(fmtd, sizeof(fmtd) - 1, "%%%dg |", width);
    fmtd[39] = 0;
    char fmts[40];
    snprintf(fmts, sizeof(fmts) - 1, "%%%ds |", width);
    fmts[39] = 0;

    printf("Table::dump() table:%s cols:%d\n", m_name.c_str(), cols);
    printrep((width + 2) * cols + 1, '-');
    printf("\n");
    printf("|");
    for (int i = 0; i < cols; i++)
        printf(fmti, m_cols[i]->m_type);
    printf("\n");
    printf("|");
    for (int i = 0; i < cols; i++)
        printf(fmts, m_cols[i]->m_name.c_str());
    printf("\n");
    printrep((width + 2) * cols + 1, '*');
    printf("\n");
    for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
        printf("|");
        Row* r = *it;

        for (int i = 0; i < cols; i++) {
            Column* c      = m_cols[i];
            int     offset = c->m_offset;

            switch (c->m_type) {
            case Coltype::_bool:
                printf(fmts, r->access_column<bool_column>(offset) ? "1" : "0");
                break;
            case Coltype::_int:
                printf(fmti, r->access_column<int_column>(offset));
                break;
            case Coltype::_float:
                printf(fmtd, r->access_column<float_column>(offset));
                break;
            case Coltype::_text:
                printf(fmts, r->access_column<text_column>(offset));
                break;
            }
        }

        printf("\n");
    }
    printrep((width + 2) * cols + 1, '-');
    printf("\n");
}

class Parser {
private:
    Token::Type m_last;

public:
    std::list<Token>                   m_tokens;
    typedef std::list<Token>::iterator Lit;

    Parser()
    {
        m_last = Token::_invalid;
    }

    void push(Token::Type type, const char* string)
    {
        if (!(type == Token::_semicolon && m_last == Token::_semicolon))
            m_tokens.push_back(Token(type, string));
        m_last = type;
    }

    void dump()
    {
        for (auto it = m_tokens.begin(); it != m_tokens.end(); it++) {
            printf("Type %d: %s\n", it->get_type(), it->get_token());
        }
    }

    bool analyze(Query& q)
    {
        auto it = m_tokens.begin();
        bool ok = true;
        while (ok) {
            ok = false;
            if (get_sample_stmt(q, it))
                ok = true;

            if (get_select_stmt(q, it))
                ok = true;
        }

        return true;
    }

    bool get_sample_stmt(Query& q, Lit& i_iter)
    {
        Lit it = i_iter;
        if (!is(it, Token::_label, "sample"))
            return false;
        it++;
        if (!is(it, Token::_number))
            return false;
        int sample = atoi(it->get_token());
        it++;
        if (!is(it, Token::_semicolon))
            return false;

        it++;
        q.m_sample = sample;
        i_iter     = it;
        return true;
    }

    bool get_select_stmt(Query& q, Lit& i_iter)
    {
        Lit it = i_iter;
        if (!get_select_core(q, it)) {
            return false;
        }

        get_from(q, it);
        get_where(q, it);
        get_group_by(q, it);
        get_order_by(q, it);
        get_limit(q, it);
        get_as(q, it);
        if (!is(it, Token::_semicolon)) {
            throw Error("Expected ';' but found '%s' !", it->get_token());
        }
        it++;
        i_iter = it;
        return true;
    }

    bool is(Lit& it, Token::Type type, const char* str = 0)
    {
        if (!str)
            return (it->get_type() == type);

        return (it->get_type() == type && cmpi(it->get_token(), str));
    }

    OP* get_result_column(std::list<Token>::iterator& it)
    {
        OP* res  = 0;
        Lit save = it;
        if (is(it, Token::_op, "*")) {
            it++;
            return new OP(Token(Token::_column, "*"));
        }
        if (it->get_type() == Token::_label) {
            std::string table = it->get_token();
            it++;
            if (is(it, Token::_op, ".")) {
                it++;
                if (is(it, Token::_op, "*")) {
                    it++;
                    std::string c = table + ".*";
                    return new OP(Token(Token::_column, c.c_str()));
                }
                it = save;
                return 0;
            }
        }
        it = save;
        if ((res = get_expr(it, 0))) {
            save = it;

            if (is(it, Token::_label, "as")) {
                it++;
                if (is(it, Token::_label)) {
                    res->m_name = it->get_token();
                    it++;
                } else
                    it = save;
            }
        }
        return res;

        // check for valid table
    }

    bool get_select_core(Query& q, Lit& it)
    {
        Lit save = it;
        if (!is(it, Token::_label, "select"))
            return false;
        it++;
        bool again   = true;
        bool success = true;
        while (again) {
            OP* op;
            if ((op = get_result_column(it))) {
                q.m_select.push_back(op);
            } else {
                success = false;
                break;
            }

            if (is(it, Token::_op, ","))
                it++;
            else
                again = false;
        }
        if (success)
            return true;

        it = save;
        return false;
    }

    bool get_ordering_terms(Ordering_terms& ordering, std::list<Token>::iterator& it)
    {
        OP* op;
        while ((op = get_expr(it, 0))) {
            bool asc = true;
            if (it->get_type() == Token::_label) {
                if (cmpi(it->get_token(), "asc")) {
                    // default
                } else if (cmpi(it->get_token(), "desc")) {
                    asc = false;
                } else if (cmpi(it->get_token(), "collate")) {
                    throw Error("unhandled option:collate");
                } else {
                    ordering.m_terms.push_back(Ordering_terms::OP_dir(op, asc));
                    break;
                }

                it++;
            }

            ordering.m_terms.push_back(Ordering_terms::OP_dir(op, asc));

            if (!is(it, Token::_op, ","))
                break;
            it++;
        }
        return true;
    }
    bool get_group_by(Query& q, Lit& it)
    {
        if (!is(it, Token::_label, "group")) {
            return true;
        }
        it++;
        if (!is(it, Token::_label, "by"))
            return false;
        it++;
        bool res = get_ordering_terms(q.m_group_by, it);
        get_having(q, it);
        return res;
    }
    bool get_as(Query& q, Lit& it)
    {
        if (!is(it, Token::_label, "as")) {
            return true;
        }
        it++;
        if (!is(it, Token::_label))
            return false;
        q.m_result->m_name = it->get_token();
        it++;
        return true;
    }

    bool get_order_by(Query& q, Lit& it)
    {
        if (!is(it, Token::_label, "order")) {
            return true;
        }
        it++;
        if (!is(it, Token::_label, "by"))
            return false;
        it++;
        return get_ordering_terms(q.m_order_by, it);
    }

    bool get_limit(Query& q, Lit& it)
    {
        Lit save = it;
        if (!is(it, Token::_label, "limit")) {
            return true;
        }
        it++;
        if (!is(it, Token::_number)) {
            it = save;
            throw Error("non numeric operand to limit");
        }
        q.m_limit = atoi(it->get_token());
        it++;
        save = it;

        if (!is(it, Token::_label, "offset")) {
            return true;
        }
        it++;
        if (!is(it, Token::_number)) {
            it = save;
            throw Error("non numeric operand to offset");
        }
        q.m_offset = atoi(it->get_token());
        it++;
        return true;
    }

    bool get_having(Query& q, Lit& it)
    {
        Lit save = it;
        if (!is(it, Token::_label, "having"))
            return true;
        it++;
        OP* res = 0;
        if ((res = get_expr(it, 0))) {
            q.m_having = res;
            return true;
        }
        it = save;
        return false;
    }

    bool get_where(Query& q, Lit& it)
    {
        Lit save = it;
        if (!is(it, Token::_label, "where"))
            return true;
        it++;
        OP* res = 0;
        if ((res = get_expr(it, 0))) {
            q.m_where = res;
            return true;
        }
        it = save;
        return false;
    }

    bool get_from(Query& q, Lit& it)
    {
        if (!is(it, Token::_label, "from"))
            return false;
        it++;
        if (it->get_type() == Token::_label) {
            const char* name = it->get_token();
            if (get_packet_handler(name)) {
                q.m_from_name = name;
                it++;
                return true;
            } else
                throw Error("Error in from statement, table '%s' not found", name);
        }
        throw Error("Error in from statement");
    }

    int get_stack_precedence(std::stack<OP*>& operator_stack)
    {
        int pre = 0;
        if (!operator_stack.empty())
            pre = operator_stack.top()->m_precedence;
        return pre;
    }

    // using The shunting yard algorithm
    OP* get_expr(Lit& it, int rec)
    {
        std::stack<OP*> operator_stack;
        std::stack<OP*> operand_stack;
        bool            success     = true;
        bool            expect_expr = true;
        while (success) {
            success  = false;
            Lit save = it;
            Lit next = it;
            next++;

            // match literal
            if (expect_expr && is_literal(it)) {
                OP* op = new OP(*it);
                it++;
                operand_stack.push(op);
                success     = true;
                expect_expr = false;
                continue;
            }
            // match literal
            if (expect_expr && is_unary_op(it)) {
                OP* op = new OP(*it);
                op->set_type(Token::_uop);
                it++;
                if (!(op->m_right = get_expr(it, rec + 1)))
                    throw Error("Got unary '%s' but could not parse following expression", op->get_token());

                operand_stack.push(op);
                success     = true;
                expect_expr = false;
                continue;
            }
            // match function-name (
            if (expect_expr && is(it, Token::_label) && is(next, Token::_paren, "(")) {
                OP* func = new OP(*it);
                func->set_type(Token::_function);
                it++;
                it++;
                if (is(it, Token::_op, "*")) {
                    it->set_type(Token::_number);
                    it->set_token("1");
                }
                func->m_param[0] = get_expr(it, rec + 1);
                if (!func->m_param[0])
                    throw Error("Missing operand to function");
                operand_stack.push(func);
                int n = 1;
                while (n < OP::max_param() && is(it, Token::_op, ",")) {

                    it++;
                    func->m_param[n++] = get_expr(it, rec + 1);
                }
                if (!is(it, Token::_paren, ")"))
                    throw Error("Expected ) after %s", func->get_token());
                it++;
                expect_expr = false;
                success     = true;
                continue;
            }
            // match [[databasename .] table-name . ] column name
            if (expect_expr && is(it, Token::_label)) {
                OP* op = new OP(*it);
                it++;
                success = true;
                op->set_type(Token::_column);
                operand_stack.push(op);
                expect_expr = false;
                continue;
            }
            // match ( expr )
            if (!expect_expr && is(it, Token::_paren, ",")) {
                break;
            }
            if (!expect_expr && is(it, Token::_paren, ")")) {
                break;
            }
            if (expect_expr && is(it, Token::_paren, "(")) {
                it++;
                OP* op = 0;
                if ((op = get_expr(it, rec + 1))) {
                    if (is(it, Token::_paren, ")")) {
                        it++;
                        operand_stack.push(op);
                        expect_expr = false;
                        success     = true;
                        continue;
                    }
                    throw Error("Error in expression no )");
                }
                it = save;
                throw Error("Error in expression");
            }
            // bin op
            if (!expect_expr && is(it, Token::_op, "is") && is(next, Token::_op, "not")) {
                it++;
                it->set_token("is not");
            }
            // bin op
            if (!expect_expr && is(it, Token::_op, "not") && is(next, Token::_op, "like")) {
                it++;
                it->set_token("not like");
            }
            if (!expect_expr && is(it, Token::_op) && OP::is_binary(it->get_token())) {
                OP* bop = new OP(*it);
                while (bop->m_precedence <= get_stack_precedence(operator_stack)) {

                    OP* stack_op = operator_stack.top();
                    operator_stack.pop();
                    if (operand_stack.size() >= 2) {

                        OP* stk1          = operand_stack.top();
                        stack_op->m_right = stk1;
                        operand_stack.pop();
                        OP* stk2         = operand_stack.top();
                        stack_op->m_left = stk2;
                        operand_stack.pop();
                        operand_stack.push(stack_op);
                    }
                }

                operator_stack.push(bop);
                it++;
                success     = true;
                expect_expr = true;
                continue;
            }
        }
        while (!operator_stack.empty()) {
            OP* bop = operator_stack.top();
            operator_stack.pop();
            if (bop) {
                if (operand_stack.size() >= 2) {
                    OP* stk      = operand_stack.top();
                    bop->m_right = stk;
                    operand_stack.pop();
                    OP* stk2    = operand_stack.top();
                    bop->m_left = stk2;
                    operand_stack.pop();
                    operand_stack.push(bop);
                }
            }
        }
        if (operand_stack.size() == 0)
            return 0;
        return operand_stack.top();
    }

    bool is_unary_op(Lit& it)
    {
        if (is(it, Token::_op, "-"))
            return true;
        if (is(it, Token::_op, "+"))
            return true;
        if (is(it, Token::_op, "~"))
            return true;
        if (is(it, Token::_op, "not"))
            return true;
        return false;
    }
    bool is_literal(Lit& it)
    {
        if (it->get_type() == Token::_number || it->get_type() == Token::_string)
            return true;
        if (it->get_type() == Token::_label && cmpi(it->get_token(), "null"))
            return true;
        return false;
    }
};

class Lexer {
public:
    Lexer(Parser& p)
        : m_parser(p)
        , num_state(_nan)
    {
    }

    Parser& m_parser;

    enum State {
        _unknown,
        _white,
        _label,
        _number,
        _op,
        _paren,
        _string
    };
    bool is_white(const char c) { return (c == ' ' || c == 9 || c == 10 || c == 13); }
    bool is_char(const char c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); }
    bool is_num(const char c) { return ((c >= '0' && c <= '9')); }
    bool is_op(const char c) { return (c == '*' || c == ',' || c == '=' || c == '<' || c == '>' || c == '/' || c == '|' || c == '%' || c == '+' || c == '-' || c == '&' || c == '~' || c == '!'); }
    bool is_paren(const char c) { return (c == '(' || c == ')'); }
    bool is_termination(const char c) { return (c == 0 || c == ';'); }
    bool is_quote(const char c) { return (c == '\''); }
    bool is_label_start(const char c) { return (is_char(c)); }
    bool is_label_part(const char c) { return (is_num(c) || is_char(c) || c == '_'); }

    bool lex(const char* i_str)
    {
        const char* p          = i_str;
        State       state      = _white;
        const char* strstart   = 0;
        bool        is_escaped = false;
        std::string str        = "";

        while (true) {
            switch (state) {
            case (_unknown): {
                if (is_white(*p))
                    state = _white;
                else if (is_label_start(*p))
                    state = _label;
                else if (is_num(*p) || (*p == '.' && is_num(p[1])))
                    state = _number;
                else if (is_quote(*p))
                    state = _string;
                else if (is_paren(*p))
                    state = _paren;
                else if (is_op(*p))
                    state = _op;
                else if (is_termination(*p)) {
                    m_parser.push(Token::_semicolon, ";");
                    if (*p++ == 0) {
                        m_parser.push(Token::_end, "END");
                        return true;
                    }
                } else {
                    m_parser.push(Token::_end, "END");
                    printf("Unknown char %c (%d) at %d! in statement %s\n", *p, *p, int(p - i_str), i_str);
                    return false;
                }
            } break;
            case (_white): {
                if (is_white(*p))
                    p++;
                else {
                    state = _unknown;
                }
            } break;
            case (_string): {
                if ((!is_escaped) && is_quote(*p)) {
                    if (!strstart) {
                        strstart = p;
                        str      = "";
                    } else {
                        m_parser.push(Token::_string, str.c_str());

                        str        = "";
                        strstart   = 0;
                        state      = _unknown;
                        is_escaped = false;
                    }
                    p++;
                } else {
                    if (is_escaped) {
                        if (!is_quote(*p))
                            str += '\\';
                        is_escaped = false;
                    }

                    if (*p == 0) {
                        printf("Unexpected end of string in statement %s\n", strstart);
                        m_parser.push(Token::_end, "END");
                        return false;
                    }
                    if (*p == '\\')
                        is_escaped = true;
                    else
                        str += (*p);
                    p++;
                }

            } break;
            case (_number): {
                if (parse_num(*p)) {
                    if (!strstart)
                        strstart = p;
                    p++;
                } else {
                    if (!strstart)
                        throw Error("Numeric problem");
                    std::string label = strstart;
                    label             = label.substr(0, p - strstart);
                    m_parser.push(Token::_number, label.c_str());

                    strstart = 0;
                    state    = _unknown;
                }
            } break;
            case (_label): {
                if (!strstart) {
                    strstart = p++;
                } else if (is_label_part(*p)) {
                    p++;
                } else {
                    std::string label = strstart;
                    label             = label.substr(0, p - strstart);

                    Token::Type type = Token::_label;
                    if (cmpi(label, "is"))
                        type = Token::_op;
                    if (cmpi(label, "not"))
                        type = Token::_op;
                    if (cmpi(label, "in"))
                        type = Token::_op;
                    if (cmpi(label, "like"))
                        type = Token::_op;
                    if (cmpi(label, "glob"))
                        type = Token::_op;
                    if (cmpi(label, "match"))
                        type = Token::_op;
                    if (cmpi(label, "regexp"))
                        type = Token::_op;
                    if (cmpi(label, "and"))
                        type = Token::_op;
                    if (cmpi(label, "or"))
                        type = Token::_op;

                    m_parser.push(type, label.c_str());

                    strstart = 0;
                    state    = _unknown;
                }

            } break;
            case (_paren): {
                std::string s;
                s = *p;
                m_parser.push(Token::_paren, s.c_str());
                p++;
                state = _unknown;
            } break;
            case (_op): {
                std::string s;
                s      = *p;
                char n = p[1];
                switch (*p) {
                case ('|'):
                    if (n == '|') {
                        p++;
                        s += *p;
                    }
                    break;
                case ('>'):
                    if (n == '=') {
                        p++;
                        s += *p;
                    }
                    if (n == '>') {
                        p++;
                        s += *p;
                    }
                    break;
                case ('<'):
                    if (n == '<') {
                        p++;
                        s += *p;
                    }
                    if (n == '=') {
                        p++;
                        s += *p;
                    }
                    if (n == '>') {
                        p++;
                        s += *p;
                    }
                    break;
                case ('='):
                    if (n == '=') {
                        p++;
                        s += *p;
                    }
                    break;
                case ('!'):
                    if (n == '=') {
                        p++;
                        s += *p;
                    }
                    break;
                }
                m_parser.push(Token::_op, s.c_str());
                p++;
                state = _unknown;
            } break;
            default:
                printf("Missing impl char %c at %d! in statement %s\n", *p, int(p - i_str), i_str);
                m_parser.push(Token::_end, "END");
                return false;
            }
        }
    }
    bool parse_num(const char p)
    {
        switch (num_state) {
        case (_nan):
            num_state = _int;
            if (p == '.')
                num_state = _dot;
            break;
        case (_int):
            if (p == '.')
                num_state = _dot;
            else if (!is_num(p)) {
                num_state = _nan;
                return false;
            }
            break;
        case (_dot):
            if (p == 'E' || p == 'e')
                num_state = _e;
            else if (!is_num(p)) {
                num_state = _nan;
                return false;
            }
            break;
        case (_dec):
            if (p == 'E' || p == 'e')
                num_state = _e;
            else if (!is_num(p)) {
                num_state = _nan;
                return false;
            }
            break;
        case (_e):
            if (p == '+' || p == '-')
                num_state = _sign;
            else if (is_num(p))
                num_state = _exp;
            else {
                throw Error("expected number digit after E");
            }
            break;
        case (_sign):
            if (!is_num(p))
                throw Error("expected number digit after E");
            num_state = _exp;
            break;
        case (_exp):
            if (!is_num(p)) {
                num_state = _nan;
                return false;
            }
            break;
        }
        return true;
    }
    enum Num_state {
        _nan,
        _int,
        _dot,
        _dec,
        _e,
        _sign,
        _exp,
    };

    Num_state num_state;
};

void Query::parse()
{
    Parser p;
    Lexer  l(p);
    l.lex(m_sql.c_str());
    //		p.dump();
    if (!p.analyze(*this))
        throw Error("error parsing select statement");
}

// return column and index in tables, or 0 for column if column isn't found
std::pair<Column*, int> lookup_column_in_tables(const std::vector<Table*>& tables,
    const std::vector<int>&                                                search_order,
    const char*                                                            name)
{
    if (strcmp(name, "*") == 0)
        return std::pair<Column*, int>((Column*)0, 0);

    for (auto i = search_order.begin(); i != search_order.end(); ++i) {
        Table* table     = tables[*i];
        int    col_index = table->get_col_index(name);
        if (col_index >= 0)
            return std::pair<Column*, int>(table->m_cols[col_index], *i);
    }

    return std::pair<Column*, int>((Column*)0, 0);
}

OP* OP::compile(const std::vector<Table*>& tables, const std::vector<int>& search_order, Query& q)
{
    OP* ret = 0;
    for (int i = 0; i < max_param(); i++) {
        if (m_param[i]) {
            m_param[i] = m_param[i]->compile(tables, search_order, q);
            if (m_param[i]->m_has_aggregate_function)
                m_has_aggregate_function = true;
        }
    }

    if (m_left) {
        m_left = m_left->compile(tables, search_order, q);
        if (m_left->m_has_aggregate_function)
            m_has_aggregate_function = true;
    }

    if (m_right) {
        m_right = m_right->compile(tables, search_order, q);
        if (m_right->m_has_aggregate_function)
            m_has_aggregate_function = true;
    }

    // default to destination row
    m_row_index = tables.size() - 1;

    if (get_type() == _column) {
        auto    lookup = lookup_column_in_tables(tables, search_order, get_token());
        Column* column = lookup.first;
        m_row_index    = lookup.second;

        if (!column)
            throw Error("Column '%s' not found", get_token());

        int offset = column->m_offset;

        m_t = column->m_type;

        switch (m_t) {
        case Coltype::_int:
            ret = new Column_access_int(*this, offset);
            break;
        case Coltype::_bool:
            ret = new Column_access_bool(*this, offset);
            break;
        case Coltype::_float:
            ret = new Column_access_float(*this, offset);
            break;
        case Coltype::_text:
            ret = new Column_access_text(*this, offset);
            break;
        }
    } else if (get_type() == _number) {
        const char* p       = get_token();
        bool        integer = true;
        while (*p != 0) {
            if (*p < '0' || *p > '9')
                integer = false;
            p++;
        }
        if (integer) {
            m_t = Coltype::_int;
            ret = new Static_int(*this);
        } else {
            m_t = Coltype::_float;
            ret = new Static_float(*this);
        }
    } else if (get_type() == _string) {
        m_t = Coltype::_text;
        ret = new Static_text(*this);
    } else if ((get_type() == _function) && m_param[0]) {
        Table* dest_table = tables[m_row_index];

        m_t = Coltype::_int;
        if (cmpi(get_token(), "if") && m_param[1] && m_param[2]) {
            m_t = m_param[1]->m_t;
            if (m_param[2]->m_t > m_t)
                m_t = m_param[2]->m_t;
            ret = new If_func(*this);
        } else if (cmpi(get_token(), "name") && m_param[1]) {
            m_t = Coltype::_text;
            ret = new Name_func(*this);
        } else if (cmpi(get_token(), "trim")) {
            m_t = Coltype::_text;
            ret = new Trim_func(*this);
        } else if (cmpi(get_token(), "rsplit") && m_param[1]) {
            m_t = Coltype::_text;
            ret = new Rsplit_func(*this);
        } else if (cmpi(get_token(), "netmask")) {
            m_t = Coltype::_text;
            ret = new Netmask_func(*this);
        } else if (cmpi(get_token(), "cc")) {
            m_t = Coltype::_text;
            ret = new Cc_func(*this);
        } else if (cmpi(get_token(), "asn")) {
            m_t = Coltype::_int;
            ret = new Asn_func(*this);
        } else if (cmpi(get_token(), "count")) {
            m_t = Coltype::_int;
            ret = new Count_func(*this, dest_table);
        } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "min")) {
            m_t = Coltype::_float;
            ret = new Min_func_float(*this, dest_table);
        } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "max")) {
            m_t = Coltype::_float;
            ret = new Max_func_float(*this, dest_table);
        } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "sum")) {
            m_t = Coltype::_float;
            ret = new Sum_func_float(*this, dest_table);
        } else if (cmpi(get_token(), "min")) {
            m_t = Coltype::_int;
            ret = new Min_func_int(*this, dest_table);
        } else if (cmpi(get_token(), "max")) {
            m_t = Coltype::_int;
            ret = new Max_func_int(*this, dest_table);
        } else if (cmpi(get_token(), "sum")) {
            m_t = Coltype::_int;
            ret = new Sum_func_int(*this, dest_table);
        } else if (cmpi(get_token(), "lower")) {
            m_t = Coltype::_text;
            ret = new Lower_func(*this);
        } else if (cmpi(get_token(), "len")) {
            m_t = Coltype::_int;
            ret = new Len_func(*this);
        } else if (cmpi(get_token(), "truncate")) {
            m_t = Coltype::_int;
            ret = new Truncate_func(*this);
        } else if (cmpi(get_token(), "stdev")) {
            m_t = Coltype::_float;
            ret = new Stdev_func(*this, dest_table);
        } else if (cmpi(get_token(), "avg")) {
            m_t = Coltype::_float;
            ret = new Avg_func(*this, dest_table);
        }
    } else if ((get_type() == _op) && m_left && m_right) {
        if (cmpi(get_token(), "||")) {
            m_t = Coltype::_text;
            ret = new Bin_op_concatenate(*this);
        } else if (cmpi(get_token(), "*")) {
            if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
                m_t = Coltype::_float;
                ret = new Bin_op_mul_float(*this);
            } else {
                m_t = Coltype::_int;
                ret = new Bin_op_mul(*this);
            }
        } else if (cmpi(get_token(), "/")) {
            m_t = Coltype::_float;
            ret = new Bin_op_div(*this);
        } else if (cmpi(get_token(), "%")) {
            m_t = Coltype::_float;
            ret = new Bin_op_modulo(*this);
        } else if (cmpi(get_token(), "+")) {
            if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
                m_t = Coltype::_float;
                ret = new Bin_op_add_float(*this);
            } else {
                m_t = Coltype::_int;
                ret = new Bin_op_add(*this);
            }
        } else if (cmpi(get_token(), "-")) {
            if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
                m_t = Coltype::_float;
                ret = new Bin_op_sub_float(*this);
            } else {
                m_t = Coltype::_int;
                ret = new Bin_op_sub(*this);
            }
        } else if (cmpi(get_token(), "<<")) {
            m_t = Coltype::_int;
            ret = new Bin_op_arithmetic_shift_left(*this);
        } else if (cmpi(get_token(), ">>")) {
            m_t = Coltype::_int;
            ret = new Bin_op_arithmetic_shift_right(*this);
        } else if (cmpi(get_token(), "&")) {
            m_t = Coltype::_int;
            ret = new Bin_op_bitwise_and(*this);
        } else if (cmpi(get_token(), "|")) {
            m_t = Coltype::_int;
            ret = new Bin_op_bitwise_or(*this);
        } else if (cmpi(get_token(), "<")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_lt(*this);
        } else if (cmpi(get_token(), "<=")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_lteq(*this);
        } else if (cmpi(get_token(), ">")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_gt(*this);
        } else if (cmpi(get_token(), ">=")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_gteq(*this);
        } else if (cmpi(get_token(), "=")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_eq(*this);
        } else if (cmpi(get_token(), "==")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_eq(*this);
        } else if (cmpi(get_token(), "like")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_like(*this);
        } else if (cmpi(get_token(), "not like")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_not_like(*this);
        } else if (cmpi(get_token(), "!=")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_not_eq(*this);
        } else if (cmpi(get_token(), "<>")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_not_eq(*this);
        } else if (cmpi(get_token(), "is")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_eq(*this);
        } else if (cmpi(get_token(), "is not")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_not_eq(*this);
        } else if (cmpi(get_token(), "and")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_and(*this);
        } else if (cmpi(get_token(), "or")) {
            m_t = Coltype::_bool;
            ret = new Bin_op_or(*this);
        }
    } else if ((get_type() == _uop) && m_right) {
        if (cmpi(get_token(), "not")) {
            m_t = Coltype::_bool;
            ret = new Un_op_not(*this);
        } else if (cmpi(get_token(), "+")) {
            ret = m_right;
        } else if (cmpi(get_token(), "-")) {
            if (m_right->ret_type() == Coltype::_float) {
                m_t = Coltype::_float;
                ret = new Un_op_neg_float(*this);
            } else {
                m_t = Coltype::_int;
                ret = new Un_op_neg(*this);
            }
        } else if (cmpi(get_token(), "~")) {
            m_t = Coltype::_int;
            ret = new Un_op_ones_complement(*this);
        }
    }
    clear_ptr();
    if (!ret)
        throw Error("Unknown operator error '%s' !", get_token());

    delete this;

    return ret;
}

void OP::evaluate_aggregate_operands(Row** rows)
{
    if (m_left)
        m_left->evaluate_aggregate_operands(rows);
    if (m_right)
        m_right->evaluate_aggregate_operands(rows);
    for (int i = 0; i < max_param(); ++i)
        if (m_param[i])
            m_param[i]->evaluate_aggregate_operands(rows);
}

void OP::combine_aggregate(Row* base_row, Row* other_row)
{
    if (m_left)
        m_left->combine_aggregate(base_row, other_row);
    if (m_right)
        m_right->combine_aggregate(base_row, other_row);
    for (int i = 0; i < max_param(); ++i)
        if (m_param[i])
            m_param[i]->combine_aggregate(base_row, other_row);
}

// return any column access ops found in given list of op trees - they don't
// have to be compiled beforehand; duplicate column tokens are skipped
std::vector<OP*> find_unique_column_ops(std::vector<OP*> ops)
{
    std::vector<OP*> res;

    while (!ops.empty()) {
        OP* op = ops.back();
        ops.pop_back();

        if (op->m_left)
            ops.push_back(op->m_left);
        if (op->m_right)
            ops.push_back(op->m_right);
        for (int i = 0; i < op->max_param(); ++i)
            if (op->m_param[i])
                ops.push_back(op->m_param[i]);

        if (op->get_type() == Token::_column) {
            bool found = false;

            for (auto i = res.begin(); i != res.end(); ++i) {
                if (cmpii((*i)->get_token(), op->get_token())) {
                    found = true;
                    break;
                }
            }

            if (!found)
                res.push_back(op);
        }
    }

    return res;
}

void Query::replace_star_column_with_all_columns()
{
    bool found_star = false;
    for (auto i = m_select.begin(); i != m_select.end(); ++i) {
        if (strcmp((*i)->get_token(), "*") == 0) {
            found_star = true;
            break;
        }
    }

    if (found_star) {
        for (auto i = m_select.begin(); i != m_select.end(); ++i)
            delete *i;
        m_select.clear();

        if (!m_from_name.empty()) {
            Packet_handler* handler = get_packet_handler(m_from_name);

            for (auto i = handler->packet_columns.begin(); i != handler->packet_columns.end(); ++i)
                m_select.push_back(new OP(Token(Token::_column, i->name)));
        }
    }
}

void Query::process_from()
{
    replace_star_column_with_all_columns();

    if (m_from_name.empty())
        return;

    std::vector<OP*> all_ops;
    all_ops.insert(all_ops.end(), m_select.begin(), m_select.end());
    if (m_where)
        all_ops.push_back(m_where);
    // skip m_having, it can't access source columns
    for (auto i = m_order_by.m_terms.begin(); i != m_order_by.m_terms.end(); ++i)
        all_ops.push_back(i->m_op);
    for (auto i = m_group_by.m_terms.begin(); i != m_group_by.m_terms.end(); ++i)
        all_ops.push_back(i->m_op);

    auto used_columns = find_unique_column_ops(all_ops);

    // add from table with used columns
    Packet_handler* handler = get_packet_handler(m_from_name);
    for (auto j = handler->packet_columns.begin(); j != handler->packet_columns.end(); ++j)
        for (auto i = used_columns.begin(); i != used_columns.end(); ++i)
            if (cmpii(j->name, (*i)->get_token()))
                m_used_from_column_ids.push_back(j->id);

    m_from = handler->create_table(m_used_from_column_ids);
}

void Query::process_select(Row** rows, Row* dest, GenericAccessor* dest_accessors)
{
    for (unsigned int i = 0, size = m_select.size(); i < size; ++i) {
        OP* op = m_select[i];
        if (!op)
            continue;

        if (op->m_has_aggregate_function) {
            // defer evaluating aggregate functions, just eval their operands
            op->evaluate_aggregate_operands(rows);
        } else {
            Variant v;
            op->evaluate(rows, v);
            dest_accessors[i].set(dest, v);
        }
    }
}

void Query::combine_aggregates_in_select(Row* base_row, Row* other_row)
{
    for (unsigned int i = 0; i < m_select.size(); ++i) {
        OP* op = m_select[i];
        if (op && op->m_has_aggregate_function)
            op->combine_aggregate(base_row, other_row);
    }
}

void Query::process_aggregates_in_select(Row** rows, Row* dest, GenericAccessor dest_accessors[])
{
    for (unsigned int i = 0; i < m_select.size(); ++i) {
        OP* op = m_select[i];
        if (op && op->m_has_aggregate_function) {
            Variant v;
            op->evaluate(rows, v);
            dest_accessors[i].set(dest, v);
        }
    }
}

bool Query::process_where(Row** rows)
{
    if (!m_where)
        return true;

    Variant v;
    m_where->evaluate(rows, v);
    return v.get_bool();
}

bool Query::process_having(Row** rows)
{
    if (!m_having)
        return true;

    Variant v;
    m_having->evaluate(rows, v);
    return v.get_bool();
}

std::vector<Variant> process_group_by_key(Ordering_terms& group_by, Row** rows)
{
    int size = group_by.m_terms.size();

    std::vector<Variant> res(size);

    for (int i = 0; i < size; ++i)
        group_by.m_terms[i].m_op->evaluate(rows, res[i]);

    return res;
}

bool Query::has_aggregate_functions()
{
    // this assumes the ops have been compiled
    for (auto it = m_select.begin(); it != m_select.end(); it++)
        if ((*it)->m_has_aggregate_function)
            return true;

    return false;
}

void Query::execute(Reader& reader)
{
    std::vector<Table*> tables;
    std::vector<int>    search_results_last, search_results_first, search_results_only;

    // set up tables
    process_from();

    if (m_from)
        tables.push_back(m_from);
    tables.push_back(m_result);

    for (int i = 0; i < int(tables.size()); ++i)
        search_results_last.push_back(i);
    for (int i = int(tables.size()) - 1; i >= 0; --i)
        search_results_first.push_back(i);

    search_results_only.push_back(tables.size() - 1);

    std::vector<Row*> row_ptrs(tables.size());
    Row**             rows = &row_ptrs[0];

    std::vector<GenericAccessor> result_accessors_vector;

    // compile
    for (auto i = m_select.begin(); i != m_select.end(); ++i) {
        *i                  = (*i)->compile(tables, search_results_last, *this);
        Column*         col = m_result->add_column((*i)->get_name(), (*i)->ret_type());
        GenericAccessor a;
        a.m_offset = col->m_offset;
        a.m_type   = col->m_type;
        result_accessors_vector.push_back(a);
    }

    if (m_where)
        m_where = m_where->compile(tables, search_results_last, *this);

    if (m_having)
        m_having = m_having->compile(tables, search_results_only, *this);

    if (m_group_by.exist())
        m_group_by.compile(tables, search_results_last, *this);

    if (m_order_by.exist()) {
        // copy any missing columns to result table as hidden so we can
        // order by them
        std::vector<OP*> ops;
        for (auto i = m_order_by.m_terms.begin(); i != m_order_by.m_terms.end(); ++i)
            ops.push_back(i->m_op);

        std::vector<OP*> column_ops = find_unique_column_ops(ops);

        for (auto i = column_ops.begin(); i != column_ops.end(); ++i) {
            const char* name   = (*i)->get_token();
            auto        lookup = lookup_column_in_tables(tables, search_results_first, name);
            if (lookup.first and lookup.second < int(tables.size()) - 1) {
                // found, but not in result table
                OP* copying_op = new OP(**i);
                copying_op     = copying_op->compile(tables, search_results_last, *this);
                m_select.push_back(copying_op);
                Column*         col = m_result->add_column(copying_op->get_name(), copying_op->ret_type(), -1, Column::HIDDEN);
                GenericAccessor a;
                a.m_offset = col->m_offset;
                a.m_type   = col->m_type;
                result_accessors_vector.push_back(a);
            }
        }

        // we only provide access to result table for "order by"; in order
        // to make the sort thing work correctly the result table currently
        // has to be at index 0
        std::vector<Table*> tables_result_only        = { m_result };
        std::vector<int>    tables_result_only_search = { 0 };
        m_order_by.compile(tables_result_only, tables_result_only_search, *this);
    }

    // execute
    GenericAccessor* result_accessors    = &result_accessors_vector[0];
    bool             aggregate_functions = has_aggregate_functions();

    int  count   = 0;
    bool limiter = !m_order_by.exist() && !m_group_by.exist() && !aggregate_functions && m_limit >= 0;

    if (m_from) {
        bool            first_row = true;
        Packet_handler* handler   = get_packet_handler(m_from_name);

        reader.seek_to_start();

        const int src_i = 0, dest_i = tables.size() - 1;

        rows[src_i] = m_from->create_row();

        if (m_group_by.exist() || aggregate_functions) {
            std::unordered_map<std::vector<Variant>, Row*> groups;

            rows[dest_i] = 0;
            while (reader.read_next(handler, m_used_from_column_ids, *rows[src_i], first_row or m_sample == 0 ? 0 : m_sample - 1)) {
                // fill in groups
                if (rows[dest_i])
                    rows[dest_i]->reset_text_columns(m_result->m_text_column_offsets);
                else
                    rows[dest_i] = m_result->create_row();

                process_select(rows, rows[dest_i], result_accessors);
                if (process_where(rows)) {
                    auto  key   = process_group_by_key(m_group_by, rows);
                    Row*& entry = groups[key];
                    if (entry) {
                        combine_aggregates_in_select(entry, rows[dest_i]);
                    } else {
                        entry        = rows[dest_i];
                        rows[dest_i] = 0;
                    }
                }

                first_row = false;
                rows[src_i]->reset_text_columns(m_from->m_text_column_offsets);
            }
            if (rows[dest_i])
                m_result->delete_row(rows[dest_i]);

            // put groups into result
            for (auto i = groups.begin(); i != groups.end(); ++i) {
                rows[dest_i] = i->second;
                // propagate the aggregate results through the evaluation tree
                process_aggregates_in_select(rows, rows[dest_i], result_accessors);
                if (process_having(rows))
                    m_result->add_row(rows[dest_i]);
                else
                    m_result->delete_row(rows[dest_i]);
            }
        } else {
            rows[dest_i] = m_result->create_row();
            while (reader.read_next(handler, m_used_from_column_ids, *rows[src_i], first_row or m_sample == 0 ? 0 : m_sample - 1)) {
                // fill in result
                process_select(rows, rows[dest_i], result_accessors);
                if (process_where(rows)) {
                    bool commit = true;
                    if (limiter) {
                        int l = count++;
                        if (m_offset > 0)
                            l -= m_offset;
                        if (m_limit >= 0 && l >= m_limit)
                            break;

                        if (l < 0)
                            commit = false;
                    }

                    if (commit) {
                        m_result->add_row(rows[dest_i]);
                        rows[dest_i] = m_result->create_row();
                    }
                }

                first_row = false;
                rows[src_i]->reset_text_columns(m_from->m_text_column_offsets);
            }
            m_result->delete_row(rows[dest_i]);
        }

        m_from->delete_row(rows[src_i]);
    } else {
        const int dest_i = tables.size() - 1;
        rows[dest_i]     = m_result->create_row();
        process_select(rows, rows[dest_i], result_accessors);
        if (process_where(rows))
            m_result->add_row(rows[dest_i]);
        else
            m_result->delete_row(rows[dest_i]);
    }

    if (m_order_by.exist())
        m_result->per_sort(m_order_by);

    if (m_limit >= 0 && !limiter)
        m_result->limit(m_limit, m_offset);
}

DB::DB()
{
    Column::init_defs();
}

DB::~DB()
{
}

bool DB::query(const char* q)
{
    return false;
}

Table* DB::get_table(const char* i_name)
{
    std::string name = lower(i_name);
    Table*      t    = 0;
    auto        it   = m_tables.find(name);
    if (it != m_tables.end())
        t = it->second;

    return t;
}
Table* DB::create_or_use_table(const char* i_name)
{
    std::string name = lower(i_name);
    Table*      t    = get_table(name.c_str());
    if (!t)
        t = create_table(name.c_str());

    return t;
}
Table* DB::create_table(const char* i_name)
{
    std::string name                    = lower(i_name);
    Table*      t                       = new Table(name.c_str());
    m_tables[std::string(name.c_str())] = t;

    return t;
}
Column::Column(const char* name, Coltype::Type type, int id, bool hidden)
    : m_name(name)
    , m_type(type)
    , m_def(Column::m_coldefs[type])
    , m_id(id)
    , m_offset(0)
{
    m_hidden = hidden;
}

void Trim_func::evaluate(Row** rows, Variant& v)
{
    Variant str;
    m_param[0]->evaluate(rows, str);
    RefCountStringHandle str_handle(str.get_text());
    const char*          s = (*str_handle)->data;

    const char*          t;
    RefCountStringHandle trim_handle;
    if (m_param[1]) {
        Variant trim;
        m_param[1]->evaluate(rows, trim);
        trim_handle.set(trim.get_text());
        t = (*trim_handle)->data;
    } else
        t = " ";

    int l = strlen(t);
    if (l <= 0) {
        v = *str_handle;
        return;
    }

    int slen  = strlen(s);
    int start = 0, end = slen;

    // left trim
    while (end - start >= l && memcmp(s + start, t, l) == 0)
        start += l;

    // right trim
    while (end - start >= l && memcmp(s + end - l, t, l) == 0)
        end -= l;

    if (start == 0 && end == slen)
        v = *str_handle;
    else {
        RefCountStringHandle res(RefCountString::construct(s, start, end));
        v = *res;
    }
}

Cc_func::Cc_func(const OP& op)
    : OP(op)
{
#ifdef HAVE_LIBMAXMINDDB
    if (__cc_mmdb) {
        return;
    }

    std::string db;
    char*       env = getenv("PACKETQ_MAXMIND_CC_DB");
    if (env) {
        db = env;
    }

    if (db.empty()) {
        std::list<std::string> paths = {
            "/var/lib/GeoIP", "/usr/share/GeoIP", "/usr/local/share/GeoIP"
        };

        if ((env = getenv("PACKETQ_MAXMIND_PATH"))) {
            paths.push_front(std::string(env));
        }

        auto i = paths.begin();
        for (; i != paths.end(); i++) {
            db = (*i) + "/GeoLite2-Country.mmdb";
            struct stat s;
            if (!stat(db.c_str(), &s)) {
                break;
            }
        }
        if (i == paths.end()) {
            return;
        }
    }

    MMDB_s* mmdb = new (std::nothrow) MMDB_s;
    if (!mmdb) {
        return;
    }

    int ret = MMDB_open(db.c_str(), 0, mmdb);
    if (ret != MMDB_SUCCESS) {
        fprintf(stderr, "Warning: cannot open MaxMind CC database \"%s\": %s\n", db.c_str(), MMDB_strerror(ret));
        delete mmdb;
        return;
    }

    __cc_mmdb = mmdb;
#endif
}

void Cc_func::evaluate(Row** rows, Variant& v)
{
#ifdef HAVE_LIBMAXMINDDB
    if (!__cc_mmdb) {
        RefCountStringHandle res(RefCountString::construct(""));
        v = *res;
        return;
    }

    Variant str;
    m_param[0]->evaluate(rows, str);
    RefCountStringHandle str_handle(str.get_text());

    int gai_error, ret;

    MMDB_lookup_result_s mmdb_result = MMDB_lookup_string(__cc_mmdb, (*str_handle)->data, &gai_error, &ret);

    if (gai_error || ret != MMDB_SUCCESS || !mmdb_result.found_entry) {
        RefCountStringHandle res(RefCountString::construct(""));
        v = *res;
        return;
    }

    MMDB_entry_data_s entry_data;
    ret = MMDB_get_value(&mmdb_result.entry, &entry_data, "country", "iso_code", NULL);

    if (ret != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) {
        RefCountStringHandle res(RefCountString::construct(""));
        v = *res;
        return;
    }

    RefCountStringHandle res(RefCountString::construct(entry_data.utf8_string, 0, entry_data.data_size));
    v = *res;
#else
    RefCountStringHandle res(RefCountString::construct(""));
    v = *res;
#endif
}

Asn_func::Asn_func(const OP& op)
    : OP(op)
{
#ifdef HAVE_LIBMAXMINDDB
    if (__asn_mmdb) {
        return;
    }

    std::string db;
    char*       env = getenv("PACKETQ_MAXMIND_ASN_DB");
    if (env) {
        db = env;
    }

    if (db.empty()) {
        std::list<std::string> paths = {
            "/var/lib/GeoIP", "/usr/share/GeoIP", "/usr/local/share/GeoIP"
        };

        if ((env = getenv("PACKETQ_MAXMIND_PATH"))) {
            paths.push_front(std::string(env));
        }

        auto i = paths.begin();
        for (; i != paths.end(); i++) {
            db = (*i) + "/GeoLite2-ASN.mmdb";
            struct stat s;
            if (!stat(db.c_str(), &s)) {
                break;
            }
        }
        if (i == paths.end()) {
            return;
        }
    }

    MMDB_s* mmdb = new (std::nothrow) MMDB_s;
    if (!mmdb) {
        return;
    }

    int ret = MMDB_open(db.c_str(), 0, mmdb);
    if (ret != MMDB_SUCCESS) {
        fprintf(stderr, "Warning: cannot open MaxMind ASN database \"%s\": %s\n", db.c_str(), MMDB_strerror(ret));
        delete mmdb;
        return;
    }

    __asn_mmdb = mmdb;
#endif
}

void Asn_func::evaluate(Row** rows, Variant& v)
{
#ifdef HAVE_LIBMAXMINDDB
    if (!__asn_mmdb) {
        v = -1;
        return;
    }

    Variant str;
    m_param[0]->evaluate(rows, str);
    RefCountStringHandle str_handle(str.get_text());

    int gai_error, ret;

    MMDB_lookup_result_s mmdb_result = MMDB_lookup_string(__asn_mmdb, (*str_handle)->data, &gai_error, &ret);

    if (gai_error || ret != MMDB_SUCCESS || !mmdb_result.found_entry) {
        v = -1;
        return;
    }

    MMDB_entry_data_s entry_data;
    ret = MMDB_get_value(&mmdb_result.entry, &entry_data, "autonomous_system_number", NULL);

    if (ret != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UINT32) {
        v = -1;
        return;
    }

    v = (int_column)entry_data.uint32;
#else
    v = -1;
#endif
}

DB g_db;

Coldef Column::m_coldefs[COLTYPE_MAX];

} // namespace packetq
