September 1992 (Revised May 1997 & March 1998)
Version III
Contents:
Embedded SQL is an embedding of the database language SQL into a procedural programming language (C in this document) referred to as the host language.
The syntax of a SQL database statement embedded in any host language is as follows:
(margin) EXEC SQL SQL_STATEMENT (terminator)
In C there is no specified margin, the terminator is ";".
Below is given a (very rough) outline of an example embedded SQL program which features the elements common to most embedded SQL applications. The program retrieves an employee's name where emp_number is equal to 10001 from a table called employees contained in a database called frans and then prints them.
/* Begin program */ EXEC SQL INCLUDE SQLCA; EXEC SQL BEGIN DECLARE SECTION host_name character_string(20) host_emp_number integer EXEC SQL END DECLARE SECTION EXEC SQL WHENEVER SQLERROR STOP EXEC SQL CONNECT frans /* Formulate query, something like: */ EXEC SQL SELECT name emp_number INTO host_name host_emp_number FROM employees WHERE emp_number = 10001 /* Print host_name and host_emp_number */ EXEC SQL DISCONNECT /* End program */
All statements that begin EXEC SQL are embedded SQL database statements. The function of some of the above statements are obvious others require some additional explanation as follows:
We are not limited to SELECT statements. We can embed any statement available in standard SQL. For example we can create a table as follows:
EXEC SQL CREATE TABLE employees (name c20, emp_number int)
This creates a table called employees that has two fields, name a character field with 20 characters and emp_number an integer field.
One of the results of issuing an INCLUDE SQLCA statement is the declaration of the SQLCA structure, which can be used for error handling. This structure is as follows:
typedef struct { char sqlcaid[8]; long sqlcabc; long sqlcode; struct { short sqlerrml; char sqlerrmc [70]; } sqlerrm; struct { char sqlwarn0; char sqlwarn1; char sqlwarn2; char sqlwarn3; char sqlwarn4; char sqlwarn5; char sqlwarn6; char sqlwarn7; } sqlwarn char sqlext[8]; } IISQLCA; static IISQLCA sqlca = {0};
Many of the variables defined by the IISQLCA structure are not used. However the following should be noted:
Error handling with SQCLA can be done implicitly by using WHENEVER statements, or explicitly by checking the contents of the SQLCA fields sqlcode, sqlerrml and sqlwarn[3].
DCLGEN is a structure-generating utility that maps the columns of a database table into a structure that you can include in a variable declaration. DCLGEN is invoked thus:
dclgen language dbname tablename filename structurename
where:
Thus:
dclgen c frans employees employees.dcl employees_struct
If you invoke this you should see output of the form:
INGRES DCLGEN -- Copyright (c) 1985, 1993 Ingres Corporation DCLGEN: Working . . . DCLGEN: Describing table 'employees' . . .
The file employees.dcl created by this command will contain a comment and two statements as follows:
/* Description of table employees from database frans */ EXEC SQL DECLARE employees TABLE (emp_number integer, name c20); struct employees_struct_ { long emp_number; char name[21]; } employees_struct;
Note that the structure tag is the structure name followed by an underscore character "_". Note also that the length of the character buffers has been increased by 1 to accommodate the C null terminator. To incorporate this file into a C program it must be compiled into c code using the esqlc compiler. Thus:
esqlc employees.dcl
This will produce a file employees.c as follows:
/* Description of table employees from database frans */ /* # line 6 "employees.dcl" */ /* host code */ struct employees_struct_ { long emp_number; char name[21]; } employees_struct;
No specific reference to this file will be made in the application embedded sql in C source code, however, it is required when this source code compiled.
We have noted that data can be retrieved from an INGRES database using the select SQL statement. However the select statement is not in it self sufficient to perform this task in embedded SQL. This is because an application needs to process each record, or row, individually. The syntax of the select statement offers no way of doing this in an embedded setting. To do this embedded SQL uses a cursor. This can be thought of as a row marker. Special cursor-oriented statements are available. What ever the case a cursor must be declared before it can be used. For example:
EXEC SQL DECLARE empcsr CURSOR FOR SELECT name, emp_number FROM employees WHERE emp_number = 10001;
Once declared we must "open" the cursor:
EXEC SQL OPEN empcsr;
The data retrieved using a select statement can be assigned to the host variables, a single row at a time, with the FETCH statement. For example:
EXEC SQL FETCH empcsr INTO :host_name, :host_emp_number;
where empcsr is the cursor name (declared earlier), host_name and host_emp_number are host variable names, note the use of the ":" symbol preceding each host name. The statement performs two functions. First it moves the cursor to the next row in the table, which becomes the current row. Then it loads the values indicated in the SELECT part of the DECLARE CURSOR statement into the host variables. The data can then be processed as required.
When a cursor is no longer required it should be "closed":
EXEC SQL CLOSE empcsr
WHENEVER provides a convenient method for handling error and exception conditions arising from embedded SQL database statements. It stipulates that some action occurs when the program attains a specific condition. The following conditions may be tested for:
Possible actions may be one of the following:
Below the example outline embedded SQL program given in Section 2.1 has been extended to illustrate cursor processing (and to make it more c like).
/* Begin program */ EXEC SQL INCLUDE SQLCA; EXEC SQL BEGIN DECLARE SECTION; host_name character_string(21); host_emp_number int; EXEC SQL END DECLARE SECTION ; EXEC SQL WHENEVER SQLERROR STOP; EXEC SQL CONNECT frans; /* Define and open cursor */ EXEC SQL DECLARE empcsr CURSOR FOR SELECT name, emp_number FROM employees WHERE emp_number = 10001; EXEC SQL OPEN empcsr; EXEC SQL WHENEVER NOT FOUND GOTO close_empcsr; /* Loop indefinitely (the WHENEVER NOT FOUND statement will cause the loop to be terminated when the end of the table is encountered. */ /* Commence loop */ EXEC SQL FETCH INTO :host_name, :host_emp_number; /* Print host_name and host_emp_number */ /* End loop */ /* Close and disconnect */ close_empcsr: EXEC SQL CLOSE close_empcsr; EXEC SQL DISCONNECT /* End program */
To write an executable embedded SQL in C program the following sequence of steps should be followed:
dclgen language dbname tablename filname1 structurename
esqlc filename1.dcl
esqlc filename.scThis creates a file filename.c which can be examined.
cc -Aa -o filename filename.c /cs/apps3/ingres/ingres/lib/libingres.a -lm -lcor using the shorthand version:
ingcc -o filename filename.cThis then creates a file filename which is the final executable.
We will now put all of the above together to produce a program, tempest, that retrieves an employee's name where emp_number is equal to 10001 from a table called employees contained in a database called frans and then prints the result. The .sc code is as follows:
/* ---------------------------------------- */ /* */ /* EMBEDDED SQL IN C TEST PROGRAM */ /* */ /* Frans Coenen */ /* (20 March, 1998) */ /* */ /* ---------------------------------------- */ /* To compile (assuming an appropriate .dcl file exists): "esqlc tempest.sc" and then "cc -Aa -o tempest tempest.c /cs/apps3/ingres/ingres/lib/libingres.a -lm -lc" */ /* Include statements. */ #includeEXEC SQL INCLUDE SQLCA; /* Function prototypes */ void cleanUp(void); /* ------ MAIN ------ */ /* Top level function */ void main(void) { EXEC SQL BEGIN DECLARE SECTION; char host_name[21]; int host_emp_number; EXEC SQL INCLUDE "employees.dcl"; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE empcsr CURSOR FOR SELECT name, emp_number FROM employees WHERE emp_number = 10001; /* An error when opening the frans database will cause the error to be printed and the program to be aborted */ EXEC SQL WHENEVER SQLERROR STOP; EXEC SQL CONNECT frans; /* Errors from here on will cause the program to clean up */ EXEC SQL WHENEVER SQLERROR CALL cleanUp; EXEC SQL OPEN empcsr; printf("Some values from the /"employees/" table /n"); /* When ever no more rows are fetched, close the cursor */ EXEC SQL WHENEVER NOT FOUND GOTO close_empcsr; /* The last executable SQL statement was OPEN so we know that the value of "sqlcode" cannot be SQLERROR or NOT FOUND */ while(sqlca.sqlcode == 0) { /* Loop is broken by NOT FOUND */ EXEC SQL FETCH empcsr INTO :host_name, :host_emp_number; /* This "printf" does not execute after the previous FETCH returns the NOT FOUND condition */ printf("%s %d/n",host_name,host_emp_number); } /* From this point onwards the program ignore all errors. Also turn off the NOT FOUND condition for consistency */ EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL WHENEVER NOT FOUND CONTINUE; close_empcsr: EXEC SQL CLOSE empcsr; EXEC SQL DISCONNECT; } /* ------ CLEAN UP ------ */ /* Error handling procedure (print error and disconnect) */ void cleanUp(void) { EXEC SQL BEGIN DECLARE SECTION; char errmsg[10]; EXEC SQL END DECLARE SECTION; EXEC SQL INQUIRE_SQL (:errmsg = ERRORTEXT); /* Get error message. */ /* Alternatively to get only the error number EXEC SQL COPY SQLERROR INTO :errmsg WITH 256; */ printf("Aborting because of error: /n%s/n",errmsg); EXEC SQL DISCONNECT; exit(-1); }
Remember that a label in C begins with an alphabetic character or an underscore, it must be the first word on the line and must be terminated with a ":", thus close_empcsr:.
Alternatively. to select all records in the employees table we would define the cursor as follows:
EXEC SQL DECLARE empcsr CURSOR FOR SELECT name, emp_number FROM employees;
To list all fields in a table which includes empty fields will result in an error (sqlcode = -40202) when ever an empty field is encountered. We can test for this and over ride the error. However the result is that the entry from the last occasion when an instantiated value for the field was encountered is printed. This can not be tested for as entries for fields may correctly be repeated. Further there is no indication of which fields are empty. The error code only indicates that one or more empty field in a row have been encountered and not the position of the field or fields within the row. At present the only solution would appear to be to include a null string in empty character fields and a value of zero in empty numeric fields. This will be quite acceptable in the database under discussion here, however in some applications a numeric value of zero may be a legitimate value. It is hoped that a solution will be found in the near future!
Created and maintained by Frans Coenen. Last updated 10 January 2002