Rozdział 7

Embedded SQL

7.1  Wprowadzenie

Embedded SQL jest to technika bezpośredniego wpisywania poleceń SQL jako instrukcji języka zanurzającego, w tym przypadku języka C. W PostgreSQL służy do tego narzędzie o nazwie ECPG.

Program w C z wbudowanymi poleceniami SQL jest najpierw przetwarzany preprocesorem ecpg na program w języku C. Preprocesor rozpoznaje polecenia SQL zawarte w programie i zastępuje je wywołaniami funkcji z biblioteki CLI dla SQL. Otrzymany program przetwarza się normalnym kompilatorem C na program wykonywalny.

ecpg -I/usr/include/ecpg test1.pgc
cc -I/usr/include/ecpg -o test1 test1.c -L/usr/local/lib -lecpg

7.2  Składnia

Wszystkie polecenia SQL muszą być poprzedzane frazą EXEC SQL i kończyć się średnikiem (,,;''). Można je umieszczać gdziekolwiek w programie w C pilnując jedynie, aby deklaracje poprzedzały polecenia wykonywalne. Przykład:

  int main ()
  {
    EXEC SQL BEGIN DECLARE SECTION;
    int w;
    EXEC SQL END DECLARE SECTION;
    ...
    EXEC SQL SELECT wiek INTO :w
             FROM Zwierz
             WHERE imie='Kropka';
    ...
    printf("Kropka waży %d kilo\n", w);
    ...
  }

Aby połączyć się z bazą danych należy użyć:

  EXEC SQL CONNECT TO bd@rainbow USER scott/tiger;

zaś rozłączenia dokonujemy pisząc

  EXEC SQL DISCONNECT;

Obsługę błędów najprościej robi się umieszczając na początku programu polecenie

  EXEC SQL WHENEVER SQLERROR SQLPRINT;

(do konstrukcji WHENEVER jeszcze wrócimy).

7.3  Zmienne

W wyrażeniach SQL można umieszczać zmienne programu. Deklaruje się je wewnątrz sekcji DECLARE używając normalnej składni C.

  EXEC SQL BEGIN DECLARE SECTION;
  deklaracje
  EXEC SQL END DECLARE SECTION;

Mozna używać następujących typów zmiennych:

Typ VARCHAR służy do przechowywania napisów zmiennej długości (jak w SQL) i jest reprezentowany sw ECPG trukturą o dwóch polach. Pole len podaje aktualną długość napisu zapisanego w polu arr, będącym tablicą znakową o rozmiarze n. Uwaga: ciąg znaków w tej tablicy nie jest napisem w sensie C -- nie jest zakończony bajtem zerowym.

  EXEC SQL BEGIN DECLARE SECTION;
  VARCHAR imie[40];
  int waga;
  EXEC SQL END DECLARE SECTION;

Odwołanie do zmiennej w wyrażeniach SQL wymaga poprzedzenia jej nazwy dwukropkiem (,,:''). Gdy pełni ona rolę literału (np. w warunkach WHERE) nie należy jej otaczać apostrofami; takie rzeczy ECPG robi samodzielnie.

7.4  Kursory

Ponieważ polecenie SELECT zwykle zwraca wiele wierszy, do ich przyjęcia należy używać kursorów. Kursor należy najpierw zadeklarować:

EXEC SQL DECLARE k1 CURSOR FOR
  SELECT imie, waga FROM Zwierz WHERE gatunek='lew';

a następnie otworzyć go

EXEC SQL OPEN k1;

Po otwarciu można z kursora pobierać kolejne wiersze do uprzednio zadeklarowanych zmiennych

EXEC SQL FETCH NEXT IN k1 INTO :imie, :waga;
if (sqlca.sqlcode == 0) {
  imie.arr[imie.len - 1] = '\0';
  printf("Imie: %s, waga: %d\n", imie.arr, waga);
}

Po wyczerpaniu wierszy wyniku kursor należy zamknąć

EXEC SQL CLOSE k1;

7.4.1  Wartości puste (NULL)

Ponieważ w C nie istnieją dogodne wartości, którymi można by reprezentować NULL, trzeba w iiny sposób przekazać informacje o ich wystąpieniu w wyniku zapytania. Używa się do tego dodatkowych zmiennych informacyjnych typu całkowitego (indicator variables), po jednej dla każdej kolumny, w której może pojawić się wartość pusta. Taką zmienną umieszcza się we frazie INTO bezpośrednio po właściwej zmiennej, oddzielając ją od niej dwukropkiem.

  int waga_p;
  ...
  EXEC SQL FETCH NEXT IN k1 INTO :imie, :waga:waga_p;

Jeśli wartością zmiennej informacyjnej nie jest zero, to wystąpiła wartość pusta (NULL).

7.5  Dynamiczny SQL

Opisane powyżej mechanizmy wystarczą dla większości aplikacji, czasem jednak polecenie SQL musi być skponstruowane dopiero w trakcie wykonania programu. Służy do tego dynamiczny SQL, pozwalający budować polecenia w zmiennych napisowych, a następnie wykonywać je. Polecenie PREPARE zamienia napis na polecenie SQL, po czym poleceniem EXECUTE wykonujemy je.

  char zapyt[256];

  sprintf(zapyt, "SELECT waga FROM %s WHERE imie = 'Kropka'", tabela);
  EXEC SQL PREPARE zapytanie FROM :zapyt;
  EXEC SQL EXECUTE zapytanie INTO :w;

Polecenia PREPARE i EXECUTE można połączyć:

  char *pol = "INSERT INTO Gatunki VALUES('krowa, 'Europa')";
  EXEC SQL EXECUTE IMMEDIATE :pol;

Zamiast EXECUTE można użyć kursora:

  char zapyt[256];

  sprintf(zapyt, "SELECT waga FROM %s WHERE imie = 'Kropka'", tabela);
  EXEC SQL PREPARE zapytanie FROM :zapyt;
  EXEC SQL DECLARE k1 CURSOR FOR zapytanie;

7.6  Obsługa błędów

Po wykonaniu każdego polecenia SQL informacja o jego wykonaniu jest zapisywana w strukturze SQLCA. Programista może bezpośrednio odczytać pola tej struktury lub użyć konstrukcji WHENEVER.

7.6.1  SQLCA

SQLCA (SQL Communications Area) to standardowa struktura do przekazywania informacji o przebiegu wykonania poleceń SQL, zwłaszcza o napotkanych błędach. Programy mogą odczytywać po każdym poleceniu zawartość jej pól.

Aby odwoływać sie do SQLCA należy dołączyć plik nagłówkowy sqlca.h poleceniem

  EXEC SQL INCLUDE sqlca;

na początku programu.

Struktura ta ma następującą postać

  struct sqlca {
    char sqlcaid[8];
    long sqlabc;
    long sqlcode;
    struct {
      int sqlerrml;
      char sqlerrmc[70];
    } sqlerrm;
    char sqlerrp[8];
    long sqlerrd[6];
    char sqlwarn[8];
    char sqlstate[5];
  };

Poszczególne pola mają następujące znaczenie:

7.6.2  Polecenie WHENEVER

Służy do automatycznej obsługi błędów i wyjątków. Składnia:

  EXEC SQL WHENEVER <warunek> <akcja>;

Obsługa następuje przez każdorazowe sprawdzanie struktury SQLCA i automatyczne wykonanie <akcji> po wykryciu <warunku>.

<Warunek> może mieć następującą postać:

<Akcja> może mieć postać:

Przykłąd:

  EXEC SQL WHENEVER SQLWARNING DO drukuj_ostrzezenie();
  EXEC SQL WHENEVER NOT FOUND BREAK;
  for (;;) {
    printf("Podaj imie zwierzaka: ");
    scanf("%s", &id);
    EXEC SQL SELECT waga INTO :waga
             FROM Zwierz
             WHERE imie = :id;
    printf("Zwierzak %s waży %d.\n", id, waga);
  }