oracle external procedure calls Теодор Иванов Софтуерен инженер

43
Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

Post on 27-Dec-2015

246 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

Oracle External Procedure Calls

Теодор Иванов Софтуерен инженер

Page 2: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

2

Oracle Documentationhttp://www.oracle.com/technology/documentation/database10gr2.html

Oracle Course

Oracle Database 10g: Advanced PL/SQLhttp://education.oracle.com/pls/web_prod-plq-dad/show_desc.redirect?dc=D17220GC10&p_org_id=1001&lang=US&source_call=

Помощ от приятел …

Как да се научим да използваме EPC

Page 3: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

3

Моят подход ...

Oracle® Database SQL Reference 10g Release 1 (10.1),Part No. B10759-01

PL/SQL User's Guide and Reference 10g Release 1 (10.1),Part No. B10807-01

Oracle® Data Cartridge Developer's Guide 10g Release 1 (10.1), Part No. B10800-01

5. Implementing Data Cartridges in C, C++ and Java

Page 4: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

4

Моят подход ...

Oracle® Database Application Developer's Guide –

Fundamentals 10g Release 1 (10.1), Part Number B10795-01

1 Programmatic Environments

Overview of Pro*C/C++

Overview of OCI and OCCI

Choosing a Programming Environment

8 Calling External Procedures

Page 5: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

5

Моят подход ...

Oracle® Call Interface - Programmer's Guide

10g Release 1 (10.1), Part No. B10779-01

1. Introduction and Upgrading

2. OCI Programming Basics

3. Datatypes

18. OCI Datatype Mapping and Manipulation Functions

19. OCI Cartridge Functions

Page 6: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

6

Какво е Oracle EPC ?

Начин да се използват функции от външни за

Oracle RDBMS библиотеки ( .lib, .so, .dll )

- SQL- PL/SQL - Java

Oracle RDBMS

C interface

Java interface

C proceduresor functions

Java proceduresor functions

Page 7: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

7

Кога се използва Oracle EPC ?

Липса на функционалност в Oracle RDBMS илинедостатачна ефективност на PL/SQL:

Многократни сметки с плаваща запетая и работа с едномерни и многомерни масиви и указатели

Описание или пресмятания, свързани със сложни информационни обекти – графи, матрици, и т.н.

Използване на вече готови библиотеки

Пълноценнo използване възможностите на операционната система

Page 8: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

8

Как работи Oracle EPC ?

Oracle RDBMS

begin res := a_plus_b( a, b );end;/

PL/SQL

SQL> select a_plus_b( a, b )from dual;

SQL

PL/SQL function a_plus_b(…), sum_lib

OCI network libraries, using tnsnames.ora

Oracle Listener process LISTENER using listener.ora

extproc agent$ORACLE_HOME/bin/extproc

Library sum_lib, sum.so EXTPROC_CONNECTION_DATA

using: EPC_IPC_KEYlooking for: EPC_SID

using: EPC_SIDexecuting: extproc agent

using: OS .so loadcalls: sum.so/a_plus_b_c()

OScreate or replace function a_plus_b(

a in binary_integer,

b in binary_integer

) return binary_integer

is language C

library sum_lib

name "a_plus_b_c"

parameters(

a int,

b int,

return int

);

/

tnsnames.ora

EXTPROC_CONNECTION_DATA =

(DESCRIPTION =

(ADDRESS_LIST =

(ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY))

)

(CONNECT_DATA =

(SID = EPC_SID)

)

)

create or replace library sum_lib

as '${ORACLE_HOME}/lib/sum.so';

/

listener.ora

SID_LIST_LISTENER =

(SID_LIST = ...

(SID_DESC =

(PROGRAM = extproc)

(SID_NAME = EPC_SID)

(ORACLE_HOME = /oracle/10.1)

(ENVS = "EXTPROC_DLLS=ANY")

)

)

LISTENER =

(DESCRIPTION_LIST = ...

(DESCRIPTION =

(ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY))

)

)

$ORACLE_HOME/lib/sum.so a_plus_b_c() { … }

Page 9: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

9

Как работи Oracle EPC ? - 2

Oracle RDBMS

begin res := a_plus_b( a, b );end;/

PL/SQL

SQL> select a_plus_b( a, b )from dual;

SQL

PL/SQL function a_plus_b(…), sum_lib

OCI network libraries, using tnsnames.ora

Oracle Listener process LISTENER using listener.ora

extproc agent$ORACLE_HOME/bin/extproc

Library sum_lib, sum.so EXTPROC_CONNECTION_DATA

using: EPC_IPC_KEYlooking for: EPC_SID

using: EPC_SIDexecuting: extproc agent

using: OS .so loadcalls: sum.so/a_plus_b_c()

OS$ORACLE_HOME/lib/sum.so a_plus_b_c() { … }

Page 10: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

10

Пример за EPC

С функция epc_demo_func_c() и Linux библиотека epc_demo.so C Тест на функцията epc_demo_func_cСъдържание на tnsnames.oraСъдържание на sqlnet.oraСъдържание на listener.oraOracle библиотека epc_demo_libPL/SQL функция epc_demo_func()Тест на PL/SQL функцията epc_demo_func()makefile

Page 11: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

11

Какво прави epc_demo_func()

Приема два параметара:

a - целочислено число

s - символен низ ( стринг )

Ако а е NULL, тогава epc_demo_func() връща 0

Ако а не е NULL, тогава разпечатва стойностите на всички входящи за функцията параметри в стринг buff и връща стойността на buff в s, а epc_demo_func() връща стойността на a

Page 12: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

12

PL/SQL библиотека и функция - epc_demo_func.sql

create or replace library epc_demo_lib as '${ORACLE_HOME}/lib/epc_demo.so';/

create or replace function epc_demo_func( a in pls_integer, s in out varchar2 ) return pls_integer is language C library epc_demo_lib name "epc_demo_func_c" parameters( a int, a indicator, s string, s length by reference int, s maxlen by reference int, return int );/

exit

при wrap на библиотеката, не можеда се използва обращение към

shell environment variables

Page 13: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

13

Съответствие на PL/SQL и C параметрите

PL/SQL PARAMETERS C

epc_demo_func( parameters( int epc_demo_func_c(

a in pls_integer,a int,

a indicator,

int a,

short a_ind,

s in out varchar2

s string,

s length by reference int,

s maxlen by reference int,

char *s,

int *s_length,

int *s_maxlen

) return pls_integer return int

)

)

Page 14: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

14

С функция - epc_demo_func_c.c#include <stdio.h>#include <string.h>#include "oci.h"

int epc_demo_func_c( int a, short a_ind, char *s, int *s_length, int *s_maxlen ){ // if a is NULL then return 0 if( a_ind == OCI_IND_NULL ) return( 0 );

продължава на следващият слайд ...

Page 15: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

15

продължение от предишният слайд ...

// prepare the new return value for s char buff[200]; sprintf( buff, "%s\na=%d, a_ind=%d, s='%s', s_length=%d, s_maxlen=%d", "C func epc_demo_func_c()\nreceived the following input paramters:", a, a_ind, s, *s_length, *s_maxlen );

// copy the prepared value into y strncpy( s, buff, *s_maxlen );

// set new s length *s_length = strlen( s );

// return the value of a return( a );}

Page 16: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

16

Тест на С функцията - epc_demo_func_c.c#ifdef TEST

int main( int argc, char *argv[] ){ char ss[200] = { "123" }; int aa = -7, res = 0, ss_len = strlen( ss ), ss_maxl = sizeof( ss ) - 1;

printf( "\nC test: begins\n\n" ); printf( "C test main(): will call C epc_demo_func_c() with

parameters:\n" ); printf( "C test main(): not NULL a = %d\n", aa ); printf( "C test main(): and s = >>>%s<<<\n\n", ss );

res = epc_demo_func_c( aa, OCI_IND_NOTNULL, ss, &ss_len, &ss_maxl );

продължава на следващият слайд ...

Page 17: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

17

продължение от предишният слайд ...

printf( "C test main(): epc_demo_func_c() returns %d\n", res ); printf( "C test main(): and resulted s is >>>%s<<<\n", ss ); printf( "C test main(): and resulted s length is %d\n", ss_len ); printf( "\nC test: ends\n\n" );

return( 0 );}

#endif

Page 18: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

18

Тест на PL/SQL функцията - epc_demo_func_test.sql

set serveroutput ondeclare aa pls_integer := -7; ss varchar2( 200 ) := '123'; res pls_integer := 0;begin dbms_output.enable; dbms_output.put_line( chr( 10 ) || 'PL/SQL test: begins' || chr( 10 ) ); dbms_output.put_line( 'PL/SQL test: will call PL/SQL epc_demo_func() ' || || 'with parameters' ); dbms_output.put_line( 'PL/SQL test: not NULL a = ' || aa ); dbms_output.put_line( 'PL/SQL test: s = >>>' || ss || '<<<' || chr( 10 ) ); res := epc_demo_func( aa, ss ); dbms_output.put_line( 'PL/SQL test: epc_demo_func() returns ' || res ); dbms_output.put_line( 'PL/SQL test: and resulted s is >>>' || ss || '<<<' ); dbms_output.put_line( chr( 10 ) || 'PL/SQL test: ends' || chr( 10 ) );end;/

Page 19: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

19

Редактиране на tnsnames.ora

Файлът се намира в $TNS_ADMINили в $ОRACLE_HOME/network/admin

EXTPROC_CONNECTION_DATA[.default_domain] = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY)) ) (CONNECT_DATA = (SID = EPC_SID) ) )

Page 20: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

20

[.default_domain]

Трябва да съответствува на стойността на:

NAMES.DEFAULT_DOMAIN = semantec.bg

от файла sqlnet.ora

Page 21: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

21

Редактиране на listener.oraСпиране на лисънъра, редактиране на listener.ora истартиране на лисънъра

SID_LIST_LISTENER = (SID_LIST = ... (SID_DESC = (PROGRAM = extproc) (SID_NAME = EPC_SID) (ORACLE_HOME = /oracle/10.1) (ENVS = "EXTPROC_DLLS=ANY") ) )

LISTENER = (DESCRIPTION_LIST = ... (DESCRIPTION = (ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY)) ) )

тези стойности трябва да съответстват на маркираните в зелено от tnsnames.ora

Page 22: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

22

Тест на редактираните *.ora файловете[oracle@didcos42 ~]$ tnsping EXTPROC_CONNECTION_DATA.SEMANTEC.BG

TNS Ping Utility for Linux: Version 10.1.0.4.0 - Production on 24-OCT-2006 20:59:47

Copyright (c) 1997, 2003, Oracle. All rights reserved.

Used parameter files:

/oracle/10.1/network/admin/sqlnet.ora

Used TNSNAMES adapter to resolve the alias

Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY))) (CONNECT_DATA = (SID = EPC_SID)))

OK (10 msec)

[oracle@didcos42 ~]$ tnsping EXTPROC_CONNECTION_DATA

TNS Ping Utility for Linux: Version 10.1.0.4.0 - Production on 24-OCT-2006 20:59:51

Copyright (c) 1997, 2003, Oracle. All rights reserved.

Used parameter files:

/oracle/10.1/network/admin/sqlnet.ora

Used TNSNAMES adapter to resolve the alias

Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EPC_IPC_KEY))) (CONNECT_DATA = (SID = EPC_SID)))

OK (10 msec)

Page 23: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

23

makefileall: so test sql

so ${ORACLE_HOME}/lib/epc_demo.so: epc_demo_func_c.c gcc -shared -o epc_demo.so epc_demo_func_c.c cp epc_demo.so ${ORACLE_HOME}/lib chmod 0755 ${ORACLE_HOME}/lib/epc_demo.so ls -l ${ORACLE_HOME}/lib/epc_demo.so

test: epc_demo_func_c.c gcc -o epc_demo_func_c epc_demo_func_c.c -D TEST ls -l epc_demo_func_c ./epc_demo_func_c

sql: epc_demo_func.sql ${ORACLE_HOME}/lib/epc_demo.so sqlplus -S di1/x @epc_demo_func.sql sqlplus -S di1/x @epc_demo_func_test.sql

Page 24: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

24

[oracle@didcos42 epc_demo]$ ls -l

total 16

-rw-r--r-- 1 oracle users 449 Oct 23 19:58 epc_demo_func.sql

-rw-r--r-- 1 oracle users 793 Oct 23 20:22 epc_demo_func_c.c

-rw-r--r-- 1 oracle users 326 Oct 23 20:07 epc_demo_func_test.sql

-rw-r--r-- 1 oracle users 456 Oct 23 20:57 makefile

[oracle@didcos42 epc_demo]$

Файловете от примера за EPC

Page 25: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

25

make test[oracle@didcos42 epc_demo]$

[oracle@didcos42 epc_demo]$

./epc_demo_func_c

C test: begins

C test main(): will call C epc_demo_func_c() with parameters:C test main(): not NULL a = -7C test main(): and s = >>>123<<<

C test main(): epc_demo_func_c() returns -7C test main(): and resulted s is >>>C func epc_demo_func_c()received the following input paramters:a=-7, a_ind=0, s='123', s_length=3, s_maxlen=199<<<C test main(): and resulted s length is 113

C test: ends

gcc -o epc_demo_func_c -I /oracle/10.1/rdbms/public -D TEST epc_demo_func_c.c ls -l epc_demo_func_c-rwxr-xr-x 1 oracle users 6175 Oct 25 18:59 epc_demo_func_c

Page 26: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

26

make sql[oracle@didcos42 epc_demo]$

[oracle@didcos42 epc_demo]$

sqlplus -S di1/x @epc_demo_func_test.sqlPL/SQL test: begins

PL/SQL test: will call PL/SQL epc_demo_func() with parametersPL/SQL test: not NULL a = -7PL/SQL test: s = >>>123<<<

PL/SQL test: epc_demo_func() returns -7PL/SQL test: and resulted s is >>>C func epc_demo_func_c()received thefollowing input paramters:a=-7, a_ind=0, s='123', s_length=3, s_maxlen=200<<<

PL/SQL test: endsPL/SQL procedure successfully completed.

sqlplus -S di1/x @epc_demo_func.sqlLibrary created.Function created.

Page 27: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

27

Дебъгване на EPC

Oracle RDBMS

SQL> select a_plus_b( 2, 3 ) from dual; <Enter>…

SQL>

SQL*Plus extproc agent$ORACLE_HOME/bin/extproc

using: OS .so loadcalls: sum.so/a_plus_b_c()

OS$ORACLE_HOME/lib/sum.so a_plus_b_c() { … }

(gdb) attach 9380 <Enter>Attaching to process 9380…(gdb) break pextproc <Enter>Breakpoint 1 at 0xb7d77782(gdb)

select function_for_debugging(…)from dual; <Enter>

[Switching to Thread -1225242944 (LWP 9380)]Breakpoint 1, 0xb7d77782 in pextproc () from /…/libagtsh.so(gdb) (gdb) break function_for_debugging <Enter>Breakpoint 2 at 0xb6e9e564(gdb)

Debugger - gdb, ddd !ps -ef | grep extproc <Enter>…oracle 9380 ... extprocEPC_SID (LOCAL=NO)…

SQL>(gdb) cont <Enter>Continuing.

(gdb) cont <Enter>Continuing.Breakpoint 2, 0xb6e9e564 in function_for_debugging () from /…/sum.so(gdb) _

[oracle@didcos42 ~]$ ps -ef | grep extprocUID PID PPID C STIME TTY TIME CMDoracle 9380 1 0 15:32 ? 00:00:00 extprocEPC_SID (LOCAL=NO)oracle 15776 15670 0 15:33 pts/1 00:00:00 grep extproc[oracle@didcos42 ~]$

Page 28: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

28

Допълнителна информация на EPC

Синтаксис на 'parameters'

Предизвикване на exception от EPC

Памет, указатели, стойности, променливи

Предаване на масиви като параметри

Изпълнение на DDL и DML от EPC

Page 29: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

29

Синтаксис на 'parameters'PARAMETERS (external_parameter[, external_parameter]...)

external_parameter stands for:{ CONTEXT |

SELF [{TDO | property}] | {parameter_name | RETURN} [property] [BY REFERENCE] [external_datatype] }

property stands for:{ INDICATOR [{STRUCT | TDO}] | LENGTH | DURATION | MAXLEN | CHARSETID | -- OCI_ATTR_CHARSET_ID CHARSETFORM -- OCI_ATTR_CHARSET_FORM}

Page 30: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

30

Съответствие на типовете PL/SQL – C

PL/SQL data type PARAMETERS ext.data type C data type

BOOLEAN,

BINARY_INTEGER,

PLS_INTEGER

[UNSIGNED] CHAR

[UNSIGNED] SHORT

[UNSIGNED] INT

[UNSIGNED] LONG

[unsigned] char

[unsigned] short

[unsigned] int

[unsigned] long

CHAR, LONG,

VARCHAR2

STRING

OCISTRING

char *

OCIString *

BLOB, CLOB OCILOBLOCATOR OCILobLocator *

NUMBER OCINUMBER OCINumber *

DATE OCIDATE OCIDate *

VARRAY, TABLE OCICOLL OCITable *

CONTEXT OCIExtProcContext *

Page 31: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

31

Съответствие на типоветеPARAMETERS Property – C

Property Allowed ext.data type C data type

INDICATOR SHORT short

LENGTH,

MAXLEN

[UNSIGNED] SHORT

[UNSIGNED] INT

[UNSIGNED] LONG

[unsigned] short

int

[unsigned] long

Page 32: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

32

Предизвикване на exception от EPC

CREATE OR REPLACE PROCEDURE plsTo_divide_proc (

dividend IN BINARY_INTEGER,divisor IN BINARY_INTEGER,result OUT FLOAT)

AS LANGUAGE CNAME "C_divide"LIBRARY MathLibWITH CONTEXTPARAMETERS (

CONTEXT,dividend INT,divisor INT,result FLOAT);

/

Page 33: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

33

void C_divide (

OCIExtProcContext *ctx;

int dividend;

int divisor;

float *result

)

{

/* Check for zero divisor. */

if( divisor == (int)0 ) {

/* Raise exception ZERO_DIVIDE, which is Oracle error 1476. */

if( OCIExtProcRaiseExcp( ctx, (int)1476 ) == OCIEXTPROC_SUCCESS )

return;

else

assert( 0 ); /* Incorrect parameters were passed. */

}

*result = (float)dividend / (float)divisor;

}

Page 34: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

34

void C_divide (

OCIExtProcContext *ctx;

int dividend;

int divisor;

float *result

)

{

/* Check for zero divisor. */

if( divisor == (int)0 ) {

/* Raise exception 20100, which is user defined */

if( OCIExtProcRaiseExcpWithMsg( ctx, (int)20100, "divisor is zero", 0 )

== OCIEXTPROC_SUCCESS )

return;

else

assert( 0 ); /* Incorrect parameters were passed. */

}

*result = (float)dividend / (float)divisor;

}

Page 35: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

35

Памет, указатели, стойности, променливи

Области на действие на променливите и използване

на стойностите им

Необходимост от OCIExtProcAllocCallMemory

Начин за заобикаляне необходимостта от

OCIExtProcAllocCallMemory

Page 36: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

36

С функция - epc_demo_func_c()int global_definition = 0;

int epc_demo_func_c( ...

){

char buf[200];static char large_buf[10485760]; // 10 MBytesint initialize_every_time = 8;static int static_initialized_every_time = 10;static int not_initialized;...

}

Page 37: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

37

Необходимост от OCIExtProcAllocCallMemory

например във функция за обединяване на два стринга:

функцията приема като параметри два стринга,

който трябва да обединят и да върне резултът от

обединието

Page 38: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

38

Начин за заобикаляне необходимостта от OCIExtProcAllocCallMemory

вместо 'out' параметри да се използват 'in out'параметри, чиито стойности да бъдат инициализиранипреди извикването на външна C функция:declare aa pls_integer := -7; ss varchar2( 200 ) := '123'; res pls_integer := 0;begin

... res := epc_demo_func( aa, ss );

...end;/

Page 39: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

39

Предаване на масиви като параметри PL/SQL

create or replace TYPE T_String_Array AS TABLE OF VARCHAR2(4000);/

create or replace function func_with_varray( pls_sql_table_of_strings in out T_String_Array, ) return pls_integer as

language Cname "c_func_with_varray"library SOME_LIBcalling standard Cwith contextparameters(

context,pls_sql_table_of_strings OCICOLL,return int);

/

Page 40: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

40

Предаване на масиви като параметри C

int c_func_with_varray( OCIExtProcContext *ctx,OCITable **pls_sql_table_of_strings,

){

// get the current number of elements into pls_sql_table_arraystatus = OCICollSize( ... );

// remove all current elements from pls_sql_table_arraystatus = OCICollTrim( ... );

// makes an OCI string from 'char *'status = OCIStringAssignText( ... );

// append one more element to the pls_sql_table_arraystatus = OCICollAppend( ... );

}

Page 41: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

41

Изпълнение на DDL и DML във EPCint c_func_with_varray(

OCIExtProcContext *ctx,...

){

OCIEnv *envhp; OCISvcCtx *svchp; OCIError *errhp; sword err; ... // get the environment handles for reuse the existing connection err = OCIExtProcGetEnv( ctx, &envhp, &svchp, &errhp ); ... // executing all other kind of OCI calls...

}

Page 42: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

42

Q & A

Page 43: Oracle External Procedure Calls Теодор Иванов Софтуерен инженер

43

Благодаря за Вашето внимание!