18 gru 2009

Zwracanie rekordu z procedury składowanej w PostgreSQL

Jeśli z procedury/funkcji składowanej w bazie danych PostgreSQL chcemy zwrócić kilka wartości, to możemy zadeklarowąć ją jako zwracającą typ RECORD, ad-hoc utworzyć rekord z wartościami i zwrócić go.
Nie jest to trudne. Niestety "dokumentaliści" PostgreSQL zapomnieli pokazać jak taką funkcję wywołać.
Poniżej przykład znaleziony na jednym z forów Posrgresa:

CREATE FUNCTION xyz() RETURNS record AS
$$
declare
 abc RECORD;
begin
 abc := (1, 2);
 return abc;
end;
$$
language plpgsql;

select a, b from xyz() as (a int, b int);


Czyli wołamy SELECTA na tym, co zwróci nasza funkcja, ale jeszcze musimy zamapować wartości z rekordu na kolumny klauzulą AS.

29 paź 2009

Nie używać wielkich liter w nazwach funkcji/procedur składowanych PostgresQL!

Postanowiłem napisać swoją pierwszą procedurę składowaną w PostgreSQL.

Utworzyłem ją za pomocą kreatora w pgAdmin, ale za nic w świecie nie mogłem jej uruchomić.

Każda próba wywołania, kończyła się komunikatem:
No function matches the given name and argument types. You might need to add explicit type casts.

Sprawdziłem, że wywołuję ją w tym samym schemacie (public), w którym jest ona zdefiniowana.

Uprościłem treść procedury do minimum (usunąłem wszystkie parametry) i oto co mi zostało:

CREATE OR REPLACE FUNCTION "InsertEvent"()
  RETURNS integer AS
$BODY$
BEGIN

    RETURN 1;

END;$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST 100;

A wołam ją tak:
select InsertEvent() AS ret

Nadal ten sam komunikat: nie może znaleźć wołanej procedury!

Minęła godzinka prób... ale zaraz, zaraz... nieco dziwnie to wygląda w definicji:
FUNCTION "InsertEvent"()

czasem pgAdmin wstawia mi cudzysłowia w identyfikatorach - nigdy nie zagłębiałem się dlaczego to robi. Zmieniłem wywołanie na:
select "InsertEvent"() AS ret

Hurrra! Działa!

Gdybym od początku nazwał procedurę nie używając konwencji CamelCase, np. insert_event, to cudzysłowia nie byłyby potrzebne i wszystko by się wykonało za pierwszym razem.

Szczegółowe wyjaśnienie co się stało:
Gdy użyłem nazwy procedury: InsertEvent, pgAdmin utworzył tzw. Quoted identifier, który jest case sensitive.
Dla nazwy: insert_event pgAdmin utworzył zwykły identyfikator, który jest case insensitive.

Więcej o identufikatorach w PostgreSQL tutaj


Wniosek:
Na wszelki wypadek lepiej używać standardowej konwencji nazywania identyfikatorów postgres: wszystkie litery małe rozdzielone znakiem "_". Np. insert_event

8 wrz 2009

Serwer ssl wykorzystujący boost::asio zawsze korzysta z jednego wątka

Napisałem serwer wykorzystujący boost::asio::ssl, który zawierał pulę 10 wątków obsługujących żądania klientów.
Szybko jednak okazało się, że żądania od klientów zawsze są obsługiwane w jednym wątku roboczym:
jeśli wątek ten wykonywał długą operację, to request od innego klienta czekał aż operacja w pierwszym wątku zakończy się.

Dokładną przyczynę tego błędu opisałem na grupie boost-users w poście:
"[asio] SSL enabled http server3 example is blocking"

Rozwiązanie problemu jest dość proste:
nie wykonywać długich operacji bezpośrednio w handlerze wywoływanym przez async_read(), async_write(). Zamiast tego handler powinien wysłać tę operację do io_serwis, aby wykonała się asynchoriniczie.

Przykład handlera wywyoływanego po asynchronicznym odczycie danych (requestu od klienta):

void Request::readHeaderHandler( const boost::system::error_code
&error, std::size_t bytesTransferred )
{
m_ioService.post( boost::bind
( &Request::executeCommandHandler::executeCommandHandler,
shared_from_this() ) );

}

void Request::executeCommandHandler()
{
// long running operation
char c;

}

1 wrz 2009

Jak serwer ssl z obiektu boost::asio::stream może uzyskać adres IP klienta?

// definicja stream-u ssl opartego na sockecie tcp
typedef boost::asio::ssl::stream ssl_socket;

// kod inicjujący nasłuch serwera na sockecie zakończony wywołaniem: async_accept() / accept()

// jak już klient się połączył, to mamy obiekt ssl_socket:
ssl_socket m_socket;

// z, którego można pobrać IP klienta:
const boost::asio::ip::tcp::endpoint &endpoint = m_socket.lowest_layer().remote_endpoint();
const boost::asio::ip::address &addr = endpoint.address();

std::cout << "client: " << addr.to_string() << std::endl;

Jak odczytywać certyfikaty X509 za pomocą openssl?

Ano tak:
http://kahdev.wordpress.com

31 sie 2009

error C2632: '__int64' followed by '__int64' is illegal przy korzystaniu z log4cpp

Kompilowałem pod Visual C++ 2008 projekt wykorzystujący log4cpp.

Niestety biblioteka log4cpp dawno już nie była aktualizowana, stąd nie do końca chce ona chodzić z aktualnymi kompilatorami.

Podczas kompilacji pod VC++2008 otrzymywałem następujący błąd:
error C2632: '__int64' followed by '__int64' is illegal

Nigdzie w necie nie znalazłem rozwiązania tego problemu. Powyższy komunikat nie wskazuje, że problem leży w log4cpp. Myślałem, że wina jest w Boost.

Po małym rozpoznaniu okazało się, że należy dodać:
#define LOG4CPP_HAVE_INT64_T

przed włączeniem pierwszego pliku nagłówkowego z log4cpp i wszystko gra!

Wolna kompilacja projektu za pomocą make z MSys

Mam projekt oparty o makefile.

Po uruchomieniu make z pakietu MSys muszę czekać ponad minutę (!) aby dowiedzieć, się że:
make: Nothing to be done for `all'.

Natomiast make zainstalowany z MinGW (mingw32-make.exe) wykonuje to samo w 10 sekund!

Zatem cóż, trzeba zmusić Eclipse żeby używał make-a z mingw. Skopiowałem make z MinGW do MSys i voila, teraz kompilacja projektu przyspieszyła!

Niestety daleko do szybkości linuksowej: poniżej 3 sekund na projekcie bez zmian :-(

13 sie 2009

Kompilacja log4cpp pod Visual C++ 2008

Próbowałem skompilować bibliotekę do logowania log4cpp, używając sposobu opisanego w samej bibliotece. Niestety autorzy przygotowali tylko plik dsp dla Visuala 6.0, który po imporcie do Visuala 2008 powoduje następujący błąd kompilacji:

error PRJ0019: A tool returned an error code from "Performing Custom
Build Step" log4cpp

Okazuje się jednak, że wystarczy utworzyć w Visualu nowy projekt z istniejącego kodu: File / New / Project From existing code

wskazać źródłowe log4cpp

w Properties dla projektu dodać katalog /include projektu log4cpp

i tak utworzony projekt ładnie skompiluje nam bibliotekę log4cpp.

Tutaj informacja jak zmusić nagłówki log4cpp do poprawnej kompilacji w naszym programie kompilowanym pod VC++2008.

4 sie 2009

Deklarować rzucane wyjątki? Tak, ale tylko w komentarzu!

Załóżmy, że piszemy metodę, która obsługuje błędy przez rzucenie wyjątku:
int calculate( int something ) throw( MyException )
{
    int calculated = 0;

    if( something < 0 )
    {
        throw MyException( "Parametr something musi byc dodatni" );
    }

    initCalculator();

    return calculated;
}

Pytanie: czy powinniśmy zadeklarować, że metoda rzuca wyjątek?

Przykładowo:
int calculate( int something ) throw( MyException );
Bardzo mi się podoba podejście Javy w kwestii deklarowania rzucanych wyjątków:
jeśli metoda rzuca jakiś wyjątek to musi on być zadeklarowany w nagłówku metody.
Pilnuje tego kompilator: jeśli programista rzuci wyjątek bez odpowiedniej deklaracji throw w nagłówku metody, to otrzyma błąd kompilacji.

Dzięki takiemu podejściu osoba wykorzystująca naszą metodę wie jakie wyjątki mogą być przez nią rzucone, przez co unikamy błędów wykonania typu "unhandled exception".
Co więcej jeśli ktoś wywoła naszą metodę bez odpowiedniej obsługi wyjątku, to otrzyma błąd kompilacji!

A co się dzieje w c++? Niestety tutaj podejście jest mniej racjonalne :-(

Przede wszystkim: kompilator do niczego nas nie zmusza. Nie musimy deklarować rzucanego wyjątku. Ale załóżmy, że jesteśmy porządni (programowaliśmy w Javie :-) i deklarujemy wyjątek. Co się dzieje?

Spójrzmy na ten fragment kodu:
struct MyException{ MyException( const string &what ){} };
struct OtherException{ OtherException( const string &what ){} };

void initCalculator() throw( OtherException )
{
    throw OtherException( "Kalkulator nie jest zainstalowany" );
}

int calculate( int something ) throw( MyException )
{
    int calculated = 0;

    if( something < 0 )
    {
        throw MyException( "Parametr something musi być dodatni" );
    }

    initCalculator();

    return calculated;
}

int main()
{
    try
    {
        calculate( 2 );
        cout << "ok" << endl;
    }
    catch( const MyException &exc )
    {
        cout << "MyException" << endl;
    }
    catch( const OtherException &exc )
    {
        cout << "OtherException" << endl;
    }
    catch( ... )
    {
        cout << "całkiem inny wyjątek" << endl;
    }
    return 0;
}

Wynik działania programu jest taki:
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

Jak widzimy main() wywołuje calculate(), która wywołuje initCalculator();
Niestety initCalculator() rzuca nam wyjątek OtherException, który został zadeklarowany w initCalculator() ale nie został zadeklarowany w calculate().

I teraz najciekawsze:
Wyjątek OtherException nie zostanie złapany w main, pomimo że mamy tam odpowiednią klauzulę catch( OtherException )!!!

Co się dzieje?
Kompilator widzi deklarację:
int calculate( int something ) throw( MyException )
i zakłada, że tylko wyjątki typu MyException mogą być przez nią rzucone.
Jeśli calculate() rzuci inny wyjątek, to natychmiast spowoduje to wywołanie globalnej funkcji kończącej program (ustawianej przez ::set_terminate()).

A jeśli nie zadeklarujemy, że metoda calculate() rzuca jakikolwiek wyjątek, to wyjątkek rzucony przez initCalculator() zostanie złapany w main(). Dokładnie tak, jak się tego spodziewamy!

Jak zatem postępować?

Deklarować wyjątki w sposób nieformalny, umieszczając słowo kluczowe throw w komentarzu:
int calculate( int something ) //throw( MyException )
Dzięki temu:
- użytkownik naszej metody będzie wiedział jakich wyjątków może się spodziewać
- jeśli nasza metoda calculate przypadkiem rzuci innym wyjątkiem, to catch(...) go złapie!