ďťż
 
Postgresql - gdzie moge sie nauczyc pisania funkcji i triggerow w ďťż
 
Postgresql - gdzie moge sie nauczyc pisania funkcji i triggerow w
Zobacz wiadomości
 
Cytat
A gdyby tak się wedrzeć na umysłów górę, / Gdyby stanąć na ludzkich myśli piramidzie, / I przebić czołem przesądów chmurę, / I być najwyższą myślą wcieloną. . . Juliusz Słowacki, Kordian
Indeks BCB i MySQL subiekt gt fototapeta
 
  Witamy

Postgresql - gdzie moge sie nauczyc pisania funkcji i triggerow w



koralgol1955@o2.pl - 25-03-2006 19:38
Postgresql - gdzie moge sie nauczyc pisania funkcji i triggerow w
  mam problem
tworzylem bazy w interbase i firebird ale za cholere nie moge poradzic sobie z
funkcjami triggerami w PG Nie rozumie skladni( wszczegolnosci w funkcjach nie
wiem czemu powstaje nap blad ze PG nie wie co to $$ i wogole co to jest $$ i
po co ) nie potrafie napisac prostch funkcji ktore wyszukuja dane w tabelach
np zliczaja i przekazuja na wyjscie , jak deklarowac zmienne uzywane wewnatrz
funkcji , . Wiem ze ze PG ma duza dokumentacje ale nic z niej nie rozumie bo
oprocz funckji ktora dodaje $1+$2 nic mi nie wychodzi :((
Czy wogole mozna we wnetrzu triggera tworzyc szereg instrukcji czy trzeba
zawsze wywolywac zasrana procedure:( . Jak wogole we funkcjach napisac jakis
blko BEGIN END tak by zasrane PG nie pokazalo bledu przy BEGIN
ehhhhh unioslem sie :(((

--
Wysłano z serwisu OnetNiusy: http://niusy.onet.pl





Piotr 'piter' Hlawski - 25-03-2006 19:38

  koralgol1955@o2.pl wrote:

> tworzylem bazy w interbase i firebird ale za cholere nie moge poradzic
> sobie z funkcjami triggerami w PG Nie rozumie skladni( wszczegolnosci w
> funkcjach nie wiem czemu powstaje nap blad ze *PG nie wie co to $$ i
> wogole co to jest $$ i po co )

od początkowego $$ do końcowego $$ jest cały kod programu funkcji pl/pgsql.
Po prostu taka konwencja. Jeśli PG nie rozumie co to $$, to oznacza, że
używasz wersji < 8.0

> nie potrafie napisac prostch funkcji ktore
> wyszukuja dane w tabelach np zliczaja i przekazuja na wyjscie , jak
> deklarowac zmienne uzywane wewnatrz funkcji , . Wiem ze ze PG ma duza
> dokumentacje ale nic z niej nie rozumie bo oprocz funckji ktora dodaje
> $1+$2 nic mi nie wychodzi :((

Zamiast się wnerwiać, poczytaj na spokojnie tą dokumentację, to nie boli.

> Czy wogole mozna we wnetrzu triggera tworzyc
> szereg instrukcji czy trzeba zawsze wywolywac zasrana procedure:( .

Tak, można (co też widać w przykładach w dokumentacji), można również
wywoływać inne funkcje.

> Jak
> wogole we funkcjach napisac jakis blko BEGIN END tak by zasrane PG nie
> pokazalo bledu przy BEGIN ehhhhh unioslem sie :(((

Bardzo cię proszę, nie bluzgaj tak, bo w rezultacie może nikt Ci nie
odpowiedzieć, a chyba nie o to ci chodzi, prawda?

CREATE FUNCTION funkcja() RETURNS integer AS
$$
DECLARE
cos int4;
BEGIN
cos = 5;
RETURN cos;
END;
$$
LANGUAGE plpgsql;

powyższe chyba jest jasne? Zaznaczam, że będzie działać w Pg w wersji 8.x
ze względu na owe $$ - o ile pamiętam w wersji 7.x było to '' (obecnie
najnowszy to 8.1.3 - i tego Ci polecam używać)

--
..:: Piter // phlawski$gmail,com // gg: 4534287 ::.
Chuck Norris zgoliłby brodę, ale nie ma na Ziemi dość twardego metalu na
żyletkę.




koralgol1955@o2.pl - 25-03-2006 19:38

  > koralgol1955@o2.pl wrote:
>
> > tworzylem bazy w interbase i firebird ale za cholere nie moge poradzic
> > sobie z funkcjami triggerami w PG Nie rozumie skladni( wszczegolnosci w
> > funkcjach nie wiem czemu powstaje nap blad ze *PG nie wie co to $$ i
> > wogole co to jest $$ i po co )
>
> od początkowego $$ do końcowego $$ jest cały kod programu funkcji pl/pgsql.
> Po prostu taka konwencja. Jeśli PG nie rozumie co to $$, to oznacza, że
> używasz wersji < 8.0
>
> > nie potrafie napisac prostch funkcji ktore
> > wyszukuja dane w tabelach np zliczaja i przekazuja na wyjscie , jak
> > deklarowac zmienne uzywane wewnatrz funkcji , . Wiem ze ze PG ma duza
> > dokumentacje ale nic z niej nie rozumie bo oprocz funckji ktora dodaje
> > $1+$2 nic mi nie wychodzi :((
>
> Zamiast się wnerwiać, poczytaj na spokojnie tą dokumentację, to nie boli.
>
> > Czy wogole mozna we wnetrzu triggera tworzyc
> > szereg instrukcji czy trzeba zawsze wywolywac zasrana procedure:( .
>
> Tak, można (co też widać w przykładach w dokumentacji), można również
> wywoływać inne funkcje.
>
> > Jak
> > wogole we funkcjach napisac jakis blko BEGIN END tak by zasrane PG nie
> > pokazalo bledu przy BEGIN ehhhhh unioslem sie :(((
>
> Bardzo cię proszę, nie bluzgaj tak, bo w rezultacie może nikt Ci nie
> odpowiedzieć, a chyba nie o to ci chodzi, prawda?
>
> CREATE FUNCTION funkcja() RETURNS integer AS
> $$
> DECLARE
> cos int4;
> BEGIN
> cos = 5;
> RETURN cos;
> END;
> $$
> LANGUAGE plpgsql;
>
> powyższe chyba jest jasne? *Zaznaczam, że będzie działać w Pg w wersji 8.x
> ze względu na owe $$ - o ile pamiętam w wersji 7.x było to '' (obecnie
> najnowszy to 8.1.3 - i tego Ci polecam używać)
>
hmmm
niestety mam 7.X hmmmm jak wiec miala by wygladac podana przez ciebie
procedura bez tych $$ tak zeby funkcjonowala??

--
Wysłano z serwisu OnetNiusy: http://niusy.onet.pl




Piotr 'piter' Hlawski - 25-03-2006 19:38

  koralgol1955@o2.pl wrote:

[...]
>> Zaznaczam, że będzie działać w Pg w wersji 8.x
>> ze względu na owe $$ - o ile pamiętam w wersji 7.x było to '' (obecnie
>> najnowszy to 8.1.3 - i tego Ci polecam używać)
>>
> hmmm
> niestety mam 7.X hmmmm jak wiec miala by wygladac podana przez ciebie
> procedura bez tych $$ tak zeby funkcjonowala??

Doczytaj _właściwą_ dokumentację

http://www.postgresql.org/docs/7.4/i...structure.html

PS. Polecam jednak przejście na Pg 8.1.x, między innymi na zmiany w Pl/pgsql
(np. EXECUTE string_sql INTO zmienna)

--
..:: Piter // phlawski$gmail,com // gg: 4534287 ::.
Samochody zostały wymyślone by szybciej uciekać przed Chuckiem Norrisem.
Chuck Norris wymyślił wypadek samochodowy.





Ronald Kuczek - 25-03-2006 19:39

  Użytkownik koralgol1955@o2.pl napisał:
> mam problem
> tworzylem bazy w interbase i firebird ale za cholere nie moge poradzic sobie z
> funkcjami triggerami w PG Nie rozumie skladni( wszczegolnosci w funkcjach nie
> wiem czemu powstaje nap blad ze PG nie wie co to $$ i wogole co to jest $$ i
> po co ) nie potrafie napisac prostch funkcji ktore wyszukuja dane w tabelach
> np zliczaja i przekazuja na wyjscie , jak deklarowac zmienne uzywane wewnatrz
> funkcji , . Wiem ze ze PG ma duza dokumentacje ale nic z niej nie rozumie bo
> oprocz funckji ktora dodaje $1+$2 nic mi nie wychodzi :((
> Czy wogole mozna we wnetrzu triggera tworzyc szereg instrukcji czy trzeba
> zawsze wywolywac zasrana procedure:( . Jak wogole we funkcjach napisac jakis
> blko BEGIN END tak by zasrane PG nie pokazalo bledu przy BEGIN
> ehhhhh unioslem sie :(((
>
Ja miałem inny problem - musiałem przeportować aplikację z Postgresa
na Firebirda i - krótko mówiąc, gdyby mój monitor miał uszy, już dawno
byłyby zwiędnięte. Wk**wiał mnie brak takich przydatnych rzeczy jak
ilike,operacji na timestampach (+-interval), brak wielu funkcji na
stringach (np. POS) i parę innych elementów , które trzeba było
obchodzić m.in. zewnętrznymi udf-ami (na szczęście jest dość sporo
gotowców), proponuję więc podejść do Postgresa z mniejszymi
uprzedzeniami :).

W dokumentacji są nawet przykłady, a to kawałek mojego kodu (uwaga,
użyłem starszej składni z $BODY$, nowa to $$, jeszcze starsza wymagała
zamknięcia kodu funkcji w '' (w związku z tym stringi wewnątrz wymagały
innej notacji, ale tu już odsyłam do dokumentacji)):

CREATE OR REPLACE FUNCTION pewna_funkcja_triggerowa()
RETURNS "trigger" AS
$BODY$
declare myoperation int4;

begin
if TG_OP='INSERT' then
--wejdz tu, jesli trigger jest wykonywany na insercie
myoperation=1;
--teraz zmien zawartosc pol
NEW.pole1:='jakastamwartosc';
END IF;
if TG_OP='UPDATE' then
--wejdz tu, jesli trigger jest wykonywany na update
myoperation=2;
--teraz zmien zawartosc pol
IF(OLD.pole1='jakastamwartosc') THEN
NEW.pole1:='jakastaminnawartosc';
END IF;
END IF;
if TG_OP='DELETE' then
--wejdz tu, jesli trigger jest wykonywany na delete
myoperation=3;
--teraz sprawdz co jest w polu pole1, jesli 'jakastaminnawartosc',
--nie usuwaj krotki
IF(OLD.pole1='jakastaminnawartosc') THEN
myoperation=-1;
END IF;
END IF;
if myoperation=-1 then
RETURN NULL;
ELSE
if myoperation=3 then
RETURN OLD;
else
RETURN NEW;
END IF;
END IF;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
COMMENT ON FUNCTION pewna_funkcja_triggerowa() IS 'Trigger function
executed for each record of pewna_tabela table';

teraz utworzenie triggera:
CREATE TRIGGER pewna_tabela_trg
BEFORE INSERT OR UPDATE OR DELETE
ON pewna_tabela
FOR EACH ROW
EXECUTE PROCEDURE pewna_funkcja_triggerowa();
Oczywiście możesz sobie napisać osobne funkcje i zadeklarować trzy
odrębne triggery na insert,update i delete, tak jak lubisz.

Teraz funkcja zwracająca resultset:
--utworzenie typu wynikowego
CREATE TYPE get_klienci_result AS
(klientid int8,
nazwa text,
ulica text,
numer text,
kodpocztowy text,
miejscowosc text,
nip text,
regon text,
sposobplatnosciid int8,
dokumentid int8,
telefon text,
fax text);
--utworzenie funkcji
CREATE OR REPLACE FUNCTION get_klienci(text)
RETURNS SETOF get_klienci_result AS
$BODY$
--in params $1- order by column name
declare retrec get_klienci_result;
query text;
BEGIN
--zbudowanie zapytania
query:='select klientid ,nazwa ,ulica, numer, kodpocztowy, miejscowosc,
nip ,
regon , sposobplatnosciid,dokumentid,telefon,fax from klienci';
if ($1>'' and $1 is not null) then
query:=query||' order by '||$1::text;
end if;
for retrec in execute query loop
--w Firebirdzie to się robi przez suspend, a tutaj tak:
return next retrec;
END LOOP;
RETURN;
END;$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION get_klienci(text) OWNER TO postgres;

wywołanie przykładowe:
select * from get_klienci('nazwisko');

Ponieważ przerabiałem to w drugą stronę, mała rada, żebyś mógł
oszczędzić sobie przekleństw:
W FB:
select kol1,kol2 from tabela into :var1,:var2;
PG:
select kol1,kol2 into var1,var2 from tabela;
FB:
IF (costam) THEN BEGIN
---
END
PG:
IF (costam) THEN
---
END IF; --(uwaga na średniki !)
FB:
FOR SELECT .... INTO ....
DO BEGIN
SUSPEND;
END
PG:
FOR typwynikowy in select ...
LOOP
RETURN NEXT typwynikowy;
END LOOP;
FB (w procedurach zwracających pojedyncza wartosc):
SUSPEND;
PG:
RETURN zwracanawartosc; --gdzie typ zwracanej wartosci deklarujesz w
nagłówku funkcji (RETURNS int4, na przykład);
Sorry za przynudzanie (o ile ktoś dobrnął do tego miejsca).
W razie czego chętnie pomogę, odezwij się na priv.

Pozdrawiam
Rony




koralgol1955@o2.pl - 28-03-2006 00:02

  > Użytkownik koralgol1955@o2.pl napisał:
> > mam problem
> > tworzylem bazy w interbase i firebird ale za cholere nie moge poradzic
sobie z
> > funkcjami triggerami w PG Nie rozumie skladni( wszczegolnosci w funkcjach
nie
> > wiem czemu powstaje nap blad ze *PG nie wie co to $$ i wogole co to jest
$$ i
> > po co ) nie potrafie napisac prostch funkcji ktore wyszukuja dane w
tabelach
> > np zliczaja i przekazuja na wyjscie , jak deklarowac zmienne uzywane
wewnatrz
> > funkcji , . Wiem ze ze PG ma duza dokumentacje ale nic z niej nie rozumie
bo
> > oprocz funckji ktora dodaje $1+$2 nic mi nie wychodzi :((
> > Czy wogole mozna we wnetrzu triggera tworzyc szereg instrukcji czy trzeba
> > zawsze wywolywac zasrana procedure:( . Jak wogole we funkcjach napisac
jakis
> > blko BEGIN END tak by zasrane PG nie pokazalo bledu przy BEGIN
> > ehhhhh unioslem sie :(((
> >
> *Ja miałem inny problem - musiałem przeportować aplikację z Postgresa
> na Firebirda i - krótko mówiąc, gdyby mój monitor miał uszy, już dawno
> byłyby zwiędnięte. Wk**wiał mnie brak takich przydatnych rzeczy jak
> ilike,operacji na timestampach (+-interval), brak wielu funkcji na
> stringach (np. POS) i parę innych elementów , które trzeba było
> obchodzić m.in. zewnętrznymi udf-ami (na szczęście jest dość sporo
> gotowców), proponuję więc podejść do Postgresa z mniejszymi
> uprzedzeniami :).
>
> W dokumentacji są nawet przykłady, a to kawałek mojego kodu (uwaga,
> użyłem starszej składni z $BODY$, nowa to $$, jeszcze starsza wymagała
> zamknięcia kodu funkcji w '' (w związku z tym stringi wewnątrz wymagały
> innej notacji, ale tu już odsyłam do dokumentacji)):
>
> CREATE OR REPLACE FUNCTION pewna_funkcja_triggerowa()
> * RETURNS "trigger" AS
> $BODY$
> * declare myoperation int4;
>
> * begin
> * if TG_OP='INSERT' then
> * * * --wejdz tu, jesli trigger jest wykonywany na insercie
> * * * myoperation=1;
> * * * --teraz zmien zawartosc pol
> * * * NEW.pole1:='jakastamwartosc'; * * * * * * * * * *
> * END IF;
> * if TG_OP='UPDATE' then
> * * * --wejdz tu, jesli trigger jest wykonywany na update
> * * *myoperation=2;
> * * * --teraz zmien zawartosc pol
> * * *IF(OLD.pole1='jakastamwartosc') THEN
> * * * *NEW.pole1:='jakastaminnawartosc'; * * * * * * * * * * *
> * * *END IF;
> * END IF;
> * if TG_OP='DELETE' then
> * * * --wejdz tu, jesli trigger jest wykonywany na delete
> * * * *myoperation=3;
> * * * --teraz sprawdz co jest w polu pole1, jesli 'jakastaminnawartosc',
> * * * --nie usuwaj krotki
> * * * IF(OLD.pole1='jakastaminnawartosc') THEN
> * * * *myoperation=-1;
> * * * END IF; *
> * END IF;
> * if myoperation=-1 then
> * *RETURN NULL;
> * ELSE
> * *if myoperation=3 then
> * * RETURN OLD;
> * * else
> * * RETURN NEW;
> * *END IF;
> * END IF;
> * end;
> $BODY$
> * LANGUAGE 'plpgsql' VOLATILE;
> COMMENT ON FUNCTION pewna_funkcja_triggerowa() IS 'Trigger function
> executed for each record of pewna_tabela table';
>
> teraz utworzenie triggera:
> CREATE TRIGGER pewna_tabela_trg
> * BEFORE INSERT OR UPDATE OR DELETE
> * ON pewna_tabela
> * FOR EACH ROW
> * EXECUTE PROCEDURE pewna_funkcja_triggerowa();
> Oczywiście możesz sobie napisać osobne funkcje i zadeklarować trzy
> odrębne triggery na insert,update i delete, tak jak lubisz.
>
> Teraz funkcja zwracająca resultset:
> --utworzenie typu wynikowego
> CREATE TYPE get_klienci_result AS
> * *(klientid int8,
> * * nazwa text,
> * * ulica text,
> * * numer text,
> * * kodpocztowy text,
> * * miejscowosc text,
> * * nip text,
> * * regon text,
> * * sposobplatnosciid int8,
> * * dokumentid int8,
> * * telefon text,
> * * fax text);
> --utworzenie funkcji
> CREATE OR REPLACE FUNCTION get_klienci(text)
> * RETURNS SETOF get_klienci_result AS
> $BODY$
> --in params $1- order by column name
> declare retrec get_klienci_result;
> * * * *query text;
> BEGIN
> --zbudowanie zapytania
> query:='select klientid ,nazwa ,ulica, numer, kodpocztowy, miejscowosc,
> nip ,
> * * * *regon , sposobplatnosciid,dokumentid,telefon,fax from klienci';
> if ($1>'' and $1 is not null) then
> * * * *query:=query||' order by '||$1::text;
> end if;
> for retrec in execute query loop
> * * * *--w Firebirdzie to się robi przez suspend, a tutaj tak:
> * * * *return next retrec;
> * * * *END LOOP;
> RETURN;
> END;$BODY$
> * LANGUAGE 'plpgsql' VOLATILE;
> ALTER FUNCTION get_klienci(text) OWNER TO postgres;
>
> wywołanie przykładowe:
> select * from get_klienci('nazwisko');
>
> Ponieważ przerabiałem to w drugą stronę, mała rada, żebyś mógł
> oszczędzić sobie przekleństw:
> W FB:
> select kol1,kol2 from tabela into :var1,:var2;
> PG:
> select kol1,kol2 into var1,var2 from tabela;
> FB:
> IF (costam) THEN BEGIN
> ---
> END
> PG:
> IF (costam) THEN
> ---
> END IF; *--(uwaga na średniki !)
> FB:
> FOR SELECT .... INTO ....
> DO BEGIN
> SUSPEND;
> END
> PG:
> FOR typwynikowy in select ...
> LOOP
> RETURN NEXT typwynikowy;
> END LOOP;
> FB (w procedurach zwracających pojedyncza wartosc):
> SUSPEND;
> PG:
> RETURN zwracanawartosc; --gdzie typ zwracanej wartosci deklarujesz w
> nagłówku funkcji (RETURNS int4, na przykład);
> Sorry za przynudzanie (o ile ktoś dobrnął do tego miejsca).
> W razie czego chętnie pomogę, odezwij się na priv.
>
> Pozdrawiam
> Rony

Dzieki wielkie Bracie!!!
TO SIE NAZYWA WZOROWA POMOC ANIE DOGRYZANIE I DELEGOWANIE DNA GOGLE!!
GRUPOWICZE MADRALE (W TEORII) - UCZCIE SIE!!!!

--
Wysłano z serwisu OnetNiusy: http://niusy.onet.pl




max - 28-03-2006 00:02

  >> RETURN zwracanawartosc; --gdzie typ zwracanej wartosci deklarujesz w
>> nagłówku funkcji (RETURNS int4, na przykład);
>> Sorry za przynudzanie (o ile ktoś dobrnął do tego miejsca).
>> W razie czego chętnie pomogę, odezwij się na priv.
>>
>> Pozdrawiam
>> Rony
>
> Dzieki wielkie Bracie!!!
> TO SIE NAZYWA WZOROWA POMOC ANIE DOGRYZANIE I DELEGOWANIE DNA GOGLE!!
> GRUPOWICZE MADRALE (W TEORII) - UCZCIE SIE!!!!
>
>
Nasuwa mi sie na myśl :
Co jest lepsze dać komu rybę czy też kupić wędkę?

Wszystko zależy od interpretacji.
Ty konstruktywna uwagę zinterpretowałeś jako podgryzanie inna osoba
mogła by pomyśleć inaczej.

Nauczyć sie czytać dokumentacje to bardzo cenna rzecz, bo w większości
przypadków jest tam wszytko napisane, choć dobry przykład jest czasmi
lepszy niż 100 stron w manualu.

pozdrawiam

MAX

p.s. co to za język z pod budki z piwem "... zasrane ..."




Robert Grabowski - 28-03-2006 00:02

  Ronald Kuczek wrote:

> Użytkownik koralgol1955@o2.pl napisał:
[...]
> W dokumentacji są nawet przykłady, a to kawałek mojego kodu (uwaga,
> użyłem starszej składni z $BODY$, nowa to $$, jeszcze starsza wymagała
> zamknięcia kodu funkcji w '' (w związku z tym stringi wewnątrz wymagały
> innej notacji, ale tu już odsyłam do dokumentacji)):
>

W $$ można wpisać cokolwiek. Co więcej działa to dla wszystkich stringów.
Czyli można np. napisać tak: select $x$ala ma kota$x$ ... ale oczywiście
korzystanie z tego w innym miejscu niż deklaracja ciała funkcji chyba nie
ma sensu.

--
pozdrawiam
Robert Grabowski
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • effulla.pev.pl
  • comp
    [PostgreSQL] - jak =?ISO-8859-2?Q?zabezpieczy=E6_interesy_tw?==?ISO-8859-2?Q?=F3rcy_systemu_=3F=3F=3F?= postgresql - int/int postgresql Select count(*) czy raczej Select count(ID) [postgresql] INSERT OR UPDATE - jak =?ISO-8859-2?Q?b=EAdzie_na?==?ISO-8859-2?Q?jlepiej=3F?= [postgresql] kilka =?ISO-8859-2?Q?rekord=F3w_subquery_jako_?==?ISO-8859-2?Q?string?= Postgres - replikcja master-master Dopasowanie do "najlepszego" dopasowania :) [ PostgreSQL] Problemy z =?ISO-8859-2?Q?instalacj=B1_PostgreSQL_na_syste?==?ISO-8859-2?Q?mach_Windows?= =?ISO-8859-2?Q?[psql]_Polskie_t=B3umaczenie_?= =?ISO-8859-2?Q?licencji_BSD_dla_PostgreSQL=3F?= [firebird] Czym =?ISO-8859-2?Q?zast=B1pi=E6_postgresowy_inte?==?ISO-8859-2?Q?rval_=3F?=
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • ets2.xlx.pl
  • Cytat

    Decede mihi sole - nie zasłaniaj mi słonca.
    Gdy kogoś kochasz, jesteś jak stworzyciel świata - na cokolwiek spojrzysz, nabiera to kształtu, wypełnia się barwą, światłem. Powietrze przytula się do ciebie, choćby był mróz, a ty masz w sobie tyle radości, że musisz ją rozdawać wokoło, bo się w tobie nie mieści
    Hoc fac - tak czyń.
    A tergo - od tyłu; z tyłu.
    I czarne włosy posiwieją. Safona

    Valid HTML 4.01 Transitional

    Free website template provided by freeweblooks.com