Stored procedures for DB2 & PostgreSQL, StatementImpl::isStoredProcedure() function

This commit is contained in:
Aleksandar Fabijanic 2007-06-16 02:48:57 +00:00
parent aaea87c6e2
commit 5feefc75cd
16 changed files with 366 additions and 46 deletions

View File

@ -157,13 +157,8 @@ 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
SQLSMALLINT toODBCDirection(Direction dir) const;
/// Returns ODBC parameter direction based on the parameter binding direction
/// specified by user.
template <typename T>
@ -184,7 +179,7 @@ private:
if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
getParamType(dir),
toODBCDirection(dir),
cDataType,
Utility::sqlDataType(cDataType),
colSize,

View File

@ -57,8 +57,8 @@ template <typename H, SQLSMALLINT handleType>
class Diagnostics
/// Utility class providing functionality for retrieving ODBC diagnostic
/// records. Diagnostics object must be created with corresponding handle
/// as constructor argument. During construction, diagnostic records with corresponding
/// fields are populated and the object is ready for querying on various diagnostic fields.
/// as constructor argument. During construction, diagnostic records fields
/// are populated and the object is ready for querying.
{
public:
@ -147,12 +147,12 @@ public:
return _fields;
}
const Iterator begin() const
Iterator begin() const
{
return _fields.begin();
}
const Iterator end() const
Iterator end() const
{
return _fields.end();
}

View File

@ -132,6 +132,12 @@ private:
void fillColumns();
void checkError(SQLRETURN rc, const std::string& msg="");
bool isStoredProcedure() const;
/// Returns true if this statement is stored procedure.
/// Only the ODBC CALL escape sequence is supported.
/// The function checks whether trimmed statement
/// text begins with '{' and ends with '}';
const SQLHDBC& _rConnection;
const StatementHandle _stmt;
@ -171,7 +177,7 @@ inline Poco::UInt32 ODBCStatementImpl::columnsReturned() const
inline bool ODBCStatementImpl::hasData() const
{
poco_assert_dbg (_pPreparation);
return (_pPreparation->columns() > 0);
return (_pPreparation->columns(!isStoredProcedure()) > 0);
}

View File

@ -150,8 +150,9 @@ public:
void prepare(std::size_t pos, const Poco::Any&);
/// Prepares an Any.
std::size_t columns() const;
std::size_t columns(bool resize = true) const;
/// Returns the number of columns.
/// Resizes the internal storage iff resize is true.
Poco::Any& operator [] (std::size_t pos);
/// Returns reference to column data.

View File

@ -81,8 +81,26 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
if (isOutBound(dir))
{
Parameter p(_rStmt, pos);
size = (SQLINTEGER) p.columnSize();
try
{
Parameter p(_rStmt, pos);
size = (SQLUINTEGER) p.columnSize();
}
catch (StatementException&)
// some drivers (e.g. MS SQL) refuse to describe output parameters
{
SQLHDESC hIPD = 0;
if (!Utility::isError(SQLGetStmtAttr(_rStmt, SQL_ATTR_IMP_PARAM_DESC, &hIPD, 0, 0)))
{
size = 1024;
SQLUINTEGER sz = size;
if (Utility::isError(SQLSetDescField(hIPD, (SQLSMALLINT) pos + 1, SQL_DESC_LENGTH, &sz, 0)))
throw StatementException(_rStmt, "Could not set output parameter size");
}
else
throw StatementException(_rStmt, "Could not get statement IPD attribute handle.");
}
char* pChar = (char*) std::calloc(size, sizeof(char));
pVal = (SQLPOINTER) pChar;
_outParams.insert(ParamMap::value_type(pVal, size));
@ -106,7 +124,7 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
getParamType(dir),
toODBCDirection(dir),
SQL_C_CHAR,
SQL_LONGVARCHAR,
(SQLUINTEGER) size,
@ -181,7 +199,7 @@ void Binder::bind(std::size_t pos, const Poco::DateTime& val, Direction dir)
if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
getParamType(dir),
toODBCDirection(dir),
SQL_C_TIMESTAMP,
SQL_TIMESTAMP,
colSize,
@ -213,7 +231,7 @@ void Binder::bind(std::size_t pos, const char* const &pVal, Direction dir)
}
SQLSMALLINT Binder::getParamType(Direction dir) const
SQLSMALLINT Binder::toODBCDirection(Direction dir) const
{
bool in = isInBound(dir);
bool out = isOutBound(dir);

View File

@ -347,4 +347,13 @@ void ODBCStatementImpl::fillColumns()
}
bool ODBCStatementImpl::isStoredProcedure() const
{
std::string str = toString();
if (trimInPlace(str).size() < 2) return false;
return ('{' == str[0] && '}' == str[str.size()-1]);
}
} } } // namespace Poco::Data::ODBC

View File

@ -69,15 +69,13 @@ Preparation::~Preparation()
}
std::size_t Preparation::columns() const
std::size_t Preparation::columns(bool resize) const
{
if (_pValues.empty())
if (_pValues.empty() && resize)
{
SQLSMALLINT nCol = 0;
if (Utility::isError(SQLNumResultCols(_rStmt, &nCol)))
throw StatementException(_rStmt);
if (nCol)
if (!Utility::isError(SQLNumResultCols(_rStmt, &nCol)) &&
0 != nCol)
{
_pValues.resize(nCol, 0);
_pLengths.resize(nCol, 0);

View File

@ -35,6 +35,7 @@
#include "CppUnit/TestSuite.h"
#include "Poco/String.h"
#include "Poco/Format.h"
#include "Poco/Tuple.h"
#include "Poco/Exception.h"
#include "Poco/Data/Common.h"
#include "Poco/Data/BLOB.h"
@ -54,6 +55,7 @@ using Poco::Data::ODBC::ConnectionException;
using Poco::Data::ODBC::StatementException;
using Poco::Data::ODBC::StatementDiagnostics;
using Poco::format;
using Poco::Tuple;
using Poco::NotFoundException;
@ -859,6 +861,170 @@ void ODBCDB2Test::testInternalStorageType()
}
void ODBCDB2Test::testStoredProcedure()
{
if (!_pSession) fail ("Test not available.");
for (int k = 0; k < 8;)
{
_pSession->setFeature("autoBind", bindValues[k]);
_pSession->setFeature("autoExtract", bindValues[k+1]);
dropObject("PROCEDURE", "storedProcedure");
*_pSession << "CREATE PROCEDURE storedProcedure(OUT outParam INTEGER) "
"BEGIN "
" SET outParam = -1; "
"END" , now;
int i = 0;
*_pSession << "{call storedProcedure(?)}", out(i), now;
assert(-1 == i);
dropObject("PROCEDURE", "storedProcedure");
*_pSession << "CREATE PROCEDURE storedProcedure(inParam INTEGER, OUT outParam INTEGER) "
"BEGIN "
" SET outParam = inParam*inParam; "
"END" , now;
i = 2;
int j = 0;
*_pSession << "{call storedProcedure(?, ?)}", in(i), out(j), now;
assert(4 == j);
dropObject("PROCEDURE", "storedProcedure");
*_pSession << "CREATE PROCEDURE storedProcedure(INOUT ioParam INTEGER) "
"BEGIN "
" SET ioParam = ioParam*ioParam; "
"END" , now;
i = 2;
*_pSession << "{call storedProcedure(?)}", io(i), now;
assert(4 == i);
dropObject("PROCEDURE", "storedProcedure");
//TIMESTAMP is not supported as stored procedure parameter in DB2
//(SQL0182N An expression with a datetime value or a labeled duration is not valid.)
*_pSession << "CREATE PROCEDURE storedProcedure(inParam VARCHAR(1000), OUT outParam VARCHAR(1000)) "
"BEGIN "
" SET outParam = inParam; "
"END" , now;
std::string inParam =
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
std::string outParam;
*_pSession << "{call storedProcedure(?,?)}", in(inParam), out(outParam), now;
assert(inParam == outParam);
dropObject("PROCEDURE", "storedProcedure");
k += 2;
}
}
void ODBCDB2Test::testStoredFunction()
{
if (!_pSession) fail ("Test not available.");
for (int k = 0; k < 8;)
{
_pSession->setFeature("autoBind", bindValues[k]);
_pSession->setFeature("autoExtract", bindValues[k+1]);
dropObject("PROCEDURE", "storedFunction");
*_pSession << "CREATE PROCEDURE storedFunction() "
"BEGIN "
" RETURN -1; "
"END" , now;
int i = 0;
*_pSession << "{? = call storedFunction()}", out(i), now;
assert(-1 == i);
dropObject("PROCEDURE", "storedFunction");
*_pSession << "CREATE PROCEDURE storedFunction(inParam INTEGER) "
"BEGIN "
" RETURN inParam*inParam; "
"END" , now;
i = 2;
int result = 0;
*_pSession << "{? = call storedFunction(?)}", out(result), in(i), now;
assert(4 == result);
dropObject("PROCEDURE", "storedFunction");
*_pSession << "CREATE PROCEDURE storedFunction(inParam INTEGER, OUT outParam INTEGER) "
"BEGIN "
" SET outParam = inParam*inParam; "
" RETURN outParam; "
"END" , now;
i = 2;
int j = 0;
result = 0;
*_pSession << "{? = call storedFunction(?, ?)}", out(result), in(i), out(j), now;
assert(4 == j);
assert(j == result);
dropObject("PROCEDURE", "storedFunction");
*_pSession << "CREATE PROCEDURE storedFunction(INOUT param1 INTEGER, INOUT param2 INTEGER) "
"BEGIN "
" DECLARE temp INTEGER;"
" SET temp = param1; "
" SET param1 = param2; "
" SET param2 = temp; "
" RETURN param1 + param2; "
"END" , now;
i = 1;
j = 2;
result = 0;
*_pSession << "{? = call storedFunction(?, ?)}", out(result), io(i), io(j), now;
assert(1 == j);
assert(2 == i);
assert(3 == result);
Tuple<int, int> params(1, 2);
assert(1 == params.get<0>());
assert(2 == params.get<1>());
result = 0;
*_pSession << "{? = call storedFunction(?, ?)}", out(result), io(params), now;
assert(1 == params.get<1>());
assert(2 == params.get<0>());
assert(3 == result);
dropObject("PROCEDURE", "storedFunction");
_pSession->setFeature("autoBind", true);
*_pSession << "CREATE PROCEDURE storedFunction(inParam VARCHAR(10), OUT outParam VARCHAR(10)) "
"BEGIN "
" SET outParam = inParam; "
" RETURN LENGTH(outParam);"//DB2 allows only integer as return type
"END" , now;
std::string inParam = "123456789";
std::string outParam;
int ret;
*_pSession << "{? = call storedFunction(?,?)}", out(ret), in(inParam), out(outParam), now;
assert(inParam == outParam);
assert(ret == inParam.size());
dropObject("PROCEDURE", "storedFunction");
k += 2;
}
}
void ODBCDB2Test::dropObject(const std::string& type, const std::string& name)
{
try
@ -1098,6 +1264,8 @@ CppUnit::Test* ODBCDB2Test::suite()
CppUnit_addTest(pSuite, ODBCDB2Test, testTupleVector);
CppUnit_addTest(pSuite, ODBCDB2Test, testInternalExtraction);
CppUnit_addTest(pSuite, ODBCDB2Test, testInternalStorageType);
CppUnit_addTest(pSuite, ODBCDB2Test, testStoredProcedure);
CppUnit_addTest(pSuite, ODBCDB2Test, testStoredFunction);
return pSuite;
}

View File

@ -119,6 +119,9 @@ public:
void testInternalExtraction();
void testInternalStorageType();
void testStoredProcedure();
void testStoredFunction();
void setUp();
void tearDown();

View File

@ -1023,7 +1023,7 @@ void ODBCOracleTest::testStoredFunction()
k += 2;
}
//string and BLOB for automatic binding only
//string for automatic binding only
_pSession->setFeature("autoBind", true);
*_pSession << "CREATE OR REPLACE "

View File

@ -51,6 +51,7 @@
using namespace Poco::Data;
using Poco::Data::ODBC::Utility;
using Poco::Data::ODBC::ODBCException;
using Poco::Data::ODBC::ConnectionException;
using Poco::Data::ODBC::StatementException;
using Poco::Data::ODBC::StatementDiagnostics;
@ -64,6 +65,11 @@ Poco::SharedPtr<Poco::Data::Session> ODBCPostgreSQLTest::_pSession = 0;
Poco::SharedPtr<SQLExecutor> ODBCPostgreSQLTest::_pExecutor = 0;
std::string ODBCPostgreSQLTest::_dbConnString;
Poco::Data::ODBC::Utility::DriverMap ODBCPostgreSQLTest::_drivers;
#ifdef POCO_OS_FAMILY_WINDOWS
const std::string ODBCPostgreSQLTest::libDir = "C:\\\\Program Files\\\\PostgreSQL\\\\8.2\\\\lib\\\\";
#else
const std::string ODBCPostgreSQLTest::libDir = "/usr/local/pgsql/lib/";
#endif
ODBCPostgreSQLTest::ODBCPostgreSQLTest(const std::string& name):
@ -857,6 +863,83 @@ void ODBCPostgreSQLTest::testInternalStorageType()
}
void ODBCPostgreSQLTest::testStoredFunction()
{
configurePLPgSQL();
for (int k = 0; k < 8;)
{
_pSession->setFeature("autoBind", bindValues[k]);
_pSession->setFeature("autoExtract", bindValues[k+1]);
*_pSession << "CREATE FUNCTION storedFunction() RETURNS INTEGER AS '"
"BEGIN "
" return -1; "
"END;'"
"LANGUAGE 'plpgsql'", now;
int i = 0;
*_pSession << "{? = call storedFunction()}", out(i), now;
assert(-1 == i);
dropObject("FUNCTION", "storedFunction()");
*_pSession << "CREATE FUNCTION storedFunction(INTEGER) RETURNS INTEGER AS '"
"BEGIN "
" RETURN $1 * $1; "
"END;'"
"LANGUAGE 'plpgsql'" , now;
i = 2;
int result = 0;
*_pSession << "{? = call storedFunction(?)}", out(result), in(i), now;
assert(4 == result);
dropObject("FUNCTION", "storedFunction(INTEGER)");
/*TODO
*_pSession << "CREATE FUNCTION storedFunction(TEXT) RETURNS TEXT AS '"
"BEGIN "
" RETURN $1;"
"END;'"
"LANGUAGE 'plpgsql'" , now;
std::string param = "123";
std::string ret;
try {
*_pSession << "{? = call storedFunction(?)}", out(ret), in(param), now;
}catch(StatementException & ex) { std::cout << ex.toString();}
assert(ret == param);
dropObject("FUNCTION", "storedFunction(TEXT)");
*/
k += 2;
}
}
void ODBCPostgreSQLTest::configurePLPgSQL()
{
if (!_pSession) fail ("Test not available.");
try
{
*_pSession << format("CREATE FUNCTION plpgsql_call_handler () "
"RETURNS OPAQUE "
"AS '%splpgsql.dll' "
"LANGUAGE 'C';", libDir), now;
*_pSession << "CREATE LANGUAGE 'plpgsql' "
"HANDLER plpgsql_call_handler "
"LANCOMPILER 'PL/pgSQL'", now;
}catch(StatementException& ex)
{
if (7 != ex.diagnostics().nativeError(0))
throw;
}
return;
}
void ODBCPostgreSQLTest::dropObject(const std::string& type, const std::string& name)
{
try
@ -1063,7 +1146,7 @@ bool ODBCPostgreSQLTest::init(const std::string& driver, const std::string& dsn)
if (_pSession && _pSession->isConnected())
std::cout << "*** Connected to [" << driver << "] test database." << std::endl;
_pExecutor = new SQLExecutor(driver + " SQL Executor", _pSession);
return true;
@ -1073,6 +1156,7 @@ bool ODBCPostgreSQLTest::init(const std::string& driver, const std::string& dsn)
CppUnit::Test* ODBCPostgreSQLTest::suite()
{
if (init("PostgreSQL ANSI", "PocoDataPostgreSQLTest"))
//if (init("Mammoth ODBCng Beta", "Mammoth ODBCng beta"))
{
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ODBCPostgreSQLTest");
@ -1126,6 +1210,7 @@ CppUnit::Test* ODBCPostgreSQLTest::suite()
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testTupleVector);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testInternalExtraction);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testInternalStorageType);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testStoredFunction);
return pSuite;
}

View File

@ -122,6 +122,8 @@ public:
void testInternalExtraction();
void testInternalStorageType();
void testStoredFunction();
void setUp();
void tearDown();
@ -140,12 +142,25 @@ private:
static bool init(const std::string& driver, const std::string& dsn);
static bool canConnect(const std::string& driver, const std::string& dsn);
void configurePLPgSQL();
/// Configures PL/pgSQL in the database. A reasonable defaults
/// for the interpreter location on WIN32 and POSIX platforms are
/// supplied (see installDir member variable).
/// If these do not work, user must determine the proper location,
/// modify the function and recompile.
/// Alternative is direct database configuration for PL/pgSQL usage.
static Poco::Data::ODBC::Utility::DriverMap _drivers;
static std::string _dbConnString;
static Poco::SharedPtr<Poco::Data::Session> _pSession;
static Poco::SharedPtr<SQLExecutor> _pExecutor;
static const bool bindValues[8];
static const std::string libDir;
/// Varible determining the location of the library directory
/// on the database installation system.
/// Used to enable PLpgSQL language programmaticaly when
/// it is not enabled.
};

View File

@ -907,9 +907,14 @@ void ODBCSQLServerTest::testStoredProcedure()
k += 2;
}
/*TODO
/*TODO - currently fails with following error:
[Microsoft][ODBC SQL Server Driver][SQL Server]Invalid parameter
2 (''): Data type 0x23 is a deprecated large object, or LOB, but is marked as output parameter.
Deprecated types are not supported as output parameters. Use current large object types instead.
_pSession->setFeature("autoBind", true);
*_pSession << "CREATE PROCEDURE storedProcedure(@inParam VARCHAR, @outParam VARCHAR OUTPUT) AS "
*_pSession << "CREATE PROCEDURE storedProcedure(@inParam VARCHAR(MAX), @outParam VARCHAR(MAX) OUTPUT) AS "
"BEGIN "
"SET @outParam = @inParam; "
"END;"
@ -1206,6 +1211,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
{
#ifdef POCO_OS_FAMILY_WINDOWS
if (init("SQL Server", "PocoDataSQLServerTest"))
//if (init("SQL Native Client", "PocoDataSQLServerTest"))
#else
if (init("FreeTDS", "PocoDataSQLServerTest"))
#endif

View File

@ -1,7 +1,7 @@
//
// SQLExecutor.cpp
//
// $Id: //poco/Main/DataConnectors/ODBC/testsuite/src/SQLExecutor.cpp#14 $
// $Id: //poco/Main/Data/ODBC/testsuite/src/SQLExecutor.cpp#14 $
//
// Copyright (c) 2006, Applied Informatics Software Engineering GmbH.
// and Contributors.

View File

@ -222,6 +222,20 @@ protected:
void resetBinding();
/// Resets binding so it can be reused again.
virtual bool isStoredProcedure() const;
/// Returns true if the statement is stored procedure.
/// Used as a help to determine whether to automatically create the
/// internal extractions when no outside extraction is supplied.
/// The reason for this function is to prevent unnecessary internal
/// extraction creation in cases (behavior exhibited by some ODBC drivers)
/// when there is data available from the stored procedure call
/// statement execution but no external extraction is supplied (as is
/// usually the case when stored procedures are called). In such cases
/// no storage is needed because output parameters serve as storage.
/// At the Data framework level, this function always returns false.
/// When connector-specific behavior is desired, it should be overriden
/// by the statement implementation.
private:
void compile();
/// Compiles the statement, if not yet compiled. doesn't bind yet
@ -241,6 +255,14 @@ private:
void resetExtraction();
/// Resets binding so it can be reused again.
template <class T, class C>
InternalExtraction<T,C>* createExtract(const MetaColumn& mc)
{
C* pData = new C;
Column<T,C>* pCol = new Column<T,C>(mc, pData);
return new InternalExtraction<T,C>(*pData, pCol);
}
template <class T>
void addInternalExtract(const MetaColumn& mc)
/// Creates and adds the internal extraction.
@ -272,23 +294,11 @@ private:
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));
}
addExtract(createExtract<T, std::vector<T> >(mc));
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));
}
addExtract(createExtract<T, std::list<T> >(mc));
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));
}
addExtract(createExtract<T, std::deque<T> >(mc));
}
StatementImpl(const StatementImpl& stmt);
@ -389,6 +399,12 @@ inline std::size_t StatementImpl::extractionCount() const
}
inline bool StatementImpl::isStoredProcedure() const
{
return false;
}
} } // namespace Poco::Data

View File

@ -153,7 +153,7 @@ void StatementImpl::compile()
compileImpl();
_state = ST_COMPILED;
if (!extractions().size())
if (!extractions().size() && !isStoredProcedure())
{
Poco::UInt32 cols = columnsReturned();
if (cols) makeExtractors(cols);