Friday, June 12, 2015

Expression-oriented programming in Groovy: transpose() is zip()

The term "expression-oriented programming", as mentioned in these blog posts, resonated with me:

Groovy is by no means a purely functional language, but it does include a lot of the basics. Moreover, it includes a few other goodies that make it really nice for expression-oriented programming.

Here's a set of posts about some of these constructs.

One oddity about Groovy's support for functional programming is that Groovy chooses unusual names for some common functions from functional programming.

I believe this is because Groovy chose names from its object-oriented heritage (SmallTalk), rather than from the functional-programming canon.

For example:

  • map() is called collect() in Groovy
  • fold() or reduce() are inject() in Groovy
  • filter() is findAll() in Groovy

One of the most obscurely-named such methods in Groovy is transpose(), and as a result it's easily overlooked.

A very useful function in functional programming languages such as Haskell and Scala is zip(), which is used to combine corresponding elements from more than one collection.

Suppose we have two lists, a and b, containing numbers. And we want to find the maximum from each pair of corresponding numbers from these lists.

Groovy provides a lot of nice methods for working with a single list. But faced with two lists to be traversed together, many would revert to old Java-style code:

def a = [5, 10, 15, 20, 25]
def b = [20, 16, 12, 8, 4]

def r1 = []
for (i in 0..<a.size()) {
  r1[i] = Math.max(a[i], b[i])
}

assert r1 == [20, 16, 15, 20, 25]

We could try to use one of Groovy's iteration methods, eachWithIndex(), but the result is hardly better:

def r2 = []
a.eachWithIndex {v, i ->
  r2[i] = Math.max(v, b[i])
}

assert r2 == [20, 16, 15, 20, 25]

The answer is to use transpose():

def r3 = [a, b].transpose().collect {v, w -> Math.max(v, w)}

assert r3 == [20, 16, 15, 20, 25]

It's a little tricky until you get the hang of it: transpose is called on a list of lists, and it returns a new list of lists. Each list in the new list contains all of the elements at the same position in the original lists.

Actually, for built-in methods like max() that Groovy defines on collections, we can use the spread operator:

def r4 = [a, b].transpose()*.max()

assert r4 == [20, 16, 15, 20, 25]

Suppose instead of taking the max of two items we wanted the sum:

def r5 = [a, b].transpose()*.sum()

assert r5 == [25, 26, 27, 28, 29]

transpose() is also nice because it generalizes nicely beyond the case of just two lists.

def c = [3, 6, 9, 12, 15]

def r6 = [a, b, c].transpose()*.sum()

assert r6 == [28, 32, 36, 40, 44]

Of course, our lists do not have to be of the same type.

Here's an example where one list contains strings and another lengths, and we want to pad each string to the corresponding length:

def strings = ["one", "two", "three", "four", "five", "six"]
def lens = [1, 2, 3, 4, 5, 6]
def r7 = [strings, lens].transpose().collect {item, len -> item.padRight(len)}

assert r7 == ["one", "two", "three", "four", "five ", "six   "]

Java 8 added lambdas and the Streams API, which permit many functional idioms. A zip() method was originally included in the Java 8 SDK previews, but unfortunately was removed before release. Never mind, we have it in Groovy!

So despite its unconventional name, keep transpose() in mind when working with lists. And if you have any interesting usages yourself, post them (or links) in the comments!

Monday, June 1, 2015

Gradle version selector incompatability

We had some interesting issues with Gradle this week.

We build a number of internal projects with Gradle, and some of these projects have interdependencies. The dependency graph of our internal projects extends to several levels.

To illustrate, this diagram shows three projects, with "server" depending on "common", and "client" depending on "server".

For internal dependencies, we use Gradle's "changing module" version selectors: "latest.integration" and "latest.release".

The problem occurred because Gradle 2.3 changed the way these version selectors are written to a published pom.xml dependency section.

Prior versions wrote the version selectors "as-is", e.g. "latest.integration". I believe that this convention originated with Ivy. But this is not a valid Maven version. So Gradle 2.3 changed to write a valid Maven version such as "LATEST" or "RELEASE".

A great post explaining the options available with Maven, and some of the pros and cons, is this on one StackOverflow.

Generally speaking, it's best to use specific version numbers in dependencies for released artifacts, for repeatable builds. But it's also desirable to have changing or dynamic dependencies for snapshot or integration builds, for continuous integration and testing.

The problem is that older versions of Gradle don't understand these "new" values ("LATEST" and "RELEASE").

Let's show this using the sample projects above. For the purpose of this illustration, we'll use a common init.gradle shared by each project, with contents:

def homeDir = System.getProperty("user.home")
def repoUrl = "file:///$homeDir/tmp/repo"

allprojects {
  apply plugin: "java"
  apply plugin: "maven"

  group = "example"

  uploadArchives {
    repositories {
      mavenDeployer {
        repository(url: repoUrl)
      }
    }
  }

  repositories {
    maven {
      url repoUrl
    }
  }
}

We'll use this init.gradle for every build in these examples, using this alias:

alias mygradle="./gradlew -I../init.gradle"

For the "common" project, we'll have these files:

➜ common git:(master) ✗ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    └── main
        └── java
            └── example
                └── Common.java

In build.gradle we have:

wrapper {
  gradleVersion = "2.2.1"
}

version = "01.00"

We can build "common" like this:

➜  common git:(master) ✗ mygradle uploadArchives
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:uploadArchives
Uploading: example/common/01.00/common-01.00.jar to repository remote at file:////Users/jhurst/tmp/repo
Transferring 1K from remote
Uploaded 1K

BUILD SUCCESSFUL

The generated pom.xml is not very interesting:

<project 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" 
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>common</artifactId>
  <version>01.00</version>
</project>

Now we look at "server". The files are:

 
➜  server git:(master) ✗ tree 
. 
├── build.gradle 
├── gradle 
│   └── wrapper 
│       ├── gradle-wrapper.jar 
│       └── gradle-wrapper.properties 
├── gradlew 
├── gradlew.bat 
└── src 
    └── main 
        └── java 
            └── example 
                └── Server.java 

The server project uses Gradle 2.4, and declares a dependency on "common" in its build.gradle:

 
wrapper { 
  gradleVersion = "2.4" 
} 
 
dependencies { 
  compile "example:common:latest.integration" 
} 
 
version = "01.00" 

We build "server":

 
➜  server git:(master) ✗ mygradle uploadArchives 
:compileJava UP-TO-DATE 
:processResources UP-TO-DATE 
:classes UP-TO-DATE 
:jar UP-TO-DATE 
:uploadArchives 
 
BUILD SUCCESSFUL 

Now we have a dependency in the generated pom.xml:

 
<project 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" 
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>server</artifactId>
  <version>01.00</version>
  <dependencies>
    <dependency>
      <groupId>example</groupId>
      <artifactId>common</artifactId>
      <version>LATEST</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

This dependency is specified using the new, Maven-compatible version selector.

Now we go to "client":

 
➜  client git:(master) ✗ tree 
. 
├── build.gradle 
├── gradle 
│   └── wrapper 
│       ├── gradle-wrapper.jar 
│       └── gradle-wrapper.properties 
├── gradlew 
├── gradlew.bat 
└── src 
    └── main 
        └── java 
            └── example 
                └── Client.java 

The client project uses Gradle 2.2.1, and declares a dependency on "server" in its build.gradle:

 
wrapper { 
  gradleVersion = "2.2.1" 
} 
 
dependencies { 
  compile "example:server:latest.integration" 
} 
 
version = "01.00" 

We attempt to build "client":

 
➜  client git:(master) ✗ mygradle uploadArchives 
:compileJava 
 
FAILURE: Build failed with an exception. 
 
* What went wrong: 
Could not resolve all dependencies for configuration ':compile'. 
> Could not find example:common:LATEST. 
  Searched in the following locations: 
      file:/Users/jhurst/tmp/repo/example/common/LATEST/common-LATEST.pom 
      file:/Users/jhurst/tmp/repo/example/common/LATEST/common-LATEST.jar 
  Required by: 
      example:client:01.00 > example:server:01.00 
 
* Try: 
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. 
 
BUILD FAILED 

The problem is that Gradle 2.2.1 does not correctly interpret "LATEST" as a changing module version selector.

It's easily fixed - we simply upgrade "client" to Gradle 2.3 or later.

You might think this is a really trivial problem. It is, once it's clear what is going on. We found it a bit confusing at first because we didn't know where the "LATEST" string in the dependency error message was coming from.

There is a further difficulty caused by this change if you use Groovy's Grapes feature in Groovy scripts to fetch dependencies. Grapes uses Ivy to resolve dependencies, and it does not understand this LATEST/RELEASE syntax in POM files either.

To show this, let's use a ivysettings.xml file as follows:

 
<ivysettings>
  <resolvers>
    <ibiblio 
      name="downloadGrapes" 
      m2compatible="true" 
      root="file:///Users/jhurst/tmp/repo"/>
  </resolvers>
  <settings defaultResolver="downloadGrapes"/>
</ivysettings>

We configure Groovy to use this using the grape.config system property:

 
export JAVA_OPTS="-Dgrape.config=$PWD/ivysettings.xml" 

Let's have a Groovy script that has a dependency on the "common" module:

 
@Grab("example:common:01.00") 
import example.Common 
 
println Common.simpleName 

When we run this, it fetches the dependency and runs successfully:

 
➜  groovy git:(master) ✗ groovy ./grabcommon.groovy 
Common 

Let's try another Groovy script that has a dependency on "server" instead:

 
@Grab("example:server:01.00") 
import example.Server 
 
println Server.simpleName 

When we run this, we get a similar failure to that with Gradle earlier:

 
➜  groovy git:(master) ✗ groovy ./grabserver.groovy 
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: 
General error during conversion: Error grabbing Grapes -- 
  [unresolved dependency: example#common;LATEST: not found] 
 
java.lang.RuntimeException: Error grabbing Grapes -- 
  [unresolved dependency: example#common;LATEST: not found] 
... 

This one is not so easy to solve.

Gradle originally used Ivy's dependency resolution code, but then switched to using its own code. Groovy's Grapes feature still uses Ivy.

We need either to improve Ivy to support this syntax in POM files, or else perhaps it would be better to get Groovy Grapes to use Gradle's dependency resolution code instead of Ivy. But given that Grapes is configured using an ivysettings.xml, and Gradle does not provide any analogous way to tell a Groovy script where to look for dependencies, it is not obvious how we would switch Groovy to use Gradle's code. Besides, Groovy needs to continue to support ivysettings.xml and all Ivy features, for backwards compatibility.

A colleague of mine pointed out that one solution is to use the long form of @Grab, with transitive = false, like this:

@Grab(group = "example", module = "server", version = "01.00", transitive = false)
@Grab("example:common:01.00")
import example.Common
import example.Server

println Server.simpleName
println Common.simpleName

This works, but it excludes all of the transitive dependencies. If you have a lot of third party dependencies and need this exclusion only for your local modules, it's not that great.

Saturday, January 25, 2014

Looking at Elixir

The Pragmatic Programmers had a big impact on me with the PickAxe book introducing Ruby, among many other things.

So when Dave Thomas gets excited about a new language, I get excited too. His new book "Programming Elixir", is in a quite different style from the PickAxe, much briefer, just tries to give an overview and a taste. This too is quite appealing. In this day of Google and Stack Overflow, we don't need hardcopy reference materials. (On the other hand, when I look at how little work I can get done these days without consulting Google, I have to admit those printed references must have actually been quite useful!)

Anyway, I've been working through this new book and Elixir a little, while on vacation. I'm no expert on Elixir, Erlang, Haskell, functional programming, or pretty much anything. Nevertheless I thought I might put a few of my impressions. Besides, this blog hasn't had enough activity, and with New Year's Resolutions and everything...

Here are the things that struck me.

Aesthetic.

It looks and feels a bit like Haskell, but ugly.

Perhaps not a fair comparison, because I feel that Haskell is the most beautiful programming language I have seen.

Length of a list, in Haskell:

len [] = 0
len (h:t) = 1 + len t

In Elixir:

def len([]), do: 0
def len([_|t]), do: 1 + length(t)

Extra 'def' keyword required, and what's with that ", do:=" gunk? Isn't Haskell's definition as clear and minimal as conceivably possible?

Another example: 'map'. In Haskell:

map _ [] = []
map f (h:t) = f h : map f t

In Elixir:

def map(_, []), do: []
def map(f, [h|t]), do: [f.(h) | map(f, t)]

It's the same, just with extra punctuation.

Laziness.

It seems like Elixir supports laziness only when explicitly using the Stream module. Functions cannot be defined recursively using list constructors to return infinite lists.

In Haskell:

repeat x = x : repeat x

In Elixir, maybe:

def repeat(v), do: Stream.iterate(v, fn _ -> v end)

It's an awkward definition because of the need to provide an inline function returning the constant value. But worse, the return type is not the same as a list.

For example, in Haskell we can define take:

take n (h:t) = if n==0 then [] else h : take (n-1) t

And then use it with 'repeat':

take 3 $ repeat 10

But with Elixir, the list definition of 'take':

def take(0, _), do: []
def take(n, [h|t]), do: [h|take(n-1, t)]

Does not work with streams:

iex(14)> Haskell.take(3, Haskell.repeat(1))
** (FunctionClauseError) no function clause matching in Haskell.take/2
  haskelllib.exs:55: Haskell.take(3, #Function<3 .80570171="" in="" stream.iterate="">)
  erl_eval.erl:569: :erl_eval.do_apply/6
  src/elixir.erl:138: :elixir.eval_forms/3

Although the builtin 'Enum.take' does work:

Enum.take(Haskell.repeat(1), 3)

But this returns a list, not a stream.

OK, for 'take' it may often be fine to create a full list rather than a lazy list. But how about 'map'?

The list version of 'map' can be defined:

def map(_, []), do: []
def map(f, [h|t]), do: [f.(h) | map(f, t)]

But this version cannot be used with streams, and we really do want lazy 'map' with lazy streams.

To be fair, this is probably a conscious decision in Elixir to separate lists and streams, just as it is in Scala. I guess that there is a fair tradeoff to be made about laziness, e.g. see this blog.

Function parameter ordering.

You might have noticed that in the above definitions of 'take', the standard Elixir version defines the arguments in a different order from the Haskell version.

The reason is that in Elixir, for the pipe operator '|>' to work, a function must take a list as its first parameter.

[1,2,3,4,5] |> Enum.take(2)

But it seems to me that it's better to give lists as the last argument, as in Haskell, for the purpose of currying:

f = filter even
f [1,2,3,4,5,6,7]

It is perhaps telling that in "Programming Elixir", the word "curry" does not occur. (Nor does "partial" in the sense of partially applied function.)

Having said that, I have to admit that the pipe operator looks appealing, largely due to its nice left-to-right reading. The example in the Prag book is:

filing = DB.find_customers
|> Orders.for_customers
|> sales_tax(2013)
|> prepare_filing

I suppose that in Haskell this would look something like this:

filing = prepare_filing $ sales_tax 2013 $ Orders_for_customers $ DB_find_customers

The right-to-left reading of Haskell is one of the least-appealing aspects of it for me, sometimes.

Summary

For me, for learning Functional Programming, I think I'll stick with Haskell. It seems like a nicer language and there are some good resources.

On the other hand, if you are on the Erlang platform, or want to be, maybe because of the nice concurrency or high availability features, then you might find Elixir an attractive alternative to (or complement to) the Erlang language.

Monday, April 29, 2013

Bad math in BBC

In the BBC Magazine story Amanda Knox and bad math in court, there is an explanation of probability relating to evidence in a legal trial.

The article quotes mathematician Coralie Colmez, co-author of "Math on Trial: How numbers get used and abused in the courtroom". Well, I hope they misquoted her. The math presented in the article does not appear to add up.

The example given is:

"You do a first test and obtain nine heads and one tail... The probability that the coin is fair given this outcome is about 8%, [and the probability] that it is biased, about 92%. Pretty convincing, but not enough to convict your coin of being biased beyond a reasonable doubt," Colmez says.

Technically in this situation we talk about the probability that a fair coin will give a particular outcome. We can calculate the chance of a fair coin giving nine or more heads (or zero or one tails), using the binomial distribution.

In Excel we can use =BINOMDIST(1,10,0.5,TRUE). In the statistical program R the formula is pbinom(1,10,0.5).

The answer is 0.01074219, or about 1%. I don't know where the 8% quoted in the article comes from.

The more subtle problem is that the chance of a fair coin producing a result is not the same as the chance of the coin being fair given a result. This is what the article seems to imply, and is a common mistake.

The theory we need for this kind of statement is Bayes' Theorem, which deals with conditional probabilities:

P(A|B) = P(B|A) P(A) / P(B)

Where P(A|B) means "Probability of A, given B".

To know the probability of this particular coin being fair after an experiment we'd need to know more information about the whole population of coins.

Monday, August 27, 2012

Ant Profiling

In my new job at Assurity Consulting I've been looking at some large builds. Some that take 6 hours or more to run in the Continuous Integration Server. There's a fairly random mix of Ant, Maven and good old shell. The last few days I spent some time diving into the details of a fairly large Ant build.

I've been using a combination of three techniques to analyze where the time is being spent in this build:

  1. Analysis by inspection.
  2. Analysis of "ant -d" output.
  3. Ant Profiler.

There are a number of files and many tasks and targets in this build, and I haven't been able to walk through the entire thing yet. But by browsing through "interesting" parts, I have been able to find out some significant things about the build.

For example, the build makes extensive use of <antcall>. Sometimes people use <antcall> when they don't understand the correct usage of "depends" on Ant targets. In this build, many of the <antcall> usages would be more appropriately done with <macrodef>. <antcall> is inefficient to use on targets within the same build file, because it reparses and copies properties. Please learn to use <macrodef> with Ant. It is your friend.

To extract some data about the Ant build as a whole, I ran it with "ant -d". On this particular build, it generates 600,000 lines of output. Again, a lot of detail.

With some shell piping we can see potential areas of duplication, or work being repeated:

ant -d >ant.debug.out 
sort ant.debug.out | uniq -c | sort -nr | head

For example, we can see whether certain Java source files are compiled more than once during the build:

egrep "^ *\\[javac\\]" ant.debug.out | sort | uniq -c | sort -nr | less

In this case I found that some classes are compiled twice, three, four or five times. Unfortunately, not enough classes to make a large difference to the build time, but still ...

Finally, I found a great Ant profiler called antro. This profiler is really neat. It is completely non-invasive. It uses the Ant listeners feature, and can be run like this to analyze a build:

ant -listener ru.jkff.antro.ProfileListener -lib ~/antro/antro.jar build.xml

This generates a JSON file, which can then be loaded into the profiler's GUI. The GUI is run simply with:

java -jar antro.jar

What could be simpler?

It provides a tree view of the build times, where you can drill down into any node and explore the detail. Using this tool it's easy to get a bird's eye view of the build time breakdown, as well as drill into areas of interest. For example, I was able to assess the overhead of <antcall> by looking at some of those nodes. Unfortunately, there seems to be no way to see the total number or overhead for a single task type such as <antcall> across the whole build. I was able to find the total number of <antcall>s (not the times though) from the "ant -d" output. This allowed me to estimate the total time overhead of <antcall> for this build.

Tuesday, August 21, 2012

Git and SVN on OSX 10.8 - is this the best way?

Don't get me started on Apple. I bought my first Mac a few weeks ago, and I'm still trying to figure out why. They are pretty, but they are also pretty frustrating. Other than the hardware compatability problems with Linux, which may actually have driven me crazy, Ubuntu is a much better-designed system.

For one thing, Apple seems determined to gradually eliminate standard UNIX software from this platform. Which kind of defeats the whole point I bought the thing -- I wanted a reliable UNIX-based system. I realize that Apple could not give a toss about UNIX geeks, having a very lucrative mass market of schmucks to serve, but still that doesn't console me much. One day I will post a long list of frustrations I have found since starting with OSX.

Today I just want to post how I got something working, in case I need it later. In Ubuntu, if I want to use Git and SVN, I have to type something like this:
sudo apt-get install subversion
sudo apt-get install git-core
Something like that. Also maybe
sudo apt-get install git-svn
I don't remember. Actually, I don't have to remember, or type it, because bash on Ubuntu has completions. There's nothing to it.

In OSX, there is no standard package management system. (Let alone bash completions.) There are a variety of independent efforts (Mac Ports, Homebrew, Fink), and they all hate each other. You pick one of those and hope for the best.

Except Apple keeps trying to break them.

The worst thing I have done with OSX so far is upgrading to 10.8. This was a stupid mistake, because 10.8 does not contain any useful features, but instead breaks things that I had come to depend on. I did it because I thought it was supposed to fix fullscreen mode with multiple monitors, which was already broken. It is still broken.

Prior to 10.8 I was able to get Git and SVN working using Homebrew. After 10.8 I get compilation errors.

So instead I installed SVN using the brain-dead Windows method of click-till-you-die installation from a binary DMG file from git-scm.com. And I installed Git similarly by clicking and clicking and clicking with a binary PKG file from WANDisco. This was fine, for independent Git and SVN operation. But Git-SVN did not work. I got

Can't locate SVN/Core.pm in @INC

because the Git Perl code is in one place, and the SVN Perl code is somewhere completely different.

Only the wise and benevolent creator can tell why these things need to talk to each other using Perl. It probably comes from Git being implemented using every UNIX hack tool under the sun.

I got them to work, apparently, by doing this:
sudo ln -s /opt/subversion/lib/svn-perl/auto /usr/local/git/lib/perl5/site_perl/auto
sudo ln -s /opt/subversion/lib/svn-perl/SVN /usr/local/git/lib/perl5/site_perl/SVN
What a hack! And only works because there was no auto/ directory there already! This is gonna break, I know it! Hopefully the Homebrew version will be working again by then.

Give me Ubuntu for this sort of thing any day. Shit, even give me an RPM-based system, I'll be happy with that.

Tuesday, May 29, 2012

Groovy AST transform gotcha

I'm a big fan of Groovy's AST transforms. Lately I've been using @Lazy in a lot of my code, because I love a declarative or functional style of programming, and @Lazy lets me do that really efficiently and concisely with Groovy. I hope that my colleagues agree!

We got caught by an interesting trap using a couple of other transforms recently: @EqualsAndHashCode and @Immutable. We like to use @EqualsAndHashCode to generate equals() and hashCode() for entity classes in our domain model. We use the 'includes' attribute to include only the primary key properties in the equals() comparison, like this:
@EqualsAndHashCode(includes = "id")
class Foo {
  String id
  String description
}
For this class, and its corresponding database table, the 'id' property is the key. Equality is not done by Java object equality, or by full value equality, but by comparing the entities' primary keys:
assert new Foo(id: "1", description: "cat") == 
       new Foo(id: "1", description: "dog")
This has important effects when storing these kinds of objects in collections.

Be careful mixing AST transform annotations though! We added @Immutable to some of our domain classes, like this:
@Immutable
@EqualsAndHashCode(includes = "id")
class Foo {
  String id
  String description
}
and got a surprising result:
assert new Foo(id: "1", description: "cat") != 
       new Foo(id: "1", description: "dog")
The reason is the order of the annotations. If we reverse them, it works correctly:
@EqualsAndHashCode(includes = "id")
@Immutable
class Foo {
  String id
  String description
}

assert new Foo(id: "1", description: "cat") == 
       new Foo(id: "1", description: "dog")
So what happens? The doc for @Immutable says:
The @Immutable annotation instructs the compiler to execute an AST transformation which adds the necessary getters, constructors, equals, hashCode and other helper methods that are typically written when creating immutable classes with the defined properties.
Later on, in more detail:
Default equals, hashCode and toString methods are provided based on the property values. Though not normally required, you may write your own implementations of these methods. For equals and hashCode, if you do write your own method, it is up to you to obey the general contract for equals methods and supply a corresponding matching hashCode method. [...]
So, I'm guessing that if we put @Immutable first, then @EqualsAndHashCode hasn't yet had a chance to do its magic, and @Immutable adds its default equals() etc, not the ones we want. But if we put @EqualsAndHashCode first, then its equals() etc methods are there for @Immutable to see, and we get the behavior we want.

Thus, problem solved, for now. But it does make one wonder a little about the interactions of all of these transforms and other annotations. We've been using Groovy AST transform annotations together with JPA annotations with no known problems to date, and I hope it continues that way.

Wednesday, December 28, 2011

Checking Foreign Key attribute consistency

While evolving a schema during development, it's sometimes hard to make sure that column attributes remain consistent across different tables.

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.

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:

...
INVOICE_NUMBER_TYPE = VARCHAR2(10)
...


Then in create_table_invoice.sql I might have:

CREATE TABLE invoice (
invoice_number @INVOICE_NUMBER_TYPE@ NOT NULL,
...
);


And also use the macro elsewhere for any foreign key column.

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.

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.

Another approach to that problem is to use a view like this:

CREATE OR REPLACE VIEW chk_foreign_key_type AS
SELECT
ac.table_name child_table,
acc.column_name child_column,
atc.data_type child_data_type,
atc.data_length child_data_length,
atc.data_scale child_data_scale,
ac2.table_name parent_table,
acc2.column_name parent_column,
atc2.data_type parent_data_type,
atc2.data_length parent_data_length,
atc2.data_scale parent_data_scale
FROM all_constraints ac
JOIN all_cons_columns acc ON acc.owner = ac.owner
AND acc.constraint_name = ac.constraint_name
JOIN all_tab_columns atc ON atc.owner = ac.owner
AND atc.table_name = acc.table_name
AND atc.column_name = acc.column_name
JOIN all_constraints ac2 ON ac2.owner = ac.owner
AND ac2.constraint_name = ac.r_constraint_name
JOIN all_cons_columns acc2 ON acc2.owner = ac2.owner
AND acc2.constraint_name = ac2.constraint_name
AND acc2.position = acc.position
JOIN all_tab_columns atc2 ON atc2.owner = acc2.owner
AND atc2.table_name = acc2.table_name
AND atc2.column_name = acc2.column_name
WHERE ac.owner = 'your_schema'
AND ac.constraint_type = 'R'
AND (atc2.data_type <> atc.data_type
OR atc2.data_length <> atc.data_length
OR NVL(atc2.data_scale, -1) <> NVL(atc2.data_scale, -1))
ORDER BY 1, 2
/

COMMENT ON TABLE chk_foreign_key_type IS
'Foreign key column(s) different from parent type/length/scale'
/


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.

Wednesday, February 9, 2011

Ant Target Dependency Graph

We can get a pretty good diagram of our Ant target dependencies very easily using an embedded Groovy script and GraphViz.

Add this to build.xml:



def u(x) {x.toString().replace("-", "_").replace(".", "_")}
new File("build.dot").text = """
digraph ant {
${project.targets.values().collect {target ->
target.dependencies.collect {dep ->
u(dep) + " -> " + u(target)
}.join("\n")
}.join("\n")}
}
"""


You will need to have the embeddable groovy-all.jar in Ant's classpath, e.g. in ~/.ant/lib/.

Then run "ant target-graph". It writes a build.dot file in the current directory.

Convert this into a picture using the GraphViz dot command:
dot -Tsvg -O build.dot

The results are surprisingly good.

Here's an example from the Apache commons-dbcp project:



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 Gradle.

Sunday, January 23, 2011

SlickEdit 2011 Wish List

Well, we're nearly through January 2011, and the SlickEdit 2011 beta is due any day now.

I've been using SlickEdit 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.

Here is my wish list for features in SlickEdit 2011:


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.

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.

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 really well, would benefit all 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.

For a couple of examples of excellent VCS integration, look at:


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 IntelliJ IDEA 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.

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.

Friday, January 7, 2011

Groovy DSL/Builders: ZIP Output Streams

Let's follow up last week's post with another example of a very similar, very simple builder.

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.

Because the JDK does not include methods to traverse the filesystem, we need to define a method to be called recursively for subdirectories:


private void zipDirectory(File dir, ZipOutputStream zos) throws IOException {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
zipDirectory(file, zos);
}
else {
ZipEntry entry = new ZipEntry(file.getPath());
entry.setSize(file.length());
entry.setTime(file.lastModified());
zos.putNextEntry(entry);
IOUtils.copy(new FileInputStream(file), zos);
}
}
}


We cheated a little here by using the Apache Commons IO IOUtils class to actually copy the file bytes to the ZIP file. Also, we don't do anything here with IOExceptions.

With this method in place, we can create a ZIP file from a folder using:


ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
zipDirectory(new File(dir), zos);
zos.close();


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:


new ZipOutputStream(new FileOutputStream(zipFile)).withStream {zos ->
new File(dir).traverse(type: FileType.FILES) {File file ->
def entry = new ZipEntry(file.path)
entry.size = file.length()
entry.time = file.lastModified()
zos.putNextEntry(entry)
zos << file.bytes
}


This code is still a bit awkward in how it interacts with Java's ZIP API, in particular the creation of the ZipEntry object.

Using a simple builder, we can rewrite this as follows:


new ZipBuilder(new FileOutputStream(zipFile)).zip {
new File(dir).traverse(type: FileType.FILES) {File file ->
entry(file.path, size: file.length(), time: file.lastModified()) {it << file.bytes}
}
}

The ZipBuilder provides two methods:

  • zip(): creates and manages the ZipOutputStream

  • entry() (nested): creates and adds a ZipEntry to the enclosing zip stream


As with other builders, this builder promotes readable code that reflects the structure of the object to be created.

Here's the code for the builder itself:


class ZipBuilder {

@InheritConstructors
static class NonClosingOutputStream extends FilterOutputStream {
void close() {
// do nothing
}
}

ZipOutputStream zos

ZipBuilder(OutputStream os) {
zos = new ZipOutputStream(os)
}

void zip(Closure closure) {
closure.delegate = this
closure.call()
zos.close()
}

void entry(Map props, String name, Closure closure) {
def entry = new ZipEntry(name)
props.each {k, v -> entry[k] = v}
zos.putNextEntry(entry)
NonClosingOutputStream ncos = new NonClosingOutputStream(zos)
closure.call(ncos)
}

void entry(String name, Closure closure) {
entry([:], name, closure)
}
}


This builder uses the same style with Closures as the HSSFWorkbookBuilder described earlier.

There are a few other Groovy (and Java) features to note:

  • Java's ZIP library requires clients to write to the ZipOutputStream for each entry created. We need to make sure that no entry closes the ZipOutputStream -- 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 NonClosingOutputStream before passing it to an entry. This class is simply defined as a FilterOutputStream (OutputStream decorator) with a no-op close() method.

  • We use Groovy's @InheritConstructors to save repeating the trivial constructor.

  • The entry() method creates a new ZipEntry with its mandatory name property. It then populates additional optional properties from a Map, using Groovy's support for setting Java Beans properties as Map 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.

  • The main overload of entry() is declared to take its arguments in this order: Map props, String name, Closure closure. When called, entry() is (typically) given arguments in a different order: String name, Map props, Closure closure. This is due to Groovy's convention for passing named arguments to a method, described here, in the section "Named Arguments".


One final note about this builder -- it doesn't just work with files. Because the constructor takes an OutputStream, 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.

Wednesday, December 29, 2010

Groovy DSL/Builders: POI Spreadsheets

It's well-known that Groovy is very rich for creating DSLs and fluent builder APIs.

I work a lot with the Apache POI library to generate Excel workbooks from data. We can use Groovy very easily to support a fluent and readable API for creating workbooks.

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:

def workbook = new HSSFWorkbook()
def sheet1 = workbook.createSheet("Data")
def row10 = sheet1.createRow(0)
row10.createCell(0).setCellValue(new HSSFRichTextString("Invoice Number"))
row10.createCell(1).setCellValue(new HSSFRichTextString("Invoice Date"))
row10.createCell(2).setCellValue(new HSSFRichTextString("Amount"))
def row11 = sheet1.createRow(1)
row11.createCell(0).setCellValue(new HSSFRichTextString("100"))
row11.createCell(1).setCellValue(Date.parse("yyyy-MM-dd", "2010-10-18"))
row11.createCell(2).setCellValue(123.45)
def row12 = sheet1.createRow(2)
row12.createCell(0).setCellValue(new HSSFRichTextString("600"))
row12.createCell(1).setCellValue(Date.parse("yyyy-MM-dd", "2010-11-17"))
row12.createCell(2).setCellValue(132.54)
def sheet2 = workbook.createSheet("Summary")
def row20 = sheet2.createRow(0)
row20.createCell(0).setCellValue(new HSSFRichTextString("Sheet: Summary"))
def row21 = sheet2.createRow(1)
row21.createCell(0).setCellValue(new HSSFRichTextString("Total"))
row21.createCell(1).setCellValue(123.45 + 132.54)


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.
One of the big advantages of builders is that the structure of the code can match closely the structure of the generated result.

Here's the same workbook, created with a simple builder API:


def workbook = new HSSFWorkbookBuilder().workbook {
sheet("Data") { // sheet1
row(["Invoice Number", "Invoice Date", "Amount"])
row(["100", Date.parse("yyyy-MM-dd", "2010-10-18"), 123.45])
row(["600", Date.parse("yyyy-MM-dd", "2010-11-17"), 132.54])
}
sheet("Summary") { // sheet2
row(["Sheet: Summary"])
row(["Total", 123.45 + 132.54])
}
}


The HSSFWorkbookBuilder class required to do this is very straightforward:


import org.apache.poi.hssf.usermodel.HSSFRichTextString
import org.apache.poi.hssf.usermodel.HSSFWorkbook
import org.apache.poi.ss.usermodel.Cell
import org.apache.poi.ss.usermodel.Row
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook

class HSSFWorkbookBuilder {

private Workbook workbook = new HSSFWorkbook()
private Sheet sheet
private int rows

Workbook workbook(Closure closure) {
closure.delegate = this
closure.call()
workbook
}

void sheet(String name, Closure closure) {
sheet = workbook.createSheet(name)
rows = 0
closure.delegate = this
closure.call()
}

void row(values) {
Row row = sheet.createRow(rows++ as int)
values.eachWithIndex {value, col ->
Cell cell = row.createCell(col)
switch (value) {
case Date: cell.setCellValue((Date) value); break
case Double: cell.setCellValue((Double) value); break
case BigDecimal: cell.setCellValue(((BigDecimal) value).doubleValue()); break
default: cell.setCellValue(new HSSFRichTextString("" + value)); break
}
}
}

}


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.

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.


def workbook = new HSSFWorkbookBuilder().workbook {
sheet("Data") {
db.eachRow(
sql,
{meta -> row(meta*.columnName)}, // header row with columns names from ResultSetMetaData
{rs -> row(rs.toRowResult().values())} // data row for each ResultSet row
)
}
sheet("SQL") {
sql.eachLine {line ->
row([line])
}
}
}

Friday, February 5, 2010

Release builds with TeamCity: Selecting the branch

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.

We finally got TeamCity doing our release builds. There were a couple of tricky points, which I thought would be worth writing up:

  • Selecting the branch
  • Manipulating the repository
  • Ensuring correct (release) versions of dependencies


Selecting the branch


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!

We found a pretty good way to do this with TeamCity, using Build Configuration Templates and Configuration Parameters.

The idea is that you set up a template that contains all of the settings for the release build, except for 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.

Here are some details, using Subversion VCS and a hypothetical project named "xxx":

Create the Release Build Template


  1. Edit project's existing checkin build config.
  2. Click "Extract Template" to create a new template.
  3. Enter "release-build-template" for Name.
  4. Back in the checkin build config, click "Detach from Template".
  5. Click OK.

We've created a template, with no configurations attached.

Set up the Release Build Template

Edit the template. Change settings as given in the following sections.

Version Control Settings


  1. Create a new VCS root named xxx-branches.
  2. Specify Subversion as the Type of VCS.
  3. Enter Svn repo URL + "/xxx/branches" as the URL.
  4. Test the Connection.
  5. Save the VCS root.
  6. Attach the template to the xxx-branches VCS root.
  7. Detach the template from the xxx-trunk VCS root.
  8. Add checkout rule for VCS root: "%release.branch%=>.". This tells TeamCity to checkout the specific release branch into the working directory.
  9. Save Version Control Settings.

Runner Settings


  1. Change Target to "release-build", or whatever you want to call your release build target.
  2. Save Runner Settings.

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.

The Ant target might look something like this:



Build Triggering Settings


  1. Delete/disable all triggering (VCS and Dependencies).
  2. Save Build Triggering Settings.
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.

Create a Release Branch Build Config for Release xx.yy

This procedure creates a release build config for a particular branch.

  1. Edit the release-build template's build configuration.
  2. Click Create Build Configuration From Template.
  3. Enter "release-xx.yy" for Name, where "xx.yy" is the name of your release branch.
  4. Enter the name of the branch for the release.branch parameter. For example, "RB-01.05".
We've created a configuration for running a release build on the branch.

To create a release:

  1. Ensure all changes for the release are checked into trunk.
  2. Create the branch. For example:
    svn copy $SVN/xxx/trunk $SVN/xxx/branches/RB-xx.yy

  3. Click Run on the release build configuration in TeamCity.



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.

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.

Thursday, October 29, 2009

Oracle SQL-Developer 2.1

I've been trying out Oracle SQL-Developer 2.1 early adopter for the last couple of days, because SQL-Developer is the standard query tool at the company where I'm working.

Personally, I mostly use Aqua Data Studio. Apart from its odious activation scheme, ADS is excellent in every respect. However, ADS is kind of expensive, while SQL-Developer is free.

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.)

I did notice one amusing glitch so far:








In fact the query only returned about 20 rows.

Friday, September 11, 2009

Table size frequencies

Here's an interesting query to run on your Oracle database:


Here's what I get on the main application I've been working on the last several years:


ROW_COUNT NUM_TABLES
---------------------- ----------
1-10 36
10-100 22
100-1000 22
1000-10000 22
10000-100000 22
100000-1000000 33
1000000-10000000 20
10000000-100000000 2
100000000-1000000000 3
1000000000-10000000000 1
183

Friday, July 24, 2009

Inline data for DbUnit tests in Grails

There is a DbUnit plugin for Grails: http://docs.codehaus.org/display/GRAILS/DBUnit+Plugin.

However, like almost all tutorials for DbUnit, this plugin assumes that the best way to organize your test data is using DbUnit's FlatXmlDataSet. The example given on the Wiki link above has this FlatXmlDataSet:


During my 5 years of working with DbUnit, I have come to the conclusion that FlatXmlDataSet is not the best way to organize test data. Here are my reasons:

  • XML is not a good representation for tabular data. CSV is more readable.

  • For most of my tests, I would rather see the test data together with the test, rather than separate.


DbUnit tests often have 3 "phases":

  1. Prime the database with setup data.

  2. Run some code of the System Under Test.

  3. Verify changed data in the database.


For most of my DbUnit tests, I would like to see the data used for the first and last parts together with the test itself.

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.

The Wiki example, used in a test with inline datasets, might look like this:

(I'm using pipe characters instead of commas in this case!)
If the same Person data were used in different test methods, we might factor it out into a common method or field.

What I tend to find is that I use a lot of common reference data in my tests, but test-specific detail data. I handle the common reference data in a setUp() method, or even in the base DbUnitTestCase 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.

We define cleanInsert() and a few other conveniences in DbUnitTestCase:

Friday, April 3, 2009

Tomcat Expert Seminar

Yesterday I attended SpringSource's Tomcat Expert Seminar.

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.

The most interesting topics for me were:

  • Large scale deployments -- showed flexible ways to organise deployment to simplify instance configuration and management.

  • Troubleshooting -- great tips on Tomcat but even more on Java and server-based systems in general.


I look forward to attending similar sessions in the future.

Tuesday, September 16, 2008

Linux on new Laptop

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.

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.

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.

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.

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.

Monday, July 28, 2008

Tests For Your Data 2: When to Use

My friend Nigel Charman commented on Tests For Your Data with some good questions.

First the short answers.

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.

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.

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. ;-)

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.

Now for the meaty question: For what types of data is this approach suited?

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.

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.

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.

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.

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.

"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.

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.

Saturday, July 26, 2008

Tests For Your Data

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.

I propose we should have tests for our data too.

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:

  • A PRIMARY KEY constraint defines a unique key for the table.
  • A UNIQUE constraint identifies an alternative candidate key, which also must be unique.
  • A FOREIGN KEY constraint defines a relationship to a parent table, and is used to enforce referential integrity.
  • A CHECK constraint can be used to check arbitrary conditions on the values in a row.

In addition to these constraints, you can also use triggers to check more complex conditions, perhaps involving multiple rows.

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.

Here's an example. Suppose we have a table billing_period:

BILLING_PERIOD ACTUAL_START ACTUAL_END
200825 2008-06-04 2008-06-10
200826 2008-06-11 2008-06-17
200827 2008-06-18 2008-06-24
etc


The billing_period 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?

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?

Start with a view like this:

CREATE VIEW chk_billing_period_7_days_long AS
SELECT *
FROM billing_period
WHERE actual_start - actual_end <> 6

This view returns a row for any billing period which is not seven days (actually six days) from its start to its end.

Here's another one, to check for overlaps:

CREATE VIEW chk_billing_period_overlaps AS
SELECT *
FROM billing_period bp1
WHERE EXISTS (
SELECT *
FROM billing_period bp2
WHERE bp2.actual_start BETWEEN bp1.actual_start AND bp1.actual_end
OR bp2.actual_end BETWEEN bp1.actual_start AND bp1.actual_end
)

Finally, to check for gaps:

CREATE VIEW chk_billing_period_gaps AS
SELECT *
FROM billing_period bp1
WHERE EXISTS (
SELECT *
FROM billing_period bp2
WHERE bp2.actual_start > bp2.actual_end
)
AND NOT EXISTS (
SELECT *
FROM billing_period bp3
WHERE bp3.actual_start = bp2.actual_end + 1
)

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.

Because we named all the views according to a convention (they all start with chk_) 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.

If our database supports it, we can add descriptive comments to the views, such as:

COMMENT ON TABLE chk_billing_period_overlaps IS
'Overlap between two or more billing periods'

This comment would make a nice subject line for an email message.

It's easy to add more check views: just define a view beginning with
chk_.

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.

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.)

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.