Introduzione
PostgreSQL è unanimemente riconosciuto come il miglior ORDBMS (Object Relational DataBase Managemente System) non commerciale presente ad oggi nel panorama mondiale, l'unico fra i prodotti open source, a poter competere ad armi pari con colossi commerciali come Oracle, Microsoft SQL Server, Interbase, Db2, e così via.
PostgreSQL è, come accennato, un prodotto open source, il che vuol dire che il suo sorgente (ossia il codice C con cui è stato sviluppato) è liberamente consultabile ed anche modificabile a proprio piacimento; esistono anche comunque, delle versioni commerciali di PostgreSQL , ad esempio quella di Red Hat, che al sorgente aggiunge anche un tool grafico di installazione ed un'assistenza limitata all'installazione stessa.
Fra le caratteristiche principali di PostgreSQL (che non analizzeremo perchè esula dagli scopi dell'articolo), ricordiamo:
- Supporto per le sub-queries
- Supporto per le chiavi esterne (Foreign Keys)
- Supporto dei Triggers
- Supporto delle View
- Supporto nativo per le Transazioni
- Supporto di tipi di dati geometrici (esclusivo di PostgreSQL)
- Supporto di tipi relativi alle reti (esclusivo di PostgreSQL)
- Supporto di indici B-tree, R-tree e Hash
- Possibilità di creare delle funzioni e dei tipi personalizzati
- Utilizzo del Multi Version Concurrency Control o MVCC
- Supporto pressochè completo delle specifiche di SQL99
- Possibilità di utilizzare, come linguaggi procedurali interni, non solo quello nativo (PL/pgSQL), ma anche Perl, Python e TCL
PostgreSQL gira su molti sistemi operativi, i BSD in primis (è nato e cresciuto sul sistema BSD), ma anche Linux, Solaris, HP-UX, AIX, Irix e Windows NT/2000.
A livello di semplice curiosità (ma è una curiosità che accomuna tutti i Database dove sia presente la parola SQL), ricordiamo che, come riportano
le FAQ del sito ufficiale, la corretta pronuncia di PostgreSQL è Post-Gres-Q-L, per noi italiani Post-Gres-chiu-el.
Un po' di storia
PostgreSQL , così come lo conosciamo (e lo chiamiamo) adesso, è frutto di un lungo lavoro iniziato nel lontano 1977; fu infatti in quell'anno che iniziò lo sviluppo di un progetto chiamato Ingres, presso l'Università della California a Berkeley, progetto che poi fu preso e migliorato dalla Relational Technologies/Ingres Corporation che produsse i primi server database che ottennero successo commercialmente. (Ingres Corp. fu in seguito acquistata dalla Computer Associates).
Nel 1986 poi, un altro gruppo, guidato das Michael Stonebraker, proseguì lo sviluppo del sorgente originario di Ingres, dando vita ad un RDBMS chiamato Postgres il cui codice fu poi preso dalla Illustra e sviluppato come prodotto commerciale. (L'Illustra fu più tardi comprata da Informix e integrata nel Informix's Universal Server).
Nel 1994 due studenti laureati a Berkeley, Jolly Chen e Andrew Yu, aggiunsero un interprete SQL a Postgres, e chiamarono il progetto Postgres95.
Nell'estate del 1996, divenne chiaro che la domanda di un server di Database open-source era grande e che un nuovo team di sviluppo doveva essere formato. Marc G. Fournier, in Toronto, Canada, si offrì di ospitare la mailing list, e mise a disposizione un server per apportare le modifiche ai file sorgenti tramite CVS.
All'inizio c'erano quattro persone impegnate nello sviluppo (il cui nome, nel frattempo, era diventato l'attuale PostgreSQL ), Marc G. Fournier, Thomas Lockhart da Pasadena, California, Vadim Mikheev da Krasnoyarsk, Russia, e Bruce Momjian.
Da allora l'evoluzione di PostgreSQL non si è più fermata, fino a diventare quello che è ora (la versione corrente, al momento in cui scrivo, è la 7.3.4), il più completo e sofisticato sistema di database relazionale ad oggetti con sorgente aperto.
Per chi volesse approfondire un pò la storia dei PostgreSQL, ecco il link all'articolo "The History of PostgreSQL Development", di Bruce Momjian, di cui ho tradotto alcune parti: http://www.daemonnews.org/199907/devhistory.html.
Installazione
Cercherò adesso di dare delle brevi note sull'intallazione di PostgreSQL in ambiente Linux, premettendo che non voglio assolutamente esaurire l'argomento.
Le istruzioni di installazione, sono quelle indicate nel "Manuale pratico di PostgreSQL", (le stesse che ho seguito io e con me hanno funzionato), istruzioni analoghe sono quelle riportate sul manuale ufficiale (http://www.PostgreSQL.org/docs/7.4/static/installation.html).
Per l'installazione su Windows, mi limito a fornire alcuni link utili:
http://www.PostgreSQL.org/docs/7.4/static/install-win32.html
http://techdocs.PostgreSQL.org/guides/InstallingOnWindows
http://www.pluto.linux.it/ildp/HOWTO/PostgreSQL-HOWTO 27.html
Dando per scontato che si sia correttamente scaricato il pacchetto PostgreSQL-[versione].tar.gz (al momento in cui scrivo, l'ultima versione è la 7.3.4) da uno dei mirrors indicati sul sito ufficiale e che sulla linux box siano correttamente installati tutti i software per la compilazione dei sorgenti, il primo passo da compiere è creare l'utente proprietario dei file di database, lo chiameremo postgres:
useradd postgres
a questo proposito aggiungo che è ASSOLUTAMENTE sconsigliato usare l'utente root come superuser di PostgreSQL.
A questo punto copiamo e scompattiamo il pacchetto con i sorgenti in una directory a scelta, in questo esempio in /usr/local/src:
cp PostgreSQL -[versione].tar.gz /usr/local/src
cd /url/local/src
tar -xzvf PostgreSQL -[versione].tar.gz
assegniamo quindi la directory con i sorgenti di PostgreSQL all'utente postgres precedentemente creato, per consentire a quest'ultimo di compilare i sorgenti stessi:
chown -R postgres.postgres PostgreSQL -[versione]
Occorre adesso configurare le varie opzioni di installazione, il comando minimale, a questo proposito, è:
./configure
ovviamente le opzioni sono molteplici, è sufficiente digitare
./configure --help
per averne una panoramica completa.
Il passaggio successivo è quello della compilazione dei sorgenti, tramite il comando make (o gmake)
make
terminata la compilazione (che può durare anche parecchio, siate pazienti...), sarebbe opportuno (ma non è obbligatorio) fare i cosìddetti test di regressione, che servono essenzialmente a verificare se tutto è andato per il verso giusto:
make check
è il momento dell'installazione di PostgreSQL (da eseguire come root):
make install
quindi assegniamo la proprietà della directory di installazione (/usr/local/pgsql quella di default) all'utente postgres:
chown -R postgres.postgres /usr/local/pgsql
A questo punto, se si è installato PostgreSQL in una directory non compresa nella variabile di ambiente PATH, si pùò aggiungere, per comodità, la directory alla var:
PATH=/usr/local/pgsql/bin:$PATH
export PATH
Infine inizializziamo e avviamo postgreQsl con i seguenti comandi (dando per scontato che la directory di installazione sia quella di default):
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data -l /tmp/pgsql.log start
con l'opzione -l /tmp/pgsql.log, redirigiamo tutte le informazioni per il debugging nel file /tmp/pgsql.log
Se tutto è andato bene, possiamo utilizzare l'interfaccia a riga di comando psql per la gestione del nostro server, magari previa creazione di un database di prova:
createdb test
psql test
Confronto con MySQL
Prima di cominciare ad approfondire l'interazione PHP-PostgreSQL, cosa che faremo nel prossimo articolo, vorrei segnalare alcuni link in cui si approfondisce l'interessante tema del confronto fra MySQL e PostgreSQL , interessante perchè non c'è dubbio alcuno che il DBMS di gran lunga più usato insieme a PHP, sia MySQL.
http://www.diff.org/diff/cinque/PostgreSQL _MySQL.shtml
http://www.itportal.it/developer/speciali/rdbms
http://www.itportal.it/developer/speciali/rdbms2
http://www.itportal.it/developer/speciali/rdbms3
http://www.sitepoint.com/article/529
http://www.sitepoint.com/article/542.
Primi test: connessione
Dopo l'introduzione spesa nel primo articolo dedicato a PHP e PostgreSQL, è arrivato il momento di cominciare ad interagire con il nostro nuovo Database (tramite PHP, ovviamente), e lo faremo utizzando la specifica libreria (come noto una delle pecche di PHP, è la mancanza di una interfaccia comune per la connessione ai DB, PEAR: una montagna di codice PHP a parte) che PHP ci mette a disposizione.
Facciamo un passo indietro, torniamo all'interfaccia a riga di comando psql e creiamo un utente, il comando da utilizzare è il seguente:
CREATE USER test_user WITH PASSWORD 'pass_test_user';
creiamo adesso un Database e attribuiamone la proprietà all'utente appena creato:
CREATE DATABASE db_test_user OWNER test_user;
adesso l'utente test_user, avrà i privilegi sul database db_test_user, che è quello su cui lavoreremo.
Il primo passo, naturalmente, è la connessione al server, a questo scopo PHP ci mette a disposizione la funzione pg_connect() (e pg_pconnect, per le connessioni persistenti), da utilizzare in questo modo:
<?php
$conn = @pg_connect('dbname=db_test_user user=test_user password=pass_test_user');
if(!$conn) {
die('Connessione fallita !<br />');
} else {
echo 'Connessione riuscita !<br />';
}
?>
La stringa di connessione poteva anche essere più semplice, ad esempio si poteva omettere la password, o poteva essere obbligatorio indicare anche l'host, tutto dipende dal settaggio del file pg_hba.conf (di default si trova in /usr/local/pgsql/data), che viene letto dal server all'avvio e contiene tutte le impostazioni di autenticazione.
La configurazione di default è molto permissiva e consente a qualsiasi utente locale di connettersi senza password, per renderla più restrittiva, occorre modificare, quantomeno, la voce METHOD (di default impostata a trust). Maggiori dettagli sull'autenticazione, si trovano nel capitolo 19 della documentazione ufficiale di PostgreSQL.
Salviamo il contenuto di questo script in un file chiamato pg_conn.php, che richiameremo poi negli altri script.
Creiamo adesso il file con la stringa di chiusura della connessione, utilizzando la funzione pg_close(), anche se, come avverte il manuale, il suo uso non è normalmente necessario, dal momento che le connessioni non persistenti vengono chiuse automaticamente alla fine dell'esecuzione dello script.:
<?php
pg_close($conn);
?>
Salviamo il contenuto di questo script in un file chiamato pg_close_conn.php, che richiameremo poi negli altri script.
Piccola osservazione, valevole anche per gli script successivi: si sarà notato l'uso della chiocciolina (@) davanti al nome della funzione, questo fa sì che PHP non restituisca alcun errore in caso di problemi e ci consente una rudimentale personalizzazione della gestione degli errori.
Costruiamo il primo db
Ora che la connessione è andata a buon fine (almeno si spera...) possiamo finalmente accingerci a costruire il primo Db, creando una o più tabelle con cui interagire. Nel nostro esempio, ci limiteremo a creare una tabella destinata a contenere una seria di contatti, e la
chiameremo agenda, vediamo subito la sintassi:
<?php
require('pg_conn.php');
if($sequence_query = @pg_query($conn, "
CREATE SEQUENCE agenda_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 2147483647
")) {
echo 'Sequenza <b>agenda_id_seq</b> creata !<br />';
} else {
die("Error nella query: " . pg_last_error($conn));
}
if($table_query = @pg_query($conn, "
CREATE TABLE agenda (
id int4 DEFAULT nextval('agenda_id_seq') NOT NULL,
nome varchar(150) DEFAULT '' NOT NULL,
indirizzo varchar(255) DEFAULT '' NOT NULL,
email varchar(255) DEFAULT '' NOT NULL,
CONSTRAINT db_test_user_agenda_pkey PRIMARY KEY (id))
")) {
echo 'Tabella <b>agenda</b> creata !<br />';
} else {
die("Error nella query: " . pg_last_error($conn));
}
require('pg_close_conn.php');
?>
Abbiamo parlato di tabella, ma la prima query non sembra proprio destinata a creare una tabella, e allora di che si tratta ?
La domanda è legittima, soprattutto da parte di chi ha sempre utilizzato MySQL come DBMS, e ha quindi avuto a che fare con la proprietà auto_increment, per far sì che il valore di un campo (in genere della chiave primaria) si incrementasse automaticamente. In PostgreSQL non funziona così, è necessario creare una sequenza, ossia un oggetto che rappresenta un valore numerico che incrementa automaticamente.
La sintassi per crearla è quella vista sopra, per altro avremmo potuto limitarci alla prima riga (CREATE SEQUENCE nome_sequenza), perchè gli altri valori inseriti sono quelli di default, ma sono stati indicati per una maggiore comprensione (per saperne di più sulle sequenze rimandiamo al riferimento del manuale ufficiale).
Altre tre considerazioni:
- La prima riguarda sempre le sequenze e completa il discorso appena fatto, come default del campo id della tabella, infatti, abbiamo indicato la funzione nextval, una delle funzioni di manipolazione delle sequenze (insieme a currval() e setval()), che non fa altro che incrementare il valore della sequenza indicata e restituire il nuovo valore.
- La seconda considerazione riguarda la parola CONSTRAINT, che stà per vincolo (di tabella, in questo caso), ossia un attributo particolare, che, nello specifico, crea una chiave primaria sul campo id, e, implicitamente, anche i due vincoli NOT NULL e UNIQUE.
- La terza considerazione riguarda l'uso della funzione @pg_query(), solo per dire che è utilizzabile a partire da PHP 4.2.0, per le versioni precedenti occorre usare pg_exec().
Eseguiamo delle query - INSERT
Cominciamo adesso a popolare la nostra agenda, eseguendo una serie di query di INSERT:
<?php
require('pg_conn.php');
if($insert_query = @pg_query($conn, "
INSERT INTO agenda
(nome, indirizzo, email)
VALUES('Giancarlo', 'via dalle scatole, 23', 'gm@html.it')
")) {
echo 'Contatto <b>Giancarlo</b> inserito !<br />';
} else {
die("Errore nella query: " . pg_last_error($conn));
}
if($insert_query = @pg_query($conn, "
INSERT INTO agenda
(nome, indirizzo, email)
VALUES('Chris', 'via dotto, 18', 'chris@html.it')
")) {
echo 'Contatto <b>Chris</b> inserito !<br />';
} else {
die("Errore nella query: " . pg_last_error($conn));
}
if($insert_query = @pg_query($conn, "
INSERT INTO agenda
(nome, indirizzo, email)
VALUES('Fabio Heller', 'via dei Geni, 1', 'fabio_heller@html.it')
")) {
echo 'Contatto <b>Fabio Heller</b> inserito !<br />';
} else {
die("Errore nella query: " . pg_last_error($conn));
}
if($insert_query = @pg_query($conn, "
INSERT INTO agenda
(nome, indirizzo, email)
VALUES('Saibal', 'via non andar, 1', 'saibal@html.it')
")) {
echo 'Contatto <b>Saibal</b> inserito !';
} else {
die("Errore nella query: " . pg_last_error($conn));
}
require('pg_close_conn.php');
?>
Il codice non necessita di particolari spiegazioni, trattandosi di semplici query INSERT effettuate tramite comandi SQL standard.
Eseguiamo delle query - SELECT
Occupiamoci adesso di estrarre e visualizzare i dati appena inseriti, utilizzando un codice HTML minimale (non è questo ciò che ci interessa):
<?php
require('pg_conn.php');
if(!$query = @pg_query("SELECT * FROM agenda"))
die("Errore nella query: " . pg_last_error($conn));
echo <<<EOD
<table border="1" cellspacing="2" cellpadding="2">
<tr>
<td><b>ID</b></td>
<td><b>NOME</b></td>
<td><b>INDIRIZZO</b></td>
<td><b>Email</b></td>
</tr>
EOD;
while($row = pg_fetch_assoc($query)) {
echo "nt<tr>ntt<td>{$row['id']}</td>ntt<td>{$row['nome']}</td>ntt";
echo "<td>{$row['indirizzo']}</td>ntt<td>{$row['email']}</td>nt</tr>";
}
echo <<<EOD
</table>
EOD;
require('pg_close_conn.php');
?>
Anche qui il codice è (volutamente) molto semplice, eseguiamo una normalissima query SELECT per estrarre tutti i records senza alcuna condizione e memorizziamo nella variabile query la risorsa che fa riferimento alla query stessa (sul tipo risorsa, si può consultare questo link), e che utilizzeremo come argomento di pg_fetch_assoc().
Questa funzione restituisce un array associativo che corrisponde alla singola riga (o tupla, se preferite); in alternativa avremmo potuto usare pg_fetch_row() (che restituisce un array indicizzato da interi) o pg_fetch_array() (che restituisce sia l'array numerico che quello associativo), con risultati analoghi. Utilizzando pg_fetch_assoc() all'interno di un ciclo while(), riusciamo ad ottenere tutte le singole righe che, nel nostro esempio, inseriamo in una semplice tabella HTML.
Avrete notato come nella query non abbiamo specificato condizioni, per cui abbiamo estratto indistintamente tutte le tuple della nostra tabella, avremmo potuto invece limitare il numero di record da estrarre tramite la clausola LIMIT, che chi utilizza MySql dovrebbe già conoscere, ma che ha un utilizzo leggermente differente.
La sintassi di LIMIT è infatti LIMIT n, dove per n si intende il numero di righe da restituire; nella maggioranza dei casi, però, essa viene utilizzata unitamente alla clausola OFFSET x, che specifica a partire da quale record (x) bisogna cominciare a estrarre i records, e che svolge lo stesso compito del primo parametro di LIMIT in MySql.
Semplici Transazioni
Concludiamo questo articolo, con un accenno alle transazioni, che, come già accennato, PostgreSQL supporta nativamente.
Quando si parla di transazioni, ci si riferisce essenzialmente al problema della "sincronizzazione" delle istruzioni con i dati "correnti" del database; di norma (ossia quando non viene aperto un blocco di transazione) le istruzioni vengono "committate" (ossia eseguite, scritte sul disco) immediatamente, ma può sorgere la necessità di differire il COMMIT di una o più istruzioni, per prevenire possibili conflitti tra utenti che potrebbero danneggiare l'integrità del Database.
Entrano qui in gioco le transazioni, PostgreSQL in particolare, usa un sistema conosciuto come MVCC (Multi Version Concurrency Control) che in pratica fa sì che, mentre si interroga il db, la transazione fissi e mantenga uno snapshot del database così com'era all'inizio della transazione stessa, a prescindere dallo stato attuale dei dati. Questo protegge la transazione dal "vedere" eventuali dati contraddittori causati da transazioni concorrenti, riuscendo così ad isolarla.
Ogni transazione inizia con il comando BEGIN e si conclude con il comando COMMIT, il comando ROLLBACK, poi, può far tornare indietro la transazione al momento in cui era iniziata, annullando così tutte le query effettuate (che in realtà non erano state eseguite, perchè la transazione non era stata committata).
Ecco un esempio semplicissimo di transazione:
<?php
require('pg_conn.php');
if(!$query = @pg_query("BEGIN"))
die("Errore nella query: " . pg_last_error($conn));
if(!$query = @pg_query("UPDATE agenda SET nome = 'GM' WHERE nome = 'Giancarlo'"))
die("Errore nella query: " . pg_last_error($conn));
if(!$query = @pg_query("COMMIT"))
die("Errore nella query: " . pg_last_error($conn));
require('pg_close_conn.php');
?>