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); void doBind(bool clear = true, bool reset = false);
/// Binds parameters. /// 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; bool hasData() const;
/// Returns true if statement returns data. /// Returns true if statement returns data.
@@ -133,12 +143,6 @@ private:
void fillColumns(); void fillColumns();
void checkError(SQLRETURN rc, const std::string& msg=""); 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 SQLHDBC& _rConnection;
const StatementHandle _stmt; const StatementHandle _stmt;
Poco::SharedPtr<Preparation> _pPreparation; Poco::SharedPtr<Preparation> _pPreparation;
@@ -147,6 +151,7 @@ private:
bool _stepCalled; bool _stepCalled;
int _nextResponse; int _nextResponse;
ColumnPtrVec _columnPtrs; ColumnPtrVec _columnPtrs;
bool _prepared;
}; };
@@ -177,7 +182,7 @@ inline Poco::UInt32 ODBCStatementImpl::columnsReturned() const
inline bool ODBCStatementImpl::hasData() const inline bool ODBCStatementImpl::hasData() const
{ {
poco_assert_dbg (_pPreparation); 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&); void prepare(std::size_t pos, const Poco::Any&);
/// Prepares an Any. /// Prepares an Any.
std::size_t columns(bool resize = true) const; std::size_t columns() const;
/// Returns the number of columns. /// 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); Poco::Any& operator [] (std::size_t pos);
/// Returns reference to column data. /// 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 inline int Preparation::actualDataSize(std::size_t pos) const
{ {
poco_assert (pos >= 0 && pos < _pValues.size()); poco_assert (pos >= 0 && pos < _pValues.size());

View File

@@ -55,7 +55,8 @@ ODBCStatementImpl::ODBCStatementImpl(SessionImpl& rSession):
_rConnection(rSession.dbc()), _rConnection(rSession.dbc()),
_stmt(rSession.dbc()), _stmt(rSession.dbc()),
_stepCalled(false), _stepCalled(false),
_nextResponse(0) _nextResponse(0),
_prepared(false)
{ {
if (session().getFeature("autoBind")) if (session().getFeature("autoBind"))
{ {
@@ -120,15 +121,28 @@ void ODBCStatementImpl::compileImpl()
// these calls must occur before. // these calls must occur before.
fixupBinding(); doBind(false, true); fixupBinding(); doBind(false, true);
bool dataAvailable = hasData(); makeInternalExtractors();
if (dataAvailable && !extractions().size()) doPrepare();
}
void ODBCStatementImpl::makeInternalExtractors()
{
if (hasData() && !extractions().size())
{ {
fillColumns(); fillColumns();
makeExtractors(columnsReturned()); 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& extracts = extractions();
Extractions::iterator it = extracts.begin(); Extractions::iterator it = extracts.begin();
Extractions::iterator itEnd = extracts.end(); Extractions::iterator itEnd = extracts.end();
@@ -139,6 +153,7 @@ void ODBCStatementImpl::compileImpl()
pos += (*it)->numOfColumnsHandled(); pos += (*it)->numOfColumnsHandled();
delete pAP; delete pAP;
} }
_prepared = true;
} }
} }
@@ -240,6 +255,11 @@ bool ODBCStatementImpl::hasNext()
{ {
if (hasData()) if (hasData())
{ {
if (!extractions().size())
makeInternalExtractors();
if (!_prepared) doPrepare();
if (_stepCalled) if (_stepCalled)
return _stepCalled = nextRowReady(); 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 } } } // 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; SQLSMALLINT nCol = 0;
if (!Utility::isError(SQLNumResultCols(_rStmt, &nCol)) && 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 } } } // namespace Poco::Data::ODBC

View File

@@ -40,6 +40,7 @@
#include "Poco/Exception.h" #include "Poco/Exception.h"
#include "Poco/Data/Common.h" #include "Poco/Data/Common.h"
#include "Poco/Data/BLOB.h" #include "Poco/Data/BLOB.h"
#include "Poco/Data/RecordSet.h"
#include "Poco/Data/StatementImpl.h" #include "Poco/Data/StatementImpl.h"
#include "Poco/Data/ODBC/Connector.h" #include "Poco/Data/ODBC/Connector.h"
#include "Poco/Data/ODBC/Utility.h" #include "Poco/Data/ODBC/Utility.h"
@@ -998,6 +999,45 @@ void ODBCOracleTest::testStoredFunction()
assert(-1 == i); assert(-1 == i);
dropObject("FUNCTION", "storedFunction"); 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 " *_pSession << "CREATE OR REPLACE "
"FUNCTION storedFunction(inParam IN NUMBER) RETURN NUMBER IS " "FUNCTION storedFunction(inParam IN NUMBER) RETURN NUMBER IS "
" BEGIN RETURN(inParam*inParam); " " BEGIN RETURN(inParam*inParam); "
@@ -1092,6 +1132,7 @@ void ODBCOracleTest::testAsync()
_pSession->setFeature("autoBind", bindValues[i]); _pSession->setFeature("autoBind", bindValues[i]);
_pSession->setFeature("autoExtract", bindValues[i+1]); _pSession->setFeature("autoExtract", bindValues[i+1]);
_pExecutor->asynchronous(); _pExecutor->asynchronous();
i += 2; i += 2;
} }
} }

View File

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

View File

@@ -2245,7 +2245,7 @@ void SQLExecutor::asynchronous()
{ {
Session tmp = *_pSession; Session tmp = *_pSession;
int rowCount = 500; int rowCount = 2000;
std::vector<int> data(rowCount); std::vector<int> data(rowCount);
Statement stmt = (tmp << "INSERT INTO Strings VALUES(?)", use(data)); Statement stmt = (tmp << "INSERT INTO Strings VALUES(?)", use(data));
Statement::Result result = stmt.executeAsync(); Statement::Result result = stmt.executeAsync();
@@ -2256,31 +2256,37 @@ void SQLExecutor::asynchronous()
assert (stmt1.isAsync()); assert (stmt1.isAsync());
assert (stmt1.wait() == rowCount); 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(); stmt1.execute();
try { try {
stmt1.execute(); stmt1.execute();
fail ("must fail"); fail ("execute() must fail");
} catch (InvalidAccessException&) } catch (InvalidAccessException&)
{ {
stmt1.wait(); stmt1.wait();
stmt1.execute(); stmt1.execute();
stmt1.wait(); stmt1.wait();
} }
// ---
stmt = tmp << "SELECT * FROM Strings", into(data), async, now; stmt = tmp << "SELECT * FROM Strings", into(data), async, now;
assert (stmt.isAsync()); assert (stmt.isAsync());
stmt.wait(); stmt.wait();
assert (stmt.execute() == 0); 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 { try {
result = stmt.executeAsync(); result = stmt.executeAsync();
fail ("must fail"); fail ("executeAsync() must fail");
} catch (InvalidAccessException&) } catch (InvalidAccessException&)
{ {
assert (stmt.isAsync()); assert (stmt.isAsync());
stmt.wait(); stmt.wait();
result = stmt.executeAsync(); result = stmt.executeAsync();
} }
// ---
assert (stmt.wait() == rowCount); assert (stmt.wait() == rowCount);
assert (result.data() == rowCount); assert (result.data() == rowCount);

View File

@@ -119,7 +119,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -249,7 +249,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -306,7 +306,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -363,7 +363,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -420,7 +420,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -477,7 +477,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); throw BindingException("It is illegal to bind to an empty data collection");
} }
@@ -534,7 +534,7 @@ public:
_end(val.end()) _end(val.end())
/// Creates the Binding. /// Creates the Binding.
{ {
if (numOfRowsHandled() == 0) if (PD_IN == direction && numOfRowsHandled() == 0)
throw BindingException("It is illegal to bind to an empty data collection"); 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. /// Convenience function for a more compact Binding creation.
{ {
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN); 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. /// Convenience function for a more compact Binding creation.
{ {
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN); 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. /// Convenience function for a more compact Binding creation.
{ {
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_OUT); 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. /// Convenience function for a more compact Binding creation.
{ {
Binding<T>* pB = new Binding<T>(t, name, AbstractBinding::PD_IN_OUT); 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) template <typename T>
/// Convenience function to allow for a more compact creation of a default extraction object Extraction<T>* into(T& t)
/// Convenience function to allow for a more compact creation of an extraction object
{ {
return new Extraction<T>(t); 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 /// Convenience function to allow for a more compact creation of an extraction object with the given default
{ {
return new Extraction<T>(t, def); return new Extraction<T>(t, def);

View File

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