I am trying to execute a simple stored procedure using Oracle OCI. The stored procedure takes a string as an input and copies it to the output parameter. Below is the oracle statement that I am executing:
DECLARE OutParam VARCHAR2(50);
BEGIN
my_stored_procedure('Test String', OutParam);
END;
And I wrote the OCI code as follows:
/* Bind a placeholder for the output parameter */
if (status = OCIBindByPos(stmthp, &bnd6p, errhp, 1,
(dvoid *)result, 1024, SQLT_STR,
(dvoid *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT))
{
checkerr(errhp, status);
cleanup();
return OCI_ERROR;
}
/* execute and fetch */
if (status = OCIStmtExecute(svchp, stmthp, errhp, (ub4)1, (ub4)0,
(CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT))
{
if (status != OCI_NO_DATA)
{
checkerr(errhp, status);
cleanup();
return OCI_ERROR;
}
}
With Oracle 11g and older versions, this worked fine and I was able to get the output parameter stored in the 'result' variable I used in the OCIBindByPos call.
However, with Oracle 12 and above this does not work for me, I am getting the following error:
OCI_ERROR - ORA-03137: malformed TTC packet from client rejected: [kpoal8Check-5] [32768]
Does anyone know why this does not work with Oracle versions 12 and above? I tested this with Oracle 12 and Oracle 19 and got the same error.
Related
I have a stored procedure with the following signature and local variables:
PROCEDURE contract_boq_import(i_project_id IN RAW,
i_boq_id IN RAW,
i_master_list_version IN NUMBER,
i_force_update_if_exists IN BOOLEAN,
i_user_id IN NUMBER,
o_boq_rev_id OUT RAW) AS
v_contract_id RAW(16);
v_contract_no VARCHAR2(100);
v_series_rev_id_count NUMBER(1);
v_project_id_count NUMBER(5);
v_now DATE;
v_boq_import_rev_id RAW(16);
v_master_project_id RAW(16);
v_prj_duplicate_items VARCHAR2(1000) := '';
I set up an output parameter using one of our DAL utilities:
var revParam = new byte[16];
dataHandler.CreateParameterRaw("o_boq_rev_id", revParam).Direction = ParameterDirection.Output;
Where CreateParameterRaw is declared as:
public DbParameter CreateParameterRaw(string name, object value)
{
OracleParameter oracleParameter = new OracleParameter();
oracleParameter.ParameterName = name;
oracleParameter.OracleDbType = OracleDbType.Raw;
oracleParameter.Value = value;
this.Parameters.Add((DbParameter) oracleParameter);
return (DbParameter) oracleParameter;
}
Then when I execute the procedure with ExecuteNonQuery I get the following error:
Oracle.ManagedDataAccess.Client.OracleException
HResult=0x80004005
Message=ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at "ITIS_PRCDRS.PA_PRJ_IMP", line 1235
The exception is thrown on line 1235:
o_boq_rev_id := v_boq_import_rev_id;
As you can see from the procedure declaration above, v_boq_import_rev_id has type RAW(16) and o_boq_rev_id has type OUT RAW, so why should the assignment on line 1235 fail? What am I doing wrong?
PS: The proc executes fine when I call it in plain PL/SQL.
In OracleParameter the default size is 0 for the parameters that may have size values. (Official reference here.)
That is why you need to modify your method which generates the raw values. Below you can find the modified method:
public DbParameter CreateParameterRaw(string name, object value, int parameterSize)
{
OracleParameter oracleParameter = new OracleParameter();
oracleParameter.ParameterName = name;
oracleParameter.OracleDbType = OracleDbType.Raw;
oracleParameter.Value = value;
oracleParameter.Size = parameterSize; /* THIS IS THE ADDED PARAMETER */
this.Parameters.Add((DbParameter) oracleParameter);
return (DbParameter) oracleParameter;
}
And as a result you can pass the size while calling CreateParameterRaw as you did in your existing code:
var revParam = new byte[16];
/* CHECK THE 16 value in the parameters that are sent to CreateParameterRaw */
dataHandler.CreateParameterRaw("o_boq_rev_id", revParam, 16).Direction = ParameterDirection.Output;
Additional suggestion: In order to keep apples with apples, I would suggest you can take Direction parameter also into the CreateParameterRawmethod. By this way CreateParameterRawbecomes the whole responsible about generating the parameters.
Credits:
Official page: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/odpnt/ParameterCtor5.html#GUID-04BE7E69-A80A-4D28-979A-CDC2516C0F93
A blog that has similar problem: http://devsilos.blogspot.com/2013/01/ora-06502-with-out-parameter-called.html?m=1
Size usage from Microsoft: https://learn.microsoft.com/en-us/dotnet/api/system.data.oracleclient.oracleparameter.size?view=netframework-4.8
This is an interesting problem with a weird solution.
Actually while using the RAW in output parameter you MUST provide some buffer space for it when adding this parameter.
Can you please provide some buffer space for this variable and try something like the following:
byte[] RAWPlaceholder = new byte[16];
cmd.AddParameter(new OracleParameter("o_boq_rev_id",
OracleDbType.Raw,
16,
RAWPlaceholder,
ParameterDirection.Output);
Please share the result of the aforementioned exercise.
Thanks
We are trying to select an array/varray type with embedded SQL.
Type/Table:
create type int55 is array(4) of integer;
create table rtable (a int, b int55);
Code for SQL Precompiler in C++ with Oracle 11g Client:
BOOL static_TestIt(void) {
EXEC SQL BEGIN DECLARE SECTION;
int a;
long b[4];
short i[4];
EXEC SQL END DECLARE SECTION;
EXEC SQL DECLARE cdtest_cursor CURSOR FOR select a,b from rtable;
EXEC SQL open cdtest_cursor;
EXEC SQL FETCH cdtest_cursor INTO :a, :b:i;
EXEC SQL close cdtest_cursor;
return TRUE;
}
The precompiler shows warning:
PCC-W-02344 invalid size of host array
During runtime i get error: ORA-00932
Does anybody know a solution to this problem?
You need to do a few more steps, based on the documentation on collection handling in Pro*C.
First, create a file called int55_in.typ containing the name of your collection type:
case=lower
type int55
Then run the object type translator tool:
$ORACLE_HOME/bin/ott intype=int55_in.typ outtype=int55_out.typ hfile=int55.h user=usr/pwd code=c
which will generate two files. In your .pc file add:
#include "int55.h"
and your code needs to use that declared collection type:
EXEC SQL BEGIN DECLARE SECTION;
int a;
int55 *b; /* collection type */
int c; /* single instance from collection */
OCIInd i; /* indicator */
EXEC SQL END DECLARE SECTION;
EXEC SQL ALLOCATE :b;
EXEC SQL DECLARE cdtest_cursor CURSOR FOR select a, b from rtable;
EXEC SQL open cdtest_cursor;
EXEC SQL FETCH cdtest_cursor INTO :a, :b:i;
Then you can use COLLECTION GET to get the individual elements; as it's a varray you can loop over the four entries, e.g. as a simple demo:
int j;
for (j = 0; j < 4; j++)
{
EXEC SQL COLLECTION GET :b INTO :c;
printf("%d %d %d\n", j, a, c);
}
EXEC SQL close cdtest_cursor;
When you compile you need tomake sure the location for oci.h (included from the generated int55.h) if it isn't already referred to; and you need to specify the generated type file:
$ORACLE_HOME/bin/proc include=$ORACLE_HOME/rdbms/public intyp=int55_out.typ ...
With a single row in your table:
insert into rtable values (1, int55(2, 3, 4, 5));
running that then gets:
0 1 2
1 1 3
2 1 4
3 1 5
Obviously you should pick your own file names, I've just used your type name as an example to hopefully make it clearer. And if you have to handle mutlipel types they can all go in a single _in.typ to generate a single _out.typand.h` file.
I'm using host arrays to insert and update data as shown in Oracle manual.
Here is a bit of code:
in my_func(void* ctx, int run_id, DB_DATA** db_data)
{
int i = 0;
EXEC SQL BEGIN DECLARE SECTION;
int ora_id_run[BULK_SIZE];
int ora_term_id[BULK_SIZE];
int ora_seq_num[BULK_SIZE];
int ora_resp[BULK_SIZE];
char ora_timestamp[BULK_SIZE][27];
EXEC SQL END DECLARE SECTION;
for (i = 0; i < BULK_SIZE; i++)
{
ora_id_run[i] = run_id;
ora_term_id[i] = db_data[i]->term_id;
ora_seq_num[i] = db_data[i]->seq_num;
ora_resp[i] = db_data[i]->resp;
memset(ora_timestamp[i], '\0', sizeof(ora_timestamp[i]));
strncpy(ora_timestamp[i], db_data[i]->timestamp, strlen(db_data[i]->timestamp));
}
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL CONTEXT USE :ctx;
EXEC SQL UPDATE T_TABLE
SET RESP = :ora_resp,
ELAPSED = TO_TIMESTAMP(:ora_timestamp, 'DD-MM-RR HH24:MI:SS.FF') - DT
WHERE ID_RUN = :ora_id_run
AND ID_TERM = :ora_term_id
AND SEQ_NUM = :ora_seq_num;
if (sqlca.sqlcode != 0)
{
ORA_ERROR;
}
EXEC SQL COMMIT WORK;
return sqlca.sqlcode;
}
BULK_SIZE is about 200, 300, 500.
And sometimes the number of rows updated is less than BULK_SIZE (I've checked it with sqlerrd[2]).
Why?
Solved!
Sometimes in updating bulk I got rows that were not inserted yet.
I have just started windows application programming 2 days ago.. I am using VC++ and it is having some issues which I am unable to figure out.
When i send proper arguments to the function below, it displays everything properly but it doesn't save the insert query to the DB.
Here is the statement:
INSERT INTO BOOKS(ID,USERNAME,ISSUER,RETURNED) VALUES(1,'xyz','xyz',0);
Here is the table creation statement of sqlite:
sql = "CREATE TABLE IF NOT EXISTS BOOKS(" \
"ID INT PRIMARY KEY NOT NULL," \
"BOOKNAME TEXT NOT NULL," \
"ISSUER TEXT NOT NULL," \
"RETURNED INT);";
Here is the function:
using namespace std;
int WriteToDB(sqlite3 *,const char [],wchar_t *,HWND *);
int WriteToDB(sqlite3 *_db,const char _query[],wchar_t *ermsg,HWND *hw)
{
sqlite3_stmt *insertStmt;
MessageBox(*hw,L"Creating insert statement",L"INFO",MB_OK);
if(sqlite3_prepare(_db, _query, 400,&insertStmt, NULL))
MessageBox(*hw,L"Prepared!",L"Info",MB_OK);
MessageBox(*hw,L"Stepping into insert statement",L"INFO",MB_OK);
MessageBox(*hw,(LPWSTR)_query,L"Input",MB_OK);
if (sqlite3_step(insertStmt) != SQLITE_DONE)
{
wcscpy(ermsg,CharToWChar("Didn't Insert Item! Please check statement or DataBase!!"));
return 0;
}
sqlite3_finalize(insertStmt);
return 1;
}
EDIT: Here is the new error i got:
In SQLite,
near "I": Syntax Error
This Statement:
INSERT INTO BOOKS(ID,BOOKNAME,ISSUER,RETURNED) VALUES(1,'xyz','xyz',0);
Mistake here:
if(sqlite3_prepare(_db, _query, 400,&insertStmt, NULL)) //...
Should be:
if(sqlite3_prepare(_db, _query, 400,&insertStmt, NULL)==SQLITE_OK) //...
Because #define SQLITE_OK 0 /* Successful result */, your previous condition was opposite to pretended one.
Advice: instead of merging data in SQL statement, consider using parameters and sqlite3_bind_* interfaces.
EDIT:
Using sqlite3_prepare_v2 is recommended over sqlite3_prepare. Using such, you can easily have information on error cause with sqlite3_errmsg:
if(sqlite3_prepare(_db, _query, 400,&insertStmt, NULL) == SQLITE_OK) {
MessageBox(*hw,L"Prepared!",L"Info",MB_OK);
} else {
MessageBox(*hw, sqlite3_errmsg16(_db), L"Error", MB_OK);
}
I'm using OCILIB.
Statements of the form CREATE OR REPLACE PROCEDURE AA1 AS BEGIN XXX; END; are always successful (ie, no error or warning is reported). How can I check whether the statement was compiled correctly?
There should be a function to get that information (I hope).
Here is a quick & dirty example that shows the issue:
#include <stdio.h>
#include <ocilib.h>
void print_error() {
printf("Error:");
OCI_Error *err = OCI_GetLastError();
if (err) printf(" %d: %s\n", OCI_ErrorGetOCICode(err), OCI_ErrorGetString(err));
else printf(" no error.");
printf("\n");
}
int main (int argc, char **argv) {
OCI_Connection *cn;
OCI_Statement *st;
OCI_EnableWarnings(TRUE);
if (!OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT | OCI_ENV_CONTEXT)) {
puts("Failed to initialize OCI");
return 2;
}
cn = OCI_ConnectionCreate("XE", "<your user>", "<your pwd>", OCI_SESSION_DEFAULT);
print_error();
OCI_SetAutoCommit(cn, TRUE); print_error();
st = OCI_StatementCreate(cn); print_error();
OCI_ExecuteStmt(st, "create or replace procedure aaa as begin garbage end;");
print_error();
OCI_Cleanup();
return 0;
}
Output:
$ ./ocitest
Error: no error.
Error: no error.
Error: no error.
Error: no error.
There was an issue in OCILIB that was not reporting SQL Warnings properly. This is fixed and OCILIB repository is up to date with the fix :)
You can check the status in the user_objects view:
select status
from user_objects
where object_type = 'PROCEDURE'
and object_name = 'AA1';
If that is not VALID, you can get the actual errors by querying the user_errors view:
select * -- or just line, position and text
from user_errors
where type = 'PROCEDURE'
and name = 'AA1'
order by sequence;
I'm surprised you don't get an exception, but this isn't a stack I use...