#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/QUnit.qm

%new-style
%require-types
%enable-all-warnings
%strict-args

%requires ../../../../qlib/DataProvider
%requires ../../../../qlib/SqlUtil
%requires ../../../../qlib/Mapper.qm
%requires ../../../../qlib/TableMapper.qm

%exec-class Main

# unused, but required for "select hash" argument
const SELECT_HASH = (
    "columns" : ("a", "b"),
    "where" : ( "a" : 1 ),
);

const MAPV = (
    "mapped_a": "a",
    "mapped_b": "b",
    "created_c" : ( "code": *string sub (*auto v, hash<auto> rec) { return sprintf("%n - %n", rec.a, rec.b); } ),
    "constant_d" : ( "constant" : 1 ),
);

const INPUT = (
    ("a":1, "b" : 1),
    ("a":1, "b" : 2),
);

const OUTPUT = (
    ("mapped_a":1, "mapped_b" : 1, "created_c" : "1 - 1", "constant_d" : 1),
    ("mapped_a":1, "mapped_b" : 2, "created_c" : "1 - 2", "constant_d" : 1),
);

# just return INPUTs as it would be returned by SQLStatement
class FakeSQLStatement inherits Qore::SQL::AbstractSQLStatement {
    private {
        int m_ix = 0;
    }

    bool next() {
        return m_ix < 2;
    }

    bool valid() { return True; }

    hash<auto> getValue() {
        hash<auto> ret = INPUT[m_ix];
        m_ix++;
        return ret;
    }

    prepare(string s, ...) {}
    execArgs(list<auto> l) {}
    hash<auto> getOutput() {}
    getSQL() {}

    hash<auto> fetchColumns(softint rows = -1) { throw "X"; }
    nothing rollback() { throw "X"; }
    list<auto> fetchRows(softint rows = -1) { throw "X"; }
    hash<auto> describe() { throw "X"; }
    nothing commit() { throw "X"; }
    nothing bindArgs(softlist<auto> vargs) { throw "X"; }
    nothing execArgs(softlist<auto> vargs) { throw "X"; }
    hash<auto> getOutputRows() { throw "X"; }
    nothing prepareRaw(string sql) { throw "X"; }
    bool active() { throw "X"; }
    nothing bindPlaceholders(...) { throw "X"; }
    *hash<auto> fetchRow() { throw "X"; }
    nothing define() { throw "X"; }
    bool currentThreadInTransaction() { throw "X"; }
    nothing bindValues(...) { throw "X"; }
    int affectedRows() { throw "X"; }
    nothing bind(...) { throw "X"; }
    nothing bindValuesArgs(softlist<auto> vargs) { throw "X"; }
    nothing close() { throw "X"; }
    nothing exec(...) { throw "X"; }
    nothing beginTransaction() { throw "X"; }
    nothing bindPlaceholdersArgs(softlist<auto> vargs) { throw "X"; }
    auto memberGate(string m) { }
    auto methodGate(string m) { }
}

# dummy datasource-like class for FakeAbstractTable
class FakeDatasource inherits Qore::SQL::AbstractDatasource {
    *string getPassword() {}
    *string getOSEncoding() {}
    *string getDBName() {}
    auto selectRows(string sql, ...) {}
    bool inTransaction() { return False;}
    auto getClientVersion() {}
    auto exec(string sql, ...) {}
    *int getPort() {}
    auto execRaw(string sql) {}
    auto getServerVersion() {}
    *string getHostName() {}
    string getDriverName() { return ""; }
    auto select(string sql, ...) {}
    *string getDBEncoding() { }
    auto vselect(string sql, *softlist<auto> vargs) {}
    string getConfigString() { return ""; }
    auto vselectRows(string sql, *softlist<auto> vargs) {}
    nothing beginTransaction() {}
    nothing rollback() {}
    auto selectRow(string sql, ...) {}
    hash<auto> getConfigHash() {}
    nothing commit() {}
    auto vselectRow(string sql, *softlist<auto> vargs) {}
    auto vexec(string sql, *softlist<auto> vargs) {}
    *string getUserName() {}
    AbstractSQLStatement getSQLStatement() { return new FakeSQLStatement(); }
}

# Actually we don't need real table. Just a FakeSQLStatement returned from getRowIterator()
class FakeAbstractTable inherits SqlUtil::AbstractTable {
    constructor() : SqlUtil::AbstractTable(new FakeDatasource(), "foo_table") {}
    AbstractIterator getRowIterator(hash<auto> h, *reference<string> sql) { return new FakeSQLStatement(); }
    AbstractIterator getRowIteratorNoExec(hash<auto> h) { return new FakeSQLStatement(); }
    AbstractIterator getStatement(hash<auto> h, *reference<string> sql) { return new FakeSQLStatement(); }
    AbstractIterator getStatementNoExec(hash<auto> h, *reference<string> sql) { return new FakeSQLStatement(); }

    string getRenameSqlImpl(string new_name) {}
    AbstractPrimaryKey getPrimaryKeyImpl(auto a) {}
    Constraints getConstraintsImpl(auto a)  { return new Constraints(); }
    AbstractCheckConstraint addCheckConstraintImpl(string cname, string src, *hash<auto> opt) {}
    nothing setupTableImpl(hash<auto> desc, *hash<auto> opt) {}
    bool uniqueIndexCreatesConstraintImpl(auto a) {}
    ForeignConstraints getForeignConstraintsImpl(*hash<auto> opt) {}
    bool checkExistenceImpl(auto a) {}
    AbstractPrimaryKey addPrimaryKeyImpl(string cname, hash<auto> ch, *hash<auto> opt) {}
    AbstractColumn addColumnImpl(string cname, hash<auto> opt, bool nullable = True) {}
    Indexes getIndexesImpl(auto a) {}
    AbstractForeignConstraint addForeignConstraintImpl(string cname, hash<auto> ch, string table, hash<auto> tch, *hash<auto> opt) {}
    Columns describeImpl() { return new Columns(("a": NOTHING, "b": NOTHING)); }
    AbstractTrigger addTriggerImpl(string tname, string src, *hash<auto> opt) {}
    AbstractIndex addIndexImpl(string iname, bool enabled, hash<auto> ch, *hash<auto> opt) {}
    *list<auto> getAlignSqlImpl(AbstractTable t, *hash<auto> opt) {}
    Triggers getTriggersImpl(auto a) {}
    string getCreateTableSqlImpl(*hash<auto> opt) {}
    bool tryInsertImpl(string sql, hash<auto> row) {}
    bool supportsTablespacesImpl(auto a) {}
    *string getSqlValueImpl(auto v) {}
    AbstractUniqueConstraint addUniqueConstraintImpl(string cname, hash<auto> ch, *hash<auto> opt) {}
    hash<auto> getQoreTypeMapImpl(auto a) {}
    *hash<auto> doReturningImpl(hash<auto> opt, reference<string> sql, list<auto> args) {}
    hash<auto> getTypeMapImpl(auto a) {}
    bool constraintsLinkedToIndexesImpl(auto a) {}
    bool emptyImpl(auto a) {}
    *list<auto> getCreateMiscSqlImpl(*hash<auto> opt, bool cache) {}
    string getCreateSqlImpl(list<auto> l) {}
    bool hasArrayBind(auto a) {}
    nothing doSelectLimitOnlyUnlockedImpl(reference<hash<QueryInfo>> info, reference<string> sql) {}
    nothing copyImpl(AbstractTable old) {}
    nothing doSelectOrderByWithOffsetSqlUnlockedImpl(reference<hash<QueryInfo>> info, reference<string> sql,
            list<auto> coll) {}
    AbstractDataProviderType getNumericTypeImpl(string type_name, bool nullable, *hash<auto> options) {}
    AbstractSavepointHelper getSavepointHelperImpl(*string savepoint) {}
    bool isDuplicateRowErrorImpl(hash<::Qore::ExceptionInfo> ex) { return False; }
}

class Main inherits QUnit::Test {

    constructor() : Test("SqlStatementMapperIterator", "1.0") {

        addTestCase("SqlStatementMapperIterator mapData()", \testMapData(), NOTHING);

        set_return_value(main());
    }

    testMapData() {
        SqlStatementMapperIterator m(new FakeAbstractTable(), SELECT_HASH, MAPV);

        int i = 0;
        while (m.next()) {
            hash<auto> row = m.getValue();
            testAssertion(sprintf("Row verification: %d", i), \equals(), (row, OUTPUT[i]));
            i++;
        }

        testAssertion("Verify item count", \equals(), (m.getCount(), 2));
    }
}
