support for stored procedures returning recordset

This commit is contained in:
Aleksandar Fabijanic 2007-10-28 23:08:35 +00:00
parent 9ea88d25dd
commit 424b717920
10 changed files with 136 additions and 64 deletions

View File

@ -118,6 +118,16 @@ private:
void doBind(bool clear = true, bool reset = false);
/// Binds parameters.
void makeInternalExtractors();
/// Creates internal extractors if none were supplied from the user.
void doPrepare();
/// Prepares placeholders for data returned by statement.
/// It is called during statement compilation for SQL statements
/// returning data. For stored procedures returning datasets,
/// it is called upon the first check for data availability
/// (see hasNext() function).
bool hasData() const;
/// Returns true if statement returns data.
@ -132,12 +142,6 @@ 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;
@ -147,6 +151,7 @@ private:
bool _stepCalled;
int _nextResponse;
ColumnPtrVec _columnPtrs;
bool _prepared;
};
@ -177,7 +182,7 @@ inline Poco::UInt32 ODBCStatementImpl::columnsReturned() const
inline bool ODBCStatementImpl::hasData() const
{
poco_assert_dbg (_pPreparation);
return (_pPreparation->columns(!isStoredProcedure()) > 0);
return (_pPreparation->columns() > 0);
}

View File

@ -150,9 +150,9 @@ public:
void prepare(std::size_t pos, const Poco::Any&);
/// Prepares an Any.
std::size_t columns(bool resize = true) const;
std::size_t columns() const;
/// Returns the number of columns.
/// Resizes the internal storage iff resize is true.
/// Resizes the internal storage iff the size is zero.
Poco::Any& operator [] (std::size_t pos);
/// Returns reference to column data.
@ -337,25 +337,6 @@ inline void Preparation::prepare(std::size_t pos, const Poco::DateTime&)
}
inline std::size_t Preparation::maxDataSize(std::size_t pos) const
{
poco_assert (pos >= 0 && pos < _pValues.size());
std::size_t sz = 0;
std::size_t maxsz = getMaxFieldSize();
try
{
sz = ODBCColumn(_rStmt, pos).length();
}
catch (StatementException&)
{
}
if (!sz || sz > maxsz) sz = maxsz;
return sz;
}
inline int Preparation::actualDataSize(std::size_t pos) const
{
poco_assert (pos >= 0 && pos < _pValues.size());

View File

@ -55,7 +55,8 @@ ODBCStatementImpl::ODBCStatementImpl(SessionImpl& rSession):
_rConnection(rSession.dbc()),
_stmt(rSession.dbc()),
_stepCalled(false),
_nextResponse(0)
_nextResponse(0),
_prepared(false)
{
if (session().getFeature("autoBind"))
{
@ -120,15 +121,28 @@ void ODBCStatementImpl::compileImpl()
// these calls must occur before.
fixupBinding(); doBind(false, true);
bool dataAvailable = hasData();
if (dataAvailable && !extractions().size())
makeInternalExtractors();
doPrepare();
}
void ODBCStatementImpl::makeInternalExtractors()
{
if (hasData() && !extractions().size())
{
fillColumns();
makeExtractors(columnsReturned());
fixupExtraction();
}
}
if (Preparation::DE_BOUND == ext && dataAvailable)
void ODBCStatementImpl::doPrepare()
{
if (!_prepared && session().getFeature("autoExtract") && hasData())
{
poco_check_ptr (_pPreparation);
Extractions& extracts = extractions();
Extractions::iterator it = extracts.begin();
Extractions::iterator itEnd = extracts.end();
@ -139,6 +153,7 @@ void ODBCStatementImpl::compileImpl()
pos += (*it)->numOfColumnsHandled();
delete pAP;
}
_prepared = true;
}
}
@ -240,6 +255,11 @@ bool ODBCStatementImpl::hasNext()
{
if (hasData())
{
if (!extractions().size())
makeInternalExtractors();
if (!_prepared) doPrepare();
if (_stepCalled)
return _stepCalled = nextRowReady();
@ -347,13 +367,4 @@ 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,9 +69,9 @@ Preparation::~Preparation()
}
std::size_t Preparation::columns(bool resize) const
std::size_t Preparation::columns() const
{
if (_pValues.empty() && resize)
if (_pValues.empty())
{
SQLSMALLINT nCol = 0;
if (!Utility::isError(SQLNumResultCols(_rStmt, &nCol)) &&
@ -145,4 +145,22 @@ void Preparation::prepare(std::size_t pos, const Poco::Any&)
}
std::size_t Preparation::maxDataSize(std::size_t pos) const
{
poco_assert (pos >= 0 && pos < _pValues.size());
std::size_t sz = 0;
std::size_t maxsz = getMaxFieldSize();
try
{
sz = ODBCColumn(_rStmt, pos).length();
}
catch (StatementException&) { }
if (!sz || sz > maxsz) sz = maxsz;
return sz;
}
} } } // namespace Poco::Data::ODBC

View File

@ -40,6 +40,7 @@
#include "Poco/Exception.h"
#include "Poco/Data/Common.h"
#include "Poco/Data/BLOB.h"
#include "Poco/Data/RecordSet.h"
#include "Poco/Data/StatementImpl.h"
#include "Poco/Data/ODBC/Connector.h"
#include "Poco/Data/ODBC/Utility.h"
@ -998,6 +999,45 @@ void ODBCOracleTest::testStoredFunction()
assert(-1 == i);
dropObject("FUNCTION", "storedFunction");
recreatePersonTable();
typedef Tuple<std::string, std::string, std::string, int> Person;
std::vector<Person> people;
people.push_back(Person("Simpson", "Homer", "Springfield", 42));
people.push_back(Person("Simpson", "Bart", "Springfield", 12));
people.push_back(Person("Simpson", "Lisa", "Springfield", 10));
*_pSession << "INSERT INTO Person VALUES (?, ?, ?, ?)", use(people), now;
*_pSession << "CREATE OR REPLACE "
"FUNCTION storedCursorFunction(ageLimit IN NUMBER) RETURN SYS_REFCURSOR IS "
" ret SYS_REFCURSOR; "
" BEGIN "
" OPEN ret FOR "
" SELECT * "
" FROM Person "
" WHERE Age < ageLimit "
" ORDER BY Age DESC; "
" RETURN ret; "
" END storedCursorFunction;" , now;
people.clear();
int age = 13;
*_pSession << "{call storedCursorFunction(?)}", in(age), into(people), now;
assert (2 == people.size());
assert (Person("Simpson", "Bart", "Springfield", 12) == people[0]);
assert (Person("Simpson", "Lisa", "Springfield", 10) == people[1]);
Statement stmt = ((*_pSession << "{call storedCursorFunction(?)}", in(age), now));
RecordSet rs(stmt);
assert (rs["LastName"] == "Simpson");
assert (rs["FirstName"] == "Bart");
assert (rs["Address"] == "Springfield");
assert (rs["Age"] == 12);
dropObject("TABLE", "Person");
dropObject("FUNCTION", "storedCursorFunction");
*_pSession << "CREATE OR REPLACE "
"FUNCTION storedFunction(inParam IN NUMBER) RETURN NUMBER IS "
" BEGIN RETURN(inParam*inParam); "
@ -1092,6 +1132,7 @@ void ODBCOracleTest::testAsync()
_pSession->setFeature("autoBind", bindValues[i]);
_pSession->setFeature("autoExtract", bindValues[i+1]);
_pExecutor->asynchronous();
i += 2;
}
}

View File

@ -1257,7 +1257,11 @@ bool ODBCSQLServerTest::canConnect(const std::string& driver, const std::string&
{
std::cout << "DSN found: " << itDSN->first
<< " (" << itDSN->second << ')' << std::endl;
format(_dbConnString, "DSN=%s", dsn);
format(_dbConnString,
"DSN=%s;"
"UID=test;"
"PWD=test;"
"DATABASE=test;", dsn);
return true;
}
}

View File

@ -2245,7 +2245,7 @@ void SQLExecutor::asynchronous()
{
Session tmp = *_pSession;
int rowCount = 500;
int rowCount = 2000;
std::vector<int> data(rowCount);
Statement stmt = (tmp << "INSERT INTO Strings VALUES(?)", use(data));
Statement::Result result = stmt.executeAsync();
@ -2256,31 +2256,37 @@ void SQLExecutor::asynchronous()
assert (stmt1.isAsync());
assert (stmt1.wait() == rowCount);
// +++ if this part of the test case fails, increase the rowCount until achieved
// that first execute is still executing when the second one is called
stmt1.execute();
try {
stmt1.execute();
fail ("must fail");
fail ("execute() must fail");
} catch (InvalidAccessException&)
{
stmt1.wait();
stmt1.execute();
stmt1.wait();
}
// ---
stmt = tmp << "SELECT * FROM Strings", into(data), async, now;
assert (stmt.isAsync());
stmt.wait();
assert (stmt.execute() == 0);
// +++ if this part of the test case fails, increase the rowCount until achieved
// that first execute is still executing when the second one is called
try {
result = stmt.executeAsync();
fail ("must fail");
fail ("executeAsync() must fail");
} catch (InvalidAccessException&)
{
assert (stmt.isAsync());
stmt.wait();
result = stmt.executeAsync();
}
// ---
assert (stmt.wait() == rowCount);
assert (result.data() == rowCount);

View File

@ -119,7 +119,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -249,7 +249,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -306,7 +306,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -363,7 +363,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -420,7 +420,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -477,7 +477,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -534,7 +534,7 @@ public:
_end(val.end())
/// Creates the Binding.
{
if (numOfRowsHandled() == 0)
if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection");
}
@ -579,7 +579,8 @@ private:
};
template <typename T> Binding<T>* use(const T& t, const std::string& name = "")
template <typename T>
Binding<T>* use(const T& t, const std::string& name = "")
/// Convenience function for a more compact Binding creation.
{
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN);
@ -588,7 +589,8 @@ template <typename T> Binding<T>* use(const T& t, const std::string& name = "")
}
template <typename T> Binding<T>* in(const T& t, const std::string& name = "")
template <typename T>
Binding<T>* in(const T& t, const std::string& name = "")
/// Convenience function for a more compact Binding creation.
{
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN);
@ -597,7 +599,8 @@ template <typename T> Binding<T>* in(const T& t, const std::string& name = "")
}
template <typename T> Binding<T>* out(const T& t, const std::string& name = "")
template <typename T>
Binding<T>* out(const T& t, const std::string& name = "")
/// Convenience function for a more compact Binding creation.
{
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_OUT);
@ -606,7 +609,8 @@ template <typename T> Binding<T>* out(const T& t, const std::string& name = "")
}
template <typename T> Binding<T>* io(const T& t, const std::string& name = "")
template <typename T>
Binding<T>* io(const T& t, const std::string& name = "")
/// Convenience function for a more compact Binding creation.
{
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN_OUT);

View File

@ -632,14 +632,16 @@ private:
};
template <typename T> Extraction<T>* into(T& t)
/// Convenience function to allow for a more compact creation of a default extraction object
template <typename T>
Extraction<T>* into(T& t)
/// Convenience function to allow for a more compact creation of an extraction object
{
return new Extraction<T>(t);
}
template <typename T> Extraction<T>* into(T& t, const T& def)
template <typename T>
Extraction<T>* into(T& t, const T& def)
/// Convenience function to allow for a more compact creation of an extraction object with the given default
{
return new Extraction<T>(t, def);

View File

@ -235,6 +235,9 @@ protected:
/// When connector-specific behavior is desired, it should be overriden
/// by the statement implementation.
void fixupExtraction();
/// Sets the AbstractExtractor at the extractors.
private:
void compile();
/// Compiles the statement, if not yet compiled. doesn't bind yet
@ -248,9 +251,6 @@ private:
Poco::UInt32 executeWithoutLimit();
/// Executes without an upper limit set.
void fixupExtraction();
/// Sets the AbstractExtractor at the extractors.
void resetExtraction();
/// Resets binding so it can be reused again.