You are on page 1of 15

Why Embedded SQL?

Embedded SQL has some small advantages over other ways to handle SQL queries. It
takes care of all the tedious moving of information to and from variables in your C
program. Many RDBMS packages support this embedded language.
There is an ANSI-standard describing how the embedded language should work. Most
embedded SQL preprocessors I have seen and heard of make extensions so it is difficult
to obtain portability between them anyway. I have not read the standard but I hope that
my implementation does not deviate too much and that it would be possible to port
programs with embedded SQL written for other RDBMS packages to Postgres and thus
promoting the spirit of free software.

The Concept
You write your program in C with some special SQL things. For declaring variables that
can be used in SQL statements you need to put them in a special declare section. You use
a special syntax for the SQL queries.
Before compiling you run the file through the embedded SQL C preprocessor and it
converts the SQL statements you used to function calls with the variables used as
arguments. Both variables that are used as input to the SQL statements and variables that
will contain the result are passed.
Then you compile and at link time you link with a special library that contains the
functions used. These functions (actually it is mostly one single function) fetches the
information from the arguments, performs the SQL query using the ordinary interface
(libpq) and puts back the result in the arguments dedicated for output.
Then you run your program and when the control arrives to the SQL statement the SQL
statement is performed against the database and you can continue with the result.

A script file installpubs2 has been used to create the database. You can download this file,
and modify it for your purposes. Scripts like this can be executed with
isql -Uuser -Ppassword < installpubs2.

example1.cp and example2.cp are two simple example programs that show how
to access a database

example3.cp shows different ways to process multiple row queries.

They include the file sybsqlex.h which defines the symbols USER and
PASSWORD. in this case, the user is "sa" and the password is empty, "".
You can download the files and compile them using the unix command make.
(You have to download the file makefile which contains all the rules for
compiling these programs.) /*

makefile
# Make the Embedded SQL/C sample programs.
#
# you can just copy this file into your program directory
# by the name "makefile" or "Makefile"
# and replace the file names (example1, exampl2 ...) with your own
# type "make example1" to compile
#
MAKE = make CC="$(CC)" AS="$(AS)" LD="$(LD)" AR="$(AR)" CPP="$(CPP)" \
SYBPLATFORM="$(SYBPLATFORM)"
#
SHELL
= /bin/sh
HEADERS
= ./sybsqlex.h
TARGET1
= example1
TARGET2
= example2
TARGET3
= example3
INCLUDE
= -I. -I$${SYBASE}/include
LIBFLAGS = -L$${SYBASE}/lib
SYBLIBDIR = $${SYBASE}/lib/
CFLAGS = $(INCLUDE) $(LIBFLAGS)
PRECOMP = $${SYBASE}/bin/cpre
LINK.c = cc $(CFLAGS)
SYBLIBS = -lct -lcs -lcomn -ltcl -lintl
MATHLIBS = -lm
AXPOSFLIBS = \
$(SYBLIBS) \
$(SYBLIBDIR)libsdna.a
# platform specific stuff
KRC_FLAGS = -C KR_C
SCKLIBS = $(SYBLIBS) \
$(SYBLIBDIR)libinsck.a \
$(MATHLIBS)
TLILIBS = $(SYBLIBS) \
$(SYBLIBDIR)libtli.a \
-lnsl \
$(MATHLIBS)
ESQLIBS = $(SCKLIBS)
PRECOMPFLAGS = $(KRC_FLAGS)
all: example1 example2 example3
example1: $(HEADERS) example1.c
$(LINK.c) -o $@ $${SYBASE}/include/sybesql.c example1.c ${ESQLIBS}
example1.c: example1.cp
$(PRECOMP) ${PRECOMPFLAGS} example1.cp
exampl2: $(HEADERS) example2.c
$(LINK.c) -o $@ $${SYBASE}/include/sybesql.c example2.c ${ESQLIBS}
example2.c: example2.cp
$(PRECOMP) ${PRECOMPFLAGS} example2.cp

example3: $(HEADERS) example3.c


$(LINK.c) -o $@ $${SYBASE}/include/sybesql.c example3.c ${ESQLIBS}
example3.c: example3.cp
$(PRECOMP) ${PRECOMPFLAGS} example3.cp
sybsqlex.h
/************************************************************
*
*
* sybsqlex.h header file for Embedded SQL/C examples *
*
*
************************************************************/
#define USER
#define PASSWORD

"sa"
""

#define ERREXIT -1
#define STDEXIT 0

**
example1.cp
**
**
This example is an interactive query program that
**
guides the user through a series of prompts to select
**
a title from the titles table in the pubs2 database.
**
It uses cursors extensively to guide the query.
*/
#include
#include "sybsqlex.h"
/* Declare the SQLCA. */
EXEC SQL INCLUDE SQLCA;
/*
** These tokens must be declared in a declare section
** because they are used in declare sections below.
*/
EXEC SQL BEGIN DECLARE SECTION;
#define TYPESIZE
13
#define TIDSIZE
6
EXEC SQL END DECLARE SECTION;
#define

EOLN

'\0'

/*
** Forward declarations of the error and message handlers and
** other subroutines called from main().
*/
void
error_handler();
void
warning_handler();
int
get_type();
int
get_titleid();
void
show_book();

#define ISWORDSPACE(c) (c == ' ' || c == '\t')


main()
{
EXEC SQL BEGIN DECLARE SECTION;
/* storage for login name and password. */
char
username[30];
char
password[30];
EXEC SQL END DECLARE SECTION;
char
char

u_type[TYPESIZE+1];
u_titleid[TIDSIZE+1];

EXEC SQL WHENEVER SQLERROR CALL error_handler();


EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND CONTINUE;
/*
** Copy the user
** the variables
*/
strcpy(username,
strcpy(password,

name and password defined in sybsqlex.h to


declared for them in the declare section.
USER);
PASSWORD);

EXEC SQL CONNECT :username IDENTIFIED BY :password;


EXEC SQL USE pubs2;
/*
** This loop controls the query.
*/
while (get_type(u_type))
{
while (get_titleid(u_titleid, u_type))
{
show_book(u_titleid);
}
}
EXEC SQL DISCONNECT DEFAULT;
exit (STDEXIT);
}
/*
** int get_type(type)
**
**
Displays a list of book types from the titles
**
table and lets the user enter one.
*/
int get_type(type)
char *type;
{
/*
** Declare a character string to hold types fetched
** from the titles table.

*/
EXEC SQL BEGIN DECLARE SECTION;
char
a_type[TYPESIZE+1];
EXEC SQL END DECLARE SECTION;
/* A buffer to hold user's gets() entry. */
char
buf[128];
/* Declare a cursor to select a list of book types. */
EXEC SQL DECLARE typelist CURSOR FOR
SELECT DISTINCT type FROM titles;
/* Open the cursor. */
EXEC SQL OPEN typelist;
/*
** List the book types on the screen. Break out of the
** loop when a FETCH sets sqlca.sqlcode to 100.
*/
printf("\n\nSelect a Book Type:\n\n");
for (;;)
{
EXEC SQL FETCH typelist INTO :a_type;
if (sqlca.sqlcode == 100)
break;
printf("\t%s\n", a_type);
}
/*
** Prompt the user for the book type. We're using gets()
** for the input function. Since gets() doesn't check the
** length of entry, we have the user enter into a large
** temporary buffer, then copy the correct number of bytes
** into the variable passed from main().
*/
printf("\nbook type? ");
gets(buf);
type[TYPESIZE] = EOLN;
strncpy(type, buf, TYPESIZE);
/*
** Close the cursor and return the length of the type string.
*/
EXEC SQL CLOSE typelist;
return (strlen(type));
}
/*
**
Displays a list of title id's and titles that are
**
from the selected type. Lets the user enter a title id.
**
Copies the entry into tid, and returns the number of
**
characters entered.
*/
int get_titleid(tid, type)
char *tid;
char *type;
{
/*

** Declare destination variables for the fetch.


*/
EXEC SQL BEGIN DECLARE SECTION;
char
b_titleid[TIDSIZE+1];
char
b_title[65];
/*
** We need to make a copy of the type argument because
** we can't put the formal argument in a declare section.
*/
char
b_type[TYPESIZE+1];
EXEC SQL END DECLARE SECTION;
/* A buffer to hold keyboard input. */
char
buf[128];
/* Copy the type into b_type so we can use it to declare
** the cursor.
*/
strcpy(b_type, type);
/* This cursor selects title_id and title to display
** a list for the user. The substring() function truncates
** titles, if necessary, so they'll fit on an 80 column
** display.
*/
EXEC SQL DECLARE titlelist CURSOR FOR
SELECT title_id, substring(title,1,64)
FROM
titles
WHERE
type LIKE :b_type;
/* Set b_titleid to null so we'll know if no titles
** were selected for the specified type.
*/
b_titleid[0] = EOLN;
/* Open the cursor. */
EXEC SQL OPEN titlelist;
/* Display the list of titles */
printf("\n\nSelect a title:\n\n");
for (;;)
{
EXEC SQL FETCH titlelist INTO :b_titleid, :b_title;
if (sqlca.sqlcode == 100)
break;
printf("
%-8s %s\n", b_titleid, b_title);
}
/* If b_titleid is still null, no titles were found, so
** we won't ask the user to select one.
*/
if (!strlen(b_titleid))
{
printf("\nThere are no '%s' titles.\n", b_type);
tid[0] = EOLN;
}
else

/* We use gets() as the input function. A title_id is


** only 6 bytes long, and gets() doesn't check the
** length of the entry. Therefore, we'll gets() into
** a longer buffer, and copy 6 bytes into the tid
** variable.
*/
printf("\ntitle ID? ");
gets(buf);
tid[6] = EOLN;
strncpy(tid, buf, 6);

/*
** Close the cursor and return the number of characters entered.
*/
EXEC SQL CLOSE titlelist;
return (strlen(tid));
}
/*
** void show_book(typeid)
**
**
Displays information about the book with the specified
**
title id. A book can have coauthors, so we need a cursor
**
to list the authors.
*/
void show_book(titleid)
char *titleid;
{
EXEC SQL BEGIN DECLARE SECTION;
/* Destination variables for fetches. */
char
m_titleid[7];
char
m_title[65];
char
m_pubname[41];
char
m_pubcity[21];
char
m_pubstate[3];
char
m_notes[201];
char
m_author[62];
/* An indicator variable for notes. */
short
i_notes;
EXEC SQL END DECLARE SECTION;
/* These variables are used in the word-wrapping routine
** that display the notes.
*/
char
*p, noteline[65];
int
i, notelen;
/* We need a local copy of the titleid to use in the
** fetch.
*/
strcpy(m_titleid, titleid);
/* Fetch the information about the title and publisher. A
** cursor is not needed because the title id is unique.
** No more than one row can be returned.

*/
EXEC SQL
SELECT substring(title, 1, 64), notes, pub_name, city, state
INTO
:m_title, :m_notes:i_notes, :m_pubname, :m_pubcity,
:m_pubstate
FROM
titles, publishers
WHERE
titles.pub_id = publishers.pub_id
AND
title_id = :m_titleid;
/* sqlca.sqlcode is set to 100 if the title_id isn't found. */
if (sqlca.sqlcode == 100)
{
printf("\n\t** Can't find title '%s'.\n", m_titleid);
return;
}
/* Display the title and publisher's name and address. */
printf("\n\n%s", m_title);
printf("\n\nPub:\t%s", m_pubname);
printf("\n\t%s %s", m_pubcity, m_pubstate);
/* There can be more than one author, so we need a cursor
** to list them. We can let SQL Server combine the first
** and last names so that only one column is returned.
*/
EXEC SQL DECLARE authors CURSOR FOR
SELECT au_fname + ' ' + au_lname
FROM
authors, titleauthor
WHERE
authors.au_id = titleauthor.au_id
AND
title_id = :m_titleid;
/* Open the cursor. */
EXEC SQL OPEN authors;
/* Each row fetched is an additional coauthor. */
printf("\n\nBy:");
for(;;)
{
EXEC SQL FETCH authors INTO :m_author;
if (sqlca.sqlcode == 100)
break;
printf("\t%s\n", m_author);
}
/* Close the cursor. */
EXEC SQL CLOSE authors;
/*
**
**
*/
if
{

This routine prints the notes column on multiple lines


with word wrapping. Nothing is printed if the indicator
variable was set to -1.
(i_notes != -1)
register

char

*q;

printf("\nNotes:");
notelen = strlen(m_notes);

p = m_notes;
while (notelen > 64)
{
noteline[64] = EOLN;
strncpy(noteline, p, 64);
for (i = strlen(noteline), q = noteline + i;
i && ! (*q == ' ' || *q == '\t');
i--, q--);
noteline[i] = EOLN;
notelen -= strlen(noteline);
printf("\t%s\n", noteline);
p += (strlen(noteline)+1);
}
if (notelen)
printf("\t%s\n", p);

/* Pause before returning so the user can read the display. */


printf("\npress return...");
getchar();
return;

/*
** void error_handler()
**
**
Displays error codes and numbers from the SQLCA and exits with
**
an ERREXIT status.
*/
void error_handler()
{
fprintf(stderr, "\n** SQLCODE=(%d)", sqlca.sqlcode);
if (sqlca.sqlerrm.sqlerrml)
{
fprintf(stderr, "\n** SQL Server Error ");
fprintf(stderr, "\n** %s", sqlca.sqlerrm.sqlerrmc);
}
fprintf(stderr, "\n\n");
exit(ERREXIT);
}
/*
** void warning_handler()
**
**
Displays warning messages.
*/
void warning_handler()
{
if (sqlca.sqlwarn[1] == 'W')
{
fprintf(stderr,
"\n** Data truncated.\n");
}

if (sqlca.sqlwarn[3] == 'W')
{
fprintf(stderr,
"\n** Insufficient host variables to store
results.\n");
}
return;
}

/*
**
example2.cp
**
**
This example displays and allows user to edit
**
rows in the authors table in the pubs2 database.
*/
#include
#include "sybsqlex.h"
/* Declare the SQLCA. */
EXEC SQL INCLUDE SQLCA;
#define
int

EOLN

'\0'

MoreRows = 1;

/*
** Forward declarations of the error and message handlers and
** other subroutines.
*/
void
error_handler();
void
warning_handler();
void
notfound_handler();
void
put_field();
void
get_field();
main()
{

EXEC SQL BEGIN DECLARE SECTION;


/* Storage for login name and password. */
char
username[30];
char
password[30];
/* Storage for columns in the authors table. */
char
m_au_id[13];
char
m_au_lname[41];
char
m_au_fname[21];
char
m_phone[13];
char
m_address[41];
char
m_city[21];
char
m_state[3];
char
m_zip[11];

EXEC SQL END DECLARE SECTION;


/* buffer for responses */
char
buf[10];
EXEC SQL WHENEVER SQLERROR CALL error_handler();
EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND CALL notfound_handler();
/*
** Copy the user
** the variables
*/
strcpy(username,
strcpy(password,
strcpy(buf,"");

name and password defined in sybsqlex.h to


declared for them in the declare section.
USER);
PASSWORD);

EXEC SQL CONNECT :username IDENTIFIED BY :password;


EXEC SQL USE pubs2;
EXEC SQL
DECLARE authors CURSOR FOR
SELECT au_id, au_lname, au_fname,
phone, address, city, state,
postalcode
FROM
authors;
EXEC SQL OPEN authors;
/*
** This loop controls the entry.
*/
while ( MoreRows )
{
if (*buf == 'Q' || *buf == 'q')
break;
EXEC SQL
FETCH authors INTO
:m_au_id, :m_au_lname, :m_au_fname,
:m_phone, :m_address, :m_city,
:m_state, :m_zip;
if ( MoreRows )
{
put_field("au_id", m_au_id);
put_field("au_lname", m_au_lname);
put_field("au_fname", m_au_fname);
put_field("phone", m_phone);
put_field("address", m_address);
put_field("city", m_city);
put_field("state", m_state);
put_field("zip", m_zip);

printf("\n\n[E]dit, [N]ext, [Q]uit ");


gets(buf);
switch (*buf)
{
case 'e':
case 'E':
put_field("au_id", m_au_id);
get_field("au_lname", m_au_lname);
get_field("au_fname", m_au_fname);
get_field("phone", m_phone);
get_field("address", m_address);
get_field("city", m_city);
get_field("state", m_state);
get_field("zip", m_zip);
printf("\n\n");
put_field("au_id", m_au_id);
put_field("au_lname", m_au_lname);
put_field("au_fname", m_au_fname);
put_field("phone", m_phone);
put_field("address", m_address);
put_field("city", m_city);
put_field("state", m_state);
put_field("zip", m_zip);
printf("\n\nUpdate? ");
gets(buf);
if (*buf == 'Y' || *buf == 'y')
{
EXEC SQL
UPDATE authors SET
au_lname = :m_au_lname,
au_fname = :m_au_fname,
phone = :m_phone,
address = :m_address,
city = :m_city,
state = :m_state,
postalcode = :m_zip
WHERE CURRENT OF authors;
}
printf("\n");
break;
case 'n':
case 'N':
break;
}
}

EXEC SQL DISCONNECT ALL;


printf("\nbye\n");
exit (STDEXIT);
}
void put_field(label, value)

char *label;
char *value;
{
printf("\n\t%-9s: %s", label, value);
}
void get_field(label, value)
char *label;
char *value;
{
char buf[128];

printf("\n\t%-9s: %s", label, value);


printf("\n\t
: ");
gets(buf);
if (*buf)
{
strcpy(value, buf);
}

/*
** void error_handler()
**
**
Displays error codes and numbers from the SQLCA and exits with
**
an ERREXIT status.
*/
void error_handler()
{
fprintf(stderr, "\n** SQLCODE=(%d)", sqlca.sqlcode);
if (sqlca.sqlerrm.sqlerrml)
{
fprintf(stderr, "\n** SQL Server Error ");
fprintf(stderr, "\n** %s", sqlca.sqlerrm.sqlerrmc);
}
fprintf(stderr, "\n\n");
}

exit(ERREXIT);

/*
** void warning_handler()
**
**
Displays warning messages.
*/
void warning_handler()
{
if (sqlca.sqlwarn[1] == 'W')
{
fprintf(stderr,
"\n** Data truncated.\n");
}

if (sqlca.sqlwarn[3] == 'W')
{
fprintf(stderr,
"\n** Insufficient host variables to store
results.\n");
}
return;
}
/*
** void notfound_handler()
**
**
Displays notfound messages.
*/
void notfound_handler()
{
if (sqlca.sqlcode == 100)
{
MoreRows = 0;
}
return;
}
/
************************************************************************
**
**
example3.cp
**
**
This example demonstrates the use of queries that
**
will return multiple rows in the authors table in the pubs2
database.
************************************************************************
*****/
#include
#include
#include "sybsqlex.h"
/* Declare the SQLCA. */
EXEC SQL INCLUDE SQLCA;
#define
int

EOLN

'\0'

MoreRows = 1;

/*
** Forward declarations of the error and message handlers and
** other subroutines.
*/
void
error_handler();
void
warning_handler();
void
notfound_handler();
void
put_field();
void
get_field();
/*** here's a little function that removes blanks
from the end of the string ***/

char *trim(char *c) {


int l;
l = strlen(c);
while (l>0 && c[l-1]==' ') {
c[l--] = '\0';
}
return c;
}
int min(int a, int b) {

return a