Wyrażenia regularne unicode

Problem

Mamy formularz z polami, które musimy zwalidować pod kątem poprawności danych. Pole może zawierać jedynie litery (duże, małe – wszystko jedno).

Jakim wyrażeniem regualarnym realizujemy sprawdzanie? Pierwsza myśl to [a-zA-Z]… niestety walidacja nie zadziała poprawnie, gdyż w zakres [a-z] uwzględnia jedynie 26 liter alfabetu łacińskiego natomiast nie uwzględnia znaków diakrytycznych czyli litery [ą, ć, ę, ł, ń, ó, ś, ź, ż] nie spełnią kryterium walidacji.

Zatem aby walidacja działała poprawnie trzeba by dodać do zbioru [a-zA-Z] litery ze znakami diakrytycznymi. Otrzymujemy zatem wyrażenie: [a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]. Wyrażenie to zadziała poprawnie, jednak jest narażone na potencjalne błędy, łatwo zapomieć o którejś literze lub zrobić literówkę. W przypadku gdy tworzymy aplikacje multijęzykową czynność tą będziemy musieli powtórzyć dla każdego z obsługiwanych języków.

Poprawne rozwiązanie

W programowaniu jak już coś sie robi to należy zrobić to porządnie, tak aby swojego kodu nie trzeba było poprawiać, modyfikować etc…

Z pomocą przychodzi nam kodowanie UNICODE, w które umożliwia napisać reguły na litery posiadające znaki diakrytyczne. Wyrażenie \p{L} oznacza jakąkolwiek literę w jakimkolwiek języku, może by być: Ł, ź, ü, ß czy cookolwiek co jest literą (także nie łacińską). Zatem nasze wyrażenie walidujące pole zawierające włącznie litery będzie wyglądać następująco:

preg_match('#^\pL+$#', 'MałyŁoß');

Gwoli wyjaśnienia \pL to skrót od \p{L} a \p{L} oznacza znak (code point) unicode \p należący do kategorii liter {L}.

Klasy/kategorie znaków w unicode:

\p{L} – wszystkie litery
\p{M} – wszystkie znaki, które samotnie nie występują, musi być połączony z innym znakiem i składa się na np. akcent, umlaut, etc…
\p{Z} – wszystkie znaki niewidoczne
\p{S} – wszystkie symbole, znaki walut, znaki matematyczne
\p{N} – wszystkie znaki numeryczne
\p{P} – wszystkie znaki interpunkcyjne: przecinki, średniki etc…
\p{C} – wszystkie niewidoczne znaki kontrolne i niewykorzystywane znaki code point

Przeczytaj więcej o właściwościach wyrażeń regualrnych w unicode.

Aktualny czas a transakcja w PostgreSQL

Ostatnio miałem ciekawy case’ik – musiałem w obrębie jednej transakcji zmienić dane rekordów. Wszystko odbywało się w kilku funkcjach plpgsql, funkcje mogły się wywoływać rekurencyjnie w triggerach a zmiany dotyczyły między innymi pól typu TIMESTAMP. Wartości pól TIMESTAMP rekordów były zmieniane i ich zmiany automatycznie wpływały na w działanie skryptu.

Jakież było moje zdziwienie gdy skrypt nie chciał działać, a wszystkie rekordy miały taką zamą wartość pola typu TIMESTAMP. Otóż okazało się, że po rozpoczęciu transakcji funkcje zwracające aktualny czas:

SELECT CURRENT_TIME;
SELECT CURRENT_TIMESTAMP;
SELECT NOW();

przestały zwracać aktualny czas a zamiast tego czas rozpoczęcia transakcji.

I o dziwo jest to rzeczywiście poprawne działanie. Na stronie http://www.postgresql.org/docs/8.3/static/functions-datetime.html można przeczytać:

Since these functions return the start time of the current transaction, their values do not change during the transaction. This is considered a feature: the intent is to allow a single transaction to have a consistent notion of the „current” time, so that multiple modifications within the same transaction bear the same time stamp.

Zatem wyjściem z tego problemu jest użycie postgres’owej funkcji timeofday() i zrzutowanie jej wyniku na TIMESTAMP, realizyje to zapytanie:

SELECT cast( timeofday() AS timestamp);

Edit: zamiast timeofday() można uzyć funkcji clock_timestamp(), która zwraca date w postaci TIMESTAMP’a

SELECT clock_timestamp();

Dla ciekawości sprawdziłem jak zachowują się inne dialekty SQL i okazało się, że zarówno MySQL jak i MSSQL realizują tą sytuację odmniennie niż PostgreSQL, zatem:

  • MySQL – w transakcji zwracany jest bieżący czas (nie czas rozpoczęcia transakcji)
  • MSSQL – w transakcji zwracany jest bieżący czas (nie czas rozpoczęcia transakcji)
  • PgSQL – w transakcji zwracany jest czas rozpoczęcia transakcji