diff --git a/C/README.md b/C/README.md index 02910c16..ed37cdc1 100644 --- a/C/README.md +++ b/C/README.md @@ -1,2 +1,11 @@ # Oracle Call Interface (OCI) Examples This directory contains samples that uses Oracle Call Interface's C libraries for accessing and working with Oracle Database. +It also contains a sample makefile that can be used to compile these samples and create executables. + +List of files +------------- +| File Name | Description | +|-----------|-------------| +|[simpleConnDemo.c](./simpleConnDemo.c)| Shows a simple connection and query to Oracle Database using OCI. For explanation and the required setup, see the [blog](https://medium.com/oracledevs/oracle-call-interface-for-c-developers-simple-database-connection-and-query-58be8243a393).| +|[sessionPoolingDemo.c](./sessionPoolingDemo.c)| Shows session pooling with Oracle Database using OCI Threads. For explanation and the required setup, see the [blog](https://medium.com/oracledevs/oracle-call-interface-for-c-developers-session-pooling-and-multithreading-87f56cec993a).| +|[drcpDemo.c](./drcpDemo.c)| Shows a simple query with DRCP connection to Oracle Database using OCI| diff --git a/C/drcp.c b/C/drcpDemo.c similarity index 98% rename from C/drcp.c rename to C/drcpDemo.c index eba03dbf..b8181f6d 100644 --- a/C/drcp.c +++ b/C/drcpDemo.c @@ -1,6 +1,6 @@ -/* Copyright (c) 2022, Oracle. All rights reserved. */ +/* Copyright (c) 2022, 2025, Oracle. All rights reserved. */ -/* drcp.c */ +/* drcpDemo.c */ /* Oracle OCI Database Resident Connection Pooling (DRCP) Example */ /* Christopher Jones, 2022 */ @@ -11,8 +11,8 @@ #include #include -// const OraText userName[] = "SCOTT"; -// const OraText userPassword[] = "TIGER"; +// const OraText userName[] = ""; +// const OraText userPassword[] = ""; /* Take the user credentials as inputs*/ OraText userName[129]; diff --git a/C/makefile b/C/makefile index 8142040c..af4289b3 100644 --- a/C/makefile +++ b/C/makefile @@ -1,18 +1,27 @@ -# Copyright (c) 2022, Oracle. All rights reserved. -# Makefile for Oracle OCI Database Resident Connection Pooling (DRCP) Example +# Copyright (c) 2022, 2025, Oracle. All rights reserved. +# Makefile for Oracle Call Interface examples # Christopher Jones, 2022 +# Sharad Chandran R, 2025 -IC=$(HOME)/instantclient_19_11 +# Update the instant client path here +IC=$(HOME)/instantclient_19_26 OLIB=$(IC) OINC=$(IC)/sdk/include +DEMOS=drcpDemo simpleConnDemo sessionPoolingDemo -#OH=/u01/app/oracle/product/12.1.0/dbhome_1 +# Uncomment the following 3 lines if you are using an +# Oracle Database home +#OH=/u01/app/oracle/product/19.26.0/dbhome_1 #OLIB=$(OH)/lib #OINC=$(OH)/rdbms/public -drcp: clean - $(CC) -Wall -Wextra -c -I$(OINC) drcp.c - $(CC) -o drcp drcp.o -Wl,--rpath -Wl,$(OLIB) -L$(OLIB) -lclntsh +.PHONY: all clean + +all: $(DEMOS) + +$(DEMOS): %: %.c + $(CC) -Wall -Wextra -c -I$(OINC) $< + $(CC) -o $@ $@.o -Wl,--rpath -Wl,$(OLIB) -L$(OLIB) -lclntsh clean: - rm -f drcp.o drcp + rm -f $(DEMOS) *.o diff --git a/C/sessionPoolingDemo.c b/C/sessionPoolingDemo.c new file mode 100644 index 00000000..813dc307 --- /dev/null +++ b/C/sessionPoolingDemo.c @@ -0,0 +1,475 @@ +/* Copyright (c) 2025, Oracle and/or its affiliates.*/ +/* + NAME + sessionPoolingDemo.c - Basic OCI Session Pooling with multithreading + DESCRIPTION + This program invokes multiple threads to insert MAXTHREADS records + into the EMPLOYEES table using session pools (Employee ids 100-109). + This program assumes that sample HR schema is setup and the EMPLOYEES + table is created with employee_id as the primary key. +*/ + +#ifndef OCI_ORACLE +# include +#endif + +#include +#include +#include + +#define MAXTHREADS 10 // Maximum number of threads + +// Maximum lengths for columns +#define MAX_NAME_LEN_FIRST 20 +#define MAX_NAME_LEN_LAST 25 +#define MAX_EMAIL_LEN 50 +#define MAX_JOB_LEN 20 + +static ub4 sessMin = 3; // Min no. of pooled sessions +static ub4 sessMax = 8; // Max no. of pooled sessions +static ub4 sessIncr = 5; // increment number + +static OCIError *errhp; +static OCIEnv *envhp; +static OCISPool *poolhp = (OCISPool *) 0; +static int employeeNum[MAXTHREADS]; + +static OraText *poolName; +static ub4 poolNameLen; + +// application user to set the database credentials here +static char *username = ""; +static char *password = ""; +static char *connstr = ""; + +/* Values to be inserted into the 'employees' table */ +static char* firstname[10] = { "A","B","C","D","E","F","G","H","I","J" }; +static char* lastname[10] = { "A","B","C","D","E","F","G","H","I","J"} ; +static char* email[10] = { "A@example.com","B@example.com","C@example.com", + "D@example.com","E@example.com","F@example.com", + "G@example.com","H@example.com","I@example.com", + "J@example.com"}; +static char* ejob[10] = { "FI_ACCOUNT","AC_ACCOUNT","SA_MAN","PU_MAN", + "PU_CLERK","IT_PROG","MK_REP","AD_VP","AC_MGR", + "HR_REP" }; +static float esal[10] = { 10000.00, 5000.00, + 8000.00, 6000.00, + 10000.00, 8000.50, + 6000.00, 6000.70, + 5000.00, 5000.00 }; + +static char* hiredate[10] = { "07-JUN-96", "08-JAN-95", "18-JUL-98", + "21-FEB-99", "02-JUL-04", "13-AUG-08", + "28-DEC-10", "27-SEP-15", "01-JUL-16", + "01-AUG-19" }; + +static unsigned int edept_id[10] = { 10, 20, 10, 20, 30, 10, 30, 20, 10, 20 }; + +static void checkerr (OCIError *errhp, sword status); +static void threadFunction (dvoid *arg); +static void fetchAndPrintData(void); +static sword threadCleanup(OCISvcCtx *svchp, OCIAuthInfo *authInfop, + OCIError* errhp1, OCIStmt *stmthp); + +/* ----------------------------------------------------------------- */ +/* Main function definition */ +/* ----------------------------------------------------------------- */ +int main(void) +{ + int timeout = 1; // 1 second + sword status; + int i = 0; + + /** Stage 1: Establish connection to the database using session pool */ + + // set the environment handle to use OCI threaded mode + OCIEnvCreate(&envhp, OCI_THREADED, (dvoid *) 0, NULL, NULL, NULL, 0, + (dvoid *) 0); + + // Allocate the error and pool handles + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR, + (size_t) 0, (dvoid **) 0); + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &poolhp, OCI_HTYPE_SPOOL, + (size_t) 0, (dvoid **) 0); + + // set the session pool timeout + checkerr(errhp, OCIAttrSet((dvoid *) poolhp, + (ub4) OCI_HTYPE_SPOOL, (dvoid *) &timeout, (ub4) 0, + OCI_ATTR_SPOOL_TIMEOUT, errhp)); + + // session pool creation + if (status = OCISessionPoolCreate(envhp, errhp, poolhp, + (OraText **) &poolName, (ub4 *) &poolNameLen, + (const OraText *) connstr, + (ub4)strlen((const signed char *) connstr), + sessMin, sessMax, sessIncr, (OraText *) username, + (ub4)strlen((const signed char *) username), + (OraText *) password, + (ub4)strlen((const signed char *) password), + OCI_DEFAULT)) + checkerr(errhp,status); + + printf("Session Pool: \"%s\" Created \n", poolName); + + /** Stage 2: Insert employee data using OCI Session Pools and Threads */ + + OCIThreadId *thrid[MAXTHREADS]; + OCIThreadHandle *thrhp[MAXTHREADS]; + + OCIThreadProcessInit(); + checkerr (errhp, OCIThreadInit(envhp, errhp)); + for (i = 0; i < MAXTHREADS; ++i) + { + checkerr(errhp, OCIThreadIdInit(envhp, errhp, &thrid[i])); + checkerr(errhp, OCIThreadHndInit(envhp, errhp, &thrhp[i])); + } + for (i = 0; i < MAXTHREADS; ++i) + { + employeeNum[i] = i; + // Create the threads for inserting the records into the + // "employee" table + checkerr(errhp, OCIThreadCreate(envhp, errhp, threadFunction, + (dvoid *) &employeeNum[i], thrid[i], thrhp[i])); + } + for (i = 0; i < MAXTHREADS; ++i) + { + checkerr(errhp, OCIThreadJoin(envhp, errhp, thrhp[i])); + checkerr(errhp, OCIThreadClose(envhp, errhp, thrhp[i])); + checkerr(errhp, OCIThreadIdDestroy(envhp, errhp, &(thrid[i]))); + checkerr(errhp, OCIThreadHndDestroy(envhp, errhp, &(thrhp[i]))); + } + checkerr(errhp, OCIThreadTerm(envhp, errhp)); + + /** Stage 3: Fetch the inserted data and print it */ + + // fetch and print the entire employee data inserted + (void) fetchAndPrintData(); + + /** Stage 4: Close the session pool and clean up buffers and handles */ + + // Close the session pool + status = OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT); + if (status != OCI_SUCCESS) + checkerr(errhp, status); + printf("Session Pool: \"%s\" Closed.\n", poolName); + + // free all handles and buffers + checkerr(errhp, OCIHandleFree((dvoid *) poolhp, OCI_HTYPE_SPOOL)); + checkerr(errhp, OCIHandleFree((dvoid *) errhp, OCI_HTYPE_ERROR)); + if (envhp) + OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV); + + OCITerminate(OCI_DEFAULT); + return 0; +} + +/* ----------------------------------------------------------------- */ +/* Inserts records into the 'employees' table */ +/* ----------------------------------------------------------------- */ +static void threadFunction(dvoid *arg) +{ + int idx = *(int *)arg; + int empno = idx + 100; + int firstnm_len = strlen((char *) firstname[idx]) + 1; + int lastnm_len = strlen((char *) lastname[idx]) + 1; + int em_len = strlen((char *) email[idx]) + 1; + int job_len = strlen((char *) ejob[idx]) + 1; + OraText firstnm[firstnm_len], + lastnm[lastnm_len], + em[em_len], + job[job_len]; + + // OCI handle and pointer declarations + OCISvcCtx *svchp = (OCISvcCtx *) 0; + OCIStmt *stmthp = (OCIStmt *) 0; + OCIError *errhp2 = (OCIError *) 0; // separate error handle for the thread + OCIAuthInfo *authp = (OCIAuthInfo *) 0; + OCIBind *bndp_arr[8] = { + (OCIBind *) 0, (OCIBind *) 0, (OCIBind *) 0, (OCIBind *) 0, + (OCIBind *) 0, (OCIBind *) 0, (OCIBind *) 0, (OCIBind *) 0 + }; + OCIDefine *defnp = (OCIDefine *) 0; + + // SQL statements to execute + OraText *insertstmt = (OraText *) "INSERT INTO EMPLOYEES(employee_id,\ + first_name, last_name, email, hire_date, job_id, salary,\ + department_id) values(:empid, :firstname, :lastname,\ + :email, :hiredt, :ejob, :esal, :edept_id)"; + + sword status; + + // allocate error handle and session authentication information handle + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp2, OCI_HTYPE_ERROR, + (size_t) 0, (dvoid **) 0); + checkerr(errhp2, OCIHandleAlloc((dvoid *) envhp, + (dvoid **) &authp, (ub4) OCI_HTYPE_AUTHINFO, + (size_t) 0, (dvoid **) 0)); + + // set user credentials into the authentication informaton handle + checkerr(errhp2, OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) username, (ub4) strlen((char *) username), + (ub4) OCI_ATTR_USERNAME, errhp2)); + checkerr(errhp2,OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) password, (ub4) strlen((char *) password), + (ub4) OCI_ATTR_PASSWORD, errhp2)); + + // get a session from the pool + checkerr(errhp2, OCISessionGet(envhp, errhp2, &svchp, authp, + (OraText *) poolName, (ub4) strlen((char *) poolName), + NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL)); + + // prepare the INSERT statement + checkerr(errhp2, OCIStmtPrepare2(svchp, (OCIStmt **) &stmthp, errhp2, + (const OraText *) insertstmt, + (ub4)strlen((const signed char *) insertstmt), + NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT)); + + // bind the placeholders in the INSERT statement + if ((status = OCIBindByName(stmthp, &bndp_arr[0], errhp2, + (OraText *) ":EMPID", -1, (dvoid *) &empno, + (sword) sizeof(empno), SQLT_INT, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[1], errhp2, + (OraText *) ":FIRSTNAME", -1, (dvoid *) firstname[idx], + firstnm_len, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[2], errhp2, + (OraText *) ":LASTNAME", -1, (dvoid *) lastname[idx], + lastnm_len, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[3], errhp2, + (OraText *) ":EMAIL", -1, (dvoid *) email[idx], + em_len, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[4], errhp2, + (OraText *) ":HIREDT", -1, (dvoid *) hiredate[idx], + strlen((char *) hiredate[idx]) + 1, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[5], errhp2, + (OraText *) ":EJOB", -1, (dvoid *) ejob[idx], + job_len, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[6], errhp2, + (OraText *) ":ESAL", -1, (dvoid *) &esal[idx], + (sword) sizeof(esal[idx]), SQLT_FLT, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(stmthp, &bndp_arr[7], errhp2, + (OraText *) ":EDEPT_ID", + -1, (dvoid *) &edept_id[idx], + (sword) sizeof(edept_id[idx]), SQLT_INT, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT))) + { + checkerr(errhp2, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; // exit from the thread function + } + + // execute and commit the INSERT statement + status = OCIStmtExecute(svchp, stmthp, errhp2, (ub4) 1, (ub4) 0, + (OCISnapshot *) 0, (OCISnapshot *) 0, OCI_DEFAULT); + if (status != OCI_SUCCESS) { + // INSERT failed! + printf("INSERT failed for employee id: %d\n", empno); + checkerr(errhp2, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; // exit from the thread function + } + + // commit the transaction + checkerr(errhp2, OCITransCommit(svchp, errhp2 ,(ub4) 0)); + + printf("Employee id %d added.\n", empno); + + // cleanup the handles + threadCleanup(svchp, authp, errhp2, stmthp); +} + +/* ----------------------------------------------------------------- */ +/* Fetch and print data from the 'employees' table */ +/* ----------------------------------------------------------------- */ +static void fetchAndPrintData(void) +{ + OraText firstnm[MAX_NAME_LEN_FIRST]; + OraText lastnm[MAX_NAME_LEN_LAST]; + OraText email[MAX_EMAIL_LEN]; + OraText job[MAX_JOB_LEN]; + sword empno; + + // Indicator variables for NULL values (0 for not NULL, -1 for NULL) + sb2 firstnm_ind = 0; // Indicator variable for firstnm + sb2 lastnm_ind = 0; // Indicator variable for lastnm + sb2 email_ind = 0; // Indicator variable for email + sb2 job_ind = 0; // Indicator variable for job + + // OCI handle and pointer declarations + OCISvcCtx *svchp = (OCISvcCtx *) 0; + OCIStmt *stmthp = (OCIStmt *) 0; + OCIError *errhp2 = (OCIError *) 0; // separate error handle for the thread + OCIAuthInfo *authp = (OCIAuthInfo *) 0; + OCIDefine *defnp = (OCIDefine *) 0; + + sword status; + + // SQL statements to execute + OraText *selectstmt = (OraText *) "SELECT employee_id, first_name, last_name,\ + email, job_id FROM EMPLOYEES"; + + // allocate error handle and session authentication information handle + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp2, OCI_HTYPE_ERROR, + (size_t) 0, (dvoid **) 0); + checkerr(errhp2, OCIHandleAlloc((dvoid *) envhp, + (dvoid **) &authp, (ub4) OCI_HTYPE_AUTHINFO, + (size_t) 0, (dvoid **) 0)); + + // set user credentials into the authentication informaton handle + checkerr(errhp2, OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) username, (ub4) strlen((char *) username), + (ub4) OCI_ATTR_USERNAME, errhp2)); + checkerr(errhp2,OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) password, (ub4) strlen((char *) password), + (ub4) OCI_ATTR_PASSWORD, errhp2)); + + // get a session from the pool + checkerr(errhp2, OCISessionGet(envhp, errhp2, &svchp, authp, + (OraText *) poolName, (ub4) strlen((char *) poolName), + NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL)); + + // prepare the SELECT statement + checkerr(errhp2, OCIStmtPrepare2(svchp, (OCIStmt **) &stmthp, errhp2, + (const OraText *) selectstmt, + (ub4)strlen((const signed char *) selectstmt), + NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT)); + + // allocate and define output buffers for the SELECT statement + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 1, + (dvoid *) &empno, (sb4) sizeof(empno), SQLT_INT, + (dvoid *) &firstnm_ind, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; + } + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 2, + (dvoid *) firstnm, (sb4) sizeof(firstnm), SQLT_STR, + (dvoid *) &firstnm_ind, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; + } + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 3, + (dvoid *) lastnm, (sb4) sizeof(lastnm), SQLT_STR, + (dvoid *) &lastnm_ind, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; + } + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 4, + (dvoid *) email, (sb4) sizeof(email), SQLT_STR, + (dvoid *) &email_ind, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; + } + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 5, + (dvoid *) job, (sb4) sizeof(job), SQLT_STR, + (dvoid *) &job_ind, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + threadCleanup(svchp, authp, errhp2, stmthp); + return; + } + + // Execute the SELECT statement + // set the 4th parameter (iters) of OCIStmtExecute to 0 as we do not know + // the number of rows that will be fetched + checkerr(errhp2, OCIStmtExecute(svchp, stmthp, errhp2, (ub4) 0, (ub4) 0, + (OCISnapshot *) 0, (OCISnapshot *) 0, OCI_DEFAULT)); + + printf("\nEmployee Details:\n"); + printf("---------------------------------------------------------------------------------------------------------------------------------\n"); + printf("%-15s %-25s %-25s %-50s %-20s\n","EMPLOYEE ID", "FIRST NAME", "LAST NAME", "EMAIL", "JOB"); + printf("---------------------------------------------------------------------------------------------------------------------------------\n"); + + // Loop until OCI_NO_DATA is returned, indicating no more rows. + while ((status = OCIStmtFetch2(stmthp, errhp, (ub4) 1, OCI_FETCH_NEXT, + (sb4) 0, OCI_DEFAULT)) != OCI_NO_DATA) { + checkerr(errhp, status); + + // Check indicator variables for NULL values and handle them + if (firstnm_ind == -1) { + strcpy(firstnm, "NULL"); + } + if (lastnm_ind == -1) { + strcpy(lastnm, "NULL"); + } + if (email_ind == -1) { + strcpy(email, "NULL"); + } + if (job_ind == -1) { + strcpy(job, "NULL"); + } + + printf("%-15d %-25s %-25s %-50s %-20s\n", empno, firstnm, lastnm, email, job); + } + printf("-------------------------------------------------------------------------------------------------------------------------------\n"); + + // cleanup the handles + threadCleanup(svchp, authp, errhp2, stmthp); +} + +/* ----------------------------------------------------------------- */ +/* Checks for errors and displays them */ +/* ----------------------------------------------------------------- */ +void checkerr(OCIError *errhp, sword status) +{ + OraText errbuf[512]; + sb4 errcode = 0; + + switch (status) + { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + (void) printf("Error - OCI_SUCCESS_WITH_INFO\n"); + break; + case OCI_NEED_DATA: + (void) printf("Error - OCI_NEED_DATA\n"); + break; + case OCI_NO_DATA: + (void) printf("Error - OCI_NODATA\n"); + break; + case OCI_ERROR: + (void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (OraText *) NULL, &errcode, + errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + (void) printf("Error - %.*s\n", 512, errbuf); + break; + case OCI_INVALID_HANDLE: + (void) printf("Error - OCI_INVALID_HANDLE\n"); + break; + case OCI_STILL_EXECUTING: + (void) printf("Error - OCI_STILL_EXECUTE\n"); + break; + case OCI_CONTINUE: + (void) printf("Error - OCI_CONTINUE\n"); + break; + default: + break; + } +} + +/*---------------------------------------------------------------------*/ +/* Close thread and clean up */ +/*---------------------------------------------------------------------*/ +sword threadCleanup(OCISvcCtx *svchp, OCIAuthInfo *authInfop, + OCIError *errhp1, OCIStmt *stmthp) +{ + checkerr(errhp1, OCIStmtRelease(stmthp, errhp1, (OraText *) NULL, (ub4) 0, + (ub4) OCI_DEFAULT)); + checkerr(errhp1, OCISessionRelease(svchp, errhp1, NULL, 0, OCI_DEFAULT)); + OCIHandleFree((dvoid *) authInfop, OCI_HTYPE_AUTHINFO); + OCIHandleFree((dvoid *) errhp1, OCI_HTYPE_ERROR); + return OCI_SUCCESS; +} diff --git a/C/simpleConnDemo.c b/C/simpleConnDemo.c new file mode 100644 index 00000000..54992759 --- /dev/null +++ b/C/simpleConnDemo.c @@ -0,0 +1,459 @@ +/* Copyright (c) 2025, Oracle and/or its affiliates.*/ +/* All rights reserved.*/ + +/* + * + * NAME + * simpleConnDemo.c - Basic Oracle Call Interface (OCI) functionality + * + * DESCRIPTION + * An example program which adds new employee records to the personnel + * data base. Checking is done to validate the integrity of the data base. + * The employee numbers are automatically selected using the current + * maximum employee number as the start. + * + * The program queries the user for data as follows: + * + * Enter employee name: + * Enter employee job: + * Enter employee salary: + * Enter employee dept: + * + * The program terminates if return key (Enter) is entered + * when the employee name is requested. + * + * If the record is successfully inserted, the following + * is printed: + * + * "ename" added to department "dname" as employee # "empno" + * + * Demonstrates creating a connection, a session and executing some SQL. + * Also shows the usage of allocating memory for application use which has + * the life time of the handle. + * +*/ + +#ifndef OCI_ORACLE +#include +#endif +#include +#include +#include + +// Table specific constants +#define ENAME_MAXLEN 80 +#define JOB_MAXLEN 50 +#define DEPTNAME_MAXLEN 14 + +/* Define the SQL statements to be used in program. */ +static OraText *insert = (OraText *) "INSERT INTO \ + emp(empno, ename, job, sal, deptno)\ + VALUES (:empno, :ename, :job, :sal, :deptno)"; +static OraText *seldept = (OraText *) "SELECT dname FROM dept WHERE \ + deptno = :1"; +static OraText *selmaxemp = (OraText *) "SELECT NVL(MAX(empno), 0) FROM emp"; + +static OCIEnv *envhp = NULL; +static OCIError *errhp = NULL; + +static void checkerr(/*_ OCIError *errhp, sword status _*/); +static void myfflush(/*_ void _*/); +static void free_buffers(/*p_ename, p_job, p_dept*/); +static sword cleanup(/*svchp, authInfop, stmthp*/); + +static sword status; +static boolean logged_on = FALSE; + +/* ----------------------------------------------------------------- */ +/* Main function definition */ +/* ----------------------------------------------------------------- */ +int main(int argc, char **argv) +{ + + sword empno, deptno; + float sal; + sword len, len2; + sb2 sal_ind, job_ind; + OraText *cp, *ename = NULL, + *job = NULL, *dept = NULL; + + // application user to set the database credentials here + char *username = ""; + char *password = ""; + char *connstr = ""; + + OCIAuthInfo *authInfop = (OCIAuthInfo *) 0; + OCISvcCtx *svchp = NULL; + OCIStmt *inserthp = NULL, // for the INSERT statement + *stmthp = NULL; // for the SELECT statements + OCIDefine *defnp = (OCIDefine *) 0; + + OCIBind *bnd1p = (OCIBind *) 0, /* the first bind handle */ + *bnd2p = (OCIBind *) 0, /* the second bind handle */ + *bnd3p = (OCIBind *) 0, /* the third bind handle */ + *bnd4p = (OCIBind *) 0, /* the fourth bind handle */ + *bnd5p = (OCIBind *) 0, /* the fifth bind handle */ + *bnd6p = (OCIBind *) 0; /* the sixth bind handle */ + + sword errcode = 0; + + /** Stage 1: Establish connection to the database */ + + /* set the environment handle */ + errcode = OCIEnvCreate((OCIEnv **) &envhp, (ub4) OCI_DEFAULT, + (dvoid *) 0, (dvoid * (*)(dvoid *,size_t)) 0, + (dvoid * (*)(dvoid *, dvoid *, size_t)) 0, + (void (*)(dvoid *, dvoid *)) 0, (size_t) 0, (dvoid **) 0); + + if (errcode != 0) { + (void) printf("OCIEnvCreate failed with errcode = %d.\n", errcode); + exit(1); + } + + /* allocate error and authentication information handles */ + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR, + (size_t) 0, (dvoid **) 0); + (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &authInfop, + (ub4) OCI_HTYPE_AUTHINFO, (size_t) 0, (dvoid **) 0); + + /* set user credentials on the authentication information handle */ + (void) OCIAttrSet((dvoid *) authInfop, (ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) username, (ub4) strlen((char *)username), + (ub4) OCI_ATTR_USERNAME, errhp); + (void) OCIAttrSet((dvoid *) authInfop, (ub4) OCI_HTYPE_AUTHINFO, + (dvoid *) password, (ub4) strlen((char *)password), + (ub4) OCI_ATTR_PASSWORD, errhp); + + /* connect to the database */ + status = OCISessionGet(envhp, errhp, &svchp, authInfop, (OraText *)connstr, + (ub4) strlen((char *)connstr), NULL, (ub4) 0, NULL, + (ub4) 0, FALSE, (ub4) OCI_DEFAULT); + if (status == OCI_ERROR) { + checkerr(errhp, status); + // Clean up the handles, if required + cleanup(svchp, authInfop, inserthp, stmthp); + return OCI_ERROR; + } + + logged_on = TRUE; + printf("Connected to the database successfully.\n"); + + /** Stage 2: Execute the SQL statements */ + + /* retrieve the current maximum employee number */ + checkerr(errhp, OCIStmtPrepare2(svchp,(OCIStmt **)&stmthp, errhp, selmaxemp, + (ub4) strlen((char *) selmaxemp), NULL, 0, + (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT)); + + /* define the output variable for the select-list. */ + checkerr(errhp, OCIDefineByPos(stmthp, &defnp, errhp, 1, (dvoid *) &empno, + (sword) sizeof(sword), SQLT_INT, (dvoid *) 0, + (ub2 *) 0, (ub2 *)0, OCI_DEFAULT)); + + /* 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) // No rows found + empno = 10; + else + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + return OCI_ERROR; + } + } + + checkerr(errhp, OCIStmtRelease((OCIStmt *)stmthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT)); + + /* Keep inserting data till the user exits the application */ + for (;;) + { + // the employee name buffer size is ENAME_MAXLEN + 2 to allow for \n & \0 + ename = (OraText *) malloc((size_t) (ENAME_MAXLEN + 2) * sizeof(OraText)); + + /* Prompt for employee name. Break on no name. */ + printf("Enter employee name (or Enter to EXIT): "); + fgets((char *) ename, (int) ENAME_MAXLEN + 1, stdin); + cp = (OraText *) strchr((char *) ename, '\n'); + if (cp == ename) + { + printf("Exiting...\n"); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_SUCCESS; + } + if (cp) + *cp = '\0'; + else + { + printf("Employee name may be truncated.\n"); + myfflush(); + } + + // the job buffer size is JOB_MAXLEN + 2 to allow for \n and \0 + job = (OraText *) malloc((size_t) (JOB_MAXLEN + 2) * sizeof(OraText)); + + /* Prompt for employee job and salary */ + printf("Enter employee job: "); + job_ind = 0; + fgets((char *) job, (int) JOB_MAXLEN + 1, stdin); + cp = (OraText *) strchr((char *) job, '\n'); + if (cp == job) + { + job_ind = -1; // make it NULL in the table + printf("Job is NULL.\n"); // using indicator variable + } + else if (cp == 0) + { + printf("Job description may be truncated.\n"); + myfflush(); + } + else + *cp = '\0'; + printf("Enter employee salary: "); + scanf("%f", &sal); + myfflush(); + sal_ind = (sal <= 0) ? -1 : 0; // set indicator variable + + /* prepare the INSERT statement */ + checkerr(errhp, OCIStmtPrepare2(svchp, (OCIStmt **) &inserthp, errhp, + insert, (ub4) strlen((char *) insert), NULL, + 0, (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT)); + + /* bind the placeholders in the INSERT statement */ + if ((status = OCIBindByName(inserthp, &bnd1p, errhp, (OraText *) ":ENAME", + -1, (dvoid *) ename, + ENAME_MAXLEN + 1, SQLT_STR, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(inserthp, &bnd2p, errhp, (OraText *) ":JOB", + -1, (dvoid *) job, + JOB_MAXLEN + 1, SQLT_STR, (dvoid *) &job_ind, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(inserthp, &bnd3p, errhp, (OraText *) ":SAL", + -1, (dvoid *) &sal, + (sword) sizeof(sal), SQLT_FLT, (dvoid *) &sal_ind, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(inserthp, &bnd4p, errhp, (OraText *) ":DEPTNO", + -1, (dvoid *) &deptno, + (sword) sizeof(deptno), SQLT_INT, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)) || + (status = OCIBindByName(inserthp, &bnd5p, errhp, (OraText *) ":EMPNO", + -1, (dvoid *) &empno, + (sword) sizeof(empno), SQLT_INT, (dvoid *) 0, + (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT))) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + + /* prepare the "seldept" statement */ + checkerr(errhp, OCIStmtPrepare2(svchp, (OCIStmt **) &stmthp, errhp, + seldept, (ub4) strlen((char *) seldept), + NULL, 0, (ub4) OCI_NTV_SYNTAX, + (ub4) OCI_DEFAULT)); + + /* bind the placeholder in the "seldept" statement */ + if (status = OCIBindByPos(stmthp, &bnd6p, errhp, 1, + (dvoid *) &deptno, (sword) sizeof(deptno), SQLT_INT, + (dvoid *) 0, (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, + OCI_DEFAULT)) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + + /* allocate the dept buffer now that you have length */ + dept = (OraText *) malloc((size_t) (DEPTNAME_MAXLEN + 1) + * sizeof(OraText)); + + /* define the output variable for the select-list. */ + if (status = OCIDefineByPos(stmthp, &defnp, errhp, 1, + (dvoid *) dept, DEPTNAME_MAXLEN + 1, SQLT_STR, + (dvoid *) 0, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + + /* + * Prompt for the employee's department number, and verify that the + * entered department number is valid by fetching the corresponding data + * in the 'dept' table. + */ + do + { + printf("Enter employee dept number: "); + scanf("%d", &deptno); + myfflush(); + /* execute the "seldept" statement */ + status = OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0, + (const OCISnapshot *) NULL, (OCISnapshot *) NULL, + OCI_DEFAULT); + if (status != OCI_SUCCESS && status != OCI_NO_DATA) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + if (status == OCI_NO_DATA) + printf("The department number you entered doesn't exist.\n"); + } while (status == OCI_NO_DATA); + + /* release the select statement handle */ + checkerr(errhp, OCIStmtRelease((OCIStmt *) stmthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT)); + + /* + * Increment empno (which currently holds the highest employee number) + * by 10, and execute the INSERT statement. + */ + empno += 10; + /* execute the INSERT statement */ + status = OCIStmtExecute(svchp, inserthp, errhp, (ub4) 1, (ub4) 0, + (const OCISnapshot *) NULL, (OCISnapshot *) NULL, + OCI_DEFAULT); + if (status != OCI_SUCCESS) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + + /* release the insert statement handle */ + checkerr(errhp, OCIStmtRelease((OCIStmt *) inserthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT)); + + /** Stage 3: Commit the change and then free the handles and buffers */ + + if (status = OCITransCommit(svchp, errhp, 0)) + { + checkerr(errhp, status); + cleanup(svchp, authInfop, inserthp, stmthp); + free_buffers(&ename, &job, &dept); + return OCI_ERROR; + } + printf("\n%s added to the %s department as employee number %d\n", + ename, dept, empno); + free_buffers(&ename, &job, &dept); + } +} + +/* ----------------------------------------------------------------- */ +/* Checks for errors and displays them */ +/* ----------------------------------------------------------------- */ +void checkerr(OCIError *errhp, sword status) +{ + OraText errbuf[512]; + sb4 errcode = 0; + + switch (status) + { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + (void) printf("Error - OCI_SUCCESS_WITH_INFO\n"); + break; + case OCI_NEED_DATA: + (void) printf("Error - OCI_NEED_DATA\n"); + break; + case OCI_NO_DATA: + (void) printf("Error - OCI_NODATA\n"); + break; + case OCI_ERROR: + (void) OCIErrorGet((dvoid *) errhp, (ub4) 1, (OraText *) NULL, &errcode, + errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + (void) printf("Error - %.*s\n", 512, errbuf); + break; + case OCI_INVALID_HANDLE: + (void) printf("Error - OCI_INVALID_HANDLE\n"); + break; + case OCI_STILL_EXECUTING: + (void) printf("Error - OCI_STILL_EXECUTE\n"); + break; + case OCI_CONTINUE: + (void) printf("Error - OCI_CONTINUE\n"); + break; + default: + break; + } +} + +/* ----------------------------------------------------------------- */ +/* Flushes the characters from standard input */ +/* ----------------------------------------------------------------- */ +void myfflush() +{ + eb1 buf[50]; + + fgets((char *) buf, 50, stdin); +} + +/* ----------------------------------------------------------------- */ +/* Free the query-related buffers */ +/* ----------------------------------------------------------------- */ +void free_buffers(OraText **p_ename, OraText **p_job, char **p_dept) { + if (*p_ename) { + free(*p_ename); + *p_ename = NULL; + } + + if (*p_job) { + free(*p_job); + *p_job = NULL; + } + + if (*p_dept) { + free(*p_dept); + *p_dept = NULL; + } +} + +/*---------------------------------------------------------------------*/ +/* Finish demo and clean up */ +/*---------------------------------------------------------------------*/ +sword cleanup(OCISvcCtx *svchp, OCIAuthInfo *authInfop, OCIStmt *inserthp, + OCIStmt *stmthp) +{ + + if (logged_on) { + if ((status = OCISessionRelease(svchp, errhp, NULL, (ub4) 0, + (ub4) OCI_DEFAULT))) + { + printf("FAILED: OCISessionRelease()\n"); + checkerr(errhp, status); + } + } + + /* Release all statement handles */ + if (inserthp) + (void) OCIStmtRelease((OCIStmt *) inserthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT); + if (stmthp) + (void) OCIStmtRelease((OCIStmt *) stmthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT); + if (stmthp) + (void) OCIStmtRelease((OCIStmt *) stmthp, errhp, (OraText *) NULL, + (ub4) 0, (ub4) OCI_DEFAULT); + + /* Free all the OCI handles allocated by OCIHandleAlloc and env handle */ + if (authInfop) + (void) OCIHandleFree((dvoid *) authInfop, (ub4) OCI_HTYPE_AUTHINFO); + if (errhp) + (void) OCIHandleFree((dvoid *) errhp, (ub4) OCI_HTYPE_ERROR); + if (envhp) + (void) OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV); + + OCITerminate(OCI_DEFAULT); + + return OCI_SUCCESS; +}