<?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-9099513356252812992</id><updated>2011-12-27T23:07:15.799-08:00</updated><category term='linux'/><category term='grails dbunit testing'/><category term='Groovy DSL Builder POI'/><category term='slickedit vcs git'/><category term='Ant Groovy GraphViz dependencies'/><title type='text'>Software Hero Worship</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>14</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-7200607949998238429</id><published>2011-12-27T22:45:00.000-08:00</published><updated>2011-12-27T23:07:15.816-08:00</updated><title type='text'>Checking Foreign Key attribute consistency</title><content type='html'>While evolving a schema during development, it's sometimes hard to make sure that column attributes remain consistent across different tables.&lt;br /&gt;&lt;br /&gt;Some databases provide domain types. So for example, with Firebird/InterBase you can define a domain type or alias for your invoice_number column as, say, VARCHAR(10). Then you can use the domain type in defining any tables containing invoice_number, and the column attributes will always be consistent.&lt;br /&gt;&lt;br /&gt;Oracle doesn't have a natural way to do this. One trick I've used is preprocessing  SQL DDL files with Ant and using "macros" for column types. So I might have a types.properties file like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:plain"&gt;...&lt;br /&gt;INVOICE_NUMBER_TYPE = VARCHAR2(10)&lt;br /&gt;...&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then in create_table_invoice.sql I might have:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:sql"&gt;CREATE TABLE invoice (&lt;br /&gt;  invoice_number @INVOICE_NUMBER_TYPE@ NOT NULL,&lt;br /&gt;  ...&lt;br /&gt;);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And also use the macro elsewhere for any foreign key column.&lt;br /&gt;&lt;br /&gt;But, this turned out to be a bit of a pain. For one thing, it makes the build more convoluted because of the preprocessing required. More importantly, it made the SQL DDL files "invalid". We couldn't just run one in sqlplus, without the preprocessing step first. We couldn't send one to a DBA. We didn't get good IDE support, because the IDE doesn't understand the type macros.&lt;br /&gt;&lt;br /&gt;As a result, I've reverted to putting hardcoded column attributes in the DDL files. But this takes me back to the problem of keeping the foreign keys parent/child attributes consistent.&lt;br /&gt;&lt;br /&gt;Another approach to that problem is to use a view like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:sql"&gt;CREATE OR REPLACE VIEW chk_foreign_key_type AS&lt;br /&gt;SELECT&lt;br /&gt;  ac.table_name child_table,&lt;br /&gt;  acc.column_name child_column,&lt;br /&gt;  atc.data_type child_data_type,&lt;br /&gt;  atc.data_length child_data_length,&lt;br /&gt;  atc.data_scale child_data_scale,&lt;br /&gt;  ac2.table_name parent_table,&lt;br /&gt;  acc2.column_name parent_column,&lt;br /&gt;  atc2.data_type parent_data_type,&lt;br /&gt;  atc2.data_length parent_data_length,&lt;br /&gt;  atc2.data_scale parent_data_scale&lt;br /&gt;FROM   all_constraints ac&lt;br /&gt;       JOIN all_cons_columns acc ON acc.owner = ac.owner &lt;br /&gt;            AND acc.constraint_name = ac.constraint_name&lt;br /&gt;       JOIN all_tab_columns atc ON atc.owner = ac.owner &lt;br /&gt;            AND atc.table_name = acc.table_name &lt;br /&gt;            AND atc.column_name = acc.column_name&lt;br /&gt;       JOIN all_constraints ac2 ON ac2.owner = ac.owner &lt;br /&gt;            AND ac2.constraint_name = ac.r_constraint_name&lt;br /&gt;       JOIN all_cons_columns acc2 ON acc2.owner = ac2.owner &lt;br /&gt;            AND acc2.constraint_name = ac2.constraint_name &lt;br /&gt;            AND acc2.position = acc.position&lt;br /&gt;       JOIN all_tab_columns atc2 ON atc2.owner = acc2.owner &lt;br /&gt;            AND atc2.table_name = acc2.table_name &lt;br /&gt;            AND atc2.column_name = acc2.column_name&lt;br /&gt;WHERE  ac.owner = 'your_schema'&lt;br /&gt;  AND  ac.constraint_type = 'R'&lt;br /&gt;  AND  (atc2.data_type &lt;&gt; atc.data_type&lt;br /&gt;        OR atc2.data_length &lt;&gt; atc.data_length&lt;br /&gt;        OR NVL(atc2.data_scale, -1) &lt;&gt; NVL(atc2.data_scale, -1))&lt;br /&gt;ORDER BY 1, 2&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;COMMENT ON TABLE chk_foreign_key_type IS&lt;br /&gt;  'Foreign key column(s) different from parent type/length/scale'&lt;br /&gt;/&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This view will return any foreign key where a data type, length or scale of a column in the child table does not match the corresponding column in the parent table.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-7200607949998238429?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/7200607949998238429/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=7200607949998238429' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7200607949998238429'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7200607949998238429'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2011/12/checking-foreign-key-attribute.html' title='Checking Foreign Key attribute consistency'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-7589031319759071489</id><published>2011-02-08T22:58:00.000-08:00</published><updated>2011-02-08T23:28:22.881-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ant Groovy GraphViz dependencies'/><title type='text'>Ant Target Dependency Graph</title><content type='html'>We can get a pretty good diagram of our &lt;a href="http://ant.apache.org/"&gt;Ant&lt;/a&gt; target dependencies very easily using an &lt;a href="http://groovy.codehaus.org/The+groovy+Ant+Task"&gt;embedded Groovy script&lt;/a&gt; and &lt;a href="http://www.graphviz.org/"&gt;GraphViz&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Add this to build.xml:&lt;br /&gt;&lt;pre class="brush:xml"&gt;&lt;target name="target-graph"&gt;&lt;br /&gt;    &lt;taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy"/&gt;&lt;br /&gt;    &lt;groovy&gt;&lt;br /&gt;      def u(x) {x.toString().replace("-", "_").replace(".", "_")}&lt;br /&gt;      new File("build.dot").text = """&lt;br /&gt;digraph ant {&lt;br /&gt;  ${project.targets.values().collect {target -&gt;&lt;br /&gt;    target.dependencies.collect {dep -&gt;&lt;br /&gt;      u(dep) + " -&gt; " + u(target)&lt;br /&gt;    }.join("\n")&lt;br /&gt;  }.join("\n")}&lt;br /&gt;}&lt;br /&gt;"""&lt;br /&gt;    &lt;/groovy&gt;&lt;br /&gt;  &lt;/target&gt;&lt;/pre&gt;&lt;br /&gt;You will need to have the embeddable groovy-all.jar in Ant's classpath, e.g. in ~/.ant/lib/.&lt;br /&gt;&lt;br /&gt;Then run "ant target-graph". It writes a build.dot file in the current directory.&lt;br /&gt;&lt;br /&gt;Convert this into a picture using the &lt;a href="http://www.graphviz.org/"&gt;GraphViz&lt;/a&gt; dot command:&lt;br /&gt;&lt;pre&gt;dot -Tsvg -O build.dot&lt;/pre&gt;&lt;br /&gt;The results are surprisingly good.&lt;br /&gt;&lt;br /&gt;Here's an example from the Apache &lt;a href="http://commons.apache.org/dbcp/"&gt;commons-dbcp&lt;/a&gt; project:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_ir1ImJq30j4/TVI9WvxbaoI/AAAAAAAAAbI/Y9c60vKbU6s/s1600/commons-dbcp-build.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 191px;" src="http://2.bp.blogspot.com/_ir1ImJq30j4/TVI9WvxbaoI/AAAAAAAAAbI/Y9c60vKbU6s/s320/commons-dbcp-build.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5571583150000925314" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I'm not going to show you the diagram I got for our system at work, the reason I wrote this script! The diagram is so big and complex, I was shocked. After being happy with Ant all these years, I think it's time to be getting serious about &lt;a href="http://www.gradle.org/"&gt;Gradle&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-7589031319759071489?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/7589031319759071489/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=7589031319759071489' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7589031319759071489'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7589031319759071489'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2011/02/ant-target-dependency-graph.html' title='Ant Target Dependency Graph'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_ir1ImJq30j4/TVI9WvxbaoI/AAAAAAAAAbI/Y9c60vKbU6s/s72-c/commons-dbcp-build.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-6633739753660691251</id><published>2011-01-22T12:35:00.000-08:00</published><updated>2011-01-22T12:48:43.266-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='slickedit vcs git'/><title type='text'>SlickEdit 2011 Wish List</title><content type='html'>Well, we're nearly through January 2011, and the SlickEdit 2011 beta is due any day now.&lt;br /&gt;&lt;br /&gt;I've been using &lt;a href="http://www.slickedit.com"&gt;SlickEdit&lt;/a&gt; since 1996, and I still get kind of excited around this time of year when a new version comes out.  Sometimes the team surprises me.  &lt;br /&gt;&lt;br /&gt;Here is my wish list for features in SlickEdit 2011:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://groovy.codehaus.org/"&gt;Groovy Language&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.scala-lang.org/"&gt;Scala Language&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://clojure.org/"&gt;Clojure Language&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://git-scm.com/"&gt;Git DVCS&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://mercurial.selenic.com/"&gt;Mercurial DVCS&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://bazaar.canonical.com/en/"&gt;Bazaar DVCS&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Useful &lt;a href="http://subversion.apache.org/"&gt;Subversion VCS&lt;/a&gt; Support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Useful "subword" features&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The first three are hot JVM languages that I'd love to see support for.  I don't really expect to see them, but you never know.  Last year, SlickEdit added support for Erlang, Haskell and F#, so they aren't completely in the dark about hot languages.&lt;br /&gt;&lt;br /&gt;The next three items are popular distributed version control systems.  Again, I don't really expect much from SlickEdit on that, yet.  Forum posts on the topic have met with disappointing reponses from SlickEdit staff -- doesn't look like they "clicked" on DVCS yet.  It took them an awful long time to move on from CVS to Subversion themselves, and even now the Subversion support is miserable compared to any other tool I've used.  And so, my wish, modernise the Subversion support.&lt;br /&gt;&lt;br /&gt;SlickEdit has a lot of advanced features for C/C++ programmers.  C/C++ programmers probably make up a very large chunk, if not the majority, of SlickEdit users.  And as far as I can tell, SlickEdit is actually one of the best "IDE"s available for C/C++.  I don't do C/C++ any more myself though, I do JVM-based languages mostly.  And with Java, SlickEdit also tries to be an uber-IDE, with Project Types, JUnit, Ant support and more.  But here it falls far, far short of industry standards.  Java programmers are really spoiled by the superb IDEs aavailable for them, and two of the best ones are even free.  Anyway, I don't wish for SlickEdit to improve its Java IDE features.  I'm happy to use a Java IDE for that kind of work.  The point I'd like to make is that supporting current VCS systems, and supporting them &lt;b&gt;really well&lt;/b&gt;, would benefit &lt;b&gt;all&lt;/b&gt; SlickEdit users.  I can't imagine many SlickEdit users are not using VCS, and many of them probably use a modern VCS, such as Git.  It's really about time SlickEdit caught up with the VCS game.&lt;br /&gt;&lt;br /&gt;For a couple of examples of excellent VCS integration, look at:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.jetbrains.com/idea"&gt;IntelliJ IDEA&lt;/a&gt; and related products from &lt;a href="http://www.jetbrains.com"&gt;JetBrains&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.syntevo.com/smartsvn/index.html"&gt;SmartSVN&lt;/a&gt;, &lt;a href="http://www.syntevo.com/smartgit/index.html"&gt;SmartGit&lt;/a&gt; and even &lt;a href="http://www.syntevo.com/smartcvs/index.html"&gt;SmartCVS&lt;/a&gt; from Syntevo&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;SlickEdit 2011 included some rather dubious new features.  My favorite "non useful feature" was Subword Navigation.  You can move the cursor through camel-cased words such as AbstractBeanFactory.  But when would anyone want to do that?  Far more useful would be file or class completion/loading using smart "camel typing", as introduced by &lt;a href="http://www.jetbrains.com/idea"&gt;IntelliJ IDEA&lt;/a&gt; and copied by other tools.  With IDEA, I can press Ctrl+N to open a class, then type "ABF" or "AbBeFa" to open the AbstractBeanFactory class.  This is really useful, and would be something SlickEdit could really benefit from.&lt;br /&gt;&lt;br /&gt;Anyway, I'm sure SlickEdit 2011 will contain a few pleasant surprises, as well as a few new annoying bugs.  As always, it will be interesting to figure whether the feature-to-bug ratio improves, or not.  I'm looking forward to the beta.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-6633739753660691251?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/6633739753660691251/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=6633739753660691251' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6633739753660691251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6633739753660691251'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2011/01/slickedit-2011-wish-list.html' title='SlickEdit 2011 Wish List'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-5462870032278168492</id><published>2011-01-06T18:04:00.000-08:00</published><updated>2011-01-06T18:12:58.126-08:00</updated><title type='text'>Groovy DSL/Builders: ZIP Output Streams</title><content type='html'>Let's follow up last week's post with another example of a very similar, very simple builder.&lt;br /&gt;&lt;br /&gt;This one is for outputting ZIPped data to a stream.  Let's take the standard example of using Java's ZIP support to zip up a folder of files.&lt;br /&gt;&lt;br /&gt;Because the JDK does not include methods to traverse the filesystem, we need to define a method to be called recursively for subdirectories:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:java"&gt;&lt;br /&gt;  private void zipDirectory(File dir, ZipOutputStream zos) throws IOException {&lt;br /&gt;    for (File file : dir.listFiles()) {&lt;br /&gt;      if (file.isDirectory()) {&lt;br /&gt;        zipDirectory(file, zos);&lt;br /&gt;      }&lt;br /&gt;      else {&lt;br /&gt;        ZipEntry entry = new ZipEntry(file.getPath());&lt;br /&gt;        entry.setSize(file.length());&lt;br /&gt;        entry.setTime(file.lastModified());&lt;br /&gt;        zos.putNextEntry(entry);&lt;br /&gt;        IOUtils.copy(new FileInputStream(file), zos);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We cheated a little here by using the Apache Commons IO &lt;tt&gt;IOUtils&lt;/tt&gt; class to actually copy the file bytes to the ZIP file.  Also, we don't do anything here with &lt;tt&gt;IOExceptions&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;With this method in place, we can create a ZIP file from a folder using:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:java"&gt;&lt;br /&gt;  ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));&lt;br /&gt;  zipDirectory(new File(dir), zos);&lt;br /&gt;  zos.close();&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Groovy's JDK IO extensions, and filesystem traversal methods, make this job quite a bit easier.  Here's the Groovy code to do the same thing:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;  new ZipOutputStream(new FileOutputStream(zipFile)).withStream {zos -&gt;&lt;br /&gt;    new File(dir).traverse(type: FileType.FILES) {File file -&gt;&lt;br /&gt;      def entry = new ZipEntry(file.path)&lt;br /&gt;      entry.size = file.length()&lt;br /&gt;      entry.time = file.lastModified()&lt;br /&gt;      zos.putNextEntry(entry)&lt;br /&gt;      zos &lt;&lt; file.bytes&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This code is still a bit awkward in how it interacts with Java's ZIP API, in particular the creation of the &lt;tt&gt;ZipEntry&lt;/tt&gt; object.&lt;br /&gt;&lt;br /&gt;Using a simple builder, we can rewrite this as follows:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;  new ZipBuilder(new FileOutputStream(zipFile)).zip {&lt;br /&gt;    new File(dir).traverse(type: FileType.FILES) {File file -&gt;&lt;br /&gt;      entry(file.path, size: file.length(), time: file.lastModified()) {it &lt;&lt; file.bytes}&lt;br /&gt;    }&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;The &lt;tt&gt;ZipBuilder&lt;/tt&gt; provides two methods:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;tt&gt;zip()&lt;/tt&gt;: creates and manages the &lt;tt&gt;ZipOutputStream&lt;/tt&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;tt&gt;entry()&lt;/tt&gt; (nested): creates and adds a &lt;tt&gt;ZipEntry&lt;/tt&gt; to the enclosing zip stream&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;As with other builders, this builder promotes readable code that reflects the structure of the object to be created.&lt;br /&gt;&lt;br /&gt;Here's the code for the builder itself:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;class ZipBuilder {&lt;br /&gt;&lt;br /&gt;  @InheritConstructors&lt;br /&gt;  static class NonClosingOutputStream extends FilterOutputStream {&lt;br /&gt;    void close() {&lt;br /&gt;      // do nothing&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  ZipOutputStream zos&lt;br /&gt;&lt;br /&gt;  ZipBuilder(OutputStream os) {&lt;br /&gt;    zos = new ZipOutputStream(os)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void zip(Closure closure) {&lt;br /&gt;    closure.delegate = this&lt;br /&gt;    closure.call()&lt;br /&gt;    zos.close()&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void entry(Map props, String name, Closure closure) {&lt;br /&gt;    def entry = new ZipEntry(name)&lt;br /&gt;    props.each {k, v -&gt; entry[k] = v}&lt;br /&gt;    zos.putNextEntry(entry)&lt;br /&gt;    NonClosingOutputStream ncos = new NonClosingOutputStream(zos)&lt;br /&gt;    closure.call(ncos)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void entry(String name, Closure closure) {&lt;br /&gt;    entry([:], name, closure)&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This builder uses the same style with &lt;tt&gt;Closure&lt;/tt&gt;s as the &lt;tt&gt;HSSFWorkbookBuilder&lt;/tt&gt; described &lt;a href="http://skepticalhumorist.blogspot.com/2010/12/groovy-dslbuilders-poi-spreadsheets.html"&gt;earlier&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;There are a few other Groovy (and Java) features to note:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Java's ZIP library requires clients to write to the &lt;tt&gt;ZipOutputStream&lt;/tt&gt; for each entry created.  We need to make sure that no entry closes the &lt;tt&gt;ZipOutputStream&lt;/tt&gt; -- it must be closed only when the zip stream is finished.  (Many of Groovy's output methods close streams automatically.)  For this reason, we wrap the output stream in a &lt;tt&gt;NonClosingOutputStream&lt;/tt&gt; before passing it to an entry.  This class is simply defined as a &lt;tt&gt;FilterOutputStream&lt;/tt&gt; (&lt;tt&gt;OutputStream decorator&lt;/tt&gt;) with a no-op &lt;tt&gt;close()&lt;/tt&gt; method.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;We use Groovy's &lt;tt&gt;@InheritConstructors&lt;/tt&gt; to save repeating the trivial constructor.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The &lt;tt&gt;entry()&lt;/tt&gt; method creates a new &lt;tt&gt;ZipEntry&lt;/tt&gt; with its mandatory &lt;tt&gt;name&lt;/tt&gt; property.  It then populates additional optional properties from a &lt;tt&gt;Map&lt;/tt&gt;, using Groovy's support for setting Java Beans properties as &lt;tt&gt;Map&lt;/tt&gt; keys.  These properties are intended to be provided as named arguments to the method, as shown in the example earlier.  This makes for a very concise and intuitive way to set the properties.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The main overload of &lt;tt&gt;entry()&lt;/tt&gt; is declared to take its arguments in this order: &lt;tt&gt;Map props&lt;/tt&gt;, &lt;tt&gt;String name&lt;/tt&gt;, &lt;tt&gt;Closure closure&lt;/tt&gt;. When called, &lt;tt&gt;entry()&lt;/tt&gt; is (typically) given arguments in a different order: &lt;tt&gt;String name&lt;/tt&gt;, &lt;tt&gt;Map props&lt;/tt&gt;, &lt;tt&gt;Closure closure&lt;/tt&gt;. This is due to Groovy's convention for passing named arguments to a method, described &lt;a href="http://groovy.codehaus.org/Extended+Guide+to+Method+Signatures"&gt;here&lt;/a&gt;, in the section "Named Arguments".&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;One final note about this builder -- it doesn't just work with files.  Because the constructor takes an &lt;tt&gt;OutputStream&lt;/tt&gt;, it can write to any stream.  So it could be used to write directly to a servlet response, for example.  Similarly, the entries are populated as streams, so they can be filled by anything that can write to a stream.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-5462870032278168492?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/5462870032278168492/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=5462870032278168492' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/5462870032278168492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/5462870032278168492'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2011/01/groovy-dslbuilders-zip-output-streams.html' title='Groovy DSL/Builders: ZIP Output Streams'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-4995940568510254585</id><published>2010-12-28T21:26:00.000-08:00</published><updated>2010-12-28T22:15:03.908-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Groovy DSL Builder POI'/><title type='text'>Groovy DSL/Builders: POI Spreadsheets</title><content type='html'>It's well-known that Groovy is very rich for creating DSLs and fluent builder APIs.&lt;br /&gt;&lt;br /&gt;I work a lot with the &lt;a href="http://poi.apache.org"&gt;Apache POI library&lt;/a&gt; to generate Excel workbooks from data.  We can use Groovy very easily to support a fluent and readable API for creating workbooks.&lt;br /&gt;&lt;br /&gt;Here's a very simple example.  Suppose we want to populate a workbook with two sheets with some data.  Using the raw POI API, we could code something like this:&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;  def workbook = new HSSFWorkbook()&lt;br /&gt;  def sheet1 = workbook.createSheet("Data")&lt;br /&gt;  def row10 = sheet1.createRow(0)&lt;br /&gt;  row10.createCell(0).setCellValue(new HSSFRichTextString("Invoice Number"))&lt;br /&gt;  row10.createCell(1).setCellValue(new HSSFRichTextString("Invoice Date"))&lt;br /&gt;  row10.createCell(2).setCellValue(new HSSFRichTextString("Amount"))&lt;br /&gt;  def row11 = sheet1.createRow(1)&lt;br /&gt;  row11.createCell(0).setCellValue(new HSSFRichTextString("100"))&lt;br /&gt;  row11.createCell(1).setCellValue(Date.parse("yyyy-MM-dd", "2010-10-18"))&lt;br /&gt;  row11.createCell(2).setCellValue(123.45)&lt;br /&gt;  def row12 = sheet1.createRow(2)&lt;br /&gt;  row12.createCell(0).setCellValue(new HSSFRichTextString("600"))&lt;br /&gt;  row12.createCell(1).setCellValue(Date.parse("yyyy-MM-dd", "2010-11-17"))&lt;br /&gt;  row12.createCell(2).setCellValue(132.54)&lt;br /&gt;  def sheet2 = workbook.createSheet("Summary")&lt;br /&gt;  def row20 = sheet2.createRow(0)&lt;br /&gt;  row20.createCell(0).setCellValue(new HSSFRichTextString("Sheet: Summary"))&lt;br /&gt;  def row21 = sheet2.createRow(1)&lt;br /&gt;  row21.createCell(0).setCellValue(new HSSFRichTextString("Total"))&lt;br /&gt;  row21.createCell(1).setCellValue(123.45 + 132.54)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is not very readable.  Even if we extract routines such as a common method to generate the cells in a row, the structure of our code does not follow closely the structure of what we want to create.&lt;br /&gt;One of the big advantages of builders is that the structure of the code can match closely the structure of the generated result.&lt;br /&gt;&lt;br /&gt;Here's the same workbook, created with a simple builder API:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;  def workbook = new HSSFWorkbookBuilder().workbook {&lt;br /&gt;    sheet("Data") { // sheet1&lt;br /&gt;      row(["Invoice Number", "Invoice Date", "Amount"])&lt;br /&gt;      row(["100", Date.parse("yyyy-MM-dd", "2010-10-18"), 123.45])&lt;br /&gt;      row(["600", Date.parse("yyyy-MM-dd", "2010-11-17"), 132.54])&lt;br /&gt;    }&lt;br /&gt;    sheet("Summary") { // sheet2&lt;br /&gt;      row(["Sheet: Summary"])&lt;br /&gt;      row(["Total", 123.45 + 132.54])&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &lt;tt&gt;HSSFWorkbookBuilder&lt;/tt&gt; class required to do this is very straightforward:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;import org.apache.poi.hssf.usermodel.HSSFRichTextString&lt;br /&gt;import org.apache.poi.hssf.usermodel.HSSFWorkbook&lt;br /&gt;import org.apache.poi.ss.usermodel.Cell&lt;br /&gt;import org.apache.poi.ss.usermodel.Row&lt;br /&gt;import org.apache.poi.ss.usermodel.Sheet&lt;br /&gt;import org.apache.poi.ss.usermodel.Workbook&lt;br /&gt;&lt;br /&gt;class HSSFWorkbookBuilder {&lt;br /&gt;&lt;br /&gt;  private Workbook workbook = new HSSFWorkbook()&lt;br /&gt;  private Sheet sheet&lt;br /&gt;  private int rows&lt;br /&gt;&lt;br /&gt;  Workbook workbook(Closure closure) {&lt;br /&gt;    closure.delegate = this&lt;br /&gt;    closure.call()&lt;br /&gt;    workbook&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void sheet(String name, Closure closure) {&lt;br /&gt;    sheet = workbook.createSheet(name)&lt;br /&gt;    rows = 0&lt;br /&gt;    closure.delegate = this&lt;br /&gt;    closure.call()&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void row(values) {&lt;br /&gt;    Row row = sheet.createRow(rows++ as int)&lt;br /&gt;    values.eachWithIndex {value, col -&gt;&lt;br /&gt;      Cell cell = row.createCell(col)&lt;br /&gt;      switch (value) {&lt;br /&gt;        case Date: cell.setCellValue((Date) value); break&lt;br /&gt;        case Double: cell.setCellValue((Double) value); break&lt;br /&gt;        case BigDecimal: cell.setCellValue(((BigDecimal) value).doubleValue()); break&lt;br /&gt;        default: cell.setCellValue(new HSSFRichTextString("" + value)); break&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;The magic is in the handling of the nested closures, and setting the delegate for each to the builder so that methods are resolved against the builder.&lt;br /&gt;&lt;br /&gt;Here's another example of using such a builder.  This one takes an SQL query and creates a workbook with two sheets.  The first sheet contains the result of running the query, and the second sheet contains the query text.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:groovy"&gt;&lt;br /&gt;  def workbook = new HSSFWorkbookBuilder().workbook {&lt;br /&gt;    sheet("Data") {&lt;br /&gt;      db.eachRow(&lt;br /&gt;        sql,&lt;br /&gt;        {meta -&gt; row(meta*.columnName)},           // header row with columns names from ResultSetMetaData&lt;br /&gt;        {rs -&gt; row(rs.toRowResult().values())}     // data row for each ResultSet row&lt;br /&gt;      )&lt;br /&gt;    }&lt;br /&gt;    sheet("SQL") {&lt;br /&gt;      sql.eachLine {line -&gt;&lt;br /&gt;        row([line])&lt;br /&gt;      }&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/9099513356252812992-4995940568510254585?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/4995940568510254585/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=4995940568510254585' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/4995940568510254585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/4995940568510254585'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2010/12/groovy-dslbuilders-poi-spreadsheets.html' title='Groovy DSL/Builders: POI Spreadsheets'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-1891727001141521863</id><published>2010-02-04T13:51:00.000-08:00</published><updated>2010-02-04T14:56:10.687-08:00</updated><title type='text'>Release builds with TeamCity: Selecting the branch</title><content type='html'>We've long had TeamCity doing regular "CI style" checkin builds for our Java/Ant projects.  We recently added nightly builds for extra reports, and for longer-running performance tests.  This was straightforward.&lt;br /&gt;&lt;br /&gt;We finally got TeamCity doing our release builds.  There were a couple of tricky points, which I thought would be worth writing up:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Selecting the branch&lt;/li&gt;&lt;li&gt;Manipulating the repository&lt;/li&gt;&lt;li&gt;Ensuring correct (release) versions of dependencies&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Selecting the branch&lt;/h2&gt;&lt;br /&gt;This was the trickiest thing.  Checkin and nightly builds always run against trunk.  Well, you could set up a checkin build for a long-running development branch too, but that's not difficult.  The release build should be done from the release branch, and that can be different for each release.  Or it can be the same, if you have to do a fix release on an existing one!&lt;br /&gt;&lt;br /&gt;We found a pretty good way to do this with TeamCity, using Build Configuration Templates and Configuration Parameters.&lt;br /&gt;&lt;br /&gt;The idea is that you set up a template that contains all of the settings for the release build, &lt;i&gt;except for&lt;/i&gt; the branch name.  The branch name is specified by a configuration parameter.  Then, the template is instantiated for each branch as desired.  Each time the template is instantiated, the branch name configuration parameter is given for that instance.&lt;br /&gt;&lt;br /&gt;Here are some details, using Subversion VCS and a hypothetical project named "xxx":&lt;br /&gt;&lt;h2&gt;Create the Release Build Template&lt;/h2&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Edit project's existing checkin build config.&lt;/li&gt;&lt;li&gt;Click "Extract Template" to create a new template.&lt;/li&gt;&lt;li&gt;Enter "release-build-template" for Name.&lt;/li&gt;&lt;li&gt;Back in the checkin build config, click "Detach from Template".&lt;/li&gt;&lt;li&gt;Click OK.  &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;We've created a template, with no configurations attached.&lt;br /&gt;&lt;h2&gt;Set up the Release Build Template&lt;/h2&gt;Edit the template. Change settings as given in the following sections.&lt;br /&gt;&lt;h3&gt;Version Control Settings&lt;/h3&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create a new VCS root named xxx-branches.&lt;/li&gt;&lt;li&gt;Specify Subversion as the Type of VCS.&lt;/li&gt;&lt;li&gt;Enter Svn repo URL + "/xxx/branches" as the URL.&lt;/li&gt;&lt;li&gt;Test the Connection.&lt;/li&gt;&lt;li&gt;Save the VCS root.&lt;/li&gt;&lt;li&gt;Attach the template to the xxx-branches VCS root.&lt;/li&gt;&lt;li&gt;Detach the template from the xxx-trunk VCS root.&lt;/li&gt;&lt;li&gt;Add checkout rule for VCS root: "%release.branch%=&gt;.". This tells TeamCity to checkout the specific release branch into the working directory.&lt;/li&gt;&lt;li&gt;Save Version Control Settings.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;Runner Settings&lt;/h3&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Change Target to "release-build", or whatever you want to call your release build target.&lt;/li&gt;&lt;li&gt;Save Runner Settings.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;You must have a target in your build script called "release-build", or whatever you want to call your release build target.  This target must build the release and publish it somewhere.  For example, it could copy it to a staging area on a server.  Or, it could publish it to your enterprise repository.&lt;br /&gt;&lt;br /&gt;The Ant target might look something like this:&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/295194.js?file=release-build-ant-target.xml"&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Build Triggering Settings&lt;/h3&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Delete/disable all triggering (VCS and Dependencies).&lt;/li&gt;&lt;li&gt;Save Build Triggering Settings.&lt;/li&gt;&lt;/ol&gt;We've modified the template so that it will checkout the source from a release branch, with the specific branch given by a configuration parameter ("release.branch"). It will then build and publish the release.&lt;br /&gt;&lt;h2&gt;Create a Release Branch Build Config for Release xx.yy&lt;/h2&gt;This procedure creates a release build config for a particular branch.&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Edit the release-build template's build configuration.&lt;/li&gt;&lt;li&gt;Click Create Build Configuration From Template.&lt;/li&gt;&lt;li&gt;Enter "release-xx.yy" for Name, where "xx.yy" is the name of your release branch.&lt;/li&gt;&lt;li&gt;Enter the name of the branch for the release.branch parameter.  For example, "RB-01.05".&lt;/li&gt;&lt;/ol&gt;We've created a configuration for running a release build on the branch.&lt;br /&gt;&lt;br /&gt;To create a release:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Ensure all changes for the release are checked into trunk.&lt;/li&gt;&lt;li&gt;Create the branch.  For example:&lt;br /&gt;&lt;pre&gt;svn copy $SVN/xxx/trunk $SVN/xxx/branches/RB-xx.yy&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Click Run on the release build configuration in TeamCity.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;You can keep the release build configuration around for a particular branch as long as you like. If you are finished with a branch, you can delete the build configuration.  If you need it again, it's easy to recreate it from the template.&lt;br /&gt;&lt;br /&gt;That's it. I hope to write up some notes on the other points (manipulating the repository and ensuring the correct release versions of dependencies) soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-1891727001141521863?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/1891727001141521863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=1891727001141521863' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/1891727001141521863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/1891727001141521863'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2010/02/release-builds-with-teamcity-selecting.html' title='Release builds with TeamCity: Selecting the branch'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-8497357891856896535</id><published>2009-10-29T00:02:00.000-07:00</published><updated>2009-10-29T00:15:29.487-07:00</updated><title type='text'>Oracle SQL-Developer 2.1</title><content type='html'>I've been trying out &lt;a href="http://www.oracle.com/technology/software/products/sql/index21_EA1.html"&gt;Oracle SQL-Developer 2.1 early adopter&lt;/a&gt; for the last couple of days, because SQL-Developer is the standard query tool at the company where I'm working.&lt;br /&gt;&lt;br /&gt;Personally, I mostly use &lt;a href="http://www.aquafold.com"&gt;Aqua Data Studio&lt;/a&gt;.  Apart from its odious activation scheme, ADS is excellent in every respect.  However, ADS is kind of expensive, while SQL-Developer is free.&lt;br /&gt;&lt;br /&gt;Previous versions of SQL-Developer were promising, but not very polished.  They would paint strange colors on the screen in my installation, and behave poorly with regard to threading and large queries.  The new version seems like a big improvement so far.  Although it doesn't have the same brilliant keyboard support as ADS, it does seem to work pretty solidly, and appears reasonably attractive.  (Much prettier than TOAD at any rate.)&lt;br /&gt;&lt;br /&gt;I did notice one amusing glitch so far:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ir1ImJq30j4/Suk_3wQwgPI/AAAAAAAAACQ/WKsJdND2nkY/s1600-h/Screenshot-Oracle-SQL-Developer-2.1-glitch-cropped.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 521px; height: 106px;" src="http://3.bp.blogspot.com/_ir1ImJq30j4/Suk_3wQwgPI/AAAAAAAAACQ/WKsJdND2nkY/s320/Screenshot-Oracle-SQL-Developer-2.1-glitch-cropped.png" alt="" id="BLOGGER_PHOTO_ID_5397915855460794610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In fact the query only returned about 20 rows.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-8497357891856896535?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/8497357891856896535/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=8497357891856896535' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/8497357891856896535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/8497357891856896535'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2009/10/oracle-sql-developer-21.html' title='Oracle SQL-Developer 2.1'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_ir1ImJq30j4/Suk_3wQwgPI/AAAAAAAAACQ/WKsJdND2nkY/s72-c/Screenshot-Oracle-SQL-Developer-2.1-glitch-cropped.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-6023830755692871456</id><published>2009-09-10T13:32:00.001-07:00</published><updated>2009-09-10T13:41:27.888-07:00</updated><title type='text'>Table size frequencies</title><content type='html'>Here's an interesting query to run on your Oracle database:&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/184799.js"&gt;&lt;/script&gt;&lt;br /&gt;Here's what I get on the main application I've been working on the last several years:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ROW_COUNT               NUM_TABLES&lt;br /&gt;----------------------  ----------&lt;br /&gt;1-10                            36&lt;br /&gt;10-100                          22&lt;br /&gt;100-1000                        22&lt;br /&gt;1000-10000                      22&lt;br /&gt;10000-100000                    22&lt;br /&gt;100000-1000000                  33&lt;br /&gt;1000000-10000000                20&lt;br /&gt;10000000-100000000               2&lt;br /&gt;100000000-1000000000             3&lt;br /&gt;1000000000-10000000000           1&lt;br /&gt;                               183&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/9099513356252812992-6023830755692871456?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/6023830755692871456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=6023830755692871456' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6023830755692871456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6023830755692871456'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2009/09/table-size-frequencies.html' title='Table size frequencies'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-3813837218087957643</id><published>2009-07-23T16:59:00.000-07:00</published><updated>2009-07-23T17:17:35.512-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails dbunit testing'/><title type='text'>Inline data for DbUnit tests in Grails</title><content type='html'>There is a DbUnit plugin for Grails: &lt;a href="http://docs.codehaus.org/display/GRAILS/DBUnit+Plugin"&gt;http://docs.codehaus.org/display/GRAILS/DBUnit+Plugin&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;However, like almost all tutorials for DbUnit, this plugin assumes that the best way to organize your test data is using DbUnit's &lt;tt&gt;FlatXmlDataSet&lt;/tt&gt;.  The example given on the Wiki link above has this &lt;tt&gt;FlatXmlDataSet&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/153739.js"&gt;&lt;/script&gt;&lt;br /&gt;During my 5 years of working with DbUnit, I have come to the conclusion that &lt;tt&gt;FlatXmlDataSet&lt;/tt&gt; is not the best way to organize test data.  Here are my reasons:&lt;br /&gt;&lt;ul compact="true"&gt;&lt;br /&gt;&lt;li&gt;XML is not a good representation for tabular data.  CSV is more readable.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For most of my tests, I would rather see the test data together with the test, rather than separate.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;DbUnit tests often have 3 "phases":&lt;br /&gt;&lt;ol compact="true"&gt;&lt;br /&gt;&lt;li&gt;Prime the database with setup data.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Run some code of the System Under Test.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Verify changed data in the database.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;For most of my DbUnit tests, I would like to see the data used for the first and last parts &lt;span style="font-weight:bold;"&gt;together&lt;/span&gt; with the test itself.&lt;br /&gt;&lt;br /&gt;For this reason, we've gravitated towards inline CSV datasets in most of our DbUnit test cases.  You can achieve this using Groovy in Grails very simply.&lt;br /&gt;&lt;br /&gt;The Wiki example, used in a test with inline datasets, might look like this:&lt;br /&gt;&lt;script src="http://gist.github.com/153740.js"&gt;&lt;/script&gt;&lt;br /&gt;(I'm using pipe characters instead of commas in this case!)&lt;br /&gt;If the same Person data were used in different test methods, we might factor it out into a common method or field.&lt;br /&gt;&lt;br /&gt;What I tend to find is that I use a lot of common &lt;span style="font-weight:bold;"&gt;reference&lt;/span&gt; data in my tests, but &lt;span style="font-weight:bold;"&gt;test-specific detail data&lt;/span&gt;.  I handle the common reference data in a &lt;tt&gt;setUp()&lt;/tt&gt; method, or even in the base &lt;tt&gt;DbUnitTestCase&lt;/tt&gt; class. I put the test-specific detail data in each test.  This prevents duplication of the common stuff, and keeps each test clean and focused. By reading the test method, you can see everything involved in the test.&lt;br /&gt;&lt;br /&gt;We define &lt;tt&gt;cleanInsert()&lt;/tt&gt; and a few other conveniences in &lt;tt&gt;DbUnitTestCase&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;script src="http://gist.github.com/153742.js"&gt;&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-3813837218087957643?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/3813837218087957643/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=3813837218087957643' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/3813837218087957643'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/3813837218087957643'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2009/07/inline-data-for-dbunit-tests-in-grails.html' title='Inline data for DbUnit tests in Grails'/><author><name>John Hurst</name><uri>http://www.blogger.com/profile/12700400206114898455</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://1.bp.blogspot.com/_ir1ImJq30j4/S1tzFmL523I/AAAAAAAAAJ8/NqvadYDBmJI/S220/JohnBigGrin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-6658317577518724372</id><published>2009-04-03T00:13:00.000-07:00</published><updated>2009-04-03T00:18:50.917-07:00</updated><title type='text'>Tomcat Expert Seminar</title><content type='html'>Yesterday I attended SpringSource's &lt;a href="http://www.springsource.com/node/1103"&gt;Tomcat Expert Seminar&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This session rocked!  I have learned a lot of neat ways to enhance our usage and troubleshooting techniques.  The presenter, Filip Hanik, was not only extremely knowledgeable, but he was also entertaining and engaging.  He handled questions from the audience extremely well, being able to give a direct and informative answer to nearly every question.&lt;br /&gt;&lt;br /&gt;The most interesting topics for me were:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Large scale deployments -- showed flexible ways to organise deployment to simplify instance configuration and management.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Troubleshooting -- great tips on Tomcat but even more on Java and server-based systems in general.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;I look forward to attending similar sessions in the future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-6658317577518724372?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/6658317577518724372/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=6658317577518724372' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6658317577518724372'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/6658317577518724372'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2009/04/yesterday-i-attended-springsources.html' title='Tomcat Expert Seminar'/><author><name>John Hurst</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-2795660489526165597</id><published>2008-09-15T11:59:00.000-07:00</published><updated>2008-09-15T12:00:41.735-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><title type='text'>Linux on new Laptop</title><content type='html'>I bought a new laptop a couple of weeks ago, because Zena's old hand-me-down IBM R50p's screen bit the dust.  So she gets my IBM/Lenovo Z61p, and I bought a Sony VGN-Z17GN.  Wow, Sony have long model numbers.&lt;br /&gt;&lt;br /&gt;The thing that sold me on the Sony was the high spec in the small (and elegant) package.  It has 4GB RAM, 320 SATA hard drive, dual core processor and a 1600x900 screen.  But the machine is tiny, and weighs only 1.5kg.  In a small laptop bag and with the power supply, it's lighter than my previous laptop's backpack, empty.&lt;br /&gt;&lt;br /&gt;Like most people, I don't care for Vista.  So I actually applied the XP Pro upgrade option before I even got the machine home.  But the OEM XP Pro disk did not include the machine's specific drivers. So I had to download a ZIP bundle from Sony which was supposed to include all of the drivers.  It did include 20 of them, which meant about 20 times of clicking through the installer, accepting whatever inane licence agreement, and rebooting.  Sony didn't include the ethernet driver in their bundle, so I hunted that down on the Intel web site.  It in turned required MSXML, so I had to find that and install it too.  In the end, a fairly typical experience of installing Windows, mind-numbingly tedious.&lt;br /&gt;&lt;br /&gt;After I finally got XP working, more or less, I popped in a freshly-burned Ubuntu disk and began the Linux install.  Wow, what a difference.  I had actually been a little worried about how hard it would be to get the video annd ethernet working with Linux, since the machine is quite new on the market. (I have the first one sold in New Zealand.)  Well, in a few minutes, with perhaps one or maybe two reboots, I had Ubuntu installed, and everything just works.  Everything I care about, anyway.  I have no idea whether the fingerprint reader is supported in Linux, but I don't care.  To be honest, the wireless network doesn't work yet, but apparently it is supported directly in the next Ubuntu version, due out next month, so I'll just wait for that.&lt;br /&gt;&lt;br /&gt;Once I restored my home directory from a backup, all my desktop and configuration settings were ready to go on the new machine. No registry hacking, no special software for migrating settings. It's funny how with Windows, a lot of the "features" are workarounds for problems that don't exist in other operating systems.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-2795660489526165597?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/2795660489526165597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=2795660489526165597' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/2795660489526165597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/2795660489526165597'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2008/09/linux-on-new-laptop.html' title='Linux on new Laptop'/><author><name>John Hurst</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-7808054430111819095</id><published>2008-07-28T02:06:00.000-07:00</published><updated>2008-07-28T02:11:41.331-07:00</updated><title type='text'>Tests For Your Data 2: When to Use</title><content type='html'>My friend &lt;a href="http://tutansblog.blogspot.com/"&gt;Nigel Charman&lt;/a&gt; commented on &lt;a href="http://skepticalhumorist.blogspot.com/2008/07/tests-for-your-data.html"&gt;Tests For Your Data&lt;/a&gt; with some good questions.  &lt;br /&gt;&lt;br /&gt;First the short answers.&lt;br /&gt;&lt;br /&gt;Where possible the constraints are matched in the application.  So the "double check" idea holds true.  Occasionally this turns up bugs in the application.  But more often it turns up bugs in manual edits of data.  &lt;br /&gt;&lt;br /&gt;With referential constraints, despite what some people seem to think, you need to define them in the database, regardless of whether they are also enforced at the application level.  Exactly the same here.  Double checks are useful, and applications are not infallible.  &lt;br /&gt;&lt;br /&gt;In my experience I'm running these only in production, and yes I regularly get production failures for them.  (That's why I do them.  ;-) &lt;br /&gt;&lt;br /&gt;I probably shouldn't have hijacked the Continuous Integration metaphor for this idea.  Basically this is a data management practice, and doesn't have much to do with the development cycle.  However, it is a practice I am very passionate about.  Just as a good test suite keeps my code healthy and vigorous, I feel that these data checks help me keep my production data healthy and clean.  &lt;br /&gt;&lt;br /&gt;Now for the meaty question: For what types of data is this approach suited?  &lt;br /&gt;&lt;br /&gt;Data from external sources.  For data entered interactively, it's usually best to reject invalid data immediately at point of entry.  For external-sourced data that are loaded in batch, this is not always the best way.  Sometimes data are correlated with data loaded via another batch stream, in a separate transaction.  There isn't always a good place to validate and reject bad data.  In such cases, check views help catch the bad data.  &lt;br /&gt;&lt;br /&gt;Data in 3rd-party systems.  We have applications for which we are not the developer.  So we have little or no control over the application logic or database constraints.  But with check views, at least we can identify data problems and work to fix them.  &lt;br /&gt;&lt;br /&gt;Multi-row conditions.  The classic case here is checking for gaps and overlaps between multiple rows containing date ranges.  In my work at Red Energy, we have many tables with bitemporal data (two time dimenions).  It's quite hard to visualize these data simply by looking at the tabular form, so it's useful to have check queries to verify that the "shape" of the data is valid.  &lt;br /&gt;&lt;br /&gt;Aggregations.  We have cases where different tables aggregate the same basic information by completely different keys.  After having the IT manager complain to me twice about two reports (driven from two tables) not balancing, I added a check query to verify that the aggregations balance.  The next time the problem happened, I was the first to know.  &lt;br /&gt;&lt;br /&gt;Parent-to-child relations.  Foreign keys can enforce that every child has a parent.  Sometimes you want to enforce that every parent has at least one child.  &lt;br /&gt;&lt;br /&gt;"Future" conditions.  Sometimes you have "static" data that cover a range of time, such as calendar data or pricing.  You enter data for the next three years, and then start running your system.  A carefully-written check view can remind you when it's time to update for the next three years.  &lt;br /&gt;&lt;br /&gt;Believe it or not, we also have some check views on the check views.  There is one that warns if any view (or PL/SQL package) in the database contains errors.  There is another that verifies that every check view has a comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-7808054430111819095?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/7808054430111819095/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=7808054430111819095' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7808054430111819095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/7808054430111819095'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2008/07/tests-for-your-data-2-when-to-use.html' title='Tests For Your Data 2: When to Use'/><author><name>John Hurst</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-5680771847651001971</id><published>2008-07-25T17:28:00.000-07:00</published><updated>2008-07-25T17:56:32.120-07:00</updated><title type='text'>Tests For Your Data</title><content type='html'>These days automated tests for your code are standard practice in any professional IT shop.  There  are a variety of automated testing tools in use, from JUnit and TestNG that can run unit tests and integration tests, through Behavior Driven Design, FITness, and others.&lt;br /&gt;&lt;br /&gt;I propose we should have tests for our data too.&lt;br /&gt;&lt;br /&gt;Code makes a lot of assumptions about the data it works on.  Many of these assumptions can be enforced using constraints in the database itself:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A PRIMARY KEY constraint defines a unique key for the table.&lt;/li&gt;&lt;li&gt;A UNIQUE constraint identifies an alternative candidate key, which also must be unique.&lt;/li&gt;&lt;li&gt;A FOREIGN KEY constraint defines a relationship to a parent table, and is used to enforce referential integrity.&lt;/li&gt;&lt;li&gt;A CHECK constraint can be used to check arbitrary conditions on the values in a row.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;In addition to these constraints, you can also use triggers to check more complex conditions, perhaps involving multiple rows.&lt;br /&gt;&lt;br /&gt;Despite all of these, there are many cases where constraints are too awkward or inefficient.  Particularly when conditions span multiple rows, database constraints and triggers are not very good for enforcing them.&lt;br /&gt;&lt;br /&gt;Here's an example.  Suppose we have a table &lt;span style="font-family:monospace;"&gt;billing_period&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;BILLING_PERIOD ACTUAL_START ACTUAL_END&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;200825         2008-06-04   2008-06-10&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;200826         2008-06-11   2008-06-17&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;200827         2008-06-18   2008-06-24&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;etc&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-family:monospace;"&gt;billing_period&lt;/span&gt; table is supposed to contain weekly billing periods, along with the dates belonging to them.  Each billing period is supposed to be exactly seven days long.  There should be no overlaps or gaps, either.  How would we enforce these conditions using constraints or triggers?&lt;br /&gt;&lt;br /&gt;You have probably written hundreds of queries to test conditions like this about the database.  How about making those queries into a test suite for your production data?&lt;br /&gt;&lt;br /&gt;Start with a view like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;CREATE VIEW chk_billing_period_7_days_long AS&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;FROM   billing_period&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;WHERE  actual_start - actual_end &lt;&gt; 6&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This view returns a row for any billing period which is not seven days (actually six days) from its start to its end.&lt;br /&gt;&lt;br /&gt;Here's another one, to check for overlaps:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;CREATE VIEW chk_billing_period_overlaps AS&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;FROM   billing_period bp1&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;WHERE  EXISTS (&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  FROM   billing_period bp2&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  WHERE  bp2.actual_start BETWEEN bp1.actual_start AND bp1.actual_end&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;     OR  bp2.actual_end BETWEEN bp1.actual_start AND bp1.actual_end&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Finally, to check for gaps:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;CREATE VIEW chk_billing_period_gaps AS&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;FROM   billing_period bp1&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;WHERE  EXISTS (&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  FROM   billing_period bp2&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  WHERE  bp2.actual_start &gt; bp2.actual_end&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;AND  NOT EXISTS (&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  SELECT *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  FROM   billing_period bp3&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;  WHERE  bp3.actual_start = bp2.actual_end + 1&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;None of these views should ever return any results.  If any of them does, we have a data integrity problem.  The problem may cause our application's views or code to fail, because of violated assumptions.&lt;br /&gt;&lt;br /&gt;Because we named all the views according to a convention (they all start with &lt;span style="font-family:monospace;"&gt;chk_&lt;/span&gt;) we can easily write a program that iterates over these views and tests them all.  This program could be scheduled to run every day.  It could email us results from any check view that returns data.&lt;br /&gt;&lt;br /&gt;If our database supports it, we can add descriptive comments to the views, such as:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;COMMENT ON TABLE chk_billing_period_overlaps IS&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;'Overlap between two or more billing periods'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This comment would make a nice subject line for an email message.&lt;br /&gt;&lt;br /&gt;It's easy to add more check views: just define a view beginning with&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;chk_&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I've gotten into the habit, when I'm designing application code or view logic, to think about the assumptions.  If an assumption can be reasonably enforced with a database constraint, I will add a constraint.  Otherwise, I write a check view for the assumption and add the check view to the database.  Also, just as when I find a bug in my application code, I write a unit test to expose it, so I also write check views to expose data bugs I find in the database.&lt;br /&gt;&lt;br /&gt;A scheduled job runs every check view every day, and emails data problems to the team.  The views comprise a test suite for our data.  The scheduled job gives us continuous integration of sorts.  We are alerted to problems virtually as soon as they happen.  (Well, the next day.)&lt;br /&gt;&lt;br /&gt;We've been running this system at Red Energy for a couple of years now.  On one application, featuring about 150 tables, we have a little over 100 check views.  I think we should have a lot more.  Even so, this system has allowed us to maintain a very high level of data integrity.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-5680771847651001971?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/5680771847651001971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=5680771847651001971' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/5680771847651001971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/5680771847651001971'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2008/07/tests-for-your-data.html' title='Tests For Your Data'/><author><name>John Hurst</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9099513356252812992.post-1226534482771058771</id><published>2008-06-03T03:34:00.000-07:00</published><updated>2008-06-03T04:00:38.872-07:00</updated><title type='text'>JAOO Sydney 2008</title><content type='html'>The message from JAOO 2008 is: Software development is in crisis. To get over it, we're all going to be programming in functional languages and deploying in the Cloud.  Maybe fundamentalist functional languages.&lt;br /&gt;&lt;br /&gt;Dave Thomas is thought-provoking.  But he can't make up his mind whether shiny things are the new one-true-path or the spawn of the devil.  He's a great devil's advocate.  He'll tell you that he'd rather maintain a legacy COBOL application than a legacy Java app. Then he'll tell you that the "new SQL" is to be found in the query features in LINQ.  Then he'll say OO is the best technology for product development, but a terrible thing for enterprise software. He thinks the computer scientists have been running the asylum.&lt;br /&gt;&lt;br /&gt;Martin Fowler is an opinionated grump but I like him more and more. It's probably just because he agrees with many of the things I believe. XSLT was a bad technology for writing user interfaces (or whole applications).  OO is still a good way to write complex domain logic.  You cannot write serious applications with Doodleware. It's better to evolve a framework out of a working application&lt;br /&gt;than to start with the framework before writing the application.&lt;br /&gt;&lt;br /&gt;There was a video clip of him claiming that it isn't "significantly" harder to design a DSL than to design an API.  I have tended to disagree with this, but I am probably confusing DSLs with full-blown languages.  If he's talking about the special-purpose "DSL"s such as Grails' GORM DSL, or Ruby-on-Rails, then of course he's right.  These are DSLs.  They're also glorified APIs in a way.  Don't get me wrong: they're great.  I'm looking forward to his new book on the subject, though I'm sad to hear that PEAA2 is on the back-burner.&lt;br /&gt;&lt;br /&gt;Fowler presented Kent Beck's four features of well-designed software:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Passes the tests&lt;/li&gt;&lt;li&gt;Code shows the intent&lt;/li&gt;&lt;li&gt;No duplication&lt;/li&gt;&lt;li&gt;"Less stuff"&lt;/li&gt;&lt;/ol&gt;Fair enough. I am interested in whether people feel that tests should be factored the same as production code. These days I am inclined to "show everything" in the test, to make each test easy to understand in isolation. Erik Dörnenburg seems to agree with this. Robert Martin says to factor the tests just the same as production code.&lt;br /&gt;&lt;br /&gt;Gregor Hohpe is always fun to watch.  He really ought to have a TV show or something.  He told us about the "new" ACID properties, for distributed systems:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Asynchronous&lt;/li&gt;&lt;li&gt;Concurrent&lt;/li&gt;&lt;li&gt;Internet-Scale&lt;/li&gt;&lt;li&gt;Distributed&lt;/li&gt;&lt;/ul&gt;And he hoped he got them 50% right...&lt;br /&gt;&lt;br /&gt;SOA seems to be "out", according to many on the Enterprise Systems Panel.  I guess that means it isn't shiny any more and the only people pushing it now are the out-of-date vendors who haven't caught on yet. I hope they catch on soon.  Like most shiny IT technologies, SOA has probably been very useful to some organisations, been a complete failure for others, and is not particularly significant for the rest of us.&lt;br /&gt;&lt;br /&gt;I am very skeptical about the "new SQL" of LINQ.  It is very cool and neato and so forth, and a small part of me is kind of envious of the .NET camp for this nice stuff coming out of Microsoft.  But application data access technologies come and go nearly every year, and SQL is still with us.  None of these conference speakers or other "leading edge" developers at the conference seem to think that ad-hoc queries are important, both for users and developers.  I make dozens of them every day.  I wonder where these people work.&lt;br /&gt;&lt;br /&gt;I talked to a rather drunk ThoughWorker (at the party) who suggested my enterprise app of 100-odd relational tables would be better factored as two or three smaller apps using hash maps for persistence.  I think he was serious.  He also claimed that the performance of the Cloud was so great that it could substitute one kind of index for another without you knowing or caring.  Maybe so. But why bother with an index at all then? You only use indexes when you care about performance, and even then you need to understand the performance characteristics of the index you are using, if you want to get any&lt;br /&gt;benefit from it.&lt;br /&gt;&lt;br /&gt;The second day we had Robert Martin.  He's also very entertaining. I asked him if his (new) book was as much fun as his talk and he said yes, so I think I will buy it.  I didn't expect a talk to change my thinking about functions and clean code, but he may have.  For example, he thinks functions should be between one and five lines long.  I have a lot that are a bit longer than that, and now I think maybe they are not as good as I thought.  He showed that by having really small functions, named very carefully for what they do, the code is really self-documenting.  Much more so than if you have, say, 20-30 line functions.&lt;br /&gt;&lt;br /&gt;He has these rules for Good Functions:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Small&lt;/li&gt;&lt;li&gt;Do one thing&lt;/li&gt;&lt;li&gt;Use descriptive names&lt;/li&gt;&lt;li&gt;No more than three arguments&lt;/li&gt;&lt;li&gt;No side-effects&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I met Rod Johnson (again), and expressed commiseration that he has to explain the licensing for SpringSource Application Platform so much, over and over again. Even many of the Spring people don't understand it. I have been told by two different Spring guys that companies won't be able to run SSAP without paying for it. I guess I'd better go figure out whom I'm supposed to be paying for this copy of Linux on my laptop then, since it's the same license (GPL). (Of course I understand Spring has a commercial license as well as GPL. It's much the same as MySQL in that respect.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9099513356252812992-1226534482771058771?l=skepticalhumorist.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://skepticalhumorist.blogspot.com/feeds/1226534482771058771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9099513356252812992&amp;postID=1226534482771058771' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/1226534482771058771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9099513356252812992/posts/default/1226534482771058771'/><link rel='alternate' type='text/html' href='http://skepticalhumorist.blogspot.com/2008/06/jaoo-sydney-2008.html' title='JAOO Sydney 2008'/><author><name>John Hurst</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
