continua (next page) main index Devo innanzitutto premettere che io sono abituato ad usare PostgreSQL ed ecpg anziché i funzionalmente equivalenti Oracle e Pro* C.

Tra PostgreSQL e Oracle ci sono alcune piccole differenze; la più rognosa è il fatto che funzione per recuperare la data attuale si chiama SYSDATE in Oracle SQL e CURRENT_TIMESTAMP in PostgreSQL. Un'altra che darà sicuramente filo da torcere a chi si cimenta con le query "di prova" è il fatto che con Oracle si può sapere in quale riga della query stiamo correntemente lavorando (WHERE ROWNUM < 100) mentre in PostgreSQL dobbiamo limitarla fin dall'inizio (LIMIT 100).

Il cosiddetto Pro* C di Oracle (linguaggio C con embedding di comandi SQL) in versione PostgreSQL si chiama ecpg (è bello avere acronimi così complicati: così, quando cerchi su internet, lo trovi facilmente).

Il preprocessor che trasforma codice C+SQL in un sorgente C si chiama per l'appunto ecpg e fa parte del pacchetto PostgreSQL. Dato un file sorgente in input (con una qualsiasi estensione), interpreta le istruzioni EXEC SQL (e protesta in caso di errore di sintassi SQL) e crea un file .c compilabile col solito gcc (linkando la libreria ecpg necessaria per connettersi al PostgreSQL master).

In poche parole, dato un sorgente Pro* C con un minimo di modifiche per "postgresqlizzarlo", si compila semplicemente con queste due istruzioni:
ecpg prova.pc
gcc -I /usr/include/pgsql prova.c -lecpg
(gcc includerà gli header-files in pgsql, compilerà prova.c e linkerà la libreria ecpg: se in fase di linking viene fuori un errore, significa che avete dimenticato di installare il pacchetto di development per PostgreSQL).

Ecco il file sorgente ecpg commentato riga per riga.


#include <stdio.h>
#include <string.h>

EXEC SQL INCLUDE sqlca.h;

L'include file sqlca.h va scritto così, in lettere minuscole e con l'estensione, in barba alla brutta abitudine dei programmatori Pro* C di scrivere tutto in maiuscolo e senza estensione (cioè EXEC SQL INCLUDE SQLCA; - come se ogni sistema operativo non facesse distinzione maiuscole/minuscole sui file name).

Io aggiungo spesso questa macro, che manda in output un consistente numero di informazioni tirate fuori dalla struttura sqlca definita dalla libreria PostgreSQL similmente a quanto avviene in Oracle:
#define sql_error() printf("!-- SQL: E%d: %s -- rows=%d, warning(%c)\n", \
          sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc, sqlca.sqlerrd[2],       \
            sqlca.sqlwarn[0]);

Ogni volta che si esegue un comando SQL con EXEC SQL vengono riempiti i campi della sqlca (pubblica) che poi uno dovrebbe andare ad estrarsi e mostrare a video o nel log... e con questa macro si fa più presto (se ti piace e cominci ad usarla nei tuoi software commerciali allora mi devi una birra!!).

Passiamo al main:


int main(int argc, char **argv)
{
  if(argc != 4)   /* controllo argomenti da command-line */
  {
    fprintf(stderr, "\nuso:  %s nomedatabase utente password\n", argv[0]);
    return 1;
  }

  /* controllo lunghezze massime degli argomenti da command-line: */
  if(strlen(argv[1])>20 || strlen(argv[2])>20 || strlen(argv[3])>20)
  {
    fprintf(stderr,
      "\n...i parametri non devono essere piu' lunghi di 20 caratteri\n");
    return 2;
  }

  /* questo blocco definisce dei dati "visibili" sia da C che da SQL */
  EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR user[21];
    VARCHAR passw[21];
    VARCHAR dbase[21];
    VARCHAR data[100];
  EXEC SQL END DECLARE SECTION;

  /* qui inizializzo rispettando il formato del tipo VARCHAR */
  strcpy(dbase.arr, argv[1]);   dbase.len = strlen(argv[1]);
  strcpy(user.arr,  argv[2]);   user.len  = strlen(argv[2]);
  strcpy(passw.arr, argv[3]);   passw.len = strlen(argv[3]);

Caso tipico: invocando il programma occorre passargli i nomi del database e dell'utente e password da utilizzare. Questi vanno passati all'ambiente SQL attraverso "variabili host" di tipo VARCHAR (una struttura consistente in uno short contenente la lunghezza usata e in un array di caratteri).

Mi dispiace, ma la procedura è proprio quella - non ho inventato io l'interfaccia tra C e SQL (altrimenti l'avrei creata molto più pulita). In compenso il pezzetto di codice sopraindicato funziona allo stesso modo (senza modifiche) sia per PostgreSQL che per Oracle.

Ecco ora un terrificante comando (tenetevi forte):
  EXEC SQL WHENEVER SQLERROR GOTO gest_err;

Come vedete, per risparmiarci i soliti controlli dopo ogni comando, conviene di più dare una goto a una complicata gestione degli errori che vedrete più avanti in fondo a questa pagina (i sacri puristi della programmazione, quelli che come me non hanno mai avuto bisogno di usare un goto in un sorgente C, non storcano il naso: dopotutto anche nella documentazione di Pro*C si usano quei maledetti goto).

Il preprocessor (ecpg di PostgreSQL o proc di Oracle) si preoccuperà di allungare ad ogni sequenza EXEC SQL il controllo degli errori (e, neanche a dirlo, la sintassi è la stessa tanto per Pro* C che per ecpg).

Ed ecco finalmente la routine di prova: ci si connette al database remoto (più o meno remoto), si crea una nuova tabella provaoggi con un solo campo caratteri oggi lungo fino a 19 caratteri:


  printf("connessione... ");   fflush(stdout);

  EXEC SQL CONNECT TO :dbase USER :user IDENTIFIED BY :passw;
  printf("OK!\n");

  EXEC SQL CREATE TABLE provaoggi ( oggi VARCHAR(19) );
  printf("creata la tabella\n");

Notare come le variabili host dbase e user siano inizializzabili da C ma utilizzabili all'esterno (SQL); notare inoltre che in caso di errore ci sarà un salto alla sezione degli errori e quindi la printf successiva non verrà eseguita.

Piccola parentesi: sul vecchio Oracle 8 la connessione si faceva così:
EXEC SQL CONNECT :user IDENTIFIED BY :passw AT :dbase;

A questo punto inserisco un record contenente la data/ora attuale, e aggiorno le modifiche nel database:


  EXEC SQL INSERT INTO provaoggi VALUES ( SUBSTRING(CURRENT_TIMESTAMP FROM 1 FOR 19) );
  printf("inserito un record con la data attuale\n");

  EXEC SQL COMMIT;
  printf("salvate le modifiche (inserimento dati)\n");

Da notare che quella che in Oracle si chiama SYSDATE qui in PostgreSQL si chiama CURRENT_TIMESTAMP - da questa, che è a formato fisso, estraggo solo i primi 19 caratteri (dal primo, per una lunghezza di 19: questa la sintassi di SUBSTRING - diversa dalla sintassi classica della SUBSTR di Oracle). Per il resto, la sintassi è la stessa tanto in Oracle che in PostgreSQL.

Passiamo ora a recuperare il campo appena inserito dalla tabella e a fare un po' di pulizia prima di chiudere la sessione:


  EXEC SQL SELECT * INTO :data FROM provaoggi;
  printf("fine della select --> data: [%*s]\n", data.len, data.arr);

Metti il tutto nella variabile host data (dichiarata nella sezione EXEC SQL DECLARE poco fa) e poi mandalo in output con una printf - da notare la sintassi con l'asterisco nel formato... il "%s" si aspetterebbe una stringa terminata dal canonico '\0' mentre il "%*s" si aspetta prima un intero (il data.len è uno short ma passato nella lista dei parametri diventa un intero) che definisce la lunghezza massima da usare per la stringa e poi la stringa stessa. Dunque andrà a prendere i primi 19 caratteri (poiché data.len era 19) e si fermerà.

Restano da fare solo le pulizie di primavera:


  EXEC SQL DROP TABLE provaoggi;
  printf("eliminata la tabella\n");

  EXEC SQL COMMIT;
  printf("salvate le modifiche (eliminazione)\n");

  EXEC SQL DISCONNECT;
  printf("chiusa la connessione\n");

  exit(0);

L'errore tipico dei principianti è dimenticare la COMMIT dopo la DROP TABLE e ritrovarsi, al prossimo lancio del programma, un errore nella creazione della tabella provaoggi.

Incredibile ma vero: qualcuno ha letto questa pagina, mi ha fatto i complimenti per la chiarezza, e poi mi chiedeva come mai non riusciva a salvare i suoi dati dopo le modifiche... aveva ovviamente dimenticato la COMMIT!!!

Piccola parentesi: su Oracle 8 la disconnessione andava fatta così:
EXEC SQL AT :dbase ROLLBACK WORK RELEASE;

A questo punto ci rimane solo la complicatissima gestione degli errori:


gest_err:
  sql_error();

  return 3;
}

E questo è tutto. Amo usare un errorlevel diverso per ogni punto in cui il programma si arresta, perché prima o poi potrebbe tornarmi utile.

La documentazione di PostgreSQL comprende anche una sezione dedicata al porting di programmi che usano SQL di Oracle verso PostgreSQL.

Ecco alcuni motivi per preferire PostgreSQL ad Oracle o MySQL:


Pronti per la discesa?

send e-mail - continua (next page)