przechowywanie historii zmian

zapewne każdy kto choć raz robił coś ciut większego w bazach danych zetknął się z problemem przechowywania zmian w tabelkach.

ci co nie mają triggerów, muszą używać kodu po stronie aplikacji klienckiej. co ma swoje wady.

z drugiej strony – w postgresie triggery są. i działają 🙂 więc można do roboty zaprząc postgresa.

załóżmy, że mamy bardzo prostą tabelkę z danymi:

CREATE TABLE objects (
id BIGSERIAL,
o_type TEXT NOT NULL DEFAULT '',
PRIMARY KEY (id)
);
chcemy zapisywać sobie modyfikacje. robimy więc tabelkę na zapis historii zmian:
CREATE TABLE history_objects (
id BIGSERIAL,
modified_on TIMESTAMP NOT NULL DEFAULT now(),
modification_type TEXT NOT NULL DEFAULT '',
modified_id INT8,
modified_o_type TEXT,
PRIMARY KEY (id)
);

rekordy w history_object zawierają:

  • modified_on – kiedy dana modyfikacja miała miejsce
  • modification_type – typ modyfikacji – insert, update czy delete.
  • modified_id – id zmodyfikowanego rekordu
  • modified_o_type – o_type w tym rekordzie

proste i miłe. tworzymy więc dwa triggery:

CREATE OR REPLACE FUNCTION trg_objects_ui() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO history_objects (modified_id, modified_o_type, modification_type) VALUES (NEW.id, NEW.o_type, TG_OP);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_ui AFTER INSERT OR UPDATE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_ui();
 
CREATE OR REPLACE FUNCTION trg_objects_d() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO history_objects (modified_id, modified_o_type, modification_type) VALUES (OLD.id, OLD.o_type, TG_OP);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_d AFTER DELETE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_d();i testujemy:

testujemy:

> SELECT * FROM objects;
id | o_type
----+--------
(0 ROWS)
 
> SELECT * FROM history_objects;
id | modified_on | modification_type | modified_id | modified_o_type
----+-------------+-------------------+-------------+-----------------
(0 ROWS)
 
> INSERT INTO objects (o_type) VALUES ('bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'xxx' WHERE o_type = 'bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
(1 ROW)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type | modified_id | modified_o_type
----+----------------------------+-------------------+-------------+-----------------
1 | 2006-07-15 12:14:56.138695 | INSERT            |           1 | bleble
2 | 2006-07-15 12:14:56.138695 | INSERT            |           2 | bleble2
3 | 2006-07-15 12:14:56.138695 | UPDATE            |           1 | xxx
4 | 2006-07-15 12:14:56.138695 | DELETE            |           2 | bleble2
(4 ROWS)

działa ślicznie.

ale jest jedno ale.

czasem chcielibyśmy logować więcej informacji. w projekcie nad którym pracują koledzy w firmy chcieli logować nazwę akcji webowej (coś jakby url) i nazwę użytkownika które spowodowały taką modyfikację.

tu pojawiają się schody – baza danych nie zna urla. baza danych nie zna nazwy użytkownika który się zalogował na www – wie tylko jaki user jest zalogowany do bazy danych, ale to jest zazwyczaj zawsze jeden i ten sam user – niezależnie od tego na kogo się użytkownik loguje via www.

hmm. trzeba więc logować z poziomu aplikacji, a nie bazy.

no tak, ale jeśli user w jednej akcji powoduje modyfikacje wielu rekordów czy wielu tabel – sprawa zaczyna się komplikować.

na szczęście jest rozwiązanie.

wystarczy założyć dodatkową tabelkę do której będziemy dodawać nowy rekord przy rozpoczynaniu procedury obsługi każdego requestu, i będziemy tam wpisywać url'a i nazwę usera. a potem tylko baza musi umieć z tego skorzystać.

aby baza mogła powiązać dane z tej nowej tabelki (nazwijmy ją sobie “actions"), musi być w stanie jakoś powiązać późniejsze insert'y, update'y i delete'y z wpisami w actions. najlepiej to zrobić używając pidu (numer identyfikacyjny procesu) backendu postgresa.

w postgresie jest funkcja która zwraca ten pid:

SELECT pg_backend_pid();

dodajmy więc tabelkę actions, od razu z indeksem na to po czym będziemy szukać:

CREATE TABLE actions (
id BIGSERIAL,
backend_pid INT4 NOT NULL DEFAULT pg_backend_pid(),
action_on TIMESTAMP NOT NULL DEFAULT now(),
action_url TEXT,
action_user TEXT,
PRIMARY KEY (id)
);
CREATE INDEX ui_actions_bpao ON actions (backend_pid, action_on);

tabelkę history_objects modyfikujemy tak aby zawierała pola na urla i username (zamiast tego można dodać action_id, ale dodanie całych pól daje nam dodatkowe możliwości):

CREATE TABLE history_objects (
id BIGSERIAL,
modified_on TIMESTAMP NOT NULL DEFAULT now(),
modification_type TEXT NOT NULL DEFAULT '',
modification_url TEXT,
modification_user TEXT,
modified_id INT8,
modified_o_type TEXT,
PRIMARY KEY (id)
);

pozostało zmodyfikować triggery:

CREATE OR REPLACE FUNCTION trg_objects_ui() RETURNS TRIGGER AS
$BODY$
DECLARE
temprec actions%ROWTYPE;
BEGIN
SELECT * INTO temprec FROM actions WHERE backend_pid = pg_backend_pid() ORDER BY backend_pid DESC, action_on DESC LIMIT 1;
IF NOT FOUND THEN
temprec.action_url := 'unknown url. direct database access?';
temprec.action_user := 'database user: ' || CURRENT_USER;
END IF;
INSERT INTO history_objects (modified_id, modified_o_type, modification_type, modification_url, modification_user) VALUES (NEW.id, NEW.o_type, TG_OP, temprec.action_url, temprec.action_user);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_ui AFTER INSERT OR UPDATE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_ui();
 
CREATE OR REPLACE FUNCTION trg_objects_d() RETURNS TRIGGER AS
$BODY$
DECLARE
temprec actions%ROWTYPE;
BEGIN
SELECT * INTO temprec FROM actions WHERE backend_pid = pg_backend_pid() ORDER BY backend_pid DESC, action_on DESC LIMIT 1;
IF NOT FOUND THEN
temprec.action_url := 'unknown url. direct database access?';
temprec.action_user := 'database user: ' || CURRENT_USER;
END IF;
INSERT INTO history_objects (modified_id, modified_o_type, modification_type, modification_url, modification_user) VALUES (OLD.id, OLD.o_type, TG_OP, temprec.action_url, temprec.action_user);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_d AFTER DELETE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_d();

no to pora na test. najpierw zobaczymy jak to działa gdy nie zapiszemy nic do actions:

> SELECT * FROM objects;
id | o_type
----+--------
(0 ROWS)
 
> SELECT * FROM history_objects;
id | modified_on | modification_type | modification_url | modification_user | modified_id | modified_o_type
----+-------------+-------------------+------------------+-------------------+-------------+-----------------
(0 ROWS)
 
> SELECT * FROM actions;
id | backend_pid | action_on | action_url | action_user
----+-------------+-----------+------------+-------------
(0 ROWS)
 
> INSERT INTO objects (o_type) VALUES ('bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'xxx' WHERE o_type = 'bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
(1 ROW)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type |           modification_url           |   modification_user   | modified_id | modified_o_type
----+----------------------------+-------------------+--------------------------------------+-----------------------+-------------+-----------------
1 | 2006-07-15 12:58:54.16915  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | bleble
2 | 2006-07-15 12:58:58.46624  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
3 | 2006-07-15 12:59:04.454578 | UPDATE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | xxx
4 | 2006-07-15 12:59:09.519242 | DELETE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
(4 ROWS)

działa ładnie. to teraz zapiszmy coś do actions i powtórzmy test:

> INSERT INTO actions (action_url, action_user) VALUES ('/jakis_url.html', 'admin');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('A:bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('A:bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'A:xxx' WHERE o_type = 'A:bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'A:bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
3 | A:xxx
(2 ROWS)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type |           modification_url           |   modification_user   | modified_id | modified_o_type
----+----------------------------+-------------------+--------------------------------------+-----------------------+-------------+-----------------
1 | 2006-07-15 12:58:54.16915  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | bleble
2 | 2006-07-15 12:58:58.46624  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
3 | 2006-07-15 12:59:04.454578 | UPDATE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | xxx
4 | 2006-07-15 12:59:09.519242 | DELETE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
5 | 2006-07-15 13:00:15.098039 | INSERT            | /jakis_url.html                      | admin                 |           3 | A:bleble
6 | 2006-07-15 13:00:18.315004 | INSERT            | /jakis_url.html                      | admin                 |           4 | A:bleble2
7 | 2006-07-15 13:00:21.304025 | UPDATE            | /jakis_url.html                      | admin                 |           3 | A:xxx
8 | 2006-07-15 13:00:24.483061 | DELETE            | /jakis_url.html                      | admin                 |           4 | A:bleble2
(8 ROWS)

działa. i to całkiem ładnie. oczywiście mechanizm można dalej rozszerzać. dodawać nowe pola (ip, browser) czy z części rezygnować.

zmienna lista “cech” – ciąg dalszy

jakiś czas temu pisałem jak poradzić sobie z przechowywaniem zmiennej listy cech obiektów w bazach relacyjnych.

okazuje się, że nie tylko ja zetknąłem się z tym problemem (szokujące, czyż nie?).

dwóch, znanych w świecie postgresowym ludzi – teodor sigaev i oleg bartunov – zajęli się tym problemem.

jeśli ich nie kojarzycie – to są ludzie którzy stworzyli i rozwinęli tsearcha. zajmują się  optymalizacjami indeksów typu gist, wymyślili własny typ indeksów (gin) i ogólnie siedzą w źródłach postgresa chyba 24 godziny na dobę. ich prace są częściowo sponsorowane przez rosyjską (aha, obaj są z rosji) agencję do wspierania badań (taki ichniejszy komitet badań naukowych).

problemem cech się zajęli, pogłówkowali i zrobili. nowy typ danych do postgresa – hstore.

co potrafi?

w jednym polu może przechowywać dowolnie wiele par klucz/wartość. wyszukiwać rekordów mających określone pola w sposób który korzysta z indeksów.

operować na tych danych i ogólnie – dać programiście wreszcie dobre rozwiązanie tego problemu.

jak się tego używa? trywialnie. nie chcę tutaj kopiować dokumentacji, odsyłam was do przykładów. warto się zainteresować, bo soft działa ładnie, skutecznie i szybko.

jak dojść do posiadania domu. prawie za darmo.

trochę ponad rok temu pewien kanadyjczyk kyle mcdonald wpadł na nietypowy pomysł.

wymyślił mianowicie, że wymieni czerwony spinacz do papieru (taki zwykły spinacz, kawałek odpowiednio zakręconego drutu) na dom.

wariat? nie. doszedł do wniosku, że o ile oczywiście nikt mu nie da od razu domu, to o ile będzie się wymieniał non stop, to wartość rzeczy którą aktualnie ma będzie rosła i w końcu może dojdzie do posiadania domu.

12 lipca 2005 (rok temu) ogłosił na swojej stronie, że chce się wymienić. da komuś ten spinacz za coś innego. opisał po co to robi, jaki ma cel itd. i czekał.

nie czekał długo. 2 dni później podjął decyzję o wymianie spinacza na długopis w kształcie rybki. jeszcze tego samego dnia wymienił długopis na ceramiczny, robiony ręcznie uchwyt do drzwiczek z wyrysowanym na nim coś co miało wyglądać jak e.t. na prochach 🙂

uchwyt został następnie (po prawie 2 tygodniach) wymieniony na grilla. miesiąc i kolejną wymianę później kyle stał się posiadaczem kolejnego czerwonego przedmiotu – generatora prądowego o mocy 1000 wat.

potem kolejna wymiana – znowu miesiąc później. generator został wymieniony na beczkę piwa (a dokładniej beczkę bez piwa, która miała zostać napełniona na koszt poprzedniego właściciela gdy nowy właściciel powie jakie piwo chce mieć w środku :)) plus neon budweisera.

w międzyczasie kyle stał się sławny. na tyle sławny, że kolejna wymiana miała wymiar medialny.

kolejną osobą do wymiany okazał się być michel barrette – podobno tam (kanada) znana postać, mająca własny program w radiu.

przedmiot wymiany – skuter śnieżny. całość wzbudziła takie zainteresowanie, że moment wymiany miał widzów w postaci ekip filmowych kilku ogólnokrajowych (cbc) czy światowych (cnn) ekip telewizyjnych! programy o wymianie i o kyle'u zostały wyemitowane, a on stał się właścicielem “skutera który kiedyś należał do 2 znanych osób" (tzn. barrette'a i samego kyle'a).

w trakcie jednego z wywiadów telewizyjnych z kyle'em padło pytanie czy jest jakieś miejsce gdzie by nie pojechał aby dokonać wymiany. odpowiedź: do yakh w kolumbii brytyjskiej.

to oczywiście spowodowało zainteresowanie ludzi stamtąd. już następnego dnia kyle dostał telefon w którym zaoferowano mu:

  • bilety lotniczne w obie strony z dowolnego miejsca w ameryce północnej do cranbrook (45 minut drogi od yahk)
  • 1 dniowy pobyt obejmujący wyżywienie, jazdę na nartach, i oczywiście podróż skuterem śnieżnym do yahk.

oferującym był magazyn “SnoRiders West" wydawany w tamtych okolicach.

wiązał się z tym pewien haczyk – wydawcy snoriders west wymagali by wymiana nastąpiła właśnie w yahk, co – biorąc pod uwagę to co kyle powiedział wcześniej, że tam nie pojedzie, powodowało, że albo zrobi coś co powiedział, że nie zrobi, albo straci niezły deal :). kyle sobie oczywiście poradził – jakbyście nie zauważyli “ma łeb".

wycieczka do yahk okazała się tak kusząca, że już niedługo kyle dostał ofertę wymiany na samochód. działającego dostawczaka. używanego, ale w pełni na chodzie. obiecał, że za darmo zawiezie go gdziekolwiek w ameryce północnej na kolejną wymianę.

trochę czasu minęło – wszystkie te zamiany trwały tyle, że kolejna – bardzo istotna – miała miejsce dopiero 22 lutego 2006.

tym razem kyle wymienił (dostawczaka) na: kontrakt nagraniowy. kontrakt ten obejmował:

  • do 30 godzin w studiu nagraniowym
  • do 50 godzin miksowania i postprodukcji.
  • transport do nagrania do i z toronto skądkolwiek na świecie
  • utrzymanie w toronto w czasie nagrywania/miksowania
  • stworzony album zostanie zaprezentowany szefom sony-bmg (jednej z największych wytwórni muzycznych), oraz puszczany w satelitarnym radiu xm.

no i oczywiście będziesz miał za darmo reklamę jako jeden z uczestników wymiany w łańcuszku “oneredpaperclip" 🙂

kolejna wymiana zajęła więcej czasu – w końcu nie każdy musi być od razu zainteresowany nagraniem płyty, a dodatkowo kyle chciał zawsze wymieniać się na coś lepszego niż to co ma.

ale w końcu, niejaka judy gnant (na jej stronie można m.in. kupić jej płytę 🙂 złożyła ofertę której kyle nie mógł odrzucić.

mianowicie – oferta dotyczyła nieruchomości! domu. niestety nie oddawanego na własność, ale wynajmowanego za darmo na okres jednego roku!

dom mieści się w phoenix, w arizonie. ma 65 metrów, kominek i dębowe posadzki. i wygląda uroczo. do tego judy dorzuca darmową podróż dla 2 osób tam i z powrotem z/do dowolnego większego lotniska w ameryce północnej.

już kilka dni później kyle dokonał kolejnej wymiany. wymienił “rok w phoenix" na “popołudnie z alice'em cooperem". całe. dla fana alice'a to gratka nie lada.

dodatkowo kyle znowu trafił do telewizji, a na koncercie alice'a został zaproszony na scenę na której alice trzymał olbrzymi czerwony spinacz (film z tego jest na stronie kyle'a).

potem przez miesiąc nic się nie działo. a następnie kjyle wykonał zamianę która była mocno kontrowersyjna – wymienił popołudnie z alice cooperem na coś co (na pierwszy rzut oka) było zdecydowanie mniej warte! śnieżną kulę! nie byle jaką, bo zmotoryzowaną, podświetlaną różnymi kolorami no i jest to kula śnieżna nie z byle mikołajem czy reniferami, ale z muzykami grupy kiss!

wiele osób uważało, że ta zmiana to koniec wymian. albo przynajmniej start od prawie początku. w końcu wartość takiej kuli jest mała.

a jednak.

znalazła się osoba która chciała taką kulę, bo jest jednym z największych kolekcjonerów kul śnieżnych na świecie. i rzeczona osoba ( corbin bernsen) dała coś niesamowitego na wymianę. coś czego nie da się kupić. rolę w filmie.

jako rolę w filmie uznano:

  • jedną opłacaną w/g standardowych stawek, wymienioną w “liście płac", mówioną rolę w filmie “donna on demand" który to film będzie kręcony na jesieni.
  • utrzymanie w trakcie kręcenia (w tym miejsce do mieszkania)
  • bilet lotniczy tam i z powrotem skądkolwiek na świecie

ta wymiana (kula kiss za rolę w filmie) miała miejsce 2 czerwca 2006. a już 7 lipca – niecały rok od pierwszej wymiany – kyle wymienił rolę w filmie na dom. prawdziwy, stojący dom 🙂
dom jest w kanadzie, w kipling, saskatchewan. ma około 100 metrów kwadratowych w dwóch kondygnacjach. 3 sypialnie, 1.5 łazienki, kuchnię, pokój dzienny i jadalnię.

dodatkowo:

  • kyle dostanie “klucze do miasta"
  • zostanie honorowym burmistrzem na 1 dzień
  • zostanie uznany dożywotnio za honorowego obywatela miasta kipling
  • dzień w którym podpiszą wymianę zostanie obwieszczony dniem czerwonego spinacza
  • miasto wybuduje największy na świecie czerwony spinacz jako pomnik projektu.

i wielkie “joł" dla tego pana.
zdjęcia domu sa już na sieci i mozna pooglądać – jest tam też ten największy spinacz. muszę przyznać, że myślałem, że będzie większy 🙂

interesujące jest to, że miasto nie może zagrać w filmie, a burmistrz nie jest zainteresowany. cóż więc zrobią?

zorganizują w mieście przesłuchania w stylu idola – zwycięzca dostanie rolę. w komisji ma być corbin barnsen. plus jest spora szansa, że pojawi się alice cooper. a biorąc pod uwagę jak bardzo znany jest ten projekt to pewnie transmisje z przesłuchań będą w każdej większej stacji telewizyjnej w stanach i kanadzie.

tak trzymać! facet zrobił coś co wielu uważało za niemozliwe. nawet u mnie w firmie ktoś stwierdził, że tylko idioci by się wymieniali.

w tym co zrobił widać 100% odwrócenie historii stryjka co to siekierke na kijek zamieniał.

nowa wersja slony’ego!

jak nie wiesz co to slony, to polecam zajrzenie na strone projektu i poczytanie. w skrócie – silnik replikacyjny do postgresa, działający jako replikacja asynchroniczna master-multi_slave.

silnik co trzeba dodać działający stabilnie, wygodnie i szybko.

ostatnio pojawiła się nowa wersja. a właściwie zapowiedź nowej wersji – prerelease wersji 1.2.

w niej – sporo ulepszeń. tym razem nie są to ulepszenia które pomagają zestawić replikację (jak większośc wersji 1.1.x), ale modyfikacje tego jak sam core slony'ego działa. efekt ma być murowany – szybciej, skuteczniej, mniej zasobożernie, bardziej monitorowalnie :):

  • najważniejsze – spore zmiany w zarządzaniu pamięcią. slony teraz wykrywa kiedy ma do czynienia z dużymi rekordami i replikuje je w inny sposób dzięki czemu nie zżera tyle pamięci co poprzednie wersje
  • szybko zapychana tabela sl_log_1 została wreszcie “dopalona". slony będzie co jakiś czas przełączał się między sl_log_1 i sl_log_2, i jak upewni się, że nic z tej nieaktualnej nie jest już używane, to będzie ją czyścił poprzez TRUNCATE – dzięki czemu rozmiar tych tabel zostanie mocno ograniczony
  • poprawki tyczące się wykonywania poleceń modyfikujących bazę danych poprzez mechanizmy slony'ego – teraz kolejne polecenia będą wykonywane oddzielnie dzięki czemu zniknie problem z odwoływaniem się do dopiero-co utworzonych tabel i/lub pól.
  • poprawki tyczące się uruchamiania slony'ego jako serwisu w windowsach (nie używam więc nie jestem w stanie zweryfikować)
  • sporo poprawek tyczących się stabilności działania slonyego. teraz ma już być zbędne uruchamianie slonego przez watchdoga.

pełna lista zmian dostępna jest w tej chwili via cvsweb.

własne operatory upraszczające życie

na jednej z list postgresowych jakiś koleś zamarudził, że o ile warunek:

where pole!=3

działa poprawnie, to już

where pole!=-3 nie działa poprawnie.

wiele osób mu wyjaśniło, że:

  • standard sql nie oferuje operatora != w ogóle. do tego jest <>
  • != w postgresie to dodatek. takie “nice to have"
  • gdy doda się spację między “!=" a “-3" to działa ok (where pole!= -3)
  • całość działa tak dlatego, że system nie ma jak rozróżnić czy chodzi o “pole" “!=" “-3" czy może “pole" “!=-" “3", a to drugie jest w pełni legalną nazwę operatora – którego domyślnie nie ma, ale może być – w końcy postgres ma architekturę mocno otwartą i można sobie dodefiniować własne operatory czy cokolwiek.

facet oczywiście marudził dalej, że powinno działa.

postanowiłem pomóc. jak? dopisać operator !=- który zadziała tak jak facet chce.

najpierw potrzebna była funkcja. 30 sekund później miałem:

CREATE FUNCTION not_equals_minus(int8, int8) RETURNS bool AS $BODY$
SELECT $1 <> -$2;
$BODY$ LANGUAGE SQL IMMUTABLE STRICT;

proste i szybkie. działa dla liczb typu int8 (bigint), ale przerobienie do innych typów to trywiał.

to teraz definiojemy operator. jak? nie pamiętam. ale szybki search w manualu dał mi odpowiednią stronę, z jej pomocą napisałem:

CREATE OPERATOR !=- (
leftarg = int8,
rightarg = int8,
PROCEDURE = not_equals_minus,
commutator = !=-
);

efekt? działa. można teraz zrobić WHERE pole <> -3, WHERE pole != -3, lub WHERE pole!=-3. kocham postgresa 🙂

chory regexp do dat

tak się złożyło, że brałem udział w projekcie którego częścią miało być przygotowanie formatu wymiany danych opartego na xml'u, a także przygotowanie do tego schemy walidującej.

trywiał.

zrobiłem xml'a, wszystko fajnie. w kilku miejscach trzeba było podać datę i godzinę.

klient chciał by było human-readable, więc wyspecifikowałem coś takiego:

format jasny i prosty. okazało się, że nie. xml schema aby móc walidować datę musi mieć ją podaną tak: 2006-07-13T20:47:00 (a przynajmniej tak mówi nasz firmowy magik od schemy).

jak format jest inny to można walidować regexp'em.

oops. myślę sobie.

walidacja miała być mocna więc te daty i godziny trzeba by jakoś dobrze sprawdzać.

formatu zmienić nie mogę bo zatwierdzony przez komisję w skład której wchodzili ludzie z 4 krajów (taki trochę międzynarodowy projekt).

no to kombinujemy z regexpem.

godzina była relatywnie prosta:

  • sekundy i minuty są z zakresu 00 do 59. więc najprostszy regexp na nie: [0-5]\d (używam tu notacji perlowej bo w perlu tego regexpa testowaliśmy, ale finalnie został przerobiony na xml-schemowego regexpa przez zamianę \d na [0-9]).
  • godziny – no tu trudniej od 00 do 19 to nie problem. potem jeszcze 20-23. da się: ( [01]\d | 2[0-3] ) (używam notacji regexpów perla z rozszerzeniem /x dzięki czemu jest to trochę czytelniejsze.
  • cały regexp na godzinę: ( [01]\d | 2[0-3] ) : [0-5]\d : [0-5]\d . tak. wiem, że mogłem na końcu dać: ( : [0-5]\d){2}, ale wiedziałem, że będę to potem konwertował do regexpów ze schemy, więc wolałem nie przeginać z “cudami".

data. no to jest trudniejsze. haczyk tkwi w latach przestępnych. w dodatku na przestępność są 3 reguły: %4, %100 i %400.

nic to. twardym trzeba być, nie miętkim.

stwierdziłem, że zrobię tego regexpa bazując na ciągu wielu alternatyw:

  • dla dowolnego roku, część miesięcy (01, 03, 05, 07, 08, 10, 12) ma 31 dni, więc można użyć: \d\d\d\d – (0[13578]|1[02]) – ([012]\d|3[01])
  • dla dowolnego roku, część miesięcy (04, 05, 09, 11) ma 30 dni, więc używam: \d\d\d\d – (0[469]|11) – ([012]\d|30)
  • w każdym roku luty ma co najmniej 28 dni: \d\d\d\d – 02 – ([01]\d|2[0-8])

no i teraz zaczynają się schody.

zacznijmy od prostszego – przestępne są te lata które są podzielne przez 4, ale nie przez 100.

ponieważ 100 jest podzielne przez 4, to znaczy, że:

  • pierwsze dwie cyfry roku są nieistotne
  • ostatnie dwie – nie mogą być 00 (bo wtedy rok jest podzielny przez 100)
  • ostatnie dwie muszą być podzielne przez 4

no to już mamy pewne zręby. w jaki sposób regexpem poznać czy liczba jest podzielna przez 4?

zobaczmy jakie to liczby. $ perl -e ‘$_%4||printf “%02u, “, $_ for 0..99'

00, 04, 08, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96

co tu widać? niewiele. ale może jakbym tak zapisał to w ten sposób:

00, 04, 08, 12, 16,

20, 24, 28, 32, 36,

40, 44, 48, 52, 56,

60, 64, 68, 72, 76,

80, 84, 88, 92, 96

WOW! mam regułkę. jeśli pierwsza cyfra jest parzysta, to druga musi być jedną z 0, 4, 8. a jeśli nieparzysta, to druga musi być równa 2 lub 6.

zapis regexpowy: ( [02468][048] | [13579][26] )

no tak. ale na to trzeba nałożyć warunek, że te cyfry to nie może być 00. to powoduje, że jeśli pierwszą cyfrą (z dwóch) jest 0, to druga może być tylko 4 lub 8. tak więc regexp na liczbę 2 cyfrową podzielną przez 4, różną od 00 jest:

( 0[48] | [2468][048] | [13579][26]

tak więc pierwszy warunek na rok przestępny – podzielny przez 4, ale niepodzielny przez 100 wygląda tak:

\d\d ( 0[48] | [2468][048] | [13579][26] ) – 02 – [012]\d

ok. pozostało wziąć warunek na lata podzielne przez 100, ale podzielne też przez 400. i to sie okazuje trywialne. po tak jak przed chwilą liczyłem lata podzielne przez 4, tak lata podzielne przez 400 liczy się tak samo:

( 0[48] | [2468][048] | [13579][26] ) 00 – 02 – [012]\d
finalny regexp walidujący datę:

(
\d\d\d\d – (0[13578]|1[02]) – ([012]\d|3[01])
|
\d\d\d\d – (0[469]|11) – ([012]\d|30)
|
\d\d\d\d – 02 – ([01]\d|2[0-8])
|
\d\d ( 0[48] | [2468][048] | [13579][26] ) – 02 – [012]\d
|
( 0[48] | [2468][048] | [13579][26] ) 00 – 02 – [012]\d
)
dodać do tego godzinę i już 🙂 super.

najzabawniejsze w tym, że przeprowadzony przeze mnie test pokazał, że ten regexp jest szybszy niż walidowanie bazujące na porównywaniu liczb!

zrobiłem testowy program. odpaliłem i dostałem taki wynik:

  • function  5348/s
  • regexp   14045/s

dziwne.

oczywiście nie polecam walidowania daty regexpami. czytelniejsze są normalne funkcje. a prędkość 5300/s to i tak aż za dużo.

nowy browser – z mocno ukierunkowanym zastosowaniem

na rynku pojawiła się nowa przeglądarka www. zasadniczo – po co komu kolejna?

ale tak jak flock jest ukierunkowany na “social networks", tak heatseek jest przeglądarką ukierunkowaną na pornografię.

działa tylko pod windows i bazuje na silniku renderującym explorera – czyli większość stron xxx będzie się wyświetlała dobrze.

a co takiego eXXXtra ma heatseek?

  • aby uruchomić program trzeba podać hasło. wszystkie ściągnięte pliki (obrazki, filmy) są automatycznie szyfrowane, i ich oglądanie jest możliwe jedynie poprzez heatseek. koniec z przypadkowym odkryciem twojej kolekcji przez żonę/dziewczynę/rodziców.
  • eliminują spyware, wirusy i popupy. pewnie nie wszystkie, bo technologie się zmieniają, ale przynajmniej walczą.
  • wygoda użytkowania – ściąganie plików/filmów inicjalizuje się jednym kliknięciem. po ściągnięciu można tworzyć playlisty czy grupować materiał w dowolny sposób
  • przy odtwarzaniu filmów przeglądarka obsługuje bookmarkowanie – dzięki czemu można szybko skoczyć do ulubionej sceny
  • “panic button" – jeden klik i aplikacja się zamyka. bez pytania, czy czegokolwiek innego

wersja podstawowa przeglądarki jest za darmo. wersja premium kosztuje jednorazowo $20, i daje kilka dodatkowych funkcji z których najważniejszą jest możliwość odszyfrowania ściągniętych plików i zapisania w dowolnym miejscu aby móc np. odtwarzać czymś innym nić heatseek.

pozostaje jedynie postawić sobie gdzieś windows 🙂

używasz linuksa z kde, ale z zazdrością patrzysz na wygląd macosx?

do kde jest moduł, nazywający się baghira, który udostępnia odpowiednie wyglądy widgetów, okien itd.

biorąc go, niestandardowy zestaw ikon można spowodować, że kde będzie wyglądało tak:

OS_Clone30.jpg

OS_Clone31.jpg

jak to zrobić? dokładne howto możesz przeczytać tutaj. howto omawia wszystkie kroki od instalacji custom-widgetów, przez ikony, fonty, sidebar konquerora itd.

wreszcie da się używać tego (nie ma co ukrywać) miłego interfejsu bez kupowania przegrzewających się, odbarwiających czy szpiegujących użytkownika mac'ów.

jedyny fajny mercedes stał się … lepszy

nie lubię mercedesów. zdarzyło mi się tym jeździć i uważam, że są:

  • przereklamowane
  • za drogie
  • niewygodne

ale. jest 1 mercedes który mi się podoba. mercedes slr mclaren.

drugi owoc współpracy mercedes-benza z mclarenem (pierwszy, mercedes mclaren f1 był szybki i imponujący, ale brzydki)  jest po prostu śliczny:

slrmclaren04.jpg
taki powstał, był i był cudowny.

jeśli ktoś nie wie – slr jest to wskrzeszona nazwa – slr'y powstawały w latach 50 i były to bardzo szybkie samochody wyścigowe.

jednym z najsłynniejszych, był mercedes slr, który w 1955 wygrał wyświg mille miglia. miał wtedy numer 722.

na pamiątkę tego egzemplarza i wspaniałego zwycięstwa, merceder wypuścił drugiego nowego slr'a: mercedes slr 722 edition.

wygląda groźniej:

slr72207.jpg

a co potrafi? z tego samego silnika co w slr'rze z 2004, wyciągnięto więcej koni: 650 zamiast 617.

efekt? 62 mile na godzinę (coś koło 100km/h) już po 3.6 sekundy! (poprzedni slr był gorszy o 0.2 sekundy). 124 mile (200km) “wyciąga" już po 10.4 sekundy!
prędkość maksymalna: 337 km/h.

cechą charakterystyczną poprzedniego slr'a było (poza fenomenalnym przyspieszaniem) świetne hamowanie.

w 722 jeszcze to poprawiono instalując nowe, większe tarcze. nie mam dokładnych danych o hamowaniu 722, ale poprzedni slr potrafił zahamować z  62 mil na godzinę na dystansie niecałych 35 metrów, generując opóźnienie 1.3g (jeśli wartość nic wam nie mówi – pojazd przyspieszający z 1.3g osiągnąłby 100km/h w ciągu 2.8 sekundy!

samochód – śliczny, sporo potrafi i jest podobno miły w użyciu. jedyny minus – cena. za nowe 722 trzeba dać 650 tysięcy euro. po 1000 euro za każdego konika pod maską 🙂