In one of my previous jobs, I heard that it would be inspiring, to write a CMS with all logic stored and executed on database side. Every request would be performed in stored procedures, theirs final result would be HTML page sent back to the user.
I will present, how to do it with PotgreSQL, Apache, Linux and a bit of bash scripting. The script is a cheat because I promised to write everything in stored procedures, but as you will see, it's just a gateway to the database, there isn't any logic.
We will start from the configuration of the Apache server, as a root, go to /etc/apache2 and create web-sql file in sites-available directory. Its content should be as below.
NameVirtualHost 127.0.0.1:8888 <VirtualHost 127.0.0.1:8888> ServerAdmin webmaster@localhost DocumentRoot /opt/websql <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /opt/websql> Options Indexes FollowSymLinks MultiViews ExecCGI AllowOverride None Order allow,deny allow from all # This directive allows us to have apache2's default start page # in /apache2-default/, but still have / go to the right place #RedirectMatch ^/$ /apache2-default/ </Directory> ErrorLog /var/log/apache2/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog /var/log/apache2/access.log combined ServerSignature On </VirtualHost>
In /etc/apache2/sites-enabled make link to the above file. After typing ls -la in command line it should look like this:
lrwxrwxrwx 1 root root 26 2008-05-26 00:00 web-sql -> ../sites-available/web-sql
Now we have virtual server that runs on 127.0.0.1 address, on 8888 port and keeps its files in /opt/websql.
In /opt make websql directory, inside create main.cgi file with below content.
#!/bin/sh echo "select index('$QUERY_STRING')" | psql -U userszybkosci -t --no-align -d testszybkosci
The script executes index() procedure with a string that was given by the user as an input parameter. We don't want to display the number of returned rows, so we are using -t parameter, spaces automatically inserted by psql are disabled by using --no-align. Mentioned options ensures that the output of the script is equivalent to the output of the index() procedure.
We need to create a user, a database and enable PL/pgSQL language on the database:
robert@robert855:~$ sudo bash Password: root@robert855:~# su postgres postgres@robert855:/home/robert$ createuser userszybkosci Shall the new role be a superuser? (y/n) n Shall the new role be allowed to create databases? (y/n) y Shall the new role be allowed to create more new roles? (y/n) n CREATE ROLE postgres@robert855:/home/robert$ createdb -O userszybkosci testszybkosci CREATE DATABASE postgres@robert855:/home/robert$ createlang plpgsql testszybkosci
Now we can move to the merit and start to create stored procedures and tables. They will be used to provide the functionality of our simple CMS:
-- -- Name: atrybut; Type: TABLE; Schema: public; Owner: userszybkosci; Tablespace: -- CREATE TABLE atrybut ( tag integer, nazwa text, wartosc text ); ALTER TABLE public.atrybut OWNER TO userszybkosci; -- -- Name: str_seq; Type: SEQUENCE; Schema: public; Owner: userszybkosci -- CREATE SEQUENCE str_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.str_seq OWNER TO userszybkosci; -- -- Name: strona; Type: TABLE; Schema: public; Owner: userszybkosci; Tablespace: -- CREATE TABLE strona ( id integer NOT NULL, tresc text, nazwa_pliku text, tytul text ); ALTER TABLE public.strona OWNER TO userszybkosci; -- -- Name: tag; Type: TABLE; Schema: public; Owner: userszybkosci; Tablespace: -- CREATE TABLE tag ( id integer NOT NULL, nazwa text ); ALTER TABLE public.tag OWNER TO userszybkosci; -- -- Name: tag_seq; Type: SEQUENCE; Schema: public; Owner: userszybkosci -- CREATE SEQUENCE tag_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.tag_seq OWNER TO userszybkosci; -- -- Name: strona_pkey; Type: CONSTRAINT; Schema: public; Owner: userszybkosci; Tablespace: -- ALTER TABLE ONLY strona ADD CONSTRAINT strona_pkey PRIMARY KEY (id); -- -- Name: tag_pkey; Type: CONSTRAINT; Schema: public; Owner: userszybkosci; Tablespace: -- ALTER TABLE ONLY tag ADD CONSTRAINT tag_pkey PRIMARY KEY (id); -- tutaj trafiaja zapytania, ona generuje tresc, taki -- engine, mozna by dorobic obsluge akcji etc.. CREATE OR REPLACE FUNCTION index(text) RETURNS text AS $$ DECLARE nazwa_pliku ALIAS FOR $1; DECLARE output TEXT DEFAULT E'Content-Type: text/html\n\n'; BEGIN -- zadanie wyslania arkusza styli IF nazwa_pliku='style' THEN output = output || add_styles(); RETURN output; END IF; output = output || '<html><link rel="stylesheet" href="main.cgi?style"/><body>' || page_content(nazwa_pliku) || '</html></body>'; RETURN output; END; $$ LANGUAGE 'plpgsql'; -- zraca style, gotowe do wsadzenia w zanaczniki <style> albo do -- wpisanie w plik CSS CREATE OR REPLACE FUNCTION add_styles() RETURNS text AS $$ DECLARE DECLARE record RECORD; DECLARE subrecord RECORD; DECLARE output TEXT DEFAULT ''; BEGIN -- petla po wszystkich tagach w arkuszy styli FOR record IN SELECT * FROM tag LOOP output:= output || record.nazwa || E' {\n'; -- poczatkowa klamerka -- petla po wszytkich atrybutach dla tagu FOR subrecord IN SELECT nazwa, wartosc FROM atrybut WHERE tag=record.id LOOP output:= output || subrecord.nazwa || ':' || subrecord.wartosc || E';\n'; END LOOP; output:= output || E'}\n'; -- koncowa klamerka END LOOP; -- skoro jestesmy tuta to w zmiennej output mamy gotowy plik css RETURN output; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION page_content(TEXT) RETURNS text AS $$ DECLARE DECLARE output TEXT DEFAULT ''; klucz ALIAS FOR $1; DECLARE r RECORD; BEGIN SELECT INTO r * FROM strona WHERE nazwa_pliku=klucz; IF NOT FOUND THEN return 'Podana strona nie istnieje.'; END IF; output = '<h1>tytul: ' || r.tytul || '</h1><hr/>tresc: ' || r.tresc; RETURN output; END; $$ LANGUAGE plpgsql;
Above data is just a dump of my database, it can be fetched by executing in command line:
plpgsql -U user_name -d databse_name -f name_of_file_with_above_dump
Now, when the database is ready, we can reset the Apache server (/etc/init.d/apache2 restart) and add some articles by using psql. Results are visible on screen:
The sources can be obtained by cloning git@github.com:RobertGawron/snippets.git (CMSInPureSQL directory).
comments from my old blog:
ReplyDeletemulander
Bardzo ciekawy post. Co ciekawe trafiłem na niego przez Google (pl/pgsql vs pl/sql -port
-porting -translation) pomimo iż twojego bloga wcześniej przeglądałem w miarę dokładnie :)
Interesuje mnie o jakiej wersji PostgreSQL myślałeś w trakcie pisania tego kodu. Język
PL/pgSQL jest dość ciekawy, mnie interesuje głównie ze względu na zbieżności z Oraclowym
PL/SQLem. Pierwsze co rzuca mi się w oczy to wykorzystywanie przez ciebie formy ‘ALIAS’ w
celu odwoływania się do parametrów funkcji. Od wersji 8.0 nie jest to już konieczne, można
zamiast tego stosować formę:
CREATE FUNCTION Simple_Example (my_parameter TEXT) RETURNS TEXT AS $$ BEGIN RETURN
my_parameter; END; $$ LANGUAGE plpgsql;
Drugim ciekawym elementem twojego kodu jest poprzedzanie deklaracji zmiennych słowem
kluczowym DECLARE:
DECLARE DECLARE record RECORD; DECLARE subrecord RECORD; DECLARE output TEXT DEFAULT ”;
BEGIN — petla po wszystkich tagach w arkuszy styli …. END;
Z tego co wyczytałem w dokumentacji, struktura języka oparta jest na blokach o strukturze:
DECLARE BEGIN EXCEPTION END;
Twój przykład interpretuje jako DECLARE DECLARE record RECORD; BEGIN NULL; END; DECLARE
subrecord RECORD; BEGIN NULL; END; DECLARE output TEXT DEFAULT ”; BEGIN NULL; END; BEGIN —
petla po wszystkich tagach w arkuszy styli …. END;
Jestem ciekaw, czy w ten sposób również traktuje ten kod samo PostgreSQL. Według
dokumentacji, format bez DECLARE jest również poprawna:
DECLARE record RECORD; subrecord RECORD; output TEXT; BEGIN — petla po wszystkich tagach w
arkuszy styli …. END;
Inną ciekawostką jest możliwość kotwiczenia typów, tak więc twój przykład można by
przekształcić na:
DECLARE record tag%ROWTYPE; subrecord atrybut%ROWTYPE; output TEXT DEFAULT ”; BEGIN —
petla po wszystkich tagach w arkuszy styli …. END;
Zajmowałeś się zawodowo pisaniem aplikacji pod PostgreSQL? Jeżeli tak to jestem ciekaw
twojej ogólnej opinii na temat tej bazy i samego języka.
mulander
Niestety formatowanie uległo małemu uszkodzeniu :P Pozwoliłem sobie przerzucić kod z
mojego poprzedniego komentarza na pastebin: http://pastebin.com/EBhYpW7b