mirror of
https://github.com/pocoproject/poco.git
synced 2025-01-30 22:31:29 +01:00
support for stored procedures returning recordset
This commit is contained in:
parent
9ea88d25dd
commit
424b717920
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user