<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Zeznania programisty &#187; Bazy danych</title>
	<atom:link href="http://www.jakubiak.eu/category/databases/feed" rel="self" type="application/rss+xml" />
	<link>http://www.jakubiak.eu</link>
	<description>Moje przeżycia związane z: FLEX, JEE, EJB, JSF, FreeBSD, PostgreSQL i PHP i Windows ;)</description>
	<lastBuildDate>Sat, 12 Mar 2011 13:00:34 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>csplit a olbrzymie pliki</title>
		<link>http://www.jakubiak.eu/2009/12/csplit-a-olbrzymie-pliki.html</link>
		<comments>http://www.jakubiak.eu/2009/12/csplit-a-olbrzymie-pliki.html#comments</comments>
		<pubDate>Wed, 02 Dec 2009 11:44:02 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[Bazy danych]]></category>
		<category><![CDATA[Programowanie]]></category>

		<guid isPermaLink="false">http://www.jakubiak.eu/2009/12/csplit-a-olbrzymie-pliki.html</guid>
		<description><![CDATA[Moim zadaniem było naprawić uszkodzony plik kopii bezpieczeństwa bazy danych PostgreSQL. Wiedziałem w której linii jest błąd i na czym polega, jednak plik kopi bezpieczeństwa był na tyle olbrzymi, że edycja jego edycja była bardzo utrudniona. Z pomocą przyszedł mi program csplit znaleziony w pakiecie GnuWin32. Program ten potrafi podzielić plik na mniejsze na podstawie [...]]]></description>
			<content:encoded><![CDATA[<p>Moim zadaniem było naprawić uszkodzony plik kopii bezpieczeństwa bazy danych PostgreSQL. Wiedziałem w której linii jest błąd i na czym polega, jednak plik kopi bezpieczeństwa był na tyle olbrzymi, że edycja jego edycja była bardzo utrudniona. Z pomocą przyszedł mi program <strong>csplit</strong> znaleziony w pakiecie <a href="http://gnuwin32.sourceforge.net/" onclick="pageTracker._trackPageview('/outgoing/gnuwin32.sourceforge.net/?referer=');">GnuWin32</a>. Program ten potrafi podzielić plik na mniejsze na podstawie dopasowania przez wyrażenie regularne. Wydałem następujące polecenie:
</p><p><pre><code>csplit –f "out_" nazwa_pliku_backup.sql "/^COPY /" {*}
head –n 1 out_*
</code></pre></p><p>W wyniku pracy programu csplit uzyskałem kilkadziesiąt mniejszych plików – po jednym dla każdej tabeli w bazie.</p>]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2009/12/csplit-a-olbrzymie-pliki.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bezpieczeństwa kopiowania danych podczas prac programistycznych</title>
		<link>http://www.jakubiak.eu/2009/09/bezpieczenstwa-kopiowania-danych-podczas-prac-programistycznych.html</link>
		<comments>http://www.jakubiak.eu/2009/09/bezpieczenstwa-kopiowania-danych-podczas-prac-programistycznych.html#comments</comments>
		<pubDate>Sun, 06 Sep 2009 10:22:35 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[Bazy danych]]></category>

		<guid isPermaLink="false">http://www.jakubiak.eu/2009/09/bezpieczenstwa-kopiowania-danych-podczas-prac-programistycznych.html</guid>
		<description><![CDATA[Programując nową wersję aplikacji, często zaczynamy od zrobienia kopii aktualnej wersji systemu produkcyjnego. To kusząca idea, jednak należy pamiętać o pewnych zasadach bezpieczeństwa z tym związanych. Idee jest kusząca – ponieważ budując nowe funkcjonalności testujemy je w warunkach zbliżonych do produkcyjnych. Dzięki temu łatwiej jest wyłapać błędy związane z wydajności i łatwiej jest przeprowadzać testy. [...]]]></description>
			<content:encoded><![CDATA[<p>Programując nową wersję aplikacji, często zaczynamy od zrobienia kopii aktualnej wersji systemu produkcyjnego. To kusząca idea, jednak należy pamiętać o pewnych zasadach bezpieczeństwa z tym związanych. Idee jest kusząca – ponieważ budując nowe funkcjonalności testujemy je w warunkach zbliżonych do produkcyjnych. Dzięki temu łatwiej jest wyłapać błędy związane z wydajności i łatwiej jest przeprowadzać testy. 
</p><p><img src="http://www.jakubiak.eu/wp-content/uploads/2009/09/090609_1022_Bezpieczest1.png" alt=""/>
	</p><p>Robiąc kopię bazy danych, należy pamiętać o pewnych zasadach bezpieczeństwa. Gdy kopia danych systemu ma być używana do celów deweloperskich – to należy zniszczyć wszystkie wrażliwe dane w bazie: imiona i nazwiska, hasła, adresy, numery telefonów. Taka kopia danych może być bowiem użyta na laptopach programistów, lub na słabiej zabezpieczonych systemach. Warto więc wypracować pewne procedury w tym zakresie. 
</p><p>Kilka lat temu zaproponowałem następującą procedurę: po wykonaniu kopii wszystkie imiona, hasła, adresy, nazwy i temu podobne dane zostaną zniszczone. Są trzy typy niszczenia.
</p><p><strong>Niszczenie tekstów i nazw własnych.</strong> Dla każdego znaku w ciągu znaków powinien zostać wylosowany inny znak. Czyli na przykład, gdy w bazie danych był ciąg znaków &#8220;Antoni Jakubiak&#8221; to mógł zostać zastąpiony &#8220;Xhkkia Uksbtlka&#8221;. Dzięki temu podczas testowania systemu widzimy ciągi znaków na ekranie, i zajmują one mniej więcej tyle samo miejsca co prawdziwe dane. Jest to rozwiązanie nieco lepsze, od kasowania danych lub losowania nowych danych w całości gdyż testuje się wygodniej. 
</p><p><strong>Niszczenie adresów e-mail </strong>jest szczególnie ważne. Po pierwsze, adresy e-mail naszych użytkowników nie powinny się gdzieś zgubić. Po drugie, gdy nasz system wysyła e-mail automatycznie, to głupio by były żeby wyszły one z testowego systemy do prawdziwego użytkownika: &#8220;Kupiłeś sokowirówkę – obciążyliśmy twoją kartę kredytową&#8221;.  Uważam, że najlepiej jest zastąpić wszystkie adresy e-mail występujące w bazie danych – własnym adresem e-mail. Podoba sprawa dotyczy numerów telefonów, szczególnie gdy nasz system dzwoni lub automatycznie wysyła SMSy.  Gorzej, jeżeli adres e-mail jest polem unikalnym – na przykład loginem. Wtedy trzeba się napracować w stylu: <em>update auth set email=concat(&#8216;mój.adres.email+dev&#8217;||next_val(&#8216;jakaś.tam.sekwencja&#8217;)||&#8217;@gmail.com&#8217;</em>. Z całą pewnością jednak warto!
</p><p><strong>Niszczenie haseł. </strong>Hasła w bazie danych zwykle są jakoś zaszyfrowane, jednak ich kradzież, szczególnie połączona z kradzieżą loginu lub e-mail, będzie kompromitująca. Uważam, że hasła najlepiej jest po prostu skasować.  W testowej kopii systemu można też ustawić wszystkim użytkownikom jednakowe hasła – jednak lepiej je po prosu skasować zupełnie. Testerzy systemu założą sobie po prostu nowe hasło w procedurze jego odzyskiwania. Oczywiście, jeżeli wcześniej zmieniliśmy adresy e-mail. 
</p><p>Napisanie takiej procedury dobremu programiście znającemu system zajmie na pewno mniej, niż dzień roboczy. Warto, żeby była to procedura półautomatyczna, lub żeby w jakiejś dokumentacji programistycznej systemu została ona zapisana. Warto pilnować, by taka procedura była przestrzegana. Ogólnie, warto pamiętać o tym by pilnować backupów systemu. </p>]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2009/09/bezpieczenstwa-kopiowania-danych-podczas-prac-programistycznych.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Jak implementować drzewo w bazie danych?</title>
		<link>http://www.jakubiak.eu/2009/04/jak-implementowac-drzewo-w-bazie-danych.html</link>
		<comments>http://www.jakubiak.eu/2009/04/jak-implementowac-drzewo-w-bazie-danych.html#comments</comments>
		<pubDate>Wed, 22 Apr 2009 05:48:22 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[Bazy danych]]></category>

		<guid isPermaLink="false">http://www.jakubiak.eu/?p=314</guid>
		<description><![CDATA[Drzewo w bazie danych to jeden z problemów, o których fajnie się dyskutuje na studiach. Istnieje wiele sposobów implementacji drzewa, znany hacker Depesz wymienił 5. Niestety, często zapomina się o najważniejszych i najprostszych metodach. Żadnych identyfikatorów rodzica. Tylko ścieżka – która jest opisana tekstowo. Lub identyfikator rodzica i ścieżka. Na przykład: Identyfikator rekorduIdentyfikator rodzicaŚcieżka1 0001210001 / [...]]]></description>
			<content:encoded><![CDATA[<p>Drzewo w bazie danych to jeden z problemów, o których fajnie się dyskutuje na studiach. <a href="http://www.depesz.com/various/various-sqltrees.php" onclick="pageTracker._trackPageview('/outgoing/www.depesz.com/various/various-sqltrees.php?referer=');">Istnieje wiele sposobów implementacji drzewa, znany hacker Depesz wymienił 5</a>. Niestety, często zapomina się o najważniejszych i najprostszych metodach. Żadnych <em>identyfikatorów rodzica</em>. Tylko <em>ścieżka</em> – która jest opisana tekstowo. Lub <em>identyfikator rodzica </em>i <em>ścieżka</em>. Na przykład:
</p><div><table style="border-collapse:collapse" border="0"><colgroup><col style="width:212px"/><col style="width:212px"/><col style="width:212px"/></colgroup><tbody valign="top"><tr><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Identyfikator rekordu</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Identyfikator rodzica</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Ścieżka</strong></span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>1</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>2</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0002</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>3</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001 / 0003</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>4</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0003 / 0004</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>5</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001 / 0003 / 0005</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>6</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0006</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>7</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">0001 / 0007</span></p></td></tr></tbody></table></div><p>
 </p><p>Jakie są zalety takiego rozwiązania? Prostota implementacji, błyskawiczne wyszukiwanie potomków, szybkie wyszukiwanie rodziców, miłe przeglądanie bazy w konsoli SQL, szybkie sortowanie. Oczywiście, gdy potrzebujemy sortowania to należy użyć dodatkowego identyfikatora kolejności, na przykład:
</p><div><table style="border-collapse:collapse" border="0"><colgroup><col style="width:128px"/><col style="width:128px"/><col style="width:80px"/><col style="width:305px"/></colgroup><tbody valign="top"><tr><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Identyfikator rekordu</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Identyfikator rodzica</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Kolejność</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Ścieżka # Kolejność</strong></span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>1</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001 # 0001</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>2</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">2</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0002 # 0002</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>3</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001 / 0003 # 0001</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>4</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0003 / 0004 # 0001</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>5</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">2</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">0001 / 0003 / 0005 # 0002</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>6</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">0001 / 0006 # 0003</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>7</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">4</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">0001 / 0007 # 0004</span></p></td></tr></tbody></table></div><p>
 </p><p>Zestawienie zalet i wad rozwiązanie z ścieżką:
</p><ul><li><div>Wady
</div><ul><li>Więcej miejsca na dysku
</li></ul></li><li><div>Zalety
</div><ul><li>Prosta implementacja
</li><li>Prostota modelu
</li><li>Błyskawiczne wyszukiwanie potomków
</li><li>Szybkie wyszukiwanie rodziców
</li><li>Szybkie sortowanie
</li><li>Miłe przeglądanie bazy w trybie administratora
</li></ul></li></ul><p>Czy są lepsze rozwiązania? Oczywiście. Wystarczy wprowadzić założenie, że w naszym drzewie może być tylko kilka poziomów zagłębienia i liczba ta jest określona przez programistę systemu. Wtedy, w bazie danych możemy zapisywać informację w następujący sposób:
</p><div><table style="border-collapse:collapse" border="0"><colgroup><col style="width:169px"/><col style="width:156px"/><col style="width:156px"/><col style="width:161px"/></colgroup><tbody valign="top"><tr><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Identyfikator rekordu</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Rodzic poziom 1</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Rodzic poziom 2</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-top:  solid #4f81bd 1.0pt; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>Kolejność</strong></span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>1</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>2</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"> </td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">2</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>3</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>4</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91"><strong>5</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">3</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-right:  none"><p><span style="color:#365f91">2</span></p></td></tr><tr><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91"><strong>6</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px"> </td><td style="padding-left: 7px; padding-right: 7px"><p><span style="color:#365f91">3</span></p></td></tr><tr style="background: #d3dfee"><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91"><strong>7</strong></span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">1</span></p></td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"> </td><td style="padding-left: 7px; padding-right: 7px; border-left:  none; border-bottom:  solid #4f81bd 1.0pt; border-right:  none"><p><span style="color:#365f91">4</span></p></td></tr></tbody></table></div><p>
 </p><p>W świecie prawdziwych problemów, często właśnie ta trywialna implementacja drzewa będzie najlepsza: jest najprostsza w implementacji i jest rewelacyjnie szybka. Przykłady kodu:
</p><p><img src="http://www.jakubiak.eu/wp-content/uploads/2009/04/042209-0548-jakimplemen11.png" alt=""/>
	</p><p>Moim zdaniem, najprostsze rozwiązanie jest zwykle najlepsze. 
</p>]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2009/04/jak-implementowac-drzewo-w-bazie-danych.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PostgreSQL, dane binarne i dumpy</title>
		<link>http://www.jakubiak.eu/2008/11/postgresql-dane-binarne-i-dumpy.html</link>
		<comments>http://www.jakubiak.eu/2008/11/postgresql-dane-binarne-i-dumpy.html#comments</comments>
		<pubDate>Fri, 14 Nov 2008 08:58:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[PostgreSQL]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2008/11/postgresql-dane-binarne-i-dumpy.html</guid>
		<description><![CDATA[Zauważyłem nowa fajną cechę w PostgreSQL. Otóż potrafi on wykonać tekstowy zrzut bazy danych, która zawiera dane binarne. Czyli, mam w tabelkach PostgreSQL pliki wgrane przez użytkowników. Robię pg_dump i wszystko działa poprawnie. Kiedyś tak nie było i dlatego teraz się cieszę.]]></description>
			<content:encoded><![CDATA[Zauważyłem nowa fajną cechę w PostgreSQL. Otóż potrafi on wykonać tekstowy zrzut bazy danych, która zawiera dane binarne. Czyli, mam w tabelkach PostgreSQL pliki wgrane przez użytkowników. Robię pg_dump i wszystko działa poprawnie. Kiedyś tak nie było i dlatego teraz się cieszę.]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2008/11/postgresql-dane-binarne-i-dumpy.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Tłumaczenia w aplikacji na wiele sposobów</title>
		<link>http://www.jakubiak.eu/2007/12/tumaczenia-w-aplikacji-na-wiele-sposobw.html</link>
		<comments>http://www.jakubiak.eu/2007/12/tumaczenia-w-aplikacji-na-wiele-sposobw.html#comments</comments>
		<pubDate>Mon, 03 Dec 2007 10:51:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JPA]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/12/tumaczenia-w-aplikacji-na-wiele-sposobw.html</guid>
		<description><![CDATA[Zdarzyło mi się napisać aplikacje WWW, która miała działać w wielu krajach, w wielu językach. Potrzebowałem systemu tłumaczeń. Mówiąc systemu mam na myśli systematyczne podejścia do sprawy tłumaczeń. Tłumaczenia w mojej aplikacji są częścią jej biznesu. Najprościej zrozumieć to na przykładzie. Jest sobie hotel który ma opis. Ten opis może być przetłumaczony na wiele języków. [...]]]></description>
			<content:encoded><![CDATA[Zdarzyło mi się napisać aplikacje WWW, która miała działać w wielu krajach, w wielu językach. Potrzebowałem systemu tłumaczeń. Mówiąc systemu mam na myśli systematyczne podejścia do sprawy tłumaczeń.

Tłumaczenia w mojej aplikacji są częścią jej biznesu. Najprościej zrozumieć to na przykładzie. Jest sobie hotel który ma opis. Ten opis może być przetłumaczony na wiele języków. Do systemu można wprowadzić nowe hotele a każdy z nich będzie miał inne tłumaczenie. 

Wyobraźmy sobie tabelkę <code>hotel</code>. Ma ona trzy pola: identyfikator, nazwa i opis. Opis to coś co trzeba przetłumaczyć. Ale jak to zrobić?
<pre>
create table hotel(
  primary key int id,
  varchar nazwa,
  varchar opis
)
</pre>

Istnieje wiele sposobów rozwiązania tego problemu. Do głowy przychodzą mi takie:
<ul>
<li>W systemie tworzymy tabelę <code>hotel</code> zawierającą kolumnę <code>opis</code> dla każdego z dostępnych języków. Będzie to wyglądać mniej więcej tak:
<pre>
table hotel(
  primary key int id,
  varchar nazwa,
  varchar opis_en, 
  varchar opis_de, 
  varchar opis_pl
)
</pre>
Przedstawione rozwiązanie powinno działać bardzo szybko, jednak ma wadę &#8211; dodanie kolejnego języka do wymaga modyfikacji bazy danych &#8211; a to jest brzydkie. 
</li>
<li>Dla każdej wersji językowej tworzymy osobną tabelę zawierającą pola, które trzeba przetłumaczyć. Wygląda to mniej więcej tak:
<pre>
table hotel(
  primary key int id,
  varchar nazwa,
)
table hotel_en(
  primary key int id,
  varchar opis
)
table hotel_pl(
  primary key int id,
  varchar opis
)
</pre>
To rozwiązanie będzie działać szybko. Dodanie nowego języka będzie wymagać modyfikacji bazy danych. Oczywiście, korzystając z tego lub z poprzedniego rozwiązania warto napisać program, który będzie tłumaczył zapytania SQL i pobierał dane z odpowiednich tabel językowych. Obydwa te rozwiązania będą działać szybko jednak moim zdaniem nie są one ładne.
</li>
<li>Znam jeszcze coś takiego:
<pre>
table hotel(
  primary key int id,
  varchar nazwa
)
table hotel_tłumaczenia(
  primary key int id,
  int identyfikator_języka,
  varchar opis
)
</pre>
To rozwiązanie jest lepsze od poprzednich tym, że wprowadzenie do systemu nowego języka nie będzie wymagało zmiany w strukturze danych. 
</li>
<li>Ja najbardziej lubiłem jednak tworzenie tablicy słownikowej. W uproszczeniu wygląda to tak:
<pre>
table słownik (
  primary key int identyfikator_tłumaczonego_tekstu,
  int identyfikator_języka,
  varchar tłumaczenie
)
table hotel(
  primary key int id,
  varchar nazwa,
  identyfikator_tłumaczonego_tekstu opis
)
</pre>
To rozwiązanie jest o tyle fajne, że proste. Nie wymaga modyfikacji struktury bazy danych w momencie dodania nowego języka. Łatwo też pisać zapytania SQL, które pobierają teksty w wybranym języku. Niestety, to rozwiązanie jest wolniejsze, szczególnie wtedy gdy tabelka hotel ma wiele pól które chcemy przetłumaczyć. 
</li>
</ul>
Myślę, że można wymyślić jeszcze wiele innych rozwiązań tego problemu. Schemat myślenia można przenieś ze świata relacji do języków programowania obiektowego. 
Można zrobić też coś takiego:
<pre>
@Entity
class Hotel{
  @Id
  private Long id;
 
  private String nazwa;

  <b>@Tłumaczenie</b>
  private String opis;
}
</pre>
Tak to nowa JAVA. Dzięki adnotacjom możemy <b>programować wielowymiarowo</b>. Więc dlaczego by nie stworzyć dla naszej aplikacji nowego wymiaru &#8211; wymiaru tłumaczeń???


Ja właśnie to robię. Tworzę bibliotekę Java, która programistom będzie dostarczać wygodnego narzędzia rozwiązującego problem tłumaczeń. Tłumaczenia, nie będą już częścią aplikacji. Będą jej nowym wymiarem.

Zainteresowanych zapraszam do projektu:
<a href="http://code.google.com/p/jpa-translator/" onclick="pageTracker._trackPageview('/outgoing/code.google.com/p/jpa-translator/?referer=');">JPA Translator</a>]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/12/tumaczenia-w-aplikacji-na-wiele-sposobw.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Zend Framework i keszowanie zapytań do bazy danych</title>
		<link>http://www.jakubiak.eu/2007/06/zend-framework-i-keszowanie-zapyta-do.html</link>
		<comments>http://www.jakubiak.eu/2007/06/zend-framework-i-keszowanie-zapyta-do.html#comments</comments>
		<pubDate>Sat, 23 Jun 2007 12:36:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[ORM]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/06/zend-framework-i-keszowanie-zapyta-do.html</guid>
		<description><![CDATA[Witam, Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład Zend Framework a w szczególności jego kod odpowiedzialny za bazy danych &#8211; Zend_Db (działający prawie jak ORM) przypomina w pewien sposób to, za co świat Javy pokochał Hibernate. Konsekwencją użycia [...]]]></description>
			<content:encoded><![CDATA[Witam,

Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład <a href="http://framework.zend.com/" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/?referer=');">Zend Framework</a> a w szczególności jego kod odpowiedzialny za bazy danych &#8211; Zend_Db (działający prawie jak <a href="http://www.jakubiak.eu/labels/orm.html">ORM</a>) przypomina w pewien sposób to, za co świat Javy pokochał <a href="http://www.hibernate.org/" onclick="pageTracker._trackPageview('/outgoing/www.hibernate.org/?referer=');">Hibernate</a>.
Konsekwencją użycia ORM w projekcie jest to, że spada nam złożoność zapytań do bazy danych, natomiast zwiększa się ich ilość. Hibernate potrafi keszować obiekty. Zend Framework niestety nie. Jednak wczoraj tego bardzo potrzebowałem, renderowanie prostej strony WWW wymagało 500 zapytań do bazy, grrr, z czego 450 było powtórzeniem poprzednich zapytań. O ile keszowanie obiektów było by bardzo skomplikowane, to w miarę prosto można było keszować wyniki zapytań SQL w obrębie jednego żądania HTTP. 
Jak się do tego zabrać? Zauważyłem, że wszystkie zapytania do bazy danych produkowane przez Zend Framework w pewnym momencie trafią do metody <a href="http://framework.zend.com/apidoc/core/Zend_Db/Adapter/Zend_Db_Adapter_Pdo_Pgsql.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Adapter/Zend_Db_Adapter_Pdo_Pgsql.html?referer=');">query</a> obiektu Zend_Db_Adapter_*, fajnie:). Naszą klasę keszującą trzeba odziedziczyć po tej klasie. Oczywiście można to zrobić na wiele innych sposobów, i bardzo ładnie wpasować w to wzorzec projektowy dekorator, ale ja nie miałem czasu. Modyfikujemy metodę query, tak by zapamiętywała swoje wyniki i trzymała w keszu. U mnie działa coś takiego (wstydził bym się tego kodu, gdyby nie to że robi coś na prawdę przydatnego):
<pre>
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    $sql = $sql . ""; // a sprobuj no bez tego
    $hash = $this->_calculateHash($sql,$bind);
    // czy uda nam sie pobrac z kesza
    if($this->_isCacheEnabled) {
      if($this->_allowCache($sql,$bind)) {
        if(isset($this->_cachedResults[$hash])) {
          return $this->_cachedResults[$hash];  
        } 
      }
    }
    $res = parent::query($sql, $bind);
    // dopisanie do kesza
    if($this->_isCacheEnabled) {
      $this->_cachedResults[$hash] = $res;
    }
    return $res;
  }
  // pozwalam na keszowanie tylko zapytan rozpoczynajacych sie od select
  private function _allowCache($sql,$bind) {
    return preg_match("/^select/i",$sql));
  }
  private function _calculateHash($sql,$bind) {
    return md5($sql.var_export($bind,true));
  }
  [...]
}
</pre>
Ten kod omal nie zadziała. Jednak trzeba zrobić jeszcze parę myków. Przeciążyć konstruktor naszego adaptera.
<pre>
  public function __construct($config) {
    parent::__construct($config);
    $this->getConnection()->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('JakubiakDbStatement', array($this)));
  }
</pre>
I dopisać taką oto koszmarną klasę:
<pre>
class JakubiakDbStatement extends PDOStatement {
  $_cache = null;
  public function fetchAll($how, $class_name=null, $ctor_args=null) {
    if(!empty($this->_cache)) {
      return $this->_cache;
    }
    $this->_cache = parent::fetchAll($how);
    return $this->_cache;
  }
}
</pre>
Ten kod, ma prawo nie działać i zawiera błędy. Jednak u mnie działa. I to dobrze działa. Zamiast 500 zapytań do bazy mam ich 50. Serwer jest mi za to wdzięczny, nie trzeba już oliwić wentylatorka od chłodzenia;). 
PS. do testów użyłem starej wersji Zend_Framework &#8211; 0.9.1.]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/06/zend-framework-i-keszowanie-zapyta-do.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Hibernate i logowanie zapytań SQL wraz w wartościami parametrów</title>
		<link>http://www.jakubiak.eu/2007/06/hibernate-i-logowanie-zapyta-sql-wraz-w.html</link>
		<comments>http://www.jakubiak.eu/2007/06/hibernate-i-logowanie-zapyta-sql-wraz-w.html#comments</comments>
		<pubDate>Fri, 01 Jun 2007 18:17:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[JPA]]></category>
		<category><![CDATA[ORM]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/06/hibernate-i-logowanie-zapyta-sql-wraz-w.html</guid>
		<description><![CDATA[Witam. Hibernate potrafi logować zapytania SQL. Można to skonfigurować na kilka sposobów. Ja wybrałem konfiguracje z log4j: # logowanie zapytań SQL log4j.logger.org.hibernate.SQL=DEBUG, R log4j.additivity.org.hibernate.SQL=false # logowanie wartości wstawianych do SQL log4j.logger.org.hibernate.type=DEBUG Pozdrawiam!]]></description>
			<content:encoded><![CDATA[Witam.

Hibernate potrafi logować zapytania SQL. Można to skonfigurować na kilka sposobów. Ja wybrałem konfiguracje z log4j:
<pre>
# logowanie zapytań SQL
log4j.logger.org.hibernate.SQL=DEBUG, R
log4j.additivity.org.hibernate.SQL=false
# logowanie wartości wstawianych do SQL
log4j.logger.org.hibernate.type=DEBUG
</pre>
Pozdrawiam!]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/06/hibernate-i-logowanie-zapyta-sql-wraz-w.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Zend Framework, zachciało mi się HQL&#8217;a</title>
		<link>http://www.jakubiak.eu/2007/04/zend-framework-zachciao-mi-si-hqla.html</link>
		<comments>http://www.jakubiak.eu/2007/04/zend-framework-zachciao-mi-si-hqla.html#comments</comments>
		<pubDate>Sat, 21 Apr 2007 23:28:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[ORM]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/04/zend-framework-zachciao-mi-si-hqla.html</guid>
		<description><![CDATA[Zend Framework &#8211; framework, o którym ostatnio sporo pisałem. HQL język używany w Hibernate, będący połączeniem ciemności świata relacji i światłości obiektów. HQL było inspiracją dla JPQL , dzięki czemu stanie się standardem przemysłowym. HQL jest fajny, bo można pisać na przykład tak: from Movie m where m.media.user.name='Antek'; zamiast dłubać w SQL: select m.* from [...]]]></description>
			<content:encoded><![CDATA[<a href="http://framework.zend.com/" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/?referer=');">Zend Framework</a> &#8211; framework, o którym ostatnio sporo pisałem. <a href="http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html" onclick="pageTracker._trackPageview('/outgoing/www.hibernate.org/hib_docs/reference/en/html/queryhql.html?referer=');">HQL</a> język używany w Hibernate, będący połączeniem ciemności świata relacji i światłości obiektów. HQL było inspiracją dla <a href="http://jlaskowski.blogspot.com/2007/04/java-persistence-zakoczenie-rozdziau-4.html" onclick="pageTracker._trackPageview('/outgoing/jlaskowski.blogspot.com/2007/04/java-persistence-zakoczenie-rozdziau-4.html?referer=');">JPQL </a>, dzięki czemu stanie się standardem przemysłowym. 
HQL jest fajny, bo można pisać na przykład tak:
<pre>
from Movie m where m.media.user.name='Antek';
</pre>
zamiast dłubać w SQL:
<pre>
select m.* from movie v 
join media m on m.mediaid=v.mediaid 
join user u on u.userid=m.userid 
where user.name='Antek';
</pre>
Niestety, Zend Framework nie posiada czegoś takiego i nie zanosi się na to. Ja, jednak klepnąłem świniaka, dzięki któremu mogę pisać podobnie. Zmodyfikowałem klasę: <a href="http://www.jakubiak.eu/2007/04/zend-framework-i-postgresql-aka-orm.html">Zend_Db_Table ^ JakubiakDbTable</a>. Nowy kod to:
<pre>
protected function _fetch($where = null, $order = null, 
$count = null, $offset = null) {
  $select = $this->_db->select();
  $select->from($this->_name, $this->_cols);
  $where = (array) $where;
  foreach ($where as $key => $val) {
    if (is_int($key)) {
      $select->where($val);
    } else {
      // drobna modyfikacja rodzica
      $key = $this->refJoin($select,$key);
      $select->where($key, $val);
    }
  }
  // dalej tak jak u rodzica[...]
}
public function refJoin($select, $where) {
  foreach($this->_referenceMap as $key => $ref) {
    $regExp = "#^$key\.#";
    if (preg_match($regExp,$where)) {
      $refDao = new $ref['refTableClass'];
      $on = "";
      foreach($ref['columns'] as $i => $colName){
        if($i>0) $on .= ' and ';
        $on .= ' ';
        $on .= $this->getTableName() . '.' . $colName . '=';
        $on .= $refDao->getTableName() . '.' .$ref['refColumns'][$i] . ' ';
      }
      $select->join($refDao->getTableName(), $on,array());
      $where = preg_replace($regExp,"",$where);
      return $refDao->refJoin($select,$where); 
    }
  }
  return $this->getTableName().'.'.$where;
}
public function getTableName() {
  return $this->_name;
}
</pre>
Kod powyżej działa, ale jest zły. Nie używaj go. Ja mogę go używać tak:
<pre>
$movieTable = new MovieTable();
$movies = $movieTable->fetchAll(array("media.user.name=?"=>$_REQUEST['username']));
</pre>
Czyli, prawie, prawie jak HQL.]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/04/zend-framework-zachciao-mi-si-hqla.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Zend Framework i PostgreSQL aka ORM</title>
		<link>http://www.jakubiak.eu/2007/04/zend-framework-i-postgresql-aka-orm.html</link>
		<comments>http://www.jakubiak.eu/2007/04/zend-framework-i-postgresql-aka-orm.html#comments</comments>
		<pubDate>Sat, 21 Apr 2007 09:56:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[ORM]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/04/zend-framework-i-postgresql-aka-orm.html</guid>
		<description><![CDATA[Proponuje kilka moich poprawek do Zend Framework 0.9.2. Dzięki temu praca z tą biblioteką jest jeszcze przyjemniejsza. Wersja 0.9.2 ma parę niedociągnięć, jednak można je poprawić poprzez dziedziczenie. Zacznę od klasy Zend_Db_Adapter_Pdo_Pgsql PostgreSQL. Pisałem już o niej. Proponuje następujące ulepszenia: class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{ public function query($sql, $bind = array()){ // poprawienie błędu dla typu [...]]]></description>
			<content:encoded><![CDATA[Proponuje kilka moich poprawek do <a href="http://framework.zend.com/" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/?referer=');">Zend Framework 0.9.2</a>. Dzięki temu praca z tą biblioteką jest jeszcze przyjemniejsza. Wersja 0.9.2 ma parę niedociągnięć, jednak można je poprawić poprzez dziedziczenie.

Zacznę od klasy <a href="http://framework.zend.com/apidoc/core/Zend_Db/Adapter/Zend_Db_Adapter_Pdo_Pgsql.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Adapter/Zend_Db_Adapter_Pdo_Pgsql.html?referer=');">Zend_Db_Adapter_Pdo_Pgsql</a>  <a href="http://www.postgresql.org/" onclick="pageTracker._trackPageview('/outgoing/www.postgresql.org/?referer=');">PostgreSQL</a>. <a href="http://www.jakubiak.eu/labels/orm.html">Pisałem już o niej</a>. Proponuje następujące ulepszenia:
<pre>class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    // poprawienie błędu dla typu Boolean
    if(is_array($bind)){
      foreach($bind as $k => $v) {
        if(is_bool($v)) {
          $bind[$k] = $v ? 't' : 'f';
        }
      }
    }
    return parent::query($sql, $bind);
  }
  public function lastInsertId($tableName = null) {
    // Jeżeli sekwencja nazywasz inaczej niż domyślnie, to prawdopodobnie trzeba
    // będzie nadpisać też tą funkcję, u mnie wygląda ona tak 
    // sekwencje dla kluczy głównych nazywam {nazwa_tabelki}_seq
    if (!$tableName) {
      throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
    }
    $this->_connect();
    $sequenceName = "{$tableName}_seq";
    $sequenceName = preg_replace('/_+/','_',$sequenceName);
    return $this->_connection->lastInsertId($sequenceName);
  }
</pre>

Teraz zabawię się z klasą która reprezentuje tabelę <a href="http://framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table.html?referer=');">Zend_Db_Table</a>. Jest ona podwaliną <a href="http://www.jakubiak.eu/labels/orm.html">ORM</a>, dlatego mi się podoba:
<pre>
class JakubiakDbTable extends Zend_Db_Table {
  // chcę, aby wszystkie wiersze w tabelkach były reprezentowane przez
  // obiekty tej klasy
  protected $_rowClass = 'JakubiakDbTableRow';
  // następnie poprawiam błąd - lastInsertedId
  public function insert(array $data)  {
    $this->_db->insert($this->_name, $data);
    return $this->_db->lastInsertId(empty($this->_seqName)
      ? $this->_name : $this->_seqName);
  }
  // przydatna funkcja do zliczania wierszy w tabeli
  public function count(array $where = array()) {
    $select = $this->_db->select();
    $select->from($this->_name, array('count'=>'count(*)'));
    $where = (array) $where;
    foreach ($where as $key => $val) {
      if (is_int($key)) {
        $select->where($val);
      } else {
        $select->where($key, $val);
      }
    }
    $select->limit(1, 0);
    $stmt = $this->_db->query($select);
    $data = $stmt->fetch();
    return empty($data['count']) ? 0 : intval($data['count']);
  }
  // pobieranie nowej encji
  public function fetchNew() {
    $newRow = parent::fetchNew();
    $newRow->loadDefaults();
    return $newRow;
  }
  // to się jeszcze przyda, aczkolwiek wydaje mi się, że ta funkcja trafi prędzej czy 
  // później do core
  public function getReferenceMap($key){
    if(empty($this->_referenceMap[$key])){
      return null;
    }
    return $this->_referenceMap[$key];     
  }
}
</pre>
I jeszcze klasa która reprezentuje encje <a href="http://framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table_Row.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table_Row.html?referer=');">Zend_Db_Table_Row</a> &#8211; lub jak kto woli &#8211; wiersz w tabeli:
<pre>
class JakubiakDbTableRow extends Zend_Db_Table_Row {
  // czy jesteśmy nową encją?
  private $_isNew = false;
  // wczytanie domyślnych wartości, na podstawie metadanych zapisanych w tabeli
  public function loadDefaults() {
    $db = $this->_getTable()->getAdapter();    
    $info = $this->_getTable()->info();
    foreach($info['metadata'] as $col => $meta){
      if(empty($meta['DEFAULT'])) continue;
      $default = $meta['DEFAULT'];
      $one = $db->query("select $default as def")->fetch();
      $this->_data[$col] = $one['def'];
    }
    $this->_isNew = true;
  }
  // zapisywanie rekordu do bazy, kod prawie taki sam jak w klasie nadrzędnej,
  // ale wykorzystuje prywatną zmienną isNew
  public function save() {
    $keys = $this->_getPrimaryKey();
    $values = array_filter($keys);
    if ($this->_isNew) {
      $this->_insert();
      $result = $this->_getTable()->insert($this->_data);
      if (is_numeric($result)) {
        $this->_data[key($keys)] = $result;
        $this->_refresh();
      }
    } else {
      $where = $this->_getWhereQuery(false);
      $this->_update();
      $depTables = $this->_getTable()->getDependentTables();
      if (!empty($depTables)) {
        $db = $this->_getTable()->getAdapter();
        $pkNew = $this->_getPrimaryKey(true);
        $pkOld = $this->_getPrimaryKey(false);
        $thisClass = get_class($this);
        foreach ($depTables as $tableClass) {
          Zend_Loader::loadClass($tableClass);
          $t = new $tableClass(array('db' => $db));
          $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
        }
      }
      $result = $this->_getTable()->update($this->_data, $where);
      if (is_int($result)) {
        // update worked, refresh with data from the table
        $this->_refresh();
      }
    }
    return $result;
  }
  // pobieranie many to one w bardziej intuicyjny sposób
  public function __get($key) {
    $r = $this->_getTable()->getReferenceMap($key);
    if (!empty($r)) {
      // tu można dopisać loader dla klasy 
      return $this->findParentRow($r['refTableClass'],$key);
    }
    return parent::__get($key);
  }
  // domyślnie setter zabrania zmieniać wartości klucza głównego, 
  // ale czasami chcę to robić, kod prawie taki sam jak rodzica
  public function __set($key, $value) {
    if (!$this->_isNew &#038;&#038; in_array($key, $this->_primary)) {
      require_once 'Zend/Db/Table/Row/Exception.php';
      throw new Zend_Db_Table_Row_Exception("Changing the primary key value(s) is not allowed");
    }
    if (!array_key_exists($key, $this->_data)) {
      require_once 'Zend/Db/Table/Row/Exception.php';
      throw new Zend_Db_Table_Row_Exception("Specified column \"$key\" is not in the row");
    }
    $this->_data[$key] = $value;
  }
</pre>
Po tej zabawie, mogę używać bibliotek na przykład tak:
<pre>
$fileTable = new FileTable();
$fileRow = $fileTable->fetchNew($_FILES[$filename]);
$fileRow->save();
$mediaTable = new MediaTable();
$mediaRow = $mediaTable->fetchNew();
$mediaRow->mediatitle = $request->getParam('mediatitle');
$mediaRow->mediadescription = $request->getParam('mediadescription');
$mediaRow->fileid = $fileRow->fileid;
$mediaRow->userid = UserTable::getFromAuth()->userid;
$mediaRow->save();
$photoTable = new PhotoTable();
$photoRow = $photoTable->fetchNew();    
$photoRow->mediaid = $mediaRow->mediaid;
$photoRow->photowidth = $width;
$photoRow->photoheight = $height;
$photoRow->save();
echo $photoRow->media->file->fileid;
</pre>
Miodzio? Nie chcę wracać do czasów, gdy nie znałem ORM. Czuję się prawie jak w <a href="http://jlaskowski.blogspot.com/2007/01/java-persistence-api-jpa-chapter-1.html" onclick="pageTracker._trackPageview('/outgoing/jlaskowski.blogspot.com/2007/01/java-persistence-api-jpa-chapter-1.html?referer=');">JPA</a>.
Acha, nie zapomnij o przeczytaniu <a href="http://framework.zend.com/manual/en/zend.db.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/manual/en/zend.db.html?referer=');">dokumentacji Zenda</a> bo jest bardzo dobra.]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/04/zend-framework-i-postgresql-aka-orm.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Zend Framework &#8211; Zend_Db_Table</title>
		<link>http://www.jakubiak.eu/2007/04/zend-framework-zenddbtable.html</link>
		<comments>http://www.jakubiak.eu/2007/04/zend-framework-zenddbtable.html#comments</comments>
		<pubDate>Mon, 09 Apr 2007 17:58:00 +0000</pubDate>
		<dc:creator>Antoni Jakubiak</dc:creator>
				<category><![CDATA[ORM]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Zend Framework]]></category>

		<guid isPermaLink="false">http://www.jakubiak.info/2007/04/zend-framework-zenddbtable.html</guid>
		<description><![CDATA[Krytykowałem już Zend Framework za zmienność. Dziś go pochwalę. Bardzo podoba mi się Zend_Db_Table. Jest to proste, ale prawdziwe OR/M. W dodatku, żeby go używać nie trzeba się sporo napisać i nie trzeba się wiele uczyć. Aktualizacja: 2007-04-10. Cholera! Jest sobie funkcja Zend_Db_Table_Abstract::insert() &#8211; ale nie działą dla PDO_PGSQL. Kod źródłowy, sprawia wrażenie nigdy nie [...]]]></description>
			<content:encoded><![CDATA[Krytykowałem już <a href="http://framework.zend.com/manual/en/zend.db.table.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/manual/en/zend.db.table.html?referer=');">Zend Framework</a> za zmienność. Dziś go pochwalę. Bardzo podoba mi się <a href="http://framework.zend.com/manual/en/zend.db.table.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/manual/en/zend.db.table.html?referer=');">Zend_Db_Table</a>. Jest to proste, ale prawdziwe <a href="http://en.wikipedia.org/wiki/Object-relational_mapping" onclick="pageTracker._trackPageview('/outgoing/en.wikipedia.org/wiki/Object-relational_mapping?referer=');">OR/M</a>. W dodatku, żeby go używać nie trzeba się sporo napisać i nie trzeba się wiele <a href="http://framework.zend.com/manual/en/zend.db.table.relationships.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/manual/en/zend.db.table.relationships.html?referer=');">uczyć</a>.
Aktualizacja: 2007-04-10.
Cholera! Jest sobie funkcja <a href="http://framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table_Abstract.html#methodinsert" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Table/Zend_Db_Table_Abstract.html_methodinsert?referer=');">Zend_Db_Table_Abstract::insert()</a> &#8211; ale nie działą dla <a href="http://framework.zend.com/apidoc/core/Zend_Db/Adapter/_Zend---Db---Adapter---Pdo---Pgsql.php.html" onclick="pageTracker._trackPageview('/outgoing/framework.zend.com/apidoc/core/Zend_Db/Adapter/_Zend---Db---Adapter---Pdo---Pgsql.php.html?referer=');">PDO_PGSQL</a>. Kod źródłowy, sprawia wrażenie nigdy nie testowanego. W dodatku jest nie poprawny merytorycznie. Aż zacytuje:<pre>
    public function insert(array $data)
    {
        $this->_db->insert($this->_name, $data);
        return $this->_db->lastInsertId();
    }
    public function lastInsertId($tableName = null, $primaryKey = 'id')
    {
        if (!$tableName) {
            throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
        }
</pre>
Zend Framework jest fajny bo nowatorski. Niestety, jest jeszcze w stadium mocno rozwojowym. Ten błąd na szczęście mogę poprawić w swojej aplikacji bez modyfikacji kodu źródłowego bibliotek Zend. Ale ile takich błędów jeszcze znajdę?]]></content:encoded>
			<wfw:commentRss>http://www.jakubiak.eu/2007/04/zend-framework-zenddbtable.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

