<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5215551487816981140</id><updated>2012-01-31T12:40:33.145-08:00</updated><category term='NTLM'/><category term='Proxy Authentication'/><category term='DBMS_EPG'/><category term='Microsoft Office'/><category term='Google Translate'/><category term='pl/sql'/><category term='XML'/><category term='Resource Templates'/><category term='stress test'/><category term='web services'/><category term='IIS'/><category term='db2'/><category term='XE'/><category term='pdf'/><category term='oracle'/><category term='Alexandria'/><category term='SOAP'/><category term='data modeling'/><category term='Oracle HTTP Server'/><category term='Apex listener'/><category term='Tomcat'/><category term='mod_plsql'/><category term='Collections'/><category term='Amazon S3'/><category term='Apex'/><category term='sql'/><category term='database design'/><category term='Apex plugins'/><category term='rss'/><category term='11g'/><category term='ApexGen'/><category term='XDB'/><category term='utl_http'/><category term='fun'/><category term='OOXML'/><category term='csv'/><category term='websheets'/><category term='DBPrism'/><category term='database-centric architecture'/><category term='Thoth Gateway'/><category term='fat database'/><category term='jQGrid'/><category term='Interactive Reports'/><category term='json'/><category term='Batch scripts'/><title type='text'>ORA-00001: Unique constraint violated</title><subtitle type='html'>Random ramblings and raves of relational relevance!</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>51</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-6826345055999631656</id><published>2011-08-03T06:00:00.000-07:00</published><updated>2011-08-03T06:23:03.319-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='NTLM'/><title type='text'>NTLM for PL/SQL</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/NTLM"&gt;NTLM&lt;/a&gt;, or more properly &lt;a href="http://en.wikipedia.org/wiki/NTLMSSP"&gt;NTLMSSP&lt;/a&gt; is a protocol used on Microsoft Windows system as part of the so-called &lt;a href="http://en.wikipedia.org/wiki/Integrated_Windows_Authentication"&gt;Integrated Windows Authentication&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Integrated Windows Authentication is also known as HTTP Negotiate authentication, NT Authentication, NTLM Authentication, Domain authentication, Windows Integrated Authentication, Windows NT Challenge/Response authentication, or simply Windows Authentication.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-fggU3v1IV88/TjlDuI2B4CI/AAAAAAAAAaE/7f0ipr28xeE/s1600/IC50098.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="282" src="http://3.bp.blogspot.com/-fggU3v1IV88/TjlDuI2B4CI/AAAAAAAAAaE/7f0ipr28xeE/s400/IC50098.gif" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;In Microsoft Internet Information Server (IIS), the system administrator can protect a website or folder with "Integrated Windows Authentication". When you browse to this website or folder, you must enter your Windows (domain) username and password to get access (although Internet Explorer will, depending on your security settings, send your credentials automatically without showing a login dialog box). Note that unlike &lt;a href="http://en.wikipedia.org/wiki/Basic_authentication"&gt;Basic Authentication&lt;/a&gt;, which sends the password as plaintext to the web server, the NTLM protocol does not send the password but rather performs a cryptographic "handshake" with the server to establish your identity.&lt;br /&gt;&lt;br /&gt;Use of Integrated Windows Authentication via NTLM on IIS is very common inside many companies (ie on intranets and internal web servers), where both the client and web server computers are part of the same, or trusting, domains.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Using NTLM from PL/SQL with UTL_HTTP&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately, from the PL/SQL developer's perspective, Oracle's &lt;a href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/u_http.htm"&gt;UTL_HTTP package&lt;/a&gt; does not support NTLM authentication (it only supports Basic authentication via the SET_AUTHENTICATION procedure).&lt;br /&gt;&lt;br /&gt;So, if you wanted to retrieve information from your intranet or call a web service (protected by Integrated Windows Authentication) from the database via PL/SQL and UTL_HTTP, you were out of luck.&lt;br /&gt;&lt;br /&gt;Until now, that is... :-)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A pure PL/SQL implementation of the NTLM protocol&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I came across &lt;a href="http://code.google.com/p/python-ntlm/"&gt;a Python implementation of the NTLM protocol&lt;/a&gt;, and I decided that it should be possible to port this code to PL/SQL. Assisted by a couple of good friends and colleagues, and after a lot of bit-fiddling, reverse-engineering, study of protocol specifications, and liberal use of network packet sniffers, we got it working!&lt;br /&gt;&lt;br /&gt;A pure PL/SQL implementation of the NTLM protocol is now available and included in the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria Utility Library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The code is organized into two packages: &lt;b&gt;NTLM_UTIL_PKG&lt;/b&gt;, which contains protocol-specific functions, and &lt;b&gt;NTLM_HTTP_PKG&lt;/b&gt;, which is the package you actually use to make HTTP callouts, and which handles the initial NTLM "handshaking" with the web server.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Example 1: Simple request&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This code simply grabs the page you direct it towards, and returns the contents as a CLOB. (What is really going on behind the scenes is a series of requests and responses to establish the authenticated connection, before the actual URL contents is served.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;declare&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_clob clob;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;begin&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; debug_pkg.debug_on;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_clob := ntlm_http_pkg.get_response_clob('http://servername/page', 'domain\username', 'password');&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; debug_pkg.print(substr(l_clob, 1, 32000));&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;end;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Example 2: Web service call&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here, a (persistent) connection is explicitly established before making one or more requests to the server.&amp;nbsp;Note the returnvalue from the&amp;nbsp;&lt;b&gt;BEGIN_REQUEST&lt;/b&gt;&amp;nbsp;function, which is the authorization string which must be passed along in the "Authorization" HTTP header on any subsequent requests.&amp;nbsp;The connection is then is closed. &lt;b&gt;Note that NTLM is a connection-based protocol, and will not work without the use of persistent connections.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;declare&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_url &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; varchar2(2000) := 'http://servername/page';&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_ntlm_auth_str varchar2(2000);&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_xml &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; xmltype;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_soap_env &amp;nbsp; &amp;nbsp; &amp;nbsp;clob := 'your_soap_envelope_here';&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;begin&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; debug_pkg.debug_on;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- perform the initial request to set up a persistent, authenticated connection&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_ntlm_auth_str := ntlm_http_pkg.begin_request (l_url, 'domain\username', 'password');&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- pass authorization header to next call(s)&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; apex_web_service.g_request_headers(1).name := 'Authorization';&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; apex_web_service.g_request_headers(1).value := l_ntlm_auth_str;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- perform the actual call&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- NOTE: for this to work, you must be using a version of apex_web_service that allows persistent connections (fixed in Apex 4.1 ???)&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- &amp;nbsp; &amp;nbsp; &amp;nbsp; see http://jastraub.blogspot.com/2008/06/flexible-web-service-api.html?showComment=1310198286769#c8685039598916415836&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; l_xml := apex_web_service.make_request(l_url, 'soap_action_name_here', '1.1', l_soap_env);&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- or use the latest version of flex_ws_api&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- flex_ws_api.g_request_headers(1).name := 'Authorization';&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- flex_ws_api.g_request_headers(1).value := l_ntlm_auth_str;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- l_xml := flex_ws_api.make_request(l_url, 'soap_action_name_here', '1.1', l_soap_env);&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; -- this will close the persistent connection&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; ntlm_http_pkg.end_request;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp; debug_pkg.print('XML response from webservice', l_xml);&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;end;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Remarks&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Tested successfully on Oracle 10g XE (with AL32UTF8 character set) and Oracle 10g EE (with WE8MSWIN1252 character set).&lt;/li&gt;&lt;li&gt;Tested successfully against IIS 6.0 with non-SSL "plain" website and SSL-enabled Sharepoint website (both set up with Integrated Windows Authentication, obviously).&lt;/li&gt;&lt;li&gt;The current version ignores cookies when setting up the connection. If you depend on cookies being present, you may have to deal with this specifically.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Given the diverse nature of network configuration, there may be bugs or unhandled cases in the code. So please test the code in your environment and leave a comment below, letting me me know if it works for you or not.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-6826345055999631656?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/6826345055999631656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=6826345055999631656' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6826345055999631656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6826345055999631656'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/08/ntlm-for-plsql.html' title='NTLM for PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-fggU3v1IV88/TjlDuI2B4CI/AAAAAAAAAaE/7f0ipr28xeE/s72-c/IC50098.gif' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-9156990050650681248</id><published>2011-08-03T05:47:00.000-07:00</published><updated>2011-08-03T05:47:05.607-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>New version of Alexandria Utility Library for PL/SQL</title><content type='html'>I've just uploaded a &lt;a href="http://code.google.com/p/plsql-utils/downloads/list"&gt;new version&lt;/a&gt; of the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria Utility Library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Updates include both small bug fixes and some major new features (which I'll return to in another post).&lt;br /&gt;&lt;br /&gt;Among the improvements are:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Additional functions in OOXML_UTIL_PKG for working with Excel 2007 and Powerpoint 2007 files.&lt;/li&gt;&lt;li&gt;Kris Scorup has contributed improved CSV parsing to the CSV_UTIL_PKG. It now handles double quotes and separator characters inside strings.&lt;/li&gt;&lt;li&gt;Anton Scheffer's packages for building PDF and XLSX files have been included in the library.&lt;/li&gt;&lt;li&gt;The &lt;a href="http://www.erasme.org/PL-FPDF,1337?lang=en"&gt;PL_FPDF library&lt;/a&gt; by Pierre-Gilles Levallois is a port of the &lt;a href="http://www.fpdf.org/"&gt;FPDF library for PHP&lt;/a&gt;.&amp;nbsp;Pierre-Gilles Levallois has his own website, but his package (which is open source under the GNU license) does not appear to have been updated for several years, and several indivuals have been making their own fixes and enhancements to this package.&amp;nbsp;Rob Duke and Brian McGinity have both contributed bug fixes and enhancements (such as internal document links, Javascript support, using clobs to work around the 32k limit, etc.).&amp;nbsp;These changes as well as some of my own have been merged and included in the Alexandria library as PDFGEN_PKG. You'll find this package in the "extras" folder (it is not installed by default if you run the install script).&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-9156990050650681248?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/9156990050650681248/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=9156990050650681248' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/9156990050650681248'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/9156990050650681248'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/08/new-version-of-alexandria-utility.html' title='New version of Alexandria Utility Library for PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4011112742093580702</id><published>2011-07-29T10:45:00.000-07:00</published><updated>2011-07-29T10:46:05.194-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Batch scripts'/><title type='text'>Count lines in multiple files using Windows command prompt</title><content type='html'>Not really Oracle-related, but I'm posting this as a reminder to myself and possibly useful to others.&lt;br /&gt;&lt;br /&gt;To count the number of lines in a given set of files using the Windows command prompt, do the following:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;for %G in (*.sql) do find /c /v "_+_" %G&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This invokes the "find" command once for each file, counting the lines that do NOT contain the string "_+_" (the string has no special significance, any weird string that would not occur "naturally" in the files can be used).&lt;br /&gt;&lt;br /&gt;There are probably more sophisticated ways of doing this, perhaps using PowerShell and whatnot. Leave a comment if you know of other methods (something that adds together all the file line counts into one grand total would be even better).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4011112742093580702?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4011112742093580702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4011112742093580702' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4011112742093580702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4011112742093580702'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/07/count-lines-in-multiple-files-using.html' title='Count lines in multiple files using Windows command prompt'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-8366732697769499889</id><published>2011-07-29T05:09:00.000-07:00</published><updated>2011-07-29T05:48:44.667-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stress test'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='fat database'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Mythbusters: Stored Procedures Edition</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-tguIRtDZzQY/TjKVnERutLI/AAAAAAAAAY8/6cvt9a2fej0/s1600/mythbusters.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="225" src="http://2.bp.blogspot.com/-tguIRtDZzQY/TjKVnERutLI/AAAAAAAAAY8/6cvt9a2fej0/s400/mythbusters.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;These days, the use of database stored procedures is &lt;a href="http://programmers.stackexchange.com/questions/65742/stored-procedures-a-bad-practice-at-one-of-worlds-largest-it-software-consulting"&gt;regarded by many as a bad practice&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Those that dislike stored procedures tend to regard them as incompatible with &lt;a href="http://en.wikipedia.org/wiki/Multitier_architecture"&gt;the three-tier architecture&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;By breaking up an application into tiers, developers only have to modify or add a specific layer, rather than have to rewrite the entire application over. There should be a presentation tier, a business or data access tier, and a data tier.&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;This is illustrated as follows:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-EyntAk2yR_I/TjKV1xaIbtI/AAAAAAAAAZA/Yn6cqg_Qnqc/s1600/593px-Overview_of_a_three-tier_application_vectorVersion.svg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="356" src="http://4.bp.blogspot.com/-EyntAk2yR_I/TjKV1xaIbtI/AAAAAAAAAZA/Yn6cqg_Qnqc/s400/593px-Overview_of_a_three-tier_application_vectorVersion.svg.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Note that the "tiers" in the figure should actually be labelled "layers", for as the accompanying Wikipedia article says:&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;The concepts of layer and tier are often used interchangeably. However, one fairly common point of view is that there is indeed a difference, and that a layer is a logical structuring mechanism for the elements that make up the software solution, while a tier is a physical structuring mechanism for the system infrastructure.&amp;nbsp;&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;In fact, those that argue that stored procedures are bad tend to equate the three logical layers with three physical tiers:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Data layer = data tier (database)&lt;/li&gt;&lt;li&gt;Logic layer = middle tier (application server)&lt;/li&gt;&lt;li&gt;Presentation layer = presentation tier&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;But if we accept the above definition of "layers" and "tiers", it is obvious that the following is a valid mapping as well:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Data layer = data tier (database)&lt;/li&gt;&lt;li&gt;Logic layer = &lt;b&gt;data tier (database)&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Presentation layer = presentation tier&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;In other words, the database becomes our "logic layer" through the use of database stored procedures, which, as the name implies, are physically stored (and executed) in the database. (And although I use the term "stored procedure", I'm primarily talking about Oracle and PL/SQL, where the PL/SQL code should be put in packages rather than stand-alone procedures.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;But why is this a bad idea? In fact, as it turns out, it might not be a bad idea at all. The usual reasons given against the use of stored procedures for "business logic" (or for anything at all, really) tend to be myths (or outright lies), repeated so many times that they are taken as the truth.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;So let's bust these myths, once and for all. And whenever someone argues against stored procedures using one of these myths, just give them a link to this blog post. (And leave comments to prove me wrong, if you will.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #1: Stored procedures can't be version controlled&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-p-fwkhWxay4/TjKWWySiZRI/AAAAAAAAAZE/LF9DCnuycW8/s1600/SNAG-0095.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://3.bp.blogspot.com/-p-fwkhWxay4/TjKWWySiZRI/AAAAAAAAAZE/LF9DCnuycW8/s640/SNAG-0095.jpg" width="577" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Stored procedure code lives in text files, which can be version controlled like any other piece of code or document. Storing/compiling the code in the database is just like (re-)deploying any other code.&lt;br /&gt;&lt;br /&gt;Claiming that stored procedures cannot be version controlled (because they are in the database) is like saying your application source code (Java, C# or whatever) cannot be version controlled because it is compiled and deployed to an application server.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #2: Managing the impact of changes in the database is hard&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-05wq0rB8RQY/TjKbwaezYhI/AAAAAAAAAZM/vCzJYYyJttk/s1600/SNAG-0108.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="424" src="http://4.bp.blogspot.com/-05wq0rB8RQY/TjKbwaezYhI/AAAAAAAAAZM/vCzJYYyJttk/s640/SNAG-0108.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Databases such as Oracle have built-in fine-grained dependency tracking.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-i11y1sec8Z4/TjKb5b9fhKI/AAAAAAAAAZQ/GvZIShfq6gI/s1600/SNAG-0110.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="395" src="http://4.bp.blogspot.com/-i11y1sec8Z4/TjKb5b9fhKI/AAAAAAAAAZQ/GvZIShfq6gI/s640/SNAG-0110.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;A wealth of information about your code is exposed via data dictionary views.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #3: Database tools lack modern IDE features&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-4zpdRK5ZXew/TjKcC4yJ1lI/AAAAAAAAAZU/um-6lG8eYzM/s1600/SNAG-0159.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="115" src="http://1.bp.blogspot.com/-4zpdRK5ZXew/TjKcC4yJ1lI/AAAAAAAAAZU/um-6lG8eYzM/s640/SNAG-0159.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-lP42rDfll3E/TjKcIyCzxWI/AAAAAAAAAZY/DTsQVoxK968/s1600/SNAG-0156.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="377" src="http://3.bp.blogspot.com/-lP42rDfll3E/TjKcIyCzxWI/AAAAAAAAAZY/DTsQVoxK968/s640/SNAG-0156.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;There are a number of free and commercial PL/SQL code editors and IDEs, and all have various levels of syntax highlighting, code insight and refactoring support.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #4: Stored procedures always result in spaghetti code&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-O-q9FEf3mJg/TjKcm7bwvcI/AAAAAAAAAZc/094lwz7GVBU/s1600/DependencyGraph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="356" src="http://3.bp.blogspot.com/-O-q9FEf3mJg/TjKcm7bwvcI/AAAAAAAAAZc/094lwz7GVBU/s640/DependencyGraph.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;To this, I can only say that bad programmers can make pasta in any language (the above is &lt;a href="http://thedailywtf.com/Articles/The-Enterprise-Dependency.aspx"&gt;a visual representation of a Java or .NET enterprise framework&lt;/a&gt; "several dozen megabytes chock full of helper classes like IEnterpriseAuthenticationProviderFactoryManagementFactory").&lt;br /&gt;&lt;br /&gt;And a good programmer can create "beautiful" code in COBOL, Visual Basic, PHP... and any stored procedure language, for that matter.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #5: Code in the database can’t be properly encapsulated and reused, you need an object-oriented language for that&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-QU9b_BLkhCQ/TjKc7OPhV7I/AAAAAAAAAZk/m8cj6BgE3YM/s1600/SNAG-0101.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="372" src="http://4.bp.blogspot.com/-QU9b_BLkhCQ/TjKc7OPhV7I/AAAAAAAAAZk/m8cj6BgE3YM/s640/SNAG-0101.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-WAD2TwYlHug/TjKc6wB8cwI/AAAAAAAAAZg/JUdkvS1nqN0/s1600/SNAG-0133.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="368" src="http://4.bp.blogspot.com/-WAD2TwYlHug/TjKc6wB8cwI/AAAAAAAAAZg/JUdkvS1nqN0/s640/SNAG-0133.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL packages, views, pipelined functions and Ref Cursors offer encapsulation and reuse. And PL/SQL has object-oriented features, too.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #6: Stored procedure languages are primitive, they lack basic features such as exception handling and dynamic execution&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-rxWK6SRbRYY/TjKdakC6UhI/AAAAAAAAAZo/ciuZpTVmKDg/s1600/SNAG-0164.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="343" src="http://3.bp.blogspot.com/-rxWK6SRbRYY/TjKdakC6UhI/AAAAAAAAAZo/ciuZpTVmKDg/s400/SNAG-0164.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL has had proper exception handling from the start, over 20 years ago (although exception handling was only introduced to SQL Server in 2005).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-eQLFXcXX0Dk/TjKdj-deDuI/AAAAAAAAAZs/hhthEn2SWiM/s1600/SNAG-0163.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="202" src="http://4.bp.blogspot.com/-eQLFXcXX0Dk/TjKdj-deDuI/AAAAAAAAAZs/hhthEn2SWiM/s640/SNAG-0163.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;DBMS_SQL, EXECUTE IMMEDIATE and "weak" Ref Cursors enable dynamic execution of code. Parameter overloading and the ANYDATA and ANYTYPE types allow for generic code to be written.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #7: Debugging stored procedures is hard/impossible&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-00SaISUx-cw/TjKdt0Xoj9I/AAAAAAAAAZw/K-eMSyOUVg0/s1600/debug13.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="483" src="http://3.bp.blogspot.com/-00SaISUx-cw/TjKdt0Xoj9I/AAAAAAAAAZw/K-eMSyOUVg0/s640/debug13.gif" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Both Oracle and SQL Server have built-in debugging capabilities, exposed via graphical user interfaces in the common IDEs, with full support for stepping though code, inspecting values, etc.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #8: Stored procedures can't be unit tested&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-TXSDR-ZwQFo/TjKd2_w2aEI/AAAAAAAAAZ0/tmgL5fIcs3g/s1600/SNAG-0091.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="418" src="http://4.bp.blogspot.com/-TXSDR-ZwQFo/TjKd2_w2aEI/AAAAAAAAAZ0/tmgL5fIcs3g/s640/SNAG-0091.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;There are a number of free and commercial unit testing frameworks available for PL/SQL. Steven Feuerstein, one of the world's leading experts on the Oracle PL/SQL language, has been &lt;a href="http://www.youtube.com/watch?v=4-EfRXvctgg"&gt;preaching the importance of unit testing in the database for years&lt;/a&gt;, and has developed several of the available unit testing frameworks.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #9: Stored procedures are not portable, and tie you to one platform&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is the "vendor lock-in" argument. But the fact is that &lt;b&gt;PL/SQL runs on multiple databases&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;Such as &lt;a href="http://www.ibm.com/developerworks/data/library/techarticle/dm-0907oracleappsondb2/index.html"&gt;DB2&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"IBM DB2 9.7 for Linux, UNIX, and Windows has out-of-the-box support for Oracle's SQL and PL/SQL dialects. This allows many applications written against Oracle to execute against DB2 virtually unchanged."&lt;/i&gt;&lt;/blockquote&gt;&lt;a href="http://www.enterprisedb.com/solutions/oracle-compatibility-technology"&gt;And Postgres (EnterpriseDB):&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"Postgres Plus Advanced Server implements a comprehensive suite of Oracle-compatible functionality within and around the core PostgreSQL engine, including: (...) Oracle SQL syntax and semantics, Functions and Packages, PL/SQL (extensive support)"&lt;/i&gt;&lt;/blockquote&gt;Add to this the fact that the Oracle database runs on more operating systems than any other database, which means that your PL/SQL code will seamlessly transfer from Windows to Unix to Linux-based systems.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-duUr52YfTPI/TjKeijI8sII/AAAAAAAAAZ4/vR7XK4JMNK8/s1600/SNAG-0104.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="206" src="http://1.bp.blogspot.com/-duUr52YfTPI/TjKeijI8sII/AAAAAAAAAZ4/vR7XK4JMNK8/s640/SNAG-0104.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;So PL/SQL-based code can actually be said to be &lt;b&gt;more portable&lt;/b&gt; than, for example, .NET code (despite the existence of Mono). There are very few truly portable technologies; even&amp;nbsp;Java is "&lt;i&gt;write once, debug everywhere&lt;/i&gt;".&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #10: It's stupid/dangerous to put business logic in the database&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This claim is usually made without any specific reason as to why it is stupid or dangerous. It usually "&lt;i&gt;just is&lt;/i&gt;", because it is "&lt;i&gt;against best practice&lt;/i&gt;" and "&lt;i&gt;everybody else is putting the business logic in the middle tier&lt;/i&gt;". Sometimes it is claimed that putting logic in the database "&lt;i&gt;mixes concerns&lt;/i&gt;", which must be a bad thing.&lt;br /&gt;&lt;br /&gt;The problem with &lt;b&gt;"business logic"&lt;/b&gt; is that nobody has a clear definition of what it is (but "&lt;i&gt;you'll know it when you see it&lt;/i&gt;"). For example, where do you draw the line between "data logic" and "business logic"? Primary keys, foreign key constraints, unique key constraints, not null constraints, check constraints -- are these "data logic" or "business logic"? "Discount must be between 0% and 5%", is that a business rule or a data constraint, and/or is it a validation rule in the presentation layer?&lt;br /&gt;&lt;br /&gt;The fact is, if you move &lt;b&gt;ALL&lt;/b&gt; your logic into stored procedures, you entirely avoid the "mixing of concerns" between the data tier and the logic tier. (And if you think such an approach dooms your project to failure, consider the next myth, which features an example of a massive [and wildly successful] application written entirely in the database.)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Oh, and by the way, if your business logic is somewhere else than in the database, you always run the risk of someone or something bypassing your middle tier (for example by logging in with SQL*Plus), directly updating the database and possibly corrupting the data.&lt;br /&gt;&lt;br /&gt;So let's turn this around and conclude instead that:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"If your business logic is not in the database, it is only a recommendation."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Myth #11: Stored procedures can't scale&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A frequent argument against stored procedures is that by placing all the work in the database server, your solution won't be able to scale up, because you need "application servers" in the middle tier to do that. The scalability of the database is limited by the fact that you can only have a single database server (or you need to rewrite your code to work with partitioned/sharded databases like Facebook have done).&lt;br /&gt;&lt;br /&gt;Of course, a lot of the people who throw around this kind of argument have never worked on an application or website which needed to scale up to millions of users (and to be clear, neither have I). That's because the vast majority of us work on much smaller enterprise business systems or "normal" websites (perhaps even the kind of website that can be well served with &lt;a href="http://ora-00001.blogspot.com/2011/03/stress-testing-oracle-xe-10g.html"&gt;free database software on a server with less juice than your laptop&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;But stored procedures CAN scale.&lt;/b&gt; It's only a matter of money. And if you have millions of users, you should be able to afford decent hardware.&lt;br /&gt;&lt;br /&gt;Let's use Oracle Application Express (Apex) as an example of &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex/apex-arch-086399.html"&gt;a big and complex PL/SQL application&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"[Application Express] lives completely within your Oracle database. It is comprised of nothing more than data in tables and large amounts of PL/SQL code. The essence of Oracle Application Express is approximately 425 tables and 230 PL/SQL packages containing 425,000+ lines of code."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;This PL/SQL application can be deployed anywhere from your laptop to any kind of server:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-7T_jwRdnEqY/TjKgGIF7VBI/AAAAAAAAAaA/R7W2NGx2v6s/s1600/SNAG-1041.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="276" src="http://3.bp.blogspot.com/-7T_jwRdnEqY/TjKgGIF7VBI/AAAAAAAAAaA/R7W2NGx2v6s/s400/SNAG-1041.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The biggest and fastest server you can buy is currently &lt;a href="http://www.oracle.com/technetwork/database/exadata/exadata-storage-exp-rack-ds-v1-425092.pdf"&gt;Oracle Exadata&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"An 8 rack configuration has a raw disk capacity of 3,360 TB and 1,680 CPU cores for SQL processing. Larger configurations can be built with additional InfiniBand switches."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;Oracle makes &lt;a href="http://about.ovum.com/app/oracle%E2%80%99s-messages-continue-a-journey-to-one-stop-shopping/"&gt;bold claims&lt;/a&gt; about this machine:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"Oracle claims that (..) two Exadata database systems would be able to handle Facebook’s entire computing load."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;It's hard for me to verify that claim, not being associated with neither Oracle nor Facebook, but let's assume it has at least some truth to it.&lt;br /&gt;&lt;br /&gt;So what about &lt;a href="http://tylermuth.wordpress.com/2011/04/12/apex-is-57-4x-faster-on-exadata/"&gt;running our "stored procedure" application on Exadata&lt;/a&gt;?&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"&lt;b&gt;Does APEX Work on Exadata?&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;i&gt;"Yep, Exadata is just Oracle. Let me say that again: It’s just Oracle. Exadata runs an 11.2 Database on Linux x64. It’s the exact same binary install if you download those binaries for “generic” Linux x64 11.2 from OTN. So, if your code / app runs on 11.2, it runs on Exadata. (..) The APEX Dev Team (my old friends and colleagues) &lt;b&gt;did absolutely nothing to port their code to Exadata&lt;/b&gt;. I've run a number of customer benchmarks with customer's data and queries and have yet to make a single change to their queries or structures to make them work on Exadata."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;So... without changing a single of those 425,000 lines of code, this "stored procedure" application can run on my old laptop (I've even tried it on an Asus EEE netbook), or it can run with 1,680 CPU cores. Without offloading any logic to an application server.&lt;br /&gt;&lt;br /&gt;I'd say that's pretty scalable.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-3kszYy_pS5A/TjKWq0QOxjI/AAAAAAAAAZI/R4vq5cH2NTc/s1600/busted.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-8366732697769499889?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/8366732697769499889/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=8366732697769499889' title='50 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8366732697769499889'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8366732697769499889'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/07/mythbusters-stored-procedures-edition.html' title='Mythbusters: Stored Procedures Edition'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-tguIRtDZzQY/TjKVnERutLI/AAAAAAAAAY8/6cvt9a2fej0/s72-c/mythbusters.jpg' height='72' width='72'/><thr:total>50</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1725375485571137587</id><published>2011-05-24T01:54:00.000-07:00</published><updated>2011-05-24T01:54:04.723-07:00</updated><title type='text'>Mobile device support in Apex 4.1</title><content type='html'>The current Apex &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex/application-express/apex-sod-087560.html"&gt;Statement of Direction&lt;/a&gt; for Apex 4.1 states that it will&amp;nbsp;"&lt;i&gt;include themes and HTML templates suitable for smart phones and mobile devices&lt;/i&gt;".&lt;br /&gt;&lt;br /&gt;If you are wondering what that means, then check out &lt;a href="http://forums.oracle.com/forums/thread.jspa?threadID=2222794"&gt;this thread on the Apex OTN Forum&lt;/a&gt; where Marc Sewtz, one of the developers on the Apex team, provides more details about this new feature.&lt;br /&gt;&lt;br /&gt;Interestingly, some of these "mobile-enabling" features are also relevant for regular applications, such as the ability to render a form without a table grid, enhanced label templates, and dynamic (SQL-based) lists.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1725375485571137587?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1725375485571137587/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1725375485571137587' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1725375485571137587'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1725375485571137587'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/05/mobile-device-support-in-apex-41.html' title='Mobile device support in Apex 4.1'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1027621587671177390</id><published>2011-04-14T22:39:00.000-07:00</published><updated>2011-04-14T22:39:46.131-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='database-centric architecture'/><title type='text'>Just give me a hash table and a shitload of RAM</title><content type='html'>In &lt;a href="http://www.theserverside.com/news/2240022782/James-Gosling-Interview-from-Basementcoderscom"&gt;this interview&lt;/a&gt;, James Gosling, the "father of Java", says:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;I’ve never got it when it comes to SQL databases. It’s like, why? Just give me a hash table and a shitload of RAM, and I’m happy. And then you do something to deal with failures.&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;Right... just "do something to deal with failures". And maybe &lt;a href="http://ora-00001.blogspot.com/2009/06/oracle-database-as-development-platform.html"&gt;add some other useful stuff&lt;/a&gt;. Shouldn't take too long to implement and make sure it works as intended.&lt;br /&gt;&lt;br /&gt;But Mr. Gosling, could you please give me a cost estimate and a delivery date for the data-centric business application I want you to build for me?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1027621587671177390?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1027621587671177390/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1027621587671177390' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1027621587671177390'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1027621587671177390'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/04/just-give-me-hash-table-and-shitload-of.html' title='Just give me a hash table and a shitload of RAM'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-957846203473525269</id><published>2011-03-28T10:48:00.000-07:00</published><updated>2011-03-28T10:59:37.878-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='Amazon S3'/><title type='text'>Amazon S3 API for PL/SQL</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-KHFHkp3EPTk/TZDEu-JQqfI/AAAAAAAAAXc/W0yS4dtpPAc/s1600/logo_aws.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-KHFHkp3EPTk/TZDEu-JQqfI/AAAAAAAAAXc/W0yS4dtpPAc/s1600/logo_aws.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Amazon S3 is part of Amazon's Web Service offering and the name is an abbreviation for &lt;a href="http://aws.amazon.com/s3/"&gt;Simple Storage Service&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"Amazon S3 provides a simple web services interface that can be used to store and retrieve any amount of data, at any time, from anywhere on the web. It gives any developer access to the same highly scalable, reliable, secure, fast, inexpensive infrastructure that Amazon uses to run its own global network of web sites. The service aims to maximize benefits of scale and to pass those benefits on to developers."&lt;/i&gt;&lt;/blockquote&gt;A few months ago, &lt;a href="http://jastraub.blogspot.com/2011/01/building-amazon-s3-client-with.html"&gt;Jason Straub&lt;/a&gt; published &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex/application-express/integration-086636.html"&gt;an Oracle whitepaper&lt;/a&gt;&amp;nbsp;on how to integrate Oracle Application Express (Apex) with Amazon S3.&lt;br /&gt;&lt;br /&gt;As Jason points out, Amazon has a &lt;a href="http://aws.amazon.com/free/"&gt;Free Usage Tier&lt;/a&gt; which allows you to get started using Amazon S3 for free. If you have ever bought a book from Amazon, they already have your credit card on file, so signing up for Amazon Web Services is quick and easy (and they won't start charging your credit card until the free trial period is over).&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Introducing the S3 API for PL/SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Inspired by Jason's whitepaper, I decided to write a &lt;b&gt;stand-alone PL/SQL API for Amazon S3&lt;/b&gt;. This API can be used in any PL/SQL solution, with or without Apex.&lt;br /&gt;&lt;br /&gt;The API supports all common S3 operations, including&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Authentication&lt;/li&gt;&lt;li&gt;Creating new buckets&lt;/li&gt;&lt;li&gt;Listing existing buckets&lt;/li&gt;&lt;li&gt;Listing existing objects (with or without filtering)&lt;/li&gt;&lt;li&gt;Creating (uploading) new objects (and setting an Access Control List - ACL)&lt;/li&gt;&lt;li&gt;Generating download links (with or without expiry dates)&lt;/li&gt;&lt;li&gt;Downloading objects&lt;/li&gt;&lt;li&gt;Deleting objects&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;See the examples below for more details.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Use Cases&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So what can you do with Amazon's S3 service in combination with a PL/SQL API?&lt;br /&gt;&lt;br /&gt;I can think of several interesting use cases, some of which I might explore further in future posts:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Backing up your database (use DBMS_DATAPUMP to dump a file to disk, then compress it using ZIP_UTIL_PKG, then encrypt it using CRYPTO_UTIL_PKG, and upload it to S3)&lt;/li&gt;&lt;li&gt;Backing up your PL/SQL source code (use data dictionary views or DBMS_METADATA to extract the source code, optionally zip and/or encrypt it, and upload to S3)&lt;/li&gt;&lt;li&gt;Backing up your Apex applications (use WWV_FLOW_UTILITIES.EXPORT_APPLICATION_TO_CLOB to generate export file, optionally zip and/or encrypt it, and upload to S3)&lt;/li&gt;&lt;li&gt;Cloud storage for file uploads (instead of storing [large] files inside your database, store them in the cloud and download them on demand -- especially relevant for Oracle XE which has a file size limit)&lt;/li&gt;&lt;li&gt;Serve static content (generate static [text, CSV, HTML, PDF] files from the database and upload to S3)&lt;/li&gt;&lt;li&gt;Replication or shared storage (upload from one machine/database, download to another)&lt;/li&gt;&lt;li&gt;Data loading or message processing (set up to poll for new incoming files - uploaded by other S3 clients - and process them)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Remember that all these things can be scheduled to run in the database using DBMS_JOB or DBMS_SCHEDULER.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Where to get the Amazon S3 API for PL/SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can &lt;a href="http://code.google.com/p/plsql-utils/downloads/list"&gt;download&lt;/a&gt; the API as part of the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria Utility Library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Getting started&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Download and compile the relevant PL/SQL API packages. Then register with Amazon for an S3 account and get your AWS keys (key and secret key), and login to the AWS Management Console to get familiar with the basic operations.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-nn0JvWK5cRE/TZDFvn13uVI/AAAAAAAAAXg/lBYTjI-Xw1E/s1600/SNAG-0884.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="281" src="http://1.bp.blogspot.com/-nn0JvWK5cRE/TZDFvn13uVI/AAAAAAAAAXg/lBYTjI-Xw1E/s400/SNAG-0884.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;If you are unfamiliar with Amazon S3, I recommend that you read this short &lt;a href="http://docs.amazonwebservices.com/AmazonS3/latest/gsg/"&gt;getting started guide&lt;/a&gt;&amp;nbsp;that describes the common operations.&lt;br /&gt;&lt;br /&gt;In the following examples we shall see how you can do the same operations using PL/SQL.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Authentication&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;From your Amazon account control panel, you'll get the key strings you need to use the Amazon web services.&lt;br /&gt;&lt;br /&gt;Before you call any of the following API methods, you must initialize the authentication package. You only have to do this once per database session (but remember, on the web, every page view is a separate database session, so in Apex you'll need to run this code for every page, typically as a Before Header page process).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-rS3KDIRJO3A/TZDGI3eo6tI/AAAAAAAAAXk/tPWNlAjj7IA/s1600/SNAG-0885.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="90" src="http://1.bp.blogspot.com/-rS3KDIRJO3A/TZDGI3eo6tI/AAAAAAAAAXk/tPWNlAjj7IA/s400/SNAG-0885.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Creating new buckets&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Buckets&lt;/i&gt; are what you use to organize your &lt;i&gt;objects &lt;/i&gt;(files) in Amazon S3. Think of them as top-level folders, but note that you cannot create more than 100 buckets in a single account, and &lt;b&gt;the bucket name must be unique across all user accounts on Amazon S3&lt;/b&gt;. So creating buckets is not really something you'd do very often, and usually it will be done manually (to resolve any name conflicts with existing buckets).&lt;br /&gt;&lt;br /&gt;A bucket is &lt;a href="http://aws.amazon.com/s3/faqs/#Where_is_my_data_stored"&gt;associated with a specific region&lt;/a&gt; where your objects will be stored. For reasons of latency/speed and possibly legal issues, it makes sense to select a region that's close to you and your users (although you may actually want to locate it far away if the purpose is backup for a major disaster in your own area).&lt;br /&gt;&lt;br /&gt;Here's how to create a new bucket via PL/SQL code:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-D0h1iGNi3iE/TZDGetW_uxI/AAAAAAAAAXo/optKYeilQgI/s1600/SNAG-0886.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="161" src="http://3.bp.blogspot.com/-D0h1iGNi3iE/TZDGetW_uxI/AAAAAAAAAXo/optKYeilQgI/s400/SNAG-0886.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Checking the AWS management console to verify that the bucket has indeed been created (in the specified region):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-HmbBlA5OcbA/TZDGiqJQYeI/AAAAAAAAAXw/mWrWUomjFbo/s1600/SNAG-0887.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-HmbBlA5OcbA/TZDGiqJQYeI/AAAAAAAAAXw/mWrWUomjFbo/s400/SNAG-0887.jpg" width="303" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Listing existing buckets&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;With one or more buckets created in your account, you can list the bucket names.&lt;br /&gt;&lt;br /&gt;There are two way to do this, either by retrieving an index-by PL/SQL table using the &lt;b&gt;GET_BUCKET_LIST&lt;/b&gt; function:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-lP8dFIRc9Wo/TZDGjP6IiYI/AAAAAAAAAX0/9MIANT3KW6o/s1600/SNAG-0888.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="188" src="http://2.bp.blogspot.com/-lP8dFIRc9Wo/TZDGjP6IiYI/AAAAAAAAAX0/9MIANT3KW6o/s400/SNAG-0888.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;or, alternatively, via SQL using a pipelined function named &lt;b&gt;GET_BUCKET_TAB&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-xAVDP5efwL4/TZDGjRNNL5I/AAAAAAAAAX4/k5STilphb-M/s1600/SNAG-0889.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="337" src="http://4.bp.blogspot.com/-xAVDP5efwL4/TZDGjRNNL5I/AAAAAAAAAX4/k5STilphb-M/s400/SNAG-0889.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Creating (uploading) new objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;An "object" is a file, and this is really what the S3 service is all about, storing files. So let's upload a file or two to our new bucket!&lt;br /&gt;&lt;br /&gt;The API lets you upload any BLOB data to S3 using the &lt;b&gt;NEW_OBJECT&lt;/b&gt; procedure.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-LchD6bNLLVU/TZDGj42eCgI/AAAAAAAAAX8/d_y061UBtdk/s1600/SNAG-0890.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="133" src="http://4.bp.blogspot.com/-LchD6bNLLVU/TZDGj42eCgI/AAAAAAAAAX8/d_y061UBtdk/s400/SNAG-0890.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;When you upload a file to S3, the default Access Control List (ACL) makes sure that only the owner of the file (you!) can access (download) it.&lt;br /&gt;&lt;br /&gt;Others get an "Access Denied" message (but see the "Generating download links" section for how to generate special time-limited download links):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-NGSnauX2GMc/TZDGk8BLPZI/AAAAAAAAAYI/Zf-q52FY3C8/s1600/SNAG-0893.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="28" src="http://2.bp.blogspot.com/-NGSnauX2GMc/TZDGk8BLPZI/AAAAAAAAAYI/Zf-q52FY3C8/s400/SNAG-0893.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;There are a number of predefined ACLs that you can specify if, for example, you want to make the file publicly available.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-XhS2v1tmX-o/TZDGkAbSkvI/AAAAAAAAAYA/3gGo-eKbWNI/s1600/SNAG-0891.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="120" src="http://2.bp.blogspot.com/-XhS2v1tmX-o/TZDGkAbSkvI/AAAAAAAAAYA/3gGo-eKbWNI/s400/SNAG-0891.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Which can then be freely downloaded by anyone (the use of HTTPS is optional).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-AhrvxX8wbdY/TZDGkTJKSiI/AAAAAAAAAYE/BkcXcuSwvIo/s1600/SNAG-0892.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="77" src="http://3.bp.blogspot.com/-AhrvxX8wbdY/TZDGkTJKSiI/AAAAAAAAAYE/BkcXcuSwvIo/s400/SNAG-0892.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;A note about "folders":&lt;/b&gt; S3 has no concept of "folders", but you can simulate folders by using a forward slash in your file names (as seen in the previous example). Some S3 clients, such as the AWS management console, will present such files in a folder structure.&amp;nbsp;As far as the PL/SQL API is concerned, the slash is simply part of the file name and has no special meaning.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Listing existing objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now that we have uploaded a couple of files, we can list the contents of the bucket via the &lt;b&gt;GET_OBJECT_LIST&lt;/b&gt; function:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-BAIl_kX4HcQ/TZDGlq9E15I/AAAAAAAAAYU/qUK94A2lPcA/s1600/SNAG-0896.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="190" src="http://3.bp.blogspot.com/-BAIl_kX4HcQ/TZDGlq9E15I/AAAAAAAAAYU/qUK94A2lPcA/s400/SNAG-0896.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;You can also get a list in SQL via a pipelined function named &lt;b&gt;GET_OBJECT_TAB&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-vw7R3FmgFQg/TZDGl4UBtKI/AAAAAAAAAYY/17J9PalZ2NI/s1600/SNAG-0897.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="201" src="http://2.bp.blogspot.com/-vw7R3FmgFQg/TZDGl4UBtKI/AAAAAAAAAYY/17J9PalZ2NI/s400/SNAG-0897.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;In both cases, you can optionally specify a prefix that acts as a search filter for the file names you want to return, and/or the maximum number of items you want to return.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-VhP775LiaVs/TZDGmJUKNwI/AAAAAAAAAYc/4M94UkVHZuo/s1600/SNAG-0898.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="157" src="http://1.bp.blogspot.com/-VhP775LiaVs/TZDGmJUKNwI/AAAAAAAAAYc/4M94UkVHZuo/s400/SNAG-0898.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Generating download links&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can access a file that has been protected by an ACL by including a special checksum parameter in the URL.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;GET_DOWNLOAD_URL&lt;/b&gt; function lets you generate the URL needed to access the file. You can specify when the link should expire, so this means you can share a download link that will stop working after a specified amount of time, which can obviously be useful in a number of scenarios.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-1b2AEAeBwCk/TZDIStgj-qI/AAAAAAAAAYk/gD0rWP5Xfsg/s1600/SNAG-0894.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="76" src="http://3.bp.blogspot.com/-1b2AEAeBwCk/TZDIStgj-qI/AAAAAAAAAYk/gD0rWP5Xfsg/s400/SNAG-0894.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Pasting the generated URL into the browser allows us to access the file:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-tJWCXTXboFg/TZDGlbz3tkI/AAAAAAAAAYQ/OoxYgeCrsHo/s1600/SNAG-0895.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="32" src="http://2.bp.blogspot.com/-tJWCXTXboFg/TZDGlbz3tkI/AAAAAAAAAYQ/OoxYgeCrsHo/s400/SNAG-0895.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Downloading objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Downloading a file from S3 using PL/SQL is straightforward with a call to the &lt;b&gt;GET_OBJECT&lt;/b&gt; function which returns a BLOB:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-N17BgPj28TY/TZDGm61hvXI/AAAAAAAAAYg/zOpqnQERW_Q/s1600/SNAG-0899.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="66" src="http://1.bp.blogspot.com/-N17BgPj28TY/TZDGm61hvXI/AAAAAAAAAYg/zOpqnQERW_Q/s400/SNAG-0899.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Deleting objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Removing a file is likewise very simple, just call the &lt;b&gt;DELETE_OBJECT&lt;/b&gt; procedure:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-hOEJDyoI-ss/TZDGiWoT6uI/AAAAAAAAAXs/FrzBArI-3d4/s1600/SNAG-0900.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="168" src="http://2.bp.blogspot.com/-hOEJDyoI-ss/TZDGiWoT6uI/AAAAAAAAAXs/FrzBArI-3d4/s400/SNAG-0900.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Summary&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The ability to upload and download any file from the Oracle database to "the cloud" (and vice versa) via PL/SQL is extremely useful for a number of purposes.&lt;br /&gt;&lt;br /&gt;Let me know if you find this API useful!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-957846203473525269?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/957846203473525269/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=957846203473525269' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/957846203473525269'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/957846203473525269'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/03/amazon-s3-api-for-plsql.html' title='Amazon S3 API for PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-KHFHkp3EPTk/TZDEu-JQqfI/AAAAAAAAAXc/W0yS4dtpPAc/s72-c/logo_aws.gif' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4882687285415251970</id><published>2011-03-14T01:17:00.000-07:00</published><updated>2011-03-14T01:17:30.420-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='websheets'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>How I hacked the Apex 4 Websheets</title><content type='html'>Or, to be more specific (and less sensationalistic), how I struggled to, and finally succeeded in, using the new Apex 4 &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex/application-express/more-websheets-154798.html"&gt;Websheets&lt;/a&gt; feature in a "&lt;a href="http://download.oracle.com/docs/cd/E17556_01/doc/install.40/e15513/overview.htm#CJAFIGFG"&gt;Runtime-Only&lt;/a&gt;" environment.&lt;br /&gt;&lt;br /&gt;What is an &lt;a href="http://download.oracle.com/docs/cd/E17556_01/doc/user.40/e15517/sec.htm#HTMDB25971"&gt;Apex runtime environment&lt;/a&gt;?&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;"&lt;i&gt;Oracle recommends that you run any sensitive production Oracle Application Express applications with a runtime installation of Oracle Application Express. A runtime installation does not expose the Web-based application development environment, thus preventing the use of Application Builder, SQL Workshop, and related utilities on a production installation. Additionally, a runtime environment only includes the Oracle Application Express database objects and privileges necessary to run applications, making it a more hardened environment.&lt;/i&gt;"&lt;/blockquote&gt;&lt;br /&gt;So, naturally, I set up Apex production environments using the runtime-only installation. But I hit a problem when I tried to deploy a Websheet application to such a production environment.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The setup&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is what I did:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Installed Oracle XE&lt;/li&gt;&lt;li&gt;Installed Apex 4 runtime (apxrtins.sql)&lt;/li&gt;&lt;li&gt;Manually created an application schema (SMALL_APPS) in the database&lt;/li&gt;&lt;li&gt;Manually created the Websheet tables (APEX$ tables) in the SMALL_APPS schema (to do this I had to manually export the table scripts from the Apex development instance using TOAD, but I guess it could also have been done through the SQL Workshop in Apex itself)&lt;/li&gt;&lt;li&gt;Manually created an Apex workspace for the SMALL_APPS schema (via the &lt;a href="http://download.oracle.com/docs/cd/E17556_01/doc/apirefs.40/e15519/apex_instance.htm#CHDGECCF"&gt;APEX_INSTANCE_ADMIN&lt;/a&gt; package)&lt;/li&gt;&lt;li&gt;Exported the Workspace definition from the development instance. The generated workspace script contains the statements to create the workspace users. I removed the part of the script that creates the workspace itself (as I had already done this in the preceding step), and changed the workspace ID to the newly created workspace ID before I ran the script.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;So far, so good.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I then proceeded to import the actual Websheet application.&lt;br /&gt;&lt;br /&gt;This, however, threw up the following error:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;WEBSHEET APPLICATION 112 - WebSheetSandbox&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Set Credentials...&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Check Compatibility...&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;API Last Extended:20100513&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Your Current Version:20100513&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;This import is compatible with version: 20100513&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;COMPATIBLE (You should be able to run this import without issues.)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Set Application ID...&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;...Remove Websheet Application&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;...Create Websheet Application&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;...Create Access Control List&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;...Create Application Authentication Set Up&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;...Create Data Grid&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Rollback&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Error starting at line 163 in command:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;declare&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;&amp;nbsp;q varchar2(32767) := null;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;begin&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;q := null;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;wwv_flow_api.create_ws_worksheet (&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;&amp;nbsp;p_id =&amp;gt; 1311502709921962+wwv_flow_api.g_id_offset,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;&amp;nbsp;p_flow_id =&amp;gt; 4900,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&amp;nbsp;&amp;nbsp;p_page_id =&amp;gt; 2,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;(snip...)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-02291: integrity constraint (APEX_040000.WWV_FLOW_WORKSHEETS_FLOW_FK) violated - parent key not found&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-06512: at "APEX_040000.WWV_FLOW_API", line 14562&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-06512: at line 5&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;02291. 00000 - "integrity constraint (%s.%s) violated - parent key not found"&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;*Cause: &amp;nbsp; &amp;nbsp;A foreign key value has no matching primary key value.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;*Action: &amp;nbsp; Delete the foreign key or add a matching primary key.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;From this it was evident that the data grids in the exported application are linked to application 4900, which is the built-in Websheets application. However, &lt;b&gt;the Apex runtime-only installation does not install application 4900&lt;/b&gt;, hence the integrity error.&lt;br /&gt;&lt;br /&gt;At this point I started to wonder if Websheets are supported in a runtime-only installation of Apex, and I &lt;a href="http://forums.oracle.com/forums/thread.jspa?messageID=9413517"&gt;posted the question in the Apex discussion forum&lt;/a&gt; on OTN.&lt;br /&gt;&lt;br /&gt;But the only answer there was silence, so after a few days I decided to just go ahead and try to install application 4900 in the runtime environment, by running the f4900.sql script (from the apex\builder folder).&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;More problems&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;With application 4900 installed successfully, I was able to install my own Websheets application.&lt;br /&gt;&lt;br /&gt;However, after login I was greeted with the following error message:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-06550: line 9, column 46: PLS-00201: identifier 'WWV_FLOW_F4000_PLUGINS.RENDER_SEARCHBOX' must be declared ORA-06550: line 9, column 1: PL/SQL: Statement ignored&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So, a package is missing. I located the package scripts (in the apex\core folder).&lt;br /&gt;&lt;br /&gt;The comments in the package header (wwv_flow_f4000_plugins.sql) actually state:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;"RUNTIME DEPLOYMENT: YES"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;But evidently the package is not installed by the runtime installation script, &lt;b&gt;so either this is a bug or the comment is wrong&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;So I added the missing package by running:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;alter session set current_schema = APEX_040000;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;@wwv_flow_f4000_plugins.sql&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;@wwv_flow_f4000_plugins.plb&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The final problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This fixed the PLS-00201 error, but now I got several other errors, all similar to the following:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Unable to bind ":WS_APP_ID"&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Unable to bind ":WEBPAGE_ID"&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-20001: run_query error q=select id, title, content, section_type, data_grid_id, report_id, data_section_style, nav_start_webpage_id, nav_max_level, nav_include_link, created_by from apex$_ws_webpg_sections where ws_app_id = :WS_APP_ID and webpage_id = :WEBPAGE_ID order by display_sequence&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;ORA-01003: no statement parsed&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Still no feedback in the discussion forum, so I decided to dig deeper into the Apex internals (an interesting exercise in itself).&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A solution!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In the end, the solution turned out to be simple. The WWV_FLOW_COMPANY_SCHEMAS table contains the workspace to schema mappings. This table contains a column called IS_APEX$_SCHEMA, and this needs to be set to "Y" (the APEX_INSTANCE_ADMIN.ADD_WORKSPACE procedure leaves the column value as NULL).&lt;br /&gt;&lt;br /&gt;So just update the column to enable Websheets:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;update wwv_flow_company_schemas&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;set is_apex$_schema = 'Y'&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;where schema = 'SMALL_APPS';&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Voila! I now have a working Websheet application in my runtime-only Apex environment.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Postscript&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;While I was typing up this for the blog post, I stumbled across the following statement in the &lt;a href="http://download.oracle.com/docs/cd/E17556_01/doc/admin.40/e15521/adm_mg_service_set.htm#AEADM195"&gt;Apex Administration Guide&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"Tip: Websheets are not supported in an Oracle Application Express runtime environment."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;I wish somebody could have pointed that out to me in the discussion forum thread. But then again, if they did, I probably wouldn't have discovered how to make it work anyway.&lt;br /&gt;&lt;br /&gt;And if anyone from Oracle is reading this, consider this an enhancement request for Apex 4.1: Support Websheets in a runtime-only environment by:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Including application 4900 in the runtime installation&lt;/li&gt;&lt;li&gt;Including the wwv_flow_f4000_plugins package in the runtime installation&lt;/li&gt;&lt;li&gt;Add a parameter to the ADD_WORKSPACE and ADD_SCHEMA procedures to specify whether websheets should be enabled or not&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4882687285415251970?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4882687285415251970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4882687285415251970' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4882687285415251970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4882687285415251970'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/03/how-i-hacked-apex-4-websheets.html' title='How I hacked the Apex 4 Websheets'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-6328050699702092196</id><published>2011-03-01T00:48:00.000-08:00</published><updated>2011-03-01T00:51:15.946-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stress test'/><category scheme='http://www.blogger.com/atom/ns#' term='XE'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><title type='text'>Stress Testing Oracle XE 10g</title><content type='html'>Oracle Express Edition (XE) is Oracle's free entry-level database product, currently available only in a 10g version. XE is usually pitched as suitable for personal work or (very) small departmental applications, but I was curious as to what kind of load it can support.&lt;br /&gt;&lt;br /&gt;Oracle XE 10g has the following &lt;a href="http://www.oracle.com/technetwork/licenses/xe-license-152020.html"&gt;limitations&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Up to 1 instance per server&lt;/li&gt;&lt;li&gt;Up to 1 CPU (will not use more even if available)&lt;/li&gt;&lt;li&gt;Up to 1 GB RAM (will not use more even if available)&lt;/li&gt;&lt;li&gt;Up to 4 GB datafiles (not including XE system data)&lt;/li&gt;&lt;li&gt;Free to develop, distribute and deploy in production&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;By the way, &lt;a href="http://bradleydbrown.blogspot.com/2010/09/oow-summary.html"&gt;several&lt;/a&gt; &lt;a href="http://blog.sydoracle.com/2011/01/oracle-xe-screenshot-tweeted.html"&gt;things&lt;/a&gt; seem to indicate that Oracle Express Edition (XE) 11g is just around the corner. It's rumored that XE 11g will raise the datafile limit from 4 GB to 11 GB, but the other limits will remain as far as I know (and I don't really know anything about it...!). So these performance tests will probably be representative both for XE 10g and 11g, although I can't know for sure until 11g is available.&lt;br /&gt;&lt;br /&gt;Here is the test environment I set up:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Hardware&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note that the "hardware" in this case actually runs virtualized in a VMWare environment.&lt;br /&gt;Here are the resources allocated to the server:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;1 Intel Xeon X7350 2,9 GHz CPU&lt;/li&gt;&lt;li&gt;4 GB RAM (but remember that XE won't use more than 1 GB anyway)&lt;/li&gt;&lt;li&gt;30 GB disk space&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Software&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The software setup was as follows:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Windows Server 2003 R2 Standard Edition Service Pack 2&lt;/li&gt;&lt;li&gt;Microsoft Internet Information Server (IIS) 6.0&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Oracle Express Edition (XE) 10g&lt;/li&gt;&lt;li&gt;Oracle Application Express (Apex) 4.0&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Note that I am &lt;b&gt;not&lt;/b&gt; using the Embedded PL/SQL Gateway (DBMS_EPG) as the web server, but rather IIS in combination with the Thoth Gateway, a free ASP.NET replacement for mod_plsql and DBMS_EPG that I wrote in C# using ODP.NET.&lt;br /&gt;&lt;br /&gt;Everything (database, web server, Apex) was installed using default settings.&lt;br /&gt;The Apex images folder (/i/) was set up with "Expires"-headers (7 days) to allow browsers to cache images.&lt;br /&gt;&lt;br /&gt;Both the database and the web server run on the same machine (server), in order to keep the setup as simple as possible (and show what is possible with just one box).&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Test page&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I set up an Apex test page with a mix of regions, including a report region, a couple of PL/SQL regions and an HTML region. For each page view, a random number is generated (to simulate different users looking at different things). The report query selects up to 200 rows based on this random number (using bind variables, of course), out of a system table/view (DBA_OBJECTS) with around 17,000 total rows.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-a5Ck6g3JNOc/TWyvsNHv0mI/AAAAAAAAAXE/t1Sq_8ed8rQ/s1600/SNAG-0869.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="https://lh4.googleusercontent.com/-a5Ck6g3JNOc/TWyvsNHv0mI/AAAAAAAAAXE/t1Sq_8ed8rQ/s400/SNAG-0869.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;There is also an after footer page process that inserts a row in a log table, so this is not just a read-only page.&lt;br /&gt;&lt;br /&gt;The application was set up with an authorization scheme of "No application authorization scheme required" to allow it to be accessed from the online stress test tool.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Stress test tool&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.loadimpact.com/"&gt;LoadImpact&lt;/a&gt; is a user-friendly, web-based stress testing service that allows you to run free tests that simulate up to 50 concurrent clients (you can pay to test with more clients). A key point here is that the load is actually generated from their test servers on the public internet, so this gives a more realistic test than simply running a stress testing tool on your local network.&lt;br /&gt;&lt;br /&gt;You don't even need to sign up, just enter the URL of a website to test it (apparently, there are safeguards to prevent this from being used as a denial-of-service attack tool).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A note regarding image caching:&lt;/b&gt; The LoadImpact service &lt;a href="http://loadimpact.com/forum/viewtopic.php?id=158"&gt;does not cache anything&lt;/a&gt;. This means that all the Apex images, Javascript and stylesheets will be loaded on every page hit, even though in reality they would be cached in the user's browser after the first page view. Since I wanted to test the database's ability to handle page views, rather than IIS's ability to serve up static files, I changed the Apex page so that the standard Javascript and CSS files were not included. I then manually added the core stylesheets (core Apex + theme) back on the page via the page template, just to preserve the look and layout of the page. But even these could have been left out in order to simulate a scenario where most users have the files cached in their browser.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Test results&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The free test at LoadImpact runs five different subtests, starting with 10 concurrent clients and ending with 50 concurrent clients. The full test takes 10 minutes, so that is in five 2-minute parts with increasing load.&lt;br /&gt;&lt;br /&gt;While this was going on, I watched the CPU usage in the Windows Task Manager and grabbed the following screenshots:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-0xBp4hFwBYc/TWywSwsjkeI/AAAAAAAAAXI/rgilSzlAw_U/s1600/SNAG-0862.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="https://lh3.googleusercontent.com/-0xBp4hFwBYc/TWywSwsjkeI/AAAAAAAAAXI/rgilSzlAw_U/s400/SNAG-0862.jpg" width="353" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-8i2TE3fIoXI/TWywZmzNYFI/AAAAAAAAAXM/IMpB5jfVypc/s1600/SNAG-0863.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="https://lh4.googleusercontent.com/-8i2TE3fIoXI/TWywZmzNYFI/AAAAAAAAAXM/IMpB5jfVypc/s400/SNAG-0863.jpg" width="353" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-no_76PVxKtM/TWyweZl31SI/AAAAAAAAAXQ/BavC7nmUcMI/s1600/SNAG-0864.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="https://lh5.googleusercontent.com/-no_76PVxKtM/TWyweZl31SI/AAAAAAAAAXQ/BavC7nmUcMI/s400/SNAG-0864.jpg" width="352" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;While there are some peaks at the start of each subtest, it seems like the database "warms up" and actually does less work at the middle and end of the subtest compared to the start. Even at 40 and 50 clients, the CPU is (on average) not working at more than half capacity.&lt;br /&gt;&lt;br /&gt;Here is the summary from LoadImpact at the end of the test:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-bY1hzOkaUYg/TWywoTJt-KI/AAAAAAAAAXU/ntGpZRuxAPo/s1600/SNAG-0868.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="https://lh3.googleusercontent.com/-bY1hzOkaUYg/TWywoTJt-KI/AAAAAAAAAXU/ntGpZRuxAPo/s400/SNAG-0868.jpg" width="346" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;First of all, note the nice and almost flat curve, with response times of under 300 ms even with 50 clients (and remember that the load generator is actually located in a different country, accessing the Apex page via the public Internet).&lt;br /&gt;&lt;br /&gt;Actually, while the test was running, I was also clicking through a separate Apex application (an actual application, not just a test page) on the same server, and I couldn't really notice that the server was under any kind of stress; the response times were excellent.&lt;br /&gt;&lt;br /&gt;The total number of requests for the whole test was 4500, but these requests include not only the actual page (the call to the "f" procedure) but also 2 stylesheets and 3 images. So the database was hit 1 in 6 times, in other words 16,7% (of 4500) or 750 times. Each hit inserted a row into a log table. If we query that log table and group by down to the second, we can get the number of database requests per second:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-HT3VJFC--v4/TWyw9Tkq-rI/AAAAAAAAAXY/FG2Xms3JA6E/s1600/SNAG-0866.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="323" src="https://lh4.googleusercontent.com/-HT3VJFC--v4/TWyw9Tkq-rI/AAAAAAAAAXY/FG2Xms3JA6E/s400/SNAG-0866.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This shows that the XE database was handling 10 or 11 page views per second.&amp;nbsp;If this can be sustained over time, that is 36,000 page views per hour, or 864,000 page views per day.&lt;br /&gt;&lt;br /&gt;By comparison, apex.oracle.com &lt;a href="http://joelkallman.blogspot.com/2011/01/is-anyone-using-oracle-application.html"&gt;is reported to have&lt;/a&gt; 4,800,000 page views per week or 685,000 page views per day. And &lt;a href="http://news.ycombinator.com/item?id=1549987"&gt;according to this&lt;/a&gt;, Reddit has 10 pageviews per second per server (with 80 servers), while Facebook has just 2 pageviews per second per server (with 30,000 servers!).&lt;br /&gt;&lt;br /&gt;I won't read too much into these numbers (as obviously a "page view" can vary wildly between different websites, and only actual use can tell whether your site is performing well or not), but clearly the free XE database is more than a toy.&lt;br /&gt;&lt;br /&gt;I'm even tempted to buy a Basic or Professional test at LoadImpact to test with more than 50 clients and see where that curve leads with 60, 80 or 100 concurrent clients...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-6328050699702092196?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/6328050699702092196/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=6328050699702092196' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6328050699702092196'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6328050699702092196'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/03/stress-testing-oracle-xe-10g.html' title='Stress Testing Oracle XE 10g'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-a5Ck6g3JNOc/TWyvsNHv0mI/AAAAAAAAAXE/t1Sq_8ed8rQ/s72-c/SNAG-0869.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7402919080273650950</id><published>2011-02-24T08:03:00.000-08:00</published><updated>2011-02-24T08:23:00.043-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Apex listener'/><category scheme='http://www.blogger.com/atom/ns#' term='Resource Templates'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Apex Listener Resource Templates</title><content type='html'>An &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex-listener/ea1-download-listener-177114.html"&gt;early adopter version 1.1 of the Apex Listener&lt;/a&gt; was released a while back.&lt;br /&gt;&lt;br /&gt;Along with the software download there is a &lt;a href="http://www.oracle.com/technetwork/developer-tools/apex-listener/documentation/apexlistenerdevguide1-1ea-177511.pdf"&gt;developer's guide&lt;/a&gt; that describes a new feature of the listener, called &lt;i&gt;Resource Templates&lt;/i&gt;. From the guide:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Oracle Application Express Listener enables data stored in an Oracle Database to be exposed via&amp;nbsp;RESTful Application Programming Interfaces (APIs).&lt;/blockquote&gt;The guide continues:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;RESTful APIs are created by configuring 'Resource Templates'. A Resource Template is a&amp;nbsp;configuration file that binds a set of URIs to a SQL query or anonymous PL/SQL block. The set of&amp;nbsp;URIs is identified by a 'URI Template', a simple syntax for describing URIs e.g. people/{userid}. The URI Template may contain zero or more parameters ( e.g. {userid}) which&amp;nbsp;along with the HTTP Headers sent with a HTTP request can be bound to parameters in the SQL&amp;nbsp;query or anonymous PL/SQL block.&amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;This is a very interesting feature that can be used outside of the Apex framework (ie in a barebones PL/SQL web application). Personally, I would prefer to be able to configure the resource templates somewhere in the database (in a special table and/or a predefined PL/SQL API), rather than messing around with the listener configuration, but the concept itself is a good one.&lt;br /&gt;&lt;br /&gt;Anyway, it's an interesting read, so do take a look if you haven't already done so.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7402919080273650950?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7402919080273650950/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7402919080273650950' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7402919080273650950'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7402919080273650950'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/apex-listener-resource-templates.html' title='Apex Listener Resource Templates'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1968219836477219691</id><published>2011-02-20T22:54:00.000-08:00</published><updated>2011-02-20T22:54:17.193-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='Microsoft Office'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='OOXML'/><title type='text'>Working with Office 2007 (OOXML) files using PL/SQL</title><content type='html'>Starting with Office 2007, Microsoft switched to an XML-based format called &lt;a href="http://en.wikipedia.org/wiki/Office_Open_XML"&gt;Office Open XML&lt;/a&gt; (OOXML).&lt;br /&gt;&lt;br /&gt;There has been some debate as to how "open" this format really is, given that the specs run to around 7,000 pages (!).&lt;br /&gt;&lt;br /&gt;Be that as it may, it's a fact of life that a lot of people use Microsoft's Office suite, and that means we have to deal with this new format in a lot of situations.&lt;br /&gt;&lt;br /&gt;The OOXML format is, as it turns out, not so difficult to deal with. The main concept is that an Office document, whether it is a Word document (.docx), Excel spreadsheet (.xlsx) or Powerpoint presentation (.pptx), is actually a compressed (.zip) file that contains a number of XML documents (as well as any image files the user has included in the document).&lt;br /&gt;&lt;br /&gt;So to work with OOXML files, we need to be able to zip and unzip files, and to parse and generate XML. Oracle (and PL/SQL) has had good support for XML for a number of years, but (even though there is a UTL_COMPRESS package in the database) there is no built-in zip/unzip support. Of course you could load some Java classes into the database to do it, but dealing with the Java stuff is always a bit of a hassle. But some time ago the good gentleman &lt;a href="http://technology.amis.nl/blog/author/anton"&gt;Anton Scheffer&lt;/a&gt; published a &lt;a href="http://technology.amis.nl/blog/7626/utl_compress-gzip-and-zlib"&gt;PL/SQL implementation&lt;/a&gt; based on UTL_COMPRESS that supports zipping and unzipping.&lt;br /&gt;&lt;br /&gt;Based on this I have written a package for working with OOXML documents. It's called &lt;b&gt;OOXML_UTIL_PKG&lt;/b&gt; and you can download it as part of (you guessed it) the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria utility library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Let's see what this package allows us to do.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Get document properties from a Word (docx) file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First we fire up Word and create a test document:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-uCFewklaJPE/TWII6lu6-5I/AAAAAAAAAWc/zNCAuO2K18I/s1600/SNAG-0839.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="322" src="http://1.bp.blogspot.com/-uCFewklaJPE/TWII6lu6-5I/AAAAAAAAAWc/zNCAuO2K18I/s400/SNAG-0839.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;By the way, you can read and write the new OOXML formats using an older version of Office (as I do in the screenshot above), by downloading the &lt;a href="http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&amp;amp;FamilyID=941B3470-3AE9-4AEE-8F43-C6BB74CD1466"&gt;Microsoft Office Compatibility Pack&lt;/a&gt;&amp;nbsp;from Microsoft.&lt;br /&gt;&lt;br /&gt;After saving the document, we can then extract the document properties using the &lt;b&gt;GET_DOCX_PROPERTIES&lt;/b&gt; function, which returns a custom record type called T_DOCX_PROPERTIES.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-_XrTf48a7Ts/TWIJKfA8n3I/AAAAAAAAAWg/pIQLjRdIlXY/s1600/SNAG-0840.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="212" src="http://3.bp.blogspot.com/-_XrTf48a7Ts/TWIJKfA8n3I/AAAAAAAAAWg/pIQLjRdIlXY/s400/SNAG-0840.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Extract plain text from a Word (docx) file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Using our test document again, we can extract the plain text of the document using the &lt;b&gt;GET_DOCX_PLAINTEXT&lt;/b&gt; function, which returns a CLOB.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-RoasnUaAKmc/TWIJU0Jh4kI/AAAAAAAAAWk/u5Kr8b0DZb0/s1600/SNAG-0841.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="370" src="http://2.bp.blogspot.com/-RoasnUaAKmc/TWIJU0Jh4kI/AAAAAAAAAWk/u5Kr8b0DZb0/s400/SNAG-0841.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;This is of course very useful if you want to search and/or index (just) the text of a document, or otherwise work with the content.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Get document properties from an Excel (xlsx) file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's first create an Excel test file (again, using Excel 2003 but saving in Excel 2007 format):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-kp8RbrFlQd0/TWIJvL6_iPI/AAAAAAAAAWo/PEntgY54NIo/s1600/SNAG-0842.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="356" src="http://4.bp.blogspot.com/-kp8RbrFlQd0/TWIJvL6_iPI/AAAAAAAAAWo/PEntgY54NIo/s400/SNAG-0842.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;(This has to be one of the lamest spreadsheets of all time, but it will do fine as an example. It has some text, some numbers, and a formula.)&lt;br /&gt;&lt;br /&gt;Similar to the Word document, we can now use the &lt;b&gt;GET_XLSX_PROPERTIES&lt;/b&gt; function, which returns a custom record type called T_XLSX_PROPERTIES.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-3zs5tieoFEs/TWIJ_ql4pRI/AAAAAAAAAWs/pY0WIzaYHtg/s1600/SNAG-0843.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="158" src="http://2.bp.blogspot.com/-3zs5tieoFEs/TWIJ_ql4pRI/AAAAAAAAAWs/pY0WIzaYHtg/s400/SNAG-0843.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;You'll notice that Word documents and Excel spreadsheets have slightly different properties.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Extract a cell value from an Excel (xlsx) file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;GET_XLSX_CELL_VALUE&lt;/b&gt; function allows us to retrieve a single value from a named cell in a specific worksheet, like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-p2ux7NGTxSE/TWIKdJCgHPI/AAAAAAAAAWw/TyBBrNRwrlc/s1600/SNAG-0844.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="165" src="http://4.bp.blogspot.com/-p2ux7NGTxSE/TWIKdJCgHPI/AAAAAAAAAWw/TyBBrNRwrlc/s400/SNAG-0844.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;i&gt;Technical Detail:&lt;/i&gt; In the XLSX format, strings (as opposed to numbers) are not stored in the actual cell where they are entered, but rather in a "shared strings" section. The cell just contains a reference back to this "shared strings" section. The GET_XLSX_CELL_VALUE function handles this for you, so you don't have to worry about that.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Extract multiple cell values from an Excel (xlsx) file&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since the function that extracts a single value from a spreadsheet must open the file, unzip it, and parse the XML content every time you call that function, there is another function (&lt;b&gt;GET_XLSX_CELL_VALUES&lt;/b&gt;, notice the plural) that allows you to retrieve multiple values in one call. In other words, the file is unzipped and the contents parsed as XML just once, which is obviously more efficient.&lt;br /&gt;&lt;br /&gt;Simply specify the names of multiple cells using an array of strings:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-EiNtmiuoAfw/TWILfnNELwI/AAAAAAAAAW0/ECqC5el-MhU/s1600/SNAG-0845.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="201" src="http://2.bp.blogspot.com/-EiNtmiuoAfw/TWILfnNELwI/AAAAAAAAAW0/ECqC5el-MhU/s400/SNAG-0845.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Write contents into OOXML file using PL/SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since the contents of OOXML files are XML files, you can manipulate the existing content, or generate new content, and then save it back to the zip file that contains your document.&lt;br /&gt;&lt;br /&gt;The following demonstrates one approach; it uses a Powerpoint file, but &lt;i&gt;this technique will also work with Word and Excel files&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;We create a Powerpoint 2007 file (.pptx) and put in some tags that we want to replace via code. In other words, this becomes a template that we can fill with dynamic values from the database.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-k_wfkVr4fWw/TWILtZGte7I/AAAAAAAAAW4/W2sMcqfipKg/s1600/SNAG-0846.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="256" src="http://1.bp.blogspot.com/-k_wfkVr4fWw/TWILtZGte7I/AAAAAAAAAW4/W2sMcqfipKg/s400/SNAG-0846.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;GET_FILE_FROM_TEMPLATE&lt;/b&gt; function takes a template file as input, and two string arrays: The tag names and actual values to replace the tags with. It unzips the file, performs the substitutions, writes back the file to the zip archive, and returns the file, which you can then save back to disk (or, more likely, store in the database or send to a web browser).&lt;br /&gt;&lt;br /&gt;The code is trivial:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-LckGJITnd8s/TWILzqlgy2I/AAAAAAAAAW8/l0NXKoG6ISc/s1600/SNAG-0847.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="216" src="http://3.bp.blogspot.com/-LckGJITnd8s/TWILzqlgy2I/AAAAAAAAAW8/l0NXKoG6ISc/s400/SNAG-0847.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Here is the result when opening the output file:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-VO5SMHJMAhs/TWIL5Idl21I/AAAAAAAAAXA/ua-d-jV8g3k/s1600/SNAG-0848.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="256" src="http://2.bp.blogspot.com/-VO5SMHJMAhs/TWIL5Idl21I/AAAAAAAAAXA/ua-d-jV8g3k/s400/SNAG-0848.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;So the next time you do a presentation, you could actually update your Powerpoint slides with the latest sales figures (or whatever) from within SQL*Plus...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Working with Office 2007 (OOXML) files from PL/SQL is easy and opens up many possibilities, both for extracting information from documents and storing them in the database, as well as generating or modifying OOXML files in the database server.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1968219836477219691?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1968219836477219691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1968219836477219691' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1968219836477219691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1968219836477219691'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/working-with-office-2007-ooxml-files.html' title='Working with Office 2007 (OOXML) files using PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-uCFewklaJPE/TWII6lu6-5I/AAAAAAAAAWc/zNCAuO2K18I/s72-c/SNAG-0839.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4802307498441443533</id><published>2011-02-14T07:26:00.000-08:00</published><updated>2011-02-14T07:26:48.247-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>Generating test data using PL/SQL</title><content type='html'>Here's a fun little PL/SQL package, part of the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library for PL/SQL&lt;/a&gt;, that you can use to generate semi-random test data.&lt;br /&gt;&lt;br /&gt;The package is called &lt;b&gt;RANDOM_UTIL_PKG&lt;/b&gt;. At the time of writing it contains the following functions:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;GET_AMOUNT&lt;/li&gt;&lt;li&gt;GET_BUSINESS_CONCEPT&lt;/li&gt;&lt;li&gt;GET_BUZZWORD&lt;/li&gt;&lt;li&gt;GET_DATE&lt;/li&gt;&lt;li&gt;GET_EMAIL_ADDRESS&lt;/li&gt;&lt;li&gt;GET_ERROR_MESSAGE&lt;/li&gt;&lt;li&gt;GET_FILE_NAME&lt;/li&gt;&lt;li&gt;GET_FILE_TYPE&lt;/li&gt;&lt;li&gt;GET_INTEGER&lt;/li&gt;&lt;li&gt;GET_MIME_TYPE&lt;/li&gt;&lt;li&gt;GET_PASSWORD&lt;/li&gt;&lt;li&gt;GET_PERSON_NAME&lt;/li&gt;&lt;li&gt;GET_TEXT&lt;/li&gt;&lt;li&gt;GET_VALUE&lt;/li&gt;&lt;li&gt;GET_WAIT_MESSAGE&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;These should all be fairly self-explanatory, but let's look at some examples of how you could use these to fill your database with "realistic" test data.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A list of users&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-C27G4X269tk/TVlI63o3igI/AAAAAAAAAWA/ArwNCEJVjLE/s1600/SNAG-0832.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="222" src="http://3.bp.blogspot.com/-C27G4X269tk/TVlI63o3igI/AAAAAAAAAWA/ArwNCEJVjLE/s400/SNAG-0832.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;An email archive&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-QH1ScOz51zU/TVlJAvglmpI/AAAAAAAAAWE/1gMZ7IaNjDQ/s1600/SNAG-0833.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="185" src="http://3.bp.blogspot.com/-QH1ScOz51zU/TVlJAvglmpI/AAAAAAAAAWE/1gMZ7IaNjDQ/s400/SNAG-0833.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A list of orders (and order items)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-BEdcZ84f60U/TVlJIbXPPTI/AAAAAAAAAWI/Tu2BQSEIFsw/s1600/SNAG-0834.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="201" src="http://2.bp.blogspot.com/-BEdcZ84f60U/TVlJIbXPPTI/AAAAAAAAAWI/Tu2BQSEIFsw/s400/SNAG-0834.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-64RMANqfukY/TVlJL3MwXuI/AAAAAAAAAWM/Tx1P5NqcWq0/s1600/SNAG-0835.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="242" src="http://3.bp.blogspot.com/-64RMANqfukY/TVlJL3MwXuI/AAAAAAAAAWM/Tx1P5NqcWq0/s400/SNAG-0835.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A list of uploaded files (or files in a folder)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-R6Fc5kYna_Q/TVlJRDm1hVI/AAAAAAAAAWQ/9tcOs_nUZYI/s1600/SNAG-0836.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="241" src="http://3.bp.blogspot.com/-R6Fc5kYna_Q/TVlJRDm1hVI/AAAAAAAAAWQ/9tcOs_nUZYI/s400/SNAG-0836.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A list of errors (an error log)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-QA0cbm1PW84/TVlJVA-jtYI/AAAAAAAAAWU/qFdwEYXxo3Y/s1600/SNAG-0837.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="383" src="http://1.bp.blogspot.com/-QA0cbm1PW84/TVlJVA-jtYI/AAAAAAAAAWU/qFdwEYXxo3Y/s400/SNAG-0837.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A list of business concepts or strategies&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is for those times when a struggling company needs to re-focus its strategy and define a new vision. No need to hire expensive management consultants, just fire up SQL*Plus and execute the following query:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-dG9rFkZwwbY/TVlJZnZTAZI/AAAAAAAAAWY/jHtqe0TpZmI/s1600/SNAG-0838.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="341" src="http://3.bp.blogspot.com/-dG9rFkZwwbY/TVlJZnZTAZI/AAAAAAAAAWY/jHtqe0TpZmI/s400/SNAG-0838.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Here are some of my favorites from the concepts I generated just now:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;Quickly innovate sticky products with resource-maximizing portals.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Credibly plagiarize robust opportunities using timely architecture.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Dynamically reinvent dynamic leadership for end-to-end web services.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Appropriately empower front-end human capital via error-free web services.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Globally plagiarize stand-alone ideas after next-generation alignment.&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4802307498441443533?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4802307498441443533/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4802307498441443533' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4802307498441443533'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4802307498441443533'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/generating-test-data-using-plsql.html' title='Generating test data using PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-C27G4X269tk/TVlI63o3igI/AAAAAAAAAWA/ArwNCEJVjLE/s72-c/SNAG-0832.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-5903713606180026777</id><published>2011-02-09T14:28:00.000-08:00</published><updated>2011-02-09T14:28:25.949-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='rss'/><title type='text'>Fun with RSS and PL/SQL, Part Two</title><content type='html'>In my previous post, I talked about &lt;a href="http://ora-00001.blogspot.com/2011/02/fun-with-rss-and-plsql-part-one.html"&gt;how to read (parse) RSS feeds using PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This post will cover how to publish your own RSS feeds, based on a SQL query.&lt;br /&gt;&lt;br /&gt;You need the &lt;b&gt;RSS_UTIL_PKG&lt;/b&gt; from the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Publishing an RSS feed using PL/SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The RSS_UTIL_PKG package contains the function &lt;b&gt;REF_CURSOR_TO_FEED&lt;/b&gt;, which takes a REF CURSOR parameter and returns a CLOB with the RSS feed (the actual format can be specified and includes RSS, Atom and RDF).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TVMT-nEd95I/AAAAAAAAAVk/Eiex1i8IxzY/s1600/SNAG-0825.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/_dBAKxejXABM/TVMT-nEd95I/AAAAAAAAAVk/Eiex1i8IxzY/s400/SNAG-0825.jpg" width="382" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Note that any query (REF CURSOR) can be used, and the column names in your query are irrelevant. What you must do is make sure that the number and order of columns (as well as the data types) match that of the T_FEED_ITEM record type defined in the RSS_UTIL_PKG package specification.&lt;br /&gt;&lt;br /&gt;In other words, just make sure your query includes the following information in this order: ID, title, description, link and date.&lt;br /&gt;&lt;br /&gt;Once you have the RSS feed as a CLOB, you would use the PL/SQL Web Toolkit (OWA) to print out the contents on a web page. You should also set the MIME type to XML, as in the following example:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TVMUJJSfWiI/AAAAAAAAAVo/RQNX6p99Yhs/s1600/SNAG-0826.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="232" src="http://1.bp.blogspot.com/_dBAKxejXABM/TVMUJJSfWiI/AAAAAAAAAVo/RQNX6p99Yhs/s400/SNAG-0826.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Example: Creating a live feed of database errors&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's put the RSS_UTIL_PKG to good use with an example. Let's say you are part of a development team, all working on various pieces of PL/SQL code in a shared development or test database.&lt;br /&gt;&lt;br /&gt;In this situation it would be useful to see the current status of any invalid objects or PL/SQL compilation errors in the database.&lt;br /&gt;&lt;br /&gt;Using an RSS feed, we can create a &lt;a href="http://www.mozilla.com/en-US/firefox/livebookmarks.html"&gt;"live bookmark"&lt;/a&gt; in Firefox that shows us the status information in a convenient manner, without leaving the web browser.&lt;br /&gt;&lt;br /&gt;We will do the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create a query that lists PL/SQL compilation errors, based on the USER_ERRORS view, and return the results as a REF CURSOR.&lt;/li&gt;&lt;li&gt;Convert the REF CURSOR to an RSS feed, and output it using the PL/SQL Web Toolkit.&lt;/li&gt;&lt;li&gt;Set up a live bookmark in Firefox based on the feed procedure.&lt;/li&gt;&lt;li&gt;Create a details page to see details of each error (RSS feed item) when it is clicked.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Starting with the query, we wrap it in a function that returns a REF CURSOR:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUYFsRmxI/AAAAAAAAAVs/BAywIE7ZnhI/s1600/SNAG-0827.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="340" src="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUYFsRmxI/AAAAAAAAAVs/BAywIE7ZnhI/s400/SNAG-0827.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;We then create a procedure to format the REF CURSOR as RSS, and output it:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TVMUJJSfWiI/AAAAAAAAAVo/RQNX6p99Yhs/s1600/SNAG-0826.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="232" src="http://1.bp.blogspot.com/_dBAKxejXABM/TVMUJJSfWiI/AAAAAAAAAVo/RQNX6p99Yhs/s400/SNAG-0826.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Then start Firefox and navigate to the procedure. This brings up Firefox's built-in RSS handler. Select "Live Bookmark" and subcribe to the feed.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TVMUiSaip0I/AAAAAAAAAVw/M9hqtXzQDt4/s1600/SNAG-0789.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/_dBAKxejXABM/TVMUiSaip0I/AAAAAAAAAVw/M9hqtXzQDt4/s400/SNAG-0789.jpg" width="306" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Whenever you want to check for errors, just click the appropriate Live Bookmark folder in the Firefox toolbar, and the feed items will be displayed in a sub-menu:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUn2ORHzI/AAAAAAAAAV0/yrEtyM2YuzQ/s1600/SNAG-0791.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="298" src="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUn2ORHzI/AAAAAAAAAV0/yrEtyM2YuzQ/s400/SNAG-0791.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;If you click on one of the items, it will take you to the details page, which we have coded to retrieve the details of the error:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TVMU5tNPvyI/AAAAAAAAAV8/qmzd31uRpko/s1600/SNAG-0828.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/_dBAKxejXABM/TVMU5tNPvyI/AAAAAAAAAV8/qmzd31uRpko/s400/SNAG-0828.jpg" width="268" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This is what the details page looks like in the browser:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUwok_fHI/AAAAAAAAAV4/e6ldULGbuCY/s1600/SNAG-0790.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="350" src="http://4.bp.blogspot.com/_dBAKxejXABM/TVMUwok_fHI/AAAAAAAAAV4/e6ldULGbuCY/s400/SNAG-0790.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We have seen that it is really easy to create your own RSS feed (all you have to do is write the query), and perhaps the example has given you some ideas and shown that RSS feeds can be used for more than news articles from online newspapers.&lt;br /&gt;&lt;br /&gt;Note that if you have downloaded the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library for PL/SQL&lt;/a&gt;, you will find the code for the PLSQL_STATUS_WEB_PKG in the "demos" folder.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-5903713606180026777?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/5903713606180026777/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=5903713606180026777' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5903713606180026777'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5903713606180026777'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/fun-with-rss-and-plsql-part-two.html' title='Fun with RSS and PL/SQL, Part Two'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_dBAKxejXABM/TVMT-nEd95I/AAAAAAAAAVk/Eiex1i8IxzY/s72-c/SNAG-0825.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-820896359852139050</id><published>2011-02-07T08:50:00.000-08:00</published><updated>2011-02-07T08:50:19.974-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='rss'/><title type='text'>Fun with RSS and PL/SQL, Part One</title><content type='html'>If you follow one or more blogs, you have probably heard about &lt;a href="http://en.wikipedia.org/wiki/RSS"&gt;RSS&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TVAh_vtzjWI/AAAAAAAAAVY/CEtuPw-FBCA/s1600/200px-Feed-icon.svg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_dBAKxejXABM/TVAh_vtzjWI/AAAAAAAAAVY/CEtuPw-FBCA/s1600/200px-Feed-icon.svg.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Several varieties of the RSS format exist, so I will refer to them collectively as "feeds" (and when I say "RSS" I really mean any kind of feed).&lt;br /&gt;&lt;br /&gt;RSS can be used to publish not just blogs, but also other kinds of information, such as news updates, audit trails, log entries, and so on.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library for PL/SQL&lt;/a&gt; contains &lt;b&gt;RSS_UTIL_PKG&lt;/b&gt;, a package for publishing and parsing RSS feeds.&lt;br /&gt;&lt;br /&gt;In this blog post I will describe how we can use this package to read (parse) RSS feeds. A follow-up post will describe how the same package can be used to publish any query as a feed.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Reading an RSS feed with SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The RSS_UTIL_PKG package contains a pipelined function named &lt;b&gt;RSS_TO_TABLE&lt;/b&gt; which can be used in a SQL query to extract feed values.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TVAhWgPDcLI/AAAAAAAAAVQ/PcAr7ltDtfk/s1600/SNAG-0814.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="230" src="http://3.bp.blogspot.com/_dBAKxejXABM/TVAhWgPDcLI/AAAAAAAAAVQ/PcAr7ltDtfk/s400/SNAG-0814.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;A single line to read an RSS feed, that's not too shabby, eh?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Reading an RSS feed with PL/SQL&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Another function in the RSS_UTIL_PKG is &lt;b&gt;RSS_TO_LIST&lt;/b&gt;, which returns a PL/SQL associative array.&lt;br /&gt;&lt;br /&gt;After the feed has been parsed to a list, you can loop through it and perform any required processing.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TVAh5cySX3I/AAAAAAAAAVU/oFIG1kyHRlQ/s1600/SNAG-0815.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="233" src="http://1.bp.blogspot.com/_dBAKxejXABM/TVAh5cySX3I/AAAAAAAAAVU/oFIG1kyHRlQ/s400/SNAG-0815.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Of course you could also use the pipelined function in a cursor FOR loop, depending on your coding style.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Creating a report in Apex based on an RSS feed&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since you can use RSS_TO_TABLE in SQL statements, you can also use it as the basis for a report region in Apex.&lt;br /&gt;&lt;br /&gt;Create a page and add a text item to input the URL of an RSS feed.&lt;br /&gt;&lt;br /&gt;Then add a report region (classic or interactive) and use the following query as the report source:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TVAigIF0X2I/AAAAAAAAAVc/--ZRQPc3FF0/s1600/SNAG-0795.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="386" src="http://1.bp.blogspot.com/_dBAKxejXABM/TVAigIF0X2I/AAAAAAAAAVc/--ZRQPc3FF0/s400/SNAG-0795.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Running the page produces the following result:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_dBAKxejXABM/TVAilqEeoRI/AAAAAAAAAVg/o7nKm58p9O8/s1600/SNAG-0794.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/_dBAKxejXABM/TVAilqEeoRI/AAAAAAAAAVg/o7nKm58p9O8/s400/SNAG-0794.jpg" width="346" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;That's it for reading (parsing) RSS feeds. In my next blog post I will describe how to publish RSS feeds from the database using PL/SQL and SQL.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-820896359852139050?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/820896359852139050/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=820896359852139050' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/820896359852139050'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/820896359852139050'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/fun-with-rss-and-plsql-part-one.html' title='Fun with RSS and PL/SQL, Part One'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_dBAKxejXABM/TVAh_vtzjWI/AAAAAAAAAVY/CEtuPw-FBCA/s72-c/200px-Feed-icon.svg.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7304078607270161749</id><published>2011-02-01T14:17:00.000-08:00</published><updated>2011-02-01T14:17:29.926-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='XML'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Display any XML as clickable tree using PL/SQL (and Apex)</title><content type='html'>I don't have too many good things to say about IE, but I do like how it displays XML files. Chrome's handling of XML files (or total lack of it) sucks. But IE (and Firefox) gives you a nice tree structure which allows you to click on the various nodes to expand or collapse them.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_dBAKxejXABM/TUiEMLDRPcI/AAAAAAAAAU4/lVEUG_Y5lpk/s1600/SNAG-0808.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="376" src="http://2.bp.blogspot.com/_dBAKxejXABM/TUiEMLDRPcI/AAAAAAAAAU4/lVEUG_Y5lpk/s400/SNAG-0808.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This is actually implemented as an &lt;a href="http://www.w3schools.com/xsl/default.asp"&gt;XSL stylesheet&lt;/a&gt; that is "hardcoded" into IE, and &lt;a href="http://forums.devx.com/archive/index.php/t-4702.html"&gt;it is possible to extract it&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Using the default IE stylesheet for XML in your own applications&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here's how we can use this XSL stylesheet in our own PL/SQL and/or Apex applications. Let's pretend we receive product information in XML format from a business partner. We parse the XML file and put the information into our product database. When something goes wrong it's helpful to look at the raw XML, so we store that in the database as well (typically in a CLOB or XMLTYPE column).&lt;br /&gt;&lt;br /&gt;Using Apex, we build a web page to look at the logs, including the XML content. We'd like a nice clickable treeview instead of the raw XML.&lt;br /&gt;&lt;br /&gt;Here's where our XSL stylesheet (extracted from IE) comes in. That stylesheet can be found in the PL/SQL package called XML_STYLESHEET_PKG which is part of &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria, the PL/SQL Utility Library&lt;/a&gt;. Download the library and install it, then continue with these instructions.&lt;br /&gt;&lt;br /&gt;In Apex, create a blank page (I'll be using page 6) and put an HTML Region on it. Then put a Textarea item (called P6_XML_INPUT) in this region. This will simulate our XML product file, although in reality you would of course pull this from the database or a file on disk. Also add a button to submit the page (back to itself).&lt;br /&gt;&lt;br /&gt;Next, add a PL/SQL Region to the page, and put the following as the region source:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TUiExHizAII/AAAAAAAAAU8/r1xSQEh0xGQ/s1600/SNAG-0798.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="308" src="http://4.bp.blogspot.com/_dBAKxejXABM/TUiExHizAII/AAAAAAAAAU8/r1xSQEh0xGQ/s400/SNAG-0798.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Run the page, paste some (valid) XML into the text area, submit the page, and voila! The PL/SQL region should show the data nicely formatted as a clickable tree.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TUiE50jgD1I/AAAAAAAAAVA/vjMUur1TE2g/s1600/SNAG-0797.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/_dBAKxejXABM/TUiE50jgD1I/AAAAAAAAAVA/vjMUur1TE2g/s640/SNAG-0797.jpg" width="451" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Another example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This "clickable tree" can also be useful to show data from regular tables or queries.&lt;br /&gt;&lt;br /&gt;You can easily create an XML representation of a query by using a REF CURSOR and passing it to the XMLTYPE type.&lt;br /&gt;&lt;br /&gt;For example, create a PL/SQL Region and use this as the source:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TUiFToGXayI/AAAAAAAAAVE/KEP5HlqkwAg/s1600/SNAG-0793.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="386" src="http://1.bp.blogspot.com/_dBAKxejXABM/TUiFToGXayI/AAAAAAAAAVE/KEP5HlqkwAg/s400/SNAG-0793.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Run the page, and the output should look something like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TUiFZkjUZ4I/AAAAAAAAAVI/ckM5PyV0Y_I/s1600/SNAG-0792.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/_dBAKxejXABM/TUiFZkjUZ4I/AAAAAAAAAVI/ckM5PyV0Y_I/s640/SNAG-0792.jpg" width="488" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Using some handy utility packages from &lt;a href="http://code.google.com/p/plsql-utils/"&gt;the PL/SQL Utility Library&lt;/a&gt; and a few lines of PL/SQL code, you can display any XML document as a clickable tree.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7304078607270161749?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7304078607270161749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7304078607270161749' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7304078607270161749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7304078607270161749'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/display-any-xml-as-clickable-tree-using.html' title='Display any XML as clickable tree using PL/SQL (and Apex)'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_dBAKxejXABM/TUiEMLDRPcI/AAAAAAAAAU4/lVEUG_Y5lpk/s72-c/SNAG-0808.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-2716841671389345739</id><published>2011-02-01T14:05:00.000-08:00</published><updated>2011-02-01T14:06:17.146-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>PL/SQL Utility Library now has a proper name</title><content type='html'>A good library needs a good name, and what could be a better name for a library than (drum roll, please): &lt;b&gt;Alexandria&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library&lt;/a&gt;, &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth gateway&lt;/a&gt;, anybody see a pattern here? :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-2716841671389345739?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/2716841671389345739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=2716841671389345739' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2716841671389345739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2716841671389345739'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/02/plsql-utility-library-now-has-proper.html' title='PL/SQL Utility Library now has a proper name'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1491180088832253262</id><published>2011-01-21T12:09:00.000-08:00</published><updated>2011-01-21T12:09:46.567-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>PL/SQL Utility Library update</title><content type='html'>I've added several packages to the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;PL/SQL Utility Library&lt;/a&gt; archive, including string, date, SQL and XML libraries. There are also some new examples in the "demos" folder.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTnn4Q09waI/AAAAAAAAAUw/myTD-SYEzw4/s1600/516px-Alexandria_Library_Inscription.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTnn4Q09waI/AAAAAAAAAUw/myTD-SYEzw4/s320/516px-Alexandria_Library_Inscription.jpg" width="274" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;All right, now it's Friday and time for a beer :-)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/plsql-utils/downloads/list"&gt;Get the latest version of the PL/SQL Utility Library from this page&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1491180088832253262?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1491180088832253262/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1491180088832253262' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1491180088832253262'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1491180088832253262'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/01/plsql-utility-library-update.html' title='PL/SQL Utility Library update'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_dBAKxejXABM/TTnn4Q09waI/AAAAAAAAAUw/myTD-SYEzw4/s72-c/516px-Alexandria_Library_Inscription.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-5533560432832892936</id><published>2011-01-19T09:30:00.000-08:00</published><updated>2011-01-19T09:30:46.894-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fun'/><category scheme='http://www.blogger.com/atom/ns#' term='database-centric architecture'/><title type='text'>Back to the Future</title><content type='html'>Come with me on a short trip through space and time, taking a look at the history of building (web sites).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The Stone Age, circa 1995&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Although it involved a lot of manual labor, mighty Stonehenge and the pyramids were built with very simple tools (or by aliens, but let's deal with that topic another day).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTca2GeU2wI/AAAAAAAAATk/Afd3f_7aKto/s1600/300px-Stonehenge-Green.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTca2GeU2wI/AAAAAAAAATk/Afd3f_7aKto/s1600/300px-Stonehenge-Green.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Web development was pretty basic back then, but conceptually simple enough.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TTca7LyvCII/AAAAAAAAATo/6KnANVdL_-s/s1600/cgi.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="192" src="http://4.bp.blogspot.com/_dBAKxejXABM/TTca7LyvCII/AAAAAAAAATo/6KnANVdL_-s/s320/cgi.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbCx30tBI/AAAAAAAAATs/umum5n1iLpM/s1600/img5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="318" src="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbCx30tBI/AAAAAAAAATs/umum5n1iLpM/s400/img5.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The Dark Ages, circa 1998&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Some pretty amazing castles were built during this age, and they still stand today.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTcbGCCg3cI/AAAAAAAAATw/6N1maWaVpXI/s1600/250px-Krak_des_chevaliers_-_artist_rendering.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTcbGCCg3cI/AAAAAAAAATw/6N1maWaVpXI/s1600/250px-Krak_des_chevaliers_-_artist_rendering.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The basic tools had improved a lot, but they were still recognizable. Even an Neanderthal from the previous age could probably be effective with these tools.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbMmdFi-I/AAAAAAAAAT0/4jO4H79uoRY/s1600/step2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbMmdFi-I/AAAAAAAAAT0/4jO4H79uoRY/s400/step2.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The Age of Enlightenment, circa 2002&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The gothic cathedrals from around this time look awesome, but were (and still are) inhabited by unpleasant religious fanatics.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbQsaJ61I/AAAAAAAAAT4/5iKQ2taptSM/s1600/250px-Reims_Kathedrale.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbQsaJ61I/AAAAAAAAAT4/5iKQ2taptSM/s320/250px-Reims_Kathedrale.jpg" width="240" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;At this point, great thinkers with lots of free time combined art and science to build magnificent works of beauty and elegance.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbX7FPrbI/AAAAAAAAAT8/tvOAQZCvI_A/s1600/IC102819.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="300" src="http://4.bp.blogspot.com/_dBAKxejXABM/TTcbX7FPrbI/AAAAAAAAAT8/tvOAQZCvI_A/s400/IC102819.gif" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbfBSq08I/AAAAAAAAAUA/MVyGIFVB4Uw/s1600/step3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="230" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbfBSq08I/AAAAAAAAAUA/MVyGIFVB4Uw/s400/step3.PNG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;The Age of the Astronauts and the Race for Space (via the Clouds), circa 2008&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A lot of very expensive spaceships were built during this age.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTcbw_ppyiI/AAAAAAAAAUE/T50gEfmJSvo/s1600/270px-STS120LaunchHiRes.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTcbw_ppyiI/AAAAAAAAAUE/T50gEfmJSvo/s320/270px-STS120LaunchHiRes.jpg" width="208" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The great empires fought a cold, mostly ideological, war to see who could be the first to reach the moon.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_dBAKxejXABM/TTcb9pHu7sI/AAAAAAAAAUI/ZKbCEZ_8hx4/s1600/Entity+Framework+design_4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="http://2.bp.blogspot.com/_dBAKxejXABM/TTcb9pHu7sI/AAAAAAAAAUI/ZKbCEZ_8hx4/s400/Entity+Framework+design_4.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTccHRN9NTI/AAAAAAAAAUM/dDCkoNRDgkU/s1600/ASP.NET+MVC+Pipeline.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTccHRN9NTI/AAAAAAAAAUM/dDCkoNRDgkU/s640/ASP.NET+MVC+Pipeline.jpg" width="425" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTccK3ae3MI/AAAAAAAAAUQ/ns_Q2Rc1rcs/s1600/350px-Microsoft_Silverlight_stack.svg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTccK3ae3MI/AAAAAAAAAUQ/ns_Q2Rc1rcs/s400/350px-Microsoft_Silverlight_stack.svg.png" width="302" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Of course this all ended with a ka-boom and the eventual retirement of the space shuttle program.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTcclpbgxDI/AAAAAAAAAUU/v7AvugWh3zE/s1600/220px-Challenger_explosion.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="323" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTcclpbgxDI/AAAAAAAAAUU/v7AvugWh3zE/s400/220px-Challenger_explosion.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;A New Hope, 2011&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Realizing that space tourism and new houses on Mars might be out of reach for most people in the foreseeable future, there is a renewed interest in more lightweight approaches to building and transportation. You might even call it eco-friendly.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTccxH29WwI/AAAAAAAAAUY/TQCOZSGHgGA/s1600/250px-Depuradora_de_Lluc.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="300" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTccxH29WwI/AAAAAAAAAUY/TQCOZSGHgGA/s400/250px-Depuradora_de_Lluc.JPG" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Even the otherwise lethargic Microsoft jumps on the bandwagon, to &lt;a href="http://blog.wekeroad.com/microsoft/someone-hit-their-head"&gt;the acclaim of professional developers&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTcc2Js9VBI/AAAAAAAAAUc/OT1aN9H3BHY/s1600/Webmatrix.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="228" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTcc2Js9VBI/AAAAAAAAAUc/OT1aN9H3BHY/s320/Webmatrix.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"WebMatrix is full 180 from the highly abstracted cathedral that is ASP.NET. (...) WebMatrix is focused on simplicity and the "Get It Done" developer. Which, to me, is a massive undersell as we're all "Get it Done" developers."&lt;/i&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;i&gt;"... you have to use raw SQL to query the database. This is going to turn off the Ivory Tower crowd who prefer to wade through XML Soup and tedious designers - but that's to their detriment. As I've always said - the best DSL I've ever seen for working with data is SQL."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;That's cool. This is what web development looks like with WebMatrix:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTcdETydGII/AAAAAAAAAUg/Jq9X_shI-tw/s1600/image_49F9DE0C.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="280" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTcdETydGII/AAAAAAAAAUg/Jq9X_shI-tw/s400/image_49F9DE0C.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Looks strangely familiar, doesn't it? A mix of HTML markup and code, and direct data access without any of that ORM stuff? Yes, that's right. For comparison, let's bring up that screenshot from the Dark Ages (1998) again:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbMmdFi-I/AAAAAAAAAT0/4jO4H79uoRY/s1600/step2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://3.bp.blogspot.com/_dBAKxejXABM/TTcbMmdFi-I/AAAAAAAAAT0/4jO4H79uoRY/s400/step2.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Conclusions &amp;amp; Take-Aways&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(&lt;a href="http://blogs.msdn.com/b/oldnewthing/archive/2010/11/30/10097857.aspx"&gt;Because there always needs to be a take-away.&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;I am (definitely) not saying that "Frameworks are evil", or that "Progress is uncool". I'm just as lazy as any programmer, but I prefer &lt;a href="http://c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html"&gt;the simplest thing that could possibly work&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I prefer frameworks that don't try to obscure that what we (most of us) are trying to do (most of the time) is to generate some HTML based on data from an SQL database.&lt;br /&gt;&lt;br /&gt;Do we really need something like this&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TTcdnK_OPAI/AAAAAAAAAUk/zcYr4zdBP5k/s1600/flowofcontrol.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="300" src="http://1.bp.blogspot.com/_dBAKxejXABM/TTcdnK_OPAI/AAAAAAAAAUk/zcYr4zdBP5k/s400/flowofcontrol.gif" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;If something like this can do the job?&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_dBAKxejXABM/TTcd1uQu13I/AAAAAAAAAUs/77yHUd2Br1M/s1600/adfns102.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="211" src="http://2.bp.blogspot.com/_dBAKxejXABM/TTcd1uQu13I/AAAAAAAAAUs/77yHUd2Br1M/s320/adfns102.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_dBAKxejXABM/TTcducCfseI/AAAAAAAAAUo/A-ZUg_O2SwQ/s1600/SNAG-0783.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="201" src="http://4.bp.blogspot.com/_dBAKxejXABM/TTcducCfseI/AAAAAAAAAUo/A-ZUg_O2SwQ/s400/SNAG-0783.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The golden path is probably somewhere in-between those two extremes. But keep in mind: Stonehenge has lasted for 5,000 years, while the space shuttle is being retired after less than 50 years.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-5533560432832892936?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/5533560432832892936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=5533560432832892936' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5533560432832892936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5533560432832892936'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/01/back-to-future.html' title='Back to the Future'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_dBAKxejXABM/TTca2GeU2wI/AAAAAAAAATk/Afd3f_7aKto/s72-c/300px-Stonehenge-Green.jpg' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4845236919853716046</id><published>2011-01-18T10:54:00.000-08:00</published><updated>2011-02-07T08:53:06.449-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Alexandria'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>PL/SQL Utility Library</title><content type='html'>Most programmers have a collection of utilities and code libraries that they re-use for several projects.&lt;br /&gt;&lt;br /&gt;I've created the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;PL/SQL Utility Library&lt;/a&gt; page on Google Code to host some generic utilities that I've written myself, some that I've collected from elsewhere (credits and links can be found in the relevant source code), as well as links to useful PL/SQL libraries that are actively maintained elsewhere.&lt;br /&gt;&lt;br /&gt;Check out the links and &lt;a href="http://code.google.com/p/plsql-utils/downloads/list"&gt;download the source code&lt;/a&gt;, there's a lot of different stuff here; from parsing CSV files, integrating with Google Maps and generating JSON, to zipping/unzipping files with PL/SQL.&lt;br /&gt;&lt;br /&gt;I plan to add several more packages as soon as I get the code cleaned up.&lt;br /&gt;&lt;br /&gt;Drop a comment below if you know of any other PL/SQL libraries or utilities that should be added to the list!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4845236919853716046?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4845236919853716046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4845236919853716046' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4845236919853716046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4845236919853716046'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/01/plsql-utility-library.html' title='PL/SQL Utility Library'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-5713488915359512994</id><published>2011-01-06T11:28:00.000-08:00</published><updated>2011-01-07T09:32:46.087-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Oracle HTTP Server'/><category scheme='http://www.blogger.com/atom/ns#' term='SOAP'/><category scheme='http://www.blogger.com/atom/ns#' term='DBMS_EPG'/><title type='text'>SOAP Server in PL/SQL</title><content type='html'>Or how to expose PL/SQL packages as SOAP web services using pure PL/SQL&lt;br /&gt;&lt;br /&gt;I have &lt;a href="http://ora-00001.blogspot.com/2009/11/publish-plsql-as-soap-web-service.html"&gt;blogged before about the various options available&lt;/a&gt; for both consuming and exposing SOAP web services using PL/SQL. (And if you don't know what SOAP is, here is &lt;a href="http://harmful.cat-v.org/software/xml/soap/simple"&gt;a tongue-in-cheek introduction&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;Here is yet another lightweight alternative, a small PL/SQL package that implements a simple SOAP server. It will generate a WSDL document on-the-fly for the packages you want to expose (subject to a whitelist). Functions (only) are invoked using dynamic SQL, and the results are returned in a SOAP envelope. Exceptions are handled using the SOAP Fault mechanism.&lt;br /&gt;&lt;br /&gt;I have successfully tested this package on both the Embedded PL/SQL Gateway (DBMS_EPG) on Oracle XE 10g, as well as on Apache/OHS with mod_plsql (tested on a 10g database).&lt;br /&gt;&lt;br /&gt;Here is a screenshot showing &lt;a href="http://webservicestudio.codeplex.com/"&gt;Web Service Studio&lt;/a&gt; used to test the Employee demo service (ie database package):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TSYXJd2o0HI/AAAAAAAAATg/9TZk3wZW4vc/s1600/SNAG-0769.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="390" src="http://1.bp.blogspot.com/_dBAKxejXABM/TSYXJd2o0HI/AAAAAAAAATg/9TZk3wZW4vc/s400/SNAG-0769.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;To try it out, follow these steps:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/plsql-utils/downloads/list"&gt;Download the source code&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Modify the package body to suit your environment (particularly the g_schema_name constant, and the is_whitelisted function)&lt;/li&gt;&lt;li&gt;Install the package into your schema&lt;/li&gt;&lt;li&gt;If you want to run the package through the Apex DAD, remember to grant execute on soap_server_pkg to anonymous (on EPG) or apex_public_user (on mod_plsql), and modify the request validation function (wwv_flow_epg_include_mod_local) as appropriate. Create a synonym if you don't want to include the schema name in the URL.&lt;/li&gt;&lt;li&gt;Use a SOAP client such as Web Service Studio or SoapUI and navigate to &lt;i&gt;http://your-server/dad-name/soap_server_pkg.wsdl?s=your_package_name&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Issues and limitations&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;This initial version only supports functions that return a single value (varchar2, number, date, clob). Functions returning complex (user-defined) types, object types or array types are not supported. But as the demo package shows, you can return complex values using a single CLOB formatted as XML.&lt;/li&gt;&lt;li&gt;The OWA toolkit has a 32K limit on the size of CGI environment variables, which means the SOAP request body is similarly restricted. So although you can return responses of any length from your web services, the requests you can receive must be under 32K in length (including the XML tags in the SOAP request envelope).&lt;/li&gt;&lt;li&gt;The Apex Listener (at least as of the EA release 1.1) differs from the EPG and mod_plsql in that it does not pass the SOAP_BODY request variable to the OWA toolkit, so this solution will not work with the Apex Listener. However, it should be trivial to add to the Listener, so if you would find it useful, then you should file an enhancement request with Oracle and ask for it.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;I'll end with a note of caution:&lt;/b&gt; &lt;i&gt;This package executes dynamic SQL. While care has been taken to sanitize the input and to implement a whitelist, you should carefully review these security measures in terms of your own environment before you expose your database on the network.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-5713488915359512994?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/5713488915359512994/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=5713488915359512994' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5713488915359512994'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5713488915359512994'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2011/01/soap-server-in-plsql.html' title='SOAP Server in PL/SQL'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_dBAKxejXABM/TSYXJd2o0HI/AAAAAAAAATg/9TZk3wZW4vc/s72-c/SNAG-0769.jpg' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7798078182855399671</id><published>2010-09-28T23:44:00.000-07:00</published><updated>2010-09-28T23:44:26.712-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XE'/><title type='text'>Good news about Oracle XE 11g</title><content type='html'>Oracle OpenWorld 2010 came and went without any announcement of Oracle 11g Express Edition (XE).&lt;br /&gt;&lt;br /&gt;However, according to &lt;a href="http://bradleydbrown.blogspot.com/2010/09/oow-summary.html"&gt;Bradley D. Brown&lt;/a&gt; at TUSC, Oracle has now (re-) confirmed that they are still working on it.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;For some time now the ACE Director community has been asking Mark Townsend (product manager for the Oracle DB) when the 11g version of Express was going to be available.&amp;nbsp; He was consistent in saying that they wouldn't even consider releasing it until 11gR2 was available.&amp;nbsp; Well...it's available now!&amp;nbsp; So the ACED community said "OK, Mark..." and he responded with - it's already in the works and should be out...when asked "when..." we got the standard answer.&amp;nbsp;&amp;nbsp;The good news is that it's coming!&amp;nbsp; Another piece of good news is that they raised the DB size limit from 4GB to 10GB.&amp;nbsp; That makes for a nice free DB engine.&lt;/blockquote&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 15px; line-height: 20px;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-size: 15px; line-height: 20px;"&gt;&lt;span class="Apple-style-span" style="color: black; font-family: 'Times New Roman'; font-size: medium; line-height: normal;"&gt;This is good news, since it has been &lt;a href="http://ora-00001.blogspot.com/2009/11/bad-news-about-oracle-xe-11g.html"&gt;a very long time&lt;/a&gt; since XE 10g was released. With Microsoft releasing updates to its free version of SQL Server at the same time as the full version, I think Oracle should do the same.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-size: 15px; line-height: 20px;"&gt;&lt;span class="Apple-style-span" style="color: black; font-family: 'Times New Roman'; font-size: medium; line-height: normal;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-size: 15px; line-height: 20px;"&gt;&lt;span class="Apple-style-span" style="color: black; font-family: 'Times New Roman'; font-size: medium; line-height: normal;"&gt;But hey, it's good, it's free, and it's coming eventually, so I'm not complaining! :-)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-size: 15px; line-height: 20px;"&gt;&lt;span class="Apple-style-span" style="color: black; font-family: 'Times New Roman'; font-size: medium; line-height: normal;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-size: 15px; line-height: 20px;"&gt;&lt;span class="Apple-style-span" style="color: black; font-family: 'Times New Roman'; font-size: medium; line-height: normal;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7798078182855399671?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7798078182855399671/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7798078182855399671' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7798078182855399671'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7798078182855399671'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/09/good-news-about-oracle-xe-11g.html' title='Good news about Oracle XE 11g'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4111058065086250580</id><published>2010-08-08T06:00:00.000-07:00</published><updated>2010-08-08T06:18:29.660-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><category scheme='http://www.blogger.com/atom/ns#' term='Proxy Authentication'/><title type='text'>Proxy Authentication with Thoth Gateway</title><content type='html'>Christian Vind &lt;a href="http://code.google.com/p/thoth-gateway/issues/detail?id=2"&gt;submitted an enhancement request&lt;/a&gt; for the Thoth Gateway to support &lt;a href="http://download.oracle.com/docs/html/E10927_01/featConnecting.htm#i1006514"&gt;Oracle proxy authentication&lt;/a&gt; by passing on the current Windows username to the database connection string.&lt;br /&gt;&lt;br /&gt;The point of proxy authentication is that&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The proxy user only has "create session" privileges but can't do much else.&lt;/li&gt;&lt;li&gt;The real user does not have "create session" privileges and cannot log on to the database without knowing the proxy user name and password (and that is only set on the web/application server).&lt;/li&gt;&lt;li&gt;The USER function returns the real user name, and all standard database auditing, roles, etc. work as usual.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;As of version 1.3 of the Thoth Gateway, proxy authentication is now supported. Here is how it works:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;IIS Setup&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Set up the application (virtual directory) in IIS where the gateway runs with Integrated Windows Authentication, so that the CGI environment variable LOGON_USER will be populated with the client's Windows username. (If the user is using Internet Explorer to browse the site, his identity will be passed on to the web server/gateway automatically; if using another browser, then an explicit logon is required.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Oracle Setup&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Define an "application server user", ie the common user that connections will be established through:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;-- Log on as DBA (SYS or SYSTEM) that has CREATE USER privilege.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;create user appserver identified by eagle;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;create user end_user identified by secret;&lt;br /&gt;grant create session to end_user;&lt;br /&gt;alter user end_user grant connect through appserver;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Times New Roman';"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now test the setup with SQL*Plus, by connecting with the "application server user", and then "becoming" the end user:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;-- note we don't specify the end_user password, but still become that user&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;SQL&amp;gt; connect appserver[end_user]/eagle&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Connected.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;SQL&amp;gt; select user from dual;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;USER&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;------------------------------&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;END_USER&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;SQL&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Note that since the point of this is to take advantage of existing Active Directory accounts, you probably want to create your users like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: 'Courier New', Courier, monospace; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;create user "your_domain\end_user" identified &lt;b&gt;externally&lt;/b&gt;;&lt;/div&gt;&lt;div style="font-family: 'Courier New', Courier, monospace; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;grant create session to "your_domain\end_user";&lt;/div&gt;&lt;div style="font-family: 'Courier New', Courier, monospace; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;alter user&amp;nbsp;"your_domain\end_user"&amp;nbsp;grant connect through appserver;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Thoth Gateway Setup&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In web.config, modify the DAD settings (the following example assumes a local Oracle XE installation):&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;param name="DatabaseConnectString" value="//127.0.0.1:1521/xe"&lt;br /&gt;param name="DatabaseConnectStringAttributes" value="Enlist=false;Proxy User Id=appserver;Proxy Password=eagle;"&lt;br /&gt;param name="DatabaseUserName" value="LOGON_USER"&lt;br /&gt;param name="DatabasePassword" value=""&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Notice the value "LOGON_USER" specified for the DatabaseUserName parameter. This is a reserved string that will be replaced with the actual value of the LOGON_USER value from the web request (ie. the user's Windows username, typically "domain\username"). You can also specify "LOGON_USER_NO_DOMAIN" to strip away the domain part of the user name -- what you use will depend on how you have set up your user accounts in Oracle.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Testing It&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;To test that everything works at this point, create a procedure similar to the following, and execute it via the gateway (don't forget to grant execute privileges on it to the end-user's account, and create a public synonym for it unless you prefix with the procedure owner's name in the URL).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;procedure test_proxy_auth&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;begin&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.header(1, 'Proxy authentication');&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.ulistopen;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.listitem ('USER = ' || user);&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.listitem ('Proxy user = ' || sys_context('userenv', 'proxy_user'));&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.listitem ('CGI LOGON_USER = ' || owa_util.get_cgi_env('LOGON_USER'));&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;htp.ulistclose;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;end test_proxy_auth;&lt;/span&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;If successful, the USER function should return the end-user's Windows username, and the Proxy User should display as "appserver".&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Postscript: A little enigma&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Actually, if you do as described above, you could possibly get this error when you try to run the procedure via the gateway:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;ORA-1045: user %s lacks CREATE SESSION privilege; logon denied&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At least, that's what I got . To get around it, I had to explicitly grant this to the "appserver" user:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;grant create session to appserver;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The funny thing is that my example above, tested via SQL*Plus, shows that this works &lt;i&gt;without&lt;/i&gt; the grant! But when attempting the same connection via ODP.NET, it gives the above error unless the grant is made.&lt;br /&gt;&lt;br /&gt;And if I revoke the "create session" from the end_user, the above example doesn't work in SQL*Plus, because of the missing privilege. Which seems to contradict the purpose of proxying, as defined at the top of this blog post.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If anybody knows why SQL*Plus and ODP.NET show different behaviour here, please let me know.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4111058065086250580?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4111058065086250580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4111058065086250580' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4111058065086250580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4111058065086250580'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/08/proxy-authentication-with-thoth-gateway.html' title='Proxy Authentication with Thoth Gateway'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7003194079880782185</id><published>2010-08-08T05:31:00.000-07:00</published><updated>2010-08-08T05:31:40.275-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mod_plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><title type='text'>Thoth Gateway version 1.3 available</title><content type='html'>There is a new version of the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;, a mod_plsql replacement for IIS, available for download. The latest version is 1.3.&lt;br /&gt;&lt;br /&gt;It contains the following bug fixes and enhancements:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Bug Fix: Issue with parsing client IP address:&lt;/b&gt; Added exception handling to prevent error when parsing client IP address with invalid format.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ignore additional request parameters:&lt;/b&gt; Certain tools and frameworks may dynamically add additional parameters to a request, which causes the corresponding PL/SQL call to fail, since these parameters are not defined in the procedure signature. As of this version, the gateway will now retry the call after dropping (ignoring) any parameters that cannot be found in the Oracle data dictionary for the procedure being called.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Support for Oracle proxy authentication (and Single Sign On) via dynamic username substitution:&lt;/b&gt; Oracle proxy authentication, combined with Integrated Windows Authentication in IIS, allows you to pass the end-user's identity from the client to the database session (so the function USER will return the end-user's Windows username, with no login required). This is useful in an intranet scenario where users are defined in an Active Directory domain and use Internet Explorer to access the PL/SQL web application.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;For existing installations, simply overwrite the existing &lt;i&gt;PLSQLGatewayModule.dll&lt;/i&gt; file in the "bin" folder with the latest version from the downloaded archive.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7003194079880782185?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7003194079880782185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7003194079880782185' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7003194079880782185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7003194079880782185'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/08/thoth-gateway-version-13-available.html' title='Thoth Gateway version 1.3 available'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-5541438162993225813</id><published>2010-07-15T02:19:00.000-07:00</published><updated>2010-07-15T02:20:04.273-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IIS'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Apex on Thoth Gateway and IIS 7</title><content type='html'>Several people have asked for instructions on how to run the&amp;nbsp;&lt;a href="http://ora-00001.blogspot.com/2009/08/thoth-gateway-modplsql-replacement-for.html"&gt;Thoth Gateway&lt;/a&gt;&amp;nbsp;(a mod_plsql replacement) on Microsoft Internet Information Server (IIS) 7.&lt;br /&gt;&lt;br /&gt;I finally had the chance (and the time!) to test the configuration on a server running IIS 7. The biggest challenge was understanding the new administration console user interface for IIS 7; it was was slightly confusing for someone who is used to IIS 6.&lt;br /&gt;&lt;br /&gt;After a bit of fiddling, I got the gateway up and running:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TD7RdI5CtNI/AAAAAAAAAS0/X0BBpj8_QWo/s1600/apex-running-on-iis7-using-thoth.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="380" src="http://3.bp.blogspot.com/_dBAKxejXABM/TD7RdI5CtNI/AAAAAAAAAS0/X0BBpj8_QWo/s400/apex-running-on-iis7-using-thoth.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I have updated the installation instructions in the latest&amp;nbsp;&lt;a href="http://code.google.com/p/thoth-gateway/downloads/list"&gt;download package&lt;/a&gt;&amp;nbsp;(version 1.2.1) with separate sections for IIS 6 and IIS 7. So download, unzip and read the instructions in the "doc" folder.&lt;br /&gt;&lt;br /&gt;And leave a comment if you run into problems (or leave a comment if you managed to get it up and running using the instructions!).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-5541438162993225813?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/5541438162993225813/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=5541438162993225813' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5541438162993225813'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5541438162993225813'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/07/apex-on-thoth-gateway-and-iis-7.html' title='Apex on Thoth Gateway and IIS 7'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_dBAKxejXABM/TD7RdI5CtNI/AAAAAAAAAS0/X0BBpj8_QWo/s72-c/apex-running-on-iis7-using-thoth.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-8887043317494559046</id><published>2010-06-26T11:22:00.000-07:00</published><updated>2010-06-26T11:24:19.137-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Replacing Apex? More like Find and Replace...</title><content type='html'>I was getting my daily dose of Apex blog posts when I noticed this advertisement:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TCZCFi3s2EI/AAAAAAAAASU/g2OkdWqnuZc/s1600/SNAG-0558.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="87" src="http://3.bp.blogspot.com/_dBAKxejXABM/TCZCFi3s2EI/AAAAAAAAASU/g2OkdWqnuZc/s400/SNAG-0558.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;I was curious, so I clicked the link and got the following page (&lt;a href="http://www.wavemaker.com/solutions/oracleforms.html"&gt;http://www.wavemaker.com/solutions/oracleforms.html&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TCZCdUoRg0I/AAAAAAAAASc/eSAIykhCA_s/s1600/SNAG-0557.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="292" src="http://3.bp.blogspot.com/_dBAKxejXABM/TCZCdUoRg0I/AAAAAAAAASc/eSAIykhCA_s/s400/SNAG-0557.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;"Well, this is strange", I thought... it says "Oracle Forms" there in the URL, and in the illustration in the middle of the page, yet the advertisement and the page heading talks about replacing "APEX". There is also a claim that the latter "costs a fortune", but as we all know Application Express is a no-cost option in the Oracle database. WTF?&lt;br /&gt;&lt;br /&gt;There is also a link to an 8-page whitepaper on "Migrating Oracle Apex Applications to Java" (http://www.wavemaker.com/pdf/Migrating-Oracle-Apex-Apps-To-Java-With-WaveMaker.pdf). This whitepaper includes the following screenshot:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_dBAKxejXABM/TCZDILIWtsI/AAAAAAAAASk/2dxxc3VZvVQ/s1600/SNAG-0559.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/_dBAKxejXABM/TCZDILIWtsI/AAAAAAAAASk/2dxxc3VZvVQ/s400/SNAG-0559.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;As well as this table:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/TCZDJ-O5l5I/AAAAAAAAASs/jJZuUrkC5tc/s1600/SNAG-0560.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="207" src="http://1.bp.blogspot.com/_dBAKxejXABM/TCZDJ-O5l5I/AAAAAAAAASs/jJZuUrkC5tc/s400/SNAG-0560.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;OK, so it is clearly Oracle Forms that is depicted and described here, but labeled as if it was Oracle Apex.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;At best, this is a clueless mistake made by some marketing sod who did a global Find &amp;amp; Replace from FORMS to APEX. At worst, this is deliberately misleading.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In any case, it's just wrong, wrong, wrong. I don't know about you, but I would not trust a company that is either incompetent, dishonest, or both. I'll stick with Apex, thank you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-8887043317494559046?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/8887043317494559046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=8887043317494559046' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8887043317494559046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8887043317494559046'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/06/replacing-apex-more-like-find-and.html' title='Replacing Apex? More like Find and Replace...'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_dBAKxejXABM/TCZCFi3s2EI/AAAAAAAAASU/g2OkdWqnuZc/s72-c/SNAG-0558.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-329479034217508066</id><published>2010-06-15T07:06:00.000-07:00</published><updated>2010-06-15T07:06:41.802-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Small patch for the Thoth Gateway (Apex on IIS)</title><content type='html'>Just a quick note to announce that a minor patch release (&lt;a href="http://code.google.com/p/thoth-gateway/downloads/list"&gt;version 1.2.1&lt;/a&gt;) of the &lt;a href="http://code.google.com/p/thoth-gateway"&gt;Thoth Gateway&lt;/a&gt; is available for download. The Thoth Gateway allows you to &lt;a href="http://ora-00001.blogspot.com/2009/08/thoth-gateway-modplsql-replacement-for.html"&gt;run Oracle Apex applications on the Microsoft IIS&lt;/a&gt; web server (a replacement for Apache and mod_plsql).&lt;br /&gt;&lt;br /&gt;The following has changed:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Bug Fix for Content-Length:&lt;/b&gt; Fixed an issue where the content-length header would be incorrectly set for non-AL32UTF8 databases if the page contained multibyte characters.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Set ODP.NET connection string attributes:&lt;/b&gt; Added option to specify additional connection string attributes in the DAD configuration. This allows you to fine-tune the connection properties. See the ODP.NET documentation for more details.&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Upgrade for existing installations:&lt;/b&gt; Simply download the latest version, unzip the archive and drop the &lt;i&gt;PLSQLGatewayModule.dll&lt;/i&gt; file into your &lt;i&gt;bin&lt;/i&gt; folder on the web server.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-329479034217508066?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/329479034217508066/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=329479034217508066' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/329479034217508066'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/329479034217508066'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/06/small-patch-for-thoth-gateway-apex-on.html' title='Small patch for the Thoth Gateway (Apex on IIS)'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4877885340036533770</id><published>2010-05-07T11:57:00.000-07:00</published><updated>2010-10-07T02:18:48.104-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='ApexGen'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>ApexGen has a new home</title><content type='html'>My first open-source project was ApexGen, a utility to generate Oracle Application Express (Apex) pages from PL/SQL, in a fraction of the time it takes to create Apex pages manually (using point-and-click).&amp;nbsp;I originally hosted it on SourceForge. However, since then I have started a few other projects hosted on Google Code, so I decided to &lt;a href="http://code.google.com/p/apexgen/"&gt;move ApexGen to Google Code&lt;/a&gt; as well.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;To summarize, here are my current Oracle, Apex and PL/SQL projects, all on Google Code:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;, a mod_plsql replacement that runs on Microsoft Internet Information Server (IIS). It allows you to use IIS as the web server for Apex applications (instead of Apache or the Embedded PL/SQL Gateway), and it has a few extra features as well, such as CLOB support, automatic Web Services published from PL/SQL, XDB integration, and integrated Windows authentication out-of-the-box.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/jqgrid-for-plsql/"&gt;JQGrid Integration Kit for PL/SQL&lt;/a&gt;, a set of PL/SQL packages that allows you to use the JQGrid component to display and edit tabular data in your Apex applications. It is faster, better-looking and more flexible than the built-in tabular forms in Apex.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/apexgen/"&gt;ApexGen&lt;/a&gt;,&amp;nbsp;a utility to generate Oracle Application Express (Apex) pages from PL/SQL, in a fraction of the time it takes to create Apex pages manually.&amp;nbsp;With Apex 4.0 just around the corner, I believe ApexGen will be due for an overhaul soon, as the export files are likely to have changed quite a bit.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4877885340036533770?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4877885340036533770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4877885340036533770' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4877885340036533770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4877885340036533770'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/05/apexgen-has-new-home.html' title='ApexGen has a new home'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7873988734670519080</id><published>2010-04-10T11:00:00.000-07:00</published><updated>2010-04-11T10:10:30.851-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='csv'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>SELECT * FROM spreadsheet (or How to parse a CSV file using PL/SQL)</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_dBAKxejXABM/S8C8Hb72HGI/AAAAAAAAAR8/hAr0LFWrjR0/s1600/csv256.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_dBAKxejXABM/S8C8Hb72HGI/AAAAAAAAAR8/hAr0LFWrjR0/s320/csv256.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I recently needed to retrieve/download a &lt;a href="http://en.wikipedia.org/wiki/Comma-separated_values"&gt;comma-separated values (CSV)&lt;/a&gt; file from a website, and insert the data in an Oracle database table.&lt;br /&gt;&lt;br /&gt;After googling around a bit, I found various pieces of the solution on AskTom, ExpertsExchange and other sites, which I put together in the following generic utility package for CSV files.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Usage&lt;/h3&gt;&lt;br /&gt;Because I have implemented the main parsing routine as a pipelined function, you can process the data either using straight SQL, or in a PL/SQL program.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_dBAKxejXABM/S8C8vm003xI/AAAAAAAAASE/TyJlqMCt9uw/s1600/SNAG-0482.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/_dBAKxejXABM/S8C8vm003xI/AAAAAAAAASE/TyJlqMCt9uw/s640/SNAG-0482.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;For example, you can retrieve a download a CSV file as a clob directly from the web and return it as a table with a single statement:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select *&lt;br /&gt;from table(csv_util_pkg.clob_to_csv(httpuritype('http://www.foo.example/bar.csv').getclob()))&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And maybe do a direct insert via INSERT .. SELECT :&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;insert into my_table (first_column, second_column)&lt;br /&gt;select c001, c002&lt;br /&gt;from table(csv_util_pkg.clob_to_csv(httpuritype('http://www.foo.example/bar.csv').getclob()))&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can of course also use SQL to filter the results (although this may affect performance):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select *&lt;br /&gt;from table(csv_util_pkg.clob_to_csv(httpuritype('http://www.foo.example/bar.csv').getclob()))&lt;br /&gt;where c002 = 'Chevy'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Or you can do it in a more procedural fashion, like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create table x_dump&lt;br /&gt;(clob_value clob,&lt;br /&gt; dump_date date default sysdate,&lt;br /&gt; dump_id number);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;declare&lt;br /&gt;  l_clob clob;&lt;br /&gt;&lt;br /&gt;  cursor l_cursor&lt;br /&gt;  is&lt;br /&gt;  select csv.*&lt;br /&gt;  from x_dump d, table(csv_util_pkg.clob_to_csv(d.clob_value)) csv&lt;br /&gt;  where d.dump_id = 1;&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;  l_clob := httpuritype('http://www.foo.example/bar.csv').getclob();&lt;br /&gt;  insert into x_dump (clob_value, dump_id) values (l_clob, 1);&lt;br /&gt;  commit;&lt;br /&gt;  dbms_lob.freetemporary (l_clob);&lt;br /&gt;&lt;br /&gt;  for l_rec in l_cursor loop&lt;br /&gt;    dbms_output.put_line ('row ' || l_rec.line_number || ', col 1 = ' || l_rec.c001);&lt;br /&gt;  end loop;&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Auxiliary functions&lt;/h3&gt;&lt;br /&gt;There are a few additional functions in the package that are not necessary for normal usage, but  may be useful if you are doing any sort of lower-level CSV parsing. The csv_to_array function operates on a single CSV-encoded line (so to use this you would have to split the CSV lines yourself first, and feed them one by one to this function):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;  l_array t_str_array;&lt;br /&gt;  l_val varchar2(4000);&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;  l_array := csv_util_pkg.csv_to_array ('10,SMITH,CLERK,"1200,50"');&lt;br /&gt;&lt;br /&gt;  for i in l_array.first .. l_array.last loop&lt;br /&gt;    dbms_output.put_line('value ' || i || ' = ' || l_array(i));&lt;br /&gt;  end loop;&lt;br /&gt;&lt;br /&gt;  -- should output SMITH&lt;br /&gt;  l_val := csv_util_pkg.get_array_value(l_array, 2);&lt;br /&gt;  dbms_output.put_line('value = ' || l_val);&lt;br /&gt;&lt;br /&gt;  -- should give an error message stating that there is no column called DEPTNO because the array does not contain seven elements&lt;br /&gt;  -- leave the column name out to fail silently and return NULL instead of raising exception&lt;br /&gt;  l_val := csv_util_pkg.get_array_value(l_array, 7, 'DEPTNO');&lt;br /&gt;  dbms_output.put_line('value = ' || l_val);&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Installation&lt;/h3&gt;&lt;br /&gt;In order to compile the package, you will need these SQL types in your schema:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create type t_str_array as table of varchar2(4000);&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create type t_csv_line as object (&lt;br /&gt;  line_number  number,&lt;br /&gt;  line_raw     varchar2(4000),&lt;br /&gt;  c001         varchar2(4000),&lt;br /&gt;  c002         varchar2(4000),&lt;br /&gt;  c003         varchar2(4000),&lt;br /&gt;  c004         varchar2(4000),&lt;br /&gt;  c005         varchar2(4000),&lt;br /&gt;  c006         varchar2(4000),&lt;br /&gt;  c007         varchar2(4000),&lt;br /&gt;  c008         varchar2(4000),&lt;br /&gt;  c009         varchar2(4000),&lt;br /&gt;  c010         varchar2(4000),&lt;br /&gt;  c011         varchar2(4000),&lt;br /&gt;  c012         varchar2(4000),&lt;br /&gt;  c013         varchar2(4000),&lt;br /&gt;  c014         varchar2(4000),&lt;br /&gt;  c015         varchar2(4000),&lt;br /&gt;  c016         varchar2(4000),&lt;br /&gt;  c017         varchar2(4000),&lt;br /&gt;  c018         varchar2(4000),&lt;br /&gt;  c019         varchar2(4000),&lt;br /&gt;  c020         varchar2(4000)&lt;br /&gt;);&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create type t_csv_tab as table of t_csv_line;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is the code for the package specification:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package csv_util_pkg&lt;br /&gt;as&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      Package handles comma-separated values (CSV)&lt;br /&gt; &lt;br /&gt;  Remarks:      &lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt;  &lt;br /&gt;  g_default_separator            constant varchar2(1) := ',';&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;  -- convert CSV line to array of values&lt;br /&gt;  function csv_to_array (p_csv_line in varchar2,&lt;br /&gt;                         p_separator in varchar2 := g_default_separator) return t_str_array;&lt;br /&gt; &lt;br /&gt;  -- convert array of values to CSV&lt;br /&gt;  function array_to_csv (p_values in t_str_array,&lt;br /&gt;                         p_separator in varchar2 := g_default_separator) return varchar2;&lt;br /&gt; &lt;br /&gt;  -- get value from array by position&lt;br /&gt;  function get_array_value (p_values in t_str_array,&lt;br /&gt;                            p_position in number,&lt;br /&gt;                            p_column_name in varchar2 := null) return varchar2;&lt;br /&gt;&lt;br /&gt;  -- convert clob to CSV&lt;br /&gt;  function clob_to_csv (p_csv_clob in clob,&lt;br /&gt;                        p_separator in varchar2 := g_default_separator,&lt;br /&gt;                        p_skip_rows in number := 0) return t_csv_tab pipelined;&lt;br /&gt;&lt;br /&gt;end csv_util_pkg;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here is the package body:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package body csv_util_pkg&lt;br /&gt;as&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      Package handles comma-separated values (CSV)&lt;br /&gt; &lt;br /&gt;  Remarks:      &lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;function csv_to_array (p_csv_line in varchar2,&lt;br /&gt;                       p_separator in varchar2 := g_default_separator) return t_str_array&lt;br /&gt;as&lt;br /&gt;  l_returnvalue      t_str_array     := t_str_array();&lt;br /&gt;  l_start_separator  pls_integer     := 0 ;&lt;br /&gt;  l_stop_separator   pls_integer     := 0 ;&lt;br /&gt;  l_length           pls_integer     := 0 ;&lt;br /&gt;  l_idx              binary_integer  := 0 ;&lt;br /&gt;  l_quote_enclosed   boolean         := false ;&lt;br /&gt;  l_offset           pls_integer     := 1 ;&lt;br /&gt;begin&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      convert CSV line to array of values&lt;br /&gt; &lt;br /&gt;  Remarks:      based on code from http://www.experts-exchange.com/Database/Oracle/PL_SQL/Q_23106446.html&lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt; &lt;br /&gt;  l_length := length(p_csv_line) ;&lt;br /&gt;&lt;br /&gt;  if l_length &amp;gt; 0 then&lt;br /&gt;    loop&lt;br /&gt;      l_idx := l_idx + 1;&lt;br /&gt;&lt;br /&gt;      l_quote_enclosed := false;&lt;br /&gt;      if substr(p_csv_line, l_start_separator + 1, 1) = '"' then&lt;br /&gt;        l_quote_enclosed := true;&lt;br /&gt;        l_offset := 2;&lt;br /&gt;        l_stop_separator := instr(p_csv_line, '"', l_start_separator + l_offset, 1);&lt;br /&gt;      else&lt;br /&gt;        l_offset := 1;&lt;br /&gt;        l_stop_separator := instr(p_csv_line, p_separator, l_start_separator + l_offset, 1);&lt;br /&gt;      end if;&lt;br /&gt;&lt;br /&gt;      if l_stop_separator = 0 then&lt;br /&gt;        l_stop_separator := l_length + 1;&lt;br /&gt;      end if;&lt;br /&gt;&lt;br /&gt;      l_returnvalue.extend;&lt;br /&gt;      l_returnvalue(l_idx) := substr(p_csv_line, l_start_separator + l_offset, (l_stop_separator - l_start_separator - l_offset));&lt;br /&gt;      exit when l_stop_separator &amp;gt;= l_length;&lt;br /&gt;      &lt;br /&gt;      if l_quote_enclosed then&lt;br /&gt;        l_stop_separator := l_stop_separator + 1;&lt;br /&gt;      end if ;&lt;br /&gt;      l_start_separator := l_stop_separator;&lt;br /&gt;    end loop;&lt;br /&gt;    &lt;br /&gt;  end if;&lt;br /&gt;  &lt;br /&gt;  return l_returnvalue;&lt;br /&gt; &lt;br /&gt;end csv_to_array;&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;function array_to_csv (p_values in t_str_array,&lt;br /&gt;                       p_separator in varchar2 := g_default_separator) return varchar2&lt;br /&gt;as&lt;br /&gt;  l_value       varchar2(32000);&lt;br /&gt;  l_returnvalue varchar2(32000);&lt;br /&gt;begin&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      convert array of values to CSV&lt;br /&gt; &lt;br /&gt;  Remarks:      &lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt;  &lt;br /&gt;  for i in p_values.first .. p_values.last loop&lt;br /&gt;    l_value := p_values(i);&lt;br /&gt;    if instr(l_value, p_separator) &amp;gt; 0 then&lt;br /&gt;      l_value := '"' || l_value || '"';&lt;br /&gt;    end if;&lt;br /&gt;    if l_returnvalue is null then&lt;br /&gt;      l_returnvalue := l_value;&lt;br /&gt;    else&lt;br /&gt;      l_returnvalue := l_returnvalue || p_separator || l_value;&lt;br /&gt;    end if;&lt;br /&gt;  end loop;&lt;br /&gt; &lt;br /&gt;  return l_returnvalue;&lt;br /&gt; &lt;br /&gt;end array_to_csv;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_array_value (p_values in t_str_array,&lt;br /&gt;                          p_position in number,&lt;br /&gt;                          p_column_name in varchar2 := null) return varchar2&lt;br /&gt;as&lt;br /&gt;  l_returnvalue varchar2(4000);&lt;br /&gt;begin&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      get value from array by position&lt;br /&gt; &lt;br /&gt;  Remarks:     &lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt;  &lt;br /&gt;  if p_values.count &amp;gt;= p_position then&lt;br /&gt;    l_returnvalue := p_values(p_position);&lt;br /&gt;  else&lt;br /&gt;    if p_column_name is not null then&lt;br /&gt;      raise_application_error (-20000, 'Column number ' || p_position || ' does not exist. Expected column: ' || p_column_name);&lt;br /&gt;    else&lt;br /&gt;      l_returnvalue := null;&lt;br /&gt;    end if;&lt;br /&gt;  end if;&lt;br /&gt; &lt;br /&gt;  return l_returnvalue;&lt;br /&gt; &lt;br /&gt;end get_array_value;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function clob_to_csv (p_csv_clob in clob,&lt;br /&gt;                      p_separator in varchar2 := g_default_separator,&lt;br /&gt;                      p_skip_rows in number := 0) return t_csv_tab pipelined&lt;br /&gt;as&lt;br /&gt;  l_line_separator         varchar2(2) := chr(13) || chr(10);&lt;br /&gt;  l_last                   pls_integer;&lt;br /&gt;  l_current                pls_integer;&lt;br /&gt;  l_line                   varchar2(32000);&lt;br /&gt;  l_line_number            pls_integer := 0;&lt;br /&gt;  l_from_line              pls_integer := p_skip_rows + 1;&lt;br /&gt;  l_line_array             t_str_array;&lt;br /&gt;  l_row                    t_csv_line := t_csv_line (null, null,  -- line number, line raw&lt;br /&gt;                                                     null, null, null, null, null, null, null, null, null, null,   -- lines 1-10&lt;br /&gt;                                                     null, null, null, null, null, null, null, null, null, null);  -- lines 11-20&lt;br /&gt;begin&lt;br /&gt; &lt;br /&gt;  /*&lt;br /&gt; &lt;br /&gt;  Purpose:      convert clob to CSV&lt;br /&gt; &lt;br /&gt;  Remarks:      based on code from http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1352202934074&lt;br /&gt;                              and  http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:744825627183&lt;br /&gt; &lt;br /&gt;  Who     Date        Description&lt;br /&gt;  ------  ----------  --------------------------------&lt;br /&gt;  MBR     31.03.2010  Created&lt;br /&gt; &lt;br /&gt;  */&lt;br /&gt;  &lt;br /&gt;  -- If the file has a DOS newline (cr+lf), use that&lt;br /&gt;  -- If the file does not have a DOS newline, use a Unix newline (lf)&lt;br /&gt;  if (nvl(dbms_lob.instr(p_csv_clob, l_line_separator, 1, 1),0) = 0) then&lt;br /&gt;    l_line_separator := chr(10);&lt;br /&gt;  end if;&lt;br /&gt;&lt;br /&gt;  l_last := 1;&lt;br /&gt;&lt;br /&gt;  loop&lt;br /&gt;  &lt;br /&gt;    l_current := dbms_lob.instr (p_csv_clob || l_line_separator, l_line_separator, l_last, 1);&lt;br /&gt;    exit when (nvl(l_current,0) = 0);&lt;br /&gt;    &lt;br /&gt;    l_line_number := l_line_number + 1;&lt;br /&gt;    &lt;br /&gt;    if l_from_line &amp;lt;= l_line_number then&lt;br /&gt;    &lt;br /&gt;      l_line := dbms_lob.substr(p_csv_clob || l_line_separator, l_current - l_last + 1, l_last);&lt;br /&gt;      --l_line := replace(l_line, l_line_separator, '');&lt;br /&gt;      l_line := replace(l_line, chr(10), '');&lt;br /&gt;      l_line := replace(l_line, chr(13), '');&lt;br /&gt;&lt;br /&gt;      l_line_array := csv_to_array (l_line, p_separator);&lt;br /&gt;&lt;br /&gt;      l_row.line_number := l_line_number;&lt;br /&gt;      l_row.line_raw := substr(l_line,1,4000);&lt;br /&gt;      l_row.c001 := get_array_value (l_line_array, 1);&lt;br /&gt;      l_row.c002 := get_array_value (l_line_array, 2);&lt;br /&gt;      l_row.c003 := get_array_value (l_line_array, 3);&lt;br /&gt;      l_row.c004 := get_array_value (l_line_array, 4);&lt;br /&gt;      l_row.c005 := get_array_value (l_line_array, 5);&lt;br /&gt;      l_row.c006 := get_array_value (l_line_array, 6);&lt;br /&gt;      l_row.c007 := get_array_value (l_line_array, 7);&lt;br /&gt;      l_row.c008 := get_array_value (l_line_array, 8);&lt;br /&gt;      l_row.c009 := get_array_value (l_line_array, 9);&lt;br /&gt;      l_row.c010 := get_array_value (l_line_array, 10);&lt;br /&gt;      l_row.c011 := get_array_value (l_line_array, 11);&lt;br /&gt;      l_row.c012 := get_array_value (l_line_array, 12);&lt;br /&gt;      l_row.c013 := get_array_value (l_line_array, 13);&lt;br /&gt;      l_row.c014 := get_array_value (l_line_array, 14);&lt;br /&gt;      l_row.c015 := get_array_value (l_line_array, 15);&lt;br /&gt;      l_row.c016 := get_array_value (l_line_array, 16);&lt;br /&gt;      l_row.c017 := get_array_value (l_line_array, 17);&lt;br /&gt;      l_row.c018 := get_array_value (l_line_array, 18);&lt;br /&gt;      l_row.c019 := get_array_value (l_line_array, 19);&lt;br /&gt;      l_row.c020 := get_array_value (l_line_array, 20);&lt;br /&gt;      &lt;br /&gt;      pipe row (l_row);&lt;br /&gt;      &lt;br /&gt;    end if;&lt;br /&gt;&lt;br /&gt;    l_last := l_current + length (l_line_separator);&lt;br /&gt;&lt;br /&gt;  end loop;&lt;br /&gt;&lt;br /&gt;  return;&lt;br /&gt; &lt;br /&gt;end clob_to_csv;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end csv_util_pkg;&lt;br /&gt;/&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;Performance&lt;/h3&gt;&lt;br /&gt;On my test server (not my laptop), it takes about 35 seconds to process 12,000 rows in CSV format. I don't consider this super-fast, but probably fast enough for many CSV processing scenarios.&lt;br /&gt;&lt;br /&gt;If you have any performance-enhancing tips, do let me know!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Bonus: Exporting CSV data&lt;/h3&gt;&lt;br /&gt;You can also use this package to export CSV data, for example by using a query like this.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select csv_util_pkg.array_to_csv (t_str_array(company_id, company_name, company_type)) as the_csv_data&lt;br /&gt;from company&lt;br /&gt;order by company_name&lt;br /&gt;&lt;br /&gt;THE_CSV_DATA&lt;br /&gt;--------------------------------&lt;br /&gt;260,Acorn Oil &amp; Gas,EXT&lt;br /&gt;261,Altinex,EXT&lt;br /&gt;262,Amerada Hess,EXT&lt;br /&gt;263,Atlantic Petroleum,EXT&lt;br /&gt;264,Beryl,EXT&lt;br /&gt;265,BG,EXT&lt;br /&gt;266,Bow Valley Energy,EXT&lt;br /&gt;267,BP,EXT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This might come in handy, even in these days of XML and JSON ... :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7873988734670519080?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7873988734670519080/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7873988734670519080' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7873988734670519080'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7873988734670519080'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/04/select-from-spreadsheet-or-how-to-parse.html' title='SELECT * FROM spreadsheet (or How to parse a CSV file using PL/SQL)'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_dBAKxejXABM/S8C8Hb72HGI/AAAAAAAAAR8/hAr0LFWrjR0/s72-c/csv256.png' height='72' width='72'/><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-8271509142583040593</id><published>2010-04-06T11:19:00.000-07:00</published><updated>2010-04-06T11:19:18.893-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><title type='text'>Using TRUNC and ROUND on dates</title><content type='html'>Maybe this is old news to some, but I recently became aware that it is possible to use TRUNC and ROUND not just on a NUMBER, but also on a DATE value.&lt;br /&gt;&lt;br /&gt;For example, you can get the start of the month for a given date (using TRUNC), or the "closest" start of the month, rounded forward or backwards in time appropriate (using ROUND):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select sysdate,&lt;br /&gt;  trunc(sysdate, 'YYYY') as trunc_year,&lt;br /&gt;  trunc(sysdate, 'MM') as trunc_month,&lt;br /&gt;  round(sysdate, 'MM') as round_month,&lt;br /&gt;  round(sysdate + 15, 'MM') as round_month2&lt;br /&gt;from dual&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above gives the following results:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;SYSDATE                   TRUNC_YEAR                TRUNC_MONTH               ROUND_MONTH               ROUND_MONTH2              &lt;br /&gt;------------------------- ------------------------- ------------------------- ------------------------- ------------------------- &lt;br /&gt;06.04.2010 20:10:56       01.01.2010 00:00:00       01.04.2010 00:00:00       01.04.2010 00:00:00       01.05.2010 00:00:00       &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Somewhat related to this topic is the relatively obscure (?) EXTRACT function, which allows you to extract a part of a DATE:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select sysdate,&lt;br /&gt;  extract(day from sysdate) as extract_day,&lt;br /&gt;  extract(month from sysdate) as extract_month,&lt;br /&gt;  extract(year from sysdate) as extract_year&lt;br /&gt;from dual&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Which gives the following results:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;SYSDATE                   EXTRACT_DAY            EXTRACT_MONTH          EXTRACT_YEAR           &lt;br /&gt;------------------------- ---------------------- ---------------------- ---------------------- &lt;br /&gt;06.04.2010 20:13:01       6                      4                      2010                   &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you try to extract the "hour", "minute" or "second" from a DATE, however, you get an &lt;b&gt;ORA-30076: invalid extract field for extract source&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;For some reason, these only work on TIMESTAMP values, not on the DATE datatype (which seems like an arbitrary limitation to me). Nevertheless:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select systimestamp,&lt;br /&gt;  extract(hour from systimestamp) as extract_hour,&lt;br /&gt;  extract(minute from systimestamp) as extract_minute,&lt;br /&gt;  extract(second from systimestamp) as extract_second&lt;br /&gt;from dual&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above gives the following results:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;SYSTIMESTAMP  EXTRACT_HOUR           EXTRACT_MINUTE         EXTRACT_SECOND         &lt;br /&gt;------------- ---------------------- ---------------------- ---------------------- &lt;br /&gt;06.04.2010 20.17.12,047000000 +02:00 18                     17                     12,047                 &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-8271509142583040593?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/8271509142583040593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=8271509142583040593' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8271509142583040593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8271509142583040593'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/04/using-trunc-and-round-on-dates.html' title='Using TRUNC and ROUND on dates'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-8763802797709351003</id><published>2010-03-07T03:43:00.000-08:00</published><updated>2010-03-07T03:59:01.859-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='jQGrid'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>jQGrid Integration Kit for PL/SQL and Apex</title><content type='html'>&lt;p&gt;I started developing applications back in the good (?) old client/server days. I was fortunate enough to discover&lt;span class="Apple-converted-space"&gt; &lt;/span&gt;&lt;a href="http://en.wikipedia.org/wiki/Embarcadero_Delphi" rel="nofollow"&gt;Delphi&lt;/a&gt; quite early. Even from the start, the lowly 16-bit Delphi version 1 had a kick-ass &lt;a href="http://delphi.about.com/od/usedbvcl/a/tdbgrid.htm"&gt;DBGrid control&lt;/a&gt; which allowed you to quickly and easily build data-centric applications. Just write a SQL statement in a TDataSet component, connect it to the grid, and voila! Instant multi-row display and editing out of the box, without any coding.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_dBAKxejXABM/S5OUeUTiuEI/AAAAAAAAAR0/YimXkwOSEWM/s1600-h/delphi_dbgrid.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 297px;" src="http://1.bp.blogspot.com/_dBAKxejXABM/S5OUeUTiuEI/AAAAAAAAAR0/YimXkwOSEWM/s400/delphi_dbgrid.jpg" alt="" id="BLOGGER_PHOTO_ID_5445859622988724290" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Fast forward a decade. While I do enjoy building web applications (with PL/SQL and Apex) these days, I've always missed the simplicity of that DBGrid in Delphi. Creating updateable grids with Apex is pretty tedious work (not being entirely satisfied with the built-in updateable tabular forms, I've employed a combination of the apex_item API, page processes for updates and deletes, and custom-made Javascript helpers). It doesn't help that you have to refer to the tabular form arrays by number, rather than by name (g_f01, g_f02, etc.), and that you are restricted to a total of 50 columns per page.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Enter &lt;a href="http://www.trirand.com/blog/"&gt;jQGrid&lt;/a&gt;, &lt;i&gt;"an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web"&lt;/i&gt;.&lt;/p&gt;jQGrid can be integrated with any server-side technology, so &lt;a href="http://code.google.com/p/jqgrid-for-plsql/"&gt;I decided to integrate it with PL/SQL and Apex&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_dBAKxejXABM/S5OUCIaB2jI/AAAAAAAAARs/RXlyXC-C6_8/s1600-h/jqgrid_for_plsql_screenshot.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 343px;" src="http://3.bp.blogspot.com/_dBAKxejXABM/S5OUCIaB2jI/AAAAAAAAARs/RXlyXC-C6_8/s400/jqgrid_for_plsql_screenshot.jpg" alt="" id="BLOGGER_PHOTO_ID_5445859138758367794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Features&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;As of version 1.0, the jQGrid for PL/SQL and Apex has the following features:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Single line of PL/SQL code to render grid&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Populate data based on REF CURSOR or SQL text (with or without bind variables). The REF CURSOR support is based on my &lt;a href="http://ora-00001.blogspot.com/2010/02/ref-cursor-to-json.html"&gt;REF Cursor to JSON&lt;/a&gt; utility package.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Define display modes (read only, sortable, editable) and edit types (checkbox, textarea, select list) per column&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Store grid configuration in database, or specify settings via code (for read-only grids)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Ajax updates (insert, update, delete) based on either automatic row processing (dynamic SQL) or against your own package API&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Multiple grids per page&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Integrated logging and instrumentation&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Usable without Apex (for stand-alone PL/SQL Web Toolkit applications) or with Apex, optionally integrated with Apex session security&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The jQGrid Integration Kit for PL/SQL is free and open source. &lt;a href="http://code.google.com/p/jqgrid-for-plsql/"&gt;&lt;b&gt;Download and try it now!&lt;/b&gt;&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-8763802797709351003?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/8763802797709351003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=8763802797709351003' title='21 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8763802797709351003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/8763802797709351003'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/03/jqgrid-integration-kit-for-plsql-and.html' title='jQGrid Integration Kit for PL/SQL and Apex'/><author><name>Morten Braten</name><uri>http://www.blogger.com/profile/12300886042835631690</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://1.bp.blogspot.com/_dBAKxejXABM/S46XWUorKOI/AAAAAAAAAQ4/w4R84fMaz4k/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_dBAKxejXABM/S5OUeUTiuEI/AAAAAAAAAR0/YimXkwOSEWM/s72-c/delphi_dbgrid.jpg' height='72' width='72'/><thr:total>21</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7624733979245651619</id><published>2010-02-11T09:51:00.000-08:00</published><updated>2011-02-12T02:09:26.212-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>REF Cursor to JSON</title><content type='html'>&lt;a href="http://www.oracle-base.com/articles/misc/UsingRefCursorsToReturnRecordsets.php"&gt;REF Cursors&lt;/a&gt; are cool. They allow you to encapsulate SQL queries behind a PL/SQL package API. For example, you can create a function called GET_EMPLOYEES that returns a SYS_REFCURSOR containing the employees in a specific department:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;function get_employees (p_deptno in number) return sys_refcursor&lt;br /&gt;as&lt;br /&gt;l_returnvalue sys_refcursor;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;open l_returnvalue&lt;br /&gt;for&lt;br /&gt;select empno, ename, job, sal&lt;br /&gt;from emp&lt;br /&gt;where deptno = p_deptno;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_employees;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The client (an application written in Java, .NET, PHP, etc.) can call your API and process the returned REF Cursor just as if it was a normal result set from a SQL query. The benefits are legion. The client no longer needs to contain embedded SQL statements, or indeed know anything about the actual database structure and query text. Privileges on the underlying tables can be revoked. The API can be shared and reused among different clients, whether they are written in Java, .NET, or any number of other languages.&lt;br /&gt;&lt;br /&gt;That is, unless your client is Oracle Application Express (Apex). Apex unfortunately lacks the ability to process REF Cursors, or, more accurately, you cannot create report regions in Apex based on REF Cursors. For standard reports, you have to either embed the SQL statement in the region definition, or return the SQL text string from a function (and hope that the string you built is valid SQL when it gets executed). For interactive reports, only embedded SQL statements are supported.&lt;br /&gt;&lt;br /&gt;I dislike having to scatter literal SQL statements all around my Apex applications, and not be able to take advantage of a package-based, shared and reusable PL/SQL API to encapsulate queries. I submitted a feature request to the Apex team back in 2007, asking for the ability to base report regions on REF Cursors, but so far this has not been implemented.&lt;br /&gt;&lt;br /&gt;The problem, as far as I know, is that Apex uses (and must use) DBMS_SQL to "describe" a SQL statement in order to get the metadata (column names, data types, etc.) for a report region. But not until Oracle 11g did DBMS_SQL include a function (TO_CURSOR_NUMBER) that allows you to &lt;a href="http://www.oraclenerd.com/2008/09/dbmssqltocursornumber.html"&gt;convert a REF Cursor into a DBMS_SQL cursor handle&lt;/a&gt;. So, as long as the minimum supported database version for Apex is Oracle 10g, support for REF Cursors is unlikely to be implemented.&lt;br /&gt;&lt;br /&gt;In the meantime, there are a couple of alternatives:&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Option 1: Pipelined functions&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;It's possible to encapsulate your queries behind a PL/SQL API by using pipelined functions. For example, the above example could be rewritten as...&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create type t_employee as object (&lt;br /&gt;empno number(4),&lt;br /&gt;ename varchar2(10),&lt;br /&gt;job varchar2(9),&lt;br /&gt;sal number&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;create type t_employee_tab as table of t_employee;&lt;br /&gt;&lt;br /&gt;function get_employees (p_deptno in number) return t_employee_tab pipelined&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;for l_rec in (select empno, ename, job, sal from emp where deptno = p_deptno) loop&lt;br /&gt;pipe row (t_employee (l_rec.empno, l_rec.ename, l_rec.job, l_rec.sal));&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;return;&lt;br /&gt;&lt;br /&gt;end get_employees;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And used from Apex (in a report region) via the TABLE statement:&lt;br /&gt;&lt;pre class="brush: sql"&gt;select *&lt;br /&gt;from table(employee_pkg.get_employees (:p1_deptno))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Option 2: XML from REF Cursor&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The DBMS_XMLGEN package can generate XML based on a REF Cursor. While this does not "describe" the REF Cursor per se, it does give us a way (from PL/SQL) to find the column names of an arbitrary REF Cursor query, and perhaps infer the data types from the data itself. A couple of &lt;a href="http://tkyte.blogspot.com/2006/01/i-like-online-communities.html"&gt;blog&lt;/a&gt; &lt;a href="http://tkyte.blogspot.com/2006/01/9i-10g-version.html"&gt;posts&lt;/a&gt; from Tom Kyte explain how this can be used to generate HTML based on a REF Cursor.&lt;br /&gt;&lt;br /&gt;So back to Apex, you could generate a "report" based on a PL/SQL region with code similar to this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;l_clob clob;&lt;br /&gt;l_rc   sys_refcursor;&lt;br /&gt;begin&lt;br /&gt;l_rc := get_employees (:p1_deptno);&lt;br /&gt;l_clob := fncRefCursor2HTML (l_rc);&lt;br /&gt;htp_print_clob (l_clob);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It would also be possible to pass your own XLST stylesheet into the conversion function (perhaps an Apex report region template fetched from the Apex data dictionary?) to control the appearance of the report.&lt;br /&gt;&lt;br /&gt;I put "report" in quotes above, because until the Apex team implements report regions based on REF Cursors, you will miss all the nice built-in features of standard (and interactive) reports, such as sorting, paging, column formatting, linking, etc.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Option 3: JSON from REF Cursor&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Bear with me, I am finally getting to the point of this blog post.&lt;br /&gt;&lt;br /&gt;JSON is cool, too, just like REF Cursors. It's &lt;a href="http://www.json.org/xml.html"&gt;the fat-free alternative to XML&lt;/a&gt;, and JSON data is really easy to work with in Javascript.&lt;br /&gt;&lt;br /&gt;For triple coolness, I want to use an API based on REF Cursors in PL/SQL, client-side data manipulation based on JSON, and Apex to glue the two together.&lt;br /&gt;&lt;br /&gt;What I need is the ability to generate JSON based on a REF Cursor.&lt;br /&gt;&lt;br /&gt;Apex does include a few JSON-related procedures in the APEX_UTIL package, including JSON_FROM_SQL. Although this procedure does support bind variables, it cannot generate JSON from a REF Cursor. (Also, the fact that is is a procedure rather than a function makes it less flexible than it could be. Dear Apex Team, can we please have overloaded (function) versions of these JSON procedures?)&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;REF Cursor to JSON: The (10g) solution&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;So I came up with this solution: Use DBMS_XMLGEN to generate XML based on a REF Cursor, and then transform the XML into JSON by using &lt;a href="http://code.google.com/p/xml2json-xslt/"&gt;an XSLT stylesheet&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Note: As mentioned above, in Oracle 11g you can use DBMS_SQL to describe a REF Cursor, so you could write your own function to generate JSON from a REF Cursor, without going through XML first. (And perhaps in Oracle 12g the powers that be at Redwood Shores will provide us with a built-in DBMS_JSON package that can both generate and parse JSON?)&lt;br /&gt;&lt;br /&gt;In the meantime, for Oracle 10g, I created the JSON_UTIL_PKG package.&lt;br /&gt;&lt;br /&gt;Here is the code for the REF_CURSOR_TO_JSON function:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;function ref_cursor_to_json (p_ref_cursor in sys_refcursor,&lt;br /&gt;p_max_rows in number := null,&lt;br /&gt;p_skip_rows in number := null) return clob&lt;br /&gt;as&lt;br /&gt;l_ctx         dbms_xmlgen.ctxhandle;&lt;br /&gt;l_num_rows    pls_integer;&lt;br /&gt;l_xml         xmltype;&lt;br /&gt;l_json        xmltype;&lt;br /&gt;l_returnvalue clob;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    generate JSON from REF Cursor&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     30.01.2010  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;l_ctx := dbms_xmlgen.newcontext (p_ref_cursor);&lt;br /&gt;&lt;br /&gt;dbms_xmlgen.setnullhandling (l_ctx, dbms_xmlgen.empty_tag);&lt;br /&gt;&lt;br /&gt;-- for pagination&lt;br /&gt;if p_max_rows is not null then&lt;br /&gt;dbms_xmlgen.setmaxrows (l_ctx, p_max_rows);&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;if p_skip_rows is not null then&lt;br /&gt;dbms_xmlgen.setskiprows (l_ctx, p_skip_rows);&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;-- get the XML content&lt;br /&gt;l_xml := dbms_xmlgen.getxmltype (l_ctx, dbms_xmlgen.none);&lt;br /&gt;&lt;br /&gt;l_num_rows := dbms_xmlgen.getnumrowsprocessed (l_ctx);&lt;br /&gt;&lt;br /&gt;dbms_xmlgen.closecontext (l_ctx);&lt;br /&gt;&lt;br /&gt;close p_ref_cursor;&lt;br /&gt;&lt;br /&gt;if l_num_rows &amp;gt; 0 then&lt;br /&gt;-- perform the XSL transformation&lt;br /&gt;l_json := l_xml.transform (xmltype(get_xml_to_json_stylesheet));&lt;br /&gt;l_returnvalue := l_json.getclobval();&lt;br /&gt;else&lt;br /&gt;l_returnvalue := g_json_null_object;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;l_returnvalue := dbms_xmlgen.convert (l_returnvalue, dbms_xmlgen.entity_decode);&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end ref_cursor_to_json;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Examples of usage&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Get a small dataset&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;l_clob clob;&lt;br /&gt;l_cursor sys_refcursor;&lt;br /&gt;begin&lt;br /&gt;l_cursor := employee_pkg.get_employees (10);&lt;br /&gt;l_clob := json_util_pkg.ref_cursor_to_json (l_cursor);&lt;br /&gt;dbms_output.put_line (substr(l_clob, 1, 200));&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;{"ROWSET":[{"EMPNO":7782,"ENAME":"CLARK","JOB":"MANAGER","MGR":7839,"HIREDATE":"09.06.1981","SAL":2450,"COMM":null,"DEPTNO":10},{"EMPNO":7839,"ENAME":"KING","JOB":"PRESIDENT","MGR":null,"HIREDATE":"31.01.2005","SAL":5000,"COMM":null,"DEPTNO":10},{"EMPNO":7934,"ENAME":"MILLER","JOB":"CLERK","MGR":7782,"HIREDATE":"23.01.1982","SAL":1300,"COMM":null,"DEPTNO":10}]}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A large dataset, with paging&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;l_clob clob;&lt;br /&gt;l_cursor sys_refcursor;&lt;br /&gt;begin&lt;br /&gt;l_cursor := test_pkg.get_all_objects;&lt;br /&gt;l_clob := json_util_pkg.ref_cursor_to_json (l_cursor, p_max_rows =&amp;gt; 3, p_skip_rows =&amp;gt; 5000);&lt;br /&gt;dbms_output.put_line (substr(l_clob, 1, 1000));&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;{"ROWSET":[{"OBJECT_ID":5660,"OBJECT_NAME":"LOGMNRT_SEED$","OBJECT_TYPE":"TABLE","LAST_DDL_TIME":"07.02.2006"},{"OBJECT_ID":5661,"OBJECT_NAME":"LOGMNRT_MDDL$","OBJECT_TYPE":"TABLE","LAST_DDL_TIME":"07.02.2006"},{"OBJECT_ID":5662,"OBJECT_NAME":"LOGMNRT_MDDL$_PK","OBJECT_TYPE":"INDEX","LAST_DDL_TIME":"07.02.2006"}]}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It works with nested datasets, too.. !&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select d.deptno, d.dname,&lt;br /&gt;cursor (select e.*&lt;br /&gt;from emp e&lt;br /&gt;where e.deptno = d.deptno) as the_emps&lt;br /&gt;from dept d&lt;br /&gt;&lt;br /&gt;declare&lt;br /&gt;l_json clob;&lt;br /&gt;begin&lt;br /&gt;l_json := json_util_pkg.sql_to_json ('select d.deptno, d.dname,&lt;br /&gt;cursor (select e.*&lt;br /&gt;from emp e&lt;br /&gt;where e.deptno = d.deptno) as the_emps&lt;br /&gt;from dept d');&lt;br /&gt;dbms_output.put_line (substr(l_json, 1, 10000));&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;{"ROWSET":[{"DEPTNO":10,"DNAME":"ACCOUNTING",&lt;br /&gt;"THE_EMPS":[{"EMPNO":7782,"ENAME":"CLARK","JOB":"MANAGER","MGR":7839,"HIREDATE":"09.06.1981","SAL":2450,"COMM":null,"DEPTNO":10},&lt;br /&gt;{"EMPNO":7839,"ENAME":"KING","JOB":"PRESIDENT","MGR":null,"HIREDATE":"31.01.2005","SAL":5000,"COMM":null,"DEPTNO":10},&lt;br /&gt;{"EMPNO":7934,"ENAME":"MILLER","JOB":"CLERK","MGR":7782,"HIREDATE":"23.01.1982","SAL":1300,"COMM":null,"DEPTNO":10}]},&lt;br /&gt;{"DEPTNO":20,"DNAME":"RESEARCH",&lt;br /&gt;"THE_EMPS":[{"EMPNO":7369,"ENAME":"SMITH","JOB":"SALESMAN","MGR":7902,"HIREDATE":"17.12.1980","SAL":880,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":7566,"ENAME":"JONES","JOB":"MANAGER","MGR":7839,"HIREDATE":"02.04.1981","SAL":2975,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":7788,"ENAME":"SCOTT","JOB":"ANALYST","MGR":7566,"HIREDATE":"09.12.1982","SAL":3000,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":7876,"ENAME":"ADAMS","JOB":"CLERK","MGR":7788,"HIREDATE":"12.01.1983","SAL":1100,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":7902,"ENAME":"FORD","JOB":"ANALYST","MGR":7566,"HIREDATE":"03.12.1981","SAL":3000,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":9999,"ENAME":"BRATEN","JOB":"CLERK","MGR":7902,"HIREDATE":"05.05.2009","SAL":1000,"COMM":null,"DEPTNO":20},&lt;br /&gt;{"EMPNO":9998,"ENAME":"DOE","JOB":"CLERK","MGR":7902,"HIREDATE":"25.04.2009","SAL":500,"COMM":null,"DEPTNO":20}]},&lt;br /&gt;{"DEPTNO":30,"DNAME":"SALES",&lt;br /&gt;"THE_EMPS":[{"EMPNO":7499,"ENAME":"ALLEN","JOB":"SALESMAN","MGR":7698,"HIREDATE":"20.02.1981","SAL":1600,"COMM":300,"DEPTNO":30},&lt;br /&gt;{"EMPNO":7521,"ENAME":"WARD","JOB":"SALESMAN","MGR":7698,"HIREDATE":"22.02.1981","SAL":3200,"COMM":500,"DEPTNO":30},&lt;br /&gt;{"EMPNO":7654,"ENAME":"MARTIN","JOB":"SALESMAN","MGR":7698,"HIREDATE":"28.09.1981","SAL":1250,"COMM":1400,"DEPTNO":30},&lt;br /&gt;{"EMPNO":7698,"ENAME":"BLAKE","JOB":"MANAGER","MGR":7839,"HIREDATE":"01.05.1981","SAL":2850,"COMM":null,"DEPTNO":30},&lt;br /&gt;{"EMPNO":7844,"ENAME":"TURNER","JOB":"SALESMAN","MGR":7698,"HIREDATE":"08.09.1981","SAL":1500,"COMM":0,"DEPTNO":30},&lt;br /&gt;{"EMPNO":7900,"ENAME":"JAMES","JOB":"CLERK","MGR":7788,"HIREDATE":"03.12.1981","SAL":950,"COMM":null,"DEPTNO":30}]},&lt;br /&gt;{"DEPTNO":40,"DNAME":"OPERATIONS",&lt;br /&gt;"THE_EMPS":null}]}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Passing a REF Cursor directly to the function call by using the CURSOR function:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select json_util_pkg.ref_cursor_to_json(cursor(select * from emp))&lt;br /&gt;from dual&lt;br /&gt;&lt;br /&gt;{"ROWSET":[{"EMPNO":7369,"ENAME":"SMITH","JOB":"SALESMAN","MGR":7902,"HIREDATE":"17.12.1980","SAL":880,"COMM":null,"DEPTNO":20},{"EMPNO":7499,"ENAME":"ALLEN","JOB":"SALESMAN","MGR":7698,"HIREDATE":"20.02.1981","SAL":1600,"COMM":300,"DEPTNO":30},{"EMPNO":7521,"ENAME":"WARD","JOB":"SALESMAN","MGR":7698,"HIREDATE":"22.02.1981","SAL":3200,"COMM":500,"DEPTNO":30},{"EMPNO":7566,"ENAME":"JONES","JOB":"MANAGER","MGR":7839,"HIREDATE":"02.04.1981","SAL":2975,"COMM":null,"DEPTNO":20},{"EMPNO":7654,"ENAME":"MARTIN","JOB":"SALESMAN","MGR":7698,"HIREDATE":"28.09.1981","SAL":1250,"COMM":1400,"DEPTNO":30},{"EMPNO":7698,"ENAME":"BLAKE","JOB":"MANAGER","MGR":7839,"HIREDATE":"01.05.1981","SAL":2850,"COMM":null,"DEPTNO":30},{"EMPNO":7782,"ENAME":"CLARK","JOB":"MANAGER","MGR":7839,"HIREDATE":"09.06.1981","SAL":2450,"COMM":null,"DEPTNO":10},{"EMPNO":7788,"ENAME":"SCOTT","JOB":"ANALYST","MGR":7566,"HIREDATE":"09.12.1982","SAL":3000,"COMM":null,"DEPTNO":20},{"EMPNO":7839,"ENAME":"KING","JOB":"PRESIDENT","MGR":null,"HIREDATE":"31.01.2005","SAL":5000,"COMM":null,"DEPTNO":10},{"EMPNO":7844,"ENAME":"TURNER","JOB":"SALESMAN","MGR":7698,"HIREDATE":"08.09.1981","SAL":1500,"COMM":0,"DEPTNO":30},{"EMPNO":7876,"ENAME":"ADAMS","JOB":"CLERK","MGR":7788,"HIREDATE":"12.01.1983","SAL":1100,"COMM":null,"DEPTNO":20},{"EMPNO":7900,"ENAME":"JAMES","JOB":"CLERK","MGR":7788,"HIREDATE":"03.12.1981","SAL":950,"COMM":null,"DEPTNO":30},{"EMPNO":7902,"ENAME":"FORD","JOB":"ANALYST","MGR":7566,"HIREDATE":"03.12.1981","SAL":3000,"COMM":null,"DEPTNO":20},{"EMPNO":7934,"ENAME":"MILLERø","JOB":"CLERK","MGR":7782,"HIREDATE":"23.01.1982","SAL":1300,"COMM":null,"DEPTNO":10},{"EMPNO":9999,"ENAME":"BRATEN","JOB":"CLERK","MGR":7902,"HIREDATE":"05.05.2009","SAL":1000,"COMM":null,"DEPTNO":20},{"EMPNO":9998,"ENAME":"DOE","JOB":"CLERK","MGR":7902,"HIREDATE":"25.04.2009","SAL":500,"COMM":null,"DEPTNO":20}]}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Download the package&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;s&gt;You can download the complete package, including the XSLT stylsheet, here (spec) and here (body).&lt;/s&gt;&lt;br /&gt;&lt;br /&gt;Update 12.02.2011: This package can now be downloaded as part of the &lt;a href="http://code.google.com/p/plsql-utils/"&gt;Alexandria library for PL/SQL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Note that to compile the packages you need the following SQL type defined in your schema:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create type t_str_array as table of varchar2(4000);&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7624733979245651619?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7624733979245651619/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7624733979245651619' title='19 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7624733979245651619'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7624733979245651619'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/02/ref-cursor-to-json.html' title='REF Cursor to JSON'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-5740474395448763046</id><published>2010-02-04T11:55:00.000-08:00</published><updated>2010-10-07T02:20:02.698-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>My first Apex 4 plugin: Flight Info from Web Service</title><content type='html'>One of the exciting new features in Apex 4 is the support for &lt;span style="font-style: italic;"&gt;plugin&lt;/span&gt; regions and items. This feature has huge potential, and will make development with Apex even more efficient, productive, and fun. There are already several plugins out there, and I think we will see a lot of interesting work in this area after Apex 4 is released.&lt;br /&gt;&lt;br /&gt;Here is my own first attempt at a (useful) plugin: A region plugin that displays up-to-date flight information for airports in Norway, based on public flight data provided by Avinor, the company that operates the Norwegian airport network.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.avinor.no/avinor/trafikk/50_Flydata"&gt;Avinor has a simple web service&lt;/a&gt; that provides flight information in XML format.&lt;br /&gt;&lt;br /&gt;I am sure there are similar (web) services for flight information in other countries (feel free to leave a comment below if you know of any).&lt;br /&gt;&lt;br /&gt;Here is the PL/SQL code behind the plugin:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;procedure render_my_plugin (&lt;br /&gt;p_region              in apex_plugin.t_region,&lt;br /&gt;p_plugin              in apex_plugin.t_plugin,&lt;br /&gt;p_is_printer_friendly in boolean )&lt;br /&gt;as&lt;br /&gt;l_clob clob;&lt;br /&gt;l_airport_code varchar2(20) := p_region.attribute_01;&lt;br /&gt;l_direction    varchar2(20) := p_region.attribute_02;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;l_clob := apex_web_service.make_rest_request(&lt;br /&gt;p_url =&amp;gt; 'http://flydata.avinor.no/XmlFeed.asp',&lt;br /&gt;p_http_method =&amp;gt; 'GET',&lt;br /&gt;p_parm_name =&amp;gt; apex_util.string_to_table('airport:direction'),&lt;br /&gt;p_parm_value =&amp;gt; apex_util.string_to_table(l_airport_code || ':' || l_direction )&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;if l_direction = 'D' then&lt;br /&gt;htp.p('&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;Departures from ' || l_airport_code || '&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;');&lt;br /&gt;else&lt;br /&gt;htp.p('&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;Arrivals to ' || l_airport_code || '&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;');&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;htp.p('&amp;lt;table width="100%"&amp;gt;');&lt;br /&gt;htp.p('&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;AIRLINE&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;FLIGHT&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;AIRPORT&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;TIME&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;GATE&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;');&lt;br /&gt;&lt;br /&gt;for l_rec in (&lt;br /&gt;SELECT *&lt;br /&gt;FROM XMLTABLE ('//airport/flights/flight'&lt;br /&gt;PASSING XMLTYPE(l_clob)&lt;br /&gt;COLUMNS unique_id       varchar2(100) path '@uniqueID',&lt;br /&gt;airline         varchar2(10) path 'airline',&lt;br /&gt;flight_id       varchar2(20) path 'flight_id',&lt;br /&gt;airport         varchar2(20) path 'airport',&lt;br /&gt;schedule_time   varchar2(100) path 'schedule_time',&lt;br /&gt;gate            varchar2(100) path 'gate')&lt;br /&gt;ORDER BY airline, flight_id) loop&lt;br /&gt;&lt;br /&gt;htp.p('&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;' || l_rec.airline || '&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;' || l_rec.flight_id || '&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;' || l_rec.airport || '&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;' || l_rec.schedule_time || '&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;' || l_rec.gate || '&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;');&lt;br /&gt;&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;htp.p('&amp;lt;/table&amp;gt;');&lt;br /&gt;&lt;br /&gt;htp.p('&amp;lt;a href="http://www.avinor.no"&amp;gt;Flight data from Avinor.&amp;lt;/a&amp;gt; Last updated: ' || to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end render_my_plugin;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The code illustrates several concepts:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;How to render a region plugin using the PL/SQL Web Toolkit (HTP.P) calls&lt;/li&gt;&lt;li&gt;How to retrieve values from the attributes defined for the plugin&lt;/li&gt;&lt;li&gt;Using the new APEX_WEB_SERVICE.MAKE_REST_REQUEST function to retrieve a web page as a CLOB&lt;/li&gt;&lt;li&gt;Using the XMLTABLE function to transform XML into a recordset that can be used in a SELECT&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;An export of &lt;a href="http://docs.google.com/leaf?id=0B02MtcYF2fZ-ZTk0YjljMjktYmMzNC00MGI0LWE4ODItOWJhNDRhMjQzYWFl&amp;amp;hl=en"&gt;my plugin can be downloaded here&lt;/a&gt;, and installed into your own Apex 4 application.&lt;br /&gt;&lt;br /&gt;After the plugin has been installed, using the plugin is as simple as adding a Region (of type Plugin) to the page, and configuring the values for Airport and Direction (the plugin attributes) in the region definition.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_eX9l46hbfLI/S2snYswX_cI/AAAAAAAAAEk/Nxcox7F8ACo/s1600-h/SNAG-0439.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5434480680637038018" src="http://3.bp.blogspot.com/_eX9l46hbfLI/S2snYswX_cI/AAAAAAAAAEk/Nxcox7F8ACo/s400/SNAG-0439.jpg" style="cursor: pointer; height: 350px; width: 400px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You can see a live demo of the plugin here (public page, does not require authentication):&lt;br /&gt;&lt;br /&gt;&lt;a href="http://tryapexnow.com/apex/f?p=test4ea:plugin_demo:0"&gt;http://tryapexnow.com/apex/f?p=test4ea:plugin_demo:0&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Note that for this page, I've also taken advantage of the built-in region caching feature of Apex. The region cache duration is set to 10 minutes, which prevents us from hitting the remote web service for every page view. I really like that you can switch on region caching in Apex without writing a single line of code.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Conclusion:&lt;/span&gt; Apex 4 plugins rock!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-5740474395448763046?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/5740474395448763046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=5740474395448763046' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5740474395448763046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/5740474395448763046'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/02/my-first-apex-4-plugin-flight-info-from.html' title='My first Apex 4 plugin: Flight Info from Web Service'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_eX9l46hbfLI/S2snYswX_cI/AAAAAAAAAEk/Nxcox7F8ACo/s72-c/SNAG-0439.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-3079590491245539236</id><published>2010-01-27T08:36:00.000-08:00</published><updated>2010-10-07T02:20:27.324-07:00</updated><title type='text'>ODP.NET minimal, non-intrusive install</title><content type='html'>This might be of interest for those who use .NET to connect to Oracle databases. (Including yours truly, who wrote the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;, a mod_plsql replacement that runs on Microsoft IIS, using C# and ODP.NET.)&lt;br /&gt;&lt;br /&gt;A while back, &lt;a href="http://blogs.msdn.com/adonet/archive/2009/06/15/system-data-oracleclient-update.aspx"&gt;Microsoft officially deprecated their ADO.NET driver for Oracle&lt;/a&gt; (System.Data.OracleClient).&lt;br /&gt;&lt;br /&gt;Fortunately, Oracle offers its own .NET driver, known as the &lt;a href="http://www.oracle.com/technology/tech/windows/odpnet/index.html"&gt;Oracle Data Provider for .NET &lt;/a&gt;(ODP.NET). This driver is a better choice for Oracle connectivity, since it supports a wider range of Oracle-specific features, and improved performance.&lt;br /&gt;&lt;br /&gt;However, ODP.NET, unlike, say, the thin JDBC drivers, still requires the normal Oracle client to be present on the machine. This Oracle client can be something of a beast, with the install package upwards of 200 megabytes. Couple this with the fact that you may have several diffent Oracle client versions installed on your machine (or application server), all specific to some application that you dare not touch for fear of it breaking.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;A non-intrusive install&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;So, here is how you can use ODP.NET with the following advantages:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Small footprint (between 30 and 100 megabytes)&lt;/li&gt;&lt;li&gt;XCopy deployment&lt;/li&gt;&lt;li&gt;No dependency on shared files, all files in your own application's folder&lt;/li&gt;&lt;li&gt;No registry or system environment changes required&lt;/li&gt;&lt;li&gt;No tnsnames.ora file required&lt;/li&gt;&lt;li&gt;No interference from other Oracle client installs on the same machine&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Sounds good, doesn't it? Let's see how this can be accomplished...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1. Download ODP.NET (xcopy version)&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Download from here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.oracle.com/technology/software/tech/windows/odpnet/utilsoft.html"&gt;http://www.oracle.com/technology/software/tech/windows/odpnet/utilsoft.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Unzip the file and locate the following 2 files:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;OraOps11w.dll&lt;/li&gt;&lt;li&gt;Oracle.DataAccess.dll&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Copy these files to your application's "bin" folder.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2. Download Oracle Instant Client&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Download from here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.oracle.com/technology/software/tech/oci/instantclient/index.html"&gt;http://www.oracle.com/technology/software/tech/oci/instantclient/index.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You have a choice between the following two versions of the Instant Client&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;a) Instant Client Basic (approx. 100 megabytes)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Unzip the file and locate the following 3 files:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;oci.dll&lt;/li&gt;&lt;li&gt;orannzsbb11.dll&lt;/li&gt;&lt;li&gt;oraociei11.dll&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;b) Instant Client Basic Lite (approx. 30 megabytes):&lt;/span&gt; This version is smaller but only supports certain character sets (WE8MSWIN1252 and AL32UTF8 are among them). It only has English messages, so in case you wonder what &lt;span style="font-style: italic;"&gt;"ORA-06556: The pipe is empty"&lt;/span&gt; sounds like in your own language, go for the non-Lite version.&lt;br /&gt;&lt;br /&gt;Unzip the file and locate the following 3 files:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;oci.dll&lt;/li&gt;&lt;li&gt;orannzsbb11.dll&lt;/li&gt;&lt;li&gt;oraociicus11.dll&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Whichever version you choose, copy these files to your application's "bin" folder. You now have a total of 5 new files in your "bin" folder.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3. Connection string&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;In your .NET program, use a connect string in the following format, to make sure you don't need to rely on any network configuration files (tnsnames.ora, etc.).&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=database_host_name)(Port=1521))(CONNECT_DATA=(SERVICE_NAME=database_service_name)))&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;4. Configuration&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;This is mostly relevant if you have other Oracle client installations already on the same machine/server.&lt;br /&gt;&lt;br /&gt;In your configuration file (web.config), you can explicitly set the path to the Oracle DLLs you want to use. Set the "DllPath" parameter to the name of your "bin" folder.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&amp;lt;configuration&amp;gt;&lt;br /&gt;&amp;lt;oracle.dataaccess.client&amp;gt;&lt;br /&gt;&amp;lt;settings&amp;gt;&lt;br /&gt;&amp;lt;add name="DllPath" value="c:\my_app_folder\bin"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="FetchSize" value="65536"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="PromotableTransaction" value="promotable"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="StatementCacheSize" value="10"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="TraceFileName" value="c:\temp\odpnet2.log"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="TraceLevel" value="0"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;add name="TraceOption" value="0"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&amp;lt;/settings&amp;gt;&lt;br /&gt;&amp;lt;/oracle.dataaccess.client&amp;gt;&lt;br /&gt;&amp;lt;/configuration&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;5. That's it!&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;You should now be able to run your ODP.NET application from your "bin" folder.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;References&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://download.oracle.com/docs/html/E10927_01/InstallODP.htm"&gt;Installing Oracle Data Provider for .NET&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://database.in2p3.fr/doc/oracle/Oracle_Database_11_Release_1_%2811.1%29_Documentation/win.111/e10927/featConfig.htm"&gt;ODP.NET Configuration&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://alderprogs.blogspot.com/2009/04/deploying-odpnet-with-oracle-instant.html"&gt;http://alderprogs.blogspot.com/2009/04/deploying-odpnet-with-oracle-instant.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-3079590491245539236?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/3079590491245539236/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=3079590491245539236' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3079590491245539236'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3079590491245539236'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2010/01/odpnet-minimal-non-intrusive-install.html' title='ODP.NET minimal, non-intrusive install'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7214587774311578949</id><published>2009-12-27T03:27:00.000-08:00</published><updated>2010-10-07T02:20:50.587-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='utl_http'/><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Translate'/><title type='text'>Using Google Translate from PL/SQL</title><content type='html'>In today's globalized world, being able to communicate in different languages is important. Personally, I'm struggling to get my level of Spanish above the &lt;i&gt;"una cerveza, por favor"&lt;/i&gt; level, and &lt;a href="http://translate.google.com/"&gt;Google Translate&lt;/a&gt; is a great tool that I use often.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Wouldn't it be great to have the power of Google Translate directly from SQL? Google exposes the translation service via a RESTful (JSON) API, so I decided to write a small PL/SQL wrapper for it.&lt;br /&gt;&lt;br /&gt;Here is the package specification:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package google_translate_pkg&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    PL/SQL wrapper package for Google Translate API&lt;br /&gt;&lt;br /&gt;Remarks:   see http://code.google.com/apis/ajaxlanguage/documentation/ &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- http://code.google.com/apis/ajaxlanguage/documentation/reference.html#LangNameArray&lt;br /&gt;g_lang_AFRIKAANS               constant varchar2(5) := 'af';&lt;br /&gt;g_lang_ALBANIAN                constant varchar2(5) := 'sq';&lt;br /&gt;g_lang_AMHARIC                 constant varchar2(5) := 'am';&lt;br /&gt;g_lang_ARABIC                  constant varchar2(5) := 'ar';&lt;br /&gt;g_lang_ARMENIAN                constant varchar2(5) := 'hy';&lt;br /&gt;g_lang_AZERBAIJANI             constant varchar2(5) := 'az';&lt;br /&gt;g_lang_BASQUE                  constant varchar2(5) := 'eu';&lt;br /&gt;g_lang_BELARUSIAN              constant varchar2(5) := 'be';&lt;br /&gt;g_lang_BENGALI                 constant varchar2(5) := 'bn';&lt;br /&gt;g_lang_BIHARI                  constant varchar2(5) := 'bh';&lt;br /&gt;g_lang_BULGARIAN               constant varchar2(5) := 'bg';&lt;br /&gt;g_lang_BURMESE                 constant varchar2(5) := 'my';&lt;br /&gt;g_lang_CATALAN                 constant varchar2(5) := 'ca';&lt;br /&gt;g_lang_CHEROKEE                constant varchar2(5) := 'chr';&lt;br /&gt;g_lang_CHINESE                 constant varchar2(5) := 'zh';&lt;br /&gt;g_lang_CHINESE_SIMPLIFIED      constant varchar2(5) := 'zh-CN';&lt;br /&gt;g_lang_CHINESE_TRADITIONAL     constant varchar2(5) := 'zh-TW';&lt;br /&gt;g_lang_CROATIAN                constant varchar2(5) := 'hr';&lt;br /&gt;g_lang_CZECH                   constant varchar2(5) := 'cs';&lt;br /&gt;g_lang_DANISH                  constant varchar2(5) := 'da';&lt;br /&gt;g_lang_DHIVEHI                 constant varchar2(5) := 'dv';&lt;br /&gt;g_lang_DUTCH                   constant varchar2(5) := 'nl';  &lt;br /&gt;g_lang_ENGLISH                 constant varchar2(5) := 'en';&lt;br /&gt;g_lang_ESPERANTO               constant varchar2(5) := 'eo';&lt;br /&gt;g_lang_ESTONIAN                constant varchar2(5) := 'et';&lt;br /&gt;g_lang_FILIPINO                constant varchar2(5) := 'tl';&lt;br /&gt;g_lang_FINNISH                 constant varchar2(5) := 'fi';&lt;br /&gt;g_lang_FRENCH                  constant varchar2(5) := 'fr';&lt;br /&gt;g_lang_GALICIAN                constant varchar2(5) := 'gl';&lt;br /&gt;g_lang_GEORGIAN                constant varchar2(5) := 'ka';&lt;br /&gt;g_lang_GERMAN                  constant varchar2(5) := 'de';&lt;br /&gt;g_lang_GREEK                   constant varchar2(5) := 'el';&lt;br /&gt;g_lang_GUARANI                 constant varchar2(5) := 'gn';&lt;br /&gt;g_lang_GUJARATI                constant varchar2(5) := 'gu';&lt;br /&gt;g_lang_HEBREW                  constant varchar2(5) := 'iw';&lt;br /&gt;g_lang_HINDI                   constant varchar2(5) := 'hi';&lt;br /&gt;g_lang_HUNGARIAN               constant varchar2(5) := 'hu';&lt;br /&gt;g_lang_ICELANDIC               constant varchar2(5) := 'is';&lt;br /&gt;g_lang_INDONESIAN              constant varchar2(5) := 'id';&lt;br /&gt;g_lang_INUKTITUT               constant varchar2(5) := 'iu';&lt;br /&gt;g_lang_IRISH                   constant varchar2(5) := 'ga';&lt;br /&gt;g_lang_ITALIAN                 constant varchar2(5) := 'it';&lt;br /&gt;g_lang_JAPANESE                constant varchar2(5) := 'ja';&lt;br /&gt;g_lang_KANNADA                 constant varchar2(5) := 'kn';&lt;br /&gt;g_lang_KAZAKH                  constant varchar2(5) := 'kk';&lt;br /&gt;g_lang_KHMER                   constant varchar2(5) := 'km';&lt;br /&gt;g_lang_KOREAN                  constant varchar2(5) := 'ko';&lt;br /&gt;g_lang_KURDISH                 constant varchar2(5) := 'ku';&lt;br /&gt;g_lang_KYRGYZ                  constant varchar2(5) := 'ky';&lt;br /&gt;g_lang_LAOTHIAN                constant varchar2(5) := 'lo';&lt;br /&gt;g_lang_LATVIAN                 constant varchar2(5) := 'lv';&lt;br /&gt;g_lang_LITHUANIAN              constant varchar2(5) := 'lt';&lt;br /&gt;g_lang_MACEDONIAN              constant varchar2(5) := 'mk';&lt;br /&gt;g_lang_MALAY                   constant varchar2(5) := 'ms';&lt;br /&gt;g_lang_MALAYALAM               constant varchar2(5) := 'ml';&lt;br /&gt;g_lang_MALTESE                 constant varchar2(5) := 'mt';&lt;br /&gt;g_lang_MARATHI                 constant varchar2(5) := 'mr';&lt;br /&gt;g_lang_MONGOLIAN               constant varchar2(5) := 'mn';&lt;br /&gt;g_lang_NEPALI                  constant varchar2(5) := 'ne';&lt;br /&gt;g_lang_NORWEGIAN               constant varchar2(5) := 'no';&lt;br /&gt;g_lang_ORIYA                   constant varchar2(5) := 'or';&lt;br /&gt;g_lang_PASHTO                  constant varchar2(5) := 'ps';&lt;br /&gt;g_lang_PERSIAN                 constant varchar2(5) := 'fa';&lt;br /&gt;g_lang_POLISH                  constant varchar2(5) := 'pl';&lt;br /&gt;g_lang_PORTUGUESE              constant varchar2(5) := 'pt-PT';&lt;br /&gt;g_lang_PUNJABI                 constant varchar2(5) := 'pa';&lt;br /&gt;g_lang_ROMANIAN                constant varchar2(5) := 'ro';&lt;br /&gt;g_lang_RUSSIAN                 constant varchar2(5) := 'ru';&lt;br /&gt;g_lang_SANSKRIT                constant varchar2(5) := 'sa';&lt;br /&gt;g_lang_SERBIAN                 constant varchar2(5) := 'sr';&lt;br /&gt;g_lang_SINDHI                  constant varchar2(5) := 'sd';&lt;br /&gt;g_lang_SINHALESE               constant varchar2(5) := 'si';&lt;br /&gt;g_lang_SLOVAK                  constant varchar2(5) := 'sk';&lt;br /&gt;g_lang_SLOVENIAN               constant varchar2(5) := 'sl';&lt;br /&gt;g_lang_SPANISH                 constant varchar2(5) := 'es';&lt;br /&gt;g_lang_SWAHILI                 constant varchar2(5) := 'sw';&lt;br /&gt;g_lang_SWEDISH                 constant varchar2(5) := 'sv';&lt;br /&gt;g_lang_TAJIK                   constant varchar2(5) := 'tg';&lt;br /&gt;g_lang_TAMIL                   constant varchar2(5) := 'ta';&lt;br /&gt;g_lang_TAGALOG                 constant varchar2(5) := 'tl';&lt;br /&gt;g_lang_TELUGU                  constant varchar2(5) := 'te';&lt;br /&gt;g_lang_THAI                    constant varchar2(5) := 'th';&lt;br /&gt;g_lang_TIBETAN                 constant varchar2(5) := 'bo';&lt;br /&gt;g_lang_TURKISH                 constant varchar2(5) := 'tr';&lt;br /&gt;g_lang_UKRAINIAN               constant varchar2(5) := 'uk';&lt;br /&gt;g_lang_URDU                    constant varchar2(5) := 'ur';&lt;br /&gt;g_lang_UZBEK                   constant varchar2(5) := 'uz';&lt;br /&gt;g_lang_UIGHUR                  constant varchar2(5) := 'ug';&lt;br /&gt;g_lang_VIETNAMESE              constant varchar2(5) := 'vi';&lt;br /&gt;g_lang_WELSH                   constant varchar2(5) := 'cy';&lt;br /&gt;g_lang_YIDDISH                 constant varchar2(5) := 'yi';&lt;br /&gt;g_lang_UNKNOWN                 constant varchar2(5) := '';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-- translate a piece of text&lt;br /&gt;function translate_text (p_text in varchar2,&lt;br /&gt;p_to_lang in varchar2,&lt;br /&gt;p_from_lang in varchar2 := null,&lt;br /&gt;p_use_cache in varchar2 := 'YES') return varchar2;&lt;br /&gt;&lt;br /&gt;-- detect language code for text&lt;br /&gt;function detect_lang (p_text in varchar2) return varchar2;&lt;br /&gt;&lt;br /&gt;-- get number of texts in cache&lt;br /&gt;function get_translation_cache_count return number;&lt;br /&gt;&lt;br /&gt;-- clear translation cache&lt;br /&gt;procedure clear_translation_cache;&lt;br /&gt;&lt;br /&gt;end google_translate_pkg;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here is the package body:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package body google_translate_pkg&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    PL/SQL wrapper package for Google Translate API&lt;br /&gt;&lt;br /&gt;Remarks:   see http://code.google.com/apis/ajaxlanguage/documentation/ &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;m_http_referrer                constant varchar2(255) := 'your-domain-name-or-website-here'; -- insert your domain/website here (required by Google's terms of use)&lt;br /&gt;m_api_key                      constant varchar2(255) := null; -- insert your Google API Key here (optional but recommended)&lt;br /&gt;&lt;br /&gt;m_service_url                  constant varchar2(255) := 'http://ajax.googleapis.com/ajax/services/language/';&lt;br /&gt;m_service_version              constant varchar2(10)  := '1.0';&lt;br /&gt;&lt;br /&gt;m_max_text_size                constant pls_integer   := 500; -- can be increased up towards 32k, the cache name size (below) must be increased accordingly &lt;br /&gt;&lt;br /&gt;type t_translation_cache is table of varchar2(32000) index by varchar2(550);&lt;br /&gt;&lt;br /&gt;m_translation_cache            t_translation_cache;&lt;br /&gt;m_cache_id_separator           constant varchar2(1) := '|';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure add_to_cache (p_from_text in varchar2,&lt;br /&gt;p_from_lang in varchar2,&lt;br /&gt;p_to_text in varchar2,&lt;br /&gt;p_to_lang in varchar2)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    add translation to cache&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;m_translation_cache (p_from_lang || m_cache_id_separator || p_to_lang || m_cache_id_separator || replace(substr(p_from_text,1,m_max_text_size), m_cache_id_separator, '')) := p_to_text;&lt;br /&gt;&lt;br /&gt;end add_to_cache;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_from_cache (p_text in varchar2,&lt;br /&gt;p_from_lang in varchar2,&lt;br /&gt;p_to_lang in varchar2) return varchar2&lt;br /&gt;as&lt;br /&gt;l_returnvalue varchar2(32000);&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    get translation from cache&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_returnvalue := m_translation_cache (p_from_lang || m_cache_id_separator || p_to_lang || m_cache_id_separator || replace(substr(p_text,1,m_max_text_size), m_cache_id_separator, ''));&lt;br /&gt;exception&lt;br /&gt;when no_data_found then&lt;br /&gt;l_returnvalue := null;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_from_cache;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_clob_from_http_post (p_url in varchar2,&lt;br /&gt;p_values in varchar2) return clob&lt;br /&gt;as&lt;br /&gt;l_request     utl_http.req;&lt;br /&gt;l_response    utl_http.resp;&lt;br /&gt;l_buffer      varchar2(32767);&lt;br /&gt;l_returnvalue clob := ' ';&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    do a HTTP POST and get results back in a CLOB&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;l_request := utl_http.begin_request (p_url, 'POST', utl_http.http_version_1_1);&lt;br /&gt;&lt;br /&gt;utl_http.set_header (l_request, 'Referer', m_http_referrer); -- note that the actual header name is misspelled in the HTTP protocol&lt;br /&gt;utl_http.set_header (l_request, 'Content-Type', 'application/x-www-form-urlencoded');&lt;br /&gt;utl_http.set_header (l_request, 'Content-Length', to_char(length(p_values)));&lt;br /&gt;utl_http.write_text (l_request, p_values);&lt;br /&gt;&lt;br /&gt;l_response := utl_http.get_response (l_request);&lt;br /&gt;&lt;br /&gt;if l_response.status_code = utl_http.http_ok then&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;loop&lt;br /&gt;utl_http.read_text (l_response, l_buffer);&lt;br /&gt;dbms_lob.writeappend (l_returnvalue, length(l_buffer), l_buffer);&lt;br /&gt;end loop;&lt;br /&gt;exception&lt;br /&gt;when utl_http.end_of_body then&lt;br /&gt;null;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;utl_http.end_response (l_response);&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_clob_from_http_post;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function translate_text (p_text in varchar2,&lt;br /&gt;p_to_lang in varchar2,&lt;br /&gt;p_from_lang in varchar2 := null,&lt;br /&gt;p_use_cache in varchar2 := 'YES') return varchar2&lt;br /&gt;as&lt;br /&gt;l_values      varchar2(2000);&lt;br /&gt;l_response    clob;&lt;br /&gt;l_start_pos   pls_integer;&lt;br /&gt;l_end_pos     pls_integer;&lt;br /&gt;l_returnvalue varchar2(32000) := null;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    translate a piece of text&lt;br /&gt;&lt;br /&gt;Remarks:    if the "from" language is left blank, Google Translate will attempt to autodetect the language&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;MBR     25.12.2009  Added cache for translations&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;if trim(p_text) is not null then&lt;br /&gt;&lt;br /&gt;if p_use_cache = 'YES' then&lt;br /&gt;l_returnvalue := get_from_cache (p_text, p_from_lang, p_to_lang);&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;if l_returnvalue is null then&lt;br /&gt;&lt;br /&gt;l_values := 'v=' || m_service_version || '&amp;amp;q=' || utl_url.escape (substr(p_text,1,m_max_text_size), false, 'UTF8') || '&amp;amp;langpair=' || p_from_lang || '|' || p_to_lang;&lt;br /&gt;&lt;br /&gt;if m_api_key is not null then&lt;br /&gt;l_values := l_values || '&amp;amp;key=' || m_api_key;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;l_response := get_clob_from_http_post (m_service_url || 'translate', l_values);&lt;br /&gt;&lt;br /&gt;if l_response is not null then&lt;br /&gt;&lt;br /&gt;l_start_pos := instr(l_response, '{"translatedText":"');&lt;br /&gt;l_start_pos := l_start_pos + 19;&lt;br /&gt;l_end_pos := instr(l_response, '"', l_start_pos);&lt;br /&gt;&lt;br /&gt;l_returnvalue := substr(l_response, l_start_pos, l_end_pos - l_start_pos);&lt;br /&gt;&lt;br /&gt;if (p_use_cache = 'YES') and (l_returnvalue is not null) then&lt;br /&gt;add_to_cache (p_text, p_from_lang, l_returnvalue, p_to_lang);&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end translate_text;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function detect_lang (p_text in varchar2) return varchar2&lt;br /&gt;as&lt;br /&gt;l_url         varchar2(2000);&lt;br /&gt;l_response    clob;&lt;br /&gt;l_start_pos   pls_integer;&lt;br /&gt;l_end_pos     pls_integer;&lt;br /&gt;l_returnvalue varchar2(255);&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    detect language code for text&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;if trim(p_text) is not null then&lt;br /&gt;&lt;br /&gt;l_url := m_service_url || 'detect?v=' || m_service_version || '&amp;amp;q=' || utl_url.escape (substr(p_text,1,m_max_text_size), false, 'UTF8');&lt;br /&gt;&lt;br /&gt;if m_api_key is not null then&lt;br /&gt;l_url := l_url || '&amp;amp;key=' || m_api_key;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;l_response := httpuritype(l_url).getclob();&lt;br /&gt;&lt;br /&gt;l_start_pos := instr(l_response, '{"language":"');&lt;br /&gt;l_start_pos := l_start_pos + 13;&lt;br /&gt;l_end_pos := instr(l_response, '",', l_start_pos);&lt;br /&gt;&lt;br /&gt;l_returnvalue := substr(l_response, l_start_pos, l_end_pos - l_start_pos);&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end detect_lang;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_translation_cache_count return number&lt;br /&gt;as&lt;br /&gt;l_returnvalue number;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    get number of texts in cache&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;l_returnvalue := m_translation_cache.count;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_translation_cache_count;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure clear_translation_cache&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    clear translation cache&lt;br /&gt;&lt;br /&gt;Remarks:    &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     25.12.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;m_translation_cache.delete;&lt;br /&gt;&lt;br /&gt;end clear_translation_cache;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end google_translate_pkg;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So, let's take the package for a test drive!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Detecting languages&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Google can (try to) figure out which language a specific text is in.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select google_translate_pkg.detect_lang ('hola mundo') as detect1,&lt;br /&gt;google_translate_pkg.detect_lang ('ich bin ein berliner') as detect2&lt;br /&gt;from dual&lt;br /&gt;&lt;br /&gt;detect1              detect2             &lt;br /&gt;-------------------- --------------------&lt;br /&gt;es                   de                  &lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Translating text&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;This is the most common usage of the package. Note that if you leave out the "from" language parameter, Google will attempt to autodetect the language.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select google_translate_pkg.translate_text ('excuse me, where is the toilet?', 'es') as the_phrase&lt;br /&gt;from dual&lt;br /&gt;&lt;br /&gt;the_phrase                              &lt;br /&gt;----------------------------------------&lt;br /&gt;Disculpe, ¿dónde está el baño?          &lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Mass translation&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Here is an example that translates several rows -- the product descriptions from the demo products table (from the Application Express demo application) -- into several languages.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select pi.product_id, pi.product_name, pi.product_description,&lt;br /&gt;google_translate_pkg.translate_text (pi.product_description, 'es', 'en') as spanish_description,&lt;br /&gt;google_translate_pkg.translate_text (pi.product_description, 'de', 'en') as german_description&lt;br /&gt;from demo_product_info pi&lt;br /&gt;order by pi.product_name&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;product_id product_name         product_description            spanish_description            german_description            &lt;br /&gt;---------- -------------------- ------------------------------ ------------------------------ ------------------------------&lt;br /&gt;3 Bluetooth Headset    Hands-Free without the wires!  Manos libres sin cables!       Hands-Free ohne Kabel!        &lt;br /&gt;8 Classic Projector    Does not include transparencie No incluye transparencias o lá Enthält keine Folien oder Fett&lt;br /&gt;s or grease pencil             piz de grasa                   stift                         &lt;br /&gt;&lt;br /&gt;2 MP3 Player           Store up to 1000 songs and tak Almacena hasta 1000 canciones  Speichern Sie bis zu 1000 Song&lt;br /&gt;e them with you                y llevarlos con usted          s, und nehmen Sie sie mit     &lt;br /&gt;&lt;br /&gt;4 PDA Cell Phone       Combine your cell phone and PD Combine su teléfono celular y  Kombinieren Sie Ihre Handy und&lt;br /&gt;A into one device              PDA en un solo dispositivo      PDA in einem Gerät           &lt;br /&gt;&lt;br /&gt;5 Portable DVD Player  Small enough to take anywhere! Lo suficientemente pequeño com Klein genug, um überall hin mi&lt;br /&gt;o para tener en cualquier luga tnehmen!                      &lt;br /&gt;r!                             tnehmen!                      &lt;br /&gt;&lt;br /&gt;10 Stereo Headphones    Noise-cancelling headphones pe El ruido auriculares con perfe Noise-Cancelling-Kopfhörer ide&lt;br /&gt;rfect for the traveler         cta para el viajero            al für den Reisenden          &lt;br /&gt;&lt;br /&gt;9 Ultra Slim Laptop    The power of a desktop in a po El poder de una computadora de Die Leistung eines Desktop in &lt;br /&gt;rtable design                   escritorio en un diseño portá ein tragbares Design          &lt;br /&gt;rtable design                  til                            ein tragbares Design          &lt;br /&gt;&lt;br /&gt;1 3.2 GHz Desktop PC   All the options, this machine  Todas las opciones, se carga e Alle Optionen, ist diese Masch&lt;br /&gt;is loaded!                     sta máquina!                   ine geladen!                  &lt;br /&gt;&lt;br /&gt;6 512 MB DIMM          Expand your PCs memory and gai Amplíe su PC la memoria y obte Erweitern Sie Ihren PC Speiche&lt;br /&gt;n more performance             ner más rendimiento            r und gewinnen mehr Leistung  &lt;br /&gt;&lt;br /&gt;7 54" Plasma Flat Scre Mount on the wall or ceiling,  Montar en la pared o el techo, Montage auf der Wand oder Deck&lt;br /&gt;en                   the picture is crystal clear!   el panorama es claro!         e, das Bild ist glasklar!     &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Apex translation&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Combine this package with the Apex dictionary views to get a kick-start when you are translating your own Apex applications into other languages (some tweaking of the results is probably necessary...).&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;select item_name, label,&lt;br /&gt;google_translate_pkg.translate_text (label, 'es', 'en') as spanish_label,&lt;br /&gt;google_translate_pkg.translate_text (label, 'nl', 'en') as dutch_label&lt;br /&gt;from apex_application_page_items&lt;br /&gt;where application_id = 103&lt;br /&gt;and label is not null&lt;br /&gt;and display_as &amp;lt;&amp;gt; 'Hidden'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;item_name            label                spanish_label        dutch_label         &lt;br /&gt;-------------------- -------------------- -------------------- --------------------&lt;br /&gt;P101_PASSWORD        Password             Contraseña           Wachtwoord          &lt;br /&gt;P101_USERNAME        User Name            Nombre de usuario    Gebruikersnaam      &lt;br /&gt;P11_CUSTOMER_ID      Customer             Cliente              Klant               &lt;br /&gt;P29_CUSTOMER_INFO    Customer Info        Información del clie Customer Info       &lt;br /&gt;nte                                      &lt;br /&gt;P29_ORDER_TIMESTAMP  Order Date           Fecha de pedido      Orderdatum          &lt;br /&gt;P29_ORDER_TOTAL      Order Total          Orden total          Bestel Totaal       &lt;br /&gt;P29_USER_ID          Sales Rep            Sales Rep            Vertegenwoordiger   &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;7 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Caching&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;I have built in a simple cache mechanism, so that you avoid the network traffic if the phrase has already been translated in your current session (and the performance benefit can be huge if you repeatedly translate the same strings).&lt;br /&gt;You can specify whether you want to use the cache (it's on by default), and there is also a procedure to clear (reset) the cache.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;It is time to throw out your old-fashioned dictionaries and fire up SQL Plus instead :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7214587774311578949?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7214587774311578949/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7214587774311578949' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7214587774311578949'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7214587774311578949'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/12/using-google-translate-from-plsql.html' title='Using Google Translate from PL/SQL'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-2737192942776900595</id><published>2009-11-28T11:48:00.000-08:00</published><updated>2012-01-26T00:38:39.702-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SOAP'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><category scheme='http://www.blogger.com/atom/ns#' term='XDB'/><title type='text'>More PL/SQL Gateway Goodies</title><content type='html'>Hot on the heels of version 1.1, which was the topic of &lt;a href="http://ora-00001.blogspot.com/2009/11/publish-plsql-as-soap-web-service.html"&gt;my previous blog post&lt;/a&gt;, the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway version 1.2&lt;/a&gt; improves on the automatic SOAP Web Service feature, and adds a few new features as well!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Improved Automatic SOAP Web Services&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Previously, every function had its own separate service endpoint. This was a bit of a pain, as you would have to (in Visual Studio-speak) add a separate web reference to each function. Now, all functions in a package are grouped together into a single service endpoint. Just add "?wsdl" to the package name, like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_eX9l46hbfLI/SxGEuKxd3VI/AAAAAAAAADY/StSkn7pAtNs/s1600/SNAG-0393.jpg"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5409250556149357906" src="http://1.bp.blogspot.com/_eX9l46hbfLI/SxGEuKxd3VI/AAAAAAAAADY/StSkn7pAtNs/s400/SNAG-0393.jpg" style="cursor: hand; cursor: pointer; height: 291px; width: 400px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Upload files to file system instead of database table&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Normally, files uploaded via a web page will be stored in the database table specified as "DocumentTableName" in the DAD configuration. In this version, there is a new configuration parameter, "DocumentFilePath", that will cause uploaded files to be saved to the file system instead.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;XDB Integration&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;This version of the Thoth Gateway adds easy integration with &lt;a href="http://www.oracle.com/technology/tech/xml/xmldb/index.html"&gt;Oracle XDB&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://docs.oracle.com/cd/B14117_01/appdev.101/b10790/adxdb034.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="303" src="http://docs.oracle.com/cd/B14117_01/appdev.101/b10790/adxdb034.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Upload files to XDB repository:&lt;/b&gt; You can specify a "DocumentXdbPath" in the DAD configuration file that causes uploaded files to be inserted as XDB resources in the specified folder. (This means there are now three different, and mutually exclusive, destinations for uploaded files: database table, file system, and XDB repository.)&lt;br /&gt;&lt;br /&gt;Here, for example, we have just uploaded a zip file to XDB, which is then available via SQL, HTTP, FTP and WebDAV as usual:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_eX9l46hbfLI/SxGE385kHnI/AAAAAAAAADg/VSkHHethH2M/s1600/SNAG-0396.jpg"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5409250724223917682" src="http://3.bp.blogspot.com/_eX9l46hbfLI/SxGE385kHnI/AAAAAAAAADg/VSkHHethH2M/s400/SNAG-0396.jpg" style="cursor: hand; cursor: pointer; height: 109px; width: 400px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Download files from XDB repository:&lt;/b&gt; You can specify an "XdbAlias" in the DAD configuration file. If this is specified, it will set up a virtual directory (similar to the "PathAlias" parameter) that forwards requests to the XDB repository. You can control which part of the repository you want to expose by specifying the "XdbPathRoot" parameter.&lt;br /&gt;&lt;br /&gt;Here we are downloading the zip file via the gateway:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_eX9l46hbfLI/SxGE-KhP1aI/AAAAAAAAADo/es6u6nntu7Q/s1600/SNAG-0395.jpg"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5409250830959236514" src="http://3.bp.blogspot.com/_eX9l46hbfLI/SxGE-KhP1aI/AAAAAAAAADo/es6u6nntu7Q/s400/SNAG-0395.jpg" style="cursor: hand; cursor: pointer; height: 278px; width: 400px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The new options are more fully explained in the installation guide.&lt;br /&gt;&lt;br /&gt;Check out &lt;a href="http://code.google.com/p/thoth-gateway/downloads/list"&gt;version 1.2&lt;/a&gt; of the Thoth Gateway now!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-2737192942776900595?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/2737192942776900595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=2737192942776900595' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2737192942776900595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2737192942776900595'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/11/more-plsql-gateway-goodies.html' title='More PL/SQL Gateway Goodies'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_eX9l46hbfLI/SxGEuKxd3VI/AAAAAAAAADY/StSkn7pAtNs/s72-c/SNAG-0393.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1542815004850099205</id><published>2009-11-17T09:50:00.000-08:00</published><updated>2010-10-07T02:21:30.104-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SOAP'/><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><title type='text'>Publish PL/SQL as SOAP Web Service</title><content type='html'>You can easily &lt;i&gt;consume&lt;/i&gt; a SOAP Web Service from PL/SQL, for example using Application Express or the &lt;a href="http://jastraub.blogspot.com/2008/06/flexible-web-service-api.html"&gt;FLEX_WS_API&lt;/a&gt; (see also my &lt;a href="http://ora-00001.blogspot.com/2009/07/calling-soap-web-service-from-plsql-by.html"&gt;companion utilities to FLEX_WS_API&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;But if you want to &lt;i&gt;publish&lt;/i&gt; (or "expose") your PL/SQL procedures as a SOAP Web Service, your options have so far been a bit limited.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;JDeveloper/JPublisher&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;JDeveloper has a "Publish as Web Service" feature that uses JPublisher to create various Java artifacts which must then be deployed to the application server. There are some &lt;a href="http://www.oracle.com/technology/obe/obe1013jdev/10131/wsfromplsqlpackage/devwsfrom%20plsql.htm"&gt;details here&lt;/a&gt;, and &lt;a href="http://susanduncan.blogspot.com/2006/05/creating-plsql-web-service-from-oracle.html"&gt;an issue you need to be aware of&lt;/a&gt; if you are using Oracle 10g Express Edition (XE).&lt;br /&gt;&lt;br /&gt;Now, this Java-based approach probably works fine for you if you have Java developers and a Java infrastructure in your company, although the need to (re-)generate the Java code whenever the PL/SQL code changes seems like a bit of a hassle to me.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Native Web Services (11g)&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Oracle 11g (Release 1) introduced "Native Web Services". This is a servlet running in the XDB listener that &lt;a href="http://www.oracle-base.com/articles/11g/NativeOracleXmlDbWebServices_11gR1.php"&gt;automagically exposes PL/SQL code as SOAP Web Services&lt;/a&gt;. Here is &lt;a href="http://tardate.blogspot.com/2007/08/first-tests-of-11g-native-web-services.html"&gt;some more information&lt;/a&gt; about it.&lt;br /&gt;&lt;br /&gt;If you are a database guy like me, you probably like the "Native" approach better than the JPublisher method. However, there are a couple of issues with Native Web Services; first of all, it's an 11g feature (which of course means that it is not available in 10g, nor in Express Edition 10g), and it requires the XDB listener (which means you must either allow direct connections to your database, or set up another web server as a proxy for XDB).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Automatic Web Services with the Thoth Gateway&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Since I like the concept of Native Web Services, I decided to implement a similar feature in the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;, a &lt;a href="http://ora-00001.blogspot.com/2009/08/thoth-gateway-modplsql-replacement-for.html"&gt;mod_plsql replacement&lt;/a&gt; for Microsoft IIS.&lt;br /&gt;&lt;br /&gt;Version 1.1 of the Thoth Gateway adds a new DAD configuration parameter called &lt;i&gt;InvocationProtocol&lt;/i&gt;. If this is set to "SOAP" (instead of the default "CGI"), PL/SQL called through the DAD will take its parameters from a SOAP request body, and respond with a SOAP response body.&lt;br /&gt;&lt;br /&gt;The Web Service Definition Language (WSDL) document is automatically generated if you append "?wsdl" to the end of the URL. This allows a tool like Visual Studio to easily add a Web Reference to your stored procedure.&lt;br /&gt;&lt;br /&gt;Let's see an example. Let's say we have the following package specification:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package employee_service&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;function get_employee_name (p_empno in number) return varchar2;&lt;br /&gt;&lt;br /&gt;function get_employees (p_search_filter in varchar2) return clob;&lt;br /&gt;&lt;br /&gt;end employee_service;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the following package body:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package body employee_service&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;function get_employee_name (p_empno in number) return varchar2&lt;br /&gt;as&lt;br /&gt;l_returnvalue emp.ename%type;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;select ename&lt;br /&gt;into l_returnvalue&lt;br /&gt;from emp&lt;br /&gt;where empno = p_empno;&lt;br /&gt;exception&lt;br /&gt;when no_data_found then&lt;br /&gt;l_returnvalue := null;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_employee_name;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_employees (p_search_filter in varchar2) return clob&lt;br /&gt;as&lt;br /&gt;l_context     dbms_xmlgen.ctxhandle;&lt;br /&gt;l_returnvalue clob;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;-- there are many ways to generate XML in Oracle, this is one of them...&lt;br /&gt;&lt;br /&gt;l_context := dbms_xmlgen.newcontext('select * from emp where lower(ename) like :p_filter_str order by empno');&lt;br /&gt;&lt;br /&gt;-- let's make Tom Kyte happy :-)&lt;br /&gt;dbms_xmlgen.setbindvalue (l_context, 'p_filter_str', lower(p_search_filter) || '%');&lt;br /&gt;&lt;br /&gt;l_returnvalue := dbms_xmlgen.getxml (l_context);&lt;br /&gt;&lt;br /&gt;dbms_xmlgen.closecontext (l_context);&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_employees;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end employee_service;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now navigate to the following URL with the browser (assuming you have &lt;a href="http://code.google.com/p/thoth-gateway/downloads/list"&gt;downloaded&lt;/a&gt; and installed the Thoth Gateway, of course; see the installation guide in the Doc folder):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;http://localhost/pls/soap-demo/employee_service.get_employee_name?wsdl&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This brings up the automatically generated WSDL:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_eX9l46hbfLI/SwLnf0ZSaYI/AAAAAAAAACg/wx_YluFN9TA/s1600/SNAG-0377.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5405137036624685442" src="http://2.bp.blogspot.com/_eX9l46hbfLI/SwLnf0ZSaYI/AAAAAAAAACg/wx_YluFN9TA/s320/SNAG-0377.jpg" style="cursor: hand; cursor: pointer; height: 248px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now use your favorite SOAP testing tool (I'm using &lt;a href="http://www.codeplex.com/WebserviceStudio"&gt;Web Service Studio&lt;/a&gt;, but another good tool is &lt;a href="http://www.soapui.org/"&gt;SoapUI&lt;/a&gt;) and enter the same URL.&lt;br /&gt;&lt;br /&gt;After the test tool has generated a proxy class for the Web Service, you should see something similar to the following:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_eX9l46hbfLI/SwLngGDgMAI/AAAAAAAAACo/Zo871JPudho/s1600/SNAG-0378.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5405137041365151746" src="http://1.bp.blogspot.com/_eX9l46hbfLI/SwLngGDgMAI/AAAAAAAAACo/Zo871JPudho/s320/SNAG-0378.jpg" style="cursor: hand; cursor: pointer; height: 210px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Fill in the value in the request and invoke the Web Service:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_eX9l46hbfLI/SwLngYbli5I/AAAAAAAAACw/H3UcQaXQNgU/s1600/SNAG-0379.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5405137046298004370" src="http://3.bp.blogspot.com/_eX9l46hbfLI/SwLngYbli5I/AAAAAAAAACw/H3UcQaXQNgU/s320/SNAG-0379.jpg" style="cursor: hand; cursor: pointer; height: 292px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The response from the Thoth Gateway is a SOAP envelope that contains the return value of the function.&lt;br /&gt;&lt;br /&gt;Invoking the second function in the example package above returns a CLOB with XML that represents a dataset with several rows:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_eX9l46hbfLI/SwLngoD0Q2I/AAAAAAAAAC4/4JYtegcg1U0/s1600/SNAG-0380.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5405137050493272930" src="http://3.bp.blogspot.com/_eX9l46hbfLI/SwLngoD0Q2I/AAAAAAAAAC4/4JYtegcg1U0/s320/SNAG-0380.jpg" style="cursor: hand; cursor: pointer; height: 292px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Pretty cool, heh? It "just works", with no extra code or configuration necessary, except specifying "SOAP" as the protocol in the DAD!&lt;br /&gt;&lt;br /&gt;You can use the usual parameters such as &lt;i&gt;InclusionList&lt;/i&gt;, &lt;i&gt;ExclusionList&lt;/i&gt; and &lt;i&gt;RequestValidationFunction&lt;/i&gt; to control access to specific procedures. Also, the CGI environment is set up as usual before the call, so your PL/SQL code can use owa_util.get_cgi_env to get information about the client (browser).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Limitations and Caveats&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;There are a couple of limitations in this first release of the SOAP feature:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You can only call PL/SQL functions (not procedures) via SOAP.&lt;/li&gt;&lt;li&gt;Functions must return VARCHAR2 or CLOB (but as we have seen in the example above, functions returning CLOBs allow you to return any XML as the response, so this should not really be a big limitation). Support for arrays and complex (user-defined) types might come later.&lt;/li&gt;&lt;li&gt;&lt;span style="text-decoration: line-through;"&gt;Each function is exposed as a service endpoint. This means that in Visual Studio, for example, you must create a separate Web Reference for each function you would like to call. A future version of the gateway might group all functions in a package into one service.&lt;/span&gt; &lt;b&gt;Update (Nov 28, 2009):&lt;/b&gt; As of Thoth Gateway version 1.2, all functions in a package are now grouped together as a single service endpoint.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;If you would like to try it out, go and &lt;a href="http://code.google.com/p/thoth-gateway/downloads/list"&gt;grab version 1.1 of the Thoth Gateway&lt;/a&gt; now!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1542815004850099205?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1542815004850099205/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1542815004850099205' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1542815004850099205'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1542815004850099205'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/11/publish-plsql-as-soap-web-service.html' title='Publish PL/SQL as SOAP Web Service'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_eX9l46hbfLI/SwLnf0ZSaYI/AAAAAAAAACg/wx_YluFN9TA/s72-c/SNAG-0377.jpg' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-9157339366642449537</id><published>2009-11-01T23:57:00.000-08:00</published><updated>2009-11-18T12:05:36.313-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g'/><category scheme='http://www.blogger.com/atom/ns#' term='XE'/><title type='text'>Bad news about Oracle XE 11g</title><content type='html'>We all love Oracle 10g Express Edition (XE), and I'm sure everyone's waiting for the 11g version which incorporates all the feature enhancements and security fixes from the last three years.&lt;br /&gt;&lt;br /&gt;However, &lt;a href="http://www.computerworld.com/s/article/9139290/Oracle_11g_Xpress_Edition_a_year_or_two_away"&gt;it now looks like we have to wait "another year or two"&lt;/a&gt; for the 11g version of Oracle Express Edition :-(&lt;br /&gt;&lt;br /&gt;Seriously, Oracle? No XE 11g before late 2011? That means something like &lt;span style="font-weight:bold;"&gt;six years&lt;/span&gt; between the 10g and the 11g version?&lt;br /&gt;&lt;br /&gt;Meanwhile, Microsoft is releasing its free SQL Server Express Edition on the same schedule as the for-pay version.&lt;br /&gt;&lt;br /&gt;If Oracle is serious about using Express Edition to gain converts to the Oracle database, it should seriously reconsider this decision to delay XE 11g.&lt;br /&gt;&lt;br /&gt;Leave a comment below if you would like to see Oracle Express Edition 11g before 2011!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-9157339366642449537?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/9157339366642449537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=9157339366642449537' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/9157339366642449537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/9157339366642449537'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/11/bad-news-about-oracle-xe-11g.html' title='Bad news about Oracle XE 11g'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-3752363150459789243</id><published>2009-10-14T09:01:00.000-07:00</published><updated>2009-10-15T00:08:38.283-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Interactive Reports'/><category scheme='http://www.blogger.com/atom/ns#' term='Collections'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Apex Interactive Report Tip #1: Aggregating numbers in string columns</title><content type='html'>I have been working on an Interactive Report based on a collection recently, to &lt;a href="http://www.oracleapplicationexpress.com/2009/02/interactive-report-based-on-plsql.html"&gt;work around&lt;/a&gt; the fact that the SQL query for Interactive Reports must be static, and my requirement was to run different queries based on user input.&lt;br /&gt;&lt;br /&gt;One problem is that since all columns in a collection are varchars (strings), the built-in aggregation feature of Interactive Reports does not work (it only allows you to aggregate on numbers, which is quite sensible, I guess). Normally you could always convert the string column to a number directly in the underlying query, but since my queries were dynamic (the whole point of using collections), that was not an option.&lt;br /&gt;&lt;br /&gt;However, after some fiddling around I discovered that &lt;span style="font-weight:bold;"&gt;you can aggregate based on computed columns&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;So, let's say we have this "transaction amount" column in our report, which consists of numbers but which Apex interprets as a string since it is retrieved from a varchar2 column in the apex_collections view:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_eX9l46hbfLI/StX4OSuJhbI/AAAAAAAAAB4/d6rkFYaSTJE/s1600-h/SNAG-0312.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 313px; height: 229px;" src="http://2.bp.blogspot.com/_eX9l46hbfLI/StX4OSuJhbI/AAAAAAAAAB4/d6rkFYaSTJE/s400/SNAG-0312.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5392489053273294258" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Use the "Compute" menu item of the Interactive Report to create a new column, using the TRUNC function (for some reason, the TO_NUMBER function is not available from the list of functions, but TRUNC works -- &lt;span style="font-style:italic;"&gt;just remember to include the number of decimals as the second parameter if you need decimals&lt;/span&gt;) and setting the appropriate numeric format mask:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4Oz1Yi5I/AAAAAAAAACA/w8Z0VYths-I/s1600-h/SNAG-0313.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 151px;" src="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4Oz1Yi5I/AAAAAAAAACA/w8Z0VYths-I/s400/SNAG-0313.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5392489062162008978" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We can now see that the new column has been added as a numeric column, since it is right-aligned instead of left-aligned (and also with a nice format mask):&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4PAqfLxI/AAAAAAAAACI/PGpOSI1o8tk/s1600-h/SNAG-0314.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 216px;" src="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4PAqfLxI/AAAAAAAAACI/PGpOSI1o8tk/s400/SNAG-0314.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5392489065605967634" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The new column is now available under the "Aggregate" menu item of the Interactive Report, so we can create a Sum of the numbers:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_eX9l46hbfLI/StX4PnlOp_I/AAAAAAAAACQ/xH3vbgvrDDA/s1600-h/SNAG-0315.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 202px;" src="http://4.bp.blogspot.com/_eX9l46hbfLI/StX4PnlOp_I/AAAAAAAAACQ/xH3vbgvrDDA/s400/SNAG-0315.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5392489076052895730" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;After adding a "Control Break" to the report, we can see that the aggregation works:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4P7y0tGI/AAAAAAAAACY/TaXSczZQyTE/s1600-h/SNAG-0316.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 171px;" src="http://3.bp.blogspot.com/_eX9l46hbfLI/StX4P7y0tGI/AAAAAAAAACY/TaXSczZQyTE/s400/SNAG-0316.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5392489081478624354" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Did I mention that I really like Interactive Reports? :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-3752363150459789243?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/3752363150459789243/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=3752363150459789243' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3752363150459789243'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3752363150459789243'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/10/apex-interactive-report-tip-1.html' title='Apex Interactive Report Tip #1: Aggregating numbers in string columns'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_eX9l46hbfLI/StX4OSuJhbI/AAAAAAAAAB4/d6rkFYaSTJE/s72-c/SNAG-0312.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-2345919455667502700</id><published>2009-10-12T02:42:00.000-07:00</published><updated>2009-10-12T05:59:32.932-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pdf'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>How to integrate PL_FPDF with Apex</title><content type='html'>In my &lt;a href="http://ora-00001.blogspot.com/2009/10/free-pdf-package-for-plsql.html"&gt;previous post about the free PL_FPDF package&lt;/a&gt; that allows you to produce PDF documents from PL/SQL, there was a comment asking for a step-by-step  guide on how to integrate this with Oracle Application Express (Apex).&lt;br /&gt;&lt;br /&gt;This is really quite simple. By default, the pl_fpdf.output procedure will "print" your PDF document to the web browser along with a header that instructs the browser that this is a downloadable document. All you need to do is to call your procedure that generates the PDF document from a page process within Apex.&lt;br /&gt;&lt;br /&gt;Here are the steps:&lt;br /&gt;&lt;br /&gt;1. Start by compiling the demo procedure from my previous post into the parsing schema of your Apex application.&lt;br /&gt;&lt;br /&gt;2. In Apex, go to your application and add a new, blank page. Let's assume the new page number is 4.&lt;br /&gt;&lt;br /&gt;3. Under the "Page Rendering" section, add a new Process of type "PL/SQL". The name of the process can be anything, but let's call it "Produce PDF". Make sure that the point is "On load - before header".&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_eX9l46hbfLI/StL9nzWgdoI/AAAAAAAAABw/2fF5c6pEKnA/s1600-h/SNAG-0303.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 170px;" src="http://4.bp.blogspot.com/_eX9l46hbfLI/StL9nzWgdoI/AAAAAAAAABw/2fF5c6pEKnA/s320/SNAG-0303.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5391650564157240962" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;4. In the process source, add the following PL/SQL code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;  -- call the procedure to generate the PDF document and send it to the browser&lt;br /&gt;  test_pl_fpdf;&lt;br /&gt;&lt;br /&gt;  -- stop the Apex engine from running the rest of the page&lt;br /&gt;  apex_application.g_unrecoverable_error := true;&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;5. Add a link to the new page (page number 4 if you follow my assumption in step 2) from any other page in the application.&lt;br /&gt;&lt;br /&gt;6. Run the application. Now, whenever you navigate to page 4 the PDF file will be downloaded to the browser.&lt;br /&gt;&lt;br /&gt;That's all you need to integrate PDF generation into your Apex application.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-2345919455667502700?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/2345919455667502700/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=2345919455667502700' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2345919455667502700'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2345919455667502700'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/10/how-to-integrate-plfpdf-with-apex.html' title='How to integrate PL_FPDF with Apex'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_eX9l46hbfLI/StL9nzWgdoI/AAAAAAAAABw/2fF5c6pEKnA/s72-c/SNAG-0303.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-285401862617083155</id><published>2009-10-08T08:17:00.000-07:00</published><updated>2011-03-14T01:51:46.251-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='pdf'/><title type='text'>Free PDF package for PL/SQL</title><content type='html'>I'm sure you've heard about &lt;a href="http://www.plpdf.com/"&gt;PL/PDF&lt;/a&gt;, the PL/SQL-based solution that allows you to create PDF documents directly from your database. PL/PDF is a good product, but it is not free (the current license price is USD 600 per database server).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_eX9l46hbfLI/Ss4FLcLLGpI/AAAAAAAAABo/2weYu--aOR0/s1600-h/pdf-file-logo-icon.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5390251498108820114" src="http://1.bp.blogspot.com/_eX9l46hbfLI/Ss4FLcLLGpI/AAAAAAAAABo/2weYu--aOR0/s320/pdf-file-logo-icon.jpg" style="cursor: pointer; height: 320px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;However, I just came across a free alternative called &lt;a href="http://www.erasme.org/PL-FPDF,1337"&gt;PL_FPDF&lt;/a&gt;, which is a PL/SQL port of the PHP-based &lt;a href="http://www.fpdf.org/"&gt;FPDF&lt;/a&gt;. The latest version of PL_PDF seems to have been released about a year ago, and I can't believe I haven't seen or heard about it before now... !&lt;br /&gt;&lt;br /&gt;&lt;b&gt;UPDATE, MARCH 2011:&lt;/b&gt; Also check out this alternative &lt;a href="http://technology.amis.nl/blog/8650/as_pdf-generating-a-pdf-document-with-some-plsql"&gt;PDF generation package by Anton Scheffer&lt;/a&gt;. It seems simpler and more robust than the PL_FPDF package, but may lack certain features. Be sure to evaluate both!&lt;br /&gt;&lt;br /&gt;PL_FPDF is just a single package, so installation is a snap. Note that it uses the ORDImage data type to place images in PDF documents, so if you are running Oracle XE (which doesn't include the ORDImage data type), you need to comment out the few procedures that deal with this data type (and obviously you will not be able to include images in your PDF documents...).&lt;br /&gt;&lt;br /&gt;After a few hours of experimentation, I was able to produce a semi-complex PDF document that tests a number of features in PL_FPDF. My test script is included below for your convenience. Note that the default assumption is that output goes to the web browser via the HTP package, but it should be trivial to add a procedure that saves the resulting BLOB to a database table instead.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Note:&lt;/span&gt; Since PL_FPDF is based on FPDF, I found the online documentation for FPDF (see link above) very useful in order to find out how the API for PL_FPDF works.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace procedure test_pl_fpdf&lt;br /&gt;as&lt;br /&gt;l_text varchar2(32000) := 'First, reduce actual complexity by eliminating unnecessary features and then hiding what you can''t eliminate. Secondly, reduce perceived complexity by minimizing visual noise and reusing elements. And finally, use the blank state to help orient users. Minimizing complexity in the user interface will help people learn your application more quickly, use it more effectively and be happier all the while.&lt;br /&gt;&lt;br /&gt;As jazz musician Charles Mingus said, "Making the simple complicated is commonplace; making the complicated simple, awesomely simple, that''s creativity."';&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;-- create document in portrait mode, use measurements in millimeters (mm), and use the A4 page format&lt;br /&gt;pl_fpdf.FPDF('P','mm','A4');&lt;br /&gt;pl_fpdf.openpdf;&lt;br /&gt;&lt;br /&gt;-- display full page, two facing pages &lt;br /&gt;pl_fpdf.setdisplaymode ('fullpage', 'two');&lt;br /&gt;&lt;br /&gt;-- a procedure that will be called at the end of each page&lt;br /&gt;-- pl_fpdf.setfooterproc('demo.test_pdf_footer');  &lt;br /&gt;&lt;br /&gt;pl_fpdf.AddPage();&lt;br /&gt;&lt;br /&gt;-- set up some headers&lt;br /&gt;pl_fpdf.SetFillColor(255,128,128);&lt;br /&gt;pl_fpdf.SetFont('Arial','B',14);&lt;br /&gt;--this header will be filled with the background color &lt;br /&gt;pl_fpdf.cell(40,7,'TABLESPACE', 1, 0, 'L', pfill =&amp;gt; 1);&lt;br /&gt;pl_fpdf.cell(40,7,'CONTENTS', 1);&lt;br /&gt;pl_fpdf.cell(40,7,'INITIAL EXTENT', 1, 1);&lt;br /&gt;&lt;br /&gt;pl_fpdf.SetFont('Arial','',14);&lt;br /&gt;&lt;br /&gt;for l_rec in (select tablespace_name, contents, initial_extent from dba_tablespaces order by 1) loop&lt;br /&gt;&lt;br /&gt;pl_fpdf.settextcolor(0,0,0);&lt;br /&gt;pl_fpdf.cell(40,7,l_rec.tablespace_name,'B');&lt;br /&gt;pl_fpdf.cell(40,7,l_rec.contents,'B');&lt;br /&gt;-- some conditional formatting&lt;br /&gt;if l_rec.initial_extent &amp;gt; 66000 then&lt;br /&gt;pl_fpdf.settextcolor(255,0,0);&lt;br /&gt;else&lt;br /&gt;pl_fpdf.settextcolor(0,0,0);&lt;br /&gt;end if;&lt;br /&gt;pl_fpdf.cell(40,7,l_rec.initial_extent,'B', 1, 'R');&lt;br /&gt;&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;-- a page that shows how to position chunks of text (with automatic line breaks) on the page&lt;br /&gt;&lt;br /&gt;pl_fpdf.AddPage();&lt;br /&gt;&lt;br /&gt;pl_fpdf.SetFont('Arial','B',16);&lt;br /&gt;pl_fpdf.setxy (100, 20);&lt;br /&gt;pl_fpdf.Cell(10,10,'Cool Quote 1:');&lt;br /&gt;&lt;br /&gt;pl_fpdf.setxy (100, 50);&lt;br /&gt;pl_fpdf.SetFont('Times','',12);&lt;br /&gt;pl_fpdf.multicell(100,0,l_text);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;pl_fpdf.SetFont('Arial','B',16);&lt;br /&gt;pl_fpdf.setxy (10, 130);&lt;br /&gt;pl_fpdf.Cell(10,10,'Cool Quote 2:');&lt;br /&gt;&lt;br /&gt;pl_fpdf.setxy (10, 150);&lt;br /&gt;pl_fpdf.SetFont('Times','',12);&lt;br /&gt;pl_fpdf.multicell(100,0,l_text);&lt;br /&gt;&lt;br /&gt;-- a page that demonstrates some simple drawing with lines and rectangles&lt;br /&gt;&lt;br /&gt;pl_fpdf.AddPage();&lt;br /&gt;&lt;br /&gt;pl_fpdf.SetFont('Arial','B',14);&lt;br /&gt;pl_fpdf.Cell(0,0,'And now, some beautiful line art...',0,1,'C');&lt;br /&gt;&lt;br /&gt;pl_fpdf.line (10,10, 50, 50);&lt;br /&gt;pl_fpdf.rect (50,50, 50, 50);&lt;br /&gt;pl_fpdf.setdrawcolor(0,255,0);&lt;br /&gt;pl_fpdf.line (150,150, 50, 50);&lt;br /&gt;&lt;br /&gt;pl_fpdf.setdrawcolor(0,0,0);&lt;br /&gt;&lt;br /&gt;-- a simple table of employees, without headings&lt;br /&gt;&lt;br /&gt;pl_fpdf.AddPage();&lt;br /&gt;&lt;br /&gt;for l_rec in (select empno, ename from emp order by ename) loop&lt;br /&gt;&lt;br /&gt;pl_fpdf.cell(40,7,l_rec.empno, 'B');&lt;br /&gt;pl_fpdf.cell(40,7, l_rec.ename, 'B', 1);&lt;br /&gt;&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;pl_fpdf.SetFont('Arial','B',48);&lt;br /&gt;pl_fpdf.settextcolor(0,0,255);&lt;br /&gt;&lt;br /&gt;pl_fpdf.setxy (100, 250);&lt;br /&gt;&lt;br /&gt;pl_fpdf.cell(80,10,'THE END', palign =&amp;gt; 'C');&lt;br /&gt;&lt;br /&gt;pl_fpdf.Output();&lt;br /&gt;&lt;br /&gt;end test_pl_fpdf;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Conclusion:&lt;/span&gt; While PL/PDF is more advanced in terms of features, PL_FPDF might be for you if you just need some simple PDF reports in your application.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-285401862617083155?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/285401862617083155/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=285401862617083155' title='23 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/285401862617083155'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/285401862617083155'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/10/free-pdf-package-for-plsql.html' title='Free PDF package for PL/SQL'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_eX9l46hbfLI/Ss4FLcLLGpI/AAAAAAAAABo/2weYu--aOR0/s72-c/pdf-file-logo-icon.jpg' height='72' width='72'/><thr:total>23</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4064761349912545592</id><published>2009-10-05T01:47:00.000-07:00</published><updated>2009-10-05T02:03:24.302-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pl/sql'/><category scheme='http://www.blogger.com/atom/ns#' term='db2'/><title type='text'>Native PL/SQL support in DB2</title><content type='html'>As reported by Steven Feuerstein in his PL/SQL programming newsletter this month, it is now possible to compile and run PL/SQL applications in IBM's DB2 database! Check out this &lt;a href="http://www.youtube.com/watch?v=EnpDMvobUmE"&gt;demonstration video&lt;/a&gt; and &lt;a href="http://www.ibm.com/developerworks/data/library/techarticle/dm-0907oracleappsondb2/index.html"&gt;more details at IBM's Developerworks&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Of course, the list of supported built-in packages is rather short (for example, UTL_HTTP and the XML packages are missing), but they do have support for REF CURSORS, CONNECT BY, and other advanced Oracle features.&lt;br /&gt;&lt;br /&gt;This is cool, because it opens up a new market for all us PL/SQL developers out here.&lt;br /&gt;&lt;br /&gt;Worth mentioning, too, is that &lt;a href="http://www.enterprisedb.com/"&gt;EnterpriseDB&lt;/a&gt; (based on PostgreSQL) also has PL/SQL support.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4064761349912545592?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4064761349912545592/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4064761349912545592' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4064761349912545592'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4064761349912545592'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/10/native-plsql-support-in-db2.html' title='Native PL/SQL support in DB2'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-3458970037469007812</id><published>2009-08-18T08:36:00.001-07:00</published><updated>2009-08-18T08:48:12.886-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Oracle HTTP Server'/><category scheme='http://www.blogger.com/atom/ns#' term='mod_plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='Thoth Gateway'/><category scheme='http://www.blogger.com/atom/ns#' term='DBPrism'/><category scheme='http://www.blogger.com/atom/ns#' term='DBMS_EPG'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Thoth Gateway: mod_plsql replacement for Microsoft IIS</title><content type='html'>Take a look at this screenshot:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_eX9l46hbfLI/SorKh8LR94I/AAAAAAAAABQ/CcsjBZgwfqw/s1600-h/apex-running-on-iis-using-thoth.jpg"&gt;&lt;img style="cursor: pointer; width: 400px; height: 312px;" src="http://1.bp.blogspot.com/_eX9l46hbfLI/SorKh8LR94I/AAAAAAAAABQ/CcsjBZgwfqw/s400/apex-running-on-iis-using-thoth.jpg" alt="" id="BLOGGER_PHOTO_ID_5371328190030673794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Yes, that's right... &lt;span style="font-weight: bold;"&gt;Oracle Application Express running on Microsoft's Internet Information Server (IIS)!&lt;/span&gt; In-between trips to the beach this summer, I decided to write a PL/SQL gateway module for IIS. The result, called the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt;, is now available as open source.&lt;br /&gt;&lt;br /&gt;This means that the deployment options for PL/SQL web applications are now:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The Embedded PL/SQL Gateway (DBMS_EPG), which is a webserver built into the database itself&lt;/li&gt;&lt;li&gt;The Oracle HTTP Server (OHS), which is based on the Apache codebase, with mod_plsql&lt;/li&gt;&lt;li&gt;Java-based web servers (such as Tomcat, WebLogic, and others) with the open-source &lt;a href="http://ora-00001.blogspot.com/2008/12/adventures-with-apex-part-three-setting.html"&gt;DBPrism&lt;/a&gt; plugin, or the upcoming, Oracle-supported Apex Listener&lt;/li&gt;&lt;li&gt; And now, Microsoft Internet Information Server (IIS) with the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;Thoth Gateway&lt;/a&gt; module&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_eX9l46hbfLI/SorK1Cb1-BI/AAAAAAAAABY/U2eWTLFOkP0/s1600-h/thoth-gateway-architecture.jpg"&gt;&lt;img style="cursor: pointer; width: 400px; height: 134px;" src="http://2.bp.blogspot.com/_eX9l46hbfLI/SorK1Cb1-BI/AAAAAAAAABY/U2eWTLFOkP0/s400/thoth-gateway-architecture.jpg" alt="" id="BLOGGER_PHOTO_ID_5371328518128269330" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The Thoth Gateway is implemented in C# as an ASP.NET HttpModule and uses the Oracle Data Provider for .NET (ODP.NET) to communicate with the database.&lt;br /&gt;&lt;br /&gt;The Thoth Gateway supports almost all of mod_plsql's features, and even adds a few more (such as CLOB parameter support for values over 32K). Check out the &lt;a href="http://code.google.com/p/thoth-gateway/"&gt;project page&lt;/a&gt; for the details.&lt;br /&gt;&lt;br /&gt;It's also worth noting that the Thoth Gateway, being a volunteer project, is not officially supported by Oracle in any way. But at least now you have the option of using Microsoft's IIS as the web server for Oracle PL/SQL web applications. In fact, you can use any of the above listed gateways side-by-side for the same PL/SQL web application (all on the same box, or on different physical servers).&lt;br /&gt;&lt;br /&gt;I am very interested in feedback if you decide to go ahead and try the Thoth Gateway, so feel free to add comments to this post about any bugs or problems you encounter, or use the project's &lt;a href="http://code.google.com/p/thoth-gateway/issues/list"&gt;issue tracker&lt;/a&gt; (adding issues requires a Google account).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-3458970037469007812?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/3458970037469007812/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=3458970037469007812' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3458970037469007812'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3458970037469007812'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/08/thoth-gateway-modplsql-replacement-for.html' title='Thoth Gateway: mod_plsql replacement for Microsoft IIS'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_eX9l46hbfLI/SorKh8LR94I/AAAAAAAAABQ/CcsjBZgwfqw/s72-c/apex-running-on-iis-using-thoth.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-1905535936472058955</id><published>2009-07-14T06:16:00.000-07:00</published><updated>2010-10-07T02:22:03.396-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SOAP'/><title type='text'>Calling a SOAP web service from PL/SQL by extending the FLEX_WS_API</title><content type='html'>Jason Straub has written a &lt;a href="http://jastraub.blogspot.com/2008/06/flexible-web-service-api.html"&gt;Flexible Web Service API&lt;/a&gt; package that allows you to call SOAP web services from PL/SQL. The API handles a lot of low-level details for you. As far as I know, the API will be incorporated into the upcoming Apex 4.0.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The FLEX_WS_API package is very useful; however, there are still a few things, such as constructing the SOAP envelope and logging requests and response for debugging purposes, that you need to implement yourself.&lt;br /&gt;&lt;br /&gt;Here are a few helpers that I have written to do just that:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;Web service log table&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First, let's make a table that can be used to log web service requests and responses.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create table ws_log (&lt;br /&gt;request_start_date  date,&lt;br /&gt;request_end_date    date default sysdate,&lt;br /&gt;log_text            varchar2(4000),&lt;br /&gt;ws_url              varchar2(4000),&lt;br /&gt;ws_method           varchar2(4000),&lt;br /&gt;ws_request          clob,&lt;br /&gt;ws_response         clob,&lt;br /&gt;val1                varchar2(4000),&lt;br /&gt;val2                varchar2(4000),&lt;br /&gt;val3                varchar2(4000)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;Web service utility package&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is the header of a package that handles logging, and also simplifies the extraction of values from the web service response.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package flex_ws_util&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    The package is a companion to the flex_ws_api package&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- get string value&lt;br /&gt;function get_value (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in varchar2 := null) return varchar2;&lt;br /&gt;&lt;br /&gt;-- get clob value&lt;br /&gt;function get_value_clob (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in varchar2 := null) return clob;&lt;br /&gt;&lt;br /&gt;-- get date value&lt;br /&gt;function get_value_date (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in date := null,&lt;br /&gt;p_date_format in varchar2 := null) return date;&lt;br /&gt;&lt;br /&gt;-- get number value&lt;br /&gt;function get_value_number (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in number := null) return number;&lt;br /&gt;&lt;br /&gt;-- log web service request&lt;br /&gt;procedure log_request (p_url in varchar2,&lt;br /&gt;p_method in varchar2,&lt;br /&gt;p_request in clob,&lt;br /&gt;p_response in xmltype,&lt;br /&gt;p_request_start_date in date := null,&lt;br /&gt;p_log_text in varchar2 := null,&lt;br /&gt;p_val1 in varchar2 := null,&lt;br /&gt;p_val2 in varchar2 := null,&lt;br /&gt;p_val3 in varchar2 := null);&lt;br /&gt;&lt;br /&gt;end flex_ws_util;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And then the package body:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package body flex_ws_util&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    The package is a companion to the flex_ws_api package&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_value (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in varchar2 := null) return varchar2&lt;br /&gt;as&lt;br /&gt;l_returnvalue varchar2(32767);&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Get string value from web service response&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_returnvalue := flex_ws_api.parse_xml (p_xml, '//' || p_name || '/text()', p_namespace);&lt;br /&gt;exception&lt;br /&gt;when others then&lt;br /&gt;l_returnvalue := nvl(p_value_if_error, sqlerrm);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_value;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_value_clob (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in varchar2 := null) return clob&lt;br /&gt;as&lt;br /&gt;l_returnvalue clob;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Get clob value from web service response&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_returnvalue := flex_ws_api.parse_xml_clob (p_xml, '//' || p_name || '/text()', p_namespace);&lt;br /&gt;exception&lt;br /&gt;when others then&lt;br /&gt;l_returnvalue := nvl(p_value_if_error, sqlerrm);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_value_clob;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_value_date (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in date := null,&lt;br /&gt;p_date_format in varchar2 := null) return date&lt;br /&gt;as&lt;br /&gt;l_str         varchar2(32767);&lt;br /&gt;l_returnvalue date;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Get date value from web service response&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_str := flex_ws_api.parse_xml (p_xml, '//' || p_name || '/text()', p_namespace);&lt;br /&gt;l_returnvalue := to_date (l_str, nvl(p_date_format, 'DD.MM.RRRR HH24:MI:SS'));&lt;br /&gt;exception&lt;br /&gt;when others then&lt;br /&gt;l_returnvalue := p_value_if_error;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_value_date;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function get_value_number (p_xml in xmltype,&lt;br /&gt;p_name in varchar2,&lt;br /&gt;p_namespace in varchar2 := null,&lt;br /&gt;p_value_if_error in number := null) return number&lt;br /&gt;as&lt;br /&gt;l_str         varchar2(32767);&lt;br /&gt;l_returnvalue number;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Get number value from web service response&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_str := flex_ws_api.parse_xml (p_xml, '//' || p_name || '/text()', p_namespace);&lt;br /&gt;l_returnvalue := to_number (l_str);&lt;br /&gt;exception&lt;br /&gt;when others then&lt;br /&gt;l_returnvalue := p_value_if_error;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;return l_returnvalue;&lt;br /&gt;&lt;br /&gt;end get_value_number;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure log_request (p_url in varchar2,&lt;br /&gt;p_method in varchar2,&lt;br /&gt;p_request in clob,&lt;br /&gt;p_response in xmltype,&lt;br /&gt;p_request_start_date in date := null,&lt;br /&gt;p_log_text in varchar2 := null,&lt;br /&gt;p_val1 in varchar2 := null,&lt;br /&gt;p_val2 in varchar2 := null,&lt;br /&gt;p_val3 in varchar2 := null)&lt;br /&gt;as&lt;br /&gt;pragma autonomous_transaction;&lt;br /&gt;l_sysdate date := sysdate;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Log web service request&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;insert into ws_log (request_start_date, request_end_date,&lt;br /&gt;log_text, ws_url, ws_method,&lt;br /&gt;ws_request, ws_response,&lt;br /&gt;val1, val2, val3)&lt;br /&gt;values (nvl(p_request_start_date, l_sysdate), l_sysdate,&lt;br /&gt;substr(p_log_text,1,4000), substr(p_url,1,4000), substr(p_method,1,4000),&lt;br /&gt;p_request, p_response.getclobval(),&lt;br /&gt;substr(p_val1,1,4000),substr(p_val2,1,4000), substr(p_val3,1,4000));&lt;br /&gt;&lt;br /&gt;commit;&lt;br /&gt;&lt;br /&gt;end log_request;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end flex_ws_util;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;PL/SQL object type for SOAP envelopes&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The following object type is used to simplify creation of SOAP envelopes to be used in web service calls:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace TYPE t_soap_envelope AS OBJECT (&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Object type to handle SOAP envelopes for web service calls&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- public properties&lt;br /&gt;service_namespace       varchar2(255),&lt;br /&gt;service_method          varchar2(4000),&lt;br /&gt;service_host            varchar2(4000),&lt;br /&gt;service_path            varchar2(4000),&lt;br /&gt;service_url             varchar2(4000),&lt;br /&gt;soap_action             varchar2(4000),&lt;br /&gt;soap_namespace          varchar2(255),&lt;br /&gt;envelope                clob,&lt;br /&gt;&lt;br /&gt;-- private properties&lt;br /&gt;m_parameters            clob,&lt;br /&gt;&lt;br /&gt;constructor function t_soap_envelope (p_service_host in varchar2,&lt;br /&gt;p_service_path in varchar2,&lt;br /&gt;p_service_method in varchar2,&lt;br /&gt;p_service_namespace in varchar2 := null,&lt;br /&gt;p_soap_namespace in varchar2 := null,&lt;br /&gt;p_soap_action in varchar2 := null) return self as result,&lt;br /&gt;&lt;br /&gt;member procedure add_param (p_name in varchar2,&lt;br /&gt;p_value in varchar2,&lt;br /&gt;p_type in varchar2 := null),&lt;br /&gt;&lt;br /&gt;member procedure add_xml (p_xml in clob),&lt;br /&gt;&lt;br /&gt;member procedure build_env,&lt;br /&gt;&lt;br /&gt;member procedure debug_envelope&lt;br /&gt;&lt;br /&gt;);&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The type body is implemented like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace type body t_soap_envelope&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:    Object type to handle SOAP envelopes for web service calls&lt;br /&gt;&lt;br /&gt;Remarks:  &lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  -------------------------------------&lt;br /&gt;MBR     17.02.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;constructor function t_soap_envelope (p_service_host in varchar2,&lt;br /&gt;p_service_path in varchar2,&lt;br /&gt;p_service_method in varchar2,&lt;br /&gt;p_service_namespace in varchar2 := null,&lt;br /&gt;p_soap_namespace in varchar2 := null,&lt;br /&gt;p_soap_action in varchar2 := null) return self as result&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;self.service_host := p_service_host;&lt;br /&gt;self.service_path := p_service_path;&lt;br /&gt;self.service_method := p_service_method;&lt;br /&gt;self.service_namespace := nvl(p_service_namespace, 'xmlns="' || p_service_host || '/"');&lt;br /&gt;self.service_url := p_service_host || '/' || p_service_path;&lt;br /&gt;self.soap_namespace := nvl(p_soap_namespace, 'soap');&lt;br /&gt;self.soap_action := nvl(p_soap_action, p_service_host || '/' || p_service_method);&lt;br /&gt;self.envelope := '';&lt;br /&gt;build_env;&lt;br /&gt;return;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;member procedure add_param (p_name in varchar2,&lt;br /&gt;p_value in varchar2,&lt;br /&gt;p_type in varchar2 := null)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;if p_type is null then&lt;br /&gt;m_parameters := m_parameters || chr(13) || '  &amp;lt;' || p_name || '&amp;gt;' || p_value || '&amp;lt;/' || p_name || '&amp;gt;';&lt;br /&gt;else&lt;br /&gt;m_parameters := m_parameters || chr(13) || '  &amp;lt;' || p_name || ' xsi:type="' || p_type || '"&amp;gt;' || p_value || '&amp;lt;/' || p_name || '&amp;gt;';&lt;br /&gt;end if;&lt;br /&gt;build_env;&lt;br /&gt;&lt;br /&gt;end add_param;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;member procedure add_xml (p_xml in clob)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;m_parameters := m_parameters || chr(13) || p_xml;&lt;br /&gt;build_env;&lt;br /&gt;&lt;br /&gt;end add_xml;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;member procedure build_env (self in out t_soap_envelope)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;self.envelope := '&amp;lt;' || self.soap_namespace || ':Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:' || self.soap_namespace || '="http://schemas.xmlsoap.org/soap/envelope/"&amp;gt;' ||&lt;br /&gt;'&amp;lt;' || self.soap_namespace || ':Body&amp;gt;' ||&lt;br /&gt;'&amp;lt;' || self.service_method || ' ' || self.service_namespace || '&amp;gt;' ||&lt;br /&gt;self.m_parameters || chr(13) ||&lt;br /&gt;'&amp;lt;/' || self.service_method || '&amp;gt;' ||&lt;br /&gt;'&amp;lt;/' || self.soap_namespace || ':Body&amp;gt;' ||&lt;br /&gt;'&amp;lt;/' || self.soap_namespace || ':Envelope&amp;gt;';    &lt;br /&gt;&lt;br /&gt;end build_env;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;member procedure debug_envelope&lt;br /&gt;as&lt;br /&gt;i      pls_integer;&lt;br /&gt;l_len  pls_integer;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;if envelope is not null then&lt;br /&gt;&lt;br /&gt;i := 1; l_len := length(envelope);&lt;br /&gt;&lt;br /&gt;while (i &amp;lt;= l_len) loop&lt;br /&gt;dbms_output.put_line(substr(envelope, i, 200));&lt;br /&gt;i := i + 200;&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;else&lt;br /&gt;dbms_output.put_line ('WARNING: The envelope is empty...');&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end debug_envelope;&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;Example of use&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;With the above objects created in your database, the code for calling a web service and extracting and logging the results now becomes simple and elegant like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;l_env          t_soap_envelope;&lt;br /&gt;l_xml          xmltype;&lt;br /&gt;l_val          varchar2(4000);&lt;br /&gt;l_start_date   date;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;l_env := t_soap_envelope ('http://www.webserviceX.NET', 'length.asmx', 'ChangeLengthUnit', 'xmlns="http://www.webserviceX.NET/"');&lt;br /&gt;&lt;br /&gt;l_env.add_param ('LengthValue', '100');&lt;br /&gt;l_env.add_param ('fromLengthUnit', 'Feet');&lt;br /&gt;l_env.add_param ('toLengthUnit', 'Meters');&lt;br /&gt;&lt;br /&gt;l_start_date := sysdate;&lt;br /&gt;&lt;br /&gt;l_xml := flex_ws_api.make_request(p_url =&amp;gt; l_env.service_url, p_action =&amp;gt; l_env.soap_action, p_envelope =&amp;gt; l_env.envelope);&lt;br /&gt;&lt;br /&gt;l_val := flex_ws_util.get_value (l_xml, 'ChangeLengthUnitResult', l_env.service_namespace, 'error');&lt;br /&gt;&lt;br /&gt;flex_ws_util.log_request (l_env.service_url, l_env.service_method, l_env.envelope, l_xml, l_start_date, p_log_text =&amp;gt; 'Converting 100 feet to meters', p_val1 =&amp;gt; l_val);&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you have complex parameters that you need to add to the request, you can use the &lt;span style="font-style: italic;"&gt;add_xml&lt;/span&gt; member procedure of the t_soap_envelope type to add any content to the envelope.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-1905535936472058955?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/1905535936472058955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=1905535936472058955' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1905535936472058955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/1905535936472058955'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/07/calling-soap-web-service-from-plsql-by.html' title='Calling a SOAP web service from PL/SQL by extending the FLEX_WS_API'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4035577766723267321</id><published>2009-07-05T05:50:00.000-07:00</published><updated>2010-07-23T11:57:31.817-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web services'/><category scheme='http://www.blogger.com/atom/ns#' term='DBMS_EPG'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Creating a REST web service with PL/SQL (and making pretty URLs for your Apex apps)</title><content type='html'>&lt;a href="http://2.bp.blogspot.com/_eX9l46hbfLI/SlCnMpbN_fI/AAAAAAAAABI/AtUXuexKPSY/s1600-h/SNAG-0241.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5354963792663805426" src="http://2.bp.blogspot.com/_eX9l46hbfLI/SlCnMpbN_fI/AAAAAAAAABI/AtUXuexKPSY/s320/SNAG-0241.jpg" style="cursor: pointer; height: 123px; width: 320px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you need to expose the data in your Oracle database to other systems in a standardized and technology-neutral way, web services are a natural choice. Broadly speaking, web services come in two flavors: &lt;a href="http://en.wikipedia.org/wiki/SOAP_%28protocol%29"&gt;SOAP &lt;/a&gt;and &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"&gt;REST&lt;/a&gt;. Despite its name, the Simple Object Access Protocol (SOAP) can be complex and overkill for many common scenarios. Representational State Transfer (REST) is considered more lightweight and easy to use.&lt;br /&gt;&lt;br /&gt;There is a good introduction to REST here: &lt;a href="http://www.xfront.com/REST-Web-Services.html"&gt;http://www.xfront.com/REST-Web-Services.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A key point is that REST is not really a standard, but an architectural style. It is not limited to web services consumed by machines, either. Applications built using Ruby on Rails and the ASP.NET MVC framework typically have user-friendly URLs based on the REST principles.&lt;br /&gt;&lt;br /&gt;For example, if you have been to &lt;a href="http://www.stackoverflow.com/"&gt;StackOverflow.com&lt;/a&gt;, you will see URLs like the following, which are "clean" and friendly both to users and to search engines:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;http://stackoverflow.com/users&lt;br /&gt;http://stackoverflow.com/users/1/jeff-atwood&lt;br /&gt;http://stackoverflow.com/questions&lt;br /&gt;http://stackoverflow.com/questions/tagged/oracle&lt;br /&gt;http://stackoverflow.com/questions/1078506/oracle-sql-developer-how-to-view-results-from-a-ref-cursor&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So, REST is a good way of exposing resources on the web, and your Oracle database is full of resources (data), but how can you build a REST service using only PL/SQL?&lt;br /&gt;&lt;br /&gt;The key to building a REST service in PL/SQL is in a documented, but little-used feature of the Embedded PL/SQL Gateway (and mod_plsql) called "Path Aliasing". I was not aware that this feature existed until I discovered it "by accident" while browsing the mod_plsql documentation:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://download.oracle.com/docs/cd/A97335_02/apps.102/a90099/feature.htm#1007126"&gt;http://download.oracle.com/docs/cd/A97335_02/apps.102/a90099/feature.htm#1007126&lt;/a&gt;&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;&lt;br /&gt;"If the PL/SQL Gateway encounters in an incoming URL the keyword entered in the Path Alias field, it invokes the procedure entered in the Path Alias Procedure field. (...) Applications that use path aliasing must implement the Path Alias Procedure. The procedure receives the rest of the URL (path_alias_URL) after the keyword, URL, as a single parameter, and is therefore responsible and also fully capable of dereferencing the object from the URL. Although there is no restriction on the name and location for this procedure, it can accept only a single parameter, p_path, with the datatype varchar2."&lt;/blockquote&gt;&lt;br /&gt;Sounds good, so let's try it out. First, we need to configure the Database Access Descriptor (DAD) to define a PL/SQL procedure which will handle our REST requests. Using the embedded gateway (DBMS_EPG), the attributes are called "path-alias" and "path-alias-procedure" (the corresponding DAD attributes for mod_plsql are "PlsqlPathAlias" and "PlsqlPathAliasProcedure").&lt;br /&gt;&lt;br /&gt;I will be using the embedded gateway for this example. Assuming you have an existing DAD called "devtest", run the following as user SYS (or another user who has the privileges to modify the EPG configuration).&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;begin&lt;br /&gt;dbms_epg.set_dad_attribute (dad_name =&amp;gt; 'devtest', attr_name =&amp;gt; 'path-alias', attr_value =&amp;gt; 'rest-demo');&lt;br /&gt;dbms_epg.set_dad_attribute (dad_name =&amp;gt; 'devtest', attr_name =&amp;gt; 'path-alias-procedure', attr_value =&amp;gt; 'rest_handler.handle_request');&lt;br /&gt;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Then we need to create the procedure itself. Run the following in the schema associated with the DAD:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package rest_handler&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:      A simple example of RESTful web services with PL/SQL (see http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services)&lt;br /&gt;&lt;br /&gt;Remarks:      The DAD must be configured to use a path-alias and path-alias-procedure&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  --------------------------------&lt;br /&gt;MBR     05.07.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- the main procedure that will handle all incoming requests&lt;br /&gt;procedure handle_request (p_path in varchar2);&lt;br /&gt;&lt;br /&gt;end rest_handler;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And then the package body (the example assumes that the EMP and DEPT demo tables exist in your schema; if not, then modify the code accordingly):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package body rest_handler&lt;br /&gt;as&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:      A simple example of RESTful web services with PL/SQL (see http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services)&lt;br /&gt;&lt;br /&gt;Remarks:      The DAD must be configured to use a path-alias and path-alias-procedure&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  --------------------------------&lt;br /&gt;MBR     05.07.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;g_request_method_get           constant varchar2(10) := 'GET';&lt;br /&gt;g_request_method_post          constant varchar2(10) := 'POST';&lt;br /&gt;g_request_method_put           constant varchar2(10) := 'PUT';&lt;br /&gt;g_request_method_delete        constant varchar2(10) := 'DELETE';&lt;br /&gt;&lt;br /&gt;g_resource_type_employees      constant varchar2(255) := 'employees';&lt;br /&gt;g_resource_type_departments    constant varchar2(255) := 'departments';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure handle_emp (p_request_method in varchar2,&lt;br /&gt;p_id in number)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:      Specific handler for Employees&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  --------------------------------&lt;br /&gt;MBR     05.07.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;if (p_id is not null) then&lt;br /&gt;&lt;br /&gt;if p_request_method = g_request_method_delete then&lt;br /&gt;&lt;br /&gt;delete&lt;br /&gt;from emp&lt;br /&gt;where empno = p_id;&lt;br /&gt;&lt;br /&gt;elsif p_request_method = g_request_method_get then&lt;br /&gt;&lt;br /&gt;for l_rec in (select * from emp where empno = p_id) loop&lt;br /&gt;htp.p(l_rec.empno || ';' || l_rec.ename || ';' || l_rec.sal);&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;else&lt;br /&gt;&lt;br /&gt;if p_request_method = g_request_method_get then&lt;br /&gt;&lt;br /&gt;for l_rec in (select * from emp order by empno) loop&lt;br /&gt;htp.p(l_rec.empno || ';' || l_rec.ename || ';' || l_rec.sal || '&lt;br /&gt;');&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end handle_emp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure handle_dept (p_request_method in varchar2,&lt;br /&gt;p_id in number)&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:      Specific handler for Departments&lt;br /&gt;&lt;br /&gt;Remarks:&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  --------------------------------&lt;br /&gt;MBR     05.07.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;if (p_id is not null) then&lt;br /&gt;&lt;br /&gt;if p_request_method = g_request_method_delete then&lt;br /&gt;&lt;br /&gt;delete&lt;br /&gt;from dept&lt;br /&gt;where deptno = p_id;&lt;br /&gt;&lt;br /&gt;elsif p_request_method = g_request_method_get then&lt;br /&gt;&lt;br /&gt;for l_rec in (select * from dept where deptno = p_id) loop&lt;br /&gt;htp.p(l_rec.deptno || ';' || l_rec.dname || ';' || l_rec.loc);&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;else&lt;br /&gt;&lt;br /&gt;if p_request_method = g_request_method_get then&lt;br /&gt;&lt;br /&gt;for l_rec in (select * from dept order by deptno) loop&lt;br /&gt;htp.p(l_rec.deptno || ';' || l_rec.dname || ';' || l_rec.loc || '&lt;br /&gt;');&lt;br /&gt;end loop;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end if;&lt;br /&gt;&lt;br /&gt;end handle_dept;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;procedure handle_request (p_path in varchar2)&lt;br /&gt;as&lt;br /&gt;l_request_method constant varchar2(10) := owa_util.get_cgi_env('REQUEST_METHOD');&lt;br /&gt;l_path_elements  apex_application_global.vc_arr2;&lt;br /&gt;l_resource       varchar2(2000);&lt;br /&gt;l_id             number;&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;&lt;br /&gt;Purpose:      The main procedure that will handle all incoming requests&lt;br /&gt;&lt;br /&gt;Remarks:      Parses the incoming path and calls a specific handler for each resource type&lt;br /&gt;&lt;br /&gt;Who     Date        Description&lt;br /&gt;------  ----------  --------------------------------&lt;br /&gt;MBR     05.07.2009  Created&lt;br /&gt;&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;-- note that an extra delimiter is added to the path, in case the user leaves out the trailing slash&lt;br /&gt;l_path_elements := apex_util.string_to_table (p_path || '/', '/');&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;l_resource := l_path_elements(1);&lt;br /&gt;l_id := l_path_elements(2);&lt;br /&gt;exception&lt;br /&gt;when value_error or no_data_found then&lt;br /&gt;l_resource := null;&lt;br /&gt;l_id := null;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;case lower(l_resource)&lt;br /&gt;when g_resource_type_employees then&lt;br /&gt;handle_emp (l_request_method, l_id);&lt;br /&gt;when g_resource_type_departments then&lt;br /&gt;handle_dept (l_request_method, l_id);&lt;br /&gt;when 'apex-employees' then&lt;br /&gt;-- we can also use this REST handler to make pretty, search-engine-friendly URLs for Apex applications without having to use Apache mod_rewrite&lt;br /&gt;apex_application.g_flow_id := 104;&lt;br /&gt;owa_util.redirect_url('http://127.0.0.1:8080/apex/f?p=104:2:' || apex_custom_auth.get_session_id_from_cookie || '::::P2_EMPNO:' || l_id, true);&lt;br /&gt;else&lt;br /&gt;owa_util.status_line(404, 'Resource type not recognized.', true);&lt;br /&gt;end case;&lt;br /&gt;&lt;br /&gt;end handle_request;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;end rest_handler;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now try the following URLs in your browser, and you should be able to see the familiar EMP and DEPT data, in all their RESTful glory! Note that for simplicity, the example code produces simple semicolon-separated values, but depending on your requirements and who will consume the service (machine or human), you will probably want to use XML or HTML as the output format.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;http://127.0.0.1:8080/devtest/rest-demo/departments&lt;br /&gt;http://127.0.0.1:8080/devtest/rest-demo/departments/10&lt;br /&gt;http://127.0.0.1:8080/devtest/rest-demo/employees&lt;br /&gt;http://127.0.0.1:8080/devtest/rest-demo/employees/7839&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Bonus: Making the URLs in your Apex applications pretty&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The URLs generated by Apex are not very friendly to users nor to search engines. You can use Apache with mod_rewrite to set up mapping between REST-style URLs to your Apex pages, but the example code above also shows how this can be accomplished using pure PL/SQL.&lt;br /&gt;&lt;br /&gt;The example assumes that you have an Apex application with Application ID = 104, and that you have made a Form on the EMP table on Page 2 of the application.&lt;br /&gt;&lt;br /&gt;Try the following link&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;http://127.0.0.1:8080/devtest/rest-demo/apex-employees/7839&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;and you should be redirected to the Apex application (and if you were already logged into the Apex application, you don't have to login again as it will reuse your existing session).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;REST for inserts and updates&lt;/span&gt;&lt;br /&gt;The four HTTP methods are GET, PUT, POST and DELETE. To create a REST web service that can update data as well as query it, we need to inspect the CGI environment variable REQUEST_METHOD and process the request accordingly (see &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services"&gt;the Wikipedia article&lt;/a&gt; for details). The example code implements the GET and DELETE methods for Employees and Departments.&lt;br /&gt;&lt;br /&gt;However, I don't see how we can implement POST or PUT processing using the PathAlias technique. The problem is that the webserver/gateway only sends the URL to the PathAliasProcedure. Any data that is POSTed to the URL is simply discarded by the gateway. Ideally, the names and values of the request should be sent to the PathAliasProcedure in name/value arrays (just like the gateway does when using flexible parameter passing). If anyone from Oracle is reading this, it can be considered an enhancement request for the next version of mod_plsql and the embedded gateway!&lt;br /&gt;&lt;br /&gt;Even with this limitation, the ability to expose (read-only) data from the database as RESTful web services using just PL/SQL is pretty cool, isn't it? :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4035577766723267321?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4035577766723267321/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4035577766723267321' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4035577766723267321'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4035577766723267321'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/07/creating-rest-web-service-with-plsql.html' title='Creating a REST web service with PL/SQL (and making pretty URLs for your Apex apps)'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_eX9l46hbfLI/SlCnMpbN_fI/AAAAAAAAABI/AtUXuexKPSY/s72-c/SNAG-0241.jpg' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-6555726993333661457</id><published>2009-06-16T14:15:00.001-07:00</published><updated>2010-10-07T02:22:34.067-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fat database'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><category scheme='http://www.blogger.com/atom/ns#' term='database-centric architecture'/><title type='text'>The Fat Database (or Thick Database) Approach</title><content type='html'>I'm a big believer in the so-called "Fat Database" paradigm for data-centric business applications.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I think I first heard the term "Thick Database" in a presentation by Dr. Paul Dorsey at the ODTUG conference in 2007.&lt;br /&gt;I prefer the slightly more hip term "Fat Database", and offer my own definition of the term:&lt;br /&gt;&lt;quote&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/quote&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;blockquote&gt;"Building applications using the Fat Database approach means leveraging the full potential of the database engine and its features, rather than treating the database as a bit bucket. If a problem can be solved using the database, it should be solved using the database, rather than in a programming language outside the database."&lt;/blockquote&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;quote&gt;&lt;/quote&gt;&lt;br /&gt;&lt;quote&gt;&lt;/quote&gt;In other words, the exact opposite of the current trend, which is to avoid any database feature except basic tables. The enterprise architecture astronauts would rather reinvent the wheel over and over again, using the latest silver bullet in the endless stream of "new and improved" languages and frameworks that appear (and disappear) every few years.&lt;br /&gt;Benefits of the Fat Database approach include reduced cost and complexity, increased performance, and a degree of immunity against the need to constantly rewrite code in a rapidly changing technology landscape.&lt;br /&gt;&lt;br /&gt;Here is a collection of links to presentations and papers related to the Fat Database approach:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Dr. Paul Dorsey, co-author of seven Oracle Press books on Designer, Database Design, Developer, and JDeveloper&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.dulcian.com/papers/ODTUG/2008/2008_ODTUG_Dorsey2_Thick.htm"&gt;Thick Database Techniques for Fusion (and other Web) Developers&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.dulcian.com/Articles/Examining%20the%20Logic%20Behind%20Database%20Independence.htm"&gt;Examining the Logic behind Database Independence&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.dulcian.com/papers/IOUG/2009/2009_Dorsey_Trenches.htm"&gt;Oracle Fusion Middleware: Tales from the Trenches&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Toon Koppelaars, co-author of &lt;span style="font-style: italic;"&gt;Applied Mathematics for Database Professionals&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://thehelsinkideclaration.blogspot.com/"&gt;The Helsinki Declaration (blog)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://web.inter.nl.net/users/T.Koppelaars/Building_Robust_Applications.doc"&gt;Building Robust Applications in a DB-Centric Way&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://web.inter.nl.net/users/T.Koppelaars/J2EE_DB_CENTRIC.doc"&gt;A Database Centric Approach to J2EE Application Development&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Others&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Lucas Jellema (Oracle ACE): &lt;a href="http://technology.amis.nl/blog/3487/oow-presentation-optimal-use-of-oracle-database-10g-and-oracle-database-11g-for-modern-application-development"&gt;Optimal Use of Oracle Database 10g and Oracle Database 11g for Modern Application Development&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Mike Ault: &lt;a href="http://www.turbo-enterprise.com/Downloads/Myth%20of%20DB%20Independence.pdf"&gt;The Myth of Database Independence&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Tom Kyte (AskTom): &lt;a href="http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:376767427785"&gt;"Why [do we need an] application server?"&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;I will return to this subject in future postings on this blog.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-6555726993333661457?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/6555726993333661457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=6555726993333661457' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6555726993333661457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6555726993333661457'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/06/fat-database-or-thick-database-approach.html' title='The Fat Database (or Thick Database) Approach'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-3262578505730494318</id><published>2009-06-10T23:18:00.000-07:00</published><updated>2009-06-10T23:26:22.372-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>The Oracle Database as Development Platform</title><content type='html'>&lt;p&gt;There is really a lot of amazing stuff that you can do using just the Oracle database and PL/SQL these days. I've made this diagram to illustrate the Oracle database developer's toolbox (click image to enlarge):&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_eX9l46hbfLI/SjCh0o42hTI/AAAAAAAAAAM/q_pKOYUlHBk/s1600-h/oracle_database_as_development_platform.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 229px;" src="http://3.bp.blogspot.com/_eX9l46hbfLI/SjCh0o42hTI/AAAAAAAAAAM/q_pKOYUlHBk/s320/oracle_database_as_development_platform.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5345950683389920562" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Note that this is all native functionality in the database itself, it does not include anything from the Oracle Fusion (Java) technology stack (except JDeveloper, which is free and can be used for general database and web development, not just Java).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-3262578505730494318?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/3262578505730494318/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=3262578505730494318' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3262578505730494318'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/3262578505730494318'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2009/06/oracle-database-as-development-platform.html' title='The Oracle Database as Development Platform'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_eX9l46hbfLI/SjCh0o42hTI/AAAAAAAAAAM/q_pKOYUlHBk/s72-c/oracle_database_as_development_platform.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4637921478279605389</id><published>2008-12-21T06:03:00.000-08:00</published><updated>2010-03-03T09:22:08.410-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DBPrism'/><category scheme='http://www.blogger.com/atom/ns#' term='Tomcat'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Adventures with Apex, part three: Setting up Apex with Tomcat and the DBPrism plugin</title><content type='html'>It is common knowledge that you can run Apex using either the Apache-based Oracle HTTP Server (with mod_plsql) or the Embedded PL/SQL Gateway (on 10g XE and 11g), but did you also know that there is a third alternative?&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.dbprism.com.ar/index.html"&gt;DBPrism project&lt;/a&gt; is a free, open-source plugin for the Tomcat web server that, among other things, can function as a replacement for mod_plsql.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Side Note 1&lt;/span&gt;: Interestingly, Oracle itself is also developing the "&lt;a href="http://forums.oracle.com/forums/thread.jspa?messageID=2791285"&gt;Apex Listener&lt;/a&gt;", what I assume is based on similar [Java] technology. So by Apex 4.0 we will have even more deployment choices.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Side Note 2&lt;/span&gt;: My wish is that someone familiar with Microsoft's IIS should write a plugin to mimic mod_plsql on that platform as well, to make it easier to start using Apex in companies that have standardized on Microsoft technology (sneaking it in the back door, so to speak :-). &lt;span style="font-weight: bold;"&gt;UPDATE (AUGUST 2009):&lt;/span&gt; I decided to implement a free, open-source PL/SQL gateway for IIS myself. The &lt;a href="http://ora-00001.blogspot.com/2009/08/thoth-gateway-modplsql-replacement-for.html"&gt;Thoth Gateway&lt;/a&gt; runs all mod_plsql applications (including Apex applications) on Microsoft Internet Information Server (IIS) 6.0 or later.&lt;br /&gt;&lt;br /&gt;Anyway, here are instructions for using Apex with Tomcat and DBPrism:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ON THE DATABASE SERVER&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. Download the latest version of Application Express from oracle.com&lt;br /&gt;&lt;br /&gt;- http://www.oracle.com/technology/products/database/application_express/download.html&lt;br /&gt;&lt;br /&gt;2. Install Apex into the database according to the instructions&lt;br /&gt;&lt;br /&gt;- do not worry about configuring either the Embedded PL/SQL Gateway (DBMS_EPG) or Oracle HTTP Server,&lt;br /&gt; - but make sure to unlock the apex_public_user schema and make a note of the password given to apex_public_user (you will need it to configure the JDBC connection on the web server)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ON THE WEB SERVER&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note: This could be the same physical machine as the database, if desired.&lt;br /&gt;&lt;br /&gt;1. Download Java Runtime Engine (JRE) from sun.com&lt;br /&gt;&lt;br /&gt;- install by running setup program&lt;br /&gt;- create an environment variable called JRE_HOME and set it to the location where JRE was installed&lt;br /&gt;&lt;br /&gt;2. Download Tomcat from tomcat.apache.org&lt;br /&gt;&lt;br /&gt;- unzip to a folder, for example c:\program files\apache-tomcat-xxx (where xxx is version number)&lt;br /&gt;- follow instructions in RUNNING.txt to start Tomcat and verify installation (http://localhost:8080)&lt;br /&gt;&lt;br /&gt;3. Download the Oracle JDBC Thin driver from Oracle.com&lt;br /&gt;&lt;br /&gt;- http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/htdocs/jdbc_111060.html&lt;br /&gt;- get the "ojdbc5.jar" file&lt;br /&gt;&lt;br /&gt;4. Download DBPrism from http://sourceforge.net/projects/dbprism/&lt;br /&gt;&lt;br /&gt;- choose the "dbprism" package (not the "cms" package)&lt;br /&gt;- unzip and get the "dpls.war" file&lt;br /&gt;- put the dpls.war file into the "webapps" folder in Tomcat&lt;br /&gt;- shutdown and restart Tomcat&lt;br /&gt;- this will create a "dpls" folder under "webapps"&lt;br /&gt;- put the "ojdbc5.jar" file from step 3 in the dpls\WEB-INF\lib folder&lt;br /&gt;- edit the prism.xconf file&lt;br /&gt; - under "variables", change the value of the "demo.db" to a valid jdbc connectionstring to the database&lt;br /&gt; - under under category named "DAD_apex", change the "dbpassword" to the correct password of the "apex_public_user" (note that this was set during/after the installation of Apex)&lt;br /&gt;&lt;br /&gt;- shutdown and restart Tomcat again&lt;br /&gt;&lt;br /&gt;5. Configure Apex images/javascript for Tomcat&lt;br /&gt;&lt;br /&gt;- Create a folder called "i" under "webapps" in Tomcat&lt;br /&gt;- shutdown and restart Tomcat&lt;br /&gt;- Extract the files in the "images" folder in the Apex installation zip file and copy it to the "i" folder&lt;br /&gt;&lt;br /&gt;6. Verify that everything works&lt;br /&gt;&lt;br /&gt;- Go to http://localhost:8080/dpls/apex (this should display the login page of the Apex environment)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Pretty cool, isn't it? :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4637921478279605389?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4637921478279605389/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4637921478279605389' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4637921478279605389'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4637921478279605389'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2008/12/adventures-with-apex-part-three-setting.html' title='Adventures with Apex, part three: Setting up Apex with Tomcat and the DBPrism plugin'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-4245460969194607478</id><published>2008-04-12T09:11:00.000-07:00</published><updated>2009-05-25T12:40:51.827-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Oracle HTTP Server'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Adventures with Apex, part two: Setting up the stand-alone Oracle HTTP Server as a Windows service</title><content type='html'>The latest stand-alone version of Oracle HTTP Server (OHS) for Windows is available on OTN as the file "&lt;span style="font-style: italic;"&gt;iAS_101330_Apache2_Modplsql2_win32.zip&lt;/span&gt;".&lt;br /&gt;&lt;br /&gt;I ran the default setup on a Windows 2003 server (a separate machine from the database server) and encountered no errors during installation. The setup process created a few Start Menu items to start and stop the Apache process (through the Oracle Process Manager, OPMN, and the "&lt;span style="font-style: italic;"&gt;opmnctl startall&lt;/span&gt;" command, which in turn starts the HTTP Server).&lt;br /&gt;&lt;br /&gt;After configuring OHS and mod_plsql on the webserver to connect to Apex on the database server (as per the Apex installation docs), everything seemed to work fine. However, when I logged out of the server, the Apex application (and OHS) stopped responding.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Investigating, I found that the Apache process (apache.exe) is started and runs under the current logged-in user when using the Start Menu item to start it. So once I logged out of the server, the Apache process shuts down...!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As far as I could tell, there should have been a Windows service called Oracle&lt;orahome&gt;ProcessManager that runs the OPMN process in the background, without requiring a logged-on user. However, there is no such service created after installation.&lt;br /&gt;&lt;br /&gt;I tried creating the service manually myself using the "&lt;span style="font-style: italic;"&gt;sc create&lt;/span&gt;" command. The service was created successfully but gave an error message ["...&lt;span style="font-style: italic;"&gt;did not respond in a timely fashion&lt;/span&gt;..."] when trying to start it.)&lt;br /&gt;&lt;br /&gt;Fortunately, I had a quick look in the Knowledgebase on Metalink and found the solution there. According to &lt;span style="font-weight: bold;"&gt;Note:459474.1&lt;/span&gt;, entitled &lt;span style="font-style: italic;"&gt;"Howto Create a Windows Services Entry for Starting Oracle Application Server Processes"&lt;/span&gt;, this behavior is &lt;span style="font-weight: bold;"&gt;by design&lt;/span&gt; (!)...:&lt;br /&gt;&lt;br /&gt;&lt;/orahome&gt;&lt;blockquote&gt;&lt;span style="font-style: italic;"&gt;Most Oracle Application Server installations automatically create a Windows Services entry to allow the OracleAS processes to start up on server startup. By design, certain OracleAS releases such as the SOA Suite 10.1.3.1 do not create the Windows Services entry. In such installations, the user starts OracleAS processes from the Windows Start Menu.  When the user logs out of the Windows session, the OracleAS processes will terminate as well.  Resulting in an unexpected Application Server shutdown.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This note provides the steps to create a Windows Services entry for starting up OracleAS processes upon reboot/startup of the server.  Processes started via Windows Services entries are not affect by a user logging out of the Windows session.&lt;/span&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;I'll leave the question as to &lt;span style="font-weight: bold;"&gt;w&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;hy on earth the setup program doesn't create the necessary Windows services by default, for what is clearly a server-type product?!? &lt;/span&gt;up in the air for now.&lt;br /&gt;&lt;br /&gt;Instead, I will provide the necessary details here for those who do not have access to Metalink:&lt;br /&gt;&lt;br /&gt;Turns out I was right about having to create a Windows service manually using the "sc" command, but there is also a few registry entries that need to be present for it to work.&lt;br /&gt;&lt;br /&gt;First, find the "key" value from the file &lt;span style="font-style: italic;"&gt;\opmn\bin\oracle.key&lt;/span&gt; on the server. In my case, the value of this key was:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;SOFTWARE\ORACLE\KEY_oracleas1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Run regedit and add the following two entries (in my case, the first entry already existed):&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&lt;br /&gt;HKLM / Software / ORACLE / Key_oracleas1 / ORACLE_HOME_KEY = SOFTWARE\ORACLE\KEY_oracleas1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&lt;br /&gt;HKLM / Software / ORACLE / oracleas1 / ORACLE_OPMN_SERVICE = OracleHTTPServerProcessManager&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then run the following command to create the service:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: plain"&gt;&lt;br /&gt;sc create OracleHTTPServerProcessManager binPath= "c:\OraHome_1\opmn\bin\opmn.exe -s" DisplayName= "OracleHTTPServerProcessManager" start= "auto"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that there must be an empty space between the equal sign and the value in the command above. Apparently some kind of quirk with the sc command. By the way, the sc command is part of the Windows server resource kit (but it was already present on my Windows 2003 server).&lt;br /&gt;&lt;br /&gt;After creating the service, go to Services and verify that the service starts up. Also verify using Task Manager that apache.exe runs under the SYSTEM user, rather than the logged-in user.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-4245460969194607478?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/4245460969194607478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=4245460969194607478' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4245460969194607478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/4245460969194607478'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2008/04/adventures-with-apex-part-two-setting.html' title='Adventures with Apex, part two: Setting up the stand-alone Oracle HTTP Server as a Windows service'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-7676659684640468576</id><published>2008-04-12T08:23:00.000-07:00</published><updated>2010-08-17T06:17:16.844-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DBMS_EPG'/><category scheme='http://www.blogger.com/atom/ns#' term='XDB'/><category scheme='http://www.blogger.com/atom/ns#' term='Apex'/><title type='text'>Adventures with Apex, part one: Boosting performance when using DBMS_EPG</title><content type='html'>We are currently developing a web application using Apex 3.1. There is a handful of developers on the team and we all work in the Apex Application Builder against a shared Oracle 11g database server, which has the Embedded PL/SQL Gateway (DBMS_EPG), rather than a full Apache (Oracle HTTP Server) setup, as per the default database install.&lt;br /&gt;&lt;br /&gt;It did not take long before we noticed a significant variation in the time it took to serve Apex pages from the database. Most of the time, pages were served quickly enough, but at uneven intervals pages would just take forever to load. It seemed as if the underlying database or webserver/XDB session  was timing out.&lt;br /&gt;&lt;br /&gt;After some head-scratching, googling, and experimentation, the following two configuration changes fixed the problem. Page performance is now consistently excellent, and we do not experience timeouts anymore.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Increase the XDB &lt;span style="font-style: italic;"&gt;session-timeout&lt;/span&gt; parameter (I changed it from the default of 6000 [1 minute] to a new value of 30000 [5 minutes].&lt;/li&gt;&lt;li&gt;Increase the &lt;span style="font-style: italic;"&gt;shared_servers&lt;/span&gt; database parameter (I changed it from the default of 1 to a new value of 5).&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Note:&lt;/span&gt; I changed both these settings at the same time, so I did not measure the effect of each individually. I suspect the &lt;span style="font-style: italic;"&gt;shared_servers&lt;/span&gt; database setting is the most important of the two.&lt;br /&gt;&lt;br /&gt;Detailed instructions follow:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;Changing the session-timeout parameter in XDB&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Connect as SYSDBA and run the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;  l_newconfig xmltype;&lt;br /&gt;begin&lt;br /&gt;  select updatexml (dbms_xdb.cfg_get(),&lt;br /&gt;'/xdbconfig/sysconfig/protocolconfig/httpconfig/session-timeout/text()', 30000)&lt;br /&gt;  into l_newconfig&lt;br /&gt;  from dual;&lt;br /&gt;&lt;br /&gt;  dbms_xdb.cfg_update (l_newconfig);&lt;br /&gt;&lt;br /&gt;  commit;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note: &lt;s&gt;The database must be restarted for the change to take effect.&lt;/s&gt; Issue "alter system register" for the new changes to take effect without restarting the database.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: 130%;"&gt;Changing the shared_servers database parameter&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Connect as SYSDBA and run the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;SQL&amp;gt; alter system set shared_servers = 5 scope=both;&lt;br /&gt;&lt;br /&gt;System altered.&lt;br /&gt;&lt;br /&gt;SQL&amp;gt; alter system register;&lt;br /&gt;&lt;br /&gt;System altered.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This will create more background sessions to handle the Apex page requests served through XDB.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-7676659684640468576?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/7676659684640468576/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=7676659684640468576' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7676659684640468576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/7676659684640468576'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2008/04/adventures-with-apex-part-one-boosting.html' title='Adventures with Apex, part one: Boosting performance when using DBMS_EPG'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-6916675408220855368</id><published>2008-03-25T00:18:00.001-07:00</published><updated>2008-03-25T00:26:16.850-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='data modeling'/><category scheme='http://www.blogger.com/atom/ns#' term='database design'/><title type='text'>Implementing table inheritance in the database</title><content type='html'>Over at &lt;a href="http://www.sqlteam.com/"&gt;sqlteam.com&lt;/a&gt;, there is an interesting article about "Implementing table inheritance in SQL Server". But the concept is just as applicable to Oracle, of course:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server"&gt;http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-6916675408220855368?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/6916675408220855368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=6916675408220855368' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6916675408220855368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/6916675408220855368'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2008/03/implementing-table-inheritance-in.html' title='Implementing table inheritance in the database'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5215551487816981140.post-2362010265655426267</id><published>2008-03-21T00:24:00.000-07:00</published><updated>2008-03-21T00:25:49.376-07:00</updated><title type='text'>Yet another Oracle blog!</title><content type='html'>I was not really planning to start a blog, but then I realized the subdomain ORA-00001 was available on Blogger, so I thought "why not"? At least now I have a place to ramble about various Oracle-related stuff.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5215551487816981140-2362010265655426267?l=ora-00001.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ora-00001.blogspot.com/feeds/2362010265655426267/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5215551487816981140&amp;postID=2362010265655426267' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2362010265655426267'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5215551487816981140/posts/default/2362010265655426267'/><link rel='alternate' type='text/html' href='http://ora-00001.blogspot.com/2008/03/yet-another-oracle-blog.html' title='Yet another Oracle blog!'/><author><name>Morten Braten</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://4.bp.blogspot.com/_eX9l46hbfLI/SlCTJaJgknI/AAAAAAAAAAg/QCKE1bmNtLU/S220/profile_picture.jpg'/></author><thr:total>0</thr:total></entry></feed>
