Table of Contents
Embedded SQL is by no means a new feature — in fact it is so old-fashioned that many people may not know about it at all. Still, it has lots of advantages for client code written in C. So I'd like to give a brief introduction and talk about its benefits and problems.
Typically you use a database from application code by calling the API functions or methods of a library. With embedded SQL, you put plain SQL code (decorated with “EXEC SQL
”) smack in the middle of your program source. To turn that into correct syntax, you have to run a pre-processor on the code that converts the SQL statements into API function calls. Only then you can compile and run the program.
Embedded SQL is mostly used with old-fashioned compiled languages like C, Fortran, Pascal, PL/I or COBOL, but with SQLJ there is also a Java implementation. One reason for its wide adoption (at least in the past) is that it is specified by the SQL standard ISO/IEC 9075-2 (SQL/Foundation). This enables you to write fairly portable applications.
To be able to discuss the features in some more detail, I'll introduce a sample C program using embedded SQL.
The sample program operates on a database table defined like this:
1 2 3 4 |
CREATE TABLE atable( key integer PRIMARY KEY, value character varying(20) ); |
The program is in a file sample.pgc
and looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#include <stdlib.h> #include <stdio.h> /* error handlers for the whole program */ EXEC SQL WHENEVER SQLERROR CALL die(); EXEC SQL WHENEVER NOT FOUND DO BREAK; static void die(void) { /* avoid recursion on error */ EXEC SQL WHENEVER SQLERROR CONTINUE; fprintf( stderr, 'database error %s:n%sn', sqlca.sqlstate, sqlca.sqlerrm.sqlerrmc ); EXEC SQL ROLLBACK; EXEC SQL DISCONNECT; exit(1); /* restore the original handler */ EXEC SQL WHENEVER SQLERROR CALL die(); } int main(int argc, char **argv) { EXEC SQL BEGIN DECLARE SECTION; int v_key, v_val_ind; char v_val[81]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE c CURSOR FOR SELECT key, value FROM atable ORDER BY key; /* connect to the database */ EXEC SQL CONNECT TO tcp:postgresql://localhost:5432/test?application_name=embedded USER laurenz; /* open a cursor */ EXEC SQL OPEN c; /* loop will be left if the cursor is done */ for(;;) { /* get the next result row */ EXEC SQL FETCH NEXT FROM c INTO :v_key, :v_val :v_val_ind; printf( 'key = %d, value = %sn', v_key, v_val_ind ? '(null)' : v_val ); } EXEC SQL CLOSE c; EXEC SQL COMMIT; EXEC SQL DISCONNECT; return 0; } |
Each SQL statement or declaration starts with the magic words EXEC SQL
and ends with a semicolon.
Most statements are translated by the preprocessor right where they are.
1 2 3 |
EXEC SQL CONNECT TO tcp:postgresql://localhost:5432/test?application_name=embedded USER laurenz; |
There are several ways to specify a connect string to the database, and of course the value does not have to be hard-coded. You can also use CONNECT TO DEFAULT
and use libpq
environment variables and a password file to connect.
It is possible to open several database connections at once; then you have to name the connections. Connections are like global variables: they are available everywhere once opened, and you don't have to pass them to functions.
Special “host variables” exchange data between the program and the SQL statements.
They have to be declared in the declare section:
1 2 3 4 |
EXEC SQL BEGIN DECLARE SECTION; int v_key, v_val_ind; char v_val[81]; EXEC SQL END DECLARE SECTION; |
This is just like any other C variable declaration, but only these variables can be used in SQL statements. They have to be prepended with a colon (:
) in SQL statements:
1 |
EXEC SQL FETCH NEXT FROM c INTO :v_key, :v_val :v_val_ind; |
The last variable, v_val_ind
, is an indicator variable: it is set to a negative number to indicate a NULL
value.
One advantage of embedded SQL is that the conversion between PostgreSQL data types and C data types is done automatically. With libpq
, you can either get strings or the internal PostgreSQL representation. There is the additional (non-standard) libpgtypes
library that provides convenient support for data types like timestamp
or numeric
.
Note that v_val
was declared as char[81]
so that it can contain any 20 UTF-8 characters. If you cannot predict the size of the result, you can use descriptor areas to exchange data.
If SQL statements cause an error or warning or return no data, you define the program behavior with WHENEVER
declarations:
1 2 |
EXEC SQL WHENEVER SQLERROR CALL die(); EXEC SQL WHENEVER NOT FOUND DO BREAK; |
These slightly diverge from the SQL standard, which only has CONTINUE
and GO TO label
actions. Also, the standard uses SQLEXCEPTION
instead of SQLERROR
. The DO BREAK
action inserts a break;
statement to break out of the containing loop.
Different from other embedded SQL statements, these directives apply to all embedded SQL statements below them in the source file. They define how the pre-processor translates SQL statements and are independent of the control flow in the program.
To avoid recursion, it is best to set the action to CONTINUE
(ignore) in the exception handler.
Note that this syntax allows to write code without adding error handling to every database call, which is convenient and makes the code concise and readable.
libpq
Some advanced or PostgreSQL-specific features of libpq
are not available in embedded SQL, for example the Large Object API or, more importantly, COPY
.
To use these, you can call the function ECPGget_PGconn(const char *connection_name)
, which returns the underlying libpq
connection object.
The pre-processor is called ecpg
and part of the PostgreSQL core distribution. By default, ecpg
assumes that source files have the extension .pgc
.
To compile the resulting C program, you can use any C compiler, and you have to link with the libecpg
library.
Here is a Makefile
that can be used to build the sample program with PostgreSQL v12 on RedHat-like systems:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CFLAGS ::= $(CFLAGS) -I/usr/pgsql-12/include -g -Wall LDFLAGS ::= $(LDFLAGS) -L/usr/pgsql-12/lib -Wl,-rpath,/usr/pgsql-12/lib LDLIBS ::= $(LDLIBS) -lecpg PROGRAMS = sample .PHONY: all clean %.c: %.pgc ecpg $< all: $(PROGRAMS) clean: rm -f $(PROGRAMS) $(PROGRAMS:%=%.c) $(PROGRAMS:%=%.o) |
We have only scratched the surface, but I hope I could demonstrate that embedded SQL is a convenient way of writing database client code in C.
Here is a list of advantages and disadvantages of embedded SQL compared to directly using the C API of libpq
:
Disadvantages:
libpq
(e.g., no direct COPY
support)Advantages:
You need to load content from reCAPTCHA to submit the form. Please note that doing so will share data with third-party providers.
More InformationYou are currently viewing a placeholder content from Facebook. To access the actual content, click the button below. Please note that doing so will share data with third-party providers.
More InformationYou are currently viewing a placeholder content from X. To access the actual content, click the button below. Please note that doing so will share data with third-party providers.
More Information
Leave a Reply