2
\$\begingroup\$

I created it to map buffers returned by ODBC driver to my objects. It works quite nice but, I have some questions marked (B), (C), (D), (E).

For those who unfamiliar with ODBC: after calling to SQLExtendedFetch driver fills buffers specified by SQLBindCol and NULL indicator in *Length in batch of ROW_ARRAY_SIZE. You can ignore parts related to ODBC.

using namespace std; // for SO
// for variant visitor
template<class... Ts> struct Visitor : Ts... { using Ts::operator()...; };
template<class... Ts> Visitor(Ts...)->Visitor<Ts...>;

//-----
template <typename T, typename V>
class COdbcBinder
{
private:
    using pll = tuple<long T::*, SQLLEN T::*, long V::*>;
    using pcs = tuple<void* T::*, SQLLEN T::*, string V::*, size_t>;
    using pts = tuple<SQL_TIMESTAMP_STRUCT T::*, SQLLEN T::*, string V::*>;
    using mapvar = variant<pll, pcs, pts>;

    static constexpr char ROW_ARRAY_SIZE = 100;
    SQLUSMALLINT N = 0;
    HSTMT stmt = SQL_NULL_HSTMT;
    TechnicalServices::Db::COdbc* m_odbc;

    // (E) smart pointer as class member. Should I manually reset it in destructor?
    unique_ptr<array<T, ROW_ARRAY_SIZE>> in;

    vector<mapvar> mappings;

public:
    COdbcBinder(TechnicalServices::Db::COdbc* odbc) : m_odbc(odbc)
    {
        SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_STMT, m_odbc->GetHdbc(), &stmt); assert(rc == 0);

        in = make_unique<array<T, ROW_ARRAY_SIZE>>();
    }
    ~COdbcBinder()
    {
        if (stmt != SQL_NULL_HSTMT)
        {
            SQLRETURN rc = SQL_ERROR;

            rc = SQLCloseCursor(stmt); assert(rc == 0);
            rc = SQLFreeHandle(SQL_HANDLE_STMT, stmt); assert(rc == 0);
        }
    }

    template <typename CharArr> // (B.0) char T::*[10] and char T::*[25] are incompatible types
    inline void BindCol(CharArr T::* ptrin, SQLLEN T::* ptrinLen, string V::* ptrout)
    {
        static_assert(sizeof(void*) == sizeof(char(T::*)[20]), "Pointer to data member T must be sizeof(void*)");

        // (B.1) is there any way to store pointer to array member of  different size in one vector without this?
        mappings.emplace_back(mapvar(make_tuple(reinterpret_cast<void* T::*>(ptrin), ptrinLen, ptrout, extent<CharArr>::value)));

        SQLRETURN rc = SQLBindCol(stmt, ++N, SQL_C_CHAR, &((*in)[0].*ptrin), sizeof((*in)[0].*ptrin), &((*in)[0].*ptrinLen));
        assert(rc == 0);
    }
    template<>
    inline void BindCol(SQL_TIMESTAMP_STRUCT T::* ptrin, SQLLEN T::* ptrinLen, string V::* ptrout)
    {
        mappings.emplace_back(mapvar(make_tuple(ptrin, ptrinLen, ptrout)));

        SQLRETURN rc = SQLBindCol(stmt, ++N, SQL_C_TIMESTAMP, &((*in)[0].*ptrin), sizeof((*in)[0].*ptrin), &((*in)[0].*ptrinLen));
        assert(rc == 0);
    }
    inline void BindCol(long T::* ptrin, SQLLEN T::* ptrinLen, long V::* ptrout)
    {
        mappings.emplace_back(mapvar(make_tuple(ptrin, ptrinLen, ptrout)));

        SQLRETURN rc = SQLBindCol(stmt, ++N, SQL_C_LONG, &((*in)[0].*ptrin), sizeof((*in)[0].*ptrin), &((*in)[0].*ptrinLen));
        assert(rc == 0);
    }

    vector<V> GetResult(const char* sql)
    {
        SQLUINTEGER numRowsFetched;
        SQLUSMALLINT rowStatusArray[ROW_ARRAY_SIZE];
        SQLRETURN rc = SQL_ERROR;

        rc = SQLSetStmtAttr(stmt, SQL_ROWSET_SIZE, (SQLPOINTER)ROW_ARRAY_SIZE, 0); assert(rc == 0);
        rc = SQLSetStmtAttr(stmt, SQL_BIND_TYPE, (SQLPOINTER)sizeof(T), 0); assert(rc == 0);

        rc = SQLExecDirect(stmt, (SQLCHAR*)sql, SQL_NTS); assert(rc == 0);
        m_odbc->LogLastError(stmt);

        vector<V> results;

        while ((rc = SQLExtendedFetch(stmt, SQL_FETCH_NEXT, 0, &numRowsFetched, rowStatusArray)) != SQL_NO_DATA)
        {
            assert(rc == 0);
            m_odbc->LogLastError(stmt);

            for (SQLUINTEGER i = 0; i < numRowsFetched; ++i)
            {
                if (rowStatusArray[i] == SQL_ROW_SUCCESS || rowStatusArray[i] == SQL_ROW_SUCCESS_WITH_INFO)
                {
                    V value;
                    const T& info = (*in)[i];
                    // (C) visitor recreated for each row.
                    const auto visitor = Visitor{
                        [&](const pll& tup)
                        {
                            const auto [ptrin, ptrinLen, ptrout] = tup;
                            value.*ptrout = info.*ptrin;
                        },
                        [&](const pcs& tup)
                        {
                            const auto [ptrin, ptrinLen, ptrout, size] = tup;
                            // (D) cast to char*(T::*) segfaults, cast to incomplete type seems ok
                            auto p = reinterpret_cast<char(T::*)[]>(ptrin);
                            value.*ptrout = BinderHelpers::EmptyIfNull(info.*p, size, info.*ptrinLen);
                        },
                        [&](const pts& tup)
                        {
                            const auto [ptrin, ptrinLen, ptrout] = tup;
                            value.*ptrout = BinderHelpers::TimestampToString(info.*ptrin, info.*ptrinLen);
                        }
                    };

                    for (const auto& v : mappings)
                    {
                        std::visit(visitor, v);
                    }

                    results.push_back(std::move(value));
                }
            }
        }

        return results;
    }
    private:
        COdbcBinder(const COdbcBinder&) = delete; // copy ctr
        COdbcBinder(COdbcBinder&&) noexcept = delete; // move ctr
        COdbcBinder& operator=(const COdbcBinder&) = delete; // copy asign
        COdbcBinder& operator=(COdbcBinder&&) noexcept = delete; // move asign
};

Usage:

struct INCIDENT_INFO {
    long id;
    long idLength;
    char comments[1024];
    long commentsLength;
    /* ... */
    SQL_TIMESTAMP_STRUCT timeSendToService;
    long timeSendToServiceLength;
};

class Incident {
    long id;
    string comments;
    /* ... */
    string timeSendToService;
};

int main()
{
    /* initialize connection into m_odbc */
    COdbcBinder<INCIDENT_INFO, Incident> binder(m_odbc);

    binder.BindCol(&INCIDENT_INFO::id, &INCIDENT_INFO::idLength, &Incident::id); // ID - 0
    binder.BindCol(&INCIDENT_INFO::comments, &INCIDENT_INFO::commentsLength, &Incident::comments); // COMMENTS - 1
    /* .... */
    binder.BindCol(&INCIDENT_INFO::timeSendToService, &INCIDENT_INFO::timeSendToServiceLength, &Incident::timeSendToService); // TIME_SEND_TO_SERVICE - 31

    vector<Incident> v = binder.GetResult("select ID, COMMENTS, " /* .... */ "TIME_SEND_TO_SERVICE from INCIDENTS");
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.