<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>The K Guy » sql pl</title>
	
	<link>http://www.thekguy.com</link>
	<description>For when a first initial is all you can remember</description>
	<lastBuildDate>Sat, 03 Sep 2011 19:31:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/TheKGuy_db2/sql-pl-db2" /><feedburner:info uri="thekguy_db2/sql-pl-db2" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>How Inception is like metaprogramming</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/6uA9IRPSCcU/how-inception-is-like-metaprogramming.html</link>
		<comments>http://www.thekguy.com/how-inception-is-like-metaprogramming.html#comments</comments>
		<pubDate>Mon, 19 Jul 2010 13:00:16 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[metaprogramming]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2686</guid>
		<description><![CDATA[I saw the movie Inception over the weekend and after reading What a Programmer Sees When He Watches Inception, in which Marke Hallowell compared the dreams-within-dreams concept from the movie to a recursive function, I think I too see parallels between the movie and computer programming. I think a parallel exists between dreams-within-dreams and programs-within-programs. [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fhow-inception-is-like-metaprogramming.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fhow-inception-is-like-metaprogramming.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p><a href="http://www.thekguy.com/wp-content/uploads/2020/07/inception_200.jpg"><img src="http://www.thekguy.com/wp-content/uploads/2020/07/inception_200.jpg" alt="Inception movie poster" title="Inception movie poster" width="200" height="148" class="alignleft size-full wp-image-2697" /></a></p>
<p>I saw the movie <a href="http://www.imdb.com/title/tt1375666/">Inception</a> over the weekend and after reading <a href="http://latestatic.com/what-a-programmer-sees-when-he-watches-incept">What a Programmer Sees When He Watches Inception</a>, in which Marke Hallowell compared the dreams-within-dreams concept from the movie to a recursive function, I think I too see parallels between the movie and computer programming. I think a parallel exists between dreams-within-dreams and programs-within-programs.<span id="more-2686"></span></p>
<p>As you&#8217;re watching the movie, especially in the later sequences, you are watching it on four and even five levels. The events that occur on each level have consequences for those levels below it. When the dreaming is over, the lower levels get to return the favor, having consequences on the levels above them by how they changed the dreamers themselves. To follow the movie you need to have an understanding of what is going on at each level.</p>
<p>Similarly in metaprogramming, you need an awareness of what level you are operating on at any given time as well as some knowledge of when and how to mix in elements from other levels. As an example, let&#8217;s take one of the simplest possible programs and see what happens when we add more levels through metaprogramming. The example is in SQL PL:</p>
<p><pre class="brush: sql">BEGIN CALL DBMS_OUTPUT.PUT_LINE('Hello world!'); END</pre></p>
<p>The output of the program is as you would expect:</p>
<p><pre class="brush: css">Hello world!</code></pre></p>
<p>Rewriting it into a metaprogram, we can add a second level:</p>
<p><pre class="brush: sql">BEGIN
  EXECUTE IMMEDIATE 'BEGIN CALL DBMS_OUTPUT.PUT_LINE(''Hello world!''); END';
END</pre></p>
<p>Notice that we needed to turn the single-quotes that surround the message into double single-quotes once we embed our original program into a string. Now let&#8217;s have the outer world of the meta-program affect the inner world of the program by having it provide part of the message:</p>
<p><pre class="brush: sql">BEGIN
  DECLARE MSG VARCHAR(128) DEFAULT 'world!';
  EXECUTE IMMEDIATE 'BEGIN CALL DBMS_OUTPUT.PUT_LINE(''Hello ' || MSG || '''); END';
END</pre></p>
<p>What if we add a third level, or dream-within-a-dream? I have used such techniques to implement a generalized version of the ANY_OF function I described in <a href="http://www.thekguy.com/new-in-db2-for-luw-9-7-2-udf-default-parameters.html">my post on default parameters</a>, so such meta-metaprogramming techniques can have their use. Let&#8217;s see what it does to our Hello World program:</p>
<p><pre class="brush: sql">BEGIN
  EXECUTE IMMEDIATE 'BEGIN DECLARE MSG VARCHAR(128) DEFAULT ''world!''; EXECUTE IMMEDIATE ''BEGIN CALL DBMS_OUTPUT.PUT_LINE(''''Hello '' || MSG || ''''''); END''; END';
END</pre></p>
<p>The conversion was actually pretty straightforward. Just as we double every single-quote when going from program to metaprogram, we double every single-quote again to go from metaprogram to meta-metaprogram, so one single-quote in the original program becomes four single-quotes in the meta-metaprogram. Let&#8217;s make the above program a little more interesting by injecting something from the outer world down deep into the innermost world:</p>
<p><pre class="brush: sql">BEGIN
  DECLARE OUTERMSG VARCHAR(128) DEFAULT 'world!';
  EXECUTE IMMEDIATE 'BEGIN DECLARE MSG VARCHAR(128) DEFAULT '''
    || OUTERMSG || 
    '''; EXECUTE IMMEDIATE ''BEGIN CALL DBMS_OUTPUT.PUT_LINE(''''Hello '' || MSG || ''''''); END''; END';
END</pre></p>
<p>The next step would be to see how something in the deepest inner-world can make it back up into the outermost world, but that&#8217;s enough for today. In my next post, I will return to discussing db2top with a new video.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2686&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fhow-inception-is-like-metaprogramming.html&amp;title=How%20Inception%20is%20like%20metaprogramming" id="wpa2a_2"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/6uA9IRPSCcU" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/how-inception-is-like-metaprogramming.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/how-inception-is-like-metaprogramming.html</feedburner:origLink></item>
		<item>
		<title>Metaprogramming in SQL (Part 4)</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/k1Bvy9N9zRc/metaprogramming-in-sql-part-4.html</link>
		<comments>http://www.thekguy.com/metaprogramming-in-sql-part-4.html#comments</comments>
		<pubDate>Fri, 02 Jul 2010 04:00:32 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[metaprogramming]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2668</guid>
		<description><![CDATA[In the past three posts, we have written metaprograms to create functions for comparing objects of built-in types, user-defined distinct types, ROW types, and ordinary ARRAY types. In today&#8217;s post, we will write a single metaprogram that can create comparison functions for all four. The only obstacle to writing a generic metaprogram for built-in, UDDT, [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-4.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-4.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>In the <a href="http://www.thekguy.com/metaprogramming-in-sql-part-1.html">past</a> <a href="http://www.thekguy.com/metaprogramming-in-sql-part-2.html">three</a> <a href="http://www.thekguy.com/metaprogramming-in-sql-part-3.html">posts</a>, we have written metaprograms to create functions for comparing objects of built-in types, user-defined distinct types, ROW types, and ordinary ARRAY types. In today&#8217;s post, we will write a single metaprogram that can create comparison functions for all four.<span id="more-2668"></span></p>
<p>The only obstacle to writing a generic metaprogram for built-in, UDDT, ROW, and ordinary ARRAY types is that we do not know how to determine which of the four it is from the name that is passed to our metaprogram. It turns out that there is a column named METATYPE in the SYSCAT.DATATYPES catalog view that we can use. Let&#8217;s look at a short example:</p>
<p><pre class="brush: sql">CREATE TYPE SIMPLETYPE AS INTEGER WITH COMPARISONS@
CREATE OR REPLACE TYPE ROWTYPE AS ROW (ONE INTEGER, TWO INTEGER)@
CREATE OR REPLACE TYPE ARRAYTYPE AS INTEGER ARRAY[]@

SELECT VARCHAR(TYPENAME, 10) TYPENAME, METATYPE 
FROM SYSCAT.DATATYPES 
WHERE TYPENAME IN ('SIMPLETYPE', 'ROWTYPE', 'ARRAYTYPE', 'INTEGER')@</pre></p>
<p>This produces a result like the following:</p>
<p><pre class="brush: css">TYPENAME   METATYPE
---------- --------
ROWTYPE    F       
ARRAYTYPE  A       
SIMPLETYPE T       
INTEGER    S

  4 record(s) selected.</pre></p>
<p>We can use this to write a generic function that inspects this SYSCAT.DATATYPES view before deciding which kind of comparison function to create (implementations have been elided for readability, but are simply the code from the previous three articles in this series):</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_EQ_FUNCTION(SCHEMA_NAME VARCHAR(128),
                                                           MODULE_NAME VARCHAR(128),
                                                           TYPE_NAME VARCHAR(128))
BEGIN
  DECLARE META_TYPE CHAR(1);
  ...
  SELECT METATYPE INTO META_TYPE
  FROM SYSCAT.DATATYPES
  WHERE TYPESCHEMA = SCHEMA_NAME
    AND (TYPEMODULENAME IS NULL AND MODULE_NAME IS NULL OR TYPEMODULENAME = MODULE_NAME)
    AND TYPENAME = TYPE_NAME;
  IF (META_TYPE = 'F') THEN
    -- Create ROW type EQ function
  ELSEIF (META_TYPE = 'T' OR META_TYPE = 'S') THEN
    -- Create UDDT/built-in type EQ function
  ELSEIF (META_TYPE = 'A') THEN
    -- Create ARRAY type EQ function
  ELSE
    SIGNAL SQLSTATE VALUE '75000' SET MESSAGE_TEXT = 'Unrecognized metatype. Cannot create comparison function.';
  END IF;
END@</pre></p>
<p>Now, we have a generic metaprogram that can create EQ functions for all four metatypes. The following example demonstrates its use on the built-in, row, and array metatypes:</p>
<p><pre class="brush: sql">SET SERVEROUTPUT ON@
CREATE OR REPLACE TYPE TESTSCH.ROWTYPE AS ROW (ONE INTEGER, TWO INTEGER)@
CREATE OR REPLACE TYPE TESTSCH.ARRAYTYPE AS INTEGER ARRAY[]@
CALL CREATE_EQ_FUNCTION('SYSIBM', NULL, 'INTEGER')@
CALL CREATE_EQ_FUNCTION('TESTSCH', NULL, 'ROWTYPE')@
CALL CREATE_EQ_FUNCTION('TESTSCH', NULL, 'ARRAYTYPE')@
BEGIN
  DECLARE SIMPLE1 INTEGER;
  DECLARE SIMPLE2 INTEGER;
  DECLARE ROW1 TESTSCH.ROWTYPE;
  DECLARE ROW2 TESTSCH.ROWTYPE;
  DECLARE ARRAY1 TESTSCH.ARRAYTYPE;
  DECLARE ARRAY2 TESTSCH.ARRAYTYPE;
  SET SIMPLE1 = 1;
  SET SIMPLE2 = 1;
  SET ROW1.ONE = 1;
  SET ROW1.TWO = 1;
  SET ROW2.ONE = 1;
  SET ROW2.TWO = 1;
  SET ARRAY1[1] = 1;
  SET ARRAY2[1] = 1;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(SIMPLE1, SIMPLE2): ' || EQ(SIMPLE1, SIMPLE2));
  CALL DBMS_OUTPUT.PUT_LINE('EQ(ROW1, ROW2): ' || EQ(ROW1, ROW2));
  CALL DBMS_OUTPUT.PUT_LINE('EQ(ARRAY1, ARRAY2): ' || EQ(ARRAY1, ARRAY2));
  SET SIMPLE2 = 2;
  SET ROW2.TWO = 2;
  SET ARRAY2[1] = 2;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(SIMPLE1, SIMPLE2): ' || EQ(SIMPLE1, SIMPLE2));
  CALL DBMS_OUTPUT.PUT_LINE('EQ(ROW1, ROW2): ' || EQ(ROW1, ROW2));
  CALL DBMS_OUTPUT.PUT_LINE('EQ(ARRAY1, ARRAY2): ' || EQ(ARRAY1, ARRAY2));
END@</pre></p>
<p>This should result in output like the following:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.


  Return Status = 0

CREATE OR REPLACE FUNCTION EQ(LEFT  SYSIBM.INTEGER, RIGHT SYSIBM.INTEGER)
SPECIFIC EQ_SIMPLE CONTAINS SQL RETURNS INTEGER 
BEGIN
   DECLARE RESULT INTEGER;
   IF (LEFT = RIGHT) THEN
     SET RESULT = 1;
   ELSE
     SET RESULT = 0;
   END IF;
   RETURN RESULT;
END


  Return Status = 0

CREATE OR REPLACE FUNCTION EQ(LEFT TESTSCH.ROWTYPE, RIGHT TESTSCH.ROWTYPE)
SPECIFIC EQ_ROW READS SQL DATA RETURNS INTEGER
BEGIN
  DECLARE RESULT INTEGER;
  IF (LEFT.ONE = RIGHT.ONE AND LEFT.TWO = RIGHT.TWO) THEN
    SET RESULT = 1;
  ELSE
    SET RESULT = 0;
  END IF;
  RETURN RESULT;
END


  Return Status = 0

CREATE OR REPLACE FUNCTION EQ(LEFT  TESTSCH.ARRAYTYPE, RIGHT TESTSCH.ARRAYTYPE)
RETURNS INTEGER SPECIFIC EQ_ARRAY READS SQL DATA
BEGIN
  DECLARE ID INTEGER DEFAULT 1;
  DECLARE RESULT INTEGER;
  IF (CARDINALITY(LEFT) = CARDINALITY(RIGHT)) THEN
    SET RESULT = 1;
    WHILE (ID <= CARDINALITY(LEFT) AND RESULT = 1) DO
       SET RESULT = EQ(LEFT[ID], RIGHT[ID]);
       SET ID = ID + 1;
    END WHILE;
  ELSE
    SET RESULT = 0;
  END IF;
  RETURN RESULT;
END 

DB20000I  The SQL command completed successfully.

EQ(SIMPLE1, SIMPLE2): 1
EQ(ROW1, ROW2): 1
EQ(ARRAY1, ARRAY2): 1
EQ(SIMPLE1, SIMPLE2): 0
EQ(ROW1, ROW2): 0
EQ(ARRAY1, ARRAY2): 0</pre></p>
<p>That ends this series on metaprogramming for now. For the next two weeks, I am on vacation. Hopefully, I will have interesting new content to share when I get back.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2668&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-4.html&amp;title=Metaprogramming%20in%20SQL%20%28Part%204%29" id="wpa2a_4"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/k1Bvy9N9zRc" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/metaprogramming-in-sql-part-4.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/metaprogramming-in-sql-part-4.html</feedburner:origLink></item>
		<item>
		<title>Metaprogramming in SQL (Part 3)</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/F03dP0mMNqE/metaprogramming-in-sql-part-3.html</link>
		<comments>http://www.thekguy.com/metaprogramming-in-sql-part-3.html#comments</comments>
		<pubDate>Wed, 30 Jun 2010 04:00:02 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[metaprogramming]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2653</guid>
		<description><![CDATA[In the first post in this series, I introduced the concept of metaprogramming in SQL using dynamic SQL and applied the technique to write a procedure that could generate comparison functions that compare two objects of any basic type. In the second post, I expanded the procedure to produce comparison functions that compare two objects [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-3.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-3.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>In the <a href="http://www.thekguy.com/metaprogramming-in-sql-part-1.html">first post in this series</a>, I introduced the concept of metaprogramming in SQL using dynamic SQL and applied the technique to write a procedure that could generate comparison functions that compare two objects of any basic type. In the <a href="http://www.thekguy.com/metaprogramming-in-sql-part-2.html">second post</a>, I expanded the procedure to produce comparison functions that compare two objects of any ROW type. In today&#8217;s post, I will introduce the ARRAY type from DB2 9.7 and show how we can go about comparing two arrays, including arrays of ROW type objects.<span id="more-2653"></span></p>
<h4>The ARRAY Type</h4>
<p>As described in the DB2 Information Center, the <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.apdv.sqlpl.doc/doc/c0055266.html">array type</a> in DB2 9.7 is a user-defined data type consisting of an ordered set of elements of a single data type. There are various restrictions on what that single data type can be. Built-in types and ROW types are acceptable, but user-defined distinct types are not. There are two kinds of array types: ordinary arrays and associative arrays. Ordinary arrays have an upper bound on the number of elements and are indexed by ordinal position. Associative arrays have no such upper bound and they let you choose how they are indexed by providing a VARCHAR or INTEGER as the index.</p>
<h4>Comparing Arrays</h4>
<p>The &#8216;=&#8217; operator cannot be used to compare two objects of ARRAY type. However, as we did for objects of ROW type in the last post, we will create a metaprogram that produces EQ functions for any ordinary ARRAY type we define. First, let&#8217;s define what it means for an ordinary array A to be considered equal to an ordinary array B:</p>
<ol>
<li>The length of array A must be equal to the length of array B.</li>
<li>For each element in array A, the corresponding element by ordinal position in array B must be equal to that of array A.</li>
</ol>
<p>Once again, let&#8217;s start with a program that compares two ordinary arrays of a specific type and we will later transform it into a metaprogram that can write comparison functions for any pair of ordinary arrays. First we need to compare the lengths (cardinalities) of the arrays:</p>
<p><pre class="brush: sql">CREATE TYPE SIMPLEARRAY AS INTEGER ARRAY[100]@

CREATE OR REPLACE FUNCTION EQ(LEFT SIMPLEARRAY, RIGHT SIMPLEARRAY)
RETURNS INTEGER
READS SQL DATA
BEGIN
  DECLARE ID INTEGER DEFAULT 1;
  DECLARE RESULT INTEGER;
  IF (CARDINALITY(LEFT) = CARDINALITY(RIGHT)) THEN
    SET RESULT = 1;</pre></p>
<p>Then, if the cardinalities match, we iterate over the elements of the arrays, comparing each one:</p>
<p><pre class="brush: sql">    WHILE (ID <= CARDINALITY(LEFT) AND RESULT = 1) DO
      IF (LEFT[ID] = RIGHT[ID]) THEN
        SET RESULT = 1;
      ELSE
        SET RESULT = 0;
      END IF;
      SET ID = ID + 1;
    END WHILE;
  ELSE
    SET RESULT = 0;
  END IF;
  RETURN RESULT;
END@</pre></p>
<p>Before we transform this into a metaprogram, we will do our comparison using EQ instead of the &#8216;=&#8217; operator. This way, it will work not just for elements that are of built-in type such as INTEGER elements but for ROW elements as well:</p>
<p><pre class="brush: sql">CREATE OR REPLACE FUNCTION EQ(LEFT SIMPLEARRAY, RIGHT SIMPLEARRAY)
RETURNS INTEGER
READS SQL DATA
BEGIN
  DECLARE ID INTEGER DEFAULT 1;
  DECLARE RESULT INTEGER;
  IF (CARDINALITY(LEFT) = CARDINALITY(RIGHT)) THEN
    SET RESULT = 1;
    WHILE (ID <= CARDINALITY(LEFT) AND RESULT = 1) DO
      SET RESULT = EQ(LEFT[ID], RIGHT[ID]);
      SET ID = ID + 1;
    END WHILE;
  ELSE
    SET RESULT = 0;
  END IF;
  RETURN RESULT;
END@</pre></p>
<p>Now, let&#8217;s make this a metaprogram. First, it will only work for SIMPLEARRAY objects:</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_SIMPLEARRAY_EQ_FUNCTION()
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  SET SQL = 'CREATE OR REPLACE FUNCTION EQ(LEFT SIMPLEARRAY, RIGHT SIMPLEARRAY) ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'READS SQL DATA ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || '  DECLARE ID INTEGER DEFAULT 1; ';
  SET SQL = SQL || '  DECLARE RESULT INTEGER; ';
  SET SQL = SQL || '  IF (CARDINALITY(LEFT) = CARDINALITY(RIGHT)) THEN ';
  SET SQL = SQL || '    SET RESULT = 1; ';
  SET SQL = SQL || '    WHILE (ID <= CARDINALITY(LEFT) AND RESULT = 1) DO ';
  SET SQL = SQL || '      SET RESULT = EQ(LEFT[ID], RIGHT[ID]); ';
  SET SQL = SQL || '      SET ID = ID + 1; ';
  SET SQL = SQL || '    END WHILE; ';
  SET SQL = SQL || '  ELSE ';
  SET SQL = SQL || '    SET RESULT = 0; ';
  SET SQL = SQL || '  END IF; ';
  SET SQL = SQL || '  RETURN RESULT; ';
  SET SQL = SQL || 'END ';
END@</pre></p>
<p>The final step is to generalize it to work for all ARRAY types:</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_EQ_FUNCTION(DATATYPE VARCHAR(128))
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  SET SQL =        'CREATE OR REPLACE FUNCTION EQ(LEFT  ' || DATATYPE || ', ';
  SET SQL = SQL ||                               'RIGHT ' || DATATYPE || ') ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'READS SQL DATA ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || '  DECLARE ID INTEGER DEFAULT 1; ';
  SET SQL = SQL || '  DECLARE RESULT INTEGER; ';
  SET SQL = SQL || '  IF (CARDINALITY(LEFT) = CARDINALITY(RIGHT)) THEN ';
  SET SQL = SQL || '    SET RESULT = 1; ';
  SET SQL = SQL || '    WHILE (ID <= CARDINALITY(LEFT) AND RESULT = 1) DO ';
  SET SQL = SQL || '      SET RESULT = EQ(LEFT[ID], RIGHT[ID]); ';
  SET SQL = SQL || '      SET ID = ID + 1; ';
  SET SQL = SQL || '    END WHILE; ';
  SET SQL = SQL || '  ELSE ';
  SET SQL = SQL || '    SET RESULT = 0; ';
  SET SQL = SQL || '  END IF; ';
  SET SQL = SQL || '  RETURN RESULT; ';
  SET SQL = SQL || 'END ';
END@</pre></p>
<p>Now, we can write a program to use the CREATE_EQ_FUNCTION procedure to create a new EQ function for any ARRAY type and use it to compare two arrays:</p>
<p><pre class="brush: sql">CALL CREATE_EQ_FUNCTION('SIMPLEARRAY')@
BEGIN
  DECLARE ARRAY1 SIMPLEARRAY;
  DECLARE ARRAY2 SIMPLEARRAY;
  SET ARRAY1[1] = 1;
  SET ARRAY2[1] = 1;
  CALL DBMS_OUTPUT.PUT_LINE('Arrays are equal: ' || EQ(ARRAY1, ARRAY2));
  SET ARRAY2[1] = 2;
  CALL DBMS_OUTPUT.PUT_LINE('Arrays are equal: ' || EQ(ARRAY1, ARRAY2));
END@</pre></p>
<p>The program above has a result like the following:</p>
<p><pre class="brush: css">  Return Status = 0

DB20000I  The SQL command completed successfully.

Arrays are equal: 1
Arrays are equal: 0</pre></p>
<p>Now we have written a CREATE_EQ_FUNCTION procedure for three distinct kinds of data types: simple types (built-in and user-defined distinct types), ROW types, and ARRAY types. In <a href="http://www.thekguy.com/metaprogramming-in-sql-part-4.html">Metaprogramming in SQL (Part 4)</a>, we will look at how to use the METATYPE field of the SYSCAT.DATATYPES catalog view to write a single CREATE_EQ_FUNCTION that works on all three types we have encountered so far.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2653&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-3.html&amp;title=Metaprogramming%20in%20SQL%20%28Part%203%29" id="wpa2a_6"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/F03dP0mMNqE" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/metaprogramming-in-sql-part-3.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/metaprogramming-in-sql-part-3.html</feedburner:origLink></item>
		<item>
		<title>Metaprogramming in SQL (Part 2)</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/9BPH7oIwwYk/metaprogramming-in-sql-part-2.html</link>
		<comments>http://www.thekguy.com/metaprogramming-in-sql-part-2.html#comments</comments>
		<pubDate>Mon, 28 Jun 2010 04:00:14 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[metaprogramming]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2595</guid>
		<description><![CDATA[In the last post on this subject, we used dynamic SQL to generalize an integer comparison function into a stored procedure that builds comparison functions of any basic type. This served as a simple example, but did not really offer much benefit over using the &#8216;=&#8217; operator. In today&#8217;s post, I will extend the same [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-2.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-2.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>In the <a href="http://www.thekguy.com/metaprogramming-in-sql-part-1.html">last post on this subject</a>, we used dynamic SQL to generalize an integer comparison function into a stored procedure that builds comparison functions of any basic type. This served as a simple example, but did not really offer much benefit over using the &#8216;=&#8217; operator. In today&#8217;s post, I will extend the same technique to make functions that compare ROW objects just as easily, even though the &#8216;=&#8217; operator cannot be used on ROW objects.<span id="more-2595"></span></p>
<p>First of all, what does it mean to say that two rows are identical? Let&#8217;s define row A to be equal to row B when:</p>
<ol>
<li>The types of row A and row B are the same</li>
<li>Each field in row A has the same value as the same field in row B</li>
</ol>
<p>The first requirement is satisfied by the strong typing provided by SQL PL. The second will require us to look inside each row for its individual fields to make a proper comparison. In <a href="http://www.thekguy.com/db2-stored-procedures-and-defaults.html">an earlier post,</a> I showed you an example of a comparison procedure for two objects of a ROW type called TEST_T. It looked like this:</p>
<p><pre class="brush: sql">CREATE OR REPLACE TYPE TEST_T AS ROW(ONE INTEGER)@
 
CREATE OR REPLACE PROCEDURE EQ(IN LEFT TEST_T, IN RIGHT TEST_T, OUT RESULT INTEGER)
BEGIN
  IF (LEFT.ONE = RIGHT.ONE) THEN
    SET RESULT = 1;
  ELSE
    SET RESULT = 0;
  END IF;
END@</pre></p>
<p>With metaprogramming, we can generalize this into a procedure that creates row comparison functions for any ROW type. But first we need a way of discovering what fields make up a row. Fortunately, there is a catalog view called SYSCAT.ROWFIELDS that tells the name and type of every field in a row. Say we query this catalog view for the TEST_T type we created above:</p>
<p><pre class="brush: sql">SELECT VARCHAR(FIELDNAME,10) FIELDNAME,
       VARCHAR(FIELDTYPESCHEMA,10) FIELDTYPESCHEMA,
       VARCHAR(FIELDTYPEMODULENAME,10) FIELDTYPEMODULENAME,
       VARCHAR(FIELDTYPENAME,10) FIELDTYPENAME
FROM SYSCAT.ROWFIELDS
WHERE TYPESCHEMA = (VALUES CURRENT SCHEMA)
  AND TYPEMODULENAME IS NULL
  AND TYPENAME = 'TEST_T'</pre></p>
<p>We get a result like this:</p>
<p><pre class="brush: css">FIELDNAME  FIELDTYPESCHEMA FIELDTYPEMODULENAME FIELDTYPENAME
---------- --------------- ------------------- -------------
ONE        SYSIBM          -                   INTEGER      

  1 record(s) selected.</pre></p>
<p>We&#8217;ll put some of this information (specifically, the fieldname) to use in our metaprogram, but let&#8217;s follow the process we followed last time and start by turning the above comparison procedure into a metaprogram with hardcoded inputs:</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_TYPE_T_EQ_FUNCTION()
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  SET SQL =        'CREATE OR REPLACE FUNCTION EQ(LEFT TEST_T, RIGHT TEST_T) ';
  SET SQL = SQL || 'CONTAINS SQL ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || 'DECLARE RESULT INTEGER; ';
  SET SQL = SQL || 'IF (LEFT.ONE = RIGHT.ONE) THEN ';
  SET SQL = SQL || '  SET RESULT = 1; ';
  SET SQL = SQL || 'ELSE ';
  SET SQL = SQL || '  SET RESULT = 0; ';
  SET SQL = SQL || 'END IF; ';
  SET SQL = SQL || 'RETURN RESULT; ';
  SET SQL = SQL || 'END';
  EXECUTE IMMEDIATE SQL;
END@</pre></p>
<p>Next, using the SYSCAT.ROWFIELDS catalog view, we generalize the above function to take the ROW type as an argument. Since the type name needs to be qualified by the schema and the module name, we have an extra challenge to deal with. The module name is NULL when the type is not defined inside a module and NULLs cannot be compared using the &#8216;=&#8217; operator. One solution is to write two queries of the SYSCAT.ROWFIELDS view, one that assumes the module name is non-NULL and a second that assumes the module name is NULL. We choose which query to use when we check whether the module name argument is NULL.</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_EQ_FUNCTION(TYPE_SCHEMA VARCHAR(128),
                                               TYPE_MODULE_NAME VARCHAR(128),
                                               TYPE_NAME VARCHAR(128))
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  DECLARE FULLYQUALIFIEDTYPE VARCHAR(386);
  DECLARE AT_END INT DEFAULT 0;
  DECLARE FIELD_NAME VARCHAR(128);
  DECLARE ROWFIELDSCURSOR CURSOR FOR SELECT FIELDNAME
                                            FROM SYSCAT.ROWFIELDS 
                                            WHERE TYPESCHEMA = TYPE_SCHEMA
                                              AND TYPEMODULENAME IS NULL
                                              AND TYPENAME = TYPE_NAME;
  DECLARE ROWFIELDSMODCURSOR CURSOR FOR SELECT FIELDNAME
                                            FROM SYSCAT.ROWFIELDS 
                                            WHERE TYPESCHEMA = TYPE_SCHEMA
                                              AND TYPEMODULENAME = TYPE_MODULE_NAME
                                              AND TYPENAME = TYPE_NAME;
  DECLARE CONTINUE HANDLER FOR NOT FOUND
    SET AT_END = 1;

  -- Types need to be qualified by their schema, and if they are in a module, by the module too.
  IF (TYPE_MODULE_NAME IS NULL) THEN
    SET FULLYQUALIFIEDTYPE = TYPE_SCHEMA || '.' || TYPE_NAME;
  ELSE
    SET FULLYQUALIFIEDTYPE = TYPE_SCHEMA || '.' || TYPE_MODULE_NAME || '.' || TYPE_NAME || ' ';
  END IF;
  SET SQL =        'CREATE OR REPLACE FUNCTION EQ(LEFT ' || FULLYQUALIFIEDTYPE;
  SET SQL = SQL || ', RIGHT ' || FULLYQUALIFIEDTYPE || ') ';
  SET SQL = SQL || 'READS SQL DATA ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || 'DECLARE RESULT INTEGER; ';
  SET SQL = SQL || 'IF (';

  IF (TYPE_MODULE_NAME IS NULL) THEN
    -- Iterate over the row fields to build the Boolean expression
    OPEN ROWFIELDSCURSOR;
    FETCH ROWFIELDSCURSOR INTO FIELD_NAME;
    WHILE AT_END = 0 DO
      SET SQL = SQL || 'LEFT.' || FIELD_NAME || ' = ' || 'RIGHT.' || FIELD_NAME;
      FETCH ROWFIELDSCURSOR INTO FIELD_NAME;
      IF (AT_END = 0) THEN
        SET SQL = SQL || ' AND ';
      END IF;
    END WHILE;
    CLOSE ROWFIELDSCURSOR;
  ELSE
    -- Iterate over the row fields to build the Boolean expression
    OPEN ROWFIELDSMODCURSOR;
    FETCH ROWFIELDSMODCURSOR INTO FIELD_NAME;
    WHILE AT_END = 0 DO
      SET SQL = SQL || 'LEFT.' || FIELD_NAME || ' = ' || 'RIGHT.' || FIELD_NAME;
      FETCH ROWFIELDSMODCURSOR INTO FIELD_NAME;
      IF (AT_END = 0) THEN
        SET SQL = SQL || ' AND ';
      END IF;
    END WHILE;
    CLOSE ROWFIELDSMODCURSOR;
  END IF;

  SET SQL = SQL || ') THEN ';
  SET SQL = SQL || '    SET RESULT = 1; ';
  SET SQL = SQL || '  ELSE ';
  SET SQL = SQL || '  SET RESULT = 0; ';
  SET SQL = SQL || 'END IF; ';
  SET SQL = SQL || 'RETURN RESULT; ';
  SET SQL = SQL || 'END';
  CALL DBMS_OUTPUT.PUT_LINE(SQL);
  EXECUTE IMMEDIATE SQL;
END@</pre></p>
<p>Notice that I put in a CALL DBMS_OUTPUT.PUT_LINE to print the SQL before the EXECUTE IMMEDIATE. This makes it easy to debug whether the metaprogram is writing the function properly by inspecting the code it produces. Once the above function is created, we can call it on our TEST_T type and use the resulting EQ function to compare two rows of type TEST_T, as in this example program:</p>
<p><pre class="brush: sql">SET SERVEROUTPUT ON@
CREATE OR REPLACE TYPE TEST_T AS ROW(ONE INTEGER)@

CREATE OR REPLACE MODULE TEST_MOD@
ALTER MODULE TEST_MOD PUBLISH
TYPE TEST_T AS ROW(TWO INTEGER)@

CALL CREATE_EQ_FUNCTION('KMCDONAL', NULL, 'TEST_T')@
CALL CREATE_EQ_FUNCTION('KMCDONAL', 'TEST_MOD', 'TEST_T')@

BEGIN
  DECLARE T1 TEST_T;
  DECLARE T2 TEST_T;
  DECLARE T3 TEST_MOD.TEST_T;
  DECLARE T4 TEST_MOD.TEST_T;

  SET T1.ONE = 1;
  SET T2.ONE = 1;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(t1, t2) = ' || EQ(t1, t2));
  SET T2.ONE = 2;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(t1, t2) = ' || EQ(t1, t2));

  SET T3.TWO = 1;
  SET T4.TWO = 1;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(t3, t4) = ' || EQ(t3, t4));
  SET T4.TWO = 2;
  CALL DBMS_OUTPUT.PUT_LINE('EQ(t3, t4) = ' || EQ(t3, t4));

END@</pre></p>
<p>The above test produces the following output:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.


  Return Status = 0

CREATE OR REPLACE FUNCTION EQ(LEFT KMCDONAL.TEST_T, RIGHT KMCDONAL.TEST_T) CONTAINS SQL RETURNS INTEGER BEGIN DECLARE RESULT INTEGER; IF (LEFT.ONE = RIGHT.ONE) THEN     SET RESULT = 1;   ELSE   SET RESULT = 0; END IF; RETURN RESULT; END


  Return Status = 0

CREATE OR REPLACE FUNCTION EQ(LEFT KMCDONAL.TEST_MOD.TEST_T , RIGHT KMCDONAL.TEST_MOD.TEST_T ) CONTAINS SQL RETURNS INTEGER BEGIN DECLARE RESULT INTEGER; IF (LEFT.TWO = RIGHT.TWO) THEN     SET RESULT = 1;   ELSE   SET RESULT = 0; END IF; RETURN RESULT; END

DB20000I  The SQL command completed successfully.

EQ(t1, t2) = 1
EQ(t1, t2) = 0
EQ(t3, t4) = 1
EQ(t3, t4) = 0</pre></p>
<p>In <a href="http://www.thekguy.com/metaprogramming-in-sql-part-3.html">Metaprogramming in SQL (Part 3)</a>, we will use the above EQ function to build a more powerful EQ function that can compare objects of the ARRAY type, which is one of the new types in DB2 9.7.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2595&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-2.html&amp;title=Metaprogramming%20in%20SQL%20%28Part%202%29" id="wpa2a_8"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/9BPH7oIwwYk" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/metaprogramming-in-sql-part-2.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/metaprogramming-in-sql-part-2.html</feedburner:origLink></item>
		<item>
		<title>Metaprogramming in SQL (Part 1)</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/068eBZkuorU/metaprogramming-in-sql-part-1.html</link>
		<comments>http://www.thekguy.com/metaprogramming-in-sql-part-1.html#comments</comments>
		<pubDate>Fri, 25 Jun 2010 04:00:26 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[metaprogramming]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2557</guid>
		<description><![CDATA[Metaprogramming can feel like magic. You call a function that you neither wrote nor imported from any library and, magically, it comes back with a result. Even more magical is how metaprogramming lets you do otherwise impossible things with your programming language. In &#8220;The Art of Metaprogramming&#8221;, Jonathan Bartlett&#8217;s developerWorks series, he lists three examples [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-1.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-1.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>Metaprogramming can feel like magic. You call a function that you neither wrote nor imported from any library and, magically, it comes back with a result. Even more magical is how metaprogramming lets you do otherwise impossible things with your programming language. In <a href="http://www.ibm.com/developerworks/linux/library/l-metaprog1.html">&#8220;The Art of Metaprogramming&#8221;</a>, Jonathan Bartlett&#8217;s developerWorks series, he lists three examples that illustrate the benefits of metaprogramming:</p>
<ul>
<li>You can use metaprogramming to pre-generate tables of data for use at run-time.</li>
<li>In applications with large amounts of boilerplate code and limited ability to abstract this code cleanly into functions, you can use metaprogramming to create a mini-language to write the boilerplate for you at run-time, simplifying your own code.</li>
<li>You can use metaprogramming to transform a programming language that promotes verbosity into one that celebrates terseness. In addition to making up for inadequate language design, this can also ease maintenance.</li>
</ul>
<p>Examples abound. A famous one is <a href="http://ar.rubyonrails.org/">Ruby On Rails&#8217; ActiveRecord class</a> inspired by <a href="http://martinfowler.com/eaaCatalog/activeRecord.html">Martin Fowler&#8217;s ActiveRecord design pattern</a>. At least in early versions of Rails, you could simply create an empty class like the following:</p>
<p><code>class Employee < ActiveRecord::Base</code><br />
<code>end</code></p>
<p>and, assuming you had a <em>division</em> column and <em>mgrlastnm</em> column in the employees table in your database, you could write code like <code>Employee.find_by_division_and_mgrlastnm "Sales", "Simpson"</code> and it would just work. The Rails metaprogramming logic would detect that such a function did not exist and would generate the missing function at run-time.</p>
<p>So how does all this relate to SQL? In this post and others, I will answer that question and attempt to illustrate the benefits of metaprogramming in SQL PL.<span id="more-2557"></span></p>
<p><a href="http://en.wikipedia.org/wiki/Metaprogramming">The Wikipedia article on metaprogramming</a> defines it as "the writing of computer programs that write or manipulate other programs (or themselves) as their data, or that do part of the work at compile time that would otherwise be done at runtime." The article goes on to show an example of generative programming (the first half of metaprogramming) in bash script and references several programming languages with powerful metaprogramming facilities like Ruby, Python and C++.</p>
<p>Fortunately, you don't need a fancy dynamic language like Ruby or Python or a template metaprogramming language like C++ to do metaprogramming. You just need a language that can treat data as code. Thanks to dynamic SQL, most valid SQL statements in the form of data (i.e. as a string) can be interpreted as code at run-time.</p>
<p>For an example, <a href="http://www.thekguy.com/db2-stored-procedures-and-defaults.html">we turn again to the writing of a generic comparison function</a>. We will start with an EQ function that just compares integers and, step by step, we will convert it into a metaprogram that can create an EQ function for any simple type (I realize that the '=' operator does a fine job of this already and I will get to a more useful application in a later post when we look at ROW types). Step 1 is to write the end result we want, that is, the definition of an integer comparison function named EQ:</p>
<p><pre class="brush: sql">CREATE OR REPLACE FUNCTION EQ(LEFT INTEGER, RIGHT INTEGER)
CONTAINS SQL
RETURNS INTEGER
BEGIN
  DECLARE RESULT INTEGER;
  IF (LEFT = RIGHT) THEN
    SET RESULT = 1;
  ELSE
    SET RESULT = 0;
  END IF;
  RETURN RESULT;
END@</pre></p>
<p>Step 2 is to turn the whole function into a string and execute that string as dynamic SQL. We'll put the logic that executes the string into its own function called CREATE_INTEGER_EQ_FUNCTION. We will use the EXECUTE IMMEDIATE statement to execute the dynamic SQL string:</p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_INTEGER_EQ_FUNCTION()
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  SET SQL =        'CREATE OR REPLACE FUNCTION EQ(LEFT INTEGER, RIGHT INTEGER) ';
  SET SQL = SQL || 'CONTAINS SQL ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || '  DECLARE RESULT INTEGER; ';
  SET SQL = SQL || '  IF (LEFT = RIGHT) THEN ';
  SET SQL = SQL || '    SET RESULT = 1; ';
  SET SQL = SQL || '  ELSE ';
  SET SQL = SQL || '  SET RESULT = 0; ';
  SET SQL = SQL || 'END IF; ';
  SET SQL = SQL || 'RETURN RESULT; ';
  SET SQL = SQL || 'END';
  EXECUTE IMMEDIATE SQL;
END@</pre></p>
<p>Now, when we execute CREATE_INTEGER_EQ_FUNCTION(), it will create our EQ function:</p>
<p><pre class="brush: css">call create_integer_eq_function
  Return Status = 0

values eq(1,2)
1          
-----------
          0
  1 record(s) selected.</pre></p>
<p>Step 3 is to generalize. We will replace CREATE_INTEGER_EQ_FUNCTION() with CREATE_EQ_FUNCTION_FOR(datatype VARCHAR(128)) so now we can call it like this: call CREATE_EQ_FUNCTION_FOR('INTEGER'). </p>
<p><pre class="brush: sql">CREATE OR REPLACE PROCEDURE CREATE_EQ_FUNCTION(DATATYPE VARCHAR(128))
MODIFIES SQL DATA
BEGIN
  DECLARE SQL VARCHAR(1024);
  SET SQL =        'CREATE OR REPLACE FUNCTION EQ(LEFT  ' || DATATYPE || ', ';
  SET SQL = SQL ||                               'RIGHT ' || DATATYPE || ') ';
  SET SQL = SQL || 'CONTAINS SQL ';
  SET SQL = SQL || 'RETURNS INTEGER ';
  SET SQL = SQL || 'BEGIN ';
  SET SQL = SQL || '  DECLARE RESULT INTEGER; ';
  SET SQL = SQL || '  IF (LEFT = RIGHT) THEN ';
  SET SQL = SQL || '    SET RESULT = 1; ';
  SET SQL = SQL || '  ELSE ';
  SET SQL = SQL || '  SET RESULT = 0; ';
  SET SQL = SQL || 'END IF; ';
  SET SQL = SQL || 'RETURN RESULT; ';
  SET SQL = SQL || 'END';
  EXECUTE IMMEDIATE SQL;
END@</pre></p>
<p>Now, if we want to create an EQ function for CHAR(1) instead, we just pass a different argument.</p>
<p><pre class="brush: css">call create_eq_function('char(1)') 

  Return Status = 0
values eq('a','a')
1          
-----------
          1
  1 record(s) selected.

values eq('a','b')
1          
-----------
          0
  1 record(s) selected.</pre></p>
<p>This works well for simple types like integers, varchars, and user-defined distinct types with comparisons, but cannot deal with ROW types and ARRAY types, which cannot be compared with the '=' operator. In <a href="http://www.thekguy.com/metaprogramming-in-sql-part-2.html">Metaprogramming in SQL (Part 2)</a>, <a href="http://www.thekguy.com/metaprogramming-in-sql-part-3.html">Metaprogramming in SQL (Part 3)</a>, and <a href="http://www.thekguy.com/metaprogramming-in-sql-part-4.html">Metaprogramming in SQL (Part 4)</a>, I'll show you the real power of metaprogramming when we tackle these more challenging data types.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2557&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fmetaprogramming-in-sql-part-1.html&amp;title=Metaprogramming%20in%20SQL%20%28Part%201%29" id="wpa2a_10"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/068eBZkuorU" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/metaprogramming-in-sql-part-1.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/metaprogramming-in-sql-part-1.html</feedburner:origLink></item>
		<item>
		<title>Why you should program to interfaces in SQL</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/d_a0eyUstos/why-you-should-program-to-interfaces-in-sql.html</link>
		<comments>http://www.thekguy.com/why-you-should-program-to-interfaces-in-sql.html#comments</comments>
		<pubDate>Mon, 21 Jun 2010 04:00:08 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[alias]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[module]]></category>
		<category><![CDATA[unit-testing]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2509</guid>
		<description><![CDATA[With DB2 9.7&#8242;s introduction of modules and the ability to create an alias on a module, you now have the ability to program to a function or procedure&#8217;s interface rather than its implementation. Why would you want to do this? In today&#8217;s post, I will go over reasons to program to an interface and show [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fwhy-you-should-program-to-interfaces-in-sql.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fwhy-you-should-program-to-interfaces-in-sql.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>With DB2 9.7&#8242;s introduction of <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.wn.doc/doc/c0053774.html">modules</a> and the ability to create an <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.sql.ref.doc/doc/r0000910.html">alias</a> on a module, you now have the ability to program to a function or procedure&#8217;s interface rather than its implementation. Why would you want to do this? In today&#8217;s post, I will go over reasons to program to an interface and show you how to do it using modules and aliases.<span id="more-2509"></span></p>
<p>Object-oriented programming languages realize their power when programmers program to an interface rather than an implementation, as it makes programs written in them more flexible. Such languages allow you to change or generalize your designs without significantly rewriting your code. However, the technique of programming to an interface is not limited to object-oriented languages. Anyone who has ever created a view on a table has some experience with the same concept applied to data.</p>
<p>Programming to an interface makes testing your code easier too, especially unit testing. In unit testing, you test components in isolation. If a component-under-test has explicit dependencies on other components, it can be difficult to test in isolation. If, however, these dependencies are on the interfaces of other components rather than on their implementations, you can replace the implementations with fakes or stubs to achieve isolation.</p>
<h4>More Testable Designs</h4>
<p>Say you have a program that reads data from a sensor and performs some calculation on it. You want to unit test that the calculation produces correct results for known values and boundary conditions. You also want this test to be repeatable so that you can rerun the test whenever you make changes to the calculation and know that you haven&#8217;t introduced a regression. The problem is that the sensor gets its data from an environment you can&#8217;t control. The system-under-test, that is, your calculation function, is coupled to sensor output that varies from one minute to the next, making it difficult or impossible to write an isolated unit test.</p>
<p>The solution is to program not to the sensor implementation but to its interface. Let&#8217;s say the software that handles reading the sensor takes the form of a user-defined function in SQL PL. Let&#8217;s pretend we have a sensor that gives us random data from the environment and let&#8217;s represent it with a function that returns a random number between 0 and 1000. Let&#8217;s say that the system-under-test is a function that reads the sensor six times and computes the average from those six readings.</p>
<p><pre class="brush: sql">SET SERVEROUTPUT ON@

CREATE OR REPLACE FUNCTION read_sensor() SPECIFIC read_sensor 
RETURNS INTEGER 
BEGIN
  DECLARE seed INTEGER;
  SET seed = (INTEGER(SECOND(CURRENT TIMESTAMP)) * 1000000) + MICROSECOND(CURRENT TIMESTAMP);
  RETURN 1000 * RAND(MOD(seed,2147483647));
END@

CREATE OR REPLACE FUNCTION avg_sensor_readings()
RETURNS DOUBLE
BEGIN
  DECLARE sum DOUBLE DEFAULT 0;
  DECLARE count INTEGER DEFAULT 0;
  WHILE count < 6 DO
    set sum = sum + read_sensor(); 
    set count = count + 1;
  END WHILE;
  RETURN sum / 6.0;
END@

CALL DBMS_OUTPUT.PUT_LINE('avg of 6 sensor readings = ' || DECIMAL(avg_sensor_readings(),9,2))@</pre></p>
<p>If we run the above program we get something like the following:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

  Return Status = 0

avg of 6 sensor readings = 523.00
</pre></p>
<p>If we run it again, we get a different average. This is unacceptable for testing our <em>avg_sensor_readings</em> function. We would like to replace the <em>read_sensor</em> function with a stub that consistently returns the numbers we need to test our averaging function. However, because we have coupled the <em>avg_sensor_readings</em> function to the sensor&#8217;s implementation, we cannot simply replace the <em>read_sensor</em> with a stub when running our tests and still be able to use the real <em>read_sensor</em> function when running in production. </p>
<p>How can we decouple these two components? If the <em>read_sensor</em> function were instead a table, we could create an alias on the table and modify the <em>avg_sensor_readings</em> function to query the alias instead of the table, but there is no such thing as an alias on a function. Fortunately, DB2 9.7 provides the solution with the introduction of modules, which can be given an alias. First, let&#8217;s move our <em>read_sensor</em> function into a module called <em>sensors</em>.</p>
<p><pre class="brush: sql">CREATE OR REPLACE MODULE sensors@

ALTER MODULE sensors PUBLISH
FUNCTION read_sensor() SPECIFIC read_sensor 
RETURNS INTEGER 
BEGIN
  DECLARE seed INTEGER;
  SET seed = (INTEGER(SECOND(CURRENT TIMESTAMP)) * 1000000) + MICROSECOND(CURRENT TIMESTAMP);
  RETURN 1000 * RAND(MOD(seed,2147483647));
END@</pre></p>
<p>Now, we just need to create an alias for our <em>sensors</em> module and modify <em>avg_sensor_readings</em> to program to the interface (the alias):</p>
<p><pre class="brush: sql">CREATE OR REPLACE ALIAS sensors_interface FOR MODULE sensors@

CREATE OR REPLACE FUNCTION avg_sensor_readings()
RETURNS DOUBLE
BEGIN
  DECLARE sum DOUBLE DEFAULT 0;
  DECLARE count INTEGER DEFAULT 0;
  WHILE count < 6 DO
    set sum = sum + sensors_interface.read_sensor(); 
    set count = count + 1;
  END WHILE;
  RETURN sum / 6.0;
END@</pre></p>
<p>Now, if we create a stub module, we can have our <em>avg_sensor_readings</em> function use the stub when it is being unit-tested and use the real function when in production. Here is the stub with the alias changed to point to the stub function instead of the original function:</p>
<p><pre class="brush: sql">CREATE OR REPLACE MODULE sensor_stubs@

ALTER MODULE sensor_stubs PUBLISH 
FUNCTION read_sensor() SPECIFIC read_sensor
RETURNS INTEGER 
RETURN 5@

CREATE OR REPLACE ALIAS sensors_interface FOR MODULE sensor_stubs@

CALL DBMS_OUTPUT.PUT_LINE('avg of 6 sensor readings (Expected: 5.00, Actual: ' || DECIMAL(avg_sensor_readings(),9,2) || ')')@</pre></p>
<p>This has the following output:</p>
<p><pre class="brush: css">DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.


  Return Status = 0

avg of 6 sensor readings (Expected: 5.00, Actual: 5.00)</pre></p>
<p>Now we always get the same average whenever we run the test. We can rely on this test to detect regressions in our <em>avg_sensor_readings</em> function. We can switch back and forth between the stub and real function without ever having to modify the <em>avg_sensor_readings</em> function.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2509&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fwhy-you-should-program-to-interfaces-in-sql.html&amp;title=Why%20you%20should%20program%20to%20interfaces%20in%20SQL" id="wpa2a_12"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/d_a0eyUstos" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/why-you-should-program-to-interfaces-in-sql.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/why-you-should-program-to-interfaces-in-sql.html</feedburner:origLink></item>
		<item>
		<title>New in DB2 for LUW 9.7.2: UDF default parameters</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/tNCz880GLUY/new-in-db2-for-luw-9-7-2-udf-default-parameters.html</link>
		<comments>http://www.thekguy.com/new-in-db2-for-luw-9-7-2-udf-default-parameters.html#comments</comments>
		<pubDate>Mon, 07 Jun 2010 04:00:39 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2314</guid>
		<description><![CDATA[IBM released DB2 for LUW 9.7.2 recently and, along with numerous quality improvements, there were a number of interesting new features. In today&#8217;s post, I will talk about the &#8220;default parameters in UDFs&#8221; feature and how that helped me shrink the amount of code I needed to write for one of my UDFs. First, I [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fnew-in-db2-for-luw-9-7-2-udf-default-parameters.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fnew-in-db2-for-luw-9-7-2-udf-default-parameters.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>IBM released <a href="http://www-01.ibm.com/software/data/db2/linux-unix-windows/">DB2 for LUW</a> 9.7.2 recently and, along with numerous quality improvements, there were a number of interesting new features. In today&#8217;s post, I will talk about the &#8220;default parameters in UDFs&#8221; feature and how that helped me shrink the amount of code I needed to write for one of my UDFs.<span id="more-2314"></span></p>
<p>First, I want to explain why I was writing this particular UDF. I needed a function to take the comparison functions that I described previously in my post on <a href="http://www.thekguy.com/db2-stored-procedures-and-defaults.html">DB2 Stored Procedures and Defaults</a> and apply more than one of them at a time. I wanted to create the equivalent of a boolean OR operation using UDFs. </p>
<p>Say you have a function named EQ and it takes two integer arguments and compares the first argument to the second and returns a 1 if they match and a 0 if they don&#8217;t. Further, say you want to perform two such comparisons, that is, make two calls to EQ, and return a 1 if either comparison succeeded and return a 0 if both comparisons failed. You could call this function ANY_OF and use it like this: </p>
<p><code>ANY_OF(EQ(1, x), EQ(2, x))</code></p>
<p>I admit that this seems like a useless function when it is just as easy to write:</p>
<p><code>EQ(1, x) OR EQ(2, x)</code></p>
<p>or even:</p>
<p><code>x = 1 OR x = 2</code></p>
<p>I do, however, have a few reasons for turning boolean operators and comparison operators into UDFs. First of all, you can make these EQ functions take any data type as their arguments, including ROW types, and second, you can make it so that instead of the EQ functions comparing the first argument to the second, they take only the first argument and produce a special object that, at a later time, you can use to perform comparisons, allowing you to do something like this:</p>
<p><code>SET special_object = ANY_OF(EQ(1), EQ(2));</code><br />
<code>CALL PERFORM_COMPARISON(special_object, x);</code></p>
<p>As odd as the above code may seem, having the ability to declare arbitrary comparison operations and use them at a later time is useful in a project I am working on, mostly because it offers type safety advantages over manipulating strings directly to do the same thing. However, we&#8217;re going to keep things simple for now and, without loss of generality, have the EQ functions take two integer arguments and return a 1 or a 0.</p>
<p>I would like the ANY_OF function to take between 2 and 5 arguments, so that I can write:</p>
<p><code>ANY_OF(EQ(1, x), EQ(2, x), EQ(3, x), EQ(4, x), EQ(5, x))</code></p>
<p>just as easily as I can write:</p>
<p><code>ANY_OF(EQ(1,x), EQ(2,x))</code></p>
<p>The problem is that we now need to write four different versions of the ANY_OF function, each taking a different number of arguments:</p>
<p><pre class="brush: sql">CREATE OR REPLACE FUNCTION ANY_OF(ARG1 INTEGER,
                                  ARG2 INTEGER)
RETURNS INTEGER
BEGIN
  IF (ARG1 = 1 OR ARG2 = 1) THEN
    RETURN 1;
  ELSE
    RETURN 0;
  END IF;
END@
...
CREATE OR REPLACE FUNCTION ANY_OF(ARG1 INTEGER,
                                  ARG2 INTEGER,
                                  ARG3 INTEGER,
                                  ARG4 INTEGER,
                                  ARG5 INTEGER)
RETURNS INTEGER
BEGIN
  IF (ARG1 = 1 OR ARG2 = 1 OR ARG3 = 1 OR ARG4 = 1 OR ARG5 = 1) THEN
    RETURN 1;
  ELSE
    RETURN 0;
  END IF;
END@</pre></p>
<p>With the introduction of default arguments in DB2 9.7.2, we need only write one version of ANY_OF:</p>
<p><pre class="brush: sql">CREATE OR REPLACE FUNCTION ANY_OF(ARG1 INTEGER, 
                                  ARG2 INTEGER,
                                  ARG3 INTEGER DEFAULT NULL,
                                  ARG4 INTEGER DEFAULT NULL,
                                  ARG5 INTEGER DEFAULT NULL)
RETURNS INTEGER
BEGIN
  IF (ARG1 = 1 OR ARG2 = 1 OR 
       (ARG3 IS NOT NULL AND ARG3 = 1) OR
       (ARG4 IS NOT NULL AND ARG4 = 1) OR
       (ARG5 IS NOT NULL AND ARG5 = 1)) THEN
    RETURN 1;
  ELSE
    RETURN 0;
  END IF;
END@</pre></p>
<p>We specify that all arguments after the second argument default to NULL. If an argument is NULL, we will ignore that argument. This saved me a lot of code. Hopefully, UDF default arguments can save you some code as well.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2314&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fnew-in-db2-for-luw-9-7-2-udf-default-parameters.html&amp;title=New%20in%20DB2%20for%20LUW%209.7.2%3A%20UDF%20default%20parameters" id="wpa2a_14"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/tNCz880GLUY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/new-in-db2-for-luw-9-7-2-udf-default-parameters.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/new-in-db2-for-luw-9-7-2-udf-default-parameters.html</feedburner:origLink></item>
		<item>
		<title>Do your uninstall scripts leave something behind?</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/MnT0MPLqnKw/testing-that-your-uninstall-scripts-leave-nothing-behind.html</link>
		<comments>http://www.thekguy.com/testing-that-your-uninstall-scripts-leave-nothing-behind.html#comments</comments>
		<pubDate>Wed, 26 May 2010 04:00:09 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[db2 luw]]></category>
		<category><![CDATA[db2look]]></category>
		<category><![CDATA[install]]></category>
		<category><![CDATA[uninstall]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2196</guid>
		<description><![CDATA[Have you ever needed to write a pair of scripts: one to create some tables, data types, or user-defined functions and a second to uninstall the same? How do you test that the uninstall script did its job and didn&#8217;t leave any database objects behind? Today, using a tool that comes with DB2, I&#8217;ll show [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Ftesting-that-your-uninstall-scripts-leave-nothing-behind.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Ftesting-that-your-uninstall-scripts-leave-nothing-behind.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>Have you ever needed to write a pair of scripts: one to create some tables, data types, or user-defined functions and a second to uninstall the same? How do you test that the uninstall script did its job and didn&#8217;t leave any database objects behind? Today, using a tool that comes with DB2, I&#8217;ll show you how to ensure that your cleanup scripts do their jobs.<span id="more-2196"></span></p>
<p>I test my cleanup scripts using a tool called <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.admin.cmd.doc/doc/r0002051.html">db2look</a>. It is a powerful tool for extracting statistics and <a href="http://en.wikipedia.org/wiki/Data_Definition_Language">DDL</a> from a database and it has several command line options. In my case, since I know that my program installs new data types, tables, variables, functions, and procedures, I use db2look with the -e option, which generates all the DDL needed to duplicate the database.</p>
<p>Say you have two scripts: install.sql and uninstall.sql. You want to verify that any database objects created by install.sql get cleaned up when you run uninstall.sql. You can verify this using db2look as follows (assuming you have the authority to create a fresh database on your development system):</p>
<ol>
<li>Create a new database to provide a baseline: <code>create database test</code></li>
<li>Run db2look on your new database and store the result: <code>db2look -d test -e -o db2look.baseline</code></li>
<li>Run your install script: <code>db2 -tf install.sql</code></li>
<li>Run you uninstall script: <code>db2 -tf uninstall.sql</code></li>
<li>Rerun db2look and store the result: <code>db2look -d test -e -o db2look.postuninstall</code></li>
<li>Run your favorite diff tool on the two db2look result files you generated: <code>diff db2look.postuninstall db2look.baseline</code></li>
</ol>
<p>If your uninstall script did its job perfectly, the only difference you should see is a one-line mismatch in which a timestamp of when db2look was run is printed. Otherwise, the diff output tells you exactly what types, procedures, or tables your uninstall script failed to clean up. This kind of test can easily be made a part of any automated testing process you have for testing your scripts. In fact, if you do this as part of your automated testing and create your baseline before your tests are run, it would also detect if any of your automated tests failed to clean up any database objects they created as well.</p>
<p>If the timestamp line makes automated comparison difficult, you can always filter it out beforehand from each file using something like <a href="http://en.wikipedia.org/wiki/Sed">sed</a>:</p>
<p><code>sed 's/-- Timestamp.*//' db2look.baseline &gt; db2look.baseline.notimestamp</code><br />
<code>sed 's/-- Timestamp.*//' db2look.postuninstall &gt; db2look.postuninstall.notimestamp</code><br />
<code>diff db2look.postuninstall.notimestamp db2look.baseline.notimestamp</code></p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2196&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Ftesting-that-your-uninstall-scripts-leave-nothing-behind.html&amp;title=Do%20your%20uninstall%20scripts%20leave%20something%20behind%3F" id="wpa2a_16"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/MnT0MPLqnKw" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/testing-that-your-uninstall-scripts-leave-nothing-behind.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/testing-that-your-uninstall-scripts-leave-nothing-behind.html</feedburner:origLink></item>
		<item>
		<title>DB2 Stored Procedures and Defaults (Conclusion)</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/b6J9BvMm-p8/stored-procedures-and-defaults-conclusion.html</link>
		<comments>http://www.thekguy.com/stored-procedures-and-defaults-conclusion.html#comments</comments>
		<pubDate>Fri, 21 May 2010 04:00:18 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[stored procedures]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2153</guid>
		<description><![CDATA[If you remember the previous post, I showed how moving some logic from an anonymous block into a scalar function caused the program to start reporting an SQL0577N, saying that the underlying procedure was trying to modify SQL data even though it was doing no such thing. The clue to the cause of the problem [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fstored-procedures-and-defaults-conclusion.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fstored-procedures-and-defaults-conclusion.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>If you remember <a href="http://www.thekguy.com/db2-stored-procedures-and-defaults.html">the previous post</a>, I showed how moving some logic from an <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.apdv.plsql.doc/doc/c0053848.html">anonymous block</a> into a <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.apdv.routines.doc/doc/c0023360.html">scalar function</a> caused the program to start reporting an <a href="http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=/com.ibm.db2.luw.messages.sql.doc/doc/msql00577n.html">SQL0577N</a>, saying that the underlying procedure was trying to modify SQL data even though it was doing no such thing. The clue to the cause of the problem is in the title and the first paragraph of the previous post: defaults.<span id="more-2153"></span> </p>
<p>The default behavior for a stored procedure when you don&#8217;t specify whether it MODIFIES SQL DATA or READS SQL DATA is to act as if you had specified MODIFIES SQL DATA. The weird part is the confusing message: <em>SQL0577N  User defined routine &#8220;KMCDONAL.EQ&#8221; (specific name &#8220;SQL100516224914100&#8243;) attempted to modify data but was not defined as MODIFIES SQL DATA</em>, especially since the problem is not that it modifies without saying &#8220;MODIFIES&#8221;, but that its default says &#8220;MODIFIES&#8221; and a procedure declared as such cannot be used in the context in which I&#8217;m attempting to use it (i.e. inside a scalar function).</p>
<p>The solution is to explicitly state CONTAINS SQL as I do on line 5 below (specifying READS SQL DATA will also work, but I don&#8217;t read SQL in the EQ procedure either, so I may as well use CONTAINS SQL):</p>
<p><pre class="brush: sql">SET SERVEROUTPUT ON@
CREATE OR REPLACE TYPE TEST_T AS ROW(ONE INTEGER)@

CREATE OR REPLACE PROCEDURE EQ(IN LEFT TEST_T, IN RIGHT TEST_T, OUT RESULT INTEGER)
CONTAINS SQL
BEGIN
  IF (LEFT.ONE = RIGHT.ONE) THEN
    SET RESULT = 1;
  ELSE
    SET RESULT = 0;
  END IF;
END@

CREATE OR REPLACE FUNCTION EQ_FN(LEFT TEST_T, RIGHT TEST_T)
RETURNS INTEGER
BEGIN
  DECLARE RESULT INTEGER;
  PREPARE S1 FROM 'CALL EQ(?, ?, ?)';
  EXECUTE S1 INTO RESULT USING LEFT, RIGHT; 
  RETURN RESULT;
END@

BEGIN
  DECLARE T1 TEST_T;
  DECLARE T2 TEST_T;
  DECLARE RESULT INTEGER;
  SET T1.ONE = 1;
  SET T2.ONE = 1;
  SET RESULT = EQ_FN(T1, T2);
  CALL DBMS_OUTPUT.PUT_LINE('Result = ' || RESULT);
END@</pre></p>
<p>This succeeds, resulting in the following output:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

Result = 1</pre></p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2153&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fstored-procedures-and-defaults-conclusion.html&amp;title=DB2%20Stored%20Procedures%20and%20Defaults%20%28Conclusion%29" id="wpa2a_18"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/b6J9BvMm-p8" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/stored-procedures-and-defaults-conclusion.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/stored-procedures-and-defaults-conclusion.html</feedburner:origLink></item>
		<item>
		<title>DB2 Stored Procedures and Defaults</title>
		<link>http://feedproxy.google.com/~r/TheKGuy_db2/sql-pl-db2/~3/6EjLAccgbR0/db2-stored-procedures-and-defaults.html</link>
		<comments>http://www.thekguy.com/db2-stored-procedures-and-defaults.html#comments</comments>
		<pubDate>Wed, 19 May 2010 04:00:43 +0000</pubDate>
		<dc:creator>Keith McDonald</dc:creator>
				<category><![CDATA[db2]]></category>
		<category><![CDATA[sql pl]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[stored procedures]]></category>

		<guid isPermaLink="false">http://www.thekguy.com/?p=2009</guid>
		<description><![CDATA[Have you ever spent a long time trying to figure out what was causing DB2 to give you a bizarre error message only to find that the root cause was a default you didn&#8217;t know was a default? I got just such a message the other day when writing an SQL stored procedure. In this [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Fwww.thekguy.com%2Fdb2-stored-procedures-and-defaults.html">
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Fwww.thekguy.com%2Fdb2-stored-procedures-and-defaults.html&amp;source=thekguy&amp;style=normal&amp;service=bit.ly&amp;b=2" height="61" width="50" />
			</a>
		</div><p>Have you ever spent a long time trying to figure out what was causing DB2 to give you a bizarre error message only to find that the root cause was a default you didn&#8217;t know was a default? I got just such a message the other day when writing an SQL stored procedure. In this post, I will show you the confusing message I encountered and give you the opportunity to see if you can figure out the solution.<span id="more-2009"></span></p>
<p>I was writing a procedure to do a simple comparison between two variables of ROW type, since there are no built-in comparison functions for variables of ROW type in DB2 9.7. It looked something like the following:</p>
<p><pre class="brush: sql">SET SERVEROUTPUT ON@
CREATE OR REPLACE TYPE TEST_T AS ROW(ONE INTEGER)@

CREATE OR REPLACE PROCEDURE EQ(IN LEFT TEST_T, IN RIGHT TEST_T, OUT RESULT INTEGER)
BEGIN
  IF (LEFT.ONE = RIGHT.ONE) THEN
    SET RESULT = 1;
  ELSE
    SET RESULT = 0;
  END IF;
END@

BEGIN
  DECLARE T1 TEST_T;
  DECLARE T2 TEST_T;
  DECLARE RESULT INTEGER;
  SET T1.ONE = 1;
  SET T2.ONE = 1;
  PREPARE S1 FROM 'CALL EQ(?, ?, ?)';
  EXECUTE S1 INTO RESULT USING T1, T2;
  CALL DBMS_OUTPUT.PUT_LINE('Result = ' || RESULT);
END@</pre></p>
<p>The above code worked just fine. As expected, the result looked like this:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

Result = 1</pre></p>
<p>But then I moved the prepare and execute code in the anonymous block above into a scalar function named EQ_FN:</p>
<p><pre class="brush: sql">CREATE OR REPLACE FUNCTION EQ_FN(LEFT TEST_T, RIGHT TEST_T)
RETURNS INTEGER
BEGIN
  DECLARE RESULT INTEGER;
  PREPARE S1 FROM 'CALL EQ(?, ?, ?)';
  EXECUTE S1 INTO RESULT USING LEFT, RIGHT; 
  RETURN RESULT;
END@

BEGIN
  DECLARE T1 TEST_T;
  DECLARE T2 TEST_T;
  DECLARE RESULT INTEGER;
  SET T1.ONE = 1;
  SET T2.ONE = 1;
  SET RESULT = EQ_FN(T1, T2);
  CALL DBMS_OUTPUT.PUT_LINE('Result = ' || RESULT);
END@</pre></p>
<p>This did not work. The error message was:</p>
<p><pre class="brush: css">DB20000I  The SET SERVEROUTPUT command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB20000I  The SQL command completed successfully.

DB21034E  The command was processed as an SQL statement because it was not a 
valid Command Line Processor command.  During SQL processing it returned:
SQL0577N  User defined routine "KMCDONAL.EQ" (specific name 
"SQL100516224914100") attempted to modify data but was not defined as MODIFIES 
SQL DATA.</pre></p>
<p>My EQ procedure does not actually modify SQL data, so why does it say it attempted to? Do you know what I did wrong? I&#8217;ll give the answer in <a href="http://www.thekguy.com/stored-procedures-and-defaults-conclusion.html">DB2 Stored Procedures and Defaults (Conclusion)</a>. Coverage of db2top continues next week.</p>
<img src="http://www.thekguy.com/?ak_action=api_record_view&id=2009&type=feed" alt="" /><p><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fwww.thekguy.com%2Fdb2-stored-procedures-and-defaults.html&amp;title=DB2%20Stored%20Procedures%20and%20Defaults" id="wpa2a_20"><img src="http://www.thekguy.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></p><img src="http://feeds.feedburner.com/~r/TheKGuy_db2/sql-pl-db2/~4/6EjLAccgbR0" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.thekguy.com/db2-stored-procedures-and-defaults.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.thekguy.com/db2-stored-procedures-and-defaults.html</feedburner:origLink></item>
	</channel>
</rss>

