std::string stored proc in-bound binding

This commit is contained in:
Aleksandar Fabijanic 2007-06-06 22:43:10 +00:00
parent 7944f7868b
commit f5d1b17306
8 changed files with 259 additions and 160 deletions

View File

@ -68,7 +68,10 @@ class ODBC_API Binder: public Poco::Data::AbstractBinder
{
public:
typedef AbstractBinder::Direction Direction;
typedef std::map<SQLPOINTER, SQLLEN> ParameterMap;
typedef Tuple<SQLPOINTER, SQLLEN, SQLSMALLINT, SQLSMALLINT> ParamTuple;
typedef std::vector<ParamTuple> ParamVec;
static const size_t DEFAULT_PARAM_SIZE = 1024;
enum ParameterBinding
{
@ -138,15 +141,18 @@ public:
std::size_t parameterSize(SQLPOINTER pAddr) const;
/// Returns bound data size for parameter at specified position.
void sync(Direction direction);
/// Synchronizes non-POD parameters.
void synchronize();
/// Transfers the results of non-POD outbound parameters from internal
/// holders back into the externally supplied buffers.
ParameterMap& outParameters();
ParamVec& outParameters();
/// Returns map of output parameter pointers and sizes.
private:
typedef std::vector<SQLLEN*> LengthVec;
typedef std::map<SQL_TIMESTAMP_STRUCT*, DateTime*> TimestampMap;
typedef std::map<char*, std::string*> StringMap;
typedef std::map<char*, BLOB*> BLOBMap;
void describeParameter(std::size_t pos);
/// Sets the description field for the parameter, if needed.
@ -156,17 +162,22 @@ private:
/// This is a private no-op in this implementation
/// due to security risk.
std::size_t getParamSize(std::size_t pos);
/// Returns parameter size as defined by data source.
/// Used to determine buffer size for variable size out-bound parameters
/// (string and BLOB).
SQLSMALLINT getParamType(Direction dir) const;
/// Returns ODBC parameter type based on the parameter binding direction
/// specified by user.
template <typename T>
void bindImpl(std::size_t pos, T& val, SQLSMALLINT cDataType, Direction dir)
{
_lengthIndicator.push_back(0);
int sqlDataType = Utility::sqlDataType(cDataType);
SQLINTEGER colSize = 0;
SQLSMALLINT decDigits = 0;
if (_pTypeInfo)
{
try
@ -180,12 +191,12 @@ private:
(SQLUSMALLINT) pos + 1,
getParamType(dir),
cDataType,
sqlDataType,
Utility::sqlDataType(cDataType),
colSize,
decDigits,
(SQLPOINTER) &val,
0,
_lengthIndicator.back())))
0)))
{
throw StatementException(_rStmt, "SQLBindParameter()");
}
@ -193,10 +204,12 @@ private:
const StatementHandle& _rStmt;
LengthVec _lengthIndicator;
ParameterMap _inParams;
ParameterMap _outParams;
ParamVec _inParams;
ParamVec _outParams;
ParameterBinding _paramBinding;
TimestampMap _timestamps;
StringMap _strings;
BLOBMap _blobs;
const TypeInfo* _pTypeInfo;
};
@ -288,12 +301,24 @@ inline Binder::ParameterBinding Binder::getDataBinding() const
}
inline Binder::ParameterMap& Binder::outParameters()
inline Binder::ParamVec& Binder::outParameters()
{
return _outParams;
}
inline std::size_t Binder::getParamSize(std::size_t pos)
{
try
{
return Parameter(_rStmt, pos).columnSize();
}catch (StatementException&)
{
return DEFAULT_PARAM_SIZE;
}
}
} } } // namespace Poco::Data::ODBC

View File

@ -128,6 +128,8 @@ private:
/// Called whenever SQLExecute returns SQL_NEED_DATA. This is expected
/// behavior for PB_AT_EXEC binding mode.
void getData();
void fillColumns();
void checkError(SQLRETURN rc, const std::string& msg="");

View File

@ -67,15 +67,41 @@ Binder::~Binder()
TimestampMap::iterator itTS = _timestamps.begin();
TimestampMap::iterator itTSEnd = _timestamps.end();
for(; itTS != itTSEnd; ++itTS) delete itTS->first;
StringMap::iterator itStr = _strings.begin();
StringMap::iterator itStrEnd = _strings.end();
for(; itStr != itStrEnd; ++itStr) std::free(itStr->first);
BLOBMap::iterator itB = _blobs.begin();
BLOBMap::iterator itBEnd = _blobs.end();
for(; itB != itBEnd; ++itB) std::free(itB->first);
}
void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
{
if (isOutBound(dir))
throw InvalidAccessException("std::string can only be in-bound");
SQLPOINTER pVal = 0;
SQLINTEGER size = (SQLINTEGER) val.size();
if (isOutBound(dir))
{
Parameter p(_rStmt, pos);
size = p.columnSize();
char* pChar = (char*) std::calloc(size, sizeof(char));
ParamTuple pt(pChar, size, SQL_C_CHAR, SQL_LONGVARCHAR);
_outParams.push_back(pt);
_strings.insert(StringMap::value_type(pChar, const_cast<std::string*>(&val)));
pVal = (SQLPOINTER) pChar;
}
else if (isInBound(dir))
{
pVal = (SQLPOINTER) val.c_str();
ParamTuple pt((SQLPOINTER) pVal, size, SQL_C_CHAR, SQL_LONGVARCHAR);
_inParams.push_back(pt);
}
else
throw IllegalStateException("Parameter must be [in] OR [out] bound.");
SQLLEN* pLenIn = new SQLLEN;
*pLenIn = SQL_NTS;
@ -83,20 +109,15 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
*pLenIn = SQL_LEN_DATA_AT_EXEC(size);
_lengthIndicator.push_back(pLenIn);
if (isInBound(dir))
_inParams.insert(ParameterMap::value_type((SQLPOINTER) val.c_str(), size));
//TODO
//if (isOutBound(dir))
// _outParams.insert(ParameterMap::value_type((SQLPOINTER) pTS, size));
if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
SQL_PARAM_INPUT,
getParamType(dir),
SQL_C_CHAR,
SQL_LONGVARCHAR,
(SQLUINTEGER) size,
0,
(SQLPOINTER) val.c_str(),
pVal,
(SQLINTEGER) size,
_lengthIndicator.back())))
{
@ -107,9 +128,7 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
void Binder::bind(std::size_t pos, const Poco::Data::BLOB& val, Direction dir)
{
if (isOutBound(dir))
throw InvalidAccessException("BLOB can only be in-bound");
SQLPOINTER pVal = 0;
SQLINTEGER size = (SQLINTEGER) val.size();
SQLLEN* pLenIn = new SQLLEN;
*pLenIn = size;
@ -118,20 +137,33 @@ void Binder::bind(std::size_t pos, const Poco::Data::BLOB& val, Direction dir)
*pLenIn = SQL_LEN_DATA_AT_EXEC(size);
_lengthIndicator.push_back(pLenIn);
if (isInBound(dir))
_inParams.insert(ParameterMap::value_type((SQLPOINTER) val.rawContent(), size));
//TODO
//if (isOutBound(dir))
// _outParams.insert(ParameterMap::value_type((SQLPOINTER) pTS, size));
if (isOutBound(dir))
{
size = getParamSize(pos);
char* pChar = (char*) std::calloc(size, sizeof(char));
ParamTuple pt(pChar, size, SQL_C_BINARY, SQL_LONGVARBINARY);
_outParams.push_back(pt);
_blobs.insert(BLOBMap::value_type(pChar, const_cast<BLOB*>(&val)));
pVal = (SQLPOINTER) pChar;
}
else if (isInBound(dir))
{
pVal = (SQLPOINTER) val.rawContent();
ParamTuple pt((SQLPOINTER) pVal, size, SQL_C_BINARY, SQL_LONGVARBINARY);
_inParams.push_back(pt);
}
else
throw IllegalStateException("Parameter must be [in] OR [out] bound.");
if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
SQL_PARAM_INPUT,
getParamType(dir),
SQL_C_BINARY,
SQL_LONGVARBINARY,
(SQLUINTEGER) size,
0,
(SQLPOINTER) val.rawContent(),
pVal,
(SQLINTEGER) size,
_lengthIndicator.back())))
{
@ -183,8 +215,15 @@ void Binder::bind(std::size_t pos, const Poco::DateTime& val, Direction dir)
std::size_t Binder::parameterSize(SQLPOINTER pAddr) const
{
ParameterMap::const_iterator it = _inParams.find(pAddr);
if (it != _inParams.end()) return it->second;
ParamVec::const_iterator it = _inParams.begin();
ParamVec::const_iterator end = _inParams.end();
for (; it != end; ++it)
if (it->get<0>() == pAddr) return it->get<1>();
it = _outParams.begin();
end = _outParams.end();
for (; it != end; ++it)
if (it->get<0>() == pAddr) return it->get<1>();
throw NotFoundException("Requested data size not found.");
}
@ -210,17 +249,22 @@ SQLSMALLINT Binder::getParamType(Direction dir) const
}
void Binder::sync(Direction direction)
void Binder::synchronize()
{
TimestampMap::iterator itTS = _timestamps.begin();
TimestampMap::iterator itTSEnd = _timestamps.end();
for(; itTS != itTSEnd; ++itTS)
{
if (PD_OUT == direction) // SQL_TIMESTAMP_STRUCT => DateTime
Utility::dateTimeSync(*itTS->second, *itTS->first);
else // DateTime => SQL_TIMESTAMP_STRUCT
Utility::dateTimeSync(*itTS->first, *itTS->second);
}
Utility::dateTimeSync(*itTS->second, *itTS->first);
StringMap::iterator itStr = _strings.begin();
StringMap::iterator itStrEnd = _strings.end();
for(; itStr != itStrEnd; ++itStr)
itStr->second->assign(itStr->first, strlen(itStr->first));
BLOBMap::iterator itB = _blobs.begin();
BLOBMap::iterator itBEnd = _blobs.end();
for(; itB != itBEnd; ++itB)
itB->second->assignRaw(itB->first, strlen(itB->first));
}

View File

@ -126,10 +126,6 @@ void ODBCStatementImpl::compileImpl()
// these calls must occur before.
fixupBinding(); doBind(false, true);
// Following code creates internal extraction storage in case when none is provided by user.
// Under normal circumstances, this is the responsibility of the Data framework. Due to some
// ODBC peculiarities, for ODBC it is implemented here. Data library detects this being already
// done and does not try to do it again.
bool dataAvailable = hasData();
if (dataAvailable && !extractions().size())
{
@ -195,7 +191,9 @@ void ODBCStatementImpl::bindImpl()
if (SQL_NEED_DATA == rc) putData();
else checkError(rc, "SQLExecute()");
_pBinder->sync(Binder::PD_OUT);
getData();
_pBinder->synchronize();
}
@ -215,22 +213,42 @@ void ODBCStatementImpl::putData()
}while (SQL_NEED_DATA == (rc = SQLParamData(_stmt, &pParam)));
checkError(rc, "SQLParamData()");
}
/* TODO: this is how manual extraction of parameters should work
in practice, for the time being, we only support automatic binding for output params
Binder::ParameterMap outParamMap = _pBinder->outParameters();
Binder::ParameterMap::iterator it = outParamMap.begin();
Binder::ParameterMap::iterator end = outParamMap.end();
void ODBCStatementImpl::getData()
{
Binder::ParamVec& outParams = _pBinder->outParameters();
if (0 == outParams.size()) return;
Binder::ParamVec::iterator it = outParams.begin();
Binder::ParamVec::iterator end = outParams.end();
for (int i = 1; it != end; ++it, ++i)
{
SQLINTEGER retLen = 0;
while (SQL_NO_DATA != (rc = SQLGetData(_stmt, i, SQL_C_TYPE_TIMESTAMP, (SQLPOINTER) (it->first + retLen), it->second - retLen, &retLen)))
if (0 == retLen || SQL_NULL_DATA == retLen) break;
char* ptr = (char*) it->get<0>();
SQLINTEGER len = it->get<1>();
//NB: Oracle SQLGetData call returns string data, but does NOT report the returned length.
// (no other drivers tested for this functionality yet)
// Thus, for the string length we trust ptr being zeroed when allocated in binder.
// As a "safety net", the last member of the binder-supplied char* array is set to '\0' (see below)
while (len > 0 && SQL_NO_DATA != (SQLGetData(_stmt, i, it->get<2>(), (SQLPOINTER) ptr, len, &retLen)))
{
if (0 == retLen ||
SQL_NULL_DATA == retLen ||
SQL_NO_TOTAL == retLen)
break;
StatementException se(_stmt);
std::cout << se.toString();
if (ptr + retLen < ptr + len)
{
ptr += retLen;
len -= retLen;
}
}
//just in case, terminate the string
((char*) it->get<0>())[it->get<1>()-1] = '\0';
}
*/
}

View File

@ -925,6 +925,36 @@ void ODBCOracleTest::testStoredProcedure()
k += 2;
}
//string and BLOB for automatic binding only
_pSession->setFeature("autoBind", true);
*_pSession << "CREATE OR REPLACE "
"PROCEDURE storedProcedure(inParam IN VARCHAR2, outParam OUT VARCHAR2) IS "
" BEGIN outParam := inParam; "
"END storedProcedure;" , now;
std::string inParam = "123";
std::string outParam;
try{
*_pSession << "{call storedProcedure(?,?)}", in(inParam), out(outParam), now;
}catch(StatementException& ex){std::cout << ex.toString();}
assert(inParam == outParam);
dropObject("PROCEDURE", "storedProcedure");
/*TODO - currently failing
*_pSession << "CREATE OR REPLACE "
"PROCEDURE storedProcedure(inParam IN BLOB, outParam OUT BLOB) IS "
" BEGIN outParam := inParam; "
"END storedProcedure;" , now;
BLOB inBLOB = "123";
BLOB outBLOB;
try{
*_pSession << "{call storedProcedure(?,?)}", in(inBLOB), out(outBLOB), now;
}catch(StatementException& ex){std::cout << ex.toString();}
assert(inBLOB == outBLOB);
dropObject("PROCEDURE", "storedProcedure");
*/
}
@ -937,10 +967,12 @@ void ODBCOracleTest::testStoredFunction()
_pSession->setFeature("autoBind", bindValues[k]);
_pSession->setFeature("autoExtract", bindValues[k+1]);
try{
*_pSession << "CREATE OR REPLACE "
"FUNCTION storedFunction RETURN NUMBER IS "
" BEGIN return(-1); "
" END storedFunction;" , now;
}catch(StatementException& se) { std::cout << se.toString() << std::endl; }
int i = 0;
*_pSession << "{? = call storedFunction()}", out(i), now;
@ -997,6 +1029,23 @@ void ODBCOracleTest::testStoredFunction()
k += 2;
}
//string and BLOB for automatic binding only
_pSession->setFeature("autoBind", true);
*_pSession << "CREATE OR REPLACE "
"FUNCTION storedFunction(inParam IN VARCHAR2, outParam OUT VARCHAR2) RETURN VARCHAR2 IS "
" BEGIN outParam := inParam; RETURN outParam;"
"END storedFunction;" , now;
std::string inParam = "123";
std::string outParam;
std::string ret;
*_pSession << "{? = call storedFunction(?,?)}", out(ret), in(inParam), out(outParam), now;
assert("123" == inParam);
assert(inParam == outParam);
assert(ret == outParam);
dropObject("PROCEDURE", "storedFunction");
}
@ -1013,7 +1062,8 @@ void ODBCOracleTest::dropObject(const std::string& type, const std::string& name
StatementDiagnostics::Iterator it = flds.begin();
for (; it != flds.end(); ++it)
{
if (942 == it->_nativeError)//ORA-00942 (table does not exist)
if (4043 == it->_nativeError || //ORA-04043 (object does not exist)
942 == it->_nativeError)//ORA-00942 (table does not exist)
{
ignoreError = true;
break;

View File

@ -136,6 +136,7 @@ inline const std::vector<char>& BLOB::content() const
inline const char* BLOB::rawContent() const
{
poco_assert (_pContent->size());
return &(*_pContent)[0];
}

View File

@ -52,6 +52,8 @@
#include "Poco/Format.h"
#include "Poco/Exception.h"
#include <vector>
#include <list>
#include <deque>
#include <sstream>
@ -239,13 +241,54 @@ private:
void resetExtraction();
/// Resets binding so it can be reused again.
template <class T, class C>
template <class T>
void addInternalExtract(const MetaColumn& mc)
/// Utility function to create and add an internal extraction.
/// Creates and adds the internal extraction.
///
/// The decision about internal extraction container is done
/// in a following way:
///
/// If this statement has _storage member set, that setting
/// overrides the session setting for storage, otherwise the
/// session setting is used.
/// If neither this statement nor the session have the storage
/// type set, std::vector is the default container type used.
{
C* pData = new C;
Column<T,C>* pCol = new Column<T,C>(mc, pData);
addExtract(new InternalExtraction<T,C>(*pData, pCol));
std::string storage;
switch (_storage)
{
case STORAGE_VECTOR_IMPL:
storage = VECTOR; break;
case STORAGE_LIST_IMPL:
storage = LIST; break;
case STORAGE_DEQUE_IMPL:
storage = DEQUE; break;
case STORAGE_UNKNOWN_IMPL:
storage = AnyCast<std::string>(session().getProperty("storage"));
break;
}
if (storage.empty()) storage = VECTOR;
if (0 == icompare(VECTOR, storage))
{
std::vector<T>* pData = new std::vector<T>;
Column<T,std::vector<T> >* pCol = new Column<T, std::vector<T> >(mc, pData);
addExtract(new InternalExtraction<T, std::vector<T> >(*pData, pCol));
}
else if (0 == icompare(LIST, storage))
{
std::list<T>* pData = new std::list<T>;
Column<T,std::list<T> >* pCol = new Column<T, std::list<T> >(mc, pData);
addExtract(new InternalExtraction<T, std::list<T> >(*pData, pCol));
}
else if (0 == icompare(DEQUE, storage))
{
std::deque<T>* pData = new std::deque<T>;
Column<T,std::deque<T> >* pCol = new Column<T, std::deque<T> >(mc, pData);
addExtract(new InternalExtraction<T, std::deque<T> >(*pData, pCol));
}
}
StatementImpl(const StatementImpl& stmt);

View File

@ -41,7 +41,7 @@
#include "Poco/Data/Extraction.h"
#include "Poco/Data/BLOB.h"
#include "Poco/SharedPtr.h"
#include "Poco/String.h"
#include "Poco/DateTime.h"
#include "Poco/Exception.h"
@ -283,20 +283,6 @@ void StatementImpl::setStorage(const std::string& storage)
void StatementImpl::makeExtractors(Poco::UInt32 count)
{
std::string storage;
switch (_storage)
{
case STORAGE_VECTOR_IMPL: storage = VECTOR; break;
case STORAGE_LIST_IMPL: storage = LIST; break;
case STORAGE_DEQUE_IMPL: storage = DEQUE; break;
case STORAGE_UNKNOWN_IMPL:
storage = AnyCast<std::string>(session().getProperty("storage"));
break;
}
if ("" == storage) storage = VECTOR;
for (int i = 0; i < count; ++i)
{
const MetaColumn& mc = metaColumn(i);
@ -304,101 +290,31 @@ void StatementImpl::makeExtractors(Poco::UInt32 count)
{
case MetaColumn::FDT_BOOL:
case MetaColumn::FDT_INT8:
if (0 == icompare(VECTOR, storage))
addInternalExtract<Int8, std::vector<Int8> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<Int8, std::list<Int8> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<Int8, std::deque<Int8> >(mc);
break;
addInternalExtract<Int8>(mc); break;
case MetaColumn::FDT_UINT8:
if (0 == icompare(VECTOR, storage))
addInternalExtract<UInt8, std::vector<UInt8> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<UInt8, std::list<UInt8> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<UInt8, std::deque<UInt8> >(mc);
break;
addInternalExtract<UInt8>(mc); break;
case MetaColumn::FDT_INT16:
if (0 == icompare(VECTOR, storage))
addInternalExtract<Int16, std::vector<Int16> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<Int16, std::list<Int16> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<Int16, std::deque<Int16> >(mc);
break;
addInternalExtract<Int16>(mc); break;
case MetaColumn::FDT_UINT16:
if (0 == icompare(VECTOR, storage))
addInternalExtract<UInt16, std::vector<UInt16> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<UInt16, std::list<UInt16> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<UInt16, std::deque<UInt16> >(mc);
break;
addInternalExtract<UInt16>(mc); break;
case MetaColumn::FDT_INT32:
if (0 == icompare(VECTOR, storage))
addInternalExtract<Int32, std::vector<Int32> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<Int32, std::list<Int32> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<Int32, std::deque<Int32> >(mc);
break;
addInternalExtract<Int32>(mc); break;
case MetaColumn::FDT_UINT32:
if (0 == icompare(VECTOR, storage))
addInternalExtract<UInt32, std::vector<UInt32> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<UInt32, std::list<UInt32> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<UInt32, std::deque<UInt32> >(mc);
break;
addInternalExtract<UInt32>(mc); break;
case MetaColumn::FDT_INT64:
if (0 == icompare(VECTOR, storage))
addInternalExtract<Int64, std::vector<Int64> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<Int64, std::list<Int64> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<Int64, std::deque<Int64> >(mc);
break;
addInternalExtract<Int64>(mc); break;
case MetaColumn::FDT_UINT64:
if (0 == icompare(VECTOR, storage))
addInternalExtract<UInt64, std::vector<UInt64> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<UInt64, std::list<UInt64> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<UInt64, std::deque<UInt64> >(mc);
break;
addInternalExtract<UInt64>(mc); break;
case MetaColumn::FDT_FLOAT:
if (0 == icompare(VECTOR, storage))
addInternalExtract<float, std::vector<float> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<float, std::list<float> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<float, std::deque<float> >(mc);
break;
addInternalExtract<float>(mc); break;
case MetaColumn::FDT_DOUBLE:
if (0 == icompare(VECTOR, storage))
addInternalExtract<double, std::vector<double> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<double, std::list<double> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<double, std::deque<double> >(mc);
break;
addInternalExtract<double>(mc); break;
case MetaColumn::FDT_STRING:
if (0 == icompare(VECTOR, storage))
addInternalExtract<std::string, std::vector<std::string> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<std::string, std::list<std::string> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<std::string, std::deque<std::string> >(mc);
break;
addInternalExtract<std::string>(mc); break;
case MetaColumn::FDT_BLOB:
if (0 == icompare(VECTOR, storage))
addInternalExtract<BLOB, std::vector<BLOB> >(mc);
else if (0 == icompare(LIST, storage))
addInternalExtract<BLOB, std::list<BLOB> >(mc);
else if (0 == icompare(DEQUE, storage))
addInternalExtract<BLOB, std::deque<BLOB> >(mc);
break;
addInternalExtract<BLOB>(mc); break;
case MetaColumn::FDT_TIMESTAMP:
addInternalExtract<DateTime>(mc); break;
default:
throw Poco::InvalidArgumentException("Data type not supported.");
}