Rajmund Radziewicz

Oracle Pro*C - czyli zagnieżdżanie instrukcji SQL w C
 


W większości systemów bazodanowych, takich jak Oracle, DB2 czy PostgreSQL istnieją narzędzia do tzw. wbudowanego SQL-a. Pozwalają one łączyć instrukcje SQL z językami takimi jak C, C++, czy też Fortran. Połączenie tego typu jest o tyle korzystne, że często daje większe możliwości niż stosowanie języków proceduralnych, do jakich m.in. należy PL/SQL. Języki proceduralne są ściśle zintegrowane z serwerem baz danych, przez co nie mają wpływu na zewnętrzne obszary aplikacji (chociażby warstwę kliencką).
Narzędzie dołączone do bazy Oracle pozwalające zagnieżdżać SQL-a w kodzie C, nazywa się Pro*C. Jest to specjalny prekompilator - który wszystkie osadzone w pliku instrukcje odwołujące się do struktur bazy, zamienia na wywołania odpowiednich funkcji bibliotecznych.  Pisząc program dedykowany dla Pro*C nadajemy mu rozszerzenie *.pc. Następnie przepuszczamy przez prekompilator:

# proc program.pc

W wyniku prekompilacji otrzymamy właściwy plik z rozszerzeniem *.c, który możemy skompilować za pomocą dowolnego kompilatora C-C++ (w Linuksie np. GCC).
Instrukcje SQL w pliku *.pc powinny znaleźć się pomiędzy słowem kluczowym "EXEC SQL" a średnikiem:

void main()

 {
int test;  

EXEC SQL SELECT count(*) INTO :test FROM emp;

printf("Liczba rekordów: %d", test);
 }

W powyższym przykładzie deklarujemy zmienną "test" typu integer, a następnie umieszczamy ją we frazie INTO. W analogiczny sposób przekazuje się wynik zapytania z bazy do programu w języku PL/SQL.

Oczywiście pojedyncze zmienne - to nie jedyne elementy jakie możemy przekazywać. Mogą to być równie dobrze tablice, struktury, unie, czy też wskaźniki. W języku C wskaźniki służą do wskazywania na inne zmienne lub pewien obszar w pamięci komputera:

int *x;

EXEC SQL SELECT * INTO :x

                 FROM Tablica1
                 WHERE KOL1=200000;

W takiej sytuacji wynik wyrażenia SELECT odnosi się  do wskaźnika *x - a nie konkretnej zmiennej x.

Ciekawą właściwością Pro*C jest także możliwość przekazywania w analogiczny sposób wskaźników do struktur. Np:

struct testowa_struktura {
    long id_rekordu [SIZE];
    char tytul [SIZE];
}

 
sql_wstaw ( struct testowa_struktura * opis, int liczb )
{
    EXEC SQL FOR :liczb
        INSERT INTO Opisy1
    VALUES ( :opis );

Funkcja "sql_wstaw" - wstawia tutaj określoną przez "liczb" ilość opisów do tablicy "Opisy1". Wskaźnik do struktury został przekazany jako parametr tej funkcji. Zaletą takiego rozwiązania jest możliwość wykonywania jednorazowo zbiorowych instrukcji INSERT. Słowo kluczowe FOR oznacza w tym wypadku ilość opisów.

W Pro*C możemy także posługiwać się kursorami. W celu wykonania instrukcji SQL - Oracle tworzy pewien obszar roboczy nazywany przestrzenią kontekstu. W przestrzeni tej przechowywane są informacje konieczne do wykonania tej instrukcji. Tak jak w przypadku PL/SQL - Pro*C pozwala nazwać przestrzeń kontekstu i odwoływać się do zawartych w niej danych za pomocą mechanizmu nazywanego kursorem. Mamy możliwość posługiwania się dwoma typami kursorów: 

  • jawnymi - gdzie użytkownik może w sposób jawny utworzyć kursor dla zapytań, których wynikiem jest wiele wierszy i wykonywać operacje na tych wierszach (najczęściej za pomocą FOR);
  • niejawnymi - tutaj Pro*C automatycznie tworzy kursor dla wszystkich wymaganych operacji.

Jeśli wynik naszego zapytania zwraca dużą ilość rekordów, to możliwe jest utworzenie kursora, który będzie umożliwiał dostęp do pojedynczych wierszy ze zwracanej listy. Przykładowy szablon procedury wykorzystującej kursor może wyglądać następująco::

int test;

EXEC SQL DECLARE nazwa_kursora CURSOR FOR SELECT ...

EXEC SQL OPEN nazwa_kursora;

EXEC SQL WHENEVER NOT FOUND DO break;

for (;;) {
    EXEC SQL
       FETCH nazwa_kurosra INTO :test;

Istotne jest również to - że możemy swobodnie łączyć programy *.pc z procedurami i pakietami PL/SQL. Dla przykładu możemy otworzyć kursor poprzez wywołanie istniejącej w bazie procedury PL/SQL'owej open_emp_cur z poziomu programu Pro*C:

sql_cursor    emp_cursor;
char          emp_name[11];
...
EXEC SQL ALLOCATE :emp_cursor;  /* alokacja zmiennych */
...
/* Otworzenie kursora */

EXEC SQL EXECUTE
    begin
        demo_cur_pkg.open_emp_cur(:emp_cursor, :dept_num);
    end;
;
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
    EXEC SQL FETCH :emp_cursor INTO :emp_name;
    printf("%s\n", emp_name);
}

Podczas uruchamiania prekompilatora - możemy przekazywać mu szereg przydatnych parametrów. M.in.

  • CLOSE_ON_COMMIT={YES | NO} - zamykanie wszystkich kursorów po wykonaniu commita
  • DBMS={V7 | V8} - kompatybilność ze starszymi wersjami Oracle'a
  • ERRORS={YES | NO} - wysyłanie komunikatów o błędach na terminal
  • INCLUDE=ścieżka - zdefiniowanie ścieżki do plików dołączanych za pomocą dyrektywy # include
  • MAXLITERAL=10..1024 - maksymalna długość stringów w wygenerowanym kodzie C


Na zakończenie tego krótkiego wprowadzenia - warto może wspomnieć pewnych o drobnych niedogodnościach związanych z używaniem omawianego prekompilatora. Przede wszystkim brakuje w C funkcji do konwersji dat, przez co zmuszeni jesteśmy używać typowo SQL-owych TO_CHAR/TO_DATE. Brak '$' jako zamiennika EXEC SQL [[begin/end] declare section] powoduje, że często małe programiki są sztucznie rozbudowane - i przez to mniej czytelne.

powrót