3 Tipy pro čitelnější web!

Na internetu lze nalézt opravdu spousty článků, blogů a všelijakých textů. Pokud jste stejně vášnivý čtenář/čtenářka jako já, pak právě vám se bude hodit pár mých triků, jak si jejich čtení trochu zpestřit a vylepšit.

[English version – 3 Tips for more readable Web!]

Tip 1 – Gutenberg by z nás měl radost

Představte si, že čtete článek v prohlížeči, vidíte jenom čitelné nadpisy, text vyrovnaný do odstavců, pěkně hezky černé na bílem, jako když listujete knihou… Že je to pohádka? Pokud dnes vstoupím na nějakou stránku (či portál), vyskakují na mě reklamy, u textu na stránce je použito hned několik řezů písma, různých barev a velikostí (vím, trochu přeháním :) , takhle přeci vypadá ten web, nebo ne? Máte pravdu, takhle vypadá dnešní web. Přitom ale velmi jednoduchým a funkčním řešením je použití bookmarkletu Readability. (pokud nevíte/nevěděli jste jako já, {ano stydím se…} co je to vlastně bookmarklet, pak je to jednoduchá JavaScriptová aplikace v podobě odkazu, který si můžete umístit třeba do horní lišty se záložkami.). „Nainstalujte“ si tento bookmarklet, podobným způsobem, jaký je naznačen na následujícím obrázku:

Instalace Readability bookmarkletu...
Instalace Readability bookmarkletu...

A teď si ukážeme, jak tento bookmarklet použít. Například na stránkách serveru ihned.cz. Vybereme si libovolný článek a podíváme se jak stránka vypadá po standartním načtení prohlížečem a jak po stisknutí bookmarkletu Redability. Výsledky jsou myslím patrné na první pohled. Víte už tedy, jak tento bookmarklet použít. No není to paráda mít zase po dlouhé době článek v takové podobě, jako když ho čtete přímo z knihy?

Originální verze článku (bez reklam :)
Originální verze článku...
Verze článku po stisknutí Redability Bookmarkletu...
Verze článku po stisknutí Redability Bookmarkletu...

Tip 2 – Synchronizujeme a čteme si cestou do práce

Jako zanícený čtenář jsem zkoumal, jak vyplnit to prázdné časové místo kdy jsem na cestě do práce. V mém případě to jsou dvě hodiny času. Nejraději bych toto místo vyplnil čtením (to právě také dělám). Jenže si nechci číst jenom knížky, ale chci konzumovat i informace z internetu, ale nějakou příjemnější formou, než v prohlížeči na mobilním telefonu. Dnešní mobilní prohlížeče zvládají to co jejich „tatínkové“ na desktopech, takže tam opět máme problém s tou haldou nepřehledného balastu, který u článků nechci mít. Jedinou záchranou pro mě byla instalace doplňku Read It Later (pokud chcete využít plnou sílu toho doplňku a synchronizovat si čtené články s více zařízeními jako je iPad, iPhone, HTC, BlackBerry, na platformách Windows Mobile, Andriod… a dalšími, je nutné se registrovat na stránkách Read It Later List, registrace je zdarma. Jaké výhody vám to přinese popíšu dále v článku).

Hlavní funkce doplňku Read It Later

  1. Skladuje odkazy na články (stránky) ve frontě. Tím odložíte čtení na později. Odkazy ve frontě fungují podobně jako záložky (bookmarks), ale tady je to opravdu ve významu fronty. Prostě nějaký článek se vám líbí, ale nemáte na něj teď čas, rádi byste si ho přečetli cestou domů z práce, například v tramvaji či autobuse. Dáte si ho do fronty a až si ho přečtete, tak se z fronty sám smaže. Jednoduché a praktické. Fronta by se neměla jenom kupit, měla by se i vyprazdňovat. Vyprazdňuje se tím, že budete nastřádané články číst.
  2. Umožňuje offline čtení – pokud si v nastavení doplňku (volba Settings -> Offline zaškrtněte „Automatically make all saved pages avilable offline“ a „Download web view„), pak se po přidání odkazu do fronty provede stažení dané stránky a uložení do cache prohlížeče (ve Firefoxu je tomu přímo na disku vyhrazena konkrétní složka, v daném profilu uživatele). V případě, že nebudete připojeni k internetu, si tak v klidu můžete offline prohlédnout články, které máte ve frontě na čtení.
  3. Odkazy ve frontě ukládá na server a umožňuje je synchronizovat na jiná zařízení (to taky přesně dělám cestou do práce – momentálně na zařízení HTC Touch Pro 2 – ukázky obrazovek jsou dále v textu)

Jak to „běží“ v prohlížeči

Po instalaci zmíněného doplňku se v prohlížeči, v místě kam zadáváte adresu (URL), objeví ikonka, pomocí níž lze přidat právě otevřenou stránku do fronty na pozdější čtení. Jak vypadá moje fronta:

Firefox - seznam nepřečtených článků "na potom"...
Firefox - seznam nepřečtených článků "na potom"...

Čtení článků z fronty Read It Later v prohlížeči

Následující sada obrázků ukazuje použití doplňku Read It Later v praxi. Myslím, že tohle potěší každého čtenáře, je vidět jenom správně zformátovaný a navíc pěkně čitelný text na celé stránce. No není tohle lepší než použití „obyčejného“ AdBlock filteru? Navíc pokud se podíváte pozorně na pravý horní roh článku (ano to ozubené kolečko na třetím obrázku), po kliknutí na něj se objeví možnost zvolit například velikost písma nebo volba černé-na-bílém nebo bílé-na-černém (day/night). Opravdu velice šikovné.

Ikonka "T", která zobrazí článek z fronty
Ikonka "T", která zobrazí článek z fronty
Článek zobrazený Firefoxem
Originální článek zobrazený Firefoxem
Článek zobrazený po kliknutí na ikonku "T" z fronty Read It Later
A článek zobrazený po kliknutí na ikonku "T" z fronty Read It Later

Čtení článků na HTC Touch Pro2

Na stránkách Read It Later najdete odkazy na aplikace pro jiná zařízení (či jiné prohlížeče), které se umí přihlásit k vašemu účtu Read It Later a stáhnout si offline verze stránek. Pak si můžete tyto stránky jednoduše číst ve vlaku, metru či autobuse cestou do práce. (čtení za jízdy v automobilu nedoporučuji :) A tady je několik obrázků z HTC Touch Pro2 zařízení, jak může vypadat takové offline čtení článků:

Hlavní obrazovka programu LateReader
Hlavní obrazovka programu LateReader
LateReader - nastavení synchronizace
LateReader - nastavení synchronizace
LaterReader - seznam článků
LaterReader - seznam článků
LaterReader - volba méně či více textu
LaterReader - volba méně či více textu
LaterReader - čtení článku (1)
LaterReader - čtení článku (1)
LaterReader - čtení článku (2)
LaterReader - čtení článku (2)

Instapaper na to jde trochu jinak

Na podobném, pro někoho možná lepším principu pracuje Instapaper. V zásadě vám pak na libovolném prohlížeči stačí mít v horní liště záložek dvě ikonky – jeden bookmarklet, který zajistí uložení do fronty odkazů a druhý odkaz na stránky Instapaper. Pro někoho může být klíčové, že se na těchto stránkách nemusíte registrovat, pokud chcete využít variantu „vše ukládej do cookie“. Pokud budete chtít synchronizovat s jiným zařízením, je nutné se registrovat, opět zdarma. Instapaper jsem přestal používat, alespoň vám ukážu, jak vypadala moje fronta odkazů na „pozdější čtení“:

Instapaper - fronta odkazů
Instapaper - fronta odkazů

Tip 3 – Odstraňujeme reklamu

Posledním a nejméně praktickým (nejméně z pohledu čtenáři lahodícím doplňkům zmíněným v předchozích částech), za to úplně nejjednodušším způsobem je možnost odstranit z článků otravnou reklamu. Tohle si jistě nikdo z těch, kdo reklamu platí nepřeje, nicméně na čtenáře se dnes ze stránek valí tolik reklamního balastu, že je to opravdu až nepříjemné. Volba, jak s reklamou naložit by podle mě měla zůstat na čtenáři.

Možností jak reklamu odstranit je několik a jsou závislé na volbě prohlížeče. Firefox je na tom ohledně odstraňování reklamy asi nejlépe a v tomto případě jen „mlátím prázdnou slámu“, když upozorním na doplňky AdBlock Plus a FlashBlock. Oba dva jsou v žebříčku popularity žroutů reklam na prvním místě. Podobné doplňky najdete dnes už jistě i pro prohlížeč Google Chrome. Ale co když používáte prohlížečů více, nebo chcete reklamu filtrovat prakticky pro všechna HTTP spojení (tedy přesně ta, co si načítají způsob připojení k síti z hodnot uložených v nastavení IE operačního systému Microsoft Windows). V takovém případě je mnohem lepší použít program Ad Muncher od Australských programátorů. Program funguje jako HTTP proxy a filtruje tak veškerý HTTP provoz na síti. Program není zadarmo, ale těch $29.95 australských dolarů přeci není moc, v přepočtu to vychází na něco kolem 500,-Kč, licence je vázána na jeden počítač, ale mám ji už 5 let a v té ceně mám aktualizace po celou dobu zdarma (tohle se vám u jiných výrobců programů jen tak nepoštěstí). Program funguje tak, že filtruje celkově obsah doručené stránky před tím, než se dostane na vykreslení do prohlížeče. Tedy přímo skenuje dotahované stránky a modifikuje tak výsledný HTML kód (včetně JavaScriptu). Tímto dokáže pěkně schroupat všechnu reklamu, případně i všelijaké ostatní nepříjemné chování některých stránek (vyskakovací okna a podobně).

Jak si poradí se stránkou portálu inhed.cz jednotlivý žrouti reklam:

Úvodní stránka portálu ihned.cz s vypnutýmy "požírači" reklam
Úvodní stránka portálu ihned.cz s vypnutými "požírači" reklam
Úvodní stránka portálu ihned.cz se zapnutým doplňkem AdBlock
Úvodní stránka portálu ihned.cz se zapnutým doplňkem AdBlock
Úvodní stránka portálu ihned.cz se zapnutým programem Ad Muncher
Úvodní stránka portálu ihned.cz se zapnutým programem Ad Muncher

Výsledky ukazují, že použití doplňku AdBlock Plus ve Firefox je stejně dobré jako použití programu Ad Muncher, nicméně přicházíte o výhodu filtrování veškerého HTTP provozu (reklama může být schovaná opravdu všude). Snad se vám tyto tipy budou líbit.

Zdroje:

  1. Scott Hanselman – Two Must-Have Tools for a More Readable Web.
  2. Ad Muncher
  3. Read It Later

Post to Twitter

Rozdělení řetězce do více řádků pomocí dotazu nad tabulkou DUAL

Jednoduchým řešením jak rozdělit řetězec v Oracle do více řádků je využití možnosti kombinace dotazu nad „tabulkou“ DUAL a využití klauzule CONNECT BY. Vše si ukážeme na jednoduchém příkladu.

[English version – How to select multiple rows from single line using a query over DUAL table]

Představte si, že existuje následující řetězec:

Anna,Martin,Sebastián,Gabriela,Pavel,

Tento řetězec potřebujeme rozdělit tak, aby výsledkem byl seznam jmen, každé jméno v jednom řádku, takto:

Anna
Martin
Sebastián
Gabriela
Pavel

Jak toho dosáhnout (jednoduchým způsobem) ukazuje následující SQL dotaz:

SELECT substr( 'Anna,Martin,Sebastián,Gabriela,Pavel,',
         ( case when rownum = 1
           then 1
           else instr( 'Anna,Martin,Sebastián,Gabriela,Pavel,', ',', 1, rownum - 1 ) + 1 end ),

         instr( substr( 'Anna,Martin,Sebastián,Gabriela,Pavel,',
         		( case when rownum = 1
                  then 1
                  else instr( 'Anna,Martin,Sebastián,Gabriela,Pavel,', ',', 1, rownum - 1 ) + 1 end )
       		), ',' ) - 1

       ) as products
FROM dual
CONNECT BY LEVEL <= length( 'Anna,Martin,Sebastián,Gabriela,Pavel,' )
                  - length ( replace('Anna,Martin,Sebastián,Gabriela,Pavel,', ',') ) ;

Post to Twitter

Jak na zpracování XML s vícenásobnými uzly (XMLSequence v Oracle)

XML, které obsahuje vícenásobné uzly (tzv. multiple nodes) je nutné v Oracle databázi zpracovat funkcí XMLSequence. Ukážeme si její použití ve třech verzích. Nejprve s XML bez namespace, potom s nadefinovaným namespace a nakonec použití přímo v SQL výrazu. Co je to XML, případně namespace se dozvíte např. na W3Schools.

[English version of the article – How-To processing XML with multiple nodes (using XMLSequence in Oracle)]

Ukázka použití bez nadefinovaného namespace

Předpokladem je následující dokument, kde „multiple-node“ je tag ipAddress – vyskytuje se pod s sebou v jedné úrovni vícekrát. Tedy jedná se o pole IP adres s položkami value a mask.

<?xml version="1.0" encoding="UTF-8"?>
<ipAddressList>
	<ipAddress>
		<value>10.10.0.1</value>
		<mask>255.0.0.0</mask>
	</ipAddress>
	<ipAddress>
		<value>192.168.1.1</value>
		<mask>255.255.255.0</mask>
	</ipAddress>
</ipAddressList>

V další ukázce je použit jednoduchý datový typ RECORD, do kterého budeme ukládat hodnoty value a mask pomocí zápisu do pole využitím klauzule BULK COLLECT.

    TYPE rec_ip_address IS RECORD (
        ip_address_value VARCHAR2(15),
        ip_address_mask  VARCHAR2(15)
    );

A tady je zmíněný skript, který demonstruje použití XMLSequence.

DECLARE
    l_xml_source      XMLType;

    TYPE rec_ip_address IS RECORD (
        ip_address_value VARCHAR2(15),
        ip_address_mask  VARCHAR2(15)
    );

    TYPE type_ip_address IS TABLE OF rec_ip_address INDEX BY BINARY_INTEGER;
    l_ip_address_list type_ip_address;

BEGIN
    l_xml_source := XMLType('<ipAddressList>
<ipAddress>
  <value>10.10.0.1</value>
  <mask>255.0.0.0</mask>
</ipAddress>
<ipAddress>
  <value>192.168.1.1</value>
  <mask>255.255.255.0</mask>
</ipAddress>
</ipAddressList>');

    SELECT EXTRACTVALUE(VALUE(xml_list), '//value') AS ip_address_from
          ,EXTRACTVALUE(VALUE(xml_list), '//mask') AS ip_address_to
      BULK COLLECT
      INTO l_ip_address_list
      FROM TABLE(XMLSEQUENCE(EXTRACT(l_xml_source, 'ipAddressList/ipAddress'))) xml_list;

    IF (l_ip_address_list.COUNT > 0) THEN

        FOR i IN l_ip_address_list.FIRST..l_ip_address_list.LAST LOOP
            DBMS_OUTPUT.put_line('[' || i || '] = ' || l_ip_address_list(i).ip_address_value || '/' || l_ip_address_list(i).ip_address_mask);
        END LOOP;

    END IF;

END;
/

Výstupem skriptu je následující seznam IP adres:

SQL> DBMS Output (Session: [1] ADMIN@TEST_DB.WORLD at: 25.08.2010 23:30:33):
SQL> ----------------------------------------------------------------------
SQL> [1] = 10.10.0.1/255.0.0.0
SQL> [2] = 192.168.1.1/255.255.255.0

Finta je v tomto případě v použití FROM TABLE(XMLSEQUENCE(…)). Funkce XMLSEQUENCE totiž vrací VARRAY a nad ním je již možné použít příkaz TABLE(…).

Ukázka použití s nadefinovaným namespace

V tomto případě nadefinujeme například namespace „ip„:

xmlns:ip="http:/martin-mares.cz/sampleIpAddressList.xsd"

Upravíme testovací XML:

<?xml version="1.0" encoding="UTF-8"?>
<ip:ipAddressList xmlns:ip="http:/martin-mares.cz/sampleIpAddressList.xsd">
	<ip:ipAddress>
		<ip:value>10.10.0.1</ip:value>
		<ip:mask>255.0.0.0</ip:mask>
	</ip:ipAddress>
	<ip:ipAddress>
		<ip:value>192.168.1.1</ip:value>
		<ip:mask>255.255.255.0</ip:mask>
	</ip:ipAddress>
</ip:ipAddressList>

A výsledný upravený skript, který umí pracovat s namespace:

DECLARE
    l_xml_source      XMLType;
    l_namespace       VARCHAR2(2000) := 'xmlns:ip="http:/martin-mares.cz/sampleIpAddressList.xsd"';

    TYPE rec_ip_address IS RECORD (
        ip_address_value VARCHAR2(15),
        ip_address_mask  VARCHAR2(15)
    );

    TYPE type_ip_address IS TABLE OF rec_ip_address INDEX BY BINARY_INTEGER;
    l_ip_address_list type_ip_address;

BEGIN
    l_xml_source := XMLType('<ip:ipAddressList ' || l_namespace || '>
<ip:ipAddress>
  <ip:value>10.10.0.1</ip:value>
  <ip:mask>255.0.0.0</ip:mask>
</ip:ipAddress>
<ip:ipAddress>
  <ip:value>192.168.1.1</ip:value>
  <ip:mask>255.255.255.0</ip:mask>
</ip:ipAddress>
</ip:ipAddressList>');

    SELECT EXTRACTVALUE(VALUE(xml_list), '//ip:value', l_namespace) AS ip_address_from
          ,EXTRACTVALUE(VALUE(xml_list), '//ip:mask', l_namespace) AS ip_address_to
      BULK COLLECT
      INTO l_ip_address_list
      FROM TABLE(XMLSEQUENCE(EXTRACT(l_xml_source, 'ip:ipAddressList/ip:ipAddress', l_namespace))) xml_list;

    IF (l_ip_address_list.COUNT > 0) THEN

        FOR i IN l_ip_address_list.FIRST..l_ip_address_list.LAST LOOP
            DBMS_OUTPUT.put_line('[' || i || '] = ' || l_ip_address_list(i).ip_address_value || '/' || l_ip_address_list(i).ip_address_mask);
        END LOOP;

    END IF;

END;
/

Výstupem je opět seznam IP adres:

SQL> DBMS Output (Session: [1] ADMIN@ZIS_VT.WORLD at: 25.08.2010 23:48:34):
SQL> ----------------------------------------------------------------------
SQL> [1] = 10.10.0.1/255.0.0.0
SQL> [2] = 192.168.1.1/255.255.255.0

Ve výše zmíněném skriptu prosím věnujte pozornost místu „EXTRACTVALUE“ (místo value je nyní použito ip:value)! Je velmi důležité při zpracování funkcí EXTRACT nezapomenout na prefix ip:!, jinak příklad nebude fungovat. V ostatních případech může dojít k vyjímce

ORA-31013: Neplatný výraz XPATH

Ukázka použití v SQL výrazu

Nyní pouze spojíme dohromady to co jsme už použily v předchozích příkladech:

    SELECT EXTRACTVALUE(VALUE(xml_list), '//value') AS ip_address
          ,EXTRACTVALUE(VALUE(xml_list), '//mask') AS mask
      FROM TABLE(XMLSEQUENCE(EXTRACT(XMLType('<ipAddressList>
                                                <ipAddress>
                                                  <value>10.10.0.1</value>
                                                  <mask>255.0.0.0</mask>
                                                </ipAddress>
                                                <ipAddress>
                                                  <value>192.168.1.1</value>
                                                  <mask>255.255.255.0</mask>
                                                </ipAddress>
                                                </ipAddressList>'), 'ipAddressList/ipAddress'))) xml_list;

Výsledkem je seznam IP adres:

SQL> IP_ADDRESS      MASK
SQL> --------------- ---------------
SQL> 10.10.0.1       255.0.0.0
SQL> 192.168.1.1     255.255.255.0

UPDATE 19.10.2010: Přidání kapitoly demonstrující aktualizaci jednotlivých uzlů.

Aktualizace uzlů pomocí SQL funkce UpdateXML

Tento příklad ukazuje, jak změnit data v XMLType před tím, než je zpracujeme funkcí XMLSequence. U uzlů, které obsahují masku 255.255.255.0 změní hodnotu na 255.255.255.128 pomocí SQL funkce UpdateXML a pomocí správného výrazu XPath:

DECLARE
    l_xml_source      XMLType;
    l_namespace       VARCHAR2(2000) := 'xmlns:ip="http:/martin-mares.cz/sampleIpAddressList.xsd"';
    l_default_mask    VARCHAR2(2000) := '255.255.255.0';
    l_new_mask        VARCHAR2(2000) := '255.255.255.128';

    TYPE rec_ip_address IS RECORD (
        ip_address_value VARCHAR2(15),
        ip_address_mask  VARCHAR2(15)
    );

    TYPE type_ip_address IS TABLE OF rec_ip_address INDEX BY BINARY_INTEGER;
    l_ip_address_list type_ip_address;

BEGIN
    l_xml_source := XMLType('<ip:ipAddressList ' || l_namespace || '>
<ip:ipAddress>
  <ip:value>10.10.0.1</ip:value>
  <ip:mask>255.0.0.0</ip:mask>
</ip:ipAddress>
<ip:ipAddress>
  <ip:value>192.168.1.1</ip:value>
  <ip:mask>' || l_default_mask || '</ip:mask>
</ip:ipAddress>
<ip:ipAddress>
  <ip:value>192.168.1.2</ip:value>
  <ip:mask>' || l_default_mask || '</ip:mask>
</ip:ipAddress>
<ip:ipAddress>
  <ip:value>192.168.1.3</ip:value>
  <ip:mask>' || l_default_mask || '</ip:mask>
</ip:ipAddress>
</ip:ipAddressList>');

    SELECT UPDATEXML(l_xml_source, '/ip:ipAddressList/ip:ipAddress[ip:mask = ''' || l_default_mask || ''']/ip:mask/text()', l_new_mask, l_namespace)
    INTO l_xml_source
    FROM dual;

    SELECT EXTRACTVALUE(VALUE(xml_list), '//ip:value', l_namespace) AS ip_address_from
          ,EXTRACTVALUE(VALUE(xml_list), '//ip:mask', l_namespace) AS ip_address_to
      BULK COLLECT
      INTO l_ip_address_list
      FROM TABLE(XMLSEQUENCE(EXTRACT(l_xml_source, 'ip:ipAddressList/ip:ipAddress', l_namespace))) xml_list;

    IF (l_ip_address_list.COUNT > 0) THEN

        FOR i IN l_ip_address_list.FIRST..l_ip_address_list.LAST LOOP
            DBMS_OUTPUT.put_line('[' || i || '] = ' || l_ip_address_list(i).ip_address_value || '/' || l_ip_address_list(i).ip_address_mask);
        END LOOP;

    END IF;

END;
/

Výsledek:

[1] = 10.10.0.1/255.0.0.0
[2] = 192.168.1.1/255.255.255.128
[3] = 192.168.1.2/255.255.255.128
[4] = 192.168.1.3/255.255.255.128

Post to Twitter

Malý Velký Twitter úspěch :)

Scott Hanselman včera reagoval na můj tweet, tak je potřeba to trochu oslavit :) Nejlépe tím, že zmíním, kdo je Scott Hanselman a odkážu vás na další jeho zajímavé zdroje. Kdo je tedy Scott Hanselman? Nyní pracuje v Microsoftu na pozici „Principal Program Manager Lead in Server and Tools Online“. Je známý Blogger a Speaker. Je velkým popularizátorem  a propagátorem ASP.NET, nyní hlavně ASP.NET MVC.

[English version of the article – Little Big Twitter success :)]

Na jeho blogu najdete články související s Visual Studiem, C#, MVC a programováním obecně. Scott se také hodně zajímá o sociální sítě, jeho videa najdete například na populárním Channel9. V češtině jej najdete například jako autora knihy od ASP.NET 3.5 v jazycích C# a Visual Basic od Computer Press.

Další informace o Scottovi najdete zde:

  1. Profil na Linked IN
  2. Twitter
  3. Blog
  4. Scott na Facebooku
  5. Výkendové audio na HanselMinutes
  6. Videa na VIMEO

… a zmíněný tweet :)

Tweet to Scott

Post to Twitter

„Indiáni“ učí instalovat Oracle na Linuxu

Na Ekvádorském serveru (EOUG) se rozhodli udělat něco pro začátečníky, kteří se chtějí naučit pracovat s databází Oracle. Po třicet dnů, budou zveřejňovat články o instalaci a zprovoznění Oracle serveru na Redhat Linux 5.1 32-bit. Články jsou anglicky s mnoha screenshoty. Myslím, že pro někoho to může být velmi přínosné. Kéž by něco takového bylo u nás. Na českém Czech Oracle User Group (COUG) najdete poslední novinku z 1.1.2007 – velká škoda.

[English version of the article – „Indians“ learn to install Oracle on Linux]

První dva články „Ekvádorské série“ najdete na těchto adresách:

  1. Day 1: Installing Oracle Database 11g on Red Hat Linux 5.1 (32 bits)
  2. Day 2: Creating an Oracle Database 11g

Zdroje:

  1. Twitter – @paolapullas
  2. Czech Oracle User Group –http://www.coug.cz

Post to Twitter

V bitvě Oracle vs. OpenSolaris bude pravděpodobně vítězem Linux

Během dne sleduji diskusi na Twitteru (kanál #oracle) kvůli ukončení podpory vývoje projektu OpenSolaris. Jak se zdá, tak vždy je něco špatné na něco dobré. V tomto případě to nahrává do karet Linuxu, který tím pravděpodobně získá další klienty. Začínají se totiž objevovat odkazy na howto typu „Jak migrovat z OpenSolaris na Linux“, jedním takovým je Migration Kit for Solaris OS to Linux (via @infolinux). Fandím v tomto případě Linuxu! :)

[English version of the article – In the battle Oracle vs. OpenSolaris will probably be the winner Linux]

Post to Twitter

Volání Webové služby (Webservice) v PL/SQL pomocí UTL_HTTP

Dnes si ukážeme, jak získat data z Webové služby (WebService) přímo v PL/SQL, jak jsem sliboval v předchozím článku.

[English version – Calling Web Services in PL/SQL using UTL_HTTP package]

K tomuto účelu potřebujete:

  1. Nástroj pro vývoj/testování webových služeb. Použijeme balíček SoapUI
  2. Prostředí SQL*Plus. Pro naše testy bude plně vyhovovat.

Vytvoření testovací webové služby

Vytvoříme testovací webovou službu popsanou následujícím WSDL dokumentem.

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="HelloService"
   targetNamespace="http://www.ecerami.com/wsdl/HelloService.wsdl"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.ecerami.com/wsdl/HelloService.wsdl"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">

   <message name="SayHelloRequest">
      <part name="firstName" type="xsd:string"/>
   </message>
   <message name="SayHelloResponse">
      <part name="greeting" type="xsd:string"/>
   </message>

   <portType name="Hello_PortType">
      <operation name="sayHello">
         <input message="tns:SayHelloRequest"/>
         <output message="tns:SayHelloResponse"/>
      </operation>
   </portType>

   <binding name="Hello_Binding" type="tns:Hello_PortType">
      <soap:binding style="rpc"
         transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="sayHello">
         <soap:operation soapAction="sayHello"/>
         <input>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </input>
         <output>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </output>
      </operation>
   </binding>

   <service name="Hello_Service">
      <documentation>WSDL File for HelloService</documentation>
      <port binding="tns:Hello_Binding" name="Hello_Port">
         <soap:address
            location="http://localhost:8088/mockHello_Binding"/>
      </port>
   </service>
</definitions>

Nainstalujeme a spustíme program SoapUI. Nový projekt v SoapUI vytvoříme přes položku v menu File > New Soap UI Project.

Vytvoření nového projektu SoapUI
Vytvoření nového projektu SoapUI

Vygenerujeme Mock službu (Mock = falešný – lepší překlad jsem nanašel) – Obrázek ukazuje, že WSDL dokument popisuje službu s jednou metodou „sayHello“.

Generování Mock service
Generování Mock service

Vyplníme název Mock service – ponechávám implicitní hodnotu, kterou nabídne SoapUI.

Název Mock service
Název Mock service

Spustíme testovací službu (pokud jste ponechali implicitní nastavení, služba se namapuje na port 8088).

Spuštění testovací webové služby
Spuštění testovací webové služby

Otevřeme a zobrazíme WSDL dokument v okně prohlížeče.

Otevření WSDL v okně prohlížeče
Otevření WSDL v okně prohlížeče

Takto vypadá okno prohlížeče s výsledným WSDL, dokumentem který vrací testovací Webová služba „HelloService“.

IE zobrazuje WSDL dokument testovací Webové služby
IE zobrazuje WSDL dokument testovací Webové služby

Spustíme dotaz nad metodu sayHello. Měly byste vidět následující.

Spuštění dotazu na sayHello
Spuštění dotazu na sayHello

Obsah dotazu na server (SOAP Request):

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <firstName xsi:type="xsd:string">Martin Mareš</firstName>
      </urn:sayHello>
   </soapenv:Body>
</soapenv:Envelope>

Obsah odpovědi serveru (SOAP Response):

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHelloResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <greeting xsi:type="xsd:string">gero et</greeting>
      </urn:sayHelloResponse>
   </soapenv:Body>
</soapenv:Envelope>

Konzumace webové služby pomocí PL/SQL balíku UTL_HTTP

Necháme naši testovací Mock service běžet v okně SoapUI a pustíme se do PL/SQL kódu SQL*Plus. Předpokladem pro správné fungování následujícího PL/SQL kódu je, že se dostanete z vaší vývojové/testovací databáze Oracle na počítač, kde máte spuštěnu testovací Webovou službu na portu 8088.

Při spuštění kódu konzumujícího webovou službu se může objevit následující chyba:

ORA-29273: selhal požadavek HTTP
ORA-06512: na "SYS.UTL_HTTP", line 1130
ORA-24247: seznam řízení přístupu (ACL) nepovolil přístup k síti
ORA-06512: na line 22

V případě, že se tak stane, je potřeba nastavit oprávnění pro volání connect a resolve z Oracle databáze. Zajistíme to následujícím skriptem (v mém případě spouštěný pro uživatele SCOTT). U skriptu prosím věnujte pozornost položce principal (ta by měla obsahovat jméno uživatele, pro nějž oprávnění přiřazujete), dále položce privilege (název privilegia „connect“ a „resolve“) a položce host (můžete vyplnit buď HOSTNAME nebo IP adresu počítače, kde běží webová služba)

BEGIN
  DBMS_NETWORK_ACL_ADMIN.CREATE_ACL(acl         => 'www.xml',
                                    description => 'Test ACL',
                                    principal   => 'SCOTT',
                                    is_grant    => true,
                                    privilege   => 'connect');

  DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(acl       => 'www.xml',
                                       principal => 'SCOTT',
                                       is_grant  => true,
                                       privilege => 'resolve');

  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl  => 'www.xml',
                                    host => 'MY-NOTEBOOK-HOSTNAME');
END;
/
COMMIT
/

Pokud nastavíte správně výše zmíněná opravnění, pak by měl začít fungovat následující skript demonstrující Request/Response naší testovací webové služby. (U skriptu věnujte pozornost proměnným l_host_name a l_port, hodnoty by měly odpovídat těm vámi nastaveným)

DECLARE
    l_http_request   UTL_HTTP.req;
    l_http_response  UTL_HTTP.resp;
    l_buffer_size    NUMBER(10) := 512;
    l_line_size      NUMBER(10) := 50;
    l_lines_count    NUMBER(10) := 20;
    l_string_request VARCHAR2(512);
    l_line           VARCHAR2(128);
    l_substring_msg  VARCHAR2(512);
    l_raw_data       RAW(512);
    l_clob_response  CLOB;
    l_host_name      VARCHAR2(128) := 'MY-NOTEBOOK-HOSTNAME';
    l_port           VARCHAR2(128) := '8088';

BEGIN
    l_string_request := '<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <firstName xsi:type="xsd:string">Martin</firstName>
      </urn:sayHello>
   </soapenv:Body>
</soapenv:Envelope>';
    UTL_HTTP.set_transfer_timeout(60);
    l_http_request := UTL_HTTP.begin_request(url => 'http://' || l_host_name || ':' || l_port || '/mockHello_Binding', method => 'POST', http_version => 'HTTP/1.1');
    UTL_HTTP.set_header(l_http_request, 'User-Agent', 'Mozilla/4.0');
    UTL_HTTP.set_header(l_http_request, 'Host', l_host_name || ':' || l_port);
    UTL_HTTP.set_header(l_http_request, 'Connection', 'close');
    UTL_HTTP.set_header(l_http_request, 'Content-Type', 'text/xml;charset=UTF-8');
    UTL_HTTP.set_header(l_http_request, 'SOAPAction', '"sayHello"');
    UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_string_request));

    <<request_loop>>
    FOR i IN 0..CEIL(LENGTH(l_string_request) / l_buffer_size) - 1 LOOP
        l_substring_msg := SUBSTR(l_string_request, i * l_buffer_size + 1, l_buffer_size);

        BEGIN
            l_raw_data := utl_raw.cast_to_raw(l_substring_msg);
            UTL_HTTP.write_raw(r => l_http_request, data => l_raw_data);
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    EXIT request_loop;
        END;
    END LOOP request_loop;

    l_http_response := UTL_HTTP.get_response(l_http_request);
    DBMS_OUTPUT.put_line('Response> status_code: "' || l_http_response.status_code || '"');
    DBMS_OUTPUT.put_line('Response> reason_phrase: "' ||l_http_response.reason_phrase || '"');
    DBMS_OUTPUT.put_line('Response> http_version: "' ||l_http_response.http_version || '"');

    BEGIN

        <<response_loop>>
        LOOP
            UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
            l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
        END LOOP response_loop;

        EXCEPTION
            WHEN UTL_HTTP.end_of_body THEN
                UTL_HTTP.end_response(l_http_response);
    END;
    DBMS_OUTPUT.put_line('Response> length: "' || LENGTH(l_clob_response) || '"');
    DBMS_OUTPUT.put_line(CHR(10) || '=== Print first ' || l_lines_count || ' lines of HTTP response... ===' || CHR(10) || CHR(10));

    <<print_response>>
    FOR i IN 0..CEIL(LENGTH(l_clob_response) / l_line_size) - 1 LOOP
        l_line := SUBSTR(l_clob_response, i * l_line_size + 1, l_line_size);
        DBMS_OUTPUT.put_line('[' || LPAD(i, 2, '0') || ']: ' || l_line);
        EXIT WHEN i > l_lines_count - 1;
    END LOOP print_response;

    IF l_http_request.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_request(l_http_request);
    END IF;

    IF l_http_response.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_response(l_http_response);
    END IF;

END;
/

Výstup skriptu je následující:

Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "483"

=== Print first 20 lines of HTTP response... ===

[00]: <soapenv:Envelope xmlns:xsi="http://www.w3.org/200
[01]: 1/XMLSchema-instance" xmlns:xsd="http://www.w3.org
[02]: /2001/XMLSchema" xmlns:soapenv="http://schemas.xml
[03]: soap.org/soap/envelope/" xmlns:urn="urn:examples:h
[04]: elloservice">
   <soapenv:Header/>
   <soapenv:B
[05]: ody>
      <urn:sayHelloResponse soapenv:encoding
[06]: Style="http://schemas.xmlsoap.org/soap/encoding/">
[07]:
         <greeting xsi:type="xsd:string">gero et
[08]: </greeting>
      </urn:sayHelloResponse>
   </s
[09]: oapenv:Body>
</soapenv:Envelope>

Konzumace reálné webové služby Weather CDYNE

Jako „bombónek“ na závěr si ukážeme konzumaci reálné webové služby Weather CDYNE (WSDL popis), konkrétně volání metody GetCityForecastByZIP. Volání následujícího skriptu předpokládá nastavení práva (ACL) v databázi Oracle (podobně, jak jsem zmínil výše v článku):

BEGIN
  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl  => 'www.xml',
                                    host => 'ws.cdyne.com');
END;
/
COMMIT
/

A teď už slíbený skript:

DECLARE
    l_http_request    UTL_HTTP.req;
    l_http_response   UTL_HTTP.resp;
    l_buffer_size     NUMBER(10) := 512;
    l_line_size       NUMBER(10) := 50;
    l_lines_count     NUMBER(10) := 20;
    l_string_request  VARCHAR2(512);
    l_line            VARCHAR2(128);
    l_substring_msg   VARCHAR2(512);
    l_raw_data        RAW(512);
    l_clob_response   CLOB;
    l_host_name       VARCHAR2(128) := 'ws.cdyne.com';
    l_port            VARCHAR2(128) := '80';
    l_zip             VARCHAR2(128) := '94065'; -- ZIP for Oracle corporation (Redwood City)
    l_resp_xml        XMLType;
    l_result_XML_node VARCHAR2(128);
    l_NAMESPACE_SOAP  VARCHAR2(128) := 'xmlns="http://www.w3.org/2003/05/soap-envelope"';
    l_response_city   VARCHAR2(128);
    l_response_date   VARCHAR2(128);
    l_response_temp   VARCHAR2(128);

BEGIN
    l_string_request := '<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetCityForecastByZIP xmlns="http://ws.cdyne.com/WeatherWS/">
      <ZIP>' || l_zip || '</ZIP>
    </GetCityForecastByZIP>
  </soap12:Body>
</soap12:Envelope>';
    UTL_HTTP.set_transfer_timeout(60);
    l_http_request := UTL_HTTP.begin_request(url => 'http://' || l_host_name || ':' || l_port || '/WeatherWS/Weather.asmx', method => 'POST', http_version => 'HTTP/1.1');
    UTL_HTTP.set_header(l_http_request, 'User-Agent', 'Mozilla/4.0');
    UTL_HTTP.set_header(l_http_request, 'Connection', 'close');
    UTL_HTTP.set_header(l_http_request, 'Content-Type', 'application/soap+xml; charset=utf-8');
    UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_string_request));

    <<request_loop>>
    FOR i IN 0..CEIL(LENGTH(l_string_request) / l_buffer_size) - 1 LOOP
        l_substring_msg := SUBSTR(l_string_request, i * l_buffer_size + 1, l_buffer_size);

        BEGIN
            l_raw_data := utl_raw.cast_to_raw(l_substring_msg);
            UTL_HTTP.write_raw(r => l_http_request, data => l_raw_data);
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    EXIT request_loop;
        END;
    END LOOP request_loop;

    l_http_response := UTL_HTTP.get_response(l_http_request);
    DBMS_OUTPUT.put_line('Response> status_code: "' || l_http_response.status_code || '"');
    DBMS_OUTPUT.put_line('Response> reason_phrase: "' ||l_http_response.reason_phrase || '"');
    DBMS_OUTPUT.put_line('Response> http_version: "' ||l_http_response.http_version || '"');

    BEGIN

        <<response_loop>>
        LOOP
            UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
            l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
        END LOOP response_loop;

        EXCEPTION
            WHEN UTL_HTTP.end_of_body THEN
                UTL_HTTP.end_response(l_http_response);
    END;
    DBMS_OUTPUT.put_line('Response> length: "' || LENGTH(l_clob_response) || '"');
    DBMS_OUTPUT.put_line(CHR(10) || '=== Print result ===' || CHR(10) || CHR(10));

    IF(l_http_response.status_code = 200) THEN
        -- Create XML type from response text
        l_resp_xml := XMLType.createXML(l_clob_response);
        -- Clean SOAP header
        SELECT EXTRACT(l_resp_xml, 'Envelope/Body/node()', l_NAMESPACE_SOAP) INTO l_resp_xml FROM dual;
        -- Extract City
        l_result_XML_node := 'GetCityForecastByZIPResponse/GetCityForecastByZIPResult/';
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'City[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_city FROM dual;
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'ForecastResult[1]/Forecast[1]/Date[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_date FROM dual;
        SELECT EXTRACTVALUE(l_resp_xml, l_result_XML_node || 'ForecastResult[1]/Forecast[1]/Temperatures[1]/DaytimeHigh[1]', 'xmlns="http://ws.cdyne.com/WeatherWS/"') INTO l_response_temp FROM dual;
    END IF;

    DBMS_OUTPUT.put_line ( 'Result> l_response_city=' || l_response_city);
    DBMS_OUTPUT.put_line ( 'Result> l_response_date=' || l_response_date);
    DBMS_OUTPUT.put_line ( 'Result> l_response_temp=' || l_response_temp);

    IF l_http_request.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_request(l_http_request);
    END IF;

    IF l_http_response.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_response(l_http_response);
    END IF;

END;
/

Výstup skriptu je následující:

Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "2673"

=== Result... ===

Result> l_response_city=Redwood City
Result> l_response_date=2010-08-17T00:00:00
Result> l_response_temp=73

Post to Twitter

Balíky UTL_HTTP, UTL_RAW a jejich využití pro metodu HTTP GET

Z databáze Oracle je možné pomocí balíku UTL_HTTP a UTL_RAW zasílat/přijímat dotazy z webového serveru. Na malé ukázce demonstruji jak tyto balíky použít. (V příkladu používám balík UTL_RAW z toho důvodu, že u některých webových serverů, možná i v kombinaci s verzí Oracle databáze, generuje funkce READ_TEXT Oracle EXCEPTION, občas se podaří dokonce vygenerovat coredump).

[English version – Packages UTL_HTTP, UTL_RAW and their use in HTTP GET method]

Jednoduché použití dotažení dat (demonstruji na dotazu GET na server martin-mares.cz)

DECLARE
    l_http_request   UTL_HTTP.req;
    l_http_response  UTL_HTTP.resp;
    l_buffer_size    NUMBER(10) := 512;
    l_line_size      NUMBER(10) := 70;
    l_lines_count    NUMBER(10) := 10;
    l_string_request VARCHAR2(512);
    l_line           VARCHAR2(128);
    l_raw_data       RAW(512);
    l_clob_response  CLOB;

BEGIN
    UTL_HTTP.set_transfer_timeout(60);
    l_http_request := UTL_HTTP.begin_request(url => 'https://martin-mares.cz', method => 'GET', http_version => 'HTTP/1.1');
    UTL_HTTP.set_header(r => l_http_request, name => 'User-Agent', value => 'Mozilla/4.0');
    UTL_HTTP.set_header(l_http_request, 'Host', 'martin-mares.cz');
    l_http_response := UTL_HTTP.get_response(l_http_request);
    DBMS_OUTPUT.put_line('Response> status_code: "' || l_http_response.status_code || '"');
    DBMS_OUTPUT.put_line('Response> reason_phrase: "' ||l_http_response.reason_phrase || '"');
    DBMS_OUTPUT.put_line('Response> http_version: "' ||l_http_response.http_version || '"');

    BEGIN

        <<response_loop>>
        LOOP
            UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
            l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
        END LOOP response_loop;

        EXCEPTION
            WHEN UTL_HTTP.end_of_body THEN
                UTL_HTTP.end_response(l_http_response);
    END;
    DBMS_OUTPUT.put_line('Response> length: "' || LENGTH(l_clob_response) || '"');
    DBMS_OUTPUT.put_line(CHR(10) || '=== Print first ' || l_lines_count || ' lines of HTTP response... ===' || CHR(10) || CHR(10));

    <<print_response>>
    FOR i IN 0..CEIL(LENGTH(l_clob_response) / l_line_size) - 1 LOOP
        l_line := SUBSTR(l_clob_response, i * l_line_size + 1, l_line_size);
        DBMS_OUTPUT.put_line('[' || LPAD(i, 2, '0') || ']: ' || SUBSTR(TRIM(l_line),1,50) || '...');
        EXIT WHEN i > l_lines_count - 1;
    END LOOP print_response;

    IF l_http_request.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_request(l_http_request);
    END IF;

    IF l_http_response.private_hndl IS NOT NULL THEN
        UTL_HTTP.end_response(l_http_response);
    END IF;

END;
/

Výstupem tohoto volání (při nastaveném „set serveroutput on“ v SQL*Plus) bude:

DBMS Output (Session: [1] SCOTT@TEST_DB.WORLD at: 20.08.2010 00:43:57):
----------------------------------------------------------------------------
Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "28003"

=== Print first 10 lines of HTTP response... ===

Response> status_code: "200"
Response> reason_phrase: "OK"
Response> http_version: "HTTP/1.1"
Response> length: "28003"

=== Print first 10 lines of HTTP response... ===

[00]: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Trans...
[01]: ://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dt...
[02]: http://www.w3.org/1999/xhtml" dir="ltr" lang="cs-C...
[03]: head profile="http://gmpg.org/xfn/11">
	<title>Ma...
[04]: itle>
	<meta http-equiv="content-type" content="t...
[05]: F-8" />
	<link rel="stylesheet" type="text/css" h...
[06]: com/wp-content/themes/the-erudite/css/erudite.css"...
[07]: 6]> <link rel="stylesheet" href="http://martin-mares.co...
[08]: /the-erudite/css/ie6.css" type="text/css">
	<sty...
[09]: edia="screen">
		.hr {behavior: url(http://since7...
[10]: mes/the-erudite/library/iepngfix.htc); }
	</style...

Příště se podíváme na to, jak konzumovat data přes HTTP POST z Webové služby Weather (CDYNE)

Post to Twitter

SQL*Plus a historie příkazů v Linuxu pomocí rlwrap

SQL*Plus sám o sobě nemá implementovánu historii procházení mezi jednotlivými příkazy (výjimkou je poslední vykonaný příkaz, ten lze opětovně vyvolat pomocí „r“+Enter). V operačním systému Windows lze toto celkem dobře provést přes funkční klávesu F7, jak ukazuje následující obrázek. Elegantně lze listovat v historii vykonávaných příkazů.

[English version – SQL*Plus and command history in Linux using rlwrap]

Historie SQL*Plus příkazů - Windows7 cmd.exe

V operačním systému Linux lze toto implementovat doplněním wrapperu pro SQL*Plus v podobě programu rlwrap (program vytvoří prostředníka mezi bashem a prostředím SQL*Plus).

UPDATE 20.08.2010 – Pokud používáte distribuci Ubuntu, lze rlwrap nainstalovat jednoduše přes „sudo aptitude rlwrap install“

Postup instalace je následující:

1. Nejprve stáhneme program rlwrap ze serveru freshmeat – aktuální verze programu. Třeba pomocí wget (url adresa aktuální verze programu se může v průběhu času změnit. Verze na které ukazuji tuto instalaci je 0.37 a název staženého souboru je „rlwrap-0.37.tar.gz“):

oracle@ubuntu-server$ wget http://freshmeat.net/urls/de7d8482e030110354012880805e76fd

2. Rozbalíme stažený soubor

oracle@ubuntu-server:~$ tar zxvf rlwrap-0.37.tar.gz
rlwrap-0.37/
rlwrap-0.37/completions/
rlwrap-0.37/completions/testclient
rlwrap-0.37/completions/coqtop
rlwrap-0.37/doc/
rlwrap-0.37/doc/rlwrap.man.in
rlwrap-0.37/doc/Makefile.am
rlwrap-0.37/doc/Makefile.in
...

3. Přesuneme se do adresáře s programem

oracle@ubuntu-server:~$ cd rlwrap-0.37/

4. A spustíme kompilaci (při kompilaci jsem použil volbu „–prefix=/usr“. Standardně se program instaluje do adresáře /usr/local/bin, já v tomto případě preferuji /usr/bin, proto jsem použil volbu „–prefix=…“)

oracle@ubuntu-server:~/rlwrap-0.37$ ./configure --prefix=/usr && make
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
...

Pokud konfigurační skript skončí chybou:

configure: error:

You need the GNU readline library(ftp://ftp.gnu.org/gnu/readline/ ) to build
this program!

Je nutné buď (a) – stáhnout knihovnu readline z adresy ftp://ftp.gnu.org/gnu/readline/, rozbalit a ručně zkompilovat nebo (b) – jako v mém případě, pokud používáte distribuci Ubuntu (nebo jí podobnou), stačí nainstalovat potřebnou knihovnu pomocí aptitude:

oracle@ubuntu-server:~/rlwrap-0.37$ sudo aptitude install libreadline6-dev

… a poté znovu spustit kompilaci rlwrap:

oracle@ubuntu-server:~/rlwrap-0.37$ ./configure --prefix=/usr && make

5. Pokud je kompilace dokončena bez chyb, stačí už jen program nainstalovat.

oracle@ubuntu-server:~/rlwrap-0.37$ sudo make install
Making install in doc
make[1]: Entering directory `/home/oracle/rlwrap-0.37/doc'
make[2]: Entering directory `/home/oracle/rlwrap-0.37/doc'
make[2]: Nothing to be done for `install-exec-am'.
test -z "/usr/share/man/man1" || /bin/mkdir -p "/usr/share/man/man1"
 /usr/bin/install -c -m 644 rlwrap.1 '/usr/share/man/man1'
make[2]: Leaving directory `/home/oracle/rlwrap-0.37/doc'
make[1]: Leaving directory `/home/oracle/rlwrap-0.37/doc'
Making install in src
make[1]: Entering directory `/home/oracle/rlwrap-0.37/src'
make[2]: Entering directory `/home/oracle/rlwrap-0.37/src'
test -z "/usr/bin" || /bin/mkdir -p "/usr/bin"
  /usr/bin/install -c rlwrap '/usr/bin'
make[2]: Nothing to be done for `install-data-am'.
make[2]: Leaving directory `/home/oracle/rlwrap-0.37/src'
make[1]: Leaving directory `/home/oracle/rlwrap-0.37/src'
Making install in filters
make[1]: Entering directory `/home/oracle/rlwrap-0.37/filters'
make[2]: Entering directory `/home/oracle/rlwrap-0.37/filters'
make[2]: Nothing to be done for `install-exec-am'.
test -z "/usr/share/man/man3" || /bin/mkdir -p "/usr/share/man/man3"
 /usr/bin/install -c -m 644 RlwrapFilter.3pm '/usr/share/man/man3'
make[2]: Leaving directory `/home/oracle/rlwrap-0.37/filters'
make[1]: Leaving directory `/home/oracle/rlwrap-0.37/filters'
make[1]: Entering directory `/home/oracle/rlwrap-0.37'
make[2]: Entering directory `/home/oracle/rlwrap-0.37'
make[2]: Nothing to be done for `install-exec-am'.
test -z "/usr/share/rlwrap" || /bin/mkdir -p "/usr/share/rlwrap"
/bin/mkdir -p '/usr/share/rlwrap/filters'
 /usr/bin/install -c -m 644  filters/README filters/RlwrapFilter.pm filters/RlwrapFilter.3pm filters/count_in_prompt filters/pipeto filters/logger filters/null filters/unbackspace filters/pipeline filters/ftp_filter filters/history_format filters/simple_macro filters/template filters/scrub_prompt filters/paint_prompt filters/censor_passwords filters/listing '/usr/share/rlwrap/filters'
/bin/mkdir -p '/usr/share/rlwrap/completions'
 /usr/bin/install -c -m 644  completions/testclient completions/coqtop '/usr/share/rlwrap/completions'
make  install-data-hook
make[3]: Entering directory `/home/oracle/rlwrap-0.37'
chmod a+x /usr/share/rlwrap/filters/*
make[3]: Leaving directory `/home/oracle/rlwrap-0.37'
make[2]: Leaving directory `/home/oracle/rlwrap-0.37'
make[1]: Leaving directory `/home/oracle/rlwrap-0.37'

6. Nezapomeňte nastavit alias pro program sqlplus

oracle@ubuntu-server:~/rlwrap-0.37$ vi ~/.bashrc

… a přidat řádek:

alias sqlplus='/usr/bin/rlwrap sqlplus'

… jak ukazuje následující obrázek:

Edit .bashrc
Edit .bashrc

Po opětovném přihlášení pod účtem, pod kterým jste byly při kompilaci přihlášeni (a z kterého pouštíte SQL*Plus) byste měli být schopni pomocí šipek „nahoru/dolu“ listovat v historii vykonávaných příkazů spouštěných z prostředí SQL*Plus.

Post to Twitter

PIPELINED FUNCTION aneb jak na data dynamicky

Výsledný SQL dotaz v Oracle databázi lze maximálně dynamicky ovlivnit/generovat pomocí takzvaných PIPELINED FUNKCÍ. Jejich použití si ukážeme na následujícím příkladu:

[English version – PIPELINED FUNCTION – how to get data dynamically]

Vytvoříme nové datové typy PersonType a PersonTypeSet

Do těchto datových typů budeme ukládat naše data.

CREATE TYPE PersonType AS OBJECT
(
  id number,
  first_name varchar2(2000),
  last_name varchar2(2000),
  description varchar2(2000)
)
/

CREATE TYPE PersonTypeSet AS TABLE OF PersonType
/

Vytvoříme PIPELINED FUNKCI

CREATE OR REPLACE FUNCTION GET_PERSONS RETURN PersonTypeSet
PIPELINED
IS
    l_one_row PersonType := PersonType(NULL, NULL, NULL, NULL);

BEGIN

    FOR i IN 1..10 LOOP
        l_one_row.id := i;
        l_one_row.first_name := 'Johnny (' || i || ')';
        l_one_row.last_name := 'English';
        l_one_row.description := 'British Super Agent';
        PIPE ROW(l_one_row);
    END LOOP;

    RETURN;
END GET_PERSONS;
/

Podíváme se na data (SQL dotaz nad pipelined funkcí!)

SQL> SELECT * FROM TABLE(GET_PERSONS());

Pipelined function result (SQL query)

UPDATE 14.09.2010 – Při výskytu chyby „ORA-22905: nelze přistupovat k řádkám z položky tabulky, která není vnořená“ je nutné zavolat CAST takto: „SELECT * FROM TABLE(CAST(GET_PERSONS() AS PersonTypeSet));“protože Oracle potřebuje znát objektový typ před tím, než nahradí hvězdičku názvy sloupců!

Post to Twitter