Click here to Skip to main content
Click here to Skip to main content

How to call an Oracle Stored Procedure that returns one or more REF CURSORS, using ADO from C++

By , 16 May 2006
 

Introduction

This article is exactly what the title suggests - How to call an Oracle stored procedure that returns one or more REF CURSORS, using ADO from C++. We needed that for one of our projects, and not knowing Oracle as much as we knew SQL Server, searched for online help for days. No article gave us the technique we needed to know. The closest we got was an article that describes how we can do it from VB. There are a lot of libraries out there for the Oracle/C++ combination - but none of them uses ADO objects. They use the OCI library, which was not what we needed. We needed plain and simple ADO - using the smart pointers _ConnectionPtr, _CommandPtr, etc. My colleague Rabindra Mohapatra did all the hard work, so thanks a lot to his persistent research, and hope this article will finally help others in our shoes in the future.

This article is a "technique" article. Hence no downloads, no images. Short, simple.

Example Stored Procedure

CREATE OR REPLACE
PROCEDURE GetEmpRS1 (p_recordset1 OUT SYS_REFCURSOR, 
              p_recordset2 OUT SYS_REFCURSOR,
              PARAM IN STRING) AS
BEGIN
  OPEN p_recordset1 FOR
  SELECT RET1 
    FROM MYTABLE
    WHERE LOOKUPVALUE > PARAM;

  OPEN p_recordset2 FOR
  SELECT RET2
   FROM MYTABLE
   WHERE LOOKUPVALUE >= PARAM;
END GetEmpRS1;

This stored procedure takes an input parameter for lookup, and has two OUT REF CURSORS. For simplicity of this example, both the REF CURSORS return a column (a single column). That is not a requirement in real life of course, you can as well associate a REF CURSOR with a SELECT statement like: SELECT * ....

What is a REF CURSOR

Cursors, as you know, help return recordsets/ resultsets. There may be another more technically correct definition of a cursor, but with my limited knowledge of databases, that statement sounds correct. A SQL Server stored procedure can return "a resultset" with a simple SELECT statement. It can even return multiple recordsets using multiple SELECT statements. Can Oracle do that? Single recordset, of course. Multiple recordsets - you need what is called a REF CURSOR. Treated just like a data type, your stored procedure takes REF CURSORS as OUT parameters, and you can return a full recordset in each REF CURSOR parameter back to the caller. So you can include as many REF CURSOR parameters as you want - your stored procedure will have the ability to return that many recordsets. Cool, huh?

C++ Code

Error handling is not included for brevity, in this example.

_ConnectionPtr m_pConn;
_RecordsetPtr pRecordset;
_CommandPtr pCommand; 
_ParameterPtr pParam1;

//We will use pParam1 for the sole input parameter.
//NOTE: We must not append (hence need not create)
//the REF CURSOR parameters. If your stored proc has
//normal OUT parameters that are not REF CURSORS, you need
//to create and append them too. But not the REF CURSOR ones!

//Hardcoding the value of i/p paramter in this example...

_variant_t vt;
vt.SetString("2");

m_pConn.CreateInstance (__uuidof (Connection));
pCommand.CreateInstance (__uuidof (Command));

//NOTE the "PLSQLRSet=1" part in 
//the connection string. You can either
//do that or can set the property separately using 
//pCommand->Properties->GetItem("PLSQLRSet")->Value = true;
//But beware if you are not working with ORACLE, trying to GetItem()
//a property that does not exist 
//will throw the adErrItemNotFound exception.

m_pConn->Open (
  _bstr_t ("Provider=OraOLEDB.Oracle;PLSQLRSet=1;Data Source=XXX"), 
  _bstr_t ("CP"), _bstr_t ("CP"), adModeUnknown);
pCommand->ActiveConnection = m_pConn;

pParam1 = pCommand->CreateParameter( _bstr_t ("pParam1"), 
          adSmallInt,adParamInput, sizeof(int),( VARIANT ) vt);
pCommand->Parameters->Append(pParam1);
pRecordset.CreateInstance (__uuidof (Recordset));

//NOTE: We need to specify the stored procedure name as COMMANDTEXT
//with proper ODBC escape sequence.
//If we assign COMMANDTYPE to adCmdStoredProc and COMMANDTEXT
//to stored procedure name, it will not work in this case.
//NOTE that in the escape sequence, the number '?'-s correspond to the
//number of parameters that are NOT REF CURSORS.

pCommand->CommandText = "{CALL GetEmpRS1(?)}";

//NOTE the options set for Execute. It did not work with most other
//combinations. Note that we are using a _RecordsetPtr object
//to trap the return value of Execute call. That single _RecordsetPtr
//object will contain ALL the REF CURSOR outputs as adjacent recordsets.

pRecordset = pCommand->Execute(NULL, NULL, 
             adCmdStoredProc | adCmdUnspecified );

//After this, traverse the pRecordset object to retrieve all
//the adjacent recordsets. They will be in the order of the
//REF CURSOR parameters of the stored procedure. In this example,
//there will be 2 recordsets, as there were 2 REF CURSOR OUT params.

while( pRecordset !=NULL ) )
{
    while( !pRecordset->GetadoEOF() )
    {
        //traverse through all the records of current recordset...
    }
    long lngRec = 0;
    pRecordset = pRecordset->NextRecordset((VARIANT *)lngRec);
}

//Error handling and cleanup code (like closing recordset/ connection)
//etc are not shown here.

The catches and tricks are all discussed in the code snippet above, as running comments. So, I am not repeating them in the article text. That's it! Calling stored procedures from C++ using ADO has its own pitfalls, but this article is not intended to discuss all of those. May be, in some other article some day! This article is specifically targeted to fill up the gaping void that exists in the digital world of interconnected computers out there - internet without an easy article on how to do Oracle REF CURSORS using ADO and C++. So long.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Koushik Biswas
Architect Yahoo! Inc
United States United States
Member
Koushik serves Yahoo! Video Streaming Center from its Dallas location. An Electronics Engineer from Jadavpur University, he has been a consultant throughout most of his career. Apart from spending time with work and projects, he loves cricket, Kishore Kumar, Bruce Willis, camping and fishing, Mediterranean food, sea beaches and his gas grill.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCan you get this to work in 64 bit?memberJohn Cougar24 Jun '10 - 21:30 
Hi Koushik
 
Can you or anyone else try to perform these calls to SPs using the Oracle 64bit Client drivers, and tell me if your REF CURSORs come back populated with data?
 
I'm having no luck at all. 32 bit drivers work fine.
GeneralMy vote of 1memberSam05710 Dec '08 - 0:12 
bgfn
QuestionInput errormemberhaibuo198125 Nov '08 - 15:16 
My stored procedure likes this:
create or replace
PROCEDURE get( p_rc OUT sys_refcursor, p_id NUMBER)
IS
...
END get;
I use
m_pCommand->CommandText = "{call get(?)}";
to call it;
but I get a pls-00306 Error,(input error, I'm using a Korean system, and I can not translate it well).
 
Anybody can help me?
haibuo1981@hotmail.com
 
King

Generalreturn table type on execute of stored procedure in oracle.memberpravinkgarg2 Jul '08 - 3:46 
Hi,
I want to return table as output on execute of stored procedure. could you please provide me with sample code for the same.
thanks & regards,
pv_pravin@yahoo.co.in
GeneralAnother working func + TSNless OLEDB connectionstringmemberEl-Marlor29 Nov '07 - 5:30 
_ConnectionString = "Provider=OraOLEDB.Oracle;User Id=myuser;Password=mypwd;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=myhostaddress)(PORT=myport))(CONNECT_DATA=(SID=mysid)))";
 
ADODB::_RecordsetPtr dbo::ExecuteCursor(string strProcName)
{
// Clear the old recordset pointer
_ActiveRecordset = NULL;
_CommandPtr pCommand;
_ParameterPtr pParam1;
 
try
{
HRESULT hzDB;
 
int intReturn = Connect(false);
if (intReturn == 0)
{
throw DboError("Error - Unable to connect to database\n");
}
 
hzDB = _ActiveRecordset.CreateInstance(__uuidof(ADODB::Recordset));
if (hzDB != S_OK)
{
throw DboError("Error - Unable to create recordset\n");
}
 
_CommandPtr pCmd("ADODB.Command");
 
pCmd->ActiveConnection = _ADOConnection;
pCmd->Properties->GetItem("PLSQLRSet")->Value = true;
pCmd->CommandText = strProcName.c_str();
#if 1
_ActiveRecordset->CursorType = ADODB::adOpenForwardOnly;
_ActiveRecordset->LockType = ADODB::LockTypeEnum::adLockReadOnly;
_ActiveRecordset = pCmd->Execute(NULL, NULL, adCmdStoredProc | adCmdUnspecified );
#else // this also works
bool b = false;
_ActiveRecordset->Open( (IDispatch *) pCmd, vtMissing, b ? ADODB::adOpenStatic : ADODB::adOpenForwardOnly, ADODB::adLockReadOnly, ADODB::adCmdStoredProc | ADODB::adCmdUnspecified );
#endif
 
if (_ActiveRecordset == NULL)
throw DboError("Error - Unable to execute proc\n");
 
_Unread = true;
}
catch (_com_error &e)
{
throw DboError(e);
}
catch (...)
{
throw DboError("Unknown database error");
}
 
return _ActiveRecordset;
}
QuestionREF CURSOR is always returning empty recordsetmemberSajalMaity5 Nov '07 - 17:34 
I'm using Oracle 10gR2, OraOLEDB.Oracle provider and PLSQLRSet=1.
 
I followed the same way, but i'm unable to retrieve data from the out ref cursor. It is always empty. I get the following error -
"Either BOF or EOF is True, or the current record has been deleted. Requested operation requires a current record."Confused | :confused:
 
I have two procedure. 1. initilizes one global temp table(after each commit whose data gets deleted)2. this procedure reads that temp table and returns all record as a refcursor.
 
If i execute my procedure through SQL prompt, it returns 10k records.
 
sample vb code -
Dim adoCon As New ADODB.Connection
Dim adoCmd As New ADODB.Command
Dim rsScalar as ADODB.Recordset
 
adoCon.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=XYZ;User Id=XYZ;Password=XYZ;PLSQLRSet=1"
adoCon.Mode = adModeReadWrite
adoCon.Open
 
With adoCmd
.ActiveConnection = adoCon
.CommandText = "{call SAJAL_TEST.InitializeDeltas( 'ML.MODIFICATIONNUMBER >= 5926163')}"
.Execute
End With
 
With adoCmd
.CommandText = "{call SAJAL_TEST.GetScalarDeltas}" // this proc has one out ref cursor parameter
Set rsScalar = adoCmd.Execute
End With

 
now rsScalar always emptyFrown | :( whereas through SQL I get 10k records.

 
Sajal.
AnswerRe: REF CURSOR is always returning empty recordsetmemberJeetendraN12 Nov '07 - 23:54 
I think this is what happening here.
 
When you call .CommandText = "{call SAJAL_TEST.GetScalarDeltas}" , autocommit is done after the command execution. This commit is causing deletion of records from temporary table. You can probably change the global temporary table option to presevre rows after commit.
 
jeetendra
Generalworked for me and another code example [modified]memberWinterwheat4 Jun '07 - 8:13 
This example worked for me exactly as it is typed. I am connected to an Oracle 10g database. Then I played around with some variations and did some more searching. Sometimes I just need a single value returned and I don't need a cursor. It also bothered me that the example above only showed strings being passed in.
 
So I found this article http://www.codeguru.com/forum/archive/index.php/t-183069.html[^] . The article was written by people using SQL Server, but I was able to combine the two to get this:
 
bool MyClass::ExecuteStoredProc2(int nTwpIndex)
{
//set up Command Ptr
ADODB::_CommandPtr pCmdPtr;
pCmdPtr.CreateInstance (__uuidof (ADODB::Command));
pCmdPtr->ActiveConnection = m_Cnn;
 
ADODB::_ParameterPtr param1;
ADODB::_ParameterPtr param2;
//bind parameters to command ptr
try
{
//notice that a value is not passed in for the 5th parameter here
param1 = pCmdPtr->CreateParameter( _bstr_t ("param1"),
ADODB::adInteger,ADODB::adParamOutput, sizeof(long));
 
pCmdPtr->Parameters->Append(param1);
 
param2 = pCmdPtr->CreateParameter( _bstr_t ("param2"),
ADODB::adInteger,ADODB::adParamInput, sizeof(long), nTwpIndex);
 
pCmdPtr->Parameters->Append(param2);
}
catch(_com_error &e)
{
ErrorHandler(e,m_ErrStr);
return 0;
}
 
pCmdPtr->CommandText = "{CALL ISTOWNSHIPIRREGULAR(?,?)}";
 
try
{
//execute command
pCmdPtr->Execute(NULL, NULL, ADODB::adCmdText);
}
catch(_com_error &e)
{
ErrorHandler(e,m_ErrStr);
return 0;
}
 
param1 = pCmdPtr->Parameters->GetItem("param1");
int nOutputVal = param1->GetValue();
 
return (nOutputVal != 0);
}
 
The stored procedure that this is calling is as follows:
 
CREATE OR REPLACE
PROCEDURE ISTOWNSHIPIRREGULAR
( isIrregular OUT NUMBER,
twpIndex IN NUMBER
) IS
BEGIN
if twpIndex = 0 then
isIrregular := -1;
else
select GREATEST(twp_frac_cd, twp_range_frc_cd) into isIrregular
from township
where township.twp_tin_no = twpIndex ;
end if;
END ISTOWNSHIPIRREGULAR;
 
Anyway I hope this helps someone...
 
Winterwheat
 

 

-- modified at 15:41 Monday 4th June, 2007
 
Winterwheaterwheat
GeneralThe type of the Command ismemberChristoph Herzog27 Dec '06 - 23:06 
Hi
Small change:
 
The type of the command should be adCmdText
 
If you set it like this adCmdStoredProc | adCmdUnspecified you are really using adCmdUnspecified...
 
(adCmdUnspecified is 0xFFFFFFFF, and or-ing that with anything will return 0xFFFFFFFF again)
 
But when i chose adCmdStoredProc i got Unspecified error when executing the command.
 
cheers, Christoph
GeneralRe: The type of the Command ismemberEuthebiu22 Oct '07 - 23:50 
That's right Christoph... Wink | ;)
 
Eusebiu

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 16 May 2006
Article Copyright 2006 by Koushik Biswas
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid