R-05-07(1), Informacje dot. kompa


Rozdział 5. MySQL

Zdecydowaliśmy wcześniej, że MySQL nie będzie idealnym rozwiązaniem dla bazy danych obsługującej naszą aplikację. Istnieją jednak zastosowania, gdzie ta popularna baza danych spełnia znakomicie swoją rolę — tam, gdzie ważna jest szybkość działania i nie trzeba używać transakcji lub innych zaawansowanych właściwości języka SQL. Dlatego właśnie chcemy przedstawić bazę danych MySQL.

W tym rozdziale omówimy instalację MySQL, a następnie zajmiemy się następującymi zagadnieniami:

Instalacja i zarządzanie

Instalacja MySQL jest bardzo prosta. Jeżeli w danej dystrybucji Linuksa nie ma kopii dystrybucyjnej MySQL, to można ją pobrać ze strony macierzystej http://www.mysql.com. Znajdują się tam zarówno pakiety skompilowane (w tym także RPM), jak i pliki źródłowe dla wielu systemów. W zasadzie wstępnie skompilowane pakiety powinny całkowicie spełniać oczekiwania, ale jeśli ktoś chce skompilować je we własnym zakresie, może pobrać wersję źródłową.

Pakiety skompilowane

Pakiety RPM są aktualnie rozpowszechniane w czterech częściach:

MySQL-<wersja>.<architektura>.rpm

Zawiera on główne pliki binarne i instrukcje oraz wielojęzyczne pliki pomocnicze. Ten pakiet trzeba zainstalować.

MySQL-client-<wersja>.<architektura>.rpm

Zawiera on podstawowe programy klienta, które mają współpracować z serwerem. Programy te tworzą oddzielny pakiet, ponieważ w przypadku instalacji na kilku komputerach pracujących tylko jako klienty bazy MySQL nie trzeba instalować składników serwera na wszystkich komputerach.

MySQL-shared-<wersja>.<architektura>.rpm

zawierający współdzielone biblioteki wymagane przez niektóre klienty.

MySQL-devel-<wersja>.<architektura>.rpm

zawierający pliki nagłówkowe i dodatkowe biblioteki potrzebne przy budowie aplikacji, które komunikują się z serwerem MySQL.

Jeżeli ktoś decyduje się na tworzenie programów korzystających z dostępu do bazy MySQL, to powinien na swoim komputerze zainstalować wszystkie wyżej wymienione pakiety.

Skrypt instalacyjny automatycznie tworzy wstępną bazę danych. Potrzebny będzie także skrypt o nazwie mysql służący do uruchamiania i zatrzymywania serwera, przechowywany w katalogu init.d. Najlepszym sposobem na odszukanie plików bazy jest sprawdzenie zawartości tego skryptu. Są tam podane standardowe ścieżki i definicje, np. po zainstalowaniu binarnego pakietu RPM w dystrybucji Red Hat pewien fragment skryptu mysql o nazwie shell variables wygląda następująco:

bindir=/usr/bin

datadir=/var/lib/mysql

pid_file=/var/lib/mysql.pid

mysql_daemon_user=mysql # Run mysql as this user.

Jak widać, jest to łatwe do zrozumienia. Podczas instalacji jest również tworzony użytkownik o nazwie „mysql”, w imieniu którego będzie uruchamiany demon serwera MySQL. W zależności od wersji skrypt instalacyjny może od razu uruchomić serwer. Sprawdzamy to za pomocą następującego polecenia:

$ ps -el | grep mysql

Jeżeli będą widoczne jakieś procesy mysqld, oznacza to, że serwer został uruchomiony. Jeżeli tak nie będzie, to należy uruchomić skrypt mysql (z katalogu init.d) z parametrem start jako użytkownik mysql. W zależności od dystrybucji polecenie może wyglądać jak niżej:

# su - mysql

$ /etc/rc.d/init.d/mysql start

Budowanie z plików źródłowych

Instalacja z plików źródłowych jest tylko trochę trudniejsza niż instalacja pakietów binarnych. Po pobraniu i rozpakowaniu plików źródłowych należy użyć polecenia:

$ ./configure --help

W ten sposób sprawdzamy, czy trzeba zmieniać jakieś opcje konfiguracyjne. Przy założeniu, że ustawienia domyślne są wystarczające, sekwencja poleceń używanych do kompilacji i instalacji serwera jest następująca:

$ ./configure

$ make

$ su -

# make install

Jeśli wszystko przebiegnie prawidłowo, to należy uruchomić (jako root) skrypt instalacyjny scripts/mysql_install_db w celu wstępnego zainicjowania serwera:

# scripts/mysql_install_db

W zależności od wersji MySQL skrypt może uruchamiać automatycznie serwer bazy danych. Jeśli tak nie jest, wówczas należy uruchomić serwer ręcznie za pomocą skryptu mysql.server umieszczonego w katalogu z plikami pomocniczymi. Skrypt mysql_install_db tworzy niektóre wymagane tabele bazy danych i inicjuje uprawnienia do plików. Ma on prostą budowę i zainteresowani mogą przejrzeć jego zawartość, chcąc się dokładniej zorientować, co on robi.

Przed zakończeniem działania skryptu pojawi się komunikat informujący o sposobie automatycznego uruchomienia MySQL podczas rozruchu systemu. Oprócz tego pojawi się przypomnienie o konieczności ustawienia hasła przez użytkownika bazy MySQL o nazwie „root”. Jest to dosyć mylące, ponieważ w MySQL użytkownik o nazwie root jest administratorem serwera bazy danych. Jeżeli te komunikaty nie pojawią się, to nie należy się tym przejmować i w razie potrzeby odszukać je w skrypcie instalacyjnym.

Ostatnią czynnością podczas instalacji z plików źródłowych jest skonfigurowanie serwera w taki sposób, aby mógł on być automatycznie uruchamiany i zatrzymywany przez system. W katalogu z plikami pomocniczymi (support-files) można znaleźć pomocny skrypt mysql.server, który należy skopiować do katalogu init.d, a następnie utworzyć dowiązania do odpowiednich plików w rc.d. Jako alternatywny sposób uruchamiania można zawsze użyć ręcznego wywołania tego skryptu z parametrem start lub stop. Za każdym razem należy upewniać się (podobnie jak w przypadku bazy PostgreSQL), czy przed wyłączeniem systemu został zatrzymany serwer bazy danych.

Konfiguracja poinstalacyjna

Jeżeli wszystko przebiegło poprawnie, to wówczas mamy utworzoną domyślną konfigurację za pomocą skryptu mysql_install_db i serwer uruchomiony w trybie demona za pomocą skryptu z init.d. Sprawdzimy teraz działanie serwera:

$ mysql -u root mysql

Powinien pojawić się komunikat „Welcome to the MySQL monitor” i za nim znak zachęty mysql>. Oznacza to, że serwer działa; jednocześnie widzimy, że każdy może się z nim połączyć i uzyska uprawnienia administratora. Pracę kończymy wpisując polecenie quit.

Można sprawdzić inaczej, czy serwer działa, używając do tego polecenia mysqladmin:

$ mysqladmin -u root version

Otrzymujemy wówczas informację nie tylko o tym, czy serwer działa, ale także o jego wersji i czasie pracy.

Jeżeli podczas korzystania z mysql nastąpi przerwa w połączeniu z bazą, to najpierw należy sprawdzić, szukając procesów mysqld za pomocą polecenia ps, czy serwer faktycznie działa. Jeśli serwer nie działa, ale jesteśmy pewni, że był uruchamiany, to można próbować użyć programu safe_mysqld z opcją --log. W katalogu, w którym była zainstalowana baza MySQL (najczęściej jest to /var/lib/mysql), powstanie wówczas plik logu o nazwie takiej jak nazwa komputera z końcówką .log.

Jeżeli serwer działa, ale nie można się z nim połączyć, to przeglądając skrypt mysql_install_db albo init.d/mysql należy sprawdzić, gdzie została zainstalowana baza danych. Zazwyczaj jest to katalog /var/lib/mysql w podkatalogu mysql. Powinny być tam pliki o nazwach mających końcówki .frm, .ISD i .ISM.

Jeżeli to się nie uda, to można próbować zatrzymać serwer, ręcznie usunąć pliki bazy danych i uruchomić ręcznie skrypt mysql_install_db w celu ponownego utworzenia bazy. Potem należy ponownie uruchomić serwer. Jest nadzieja, że tym razem wszystko się uda, ale jeśli tak nie będzie, to należy poszukać dodatkowych informacji na temat wyszukiwania błędów w dokumentacji dostarczanej w pakiecie MySQL. Jeżeli był instalowany pakiet binarny, to dokumentacja znajduje się zapewne w katalogu /usr/doc/MySQL-<wersja>. Można także sprawdzić informacje w Internecie na stronie MySQL.

Bardzo kłopotliwy błąd powstaje wówczas, gdy uprawnienia do plików bazy danych nie są zgodne z uprawnieniami użytkownika mysql tworzonego automatycznie podczas instalacji albo gdy demon mysqld został uruchomiony w imieniu użytkownika o niewłaściwym identyfikatorze. W takim wypadku można połączyć się z bazą, ale nie udaje się uruchomić innych programów, np. mysqladmin i mysqlshow. Trzeba wówczas dokładnie sprawdzić atrybuty plików bazy danych i użytkownika, który uruchomił proces mysqld, ponieważ błąd prawdopodobnie jest spowodowany ich niedopasowaniem. Właścicielem wszystkich plików bazy danych powinien być użytkownik mysql.

Kolejne zadanie polega na nadaniu hasła administratorowi bazy danych uprawniającego go do dostępu do serwera. Służy do tego następujące polecenie mysqladmin:

$ mysqladmin -u root password nowe_haslo

Nadaliśmy w ten sposób początkowe hasło dostępu „nowe_haslo” i próba połączenia z bazą się nie uda, chyba że nada się hasło użytkownikowi:

$ mysql -u root -psecretpassword mysql

Zwróćmy tu uwagę na trochę dziwną składnię: między literą p a faktycznym hasłem nie może być spacji. Ostatni parametr, czyli mysql, jest nazwą bazy danych. Jeżeli hasło nie zostanie wpisane (czyli wystąpi tylko opcja -p), to mysql zażąda jego podania. Ponieważ podawanie hasła w wierszu poleceń nie jest bezpieczne (inni użytkownicy mogą je zobaczyć np. za pomocą polecenia ps), to lepiej jest używać następującej postaci tego polecenia, nie wpisując hasła:

$ mysql -u root -p mysql

Baza zażąda wówczas hasła. Po uruchomieniu mysql można sprawdzić, czy istnieje testowa baza danych używając polecenia:

mysql> select host, db, user from db;

Powinniśmy otrzymać coś takiego:

+------+----------+------+

| host | db | user |

+------+----------+------+

| % | test | |

| % | test\_% | |

+------+----------+------+

2 rows in set (0.00 sec)

Aby zakończyć pracę z mysql, należy użyć polecenia quit.

Zarządzanie bazą MySQL

W pakiecie MySQL znajduje się kilka programów pomocniczych wspomagających zarządzanie bazą danych. Najczęściej używany jest program mysqladmin, ale zanim przejdziemy do omawiania sposobu tworzenia programów-klientów, omówimy skrótowo jeszcze kilka innych programów administracyjnych.

Polecenia

Wszystkie polecenia oprócz mysqlshow mogą mieć trzy standardowe argumenty:

Argument -h jest używany przy łączeniu się z serwerem bazy danych działającym na innym komputerze i na komputerze lokalnym zawsze można go pominąć. Jeżeli argument -p jest podany bez hasła, to zakłada się, że w danym poleceniu MySQL hasło nie jest potrzebne.

isamchk

Jest to program pomocniczy służący do sprawdzania i naprawy tabel bazy danych MySQL. Należy go uruchamiać w imieniu pseudoużytkownika (ang. pseudo-user) utworzonego przez MySQL, przechodząc do odpowiedniego podkatalogu bazy danych, która ma być sprawdzana. Aby np. sprawdzić bazę fud, należy przejść do katalogu /var/lib/mysql/fud. Program isamchk ma wiele opcji, które zostaną wypisane po uruchomieniu go bez parametrów.

Ogólnie mówiąc, jeżeli chcemy sprawdzić wszystkie tabele w danej bazie, to program ten należy uruchomić z jakimiś opcjami, podając dodatkowo parametr *.ISM. Najważniejsze opcje wypisane są niżej:

-a

Analiza plików.

-e

Rozszerzone sprawdzanie.

-r

Poprawianie znalezionych błędów.

-s

Praca bez komunikatów aż do momentu znalezienia błędu.

Po wywołaniu isamchk bez parametrów można zapoznać się z wyczerpującym opisem tego programu.

Należy mieć nadzieję, że nie trzeba będzie nigdy używać tego programu, ale gdy zdarzy się niekontrolowane wyłączenie komputera, może on okazać się pomocny przy odtwarzaniu na dysku uszkodzonych plików bazy danych. Do takich zadań służy właśnie ten program.

mysql

Jest to standardowe narzędzie wiersza poleceń, które może być używane do wielu zadań administracyjnych opisanych w następnych częściach rozdziału.

Polecenie mysql ma dodatkowy argument, który musi być podany po wszystkich opcjach. Jest to nazwa bazy danych, z którą chcemy się połączyć, np. użytkownik rick mający hasło bar uruchamia polecenie mysql w bazie foo następująco:

$ mysql -u rick -pbar foo

Taki sposób podawania nazwy bazy danych jest dosyć wygodny. Opis wszystkich opcji polecenia mysql jest wyświetlany po użyciu opcji -h.

Można także zażądać, by program mysql pobierał polecenia z pliku, przekierowując standardowe wejście na plik, jak w przykładzie poniżej:

$ mysql -u rick -pbar foo < sqlcommands.sql

Po przetworzeniu poleceń pobranych z pliku program mysql kończy działanie.

mysqladmin

Jest to główny program do zarządzania bazą danych. Oprócz normalnych argumentów -u user i -p obsługuje on cztery podstawowe polecenia:

create databasename

Tworzenie nowej bazy danych.

drop databasename

Usuwanie bazy danych.

password newpassword

Zmiana hasła (tak jak to było opisane wcześniej).

status

Informacja o stanie serwera.

version

Informacja o numerze wersji serwera i czasie jego działania.

Po wywołaniu mysqladmin bez parametrów zostanie wyświetlony opis dostępnych poleceń.

mysqlbug

Mamy nadzieję, że nigdy nie trzeba będzie korzystać z tego programu! Zbiera on informację o danej instalacji i konfiguracji SQL i tworzy standardowy raport, który może być przesłany do autorów pakietu łącznie ze szczegółowym opisem problemu.

mysqldump

Jest to bardzo pomocny program, który umożliwia zapis bazy danych do pliku (łącznie ze wszystkimi lub tylko z wybranymi tabelami). Zapisuje on w pliku standardowe polecenia SQL, które mogą być uruchomione po przekierowaniu pliku na wejście programu mysql lub mysqlimport. Jako parametr podaje się nazwę bazy danych i opcjonalnie listę tabel z tej bazy. Oprócz standardowych opcji -u i -p są jeszcze dwie najczęściej używane:

--add-drop-table

Dołączenie do pliku wyjściowego polecenia SQL usuwającego tabele przed ich ponownym utworzeniem.

-t

Zapis do pliku tylko danych z tabel.

-d

Zapis do pliku tylko struktury tabel.

Informacja jest przesyłana na standardowe wyjście, a więc prawdopodobnie trzeba będzie przekierować ją do pliku.

Program mysqldump można wykorzystać jako narzędzie do okresowego tworzenia zapasowych kopii bazy danych lub przy eksporcie danych do innej bazy. Dane wyjściowe mają postać prostego pliku ASCII i łatwo je odczytać. Można także dodać do nich własne komentarze. Jeżeli np. chcemy zachować bazę rick w pliku rick.dump, to powinniśmy użyć następującego polecenia:

$ mysqldump -u rick -p rick > rick.dump

Powstający w wyniku tego plik (przy założeniu, że w bazie istnieje tylko jedna tabela) może wyglądać następująco:

# MySQL dump 7.1

#

# Host: localhost Database: rick

#--------------------------------------------------------

# Server version 3.22.32-log

#

# Table structure for table 'children'

#

CREATE TABLE children (

childno int(11) DEFAULT '0' NOT NULL auto_increment,

fname varchar(30),

age int(11),

PRIMARY KEY (childno)

);

#

# Dumping data for table 'children'

#

INSERT INTO children VALUES (1,'Jenny',14);

INSERT INTO children VALUES (2,'Andrew',10);

INSERT INTO children VALUES (3,'Gavin',4);

INSERT INTO children VALUES (4,'Duncan',2);

INSERT INTO children VALUES (5,'Emma',0);

INSERT INTO children VALUES (6,'Alex',11);

INSERT INTO children VALUES (7,'Adrian',5);

mysqlimport

Program mysqlimport uzupełnia się z programem mysqldump i służy do tworzenia bazy danych z pliku tekstowego (oczywiście, plik ten nie musi być koniecznie utworzony za pomocą programu mysqldump, można utworzyć go także ręcznie). Program mysqlimport wymaga podania jedynie nazwy pliku tekstowego, z którego mają być odczytywane polecenia.

Polecenia SQL zapisane w pliku może także odczytywać program mysql.

mysqlshow

Program mysqlshow jest wygodnym narzędziem do wyświetlania informacji o serwerze, bazie danych i o tabeli — zależy to od użytych opcji:

Nie ma sensu podawanie nazwy kolumny, ponieważ pełna informacja o każdej kolumnie jest dostępna w opisie tabeli.

Tworzenie użytkowników i nadawanie uprawnień

Najczęściej spotykanym zadaniem administratora, oprócz tworzenia kopii zapasowych ważnych danych, jest konfigurowanie uprawnień użytkowników. Począwszy od wersji 3.22 pakietu MySQL, uprawnienia użytkownika mogą być modyfikowane za pomocą dwóch poleceń SQL: grant i revoke. Te obydwa polecenia są uruchamiane z wiersza poleceń programu mysql.

grant

Polecenie grant używane w MySQL jest podobne do opisanego w standardzie SQL92, różni się ono jednak kilkoma istotnymi szczegółami. Ogólna postać polecenia grant wygląda następująco:

grant uprawnienie on obiekt to użytkownik [hasło_użytkownika] [opcja];

Istnieje kilka uprawnień, które można nadać użytkownikowi:

Alter

Zmiana tabel i indeksów

Create

Tworzenie baz danych i tabel

Delete

Usuwanie danych z bazy

Drop

Usuwanie baz danych i tabel

Index

Obsługa indeksów

Insert

Dodawanie danych do bazy

Select

Pobieranie danych

Update

Modyfikacja danych

All

Wszystkie operacje

Istnieje także kilka uprawnień administratora bazy danych, ale nie dotyczą one omawianych tutaj zagadnień.

Obiekt, którego dotyczą udzielane uprawnienia, jest identyfikowany jako:

databasename.tablename

Dozwolone jest stosowanie nazw wieloznacznych, w których „*” oznacza „wszystko”. Użycie rick.* oznacza więc wszystkie tabele w bazie danych rick. Konsekwencją takiego sposobu oznaczeń używanego przez MySQL jest możliwość nadania uprawnień do takiej bazy danych, która nie istnieje. Może to się wydawać nieco dziwne, ale w praktyce może być całkiem użyteczne (można np. nadać użytkownikowi uprawnienia do utworzenia konkretnej bazy danych).

Nazwa użytkownika podawana jako argument może dotyczyć użytkownika istniejącego albo użytkownika nowego — i w tym wypadku zostanie on automatycznie utworzony. Użytkownicy łączący się jako klienci z serwerem MySQL zawsze są identyfikowani jako username.host (dotyczy to nawet użytkowników lokalnych, dla których nazwą komputera będzie localhost).

Specjalny symbol wieloznaczny „%” oznacza „dowolny komputer”. Polecenia grant można użyć kilkakrotnie, ustalając uprawnienia dla określonych komputerów, których użytkownicy będą się łączyć z serwerem. Można też nadawać uprawnienia od razu całej grupie komputerów z określonej domeny:

rick@"%.docbox.co.uk"

W takim przypadku wymagane jest użycie cudzysłowu.

Użycie frazy „identified by” powoduje ustawienie hasła dla danego użytkownika. Zazwyczaj podczas tworzenia nowego użytkownika nadaje mu się hasło automatycznie, bowiem pozostawienie go bez hasła zagraża bezpieczeństwu bazy danych.

with grant

Opcja with grant pozwala użytkownikowi przekazywać posiadane przez niego uprawnienia. Normalnie nie używa się tej opcji, chyba że dotyczy ona tworzonego konta administratora bazy.

Dość już teorii, utwórzmy wreszcie konto użytkownika rick, który będzie mógł łączyć się z dowolnego komputera, nadajmy mu hasło bar i pozwólmy mu na utworzenie bazy foo. Polecenie mysql służące do tego celu wygląda następująco:

mysql> grant all on foo.* to rick@"%" identified by "bar";

Trzeba pamiętać o końcowym średniku wymaganym przy każdym poleceniu SQL.

W taki sposób został utworzony użytkownik rick z hasłem bar. Użytkownik ten będzie mógł połączyć się z dowolnego komputera oraz utworzyć bazę foo i zarządzać nią. Dysponując takimi uprawnieniami, rick może utworzyć bazę foo za pomocą zwykłego polecenia SQL create database.

revoke, delete

Rozważając nadawanie uprawnień, musimy mieć także możliwość ich odbierania. W ogólnym przypadku służy do tego polecenie revoke, które ma następującą składnię:

revoke uprawnienie on obiekt from użytkownik;

Jak widać, ma ono postać zbliżoną do polecenia grant, na przykład:

revoke insert on foo.* from rick@"%";

Istnieje tu jednak niewielkie odstępstwo, ponieważ nawet po zabraniu wszystkich uprawnień danemu użytkownikowi zachowuje on niepożądaną możliwość łączenia się z bazą danych. Aby całkowicie usunąć konto użytkownika, należy usunąć jego dane z tabeli użytkowników bazy danych MySQL (polecenie delete) i wymusić na mysql ponowny odczyt uprawnień (polecenie flush). Dokonuje się tego w następujący sposób:

mysql> use mysql

mysql> delete from user where User = "rick" and Host = "%";

mysql> flush privileges;

Hasła

Jeżeli zdarzy się, że zapomnimy o nadaniu hasła, to możemy je ustawić w dowolnym czasie. W tym celu należy zalogować się do bazy mysql jako root i użyć następującego polecenia:

mysql> select host, user, password from user;

Powinniśmy wówczas otrzymać następującą listę:

+-----------+----------+------------------+

| host | user | password |

+-----------+----------+------------------+

| localhost| root | 67457e226a1a15bd |

| % | rick | 7c9e0a41222752fa |

| .% | foo | |

+-----------+----------+------------------+

2 rows in set (0.00 sec)

Załóżmy, że chcemy ustawić hasło bar dla użytkownika foo. Wpisujemy wówczas:

mysql> update user set password= password('bar') where user= 'foo';

Wyświetlając ponownie zawartość tabeli mysql.user, otrzymujemy:

mysql> select host, user, password from user;

+-----------+----------+------------------+

| host | user | password |

+-----------+----------+------------------+

| localhost| root | 67457e226a1a15bd |

| % | rick | 7c9e0a41222752fa |

| .% | foo | 7c9e0a41222752fa |

+-----------+----------+------------------+

2 rows in set (0.00 sec)

mysql>

Teraz możemy być pewni, że hasło użytkownika foo jest takie samo, jak wcześniej ustawione hasło użytkownika rick, bowiem takie same są hasła zaszyfrowane.

Tworzenie bazy danych

Pobawimy się teraz bazą danych o nazwie rick. Najpierw jako użytkownik root bazy mysql musimy nadać użytkownikowi rick uprawnienia do tworzenia nowej bazy:

mysql> grant all on rick.* to rick@% identified by "bar";

Użytkownik rick ma teraz wszystkie uprawnienia do bazy danych o nazwie rick przy połączeniach z dowolnego komputera.

Zakończymy pracę z programem mysql i wywołamy go ponownie jako użytkownik rick.

mysql> quit

Bye

$ mysql -u rick

mysql> create database rick;

Podłączamy się do bazy rick:

mysql> use rick

Możemy teraz tworzyć dowolne tabele w tej bazie.

Jak pamiętamy z opisu bazy PostgreSQL podanego w poprzednim rozdziale, w kilku przykładach była tam używana tabela children. Polecenia SQL tworzące tę tabelę są następujące:

create table children (

childno SERIAL,

fname VARCHAR,

age INTEGER

);

Jeżeli zechcemy użyć tego w MySQL, to napotkamy na pierwszą przeszkodę: MySQL nie obsługuje słowa kluczowego SERIAL. Na szczęście można ją łatwo pokonać (patrz podrozdział opisujący różnice między PostgreSQL i MySQL). Równoważnikiem powyższych poleceń SQL w bazie MySQL jest:

create table children (

childno INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,

fname VARCHAR(30),

age INTEGER

);

Jak widać, można łatwo wprowadzić poprawki. Wprowadzanie danych do tabel odbywa się podobnie jak w PostgreSQL, trzeba jedynie pamiętać o pomijaniu kolumny z autoinkrementacją.

insert into children(fname, age) values("Jenny", 14);

insert into children(fname, age) values("Andrew", 10);

W taki oto sposób można wstawiać dowolną liczbę wierszy.

Wspomaganie SQL w PostgreSQL i MySQL

W rozdziale 3. omówiliśmy podstawy SQL, koncentrując się głównie na bazie PostgreSQL. Sposób obsługi SQL w bazie MySQL jest podobny. Pomimo kilku wspomnianych wyżej różnic podstawowy zakres poleceń SQL jest w obydwu bazach taki sam i większość z nich będzie działać na obydwu platformach. Należy pamiętać, że obydwa serwery są intensywnie rozwijane, więc lista różnic może się w przyszłości drastycznie zmienić. Oczywiście, podane tutaj różnice nie są jedynymi — w książce opisujemy tylko te, z którymi najczęściej można się spotkać w codziennych zastosowaniach.

lock tables tablename READ | WRITE [,tablename READ | WRITE ...];

unlock tables;

W PostgreSQL także istnieje polecenie blokujące tabelę, ale prawie nigdy nie powinno ono być używane. Zamiast tego przy automatycznej modyfikacji danych w PostgreSQL należy używać transakcji. Ponieważ MySQL nie obsługuje obecnie transakcji (patrz niżej), polecenie lock jest w nim używane znacznie częściej.

Blokada tabeli dla odczytu zapobiega jakiejkolwiek modyfikacji tej tabeli. Blokada tabeli dla zapisu zapobiega odczytowi lub zapisowi danych przez inne procesy, ale główny wątek może je zarówno odczytywać, jak i zapisywać. Polecenia blokady nie mogą być zagnieżdżane, ponieważ przed wykonaniem każdego nowego polecenia blokady usuwana jest automatycznie blokada tabel obwiązująca w danym momencie.

Po pierwsze, można modyfikować (update) tabele, podając wartości wszystkich pól modyfikowanego wiersza. Jeżeli np. chcemy zmienić kwotę salda na koncie klienta z 3 na 4, a w tabeli występują kolumny zawierające imię, nazwisko, numer konta i sald, to zamiast pisać:

update account set amount = 4 where accnum = 3013;

wstawiamy następujące polecenie:

update account set amount = 4 where

accnum=3013 and customerfn = "Bilbo" and

customerln = "Baggins" and amount = 3;

Dzięki temu, jeżeli jakieś pole (kolumna) w danym wierszu zostało zmodyfikowane, zanim stwierdziliśmy, że wartość salda wynosi 3 i chcemy ją zmienić na 4, to nasza modyfikacja nie uda się, ponieważ nie będzie można znaleźć wiersza spełniającego wymagane kryteria. Po sprawdzeniu przyczyny niepowodzenia i obsłużeniu błędu należy podjąć właściwą decyzję o dalszym postępowaniu.

Po drugie, można użyć polecenia lock tables, blokując dostęp innych użytkowników do modyfikowanej tabeli. Jest to prostsze, ale nie tak łatwe jak w przypadku transakcji, a dodatkowo obniża wydajność bazy danych, gdy pracuje z nią kilku użytkowników i wymagane są częste modyfikacje danych.

Są to najważniejsze różnice, z którymi można się spotkać, stosując zestaw poleceń SQL opisywanych w tej książce.

Dostęp do danych MySQL z języka C

Podobnie jak baza PostgreSQL, baza MySQL może być dostępna z poziomu wielu języków programowania. Autorzy wiedzą o następujących:

Istnieje także sterownik ODBC umożliwiający dostęp z aplikacji Microsoft Windows oraz sterownik ODBC dla systemu Linux — można więc korzystać i z takiej metody dostępu do bazy MySQL.

W czasie pisania tej książki istniały zastrzeżenia dotyczące bezpieczeństwa sterowników ODBC, więc nie zalecamy korzystania z nich jako głównego sposobu dostępu do bazy.

W tym rozdziale zajmiemy się tylko interfejsem języka C, co wcale nie oznacza, że jest on lepszy pod jakimś względem niż interfejsy w innych językach programowania. Po prostu, wokół języka C koncentruje się cała książka. Interfejs programowy dla MySQL w języku C jest bardzo zwięzły i podobny w wielu przypadkach do interfejsu libpq w PostgreSQL. Nie ma tu jednak odpowiednika osadzonych poleceń SQL w kodzie C, które w przypadku PostgreSQL są dostępne w postaci biblioteki ecpg.

Funkcje połączeniowe

Rozróżnia się dwie fazy połączenia z języka C do bazy MySQL:

Do inicjacji połączenia służy funkcja mysql_init:

MYSQL *mysql_init(MYSQL *);

Zazwyczaj przekazuje się do tej funkcji wartość NULL, a zwracany jest wskaźnik do zainicjowanej struktury. Jeżeli będzie przekazana istniejąca struktura, to nastąpi jej ponowna inicjacja. W przypadku wystąpienia błędu zwracana jest wartość NULL.

MySQL faktycznie udostępnia dwa sposoby łączenia się z bazą danych, ale funkcja mysql_connect, którą można czasem zobaczyć w starszych programach, jest już przestarzała i nie będziemy jej tutaj opisywać.

Mamy więc utworzoną i zainicjowaną strukturę, ale nie zapewniliśmy jeszcze parametrów wymaganych do połączenia z bazą. Do ustawiania parametrów i faktycznego połączenia służy funkcja mysql_real_connect:

MYSQL *mysql_real_connect(MYSQL *connection,

const char *server_host,

constchar *sql_user_name,

const char *sql_password,

const char *db_name,

unsigned int port_number,

const char *unix_cocket_name,

unsigned int flags);

Wskaźnik połączenia musi wskazywać na strukturę zainicjowaną wcześniej za pomocą funkcji mysql_init. Parametr server_host jest nazwą lub adresem IP komputera, na którym działa serwer MySQL. Jeżeli serwer działa na komputerze lokalnym, to należy użyć nazwy localhost, co pozwoli na optymalizacje takiego połączenia.

Parametry sql_user_name i sql_password są używane przy logowaniu do bazy danych. Jeśli nazwa używana przy logowaniu jest równa NULL, to przyjmowany jest bieżący identyfikator użytkownika. Jeśli jako hasło podana będzie wartość NULL, to logujący się użytkownik będzie miał dostęp tylko do tych danych na serwerze, które nie wymagają użycia hasła. Hasło jest przesyłane przez sieć w postaci zaszyfrowanej.

Numer portu (port_number) i nazwa gniazda uniksowego (unix_socket_name) powinny mieć odpowiednio wartości 0 i NULL, chyba że istnieją jakieś specjalne powody wymuszające użycie wartości niestandardowych. Domyślnie ustawiane są odpowiednie wartości.

Znaczniki (flags) to wartość tworzona z wzorców bitowych poddanych logicznej operacji OR, umożliwiająca zmianę niektórych właściwości protokołu używanego podczas połączenia. Prawdopodobnie tylko dwa z nich będą rzeczywiście potrzebne:

W rozdziale poświęconym PostgreSQL mówiliśmy, że można określić liczbę wierszy, na które działały polecenia INSERT, UPDATE i DELETE. W przypadku polecenia UPDATE działanie bazy MySQL troszeczkę różni się od działania PostgreSQL (i większości innych baz głównego nurtu).

Jeśli PostgreSQL zwraca liczbę wierszy, na które zadziałało polecenie UPDATE, to faktycznie jest ona równa tylko liczbie tych wierszy, które spełniają kryteria podane w deklaracji WHERE. W przypadku MySQL jest to liczba zmienionych wierszy, która może różnić się nieco od poprzedniej.

Załóżmy, że mamy tabelę zawierającą wpisy trojga dzieci o imieniu Ann, w wieku 3 lata, 4 lata i 5 lat. W bazie PostgreSQL polecenie takie jak niżej:

UPDATE age SET age = 3 WHERE name = 'Ann'

będzie sygnalizować działanie na trzy wiersze — jest to liczba dzieci o imieniu Ann. Baza MySQL będzie sygnalizować dwa wiersze — czyli liczbę wierszy faktycznie zmienionych. Przekazując do funkcji connect znacznik CLIENT_FOUND_ROWS, można upodobnić w tym obszarze działanie MySQL do działania PostgreSQL

Inne, rzadziej używane znaczniki są opisane w dokumentacji.

Jeżeli połączenie się nie uda, to zostanie zwrócona wartość NULL. Aby poznać przyczynę błędu, możemy użyć funkcji mysql_error opisanej w dalszej części rozdziału.

Do zamknięcia połączenia po zakończeniu pracy należy używać funkcji mysql_close (zazwyczaj jej wywołanie umieszcza się na końcu programu):

void mysql_close(MYSQL *connection);

Takie wywołanie zamyka połączenie. Jeżeli struktura opisująca połączenie była utworzona za pomocą mysql_init (dzięki przekazaniu wartości NULL w pierwotnym wywołaniu mysql_init), to zostanie zwolniona, wskaźnik do niej przestanie mieć sens i można będzie użyć go ponownie.

Funkcją bardzo blisko związaną z funkcjami połączenia jest mysql_options (ponieważ można ją wywołać tylko między funkcjami mysql_init i mysql_real_connect). Służy ona do ustawiania opcji połączenia.

int mysql_options(MYSQL *connection, enum ustawiana_opcja,

const char *argument);

Ponieważ w jednym wywołaniu można ustawiać tylko jedną opcję, to trzeba wywoływać tę funkcję tyle razy, aż wymagane opcje zostaną ustawione (pamiętając o umieszczeniu wywołań między wywołaniami mysql_init i mysql_real_connect). Niektóre opcje wymagają argumentu, który nie jest typu char i w tym przypadku trzeba zmienić typ wartości na const char *. Dostępnych jest kilka opcji, ale tutaj szerzej omówimy tylko trzy najważniejsze. Pełna lista opcji jest podana w podręczniku systemowym, w dokumentacji rozprowadzanej w pakiecie MySQL (zwykle jest ona umieszczana w katalogu /usr/doc) oraz w podręczniku w formacie pdf, który można pobrać ze strony macierzystej MySQL.

Opcja

Typ argumentu

Znaczenie

MYSQL_OPT_CONNECT_TIMEOUT

const unsigned int *

Ograniczenie czasowe połączenia w postaci liczby sekund.

MYSQL_OPT_COMPRESS

Brak, trzeba użyć NULL

Użycie kompresji podczas połączenia sieciowego.

MYSQL_INIT_COMMAND

const char *

Polecenie, jakie ma być wysyłane za każdym razem, gdy ustanawiane jest połączenie.

W przypadku udanego ustawienia opcji zwracane jest zero. Ponieważ jedyną operacją wykonywaną przez tę funkcję jest ustawianie znaczników w strukturze opisującej połączenie, to błąd oznacza po prostu użycie niewłaściwej opcji.

Aby np. ustawić siedmiosekundowe ograniczenie czasowe dla połączenia, można użyć następującego kodu:

unsigned int timeout = 7;

...

connection = mysql_init(NULL);

ret = mysql_options(connection, MYSQL_OPT_CONNECT_TIMEOUT, (const char

*)&timeout);

if (ret) {

/* Handle error */

...

}

connection = mysql_real_connect(connection...

Po zapoznaniu się z podstawowymi informacjami na temat połączeń z bazą MySQL spróbujemy napisać krótki program testowy.

Program ten nazywa się connect1.c i będzie łączył się z serwerem bazy rick na komputerze lokalnym jako użytkownik rick posługujący się hasłem bar.

#include <stdlib.h>

#include <stdio.h>

#include "mysql.h"

int main(int argc, char *argv[]) {

MYSQL *conn_ptr

conn_ptr = mysql_init(NULL);

if (!conn_ptr) {

fprintf(stderr, "mysql_init failed\n");

return EXIT_FAILURE;

}

conn_ptr = mysql_real_connect(conn_ptr, "localhost", "rick", "bar",

"rick", 0, NULL, 0);

if (conn_ptr) {

printf("Connection success\n");

} else {

printf("Connection failed\n");

}

mysql_close(conn_ptr);

return EXIT_SUCCESS;

}

Program należy skompilować. Zależnie od sposobu instalacji bazy MySQL może być potrzebne dopisanie ścieżek do dołączanych plików nagłówkowych i do bibliotek oraz konsolidacja z modułem bibliotecznym mysqlclient. Przy instalacji z pakietów RPM wymagany wiersz uruchamiający kompilator ma postać:

$ gcc -I/usr/include/mysql connect1.c -L/usr/mysql -lmysqlclient -o connect1

Po uruchomieniu programu powinniśmy ujrzeć komunikat mówiący o udanym połączeniu z bazą:

$ ./connect1

Connection success

$

Widzimy więc, że nawiązywanie połączeń z bazą danych MySQL jest bardzo proste.

Obsługa błędów

Zanim przejdziemy do bardziej przydatnych programów, musimy zapoznać się ze sposobem obsługi błędów stosowanym w MySQL. Wszystkie błędy są sygnalizowane za pomocą zwracanego kodu, a ich szczegóły są zawarte w strukturze obsługującej połączenie. Trzeba poznać tylko dwie następujące funkcje:

unsigned int mysql_errno(MYSQL *connection);

i

char *mysql_error(MYSQL *connection);

Jeżeli jakaś funkcja mysql zwraca liczbę całkowitą będącą kodem błędu (ogólnie dotyczy to każdej niezerowej wartości), to można ten kod uzyskać, korzystając z funkcji mysql_errno odwołującej się do struktury połączenia. Jeśli nie był ustawiony żaden kod błędu, to zwracane jest zero. Kod ten jest modyfikowany przy każdym odwołaniu do biblioteki, więc można pobierać tylko kod błędu dotyczący ostatniego polecenia (wyjątkiem są tu oczywiście wywołania samych funkcji do obsługi błędów, które nie modyfikują kodu błędu).

Kody błędów są zdefiniowane w pliku errmsg.h lub mysql_error.h, które znajdują się w specyficznym dla MySQL katalogu include przeznaczonym na pliki nagłówkowe. Pierwszy z tych plików zawiera definicje kodów błędu związanego z klientami (np. utrata połączenia z bazą), w drugim zaś znajdują się definicje błędów serwera (np. przekazanie niewłaściwego polecenia).

Jeżeli potrzebne są tekstowe komunikaty o błędach, to można wywołać funkcję mysql_error. Komunikat przechowywany jest w wewnętrznym statycznym obszarze pamięci, zatem przed wykonaniem jakichś operacji trzeba go stamtąd skopiować w inne miejsce.

Po dodaniu podstawowej obsługi błędów do naszego programu testowego możemy zobaczyć, jak działa to w rzeczywistości. Niektórzy zapewne zauważyli, że możemy napotkać tu pewien problem. Jeżeli funkcja mysql_real_connect zwróci przy błędzie wskaźnik połączenia o wartości NULL, to w jaki sposób uzyskamy kod błędu? Odpowiedzią jest przyporządkowanie zmiennej do wskaźnika połączenia, która jest dostępna nawet wówczas, gdy wywołanie funkcji mysql_real_connect się nie uda.

W podanym niżej programie connect2.c pokazujemy sposób wykorzystania struktury połączenia, która nie jest tworzona dynamicznie, oraz podstawową obsługę błędów. Zmiany w stosunku do poprzedniego programu zostały wyróżnione:

#include <stdlib.h>

#include <stdio.h>

#include "mysql.h"

int main(int argc, char *argv[]) {

MYSQL my_connection;

mysql_init(&my_connection);

if (mysql_real_connect(&my_connection, "localhost", "rick",

"bar", "rick", 0, NULL, 0)) {

printf("Connection success\n");

mysql_close(&my_connection);

} else {

fprintf(stderr, "Connection failed\n");

if (mysql_errno(&my_connection)) {

fprintf)stderr, "Connection error %d: %s\n",

mysql_errno(&my_connection), mysql_error(&my_connection));

}

}

return EXIT_SUCCESS;

}

W rzeczywistości rozwiązaliśmy nasz problem zupełnie łatwo, unikając „nadpisywania” wskaźnika połączenia przez zwracany wynik w wypadku nieudanego wywołania mysql_real_connect. Niezależnie od tego, jest to przykład innego sposobu korzystania ze struktury połączenia. Jeżeli wymusimy powstanie błędu (np. używając niewłaściwego hasła), to otrzymamy kod błędu i tekst komunikatu o błędzie, czyli to wszystko, czego oczekujemy od interaktywnego narzędzia mysql.

Wykonywanie poleceń SQL

Mamy już możliwość połączenia się z bazą i wiemy, jak obsługiwać błędy, zatem nadeszła pora na zajęcie się czymś, co można będzie wykorzystać w naszej bazie. Dowolne polecenie SQL jest uruchamiane za pomocą funkcji mysql_query:

int mysql_query(MYSQL *connection, const char *query)

Jak widać, wywołanie to jest nadzwyczaj proste. Wymaga ono podania wskaźnika struktury połączenia i napisu zawierającego polecenie SQL, które ma być wykonane. W odróżnieniu od wiersza poleceń nie należy tu używać średnika kończącego polecenie. Jeżeli uda się wykonać polecenie, to zostanie zwrócone zero. W specjalnym przypadku, gdy wymagane jest dołączenie danych binarnych, można użyć pokrewnej funkcji o nazwie mysql_real_query, ale w tym rozdziale nie będziemy jej omawiać.

Polecenia SQL, które nie zwracają żadnych danych

Najpierw zapoznamy się z poleceniami UPDATE, DELETE i INSERT. Ponieważ nie zwracają one żadnych danych z bazy, to łatwiej będzie się nimi posługiwać.

Następną ważną funkcją, którą tutaj omówimy, jest funkcja sprawdzająca liczbę wierszy poddanych działaniu danego polecenia:

my_ulonglong mysql_affected_rows(MYSQL *connection);

Prawdopodobnie najbardziej oczywistą rzeczą jest jej nietypowy wynik. Ze względu na przenośność kodu zwracany wynik ma specjalny typ bez znaku. Jeżeli chcemy go wykorzystać w funkcji printf, to należy zmienić jego typ na unsigned long i użyć formatu %lu. Funkcja ta zwraca liczbę wierszy poddanych działaniu poleceń UPDATE, INSERT lub DELETE przekazanych do bazy za pomocą funkcji mysql_query.

W odróżnieniu od pozostałych funkcji mysql_, zerowy wynik oznacza, że nie zostały wybrane żadne wiersze zaś wynik dodatni oznacza na ogół faktyczną liczbę wierszy, na które działały polecenia SQL.

Jak już wspomnieliśmy wcześniej, podczas korzystania z funkcji mysql_affected_rows pojawiają się pewne niespodziewane wyniki. Spójrzmy wiec najpierw na liczbę wierszy poddawaną działaniu polecenia INSERT, która zachowuje się tak jak oczekiwaliśmy. Po dopisaniu wyróżnionego niżej kodu do programu connect2.c nazwiemy go insert1.c:

#include <stdlib.h>

#include <stdio.h>

#include "mysql.h"

int main(int argc, char *argv[]) {

MYSQL my_connection;

int res;

mysql_init(&my_connection);

if (mysql_real_connect(&my_connection, "localhost",

"rick", "bar", "rick", 0, NULL, 0)) {

printf("Connection success\n");

res = mysql_query(&my_connection, "INSERT INTO children(fname, age)

VALUES('Ann', 3)");

if (!res) {

printf("Inserted %lu rows\n",

(unsigned long)mysql_affected_rows(&my_connection));

} else {

fprintf(stderr,"Insert error %d: %s\n", mysql_errno(&my_connection),

mysql_error(&my_connection));

}

my_close(&my_connection);

} else {

fprintf(stderr, "Connection failed\n");

if (mysql_errno(&my_connection)) {

fprintf(stderr, "Connection error %d: %s\n",

mysql_errno(&my_connection), mysql_error(&my_connection));

}

}

return EXIT_SUCCESS;

}

Tak jak tego oczekiwaliśmy, liczba wierszy podana przez program wynosi jeden.

Zmienimy teraz kod programu, zastępując część realizującą polecenie „INSERT” następującym fragmentem:

mysql_errno(&my_connection), mysql_error(&my_connection));

}

}

res = mysql_query(&my_connection, "UPDATE children SET AGE = 4

WHERE fname = 'Ann'");

if (!res) {

printf("Updated %lu rows\n",

(unsigned long)mysql_affected_rows(&my_connection));

} else {

fprintf(stderr, "Update error %d: %s\n", mysql_errno(&my_connection),

mysql_error(&my_connection));

}

Nowy program nazwiemy update1.c.

Załóżmy teraz, że w tabeli children są następujące dane:

+---------+--------+------+

| childno | fname | age |

+---------+--------+------+

| 1| Jenny | 14|

| 2| Andrew | 10|

| 3| Gavin | 4|

| 4| Duncan | 2|

| 5| Emma | 0|

| 6| Alex | 11|

| 7| Adrian | 5|

| 8| Ann | 3|

| 9| Ann | 4|

| 10| Ann | 3|

| 11| Ann | 4|

+---------+--------+------+

Po uruchomieniu programu update1 spodziewamy się, że zwrócona liczba wierszy będzie wynosić 4, ale program sygnalizuje 2, ponieważ w rzeczywistości zmienił tylko dwa wiersze, niezależnie od tego, że deklaracja WHERE wykrywa cztery. Jeśli funkcja mysql_affected_rows ma zwracać w takim przypadku 4, czyli wynik zgodny z innymi bazami danych, to do funkcji mysql_real_connect należy przekazać znacznik CLIENT_FOUND_ROWS, podobnie jak w programie update2.c:

if (mysql_real_connect(&my_connection, "localhost"'

"rick", "bar", "rick", 0, NULL, CLIENT_FOUND_ROWS)) {

Jeżeli przywrócimy poprzednie dane w bazie i uruchomimy tak zmodyfikowany program, to otrzymamy liczbę wierszy równą 4.

Funkcja mysql_affected_rows charakteryzuje się jeszcze jednym udziwnieniem, które objawia się przy usuwaniu danych z bazy. Jeżeli usuniemy dane spełniające kryterium podane w deklaracji WHERE, to mysql_affected_rows zawróci liczbę faktycznie usuniętych wierszy — tak jak tego oczekujemy. Jeżeli jednak nie ma deklaracji WHERE i z tego powodu zostaną usunięte wszystkie wiersze tabeli, to funkcja ta zwraca wartość zerową. Wynika to z tego, że w takich przypadkach w wyniku optymalizacji działania bazy usuwana jest cała tabela. Na takie zachowanie nie ma wpływu znacznik opcji CLIENT_FOUND_ROWS.

Polecenia zwracające dane

Nadszedł teraz czas na zajęcie się najczęściej używanym poleceniem SQL, a mianowicie poleceniem SELECT, które służy do pobierania danych z bazy.

MySQL obsługuje również polecenia SHOW, DESCRIBE oraz EXPLAIN, które również zwracają wyniki, ale nie będziemy ich tutaj omawiać. Jak zwykle, ich szczegółowy opis można znaleźć w podręczniku.

Jak pamiętamy z opisu PostgreSQL, możemy tam pobrać dane za pomocą polecenia SELECT, umieszczając je w wywołaniu pqexec (wtedy wszystkie dane zostaną pobrane w jednej porcji), albo użyć kursora do pobierania danych wiersz po wierszu (co nie dopuszcza do przeciążenia sieci lub programu klienta).

MySQL dysponuje prawie takimi samymi możliwościami wyboru metod pobierania danych, mimo że pobieranie wiersz po wierszu nie jest tu nazwane „operacją z użyciem kursora”. Interfejs (API) opisujący pobieranie danych za pomocą tych dwóch metod niewiele się różni, zatem łatwiej jest przełączyć się z użycia jednej metody na użycie drugiej.

Funkcje umożliwiające jednorazowe pobranie wszystkich danych

Za pomocą polecenia SELECT (lub innych poleceń zwracających dane) można pobrać wszystkie dane od razu używając jednego wywołania funkcji mysql_store_result:

MYSQL_RES *mysql_store_result(MYSQL *connection);

Aby zachować pobrane dane w tzw. zestawie wyników, funkcja ta musi być wywołana po funkcji mysql_query. Pobieranie danych z serwera i ich zachowanie w programie klienta następuje natychmiastowo. Funkcja zwraca wskaźnik do nie omawianej jeszcze struktury nazywanej zestawem wyników. Jeżeli wykonanie polecenia się nie uda, to zwracany jest wskaźnik o wartości NULL.

[[[ramka]]]

Podobnie jak w bazie PostgreSQL, tutaj zwrócenie wskaźnika NULL także oznacza, że wystąpił błąd i jest to coś innego niż brak danych. Nawet wówczas, gdy zwrócona wartość nie jest równa NULL, to nie oznacza to, że dane będą dostępne.

[[[koniec ramki]]]

Jeżeli wartość NULL nie była zwrócona, to można wywołać funkcję mysql_num_rows i pobrać liczbę wierszy faktycznie poddanych działaniu polecenia SQL (może ona być równa zeru).

my_ulonglong mysql_num_rows(MYSQL_RES *result);

Funkcja ta odnosi się do struktury wynikowej zwróconej przez mysql_store_result i zwraca liczbę wierszy w zestawie wynikowym, która może być równa zeru. Jeżeli wywołanie funkcji mysql_store_result odbyło się pomyślnie, to wywołanie mysql_num_rows także się uda.

Kombinacja funkcji mysql_store_result i mysql_num_rows stanowi prosty i intuicyjny sposób pobierania danych. Po udanym wywołaniu mysql_store_result wszystkie dane otrzymane w wyniku zapytania będą zachowane w programie klienta i można je pobierać ze struktury wynikowej bez narażania się na ryzyko utraty połączenia z bazą danych — dane są już przechowywane lokalnie. Można także bezpośrednio określić liczbę pobranych wierszy, co ułatwia programowanie. Jak wspomnieliśmy wcześniej, wszystkie pobrane dane są przesyłane do programu klienta w postaci jednej porcji. Przy zestawach wynikowych o dużych rozmiarach może to wymagać zaangażowania znacznej części zasobów serwera, sieci i klienta. Dlatego właśnie, pracując z dużymi zestawami danych, lepiej pobierać je mniejszymi porcjami. Wkrótce omówimy taki sposób pracy wykorzystujący funkcje mysql_use_result.

Po pobraniu wszystkich danych do programu klienta możemy pobierać je za pomocą funkcji mysql_fetch_row, a także przemieszczać się w zestawie wynikowym za pomocą funkcji mysql_data_seek, mysql_row_seek i mysql_row_tell. Zanim omówimy pobieranie danych porcjami, spójrzmy na opis tych funkcji.

mysql_fetch_row

MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

Funkcja ta odnosi się do struktury wynikowej otrzymanej w wyniku wywołania mysql_store_result i pobiera z niej pojedynczy wiersz, zwracając dane w postaci utworzonej przez siebie struktury zgodnej ze strukturą wiersza. Jeżeli nie ma danych lub wystąpi błąd, to zostanie zwrócona wartość NULL. Przetwarzanie danych w strukturze wiersza omówimy później.

mysql_data_seek

void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset);

Ta funkcja pozwala na dokonywanie przeskoków w zestawie wynikowym, ustawiając numer wiersza zwracanego w następnym wywołaniu mysql_fetch_row. Wartość offset jest numerem wiersza i musi się mieścić w przedziale od zera do wartości o jeden mniejszej niż liczba wierszy w zestawie wynikowym. Użycie wartości zerowej spowoduje, że w następnym wywołaniu mysql_fetch_row zostanie zwrócony pierwszy wiersz.

mysql_row_tell, mysql_row_seek

MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *result);

Ta funkcja zwraca wartość offsetu odniesioną do bieżącej pozycji w zestawie wynikowym. Nie jest to numer wiersza, więc nie można tej wartości użyć w funkcji mysql_data_seek. Można natomiast skorzystać z następującego wywołania:

MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset);

Spowoduje ono zmianę bieżącej pozycji w zestawie wynikowym i zwrócenie pozycji poprzedniej.

Powyższa para funkcji przydaje się czasem przy dokonywaniu przeskoków między znanymi miejscami w zestawie wynikowym. Należy jednak pamiętać o tym, że nigdy nie wolno pomylić wartości offsetu zwracanej przez funkcje mysql_row_tell i mysql_row_seek z numerem wiersza zwracanym przez mysql_data_seek. Wartości te nie mogą być używane zamiennie, ponieważ wyniki uzyskiwane w takich przypadkach będą błędne.

mysql_free_result

Przed użyciem tych pomocniczych funkcji omówimy ostatnią z nich, czyli mysql_free_result.

void mysql_free_result(MYSQL_RES *result);

Po zakończeniu pracy z zestawem wynikowym zawsze trzeba wywołać tę funkcję, pozwalając w ten sposób bibliotece MySQL na zwolnienie pamięci zajmowanej przez utworzone obiekty.

Pobieranie danych

Możemy już utworzyć pierwszy program pobierający dane z bazy MySQL. Mamy zamiar wybrać zawartość wszystkich wierszy, w których pole age ma wartość większą niż 5. Niestety, nie potrafimy jeszcze przetwarzać tych danych, dlatego posłużymy się pętlą pobierającą je kolejno. Program nazywa się select1.c:

#include <stdlib.h>

#include <stdio.h>

#include "mysql.h"

MYSQL my_connection;

MYSQL_RES *res_ptr;

MYSQL_ROW sqlrow;

int main(int argc, char *argv[]) {

int res;

mysql_init(&my_connection);

if (mysql_real_connect(&my_connection, "localhost", "rick",

"bar", "rick", 0, NULL, 0)) {

printf("Connection success\n");

res = mysql_query(&my_connection, "SELECT childno, fname,

age FROM children WHERE age > 5");

if (res) {

printf("SELECT error: %s\n", mysql_error(&my_connection));

} else {

res_ptr = mysql_store_result(&my_connection);

if (res_ptr) {

printf("Retrived %lu rows\n", (unsigned long)mysql_num_rows(res_ptr));

while ((sqlrow = mysql_fetch_row(res_ptr)) {

printf("Fetched data...\n");

}

if (mysql_errno(&my_connection)) {

fprintf(stderr, "Retrive error: %s\n", mysql_error(&my_connection));

}

}

mysql_free_result(res_ptr);

}

mysql_close(&my_connection);

} else {

fprintf(stderr, "Connection failed\n");

if (mysql_errno(&my_connection)) {

fprintf(stderr, "Connection error %d: %s\n",

mysql_errno(&my_connection), mysql_error(&my_connection));

}

}

return EXIT_SUCCESS;

}

Wyróżniono tu ważny fragment kodu, który jest odpowiedzialny za pobranie zestawu wyników i przejście w pętli przez pobrane dane.

Pobieranie danych po jednym wierszu naraz

Aby pobierać dane wiersz po wierszu, a nie pobierać ich w całości i przechowywać ich w programie klienta, musimy zastąpić wywołanie mysql_store_result przez wywołanie funkcji mysql_use_result:

MYSQL_RES *mysql_use_result(MYSQL *connection);

Ta funkcja również odnosi się do obiektu definiującego połączenie i zwraca wskaźnik do zestawu wynikowego lub NULL w wypadku wystąpienia błędu. Wskaźnik ten jest zwracany podobnie jak w funkcji mysql_store_result, natomiast nie następuje faktyczne pobranie danych do zestawu wynikowego, a tylko jego inicjacja i ustawienie w gotowości do przyjęcia danych.

[[[ramka]]]

Aby naprawdę pobrać dane, trzeba powtarzać wywołania mysql_fetch_row tak jak poprzednio, aż do pobrania wszystkich danych. Jeżeli przy wywołaniu funkcji mysql_use_result wystąpi błąd, to następujące po tym pobieranie danych zostanie zakłócone.

[[[koniec ramki]]]

Co się stanie, jeżeli użyjemy funkcji mysql_use_result? Potencjalnie zyskujemy większą swobodę, ponieważ mniej obciążamy sieć i program klienta, ale istnieje także wada takiego sposobu pobierania danych: przy wywołaniu mysql_use_result nie możemy skorzystać z funkcji mysql_num_rows, mysql_data_seek, mysql_row_seek i mysql_row_tell. W rzeczywistości nie jest to do końca prawdą, ponieważ możemy wywołać mysql_num_rows, lecz nie otrzymamy wtedy liczby dostępnych wierszy aż do momentu pobrania ostatniego z nich za pomocą funkcji mysql_fetch_result. Faktycznie więc nie przyda się to do niczego.

Powiększamy także opóźnienie miedzy żądaniami pobrania wiersza, ponieważ za każdym razem musi on być przesyłany przez sieć. Dodatkowy problem może stwarzać możliwość wystąpienia błędu sieci podczas utrzymywania otwartego połączenia z bazą danych, zanim wszystkie dane zostaną pobrane. Nie będziemy wówczas mieli rzeczywistego dostępu do danych, ponieważ nie są one przechowywane lokalnie.

Mamy także duże korzyści: wyrównujemy obciążenie sieci i znacznie zmniejszamy potencjalnie duże zapotrzebowanie na pamięć (ang. storage overhead) w programie klienta. Przy dużych zestawach danych prawie zawsze preferuje się pobieranie wiersza po wierszu za pomocą funkcji mysql_use_result.

Zmiana w programie select1.c polegająca na użyciu funkcji mysql_use_result jest łatwa do przeprowadzenia. Otrzymujemy w ten sposób program select2.c, w którym zaznaczyliśmy zmienione wiersze:

if (res) {

printf("SELECT error: %s\n", mysql_error(&my_connection));

} else {

res_ptr = mysql_use_result(&my_connection);

if (res_ptr) {

while ((sqlrow = mysql_fetch_row(res_ptr))) {

printf("Fetched data...\n");

}

if (mysql_errno(&my_connection)) {

printf("Retrieve error: %s\n", mysql_error(&my_connection));

}

}

mysql_free_result(res_ptr);

}

Zwróćmy uwagę na to, że nie jest tu znana liczba wierszy pobranych bezpośrednio po otrzymaniu wyniku. Oprócz tego użyto tutaj wcześniej stosowanej metody obsługi błędów polegającej na sprawdzaniu wartości zwracanej przez mysql_errno(&my_connection). Pisząc program wykorzystujący funkcję mysql_store_result i mając zamiar zmienić ją w przyszłości na funkcję mysql_use_result, lepiej od razu wprowadzić stosowne zmiany. Dla pewności można także sprawdzać wartości zwracane przez wszystkie funkcje.

Przetwarzanie zwróconych danych

Samo pobieranie danych nie ma większego sensu, jeżeli nie będziemy z nimi niczego robić. Podobnie jak w bazie PostgreSQL, tutaj również rozróżnia się dwa rodzaje danych:

Najpierw zobaczymy, jak można odtwarzać i wyświetlać dane, a następnie zajmiemy się sposobami określania nazw kolumn i innych informacji o danych.

W nowszych wersjach MySQL dostępna jest funkcja mysql_field_client, która odnosi się do obiektu połączenia i zwraca liczbę pól w zestawie wynikowym:

unsigned int mysql_field_count(MYSQL *connection);

Ta funkcja może być także wykorzystana do bardziej ogólnego przetwarzania, np. do określania, czy przy wywołaniu mysql_store_result wystąpił błąd. Jeżeli mysql_store_result zwróci NULL, ale funkcja mysql_field_count zwraca liczbę większą od zera, to możemy być pewni, że w zestawie wynikowym są jakieś dane, chociaż wystąpił błąd przy ich odtwarzaniu. Jeżeli jednak funkcja mysql_field_count zwraca zero, to nie ma żadnych kolumn do pobrania, a zatem próba zachowania wyniku nie uda się.

Prawdopodobnie taka metoda będzie częściej stosowana wówczas, gdy polecenie SQL nie jest znane z góry albo gdy programista chce utworzyć moduł przeznaczony do całkowicie ogólnego przetwarzania zapytań.

W programach wykorzystujących starsze wersje MySQL można spotkać wywołania funkcji mysql_num_fields. Posługują się one wskaźnikami struktury połączenia lub struktury wyników i zwracają liczbę wierszy.

W nowszych programach należy w zasadzie używać funkcji mysql_field_count, chyba że program ma współpracować ze starszą wersją MySQL.

Jeżeli chcemy po prostu otrzymać informację w postaci niesformatowanego tekstu i wiemy, w jaki sposób bezpośrednio wyświetlić dane, to możemy wykorzystać strukturę MYSQL_ROW zwracaną przez funkcję mysql_fetch_row. Dodamy więc do naszego programu select2.c prostą funkcję display_row wyświetlającą dane.

Zwróćmy uwagę na to, że w celu uproszczenia programu struktura połączenia, struktura wyników i informacja zwracana przez funkcję mysql_fetch_row mają charakter globalny. W kodzie produkcyjnym nie zalecamy takiego podejścia.

Oto jak wygląda nasza prosta funkcja wyświetlająca dane:

void display_row() {

unsigned int field_count;

field_count = 0;

while (field_count < mysql_field_count(&my_connection)) {

printf("%s ", sqlrow[field_count]};

field_count++;

}

printf("\n");

}

Dodamy ją do programu select.c oraz dołączymy jej deklarację i wywołanie:

void display_row();

intmsin(int argc, char *argv[]) {

int res;

mysql_init(&mysql_real_connect(&my_connection, "localhost", "rick",

"bar, "rick", 0, NULL, 0)) {

printf("Connection success\n");

res = mysql_query(&my_connection, "SELECT childno, fname,

age FROM children WHERE age > 5");

if (res) {

printf("SELECT error: %s\n", mysql_error(&my_connection));

} else {

res_ptr = mysql_use_result(&my_connection);

if (res_ptr) {

while ((sqlrow = mysql_fetch_row(res_ptr))) {

printf("Fetched data...\n");

display_row();

}

Nowy program nazwiemy select3.c. Następnie skompilujemy go i uruchomimy:

$ gcc -I/usr/include/mysql select3.c -L/usr/lib/mysql -lmysqlclient -o select3

$ ./select3

Connection success

Fetched data...

1 Jenny 14

Fetched data...

2 Andrew 10

Fetched data...

6 Alex 11

$

Jak widać, wszystko działa prawidłowo, chociaż formatowanie wyników jest bardzo ograniczone. Nie braliśmy pod uwagę możliwych wartości NULL w zwracanych wynikach. Jeżeli trzeba np. przedstawić dane w postaci tabelarycznej, to należy uzyskać zarówno dane, jak i informacje dodatkowe o tych danych w nieco innej postaci. W jaki sposób mamy to zrobić?

Zamiast korzystania z obiektu będącego wierszem (zdefiniowanego jako char **), który jest zwracany przez mysql_fetch_row, możemy pobierać informację w postaci pojedynczych pól do struktury zwierającej zarówno dane, jak i metadane. Wykonuje to funkcja mysql_fetch_field:

MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result);

Należy powtarzać wywołania tej funkcji wielokrotnie, przechodząc po kolei przez wszystkie pola. Jeżeli nie będzie już żadnych pól do przetworzenia, to funkcja zwróci wartość NULL. Wskaźnik zwróconej struktury pola może być także wykorzystany do uzyskania różnych informacji o kolumnie przechowywanej w tej strukturze. Definicja tej struktury jest podana w pliku mysql.h:

Pole w strukturze MYSQL_FIELD

Znaczenie

char *name;

Nazwa kolumny jako napis.

char *table;

Nazwa tabeli, z której pochodzi kolumna. Przydaje się, gdy dane są pobierane z większej liczby tabel. Należy pamiętać, że dla wyliczanych wartości (np. MAX) jako nazwę tabeli otrzymujemy pusty napis.

char *def;

Jeśli wywołamy funkcję mysql_list_fields (która nie jest tu opisana), to pole def będzie zawierało wartość domyślną dla kolumny.

enum enum_field_types type;

Typ wartości w kolumnie. Patrz niżej.

unsigned int length;

Szerokość kolumny określona przy definiowaniu tabeli.

unsigned int max_length;

Jeśli użyjemy funkcji mysql_store_result, to pole max_length będzie zawierać rzeczywistą znalezioną największą szerokość kolumny. Pole to nie jest ustawiane przy wywołaniu mysql_use_result.

unsigned int flags;

Znaczniki informujące o definicji kolumny, a nie o faktycznie znalezionych danych. Najczęściej są to: NOT_NULL_FLAG, PRI_KEY_FLAG, UNSIGNED_FLAG, AUTO_INCREMENT_FLAG, BINARY_FLAG. Pełny spis znajduje się w dokumentacji.

unsigned int decimals;

Liczba miejsc dziesiętnych, ważna tylko w polach liczbowych.

Zakres typów kolumn jest bardzo szeroki. Pełna lista jest podana w pliku mysql_com.h i w dokumentacji. Najczęściej spotyka się następujące typy:

Jednym ze szczególnie przydatnych makropoleceń jest IS_NUM, zwracające wartość TRUE, jeżeli dane pole jest typu liczbowego, na przykład:

if (IS_NUM(mysql_field_ptr->type)) printf("Numeric type field\n");

Przed modyfikacją naszego programu musimy jeszcze wspomnieć o pewnej dodatkowej funkcji:

MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result,

MYSQL_FIELD_OFSET offset);

Funkcja ta umożliwia zmianę bieżącego numeru pola, który jest powiększany automatycznie przy każdym wywołaniu mysql_fetch_field, a po przekazaniu offsetu równego zero — skok do pierwszej kolumny w wyniku. Funkcja zwraca poprzednią wartość offsetu.

Mamy już wystarczająco dużo informacji do wprowadzenia takich zmian w przykładowym programie wybierającym dane, aby informacja o kolumnie stała się dostępna. Dzięki temu można będzie wyświetlać dane w bardziej przejrzysty sposób.

Nasz zmodyfikowany program nazywa się select4.c. Pokazujemy go tutaj w całości, aby czytelnik dysponował pełny przykładem. W programie nie próbujemy analizować typów kolumn, pokazując jedynie obowiązujące zasady.

#include <stdlib.h>

#include <stdio.h>

#include "mysql.h"

MYSQL my_connection;

MYSQL_RES *res_ptr;

MYSQL_ROW sqlrow;

void display_header();

void display_row();

int main(int argc, char *argv[]) {

int res;

int first_row = 1;

mysql_init(&my_connection);

if (mysql_real_connect(&my_connection, "localhost", "rick",

"bar", "rick", 0, NULL, 0)) {

printf("Connection success\n");

res = mysql_query(&my_connection, "SELECT childno, fname,

age FROM children WHERE age > 5");

if (res) {

fprintf(stderr, "SELECT error: %s\n", mysql_error(&my_connection));

} else {

res_ptr = mysql_use_result(&my_connection);

if (res_ptr) {

display_header();

while ((sqlrow = mysql_fetch_row(res_ptr))) {

if (first_row) {

display_header();

first_row = 0;

}

display_row();

}

if (mysql_errno(&my_connection)) {

fprintf(stderr, "Retrieve error: %s\n",

mysql_error(&my_connection));

}

}

mysql_free_result(res_ptr);

}

mysql_close(&my_connection);

} else {

fprintf(stderr, "Connection failed\n");

if (mysql_errno(&my_connection)) {

fprintf(stderr, "Connection error %d: %s\n",

mysql_errno(&my_connection),

mysql_error(&my_connection));

}

}

return EXIT_SUCCESS;

}

void display_header() {

MYSQL_FIELD *field_ptr;

printf("Column details:\n");

while ((field_ptr = mysql_fetch_field(res_ptr)) != NULL) {

printf("\t Name: %s\n", field_ptr->name);

printf("\t Type: ");

if (IS_NUM(field_ptr->type)) {

printf("Numeric field\n");

} else {

switch(field_ptr->type) {

case FIELD_TYPE_VAR_STRING:

printf("VARCHAR\n");

break;

case FIELD_TYPE_LONG:

printf("LONG\n");

break;

default:

printf("Type is %d, check in mysql_com.h\n", field_ptr->type);

} /* switch */

} /* else */

printf("\t Max width %d\n", field_ptr->length);

if (field_ptr->flags & AUTO_INCREMENT_FLAG)

printf("\t Auto increments\n");

printf("\n");

} /* while */

}

void display_row() {

unsigned int field_count;

field_count = 0;

while (field_count < mysql_field_count(&my_connection)) {

if (sqlrow[field_count]) printf("%s ", sqlrow[field_count]);

else printf("NULL");

field_count++;

}

printf("\n");

}

Po kompilacji uruchamiamy program i otrzymujemy:

$ ./select4

Connection success

Column details:

Name: childno

Type: Numeric field

Max width 11

Auto increments

Name: fname

Type: VARCHAR

Max width 30

Name: age

Type: Numeric field

Max width 11

Column details:

1 Jenny 14

2 Andrew 10

6 Alex 11

$

Nie wygląda to wprawdzie w pełni doskonale, ale spełnia swoją rolę informacyjną.

Istnieją także inne funkcje umożliwiające pobieranie tablic pól i przeskakiwanie między kolumnami. W ogólnym przypadku wszystkie wymagane funkcje są zawarte w naszym przykładzie, natomiast zainteresowani czytelnicy mogą dowiedzieć się więcej z podręcznika opisującego MySQL.

Funkcje dodatkowe

Istnieje kilka funkcji API, których nie można zaklasyfikować do omawianych dotychczas kategorii. Są one jednak przydatne i dlatego warto je poznać. Jeżeli tylko jest to możliwe, to wszelkie operacje na bazie danych powinny się odbywać za pośrednictwem interfejsu mysql_query. Istnieje np. w API funkcja mysql_create_db, która służy do tworzenia bazy danych, ale prostsze jest użycie polecenia CREATE DATABASE w połączeniu z mysql_query, ponieważ do utworzenia bazy wystarcza wtedy jedynie znajomość SQL, a nie specjalizowanych wywołań API.

Dodatkowe funkcje API, które bywają przydatne w praktyce, są następujące:

mysql_get_client_info

char *mysql_get_client_info(void);

Zwraca informacje o wersji biblioteki używanej przez klienta.

mysql_get_host_info

char *mysql_get_host_info(MYSQL *connection);

Zwraca informacje o połączeniu z serwerem.

mysql_get_server_info

char *mysql_get_server_info(MYSQL *connection);

Zwraca informacje o serwerze, z którym jesteśmy w danym momencie połączeni.

mysql_info

char *mysql_info(MYSQL *connection);

Zwraca informacje o ostatnio wykonanym zapytaniu, lecz dotyczy to tylko kilku rodzajów zapytań, głównie INSERT i UPDATE. W innych przypadkach zwraca wartość NULL.

mysql_select_db

int mysql_select_db(MYSQL *connection, const char *dbname);

Zmienia domyślną bazę danych na tę, która jest podana jako parametr, pod warunkiem, że użytkownik ma właściwe uprawnienia. W przypadku powodzenia zwracane jest zero.

mysql_shutdown

int mysql_shutdown(MYSQL *connection);

Zamyka serwer bazy danych, do którego jesteśmy podłączeni, jeżeli mamy odpowiednie uprawnienia. W przypadku powodzenia zwracane jest zero.

Materiały źródłowe

Głównym źródłem informacji o MySQL jest macierzysta strona WWW pod adresem: http://www.mysql.com.

Książki, do których można zajrzeć to: MySQL, Paul DuBois, wyd. New Riders (ISBN 0-7357-0921-1) oraz MySQL & mSQL, Randy Jay Yarger, George Reese, Tim King, wyd. O'Reilly & Associates (ISBN 1-56592-434-7).

Podsumowanie

W tym rozdziale zapoznaliśmy się pobieżnie z MySQL. Nie jest to wprawdzie baza tak bogata we właściwości, jak PostgreSQL, ale bez wątpienia jest to produkt bardzo wydajny i wyposażony w interfejs do programów w języku C.

Zobaczyliśmy sposób instalacji i konfiguracji podstawowej bazy danych MySQL i niektóre ważne polecenia pomocnicze, a następnie zapoznaliśmy się z API dla języka C, jednego z wielu języków, z których można mieć dostęp do danych w bazie MySQL.

Główną zaletą MySQL w porównaniu z PostgreSQL jest wydajność. Jako baza danych tylko do odczytu jest ona stosowana w wielu ośrodkach WWW. Jej głównymi słabościami są słabsze wspomaganie standardu SQL i brak możliwości obsługi transakcji.

19 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

19 D:\1-dokumenty\Word\Zaawansowane programowanie w systemie Linux\R-05-05.doc



Wyszukiwarka

Podobne podstrony:
R-00-07(1), Informacje dot. kompa
R-25-07(1), Informacje dot. kompa
R-26-07(1), Informacje dot. kompa
R04-05(2), Informacje dot. kompa
r18-05(1), Informacje dot. kompa
R15-05(2), Informacje dot. kompa
r01-05(1), Informacje dot. kompa
r03-05(1), Informacje dot. kompa
r02-05(1), Informacje dot. kompa
r16-05(1), Informacje dot. kompa
r14-05(1), Informacje dot. kompa
R07-05(3), Informacje dot. kompa
05(1), Informacje dot. kompa
r11-05(1), Informacje dot. kompa
R01-05(4), Informacje dot. kompa
R02-05(4), Informacje dot. kompa
r09-05(2), Informacje dot. kompa
R00-05(4), Informacje dot. kompa
R10-05(2), Informacje dot. kompa

więcej podobnych podstron