what am I missing?
George, June 25, 2002 - 5:08 pm UTC
Tom,
I am receiving the error "Lost RPC connection", and trying
to copy this example as closely as possible to get results... I thought perhaps it was something to do with
my C code, but when I add a main() that calls my sh()
function, the resulting executable works fine.. so I have
focussed on passing parameters.. I have had no luck :-(
Ultimately, I'd like to execute something... for now I'd
be happy returning a number without raising an error.
Any help you can lend is greatly appreciated:
my SQL:
CREATE OR REPLACE LIBRARY spi_unix_extproc is
'/tmp_mnt/home/gliblick/oscommands/spi_unix_extproc.so' ;
/
CREATE OR REPLACE PACKAGE spi_unix_util IS
FUNCTION oscommand( ps_cmd in varchar2 )
RETURN NUMBER ;
end spi_unix_util;
/
CREATE OR REPLACE PACKAGE BODY spi_unix_util IS
FUNCTION oscommand( ps_cmd in varchar2 ) RETURN NUMBER
AS
LANGUAGE C
NAME "sh"
LIBRARY spi_unix_extproc
WITH CONTEXT
PARAMETERS ( CONTEXT,
ps_cmd STRING,
ps_cmd LENGTH SB4,
RETURN INDICATOR int );
END spi_unix_util;
/
my C code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <oci.h>
#ifdef WIN_NT
_declspec (dllexport)
#endif
int sh( OCIExtProcContext *ctx,
char *cmd,
short cmd_i,
short *return_indicator )
{
int num = 199;
/*
num = system(cmd);
*/
return( num );
}
and when I test it from SQL*Plus:
SQL> @test_c
BEGIN :x := spi_unix_util.oscommand( 'touch /tmp_mount/home/gliblick/oscommand/z' ); END;
*
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "CALSDBA.SPI_UNIX_UTIL", line 0
ORA-06512: at line 1
I have also had our DBA compile and run this test as
the Oracle UNIX user, in the $ORACLE_HOME directory.. but
no luck. I am now at a loss for how to proceed...
June 25, 2002 - 7:07 pm UTC
That function maps to a C routine with a prototype like this:
Maps to a C function with the following prototype:
OCINumber *sh
(
OCIExtProcContext *, /* 1 : With-Context */
char *, /* 2 : PS_CMD */
sb4 , /* 3 : PS_CMD (Length) */
int * /* 4 : RETURN (Indicator) */
);
You have short, where you should have sb4 (4 bytes, not 2 bytes). Also, you have short * for return indicator, that is wrong as well.
You are not setting the return indicator at all -- thats wrong as well.
Lastly -- you are saying the function returns a NUMBER. Well, that would map to a C Datatype of "OCINumber *" -- that is where the grievous error is in this case! You are returning the number 199 which, when we dereference as a pointer, causes an immediate seg-fault!
If you have my book -- i spend about 70 pages on this topic, going over everything you need to know (if you are interested)
Your quick fix (after fixing the INPUT parameters!) is to return the plsql type BINARY_INTEGER. That is mapped to the C int type which is what you are returning....
bah humbug ;-)
George, June 26, 2002 - 5:06 pm UTC
with this advice I have made something work...
and bought your book (at full price, grumble, grumble)
Both you and your book have been very helpful and I will
soon understand enough to complete my requirements :-)
What happenned to the good old days when I just executed
a "host" command - you don't have to answer that ?!
thanks and good tidings, George :-)
ORA-30110
A reader, August 14, 2003 - 11:07 am UTC
Hi Tom,
We are on AIX 5.1 with Oracle 8.1.7 and are getting the following
error in a
External Procedure:
ORA-30110: syntax error at '%.*s'
Please set up your test case as:
CREATE OR REPLACE LIBRARY test AS '/usr/dk/test/test.so';
/
CREATE OR REPLACE FUNCTION GetDummy
RETURN VARCHAR2
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT,RETURN INDICATOR short,RETURN LENGTH int,RETURN
STRING
);
/
The parameter file test.ini contains:
debugf = true
File test.c is:
#include <stdio.h>
#include <string.h>
#include <oci.h>
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{
char *dummy = OCIExtProcAllocCallMemory(ctx,50);
ub1 false = 0, debugf_flag;
text *errbuf = OCIExtProcAllocCallMemory(ctx,512);
sb4 errcode = 0;
if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,"Error",0);
return NULL;
}
if ( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN, 0, &false, NULL, NULL ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractFromFile( envhp, errhp, 0, (text *)
"/tmp/test.ini" ) !=
OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractToBool( envhp, errhp, (text *) "debugf",
0, &debugf_flag
) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20002,"here1",0);
return NULL;
}
if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf,
(ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
return NULL;
}
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}
We compile using $ make -f $ORACLE_HOME/rdbms/demo/demo_rdbms.mk
extproc_with_context SHARED_LIBNAME=test.so OBJS="test.o"
Please help
Regards,
August 14, 2003 - 11:40 am UTC
how are you calling it?
i created a /test/temp.ini with:
[tkyte@localhost test2]$ cat /tmp/test.ini
debugf = true
[tkyte@localhost test2]$ od -c /tmp/test.ini
0000000 d e b u g f = t r u e \n \n
0000017
and then:
ops$tkyte@ORA920LAP> select getdummy from dual;
GETDUMMY
---------------------------------------------------
Success.
worked first time...
Thanks
A reader, August 14, 2003 - 1:42 pm UTC
Dear Tom,
I provided this test case to Oracle Metalink, they opened the bug with the number:3056017 initially. The bug diagnostics concentrated the test case to the memory allocation at the end, and bypassed the Cartridge Services routines. They have later on closed the bug, even when I've requested them to have a look at the Cartridge Sevices routines.
They now doubt if the above test case is valid, and are advising me to test the above in SYS or SYSTEM account.
The whole story is in TAR# 3163706.996 and TAR#2735554.1 (extension of the previous TAR).
I've waited endlessly for you in your forum for confirmation on this test case, and am extremely indebted on your prompt response on this.
Oracle Metalink on the other hand, promised us the resolution by 15th Aug (1 month after it was reported) after diagnosing this as a Bug, then denied it as a Bug, it is now not reporducible at Metalink and have asked to test it with SYS/SYSTEM account.
Extremely sorry for boring you with all this.
I've tried the od -c command it looks exactly the same as you have shown me. I have also tried to know the return code from OCIExtractFromFile it is OCI_ERROR, and error is ORA-30110, which says it could not find the occurance of debugf in the test.ini
Is there something I can do to find the solution?
Extremely thankful for your help.
Best regards,
August 14, 2003 - 2:08 pm UTC
well, they closed the bug because of a bug in the test case:
char *getDummy( OCIExtProcContext *ctx, short *return_i, int *return_l )
{
int *myCtx;
char dummy[50];
....
}
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}
you are returning a pointer to a stack variable!!!
that is why that bug was "not a bug"
but -- can you put a couple of
fopen( 'a')
fprintf ( 'im on line %d', __LINENO__ )
fclose
debug lines in there to see which call exactly is failing?
Thanks
A reader, August 14, 2003 - 2:34 pm UTC
Dear Tom,
Thanks very much for your response.
The code now looks like this:
#include <stdio.h>
#include <string.h>
#include <oci.h>
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
FILE *fp;
char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{
char *dummy = OCIExtProcAllocCallMemory(ctx,50);
ub1 false = 0, debugf_flag;
text *errbuf = OCIExtProcAllocCallMemory(ctx,512);
sb4 errcode = 0;
if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
return NULL;
}
if ( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,
0, &false, NULL, NULL ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractFromFile( envhp, errhp, 0,
(text *) "/tmp/test.ini" )
== OCI_ERROR )
{
fp = fopen("/tmp/a","w");
fprintf(fp,"im on line %d",__LINE__);
fclose(fp);
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
if ( OCIExtractToBool( envhp, errhp, (text *) "debugf",
0,
&debugf_flag ) == OCI_NO_DATA )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20222,"here1",0);
return NULL;
}
if ( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS )
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,20003,"here2",0);
return NULL;
}
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}
The error in /tmp/a is
im on line 56
The line 56 is fprintf line (there is only one in the code).
Thanks very much for your time and effort.
Kind regards,
August 14, 2003 - 2:38 pm UTC
would you mind fopening /tmp/test.ini , freading the first 1k of it and fwriting what you get into the debug? just to see what we see?
i'll have to try later on 817 (only 9ir2 right now) but it'll be solaris
Thanks
A reader, August 14, 2003 - 2:57 pm UTC
Thanks very much Tom for your time and effort. I do not have the words for appreciation here.
I tried the following code:
#include <stdio.h>
FILE *fi,*fo;
char buffer[1024];
main()
{
fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),1024,fo);
fclose(fi);
fclose(fo);
}
$ cc -o dk dk.c
$ dk
$ ll /tmp/debuf
-rw-rw-r-- 1 eve eve 1024 Aug 14 19:47 /tmp/debuf
$ cat /tmp/debuf
debugf = true
$ cat
/tmp/test.ini
debugf = true
$ ll /tmp/test.ini
-rwxrwxrwx 1 eve eve 15 Aug 14 18:19 /tmp/test.ini
$
I feel (based on the response of the first Support Analyst) that the problem is with the AIX machines, it is not with the solaris.
Thanks once again.
Kind regards
#include <stdio.h>
FILE *fi,*fo;
char buffer[1024];
main()
{
fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),1024,fo);
fclose(fi);
fclose(fo);
}
August 14, 2003 - 3:00 pm UTC
you need to
n = fread()
fwrite( ..., n, ... )
and do it in the EXTPROC -- i wanna see what it sees
i'll try later.
Thanks
A reader, August 15, 2003 - 6:30 am UTC
Hi Tom,
Sorry I misunderstood.
#include <stdio.h>
#include <string.h>
#include <oci.h>
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
char *getDummy( OCIExtProcContext *ctx, short *return_i, int
*return_l )
{
FILE *fi,*fo;
char buffer[1024];
int n;
char *dummy;
dummy=OCIExtProcAllocCallMemory(ctx,50);
if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
}
fi = fopen("/tmp/test.ini","r");
fo = fopen("/tmp/debuf","w");
n = fread(buffer,sizeof(char),1024,fi);
fwrite(buffer,sizeof(char),n,fo);
fclose(fi);
fclose(fo);
strcpy(dummy,"Success.");
*return_l = strlen(dummy);
*return_i = OCI_IND_NOTNULL;
return dummy;
}
$ ll /tmp/debuf
-rw-r--r-- 1 oracle dba 15 Aug 15 08:59 /tmp/debuf
$ cat /tmp/debuf
debugf = true
$ cat /tmp/test.ini
debugf = true
$ ll /tmp/test.ini
-rwxrwxrwx 1 eve eve 15 Aug 14 18:19 /tmp/test.ini
$ od -c /tmp/test.ini
0000000 d e b u g f = t r u e \n \n
0000017
$ od -c /tmp/debuf
0000000 d e b u g f = t r u e \n \n
0000017
$
August 15, 2003 - 10:04 am UTC
Ok, lets try this piece of code. this is a generic template I use for all of my external procedures (if you have my book expert one on one Oracle -- this is from there)
include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <oci.h>
#ifdef WIN_NT
#define INI_FILE_NAME "c:\\temp\\extproc.ini"
#else
#define INI_FILE_NAME "/tmp/extproc.ini"
#endif
#define strupr(a) {char * cp; for(cp=a;*cp;*cp=toupper(*cp), cp++);}
/*
* This is our "context". It holds what normally might be global variables
* in a typical program. We cannot (should not) use global variables in an
* external procedure. Also, since static data will be reinitialized
* between calls -- globals would not work correct anyway. We will use the
* OCI context management API calls to get and set a global context for
* our extproc. You would add any state variables you needed to preserve
* from call to call in this structure below.
*
* the debugf_* variables are initialized from a parameter file. In our
* init() routine, we will read the parameter file and set these values.
* you may add your own parameters to this structure as well and set them
* in init() as I do.
*
* curr_lineno and curr_filename are set by the debugf macro below so when
* we create a trace file, I can tell you what line number and source code
* file a message was generated from
*
*/
typedef struct myCtx
{
OCIExtProcContext * ctx; /* Context passed to all external procs */
OCIEnv * envhp; /* OCI environment handle */
OCISvcCtx * svchp; /* OCI Service handle */
OCIError * errhp; /* OCI Error handle */
int curr_lineno;
char * curr_filename;
ub1 debugf_flag;
char debugf_path[255];
char debugf_filename[50];
/* add your own state variables here... */
}
myCtxStruct;
/*
* Here is the implementation of our "debugf" routine. It is used to
* instrument the code and aid in debugging runtime issues.
*
* It works much like the C printf function (well, exactly like it)
* in that you send a C format and then a varying number of arguments
* to be formatted and printed. We add to it the Year, Month, Day,
* Hour, Minute, Second in GMT to each trace record as well as the name
* of the C source code file and the line number.
*
* Note that in order to be as OS independent as possible, we use the
* OCI API's to write to files. The File API's are expected to have
* been initialized in the init() routine alread and will be closed out
* in the term() routine.
*/
void _debugf( myCtxStruct * myCtx, char * fmt, ... )
{
va_list ap;
OCIFileObject * fp;
time_t theTime = time(NULL);
char msg[8192];
ub4 bytes;
if ( OCIFileOpen( myCtx->envhp, myCtx->errhp, &fp,
myCtx->debugf_filename,
myCtx->debugf_path,
OCI_FILE_WRITE_ONLY, OCI_FILE_APPEND|OCI_FILE_CREATE,
OCI_FILE_TEXT ) != OCI_SUCCESS ) return;
strftime( msg, sizeof(msg),
"%y%m%d %H%M%S GMT ", gmtime(&theTime) );
OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );
va_start(ap,fmt);
vsprintf( msg, fmt, ap );
va_end(ap);
strcat( msg,"\n");
OCIFileWrite( myCtx->envhp, myCtx->errhp, fp, msg, strlen(msg), &bytes );
OCIFileClose( myCtx->envhp, myCtx->errhp, fp );
}
/*
* this macro is a more convienent way to use debugf. Instead of have
* to pass the __LINE__, __FILE__ each time we call -- we just code:
*
* debugf( myCtx, "This is some format %s", some_string );
*
* and this macro will set them in our context and then call _debugf for us.
*
*/
void _debugf( myCtxStruct * myCtx, char * fmt, ... );
#define debugf \
if ((myCtx!=NULL) && (myCtx->debugf_flag)) \
myCtx->curr_lineno = __LINE__, \
myCtx->curr_filename = __FILE__, \
_debugf
/*
* This routine works like the PLSQL builtin by the same name. It
* sets an application defined error message and error code. It also
* works like the C printf routine in that you pass to it a C format
* string and then a varying number of arguments
*
* The resulting string is limited to 8k in size. Note the call
* to debugf. Debugf is the macro above, we will use it throughout
* the code to 'instrument' it.
*/
static int raise_application_error( myCtxStruct * myCtx,
int errCode,
char * errMsg, ...)
{
char msg[8192];
va_list ap;
va_start(ap,errMsg);
vsprintf( msg, errMsg, ap );
va_end(ap);
debugf( myCtx, "raise application error( %d, %s )", errCode, msg );
if ( OCIExtProcRaiseExcpWithMsg(myCtx->ctx,errCode,msg,0) ==
OCIEXTPROC_ERROR )
{
debugf( myCtx, "Unable to raise exception" );
}
return -1;
}
/*
* This is a convienence routine to allocate storage for an error message
* and return it. Note that the type of storage we allocate is CALL based,
* hence, when we return from the extproc, OCI will automatically free it
* for us.
*
* This routine removes the trailing newline from the error message as well
*/
static char * lastOciError( myCtxStruct * myCtx )
{
sb4 errcode;
char * errbuf = (char*)OCIExtProcAllocCallMemory( myCtx->ctx, 256 );
strcpy( errbuf, "unable to retrieve message\n" );
OCIErrorGet( myCtx->errhp, 1, NULL, &errcode, errbuf,
255, OCI_HTYPE_ERROR );
errbuf[strlen(errbuf)-1] = 0;
return errbuf;
}
/*
* Here is the init routine. This routine must be called very early
* in the execution of any external procedure using this template. It
* will
* o retrieve the OCI handles and set them in the context for us
* o retrieve our CONTEXT, if the context has not yet been allocated,
* it will allocate storage for our context and save it. The memory
* is allocated to last as long as our process lasts.
* o it will read and retrieve the values from the parameter file if
* they have not been. You can add more parameters of your own by:
* - adding elements the the myCtxStruct above
* - increment the counter sent into ExtractSetNumKeys appropriately
* - adding a call to ExtractSetKey
* - adding a call to ExtractTo* (string, bool, int, etc)
* o it will initialize the FILE apis via OCIFileInit(). This is crucial
* for the successful operation of debugf above.
*
* You may add other "init" type of calls. for example, if you choose to
* use the String Formatting Interface (similar to vsprintf() but in OCI)
* You could add a call to OCIFormatInit() here. You would add the
* corresponding OCIFormatTerm() call to term() below.
*
*/
static myCtxStruct * init( OCIExtProcContext * ctx )
{
ub1 false = 0;
myCtxStruct *myCtx = NULL;
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
ub4 key = 1;
if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,
"failed to get OCI Connection",0);
return NULL;
}
if ( OCIContextGetValue( envhp, errhp, (ub1*)&key, sizeof(key),
(dvoid**)&myCtx ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,"failed to get OCI Context",0);
return NULL;
}
if ( myCtx == NULL )
{
if ( OCIMemoryAlloc( envhp, errhp, (dvoid**)&myCtx,
OCI_DURATION_PROCESS,
sizeof(myCtxStruct),
OCI_MEMORY_CLEARED ) != OCI_SUCCESS )
{
OCIExtProcRaiseExcpWithMsg(ctx,20000,
"failed to get OCI Memory",0);
return NULL;
}
myCtx->ctx = ctx;
myCtx->envhp = envhp;
myCtx->svchp = svchp;
myCtx->errhp = errhp;
if ( OCIContextSetValue( envhp, errhp,
OCI_DURATION_SESSION, (ub1*)&key,
sizeof(key), myCtx ) != OCI_SUCCESS )
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}
if (( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS ) ||
( OCIExtractSetNumKeys( envhp, errhp, 3 ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,
0, &false, NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf_filename",
OCI_EXTRACT_TYPE_STRING,
0, "extproc.log",
NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, "debugf_path",
OCI_EXTRACT_TYPE_STRING,
0, "", NULL, NULL ) != OCI_SUCCESS ) ||
( OCIExtractFromFile( envhp, errhp, 0,
INI_FILE_NAME ) != OCI_SUCCESS ) ||
( OCIExtractToBool( envhp, errhp, "debugf", 0,
&myCtx->debugf_flag ) != OCI_SUCCESS ) ||
( OCIExtractToStr( envhp, errhp, "debugf_filename", 0,
myCtx->debugf_filename,
sizeof(myCtx->debugf_filename ) )
!= OCI_SUCCESS ) ||
( OCIExtractToStr( envhp, errhp, "debugf_path",
0, myCtx->debugf_path,
sizeof(myCtx->debugf_path ) )
!= OCI_SUCCESS ) ||
( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}
}
else
{
myCtx->ctx = ctx;
myCtx->envhp = envhp;
myCtx->svchp = svchp;
myCtx->errhp = errhp;
}
if ( OCIFileInit( myCtx->envhp, myCtx->errhp ) != OCI_SUCCESS )
{
raise_application_error(myCtx, 20000, "%s", lastOciError(myCtx));
return NULL;
}
return myCtx;
}
/*
* This must be called after any successful call to init() above. It
* should be the last thing you call in your routine before returning
* from C to SQL
*/
static void term( myCtxStruct * myCtx )
{
OCIFileTerm( myCtx->envhp, myCtx->errhp );
}
/*
* everthing above this is BOILERPLATE!!!
*/
/*
* With the string (the varchar) we'll use 2 parameters -- the
* indicator variable and the LENGTH field. This time, much like an
* OUT parameter, we set the length field to let the caller know how
* long the returned string is.
*
* Many of the same considerations apply for returning strings as above
* -- we'll allocate storage, set the indicator, supply the value and
* return it...
*/
#ifdef WIN_NT
_declspec (dllexport)
#endif
char * return_string
( OCIExtProcContext * ctx,
short * return_i,
int * return_l )
{
char * data_we_want_to_return = "Hello World!";
char * return_value;
myCtxStruct*myCtx;
if ( (myCtx = init( ctx )) == NULL ) return NULL;
debugf( myCtx, "Enter return String" );
return_value = (char *)OCIExtProcAllocCallMemory(ctx,
strlen(data_we_want_to_return)+1 );
if( return_value == NULL )
{
raise_application_error( myCtx, -20001, "%s", "no memory" );
}
else
{
*return_i = OCI_IND_NULL;
strcpy( return_value, data_we_want_to_return );
*return_l = strlen(return_value);
*return_i = OCI_IND_NOTNULL;
}
term(myCtx);
return return_value;
}
The Makefile I use is:
MAKEFILE= $(ORACLE_HOME)/rdbms/demo/demo_rdbms.mk
INCLUDE= -I$(ORACLE_HOME)/rdbms/demo \
-I$(ORACLE_HOME)/rdbms/public \
-I$(ORACLE_HOME)/plsql/public \
-I$(ORACLE_HOME)/network/public
TGTDLL= extproc.so
OBJS = extproc.o
all: extproc.so
clean:
rm *.o
extproc.so: $(OBJS)
$(MAKE) -f $(MAKEFILE) extproc_callback \
SHARED_LIBNAME=$(TGTDLL) OBJS=$(OBJS)
CC=cc
CFLAGS= -g -I. $(INCLUDE) -Wall
but your command line should work as well... Then test.sql is:
create or replace library demoPassing
as '/usr/oracle/ora920/bin/extproc.so'
/
create or replace
function return_string return varchar2
as
language C name "return_string" library demoPassing
with context parameters
( CONTEXT, RETURN INDICATOR short, RETURN LENGTH int, RETURN STRING );
-- char *return_string
-- (
-- OCIExtProcContext *, /* 1 : With-Context */
-- short *, /* 2 : RETURN (Indicator) */
-- int * /* 3 : RETURN (Length) */
-- );
/
select return_string from dual;
i could not reproduce your issue on solaris with your code - could be a bug in the code -- if this code works, we'll have to see what is different about them
Thanks!
A reader, August 15, 2003 - 11:57 am UTC
Dear Tom,
I made two changes to your code:
1. removed the -Wall from the compiler options (not available on AIX)
2. put the return_string in quotes
The code fails with the message:
SQL> ed
Wrote file afiedt.buf
1 create or replace
2 function return_string return varchar2
3 as
4 language C name
5 "return_string"
6 library demoPassing
7 with context parameters ( CONTEXT,
8 RETURN INDICATOR short,
9 RETURN LENGTH int,
10* RETURN STRING );
SQL>
SQL> /
Function created.
SQL> select return_string from dual
2 /
select return_string from dual
*
ERROR at line 1:
ORA-20000:
SQL> /
RETURN_STRING
----------------------------------------------------------------------------
----
Hello World!
SQL>
Runs well on the next run.
Please note that if I add the -G compiler option it fails with ORA-21500. The message I reported in the TAR. Also, as before the second run is okay.
SQL> select return_string from dual;
select return_string from dual
*
ERROR at line 1:
ORA-20000: ORA-21500:T?
SQL> /
RETURN_STRING
----------------------------------------------------------------------------
----
Hello World!
SQL>
Tom, the test case I submitted to Metalink has drawn heavily on your code (I had your book, but lost it. So planning to buy another). I only goofed up in the memory allocation at the end that returned crap on AIX (the only excuse I have for myself is that worked on the IBM/ Dynix NUMA Oracle 8.1.5 very well - so didn't figure out the problem). So if you would allow me to relate this to the analysis by bug diagnostics at Metalink, in the Reproducibility section 8.1.7.4 AIX 32 bit ORA-21500 is returned.
May be this problem would be reproducible after all. I've the management after me to report on the status as it is 15th Aug.
Many thanks
August 15, 2003 - 12:47 pm UTC
I would say this is a bug on AIX -- to do with parsing that file. That my code fails and then runs is expected cause I only parse that file once per session.
I do not have an AIX box anywhere to play with -- so my suggestion is
simplify the test case -- function returning an integer, just send the context, no indicators, minimal code.
something they can easily reproduce in house. If you like, you can put it here before filing with them and we'll eyeball it to make sure it is OK first.
NO GLOBALS -- they always cause suspicion.
Thanks
A reader, August 18, 2003 - 4:16 am UTC
Dear Tom,
Please see if this is okay?
test.c file:
#include <oci.h>
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIError *errhp;
int getDummy( OCIExtProcContext *ctx )
{
ub1 false = 0, debugf_flag;
text *errbuf = OCIExtProcAllocCallMemory(ctx,512);
sb4 errcode = 0;
if ( OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ) != OCI_SUCCESS
)
{
OCIExtProcRaiseExcpWithMsg(ctx,20001,(text
*)"Error",0);
return NULL;
}
if (( OCIExtractInit( envhp, errhp ) != OCI_SUCCESS ) ||
( OCIExtractSetNumKeys( envhp, errhp, 1 ) != OCI_SUCCESS ) ||
( OCIExtractSetKey( envhp, errhp, (text *) "debugf",
OCI_EXTRACT_TYPE_BOOLEAN,
0, &false, NULL, NULL ) != OCI_SUCCESS )
||
( OCIExtractFromFile( envhp, errhp, 0,
(text *) "/tmp/test.ini" )
!= OCI_SUCCESS )
||
( OCIExtractToBool( envhp, errhp, (text *) "debugf",
0,
&debugf_flag ) != OCI_SUCCESS ) ||
( OCIExtractTerm( envhp, errhp ) != OCI_SUCCESS ))
{
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
OCIExtProcRaiseExcpWithMsg(ctx,errcode,errbuf,0);
return NULL;
}
return ((int) 0);
}
CREATE OR REPLACE FUNCTION GetDummy
RETURN binary_integer
AS LANGUAGE C
LIBRARY test
NAME "getDummy"
WITH CONTEXT
PARAMETERS (CONTEXT, RETURN int);
/
Error that is returned (instead of 0 expected):
SQL> /
select getdummy from dual
*
ERROR at line 1:
ORA-30110: syntax error at '%.*s'
Thanks
August 18, 2003 - 7:26 am UTC
OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf),
OCI_HTYPE_ERROR);
shouldn't sizeof(errbuf) be 511? since you allocated 512 bytes?
but otherwise -- looks good.
Thanks
A reader, August 18, 2003 - 11:18 am UTC
Dear Tom,
All the examples that Oracle have provided use sizeof(errbuf) and not sizeof(errbuf) -1.
August 18, 2003 - 2:57 pm UTC
that works if errbuf was defined as
char errbuf[255];
sizeof(errbuf) would be 255 -- telling us "255 bytes of storage for the error message"
if you however:
char * errbuf = call_to_allocate_memory();
sizeof( errbuf ) is now 4 (generally), even if you allocated 255 bytes.
Thanks
A reader, August 19, 2003 - 3:48 pm UTC
Thanks Tom,
I have forwarded the test case to metalink.
Also for above, it's because sizeof is determined at compile time and not run time.
Thanks again
Can you please help I am getting the same error
lou, June 22, 2004 - 4:37 pm UTC
select * from table(prepids.party_match('john'))
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
ORA-06512: at "SSA_QA.SSA_IDS", line 149
ORA-06512: at "SSA_QA.SSA_IDS", line 180
ORA-06512: at "SSA_QA.PREPIDS", line 79
ORA-06512: at "SSA_QA.PREPIDS", line 539
ORA-06512: at "SSA_QA.PREPIDS", line 565
ORA-06512: at "SSA_QA.PREPIDS", line 644
ORA-06512: at line 1
====================================
FUNCTION PARTY_MATCH(
name in VARCHAR2,
pty_typ_cd in VARCHAR2 DEFAULT NULL,
matchTolerance in VARCHAR2 := 'Typical')
-- RETURN VARCHAR2 IS
RETURN pty_srch_lst PIPELINED IS
i BINARY_INTEGER;
sockh BINARY_INTEGER;
tmp_str CHAR (200);
systemName VARCHAR2 (15);
search VARCHAR2 (30);
search_comment VARCHAR2 (100);
IDT VARCHAR2 (256);
memSize BINARY_INTEGER;
IDTLength BINARY_INTEGER;
numFields BINARY_INTEGER;
parameters LONG RAW;
sa ssa_ids.ids_StringArray;
searchRec LONG RAW;
recs BINARY_INTEGER;
records LONG RAW;
score BINARY_INTEGER;
PTY_NAME VARCHAR2(105) ;
PTY_ID NUMBER (9) ;
PTY_TYP_CDE VARCHAR2(5) ;
PTY_NA_ID NUMBER (15) ;
pty_lst pty_srch_lst;
pty pty_srch;
BEGIN
--libCheck('SSA_SSASE');
systemName:='Party';
memSize := 100;
recs := 0;
-- dbms_output.enable(1000000);
sockh := getConnection(systemName);
initializeParamsForPartyMatch(name,pty_typ_cd,search,parameters);
search_idt_get_ids (sockh,search,IDT,memSize);
search_fields_count_ids (sockh, search, numFields);
dbms_output.put_line (' numFields = ' || numFields);
reportSearchFields (sockh, search, numFields);
-- Initialize the searchRec and records
idt_length_get_ids (sockh, IDT, IDTLength);
searchRec := ssa_ids_util.chartoraw (' ', IDTLength);
records := ssa_ids_util.chartoraw (' ', 1);
dbms_output.put_line('Name: '||name||', Type: '||pty_typ_cd||', Parameters: '||ssa_ids_util.rawtochar(parameters));
search_start_ids (sockh, search, 'Typical', matchTolerance,
parameters, numFields, searchRec,
'', recs, records, 0);
dbms_output.put_line ('Records found: '||recs);
--result:='';
tmp_str:='';
FOR i IN 1..recs LOOP
simple_search_get_ids (sockh, search, searchRec,
IDTLength, score);
-- dbms_output.put_line (' score = ' || score);
tmp_str:=substr (ssa_ids_util.rawtochar (searchRec), 1, 135);
-- dbms_output.put_line (' searchRec = ' || tmp_str);
PTY_NAME := substr(tmp_str, 1 ,105) ;
PTY_NA_ID := substr(tmp_str, 106 , 15) ;
PTY_ID := substr(tmp_str, 121 , 9) ;
PTY_TYP_CDE := substr(tmp_str, 130 , 5) ;
pty := pty_srch( score ,
PTY_ID ,
PTY_NAME ,
PTY_TYP_CDE ,
PTY_NA_ID );
PIPE ROW(pty);
END LOOP;
--FOR i IN 1..recs LOOP
--dbms_output.put_line (' ID('||i||'): '|| result(i));
--END LOOP;
search_finish_ids (sockh, search);
shutdown (sockh);
return;
END PARTY_MATCH;
FUNCTION TITLE_MATCH(
TITLE IN VARCHAR2,
PTY_ID IN NUMBER,
IP_NAME IN VARCHAR2,
PTY_NA_ID IN NUMBER,
ROLE IN VARCHAR2,
MATCHTOLERANCE IN VARCHAR2 := 'Typical')
-- RETURN VARCHAR2 IS
RETURN title_srch_lst PIPELINED IS
i BINARY_INTEGER;
sockh BINARY_INTEGER;
tmp_str CHAR (400);
systemName VARCHAR2 (15);
search VARCHAR2 (30);
-- search_comment VARCHAR2 (100);
IDT VARCHAR2 (256);
memSize BINARY_INTEGER;
IDTLength BINARY_INTEGER;
numFields BINARY_INTEGER;
sa ssa_ids.ids_StringArray;
parameters LONG RAW;
searchRec LONG RAW;
recs BINARY_INTEGER;
records LONG RAW;
score BINARY_INTEGER;
IDS_WRK_TTL_ID NUMBER(15) ;
IDS_TTL VARCHAR2(150);
IDS_WRK_ID NUMBER(11) ;
IDS_IDS_ENTMT_ID NUMBER(15) ;
IDS_IDS_REC_PFR_ID VARCHAR2(15) ;
IDS_IDS_PTY_NA_ID NUMBER(15) ;
IDS_IDS_ROL_TYP_CDE VARCHAR2(2) ;
IDS_NAME VARCHAR2(100);
IDS_PTY_ID NUMBER(9) ;
-- titleSrchLst title_srch_lst;
titleSrch title_srch;
mtch_tolerance VARCHAR2(50) ;
BEGIN
--libCheck('SSA_SSASE');
systemName:='Title';
search := 'Title';
memSize := 100;
recs := 0;
-- dbms_output.enable(100000);
sockh := getConnection(systemName);
-- initialize parameters.
initializeParamsForTitleMatch(
TITLE ,
PTY_ID ,
IP_NAME ,
PTY_NA_ID ,
ROLE ,
search ,
parameters );
search_idt_get_ids (sockh,search,IDT,memSize);
search_fields_count_ids (sockh, search, numFields);
dbms_output.put_line (' numFields = ' || numFields);
reportSearchFields (sockh, search, numFields);
-- Initialize the searchRec and records
idt_length_get_ids (sockh, IDT, IDTLength);
searchRec := ssa_ids_util.chartoraw (' ', IDTLength);
records := ssa_ids_util.chartoraw (' ', 1);
--parameters is split in 2 parts due to limitiation in dbms_output.
dbms_output.put_line('Parameters: '||
SUBSTR(ssa_ids_util.rawtochar(parameters), 1, 200));
-- dbms_output.put_line('Parameters: '||
-- SUBSTR(ssa_ids_util.rawtochar(parameters),201,
-- LENGTH(ssa_ids_util.rawtochar(parameters))));
mtch_tolerance := matchTolerance;
IF mtch_tolerance is NULL
THEN
mtch_tolerance := 'Typical';
END IF;
search_start_ids (sockh, search, 'Exhaustive', 'Loose',
parameters, numFields, searchRec,
'', recs, records, 0);
dbms_output.put_line ('Records found: '||recs);
--result:='';
tmp_str:='';
FOR i IN 1..recs LOOP
simple_search_get_ids (sockh, search, searchRec,
IDTLength, score);
-- dbms_output.put_line (' score = ' || score);
tmp_str:=substr (ssa_ids_util.rawtochar (searchRec), 1, 400);
-- dbms_output.put_line (' searchRec = ' || tmp_str);
IDS_WRK_TTL_ID := substr(tmp_str, 1 , 15 ) ;
IDS_TTL := substr(tmp_str, 16 , 150 ) ;
IDS_WRK_ID := substr(tmp_str, 166, 11 ) ;
IDS_IDS_ENTMT_ID := substr(tmp_str, 177, 15 ) ;
IDS_IDS_REC_PFR_ID := substr(tmp_str, 192, 15 ) ;
IDS_IDS_PTY_NA_ID := substr(tmp_str, 207, 15 ) ;
IDS_IDS_ROL_TYP_CDE := substr(tmp_str, 222, 2 ) ;
IDS_NAME := substr(tmp_str, 224, 100 ) ;
IDS_PTY_ID := substr(tmp_str, 324, 9 ) ;
titleSrch := title_srch( score ,
IDS_WRK_TTL_ID ,
IDS_TTL ,
IDS_WRK_ID ,
IDS_IDS_ENTMT_ID ,
IDS_IDS_REC_PFR_ID ,
IDS_IDS_PTY_NA_ID ,
IDS_IDS_ROL_TYP_CDE ,
IDS_NAME ,
IDS_PTY_ID );
PIPE ROW(titleSrch);
END LOOP;
--FOR i IN 1..recs LOOP
--dbms_output.put_line (' ID('||i||'): '|| result(i));
--END LOOP;
search_finish_ids (sockh, search);
shutdown (sockh);
return;
END TITLE_MATCH;
END PREPIDS;
/
June 22, 2004 - 10:16 pm UTC
your C code is "crashing".
(as stated in the original answer.... problem in your C code, not in plsql or anything else)
Corollary EXTPROC Tactical Question
Robert, July 03, 2004 - 10:43 am UTC
Tom,
We have a very simple extproc program which our Developers use to issue unix calls from pl/sql using the system() function. We are wanting to spiff up this procedure to pass the 'stdout' output back to the calling pl/sql procedure.
In trying to figure out the best way to do this I have arrived at these possibilities. I would greatly value your advise and comments in this matter.
(1) Pass "> /tmp/stdout$$.txt" along with unix command to be run, and then use utl_file to read this file after extproc command returns. -->This is what we are doing now.
(2) Pass stdout back as an in/out variable
(3) Pass stdout back as the function return code
(4) Pass stdout back via a pl/sql pipe which calling program can read.
(5) Have extproc insert stdout into a database table so the calling pl/sql program can query it.
(6) Any other ideas?
Thanks,
Robert.
July 03, 2004 - 11:56 am UTC
Security Risk Calling Host Commands from Java
Robert, July 03, 2004 - 11:08 pm UTC
Tom,
But what about the security risk of calling host commands from java routines... since the commands are executed as 'oracle'?
By using extproc running as a lesser privileged user, you can limit the permissions and lock down security.
What am I missing?
Thank You!
Robert.
July 04, 2004 - 11:09 am UTC
you have the ability to very very very finely control which programs are callable and not -- something you don't have the capability to do with extprocs.
you can specify "this guy can run programs A and B" with java
with an extproc, you can specify the user extprocs run as -- by running a listener as that user, but you cannot limit the programs they may execute.
We have a "unique" situation...
Robert, July 05, 2004 - 11:03 am UTC
Tom,
Thanks for your expert help!
But our Developers are creating *dynamic* shell scripts from within their pl/sql programs, mainly dealing with ftp'ing files and processing them, and we are on 8.1.7.4 so we cannot use utl_smtp (?) to ftp binary files.
That is why we went with controlling security by extproc and unix userid.
So unless we could possibly standardize various shell script templates into which Developers could pass parameters (e.g. filenames, etc.). I think we will have to stick with the more complicated extproc and OCI type stuff.
Any further practical or philosophical words on this subject would be greatly appriciated!
Thanks,
Robert.
Does ftp.pk[sb] work for *binary* ftp in 8i ?
Robert, July 06, 2004 - 10:41 am UTC
Tom,
Please forgive my denseness...
But the example notes for the ftp.pk[sb] package you referred to says the following...
<quote>
The implementation of binary transfers relies on UTL_FILE features only available in Oracle9i Release 2.
<quote>
I take it from this that I *cannot* use this to ftp *binary* files with 8i database.
Am I missing something?
Thanks,
Robert.
July 06, 2004 - 11:19 am UTC
to save a binary file without using ftp they would be needed... to ftp a binary file in or out - it does not need it.
the example I had there was "saving a blob to disk" -- everything we need is there, the utl_file stuff isn't needed to get a file into the database via ftp (nor out actually). I showed that a binary interface...
Got it!
A reader, July 06, 2004 - 11:52 am UTC
Tom,
I get it now...I think.
You use the '[get|put]_remote_binary_data' procedures in the 'ftp' package.
...It's a two-step process:
DECLARE
l_conn UTL_TCP.connection;
l_blob blob;
BEGIN
-------------------------------------
-- Step 1: GET BINARY file from a server
-------------------------------------
l_conn := ftp.login('xxxx-xxx-xxx', '21', 'ftpuser', 'xxxxxxx');
ftp.binary(p_conn => l_conn);
l_blob := ftp.get_remote_binary_data
(p_conn => l_conn,
p_file => '/tmp/oracleO');
ftp.logout(l_conn);
utl_tcp.close_all_connections;
-------------------------------------
-- Step 2: PUT BINARY file to another server
-------------------------------------
l_conn := ftp.login('yyyyy-yyy-yyyy', '21', 'ftpuser', 'yyyyyyy');
ftp.binary(p_conn => l_conn);
ftp.put_remote_binary_data
(p_conn => l_conn,
p_file => '/tmp/new_oracleO',
p_data => l_blob);
ftp.logout(l_conn);
utl_tcp.close_all_connections;
END;
/
.......Am I correct, sir?
Thanks!
Robert.
July 06, 2004 - 1:58 pm UTC
yes, as long as you stick with blobs and clobs and use ftp to get/put to the file system -- it'll be just fine in 8i.
How to pass a PL/SQL *record* to/from a 'C' *struct*
Robert, July 27, 2004 - 3:42 pm UTC
Tom,
I have been studying your Chapter 18 on 'C-Based External Procedures' and have successfully created non-OCI pl/sql external procedures, passing several parameters of number and varchar2.
But is there a way to pass a pl/sql record to a 'c' external procedure and have the 'c' program 'map' that record as a struct?
If so, could you provide a simple example of how to 'map' these together.
Thanks,
Robert.
July 27, 2004 - 4:01 pm UTC
plsql records cannot be bound to 3gl structs/recs/whatever.
the only thing that can be bound to a plqsl record is another plsql records.
You can use object types "create type foo as object ( x int, y date );" and use those, but not a plsql record type
We can pass pl/sql objects to external proc?
Robert, July 27, 2004 - 5:25 pm UTC
Tom,
Do I understand you correctly that you can pass a pl/sql object (e.g. 'foo' in your example) to a 'c' external procedure?
If so, could you show a small example?
Thanks,
Robert
July 27, 2004 - 7:42 pm UTC
you can pass a foo - i only have demo of collections of simple types like collections of numbers, dates, and strings -- objects, I find them too "complicated" from the 3gl perspective -- but there are examples of binding to object types in the OCI guides -- that is all they are -- "object types"
you want to see about "ott"
</code>
http://otn.oracle.com/pls/db92/db92.drilldown?remark=&word=ott&book=&preference= <code>
the object type translator as well.
Difference
IK, January 24, 2006 - 8:26 pm UTC
Tom,
Apologies if iam digressing here.
Could you please explain in simple terms, conceptually what the difference is between ext proc and data cartridges.
Thanks,
January 25, 2006 - 12:54 pm UTC
Data cartridges are like an "application"
</code>
http://docs.oracle.com/docs/cd/B19306_01/appdev.102/b14289/dciwhatis.htm#sthref16 <code>
they are an implementation. They extend the functionality of the server by adding say a new datatype.
A data cartridge might well use an external procedure in it's implementation. You might choose to implement your data cartridge code in say the "C" language. The way to do that would be to code the "C" routines as external procedures which are now callable via the server.
So, a data cartridge is an implementation, that might use the database feature "external procedures"..
Thanks
IK, January 25, 2006 - 3:02 pm UTC
Thanks very much.
So, if i add a new datatype using C. Say, a struct. Can i populate it with values and store it in the database using my external proc? And, it would be stored as a BLOB, i guess.
Could you please point me (URL) to a full example if you can, please?
Iam having a mental block in understanding the concepts.
Thanks,
January 25, 2006 - 5:07 pm UTC
you would either
a) map it to scalars in a table, insert the components of the struct (my choice)
b) map it to a complex object type, hence create your own sql type.
c) store it in a blob - something I would never recommend, demonstrate, or even suggest.
A) would be my choice.
Datatype?
A reader, January 27, 2006 - 1:20 pm UTC
So, when you say "They extend the functionality of the server by adding say a new datatype." - do you mean an object type?
Could you clarify that part please? I dont seem to get how we could add a new datatype.
Thanks-
January 28, 2006 - 12:31 pm UTC
an object type coupled with functionality. like spatial does, like text does, like image does.....
A reader, June 10, 2008 - 5:31 am UTC
Hi Tom,
I need to call an external C library with fuction that accept parameter of pointer to array. What is the correct syntax for the "create function".
The specs for the C function is,
long FAID_size (long start_date,
long roster_count,
long* roster_from, -- array of start time
long* roster_to, -- array of end time
long* roster_id, -- array of Roster ID
long* roster_id_count
)
The pl/sql wrapper is,
CREATE OR REPLACE PACKAGE FAID_DLL_PKG AS
TYPE int_VarType IS VARRAY(50) OF PLS_INTEGER;
FUNCTION FAID_Size (
p_start_date in PLS_INTEGER,
p_roster_count in PLS_INTEGER,
p_roster_from in out int_VarType,
p_roster_to in out int_VarType,
p_roster_id in out int_VarType,
p_roster_id_cnt in out PLS_INTEGER
)
RETURN PLS_INTEGER;
end;
CREATE OR REPLACE PACKAGE BODY FAID_DLL_PKG AS
FUNCTION FAID_Size (
p_start_date in PLS_INTEGER,
p_roster_count in PLS_INTEGER,
p_roster_from in out int_VarType,
p_roster_to in out int_VarType,
p_roster_id in out int_VarType,
p_roster_id_cnt in out PLS_INTEGER
)
RETURN PLS_INTEGER
AS EXTERNAL
LANGUAGE C
NAME FAID_Size
LIBRARY FAID_DLL
PARAMETERS (
CONTEXT,
p_start_date long,
p_roster_count long,
p_roster_from BY REFERENCE ,
p_roster_to BY REFERENCE ,
p_roster_id BY REFERENCE ,
p_roster_id_cnt BY REFERENCE ,
RETURN long
);
end;
Thanks very much for your help.
June 10, 2008 - 8:07 am UTC
You'll need a different C function call - you cannot call any arbitrary function - it has to have datatypes we can map to, using SQL types.
If you have access to Expert One on One Oracle - in the chapter on external procedures, you'll find a full example, but in short, it would look like this:
tkyte@TKYTE816> create or replace type numArray as table of number
2 /
Type created.
...
12
13 procedure pass( p_in in numArray, p_out out numArray );
14
.....
100 procedure pass( p_in in numArray, p_out out numArray )
101 as
102 language C name "pass_numArray" library demoPassing
103 with context parameters
104 ( CONTEXT,
105 p_in OCIColl, p_in INDICATOR short,
106 p_out OCIColl, p_out INDICATOR short );
107
108 -- void pass_numArray
109 -- (
110 -- OCIExtProcContext *, /* 1 : With-Context */
111 -- OCIColl *, /* 2 : P_IN */
112 -- short , /* 3 : P_IN (Indicator) */
113 -- OCIColl **, /* 4 : P_OUT */
114 -- short * /* 5 : P_OUT (Indicator) */
115 -- );
116
......
#ifdef WIN_NT
_declspec (dllexport)
#endif
void pass_numArray
( OCIExtProcContext * ctx /* CONTEXT */,
OCIColl * p_in /* OCICOL */,
short p_in_i /* INDICATOR short */,
OCIColl ** p_out /* OCICOL */,
short * p_out_i /* INDICATOR short */
)
{
ub4 arraySize;
double tmp_dbl;
boolean exists;
OCINumber *ocinum;
int i;
myCtxStruct*myCtx;
if ( (myCtx = init( ctx )) == NULL ) return;
debugf( myCtx, "Enter Pass numArray" );
if ( p_in_i == OCI_IND_NULL )
{
raise_application_error( myCtx, ERROR_ARRAY_NULL,
"Input array was NULL" );
}
else
if ( OCICollSize( myCtx->envhp, myCtx->errhp,
p_in, &arraySize ) != OCI_SUCCESS )
{
raise_application_error(myCtx,ERROR_OCI_ERROR,
"%s",lastOciError(myCtx));
}
else
{
debugf( myCtx, "IN Array is %d elements long", arraySize );
for( i = 0; i < arraySize; i++ )
{
if (OCICollGetElem( myCtx->envhp, myCtx->errhp, p_in, i,
&exists, (dvoid*)&ocinum, 0 ) != OCI_SUCCESS )
{
raise_application_error(myCtx,ERROR_OCI_ERROR,
"%s",lastOciError(myCtx));
break;
}
if (OCINumberToReal( myCtx->errhp, ocinum,
sizeof(tmp_dbl), &tmp_dbl ) != OCI_SUCCESS )
{
raise_application_error(myCtx,ERROR_OCI_ERROR,"%s",
lastOciError(myCtx));
break;
}
debugf( myCtx, "p_in[%d] = %g", i, tmp_dbl );
if ( OCICollAppend( myCtx->envhp, myCtx->errhp, ocinum, 0,
*p_out ) != OCI_SUCCESS )
{
raise_application_error(myCtx,ERROR_OCI_ERROR,
"%s",lastOciError(myCtx));
break;
}
debugf( myCtx, "Appended to end of other array" );
}
*p_out_i = OCI_IND_NOTNULL;
}
term(myCtx);
}
You'll be passing a collection - you will need to write a wrapper C function that takes the collection, allocates a long array, fills it, invokes existing C function and then repackages the answer back into the collection.
A reader, June 10, 2008 - 10:59 pm UTC
I've got a copy of your book at the previous company I worked, but not here. I am ordering a copy of your book through amazon. Might take a week for that.
I am not a C programmer at all. That might take a while for me to digest this.
From my understanding, the alternative to accessing external function is using ORA_FFI. But I though accessing via pl/sql in database would be easier.
Could you tell me which is the better method to access external function in my case. Thanks very much.
June 11, 2008 - 8:02 am UTC
ora_ffi - the "foreign function interface" is for forms/reports and the like - in the middle tier.
I haven't ever used it - but it'll have similar issues - the C concept of an array with long * is a bit different then the plsql representation of a plsql index by table, collection or varray.
What I'm trying to say is that not just any C function is callable from PLSQL, you might have to implement an intermediate C function that marshalls the data between PLSQL and C.
Configure listener and tnsnames
A reader, June 11, 2008 - 11:29 pm UTC
Hi Tom,
You have been most helpfull with my problem. Still another question.
I am getting error (ORA-28575:unable to open RPC connection to external procedure agent ) when testing the connection calling "GetDiskFreeSpaceA" in "kernel32.dll".
Have we got the correct configuration for the connection. Thanks.
Listener.ora
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY=EXTKEY))
(ADDRESS = (PROTOCOL = TCP)(HOST =)(PORT = 1521))
)
)
)
tnsnames.ora
crsdevlthebe.intranet =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = IPC)(KEY=EXTKEY))
(ADDRESS = (PROTOCOL = TCP) (Host = thebe.intranet) (Port = 1521))
(CONNECT_DATA = (SERVICE_NAME = crsdevl.intranet))
)
June 12, 2008 - 7:16 am UTC
external procedure
A reader, July 15, 2008 - 4:58 am UTC
Hi Tom,
If my database server runs on Unix, does that mean I can only create association to Unix specific library ( library with .so extension) using 'create library'.
Can I call a VB DLL?
Thanks very much.
Tu-Oanh
July 15, 2008 - 9:47 am UTC
you would need the Oracle software installed on the remote machine - the listener, the database and all.
A reader, July 15, 2008 - 5:17 am UTC
PS. How do I call a window library written in VB from PL/SQL or Forms.
I know you are not a Forms person, but when I try ORA_FFI from Forms; I could not register the function. The library loaded fine.
July 15, 2008 - 9:47 am UTC
otn.oracle.com has a discussion forum all about forms and using forms.... try there
N3sgus
Joseph Boniface, April 29, 2010 - 6:41 am UTC
Can u provide me with some more info on n3sgus dll
We are working on a project which involves matching data like name, address. We have been using "n3sgus.dll" for this purpose.
This project was first started in 2000 in VB platform. Now we need to re-engineer the project to J2EE and for that, we need to re-engineer the methods used in n3sgus.dll. We are not sure in which language this dll is built and there are not Java equivalent available for the same.
We would like to know the alternate methods of using this DLL in J2EE application. Is it recommended to call DLL from Java program
April 29, 2010 - 7:51 am UTC
"U" isn't available, "U" is dead as far as I know. Look it up, it is true.
http://en.wikipedia.org/wiki/U_of_Goryeo I have no clue what n3sgus.dll is, does, or where it came from. That would be something for you to figure out.