Index: unk/src/groovy/HqlBuilder.groovy
===================================================================
--- /trunk/src/groovy/HqlBuilder.groovy	(revision 642)
+++ 	(revision )
@@ -1,480 +1,0 @@
-/* Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Provides a DSL for building and managing HQL strings.
- * For more usage examples see the HqlBuilderTests.
- * HQL reference see http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/queryhql.html
- *
- * DML reference see http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/batch.html#batch-direct
- * DML-style clauses: "update, delete, insert into" may not be well tested, the "set" clause has basic implementation.
- * So double check the q.query output for these cases.
- *
- * Primary goals:
- * 1. Easy to read and understand in code.
- * 2. Easy to read and understand when printed (e.g when displayed in a report).
- * 3. Easy to execute with correct paginateParams and namedParams.
- * 4. Easy to change a clause and execute again.
- *
- * Basic usage:
- *    def q = new HqlBuilder().query {
- *        select 'count(distinct book)'
- *        from 'Book as book'
- *        where 'book.id > 100'
- *            and 'book.inStock = true'
- *    }
- *    def totalCount = Book.executeQuery(q.query, q.namedParams, q.paginateParams)[0].toInteger()
- *    q.select = 'distinct book'
- *    def list = Book.executeQuery(q.query, q.namedParams, q.paginateParams)
- *
- *    def bookList = new PagedResultList(list, totalCount)
- *    log.debug '\n' + q.printFormattedQuery
- *
- * @author Gavin Kromhout
- * @version DraftB
- *
- */
-class HqlBuilder {
-
-    // HQL clauses.
-    // Each clause is a map key with a list of terms.
-    def clauses = [:]
-
-    // HQL namedParams.
-    // HQL requires the namedParams to match exactly with the clause expressions.
-    def namedParams = [:]
-
-    // HQL paginateParams.
-    def paginateParams = [max: 1000, offset: 0]
-
-    // The where clause terms are handled separately from other clauses
-    // and are a list of logicalTerms.
-    // The where clause is built by buildWhereClause.
-    def whereClauseTerms = []
-
-    // LogicalIndexStack holds indexes of the current whereClauseTerm nesting.
-    def logicalIndexStack = []
-
-   def logicalBuilders = [AND: 'and',
-                                        OR: 'or']
-
-    def nestingStack = []
-
-    // Sort and Order.
-    // It is easier and more flexible to simply add order as a clause, e.g: order 'by name desc, id asc'
-    // def sort = "" // e.g. instanceName.id
-    // def order = "" // e.g. asc or desc
-
-    /**
-     * Constructor.
-     * Any property that exists (or responds) in the class may be supplied as an argument.
-     * E.g: max:20, offset:10, debug:true
-     * The debug property does not really exist, but if true and no external log property
-     * has been setup then the internal mockLogger will be configured in debug mode.
-     *
-     * @param args A map of arguments, defaults to an empty map.
-     *
-     */
-    def HqlBuilder(Map args = [:]) {
-        args.each { arg ->
-            def argKey = arg.key.toLowerCase()
-            if(super.hasProperty(argKey))
-                this[argKey] = arg.value
-        }
-        if(!super.metaClass.hasMetaProperty('log'))
-            mockLogging(args.debug)
-        log.debug "HqlBuilder()"
-    }
-
-    /**
-     * Call with no args.
-     *  Has no real use other than to prevent obscure errors.
-     */
-    def call() {
-        log.debug "call()"
-    }
-
-    /**
-     * Call with closure as last arg.
-     * A typically used build call, e.g: q { } is equivalent to q.call() { }
-     */
-    def call(Closure cl) {
-        log.debug "call(Closure cl)"
-        handleClosure(cl)
-    }
-
-    /**
-     * Domain specific build method.
-     *  Has no real use other than to prevent obscure errors
-     * when user makes a call to query() and Groovy calls query(Closure cl)
-     *
-     * @returns This object.
-     *
-     */
-    def query() {
-        log.debug "query()"
-        return this // Must return this object to q.
-    }
-
-    /**
-     * Domain specific build method.
-     * The recommended build call, e.g: def q = new HqlBuilder().query { }
-     *
-     * @param cl The closure that will be used to build the query.
-     * @returns This object.
-     *
-     */
-    def query(Closure cl) {
-        log.debug "query(Closure cl)"
-        handleClosure(cl)
-        return this // Must return this object to q.
-    }
-
-    /**
-     * InvokeMethod resolves all undefined methods.
-     * Which include the clause methods, e.g: select 'book' is equivalent to select('book').
-     * Note that defined methods will be called directly since this class does not implement GroovyInterceptable.
-     * If class was "HqlBuilder implements GroovyInterceptable" then even println would be intercepted and
-     * several exlusions might be needed. e.g: if(methodName != 'call' && methodName != 'println')
-     */
-    def invokeMethod(String methodName, args) {
-
-        log.debug "invokeMethod(${methodName}, ${args})"
-
-        // Call any closures first, that way the nesting is handled and we just keep a reference.
-        if(args[-1] instanceof Closure) {
-            handleClosure(args[-1], methodName)
-            args = args.minus(args[-1])
-        }
-
-        if(!clauses.containsKey(methodName) && !isLogicalBuilder(methodName))
-            clauses[methodName] = []
-
-        if(args) {
-            if(isWhereClauseBuilder(methodName)) {
-                logicalBuilder(methodName, args)
-                return
-            }
-        }
-
-        for(arg in args) {
-            if(arg instanceof String || arg instanceof GString)
-                clauses[methodName] << arg
-        }
-
-    } // invokeMethod()
-
-    /**
-     * PropertyMissing.
-     * Allows clauses to be added after build, e.g: q.order = 'by book.name asc'
-     * and clauses to be removed, e.g: q.order = null
-     */
-    def propertyMissing(String propertyName, value) {
-        log.debug "propertyMissing(${propertyName}, ${value})"
-
-        if(value == null) {
-            removeClause(propertyName)
-            if(propertyName.toLowerCase() == 'where')
-                whereClauseTerms.clear()
-            return
-        }
-
-        if(!clauses.containsKey(propertyName))
-            clauses[propertyName] = []
-
-        // Occurs when user assigns to where clause, e.g: q.where = 'book.id > 100'
-        if(propertyName.toLowerCase() == 'where') {
-            whereClauseTerms.clear()
-            logicalBuilder(propertyName, [value])
-            return
-        }
-
-        if(value instanceof String || value instanceof GString)
-            clauses[propertyName] = [value]
-    } // propertyMissing(String propertyName, value)
-
-    /**
-     * PropertyMissing.
-     * Allow clauses to be accessed directly by name, e.g: println q.order.
-     * Since clauses is a Map null is simply returned for a non-existant clause.
-     */
-    def propertyMissing(String propertyName) {
-        log.debug "propertyMissing(${propertyName})"
-
-        if(!clauses.containsKey(propertyName))
-            clauses[propertyName] = []
-
-        // Occurs when user performs an operation on where clause.
-        // E.g: q.where << "book.id = 100" which is actually NOT a supported operation since
-        // calling the method provides the correct function e.g: q.where "book.id > 100".
-        // Also allows `println q.where` to be short hand for `println q.whereClauseTerms`
-        if(propertyName.toLowerCase() == 'where') {
-            return whereClauseTerms
-        }
-
-        clauses[propertyName]
-    } // propertyMissing(String propertyName)
-
-    def setMax(Integer value) {
-        paginateParams.max = value
-    }
-
-    def getMax() {
-        paginateParams.max
-    }
-
-    def setOffset(Integer value) {
-        paginateParams.offset = value
-    }
-
-    def getOffset() {
-        paginateParams.offset
-    }
-
-    /**
-     * RemoveClause.
-     * Allows clauses to be removed, e.g: q.removeClause('order')
-     *
-     * @param clauseName The clause to remove.
-     *
-     */
-    def removeClause(String clauseName) {
-            clauses.remove(clauseName)
-    }
-
-    /**
-     * BuildWhereClause.
-     * Build the where clause from whereClauseTerms.
-     */
-    def buildWhereClause(printFormat = false) {
-        //log.debug "buildWhereClause()"
-
-        if(!whereClauseTerms)
-            return ''
-
-        def whereClause = 'where '
-
-        def buildExpression // declared separately to allow recurrsion.
-        buildExpression = { term ->
-            def result = ''
-            def termCount = term.expressions.size()
-            if(termCount > 1) {
-                term.expressions.eachWithIndex { t, index ->
-                    if(index == 0)
-                        result += buildExpression(t)
-                    else if(printFormat)
-                        result += " \n\t${t.logic} ${buildExpression(t)}"
-                    else
-                        result += " ${t.logic} ${buildExpression(t)}"
-
-                }
-                result = "( "+result+" )"
-            }
-            else {
-                if(term.expressions[0] instanceof Map)
-                    result += "${term.expressions[0].expressions[0]}"
-                else
-                    result += "${term.expressions[0]}"
-            }
-            return result
-        }
-
-        whereClauseTerms.eachWithIndex { tm, index ->
-            if(index == 0)
-                whereClause += buildExpression(tm)
-            else if(printFormat)
-                whereClause += " \n\t${tm.logic} ${buildExpression(tm)}"
-            else
-                whereClause += " ${tm.logic} ${buildExpression(tm)}"
-        }
-
-        return whereClause
-    } // buildWhereClause(printFormat = false)
-
-    /**
-     * LogicalBuilder.
-     * Build the whereClauseTerms
-     * by appending logicalTerms to the appropriate expressions.
-     */
-    def logicalBuilder(logicalName, args) {
-        log.debug "logicalBuilder(${logicalName}, ${args})"
-        log.debug "logicalIndexStack: ${logicalIndexStack}"
-
-        def logic = getLogicalString(logicalName)
-
-        for(arg in args) {
-            if(arg instanceof String || arg instanceof GString) {
-                arg = arg.trim()
-                if(arg) { // prevent empty strings being added.
-                    if(logicalIndexStack.size() > 0) {
-                        // Append to current index position.
-                        whereClauseTerms[logicalIndexStack[-1]].expressions << logicalTerm(logic, arg)
-                    }
-                    else {
-                        // Append to 'root'.
-                        whereClauseTerms << logicalTerm(logic, null) // empty expression logicalTerm.
-                        whereClauseTerms[-1].expressions << logicalTerm(logic, arg) // append logicalTerm to expressions
-                    }
-                } // if(arg)
-            } // if(arg instanceof)
-        } // for
-
-    } // logicalBuilder(logicalName, args)
-
-    /**
-     * LogicalTerm.
-     * A logicalTerm is a map object that holds the logic and list of expressions of a whereClauseTerm.
-     */
-    def logicalTerm = { logic, expression ->
-        expression = expression ? [expression] : []
-        ['logic': getLogicalString(logic), 'expressions': expression]
-    }
-
-    /**
-     * GetLogicalString.
-     *
-     * @param logicalName The name to get the matching logicalBuilder string for.
-     */
-    private getLogicalString(logicalName) {
-
-        switch(logicalName.toLowerCase()) {
-            case 'where':
-                logicalBuilders.AND
-                break
-            case logicalBuilders.AND:
-                logicalBuilders.AND
-                break
-            case logicalBuilders.OR:
-                logicalBuilders.OR
-                break
-        }
-
-    }
-
-    /**
-     * HandleClosure.
-     * Setting delegate and DELEGATE_FIRST allows closure to access this object's properties first.
-     */
-    private handleClosure(Closure cl, String methodName = 'root') {
-        log.debug "handleClosure(${cl.toString()}, ${methodName})"
-        if(isWhereClauseBuilder(methodName)) {
-            whereClauseTerms << logicalTerm(getLogicalString(methodName), null)
-            logicalIndexStack << whereClauseTerms.size()-1
-        }
-        nestingStack.push(methodName)
-        cl.delegate = this
-        cl.resolveStrategy = Closure.DELEGATE_FIRST
-        cl.call()
-        //log.debug "nestingStack: $nestingStack"
-        nestingStack.pop()
-        if(isWhereClauseBuilder(methodName)) {
-            logicalIndexStack.pop()
-        }
-    }
-
-    /**
-     * MockLogging.
-     * This class has super cow powers and can mock out it's own debug logging.
-     */
-    private mockLogging(debug = false) {
-        def mockLogger = {}
-        if(debug) {
-            mockLogger = {msg ->
-                    println "${super.getClass()} - DEBUG: $msg"
-            }
-        }
-        super.metaClass.log = [debug: mockLogger]
-        log.debug "Internal mockLogger configured."
-    }
-
-    /**
-     * IsLogicalBuilder.
-     * Determine if a method is a logicalBuilder.
-     */
-    private isLogicalBuilder(String methodName) {
-        logicalBuilders.find{ it.value == methodName.toLowerCase()} ? true:false
-    }
-
-    /**
-     * IsWhereClauseBuilder.
-     * Determine if a method is a where clause builder.
-     */
-    private isWhereClauseBuilder(String methodName) {
-        methodName = methodName.toLowerCase()
-        if(methodName == 'where' || isLogicalBuilder(methodName))
-            return true
-        else
-            return false
-    }
-
-    /**
-     * GetQuery.
-     * Assemble and return the query in a format that can be directly executed.
-     * E.g: executeQuery(q.query, q.namedParams, q.paginateParams).
-     */
-    def getQuery() {
-        clauses.collect { clause ->
-            switch (clause.key.toLowerCase()) {
-                case 'select':
-                    clause.key + ' ' + clause.value.join(', ')
-                    break
-                case 'set':
-                    clause.key + ' ' + clause.value.join(', ')
-                    break
-                case 'where':
-                    buildWhereClause()
-                    break
-                case 'order':
-                    clause.key + ' ' + clause.value.join(', ')
-                    break
-                case 'group':
-                    clause.key + ' ' + clause.value.join(', ')
-                    break
-                default:
-                    clause.key + ' ' + clause.value.join(' ')
-            }
-        }.join(' ')
-    } // getQuery()
-
-    /**
-     * GetPrintFormattedQuery.
-     * Assemble and return the query in a format that can be more easily printed and read by a person.
-     * E.g: println q.printFormattedQuery or when displayed in a report.
-     */
-    def getPrintFormattedQuery() {
-        clauses.collect { clause ->
-            switch (clause.key.toLowerCase()) {
-                case 'select':
-                    clause.key + ' ' + clause.value.join(', \n\t')
-                    break
-                case 'set':
-                    clause.key + ' ' + clause.value.join(', \n\t')
-                    break
-                case 'where':
-                    buildWhereClause(true)
-                    break
-                case 'order':
-                    clause.key + ' ' + clause.value.join(', \n\t')
-                    break
-                case 'group':
-                    clause.key + ' ' + clause.value.join(', \n\t')
-                    break
-                default:
-                    clause.key + ' ' + clause.value.join(' \n\t')
-            }
-        }.join(' \n')
-    } // getPrintFormattedQuery()
-
-} // end class
Index: /trunk/src/groovy/net/kromhouts/HqlBuilder.groovy
===================================================================
--- /trunk/src/groovy/net/kromhouts/HqlBuilder.groovy	(revision 643)
+++ /trunk/src/groovy/net/kromhouts/HqlBuilder.groovy	(revision 643)
@@ -0,0 +1,482 @@
+/* Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.kromhouts
+
+/**
+ * Provides a DSL for building and managing HQL strings.
+ * For more usage examples see the HqlBuilderTests.
+ * HQL reference see http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/queryhql.html
+ *
+ * DML reference see http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/batch.html#batch-direct
+ * DML-style clauses: "update, delete, insert into" may not be well tested, the "set" clause has basic implementation.
+ * So double check the q.query output for these cases.
+ *
+ * Primary goals:
+ * 1. Easy to read and understand in code.
+ * 2. Easy to read and understand when printed (e.g when displayed in a report).
+ * 3. Easy to execute with correct paginateParams and namedParams.
+ * 4. Easy to change a clause and execute again.
+ *
+ * Basic usage:
+ *    def q = new HqlBuilder().query {
+ *        select 'count(distinct book)'
+ *        from 'Book as book'
+ *        where 'book.id > 100'
+ *            and 'book.inStock = true'
+ *    }
+ *    def totalCount = Book.executeQuery(q.query, q.namedParams, q.paginateParams)[0].toInteger()
+ *    q.select = 'distinct book'
+ *    def list = Book.executeQuery(q.query, q.namedParams, q.paginateParams)
+ *
+ *    def bookList = new PagedResultList(list, totalCount)
+ *    log.debug '\n' + q.printFormattedQuery
+ *
+ * @author Gavin Kromhout
+ * @version DraftB
+ *
+ */
+class HqlBuilder {
+
+    // HQL clauses.
+    // Each clause is a map key with a list of terms.
+    def clauses = [:]
+
+    // HQL namedParams.
+    // HQL requires the namedParams to match exactly with the clause expressions.
+    def namedParams = [:]
+
+    // HQL paginateParams.
+    def paginateParams = [max: 1000, offset: 0]
+
+    // The where clause terms are handled separately from other clauses
+    // and are a list of logicalTerms.
+    // The where clause is built by buildWhereClause.
+    def whereClauseTerms = []
+
+    // LogicalIndexStack holds indexes of the current whereClauseTerm nesting.
+    def logicalIndexStack = []
+
+   def logicalBuilders = [AND: 'and',
+                                        OR: 'or']
+
+    def nestingStack = []
+
+    // Sort and Order.
+    // It is easier and more flexible to simply add order as a clause, e.g: order 'by name desc, id asc'
+    // def sort = "" // e.g. instanceName.id
+    // def order = "" // e.g. asc or desc
+
+    /**
+     * Constructor.
+     * Any property that exists (or responds) in the class may be supplied as an argument.
+     * E.g: max:20, offset:10, debug:true
+     * The debug property does not really exist, but if true and no external log property
+     * has been setup then the internal mockLogger will be configured in debug mode.
+     *
+     * @param args A map of arguments, defaults to an empty map.
+     *
+     */
+    def HqlBuilder(Map args = [:]) {
+        args.each { arg ->
+            def argKey = arg.key.toLowerCase()
+            if(super.hasProperty(argKey))
+                this[argKey] = arg.value
+        }
+        if(!super.metaClass.hasMetaProperty('log'))
+            mockLogging(args.debug)
+        log.debug "HqlBuilder()"
+    }
+
+    /**
+     * Call with no args.
+     *  Has no real use other than to prevent obscure errors.
+     */
+    def call() {
+        log.debug "call()"
+    }
+
+    /**
+     * Call with closure as last arg.
+     * A typically used build call, e.g: q { } is equivalent to q.call() { }
+     */
+    def call(Closure cl) {
+        log.debug "call(Closure cl)"
+        handleClosure(cl)
+    }
+
+    /**
+     * Domain specific build method.
+     *  Has no real use other than to prevent obscure errors
+     * when user makes a call to query() and Groovy calls query(Closure cl)
+     *
+     * @returns This object.
+     *
+     */
+    def query() {
+        log.debug "query()"
+        return this // Must return this object to q.
+    }
+
+    /**
+     * Domain specific build method.
+     * The recommended build call, e.g: def q = new HqlBuilder().query { }
+     *
+     * @param cl The closure that will be used to build the query.
+     * @returns This object.
+     *
+     */
+    def query(Closure cl) {
+        log.debug "query(Closure cl)"
+        handleClosure(cl)
+        return this // Must return this object to q.
+    }
+
+    /**
+     * InvokeMethod resolves all undefined methods.
+     * Which include the clause methods, e.g: select 'book' is equivalent to select('book').
+     * Note that defined methods will be called directly since this class does not implement GroovyInterceptable.
+     * If class was "HqlBuilder implements GroovyInterceptable" then even println would be intercepted and
+     * several exlusions might be needed. e.g: if(methodName != 'call' && methodName != 'println')
+     */
+    def invokeMethod(String methodName, args) {
+
+        log.debug "invokeMethod(${methodName}, ${args})"
+
+        // Call any closures first, that way the nesting is handled and we just keep a reference.
+        if(args[-1] instanceof Closure) {
+            handleClosure(args[-1], methodName)
+            args = args.minus(args[-1])
+        }
+
+        if(!clauses.containsKey(methodName) && !isLogicalBuilder(methodName))
+            clauses[methodName] = []
+
+        if(args) {
+            if(isWhereClauseBuilder(methodName)) {
+                logicalBuilder(methodName, args)
+                return
+            }
+        }
+
+        for(arg in args) {
+            if(arg instanceof String || arg instanceof GString)
+                clauses[methodName] << arg
+        }
+
+    } // invokeMethod()
+
+    /**
+     * PropertyMissing.
+     * Allows clauses to be added after build, e.g: q.order = 'by book.name asc'
+     * and clauses to be removed, e.g: q.order = null
+     */
+    def propertyMissing(String propertyName, value) {
+        log.debug "propertyMissing(${propertyName}, ${value})"
+
+        if(value == null) {
+            removeClause(propertyName)
+            if(propertyName.toLowerCase() == 'where')
+                whereClauseTerms.clear()
+            return
+        }
+
+        if(!clauses.containsKey(propertyName))
+            clauses[propertyName] = []
+
+        // Occurs when user assigns to where clause, e.g: q.where = 'book.id > 100'
+        if(propertyName.toLowerCase() == 'where') {
+            whereClauseTerms.clear()
+            logicalBuilder(propertyName, [value])
+            return
+        }
+
+        if(value instanceof String || value instanceof GString)
+            clauses[propertyName] = [value]
+    } // propertyMissing(String propertyName, value)
+
+    /**
+     * PropertyMissing.
+     * Allow clauses to be accessed directly by name, e.g: println q.order.
+     * Since clauses is a Map null is simply returned for a non-existant clause.
+     */
+    def propertyMissing(String propertyName) {
+        log.debug "propertyMissing(${propertyName})"
+
+        if(!clauses.containsKey(propertyName))
+            clauses[propertyName] = []
+
+        // Occurs when user performs an operation on where clause.
+        // E.g: q.where << "book.id = 100" which is actually NOT a supported operation since
+        // calling the method provides the correct function e.g: q.where "book.id > 100".
+        // Also allows `println q.where` to be short hand for `println q.whereClauseTerms`
+        if(propertyName.toLowerCase() == 'where') {
+            return whereClauseTerms
+        }
+
+        clauses[propertyName]
+    } // propertyMissing(String propertyName)
+
+    def setMax(Integer value) {
+        paginateParams.max = value
+    }
+
+    def getMax() {
+        paginateParams.max
+    }
+
+    def setOffset(Integer value) {
+        paginateParams.offset = value
+    }
+
+    def getOffset() {
+        paginateParams.offset
+    }
+
+    /**
+     * RemoveClause.
+     * Allows clauses to be removed, e.g: q.removeClause('order')
+     *
+     * @param clauseName The clause to remove.
+     *
+     */
+    def removeClause(String clauseName) {
+            clauses.remove(clauseName)
+    }
+
+    /**
+     * BuildWhereClause.
+     * Build the where clause from whereClauseTerms.
+     */
+    def buildWhereClause(printFormat = false) {
+        //log.debug "buildWhereClause()"
+
+        if(!whereClauseTerms)
+            return ''
+
+        def whereClause = 'where '
+
+        def buildExpression // declared separately to allow recurrsion.
+        buildExpression = { term ->
+            def result = ''
+            def termCount = term.expressions.size()
+            if(termCount > 1) {
+                term.expressions.eachWithIndex { t, index ->
+                    if(index == 0)
+                        result += buildExpression(t)
+                    else if(printFormat)
+                        result += " \n\t${t.logic} ${buildExpression(t)}"
+                    else
+                        result += " ${t.logic} ${buildExpression(t)}"
+
+                }
+                result = "( "+result+" )"
+            }
+            else {
+                if(term.expressions[0] instanceof Map)
+                    result += "${term.expressions[0].expressions[0]}"
+                else
+                    result += "${term.expressions[0]}"
+            }
+            return result
+        }
+
+        whereClauseTerms.eachWithIndex { tm, index ->
+            if(index == 0)
+                whereClause += buildExpression(tm)
+            else if(printFormat)
+                whereClause += " \n\t${tm.logic} ${buildExpression(tm)}"
+            else
+                whereClause += " ${tm.logic} ${buildExpression(tm)}"
+        }
+
+        return whereClause
+    } // buildWhereClause(printFormat = false)
+
+    /**
+     * LogicalBuilder.
+     * Build the whereClauseTerms
+     * by appending logicalTerms to the appropriate expressions.
+     */
+    def logicalBuilder(logicalName, args) {
+        log.debug "logicalBuilder(${logicalName}, ${args})"
+        log.debug "logicalIndexStack: ${logicalIndexStack}"
+
+        def logic = getLogicalString(logicalName)
+
+        for(arg in args) {
+            if(arg instanceof String || arg instanceof GString) {
+                arg = arg.trim()
+                if(arg) { // prevent empty strings being added.
+                    if(logicalIndexStack.size() > 0) {
+                        // Append to current index position.
+                        whereClauseTerms[logicalIndexStack[-1]].expressions << logicalTerm(logic, arg)
+                    }
+                    else {
+                        // Append to 'root'.
+                        whereClauseTerms << logicalTerm(logic, null) // empty expression logicalTerm.
+                        whereClauseTerms[-1].expressions << logicalTerm(logic, arg) // append logicalTerm to expressions
+                    }
+                } // if(arg)
+            } // if(arg instanceof)
+        } // for
+
+    } // logicalBuilder(logicalName, args)
+
+    /**
+     * LogicalTerm.
+     * A logicalTerm is a map object that holds the logic and list of expressions of a whereClauseTerm.
+     */
+    def logicalTerm = { logic, expression ->
+        expression = expression ? [expression] : []
+        ['logic': getLogicalString(logic), 'expressions': expression]
+    }
+
+    /**
+     * GetLogicalString.
+     *
+     * @param logicalName The name to get the matching logicalBuilder string for.
+     */
+    private getLogicalString(logicalName) {
+
+        switch(logicalName.toLowerCase()) {
+            case 'where':
+                logicalBuilders.AND
+                break
+            case logicalBuilders.AND:
+                logicalBuilders.AND
+                break
+            case logicalBuilders.OR:
+                logicalBuilders.OR
+                break
+        }
+
+    }
+
+    /**
+     * HandleClosure.
+     * Setting delegate and DELEGATE_FIRST allows closure to access this object's properties first.
+     */
+    private handleClosure(Closure cl, String methodName = 'root') {
+        log.debug "handleClosure(${cl.toString()}, ${methodName})"
+        if(isWhereClauseBuilder(methodName)) {
+            whereClauseTerms << logicalTerm(getLogicalString(methodName), null)
+            logicalIndexStack << whereClauseTerms.size()-1
+        }
+        nestingStack.push(methodName)
+        cl.delegate = this
+        cl.resolveStrategy = Closure.DELEGATE_FIRST
+        cl.call()
+        //log.debug "nestingStack: $nestingStack"
+        nestingStack.pop()
+        if(isWhereClauseBuilder(methodName)) {
+            logicalIndexStack.pop()
+        }
+    }
+
+    /**
+     * MockLogging.
+     * This class has super cow powers and can mock out it's own debug logging.
+     */
+    private mockLogging(debug = false) {
+        def mockLogger = {}
+        if(debug) {
+            mockLogger = {msg ->
+                    println "${super.getClass()} - DEBUG: $msg"
+            }
+        }
+        super.metaClass.log = [debug: mockLogger]
+        log.debug "Internal mockLogger configured."
+    }
+
+    /**
+     * IsLogicalBuilder.
+     * Determine if a method is a logicalBuilder.
+     */
+    private isLogicalBuilder(String methodName) {
+        logicalBuilders.find{ it.value == methodName.toLowerCase()} ? true:false
+    }
+
+    /**
+     * IsWhereClauseBuilder.
+     * Determine if a method is a where clause builder.
+     */
+    private isWhereClauseBuilder(String methodName) {
+        methodName = methodName.toLowerCase()
+        if(methodName == 'where' || isLogicalBuilder(methodName))
+            return true
+        else
+            return false
+    }
+
+    /**
+     * GetQuery.
+     * Assemble and return the query in a format that can be directly executed.
+     * E.g: executeQuery(q.query, q.namedParams, q.paginateParams).
+     */
+    def getQuery() {
+        clauses.collect { clause ->
+            switch (clause.key.toLowerCase()) {
+                case 'select':
+                    clause.key + ' ' + clause.value.join(', ')
+                    break
+                case 'set':
+                    clause.key + ' ' + clause.value.join(', ')
+                    break
+                case 'where':
+                    buildWhereClause()
+                    break
+                case 'order':
+                    clause.key + ' ' + clause.value.join(', ')
+                    break
+                case 'group':
+                    clause.key + ' ' + clause.value.join(', ')
+                    break
+                default:
+                    clause.key + ' ' + clause.value.join(' ')
+            }
+        }.join(' ')
+    } // getQuery()
+
+    /**
+     * GetPrintFormattedQuery.
+     * Assemble and return the query in a format that can be more easily printed and read by a person.
+     * E.g: println q.printFormattedQuery or when displayed in a report.
+     */
+    def getPrintFormattedQuery() {
+        clauses.collect { clause ->
+            switch (clause.key.toLowerCase()) {
+                case 'select':
+                    clause.key + ' ' + clause.value.join(', \n\t')
+                    break
+                case 'set':
+                    clause.key + ' ' + clause.value.join(', \n\t')
+                    break
+                case 'where':
+                    buildWhereClause(true)
+                    break
+                case 'order':
+                    clause.key + ' ' + clause.value.join(', \n\t')
+                    break
+                case 'group':
+                    clause.key + ' ' + clause.value.join(', \n\t')
+                    break
+                default:
+                    clause.key + ' ' + clause.value.join(' \n\t')
+            }
+        }.join(' \n')
+    } // getPrintFormattedQuery()
+
+} // end class
Index: unk/test/unit/HqlBuilderTests.groovy
===================================================================
--- /trunk/test/unit/HqlBuilderTests.groovy	(revision 642)
+++ 	(revision )
@@ -1,475 +1,0 @@
-/* Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import org.apache.commons.logging.LogFactory
-
-/**
- * Unit tests for HqlBuilder class.
- * GroovyTestCase is used so that class does not depend on Grails as it may be useful outside of Grails.
- *
- * @author Gavin Kromhout
- * @version DraftB
- *
- */
-public class HqlBuilderTests extends GroovyTestCase {
-
-    def n = '\n'
-    def t = '\t'
-    def savedMetaClass
-
-    protected void setUp() {
-        super.setUp()
-        savedMetaClass = HqlBuilder.metaClass
-        def emc = new ExpandoMetaClass(HqlBuilder, true, true)
-        emc.log = LogFactory.getLog(getClass())
-        emc.initialize()
-        GroovySystem.metaClassRegistry.setMetaClass(HqlBuilder, emc)
-    }
-
-    protected void tearDown() {
-        GroovySystem.metaClassRegistry.removeMetaClass(HqlBuilder)
-        GroovySystem.metaClassRegistry.setMetaClass(HqlBuilder, savedMetaClass)
-        super.tearDown()
-    }
-
-    void testBasicUsage() {
-
-        def q = new HqlBuilder().query {
-            select 'count(distinct book)'
-            from 'Book as book'
-            where 'book.id > 100'
-        }
-
-        assert q.query == 'select count(distinct book) from Book as book where book.id > 100'
-
-        q.select = 'distinct book'
-        assert q.query == 'select distinct book from Book as book where book.id > 100'
-        assert q.printFormattedQuery == 'select distinct book \nfrom Book as book \nwhere book.id > 100'
-
-    } // testBasicUsage()
-
-    void testBasicUsageAlternateForm() {
-
-        def q = new HqlBuilder()
-
-        q {
-            select 'distinct book'
-            from 'Book as book'
-            where 'book.id > 100'
-        }
-
-        assert q.query == 'select distinct book from Book as book where book.id > 100'
-
-    } // testBasicUsageAlternateForm()
-
-    void testPaginateParams() {
-
-        def q = new HqlBuilder(max: 99, offset: 11).query {
-            select 'distinct book'
-            from 'Book as book'
-            where 'book.id > 100'
-        }
-
-        assert q.max == 99
-        assert q.offset == 11
-
-    } // testPaginateParams()
-
-    void testPaginateParamsAlternateForm() {
-
-        def q = new HqlBuilder().query {
-            max = 99
-            offset = 11
-            select 'distinct book'
-            from 'Book as book'
-            where 'book.id > 100'
-        }
-
-        assert q.max == 99
-        assert q.offset == 11
-
-    } // testPaginateParamsAlternateForm()
-
-    void testNamedParams() {
-        def startId = 13
-        def endId = 23
-
-        def q = new HqlBuilder().query {
-            namedParams.startId = startId
-            select 'distinct book'
-            from 'Book as book'
-            where 'book.id > :startId'
-                and 'book.id < :endId'
-        }
-
-        q.namedParams.endId = endId
-
-        assert q.namedParams.startId == startId
-        assert q.namedParams.endId == endId
-
-    } // testNamedParams()
-
-    void testMultipleTerms() {
-
-        def q = new HqlBuilder().query {
-            select 'book.name',
-                        'type.name'
-            from 'Book as book',
-                    'left join book.group as group',
-                    'left join group.type as type'
-            where "book.name like '%Ned%'",
-                        'group = :group'
-        }
-
-        def expectedQuery = /select book.name, type.name from Book as book left join book.group as group left join group.type as type/
-        expectedQuery +=  / where book.name like '%Ned%' and group = :group/
-        assert q.query == expectedQuery
-
-        def expectedPrintFormat = /select book.name, ${n}${t}type.name/
-        expectedPrintFormat += / ${n}from Book as book ${n}${t}left join book.group as group ${n}${t}left join group.type as type/
-        expectedPrintFormat += / ${n}where book.name like '%Ned%' ${n}${t}and group = :group/
-        assert q.printFormattedQuery == expectedPrintFormat
-
-    } // testMultipleTerms()
-
-    void testMultipleTermsAlternateForm() {
-
-        def q = new HqlBuilder().query {
-            select 'book.name' // Create clause and append arg to clause's term list.
-            select 'type.name' // Method arg is appended to existing clause's term list.
-            from 'Book as book'
-            left 'join book.group as group'
-            left 'left join group.type as type' // 'left join' has to be repeated since left is an existing clause.
-            where(/book.name like '%Ned%'/) // Slashy string literals have to be protected when calling a method.
-            where 'group = :group'
-        }
-
-        def expectedQuery = /select book.name, type.name from Book as book left join book.group as group left join group.type as type/
-        expectedQuery +=  / where book.name like '%Ned%' and group = :group/
-        assert q.query == expectedQuery
-
-        def expectedPrintFormat = /select book.name, ${n}${t}type.name/
-        expectedPrintFormat += / ${n}from Book as book ${n}left join book.group as group ${n}${t}left join group.type as type/
-        expectedPrintFormat += / ${n}where book.name like '%Ned%' ${n}${t}and group = :group/
-        assert q.printFormattedQuery == expectedPrintFormat
-
-    } // testMultipleTermsAlternateForm()
-
-    void testPlaceHolder() {
-
-        def q = new HqlBuilder().query {
-            select 'distinct book'
-            from 'Book as book'
-            where  // Place holder as propertyMissing call.
-            order 'by book.name asc'
-        }
-
-        // Assign to place holder which is in the middle of the query string.
-        q.where = /book.name like '%Ned%'/
-
-        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
-
-    } // testPlaceHolder()
-
-    void testPlaceHolderAlternateForm() {
-
-        def q = new HqlBuilder().query {
-            select 'distinct book'
-            from 'Book as book'
-            where '' // Place holder as method call, tests for nulls when also using append method call bellow.
-            order 'by book.name asc'
-        }
-
-        // Append to place holder which is in the middle of the query string.
-        q.where(/book.name like '%Ned%'/)
-
-        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
-
-    } // testPlaceHolderAlternateForm()
-
-    void testClauseRemoval() {
-
-        def q = new HqlBuilder().query {
-            select 'count(distinct book)'
-            from 'Book as book'
-            where = /book.name like '%Ned%'/  // Slashy string literals don't need protecting when assigning.
-            order 'by book.name asc'
-        }
-
-        q.order = null // Remove clause.
-        assert q.query == /select count(distinct book) from Book as book where book.name like '%Ned%'/
-
-    } // testClauseRemoval()
-
-    void testClauseRemovalAlternateForm() {
-
-        def q = new HqlBuilder().query {
-            select 'count(distinct book)'
-            from 'Book as book'
-            where = /book.name like '%Ned%'/  // Slashy string literals don't need protecting when assigning.
-            order 'by book.name asc'
-        }
-
-        q.removeClause('order') // Remove clause, alternate form.
-        assert q.query == /select count(distinct book) from Book as book where book.name like '%Ned%'/
-
-    } // testClauseRemovalAlternateForm()
-
-    void testLogicalBuilder() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where "book.name like '%Ned%'"
-                or "book.onSpecial = true"
-        }
-
-        assert q.query == /from Book as book where book.name like '%Ned%' or book.onSpecial = true/
-
-    } // testLogicalBuilder()
-
-    void testLogicalBuilderNesting() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where "book.name like '%Ned%'"
-                or {
-                    where "book.onSpecial = true"
-                    and 'book.inStock = true'
-                }
-        }
-
-        assert q.query == /from Book as book where book.name like '%Ned%' or ( book.onSpecial = true and book.inStock = true )/
-
-    } // testLogicalBuilderNesting()
-
-    void testLogicalBuilderNestingLoop() {
-        def range = 1..2
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where 'book.inStock = true'
-                and {
-                    range.each {
-                        or "book.id = $it"
-                    }
-                }
-        }
-
-        assert q.query == /from Book as book where book.inStock = true and ( book.id = 1 or book.id = 2 )/
-
-    } // testLogicalBuilderNestingLoop()
-
-    void testWhereClosure() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where {
-                and 'book.id = 1'
-            }
-        }
-
-        // Only 1 expression so no brackets.
-        assert q.query == /from Book as book where book.id = 1/
-
-    } // testWhereClosure()
-
-    void testWhereClosureAlternate() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-        }
-
-        q.where {
-            and 'book.id = 1',
-                    'book.inStock = true'
-        }
-
-        // More than 1 expression so brackets are included.
-        assert q.query == /from Book as book where ( book.id = 1 and book.inStock = true )/
-
-    } // testWhereClosureAlternate()
-
-// This is very likely to be a common usage error as it may seem like a natural way to write the where clause.
-// Is it possible to intercept the String & GString constructors just inside the closure and call where 'book.id = 1'?
-// Perhaps by internally creating a new Closure and using something like this:
-// http://groovy.codehaus.org/JN3515-Interception ???
-// Or is it possible to examine each statment of a closure?
-    void testWhereClosureWithNewString() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where {
-                'book.id = 1' // This statement is missing a method call and hence will simply be excluded.
-                and 'book.inStock = true'
-            }
-        }
-
-        // Would be nice if the first case was true.
-        assertFalse q.query == /from Book as book where ( book.id = 1 and book.inStock = true )/
-        assert q.query == /from Book as book where book.inStock = true/
-
-    } // testSelectWhereClosureWithNewString()
-
-    void testWithConditionals() {
-        def y = true
-        def n = false
-
-        def q = new HqlBuilder().query {
-            select 'distinct book'
-            from 'Book as book'
-            if(y)
-                where(/book.name like '%Ned%'/)
-            if(n)
-                order ''
-            else
-                order 'by book.name asc'
-        }
-
-        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
-
-    } // testWithConditionals()
-
-    void testSelectWithLooping() {
-        def selections = ['id', 'name', 'description']
-
-        def q = new HqlBuilder().query {
-            for(s in selections) {
-                select "book.${s}"
-            }
-            from 'Book as book'
-        }
-
-        assert q.query == /select book.id, book.name, book.description from Book as book/
-
-    } // testSelectWithLooping()
-
-    void testWhereWithLooping() {
-        def range = 1..3
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where 'book.inStock = true'
-                range.each {
-                    or "book.id = $it"
-                }
-        }
-
-        assert q.query == /from Book as book where book.inStock = true or book.id = 1 or book.id = 2 or book.id = 3/
-
-    } // testWhereWithLooping()
-
-    void testWhereDirectlyWithLoops() {
-        def range = 1..3
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where
-                range.each {
-                    or "book.id = $it"
-                }
-        }
-
-        assert q.query == /from Book as book where book.id = 1 or book.id = 2 or book.id = 3/
-
-    } // testWhereDirectlyWithLoops()
-
-    void testWhereNodeWithLoops() {
-        def range = 1..3
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where {
-                range.each {
-                    or "book.id = $it"
-                }
-            }
-        }
-
-        assert q.query == /from Book as book where ( book.id = 1 or book.id = 2 or book.id = 3 )/
-
-    } // testWhereNodeWithLoops()
-
-    void testOrderByMultipleTerms() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where 'book.id > 100'
-            order 'by book.name asc',
-                        'book.id desc'
-        }
-
-        assert q.query == 'from Book as book where book.id > 100 order by book.name asc, book.id desc'
-
-        assert q.printFormattedQuery == 'from Book as book \nwhere book.id > 100 \norder by book.name asc, \n\tbook.id desc'
-
-    } // testOrderByMultipleTerms()
-
-    void testGroupByMultipleTerms() {
-
-        def q = new HqlBuilder().query {
-            from 'Book as book'
-            where 'book.id > 100'
-            group 'by book.name asc',
-                        'book.id desc'
-        }
-
-        assert q.query == 'from Book as book where book.id > 100 group by book.name asc, book.id desc'
-
-        assert q.printFormattedQuery == 'from Book as book \nwhere book.id > 100 \ngroup by book.name asc, \n\tbook.id desc'
-
-    } // testGroupByMultipleTerms()
-
-    void testUpdate() {
-        def q = new HqlBuilder().query {
-            update 'Book b'
-            set 'b.name = :newName',
-                'b.inStock = true'
-            where 'b.name = :oldName'
-        }
-
-        assert q.query == 'update Book b set b.name = :newName, b.inStock = true where b.name = :oldName'
-
-        assert q.printFormattedQuery == 'update Book b \nset b.name = :newName, \n\tb.inStock = true \nwhere b.name = :oldName'
-
-    } // testUpdate()
-
-    void testDelete() {
-        def q = new HqlBuilder().query {
-            delete 'Book b'
-            where 'b.name = :oldName'
-        }
-
-        assert q.query == 'delete Book b where b.name = :oldName'
-
-        assert q.printFormattedQuery == 'delete Book b \nwhere b.name = :oldName'
-
-    } // testDelete()
-
-    void testInsertInto() {
-        def q = new HqlBuilder(debug:true).query {
-            insert 'into ArchiveBook (id, name)'
-            select 'b.id',
-                        'b.name'
-            from 'Book b'
-            where 'b.name = :oldName'
-        }
-
-        assert q.query == 'insert into ArchiveBook (id, name) select b.id, b.name from Book b where b.name = :oldName'
-
-        assert q.printFormattedQuery == 'insert into ArchiveBook (id, name) \nselect b.id, \n\tb.name \nfrom Book b \nwhere b.name = :oldName'
-
-    } // testInsertInto()
-
-} // end class
Index: /trunk/test/unit/net/kromhouts/HqlBuilderTests.groovy
===================================================================
--- /trunk/test/unit/net/kromhouts/HqlBuilderTests.groovy	(revision 643)
+++ /trunk/test/unit/net/kromhouts/HqlBuilderTests.groovy	(revision 643)
@@ -0,0 +1,477 @@
+/* Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.kromhouts
+
+import org.apache.commons.logging.LogFactory
+
+/**
+ * Unit tests for HqlBuilder class.
+ * GroovyTestCase is used so that class does not depend on Grails as it may be useful outside of Grails.
+ *
+ * @author Gavin Kromhout
+ * @version DraftB
+ *
+ */
+public class HqlBuilderTests extends GroovyTestCase {
+
+    def n = '\n'
+    def t = '\t'
+    def savedMetaClass
+
+    protected void setUp() {
+        super.setUp()
+        savedMetaClass = HqlBuilder.metaClass
+        def emc = new ExpandoMetaClass(HqlBuilder, true, true)
+        emc.log = LogFactory.getLog(getClass())
+        emc.initialize()
+        GroovySystem.metaClassRegistry.setMetaClass(HqlBuilder, emc)
+    }
+
+    protected void tearDown() {
+        GroovySystem.metaClassRegistry.removeMetaClass(HqlBuilder)
+        GroovySystem.metaClassRegistry.setMetaClass(HqlBuilder, savedMetaClass)
+        super.tearDown()
+    }
+
+    void testBasicUsage() {
+
+        def q = new HqlBuilder().query {
+            select 'count(distinct book)'
+            from 'Book as book'
+            where 'book.id > 100'
+        }
+
+        assert q.query == 'select count(distinct book) from Book as book where book.id > 100'
+
+        q.select = 'distinct book'
+        assert q.query == 'select distinct book from Book as book where book.id > 100'
+        assert q.printFormattedQuery == 'select distinct book \nfrom Book as book \nwhere book.id > 100'
+
+    } // testBasicUsage()
+
+    void testBasicUsageAlternateForm() {
+
+        def q = new HqlBuilder()
+
+        q {
+            select 'distinct book'
+            from 'Book as book'
+            where 'book.id > 100'
+        }
+
+        assert q.query == 'select distinct book from Book as book where book.id > 100'
+
+    } // testBasicUsageAlternateForm()
+
+    void testPaginateParams() {
+
+        def q = new HqlBuilder(max: 99, offset: 11).query {
+            select 'distinct book'
+            from 'Book as book'
+            where 'book.id > 100'
+        }
+
+        assert q.max == 99
+        assert q.offset == 11
+
+    } // testPaginateParams()
+
+    void testPaginateParamsAlternateForm() {
+
+        def q = new HqlBuilder().query {
+            max = 99
+            offset = 11
+            select 'distinct book'
+            from 'Book as book'
+            where 'book.id > 100'
+        }
+
+        assert q.max == 99
+        assert q.offset == 11
+
+    } // testPaginateParamsAlternateForm()
+
+    void testNamedParams() {
+        def startId = 13
+        def endId = 23
+
+        def q = new HqlBuilder().query {
+            namedParams.startId = startId
+            select 'distinct book'
+            from 'Book as book'
+            where 'book.id > :startId'
+                and 'book.id < :endId'
+        }
+
+        q.namedParams.endId = endId
+
+        assert q.namedParams.startId == startId
+        assert q.namedParams.endId == endId
+
+    } // testNamedParams()
+
+    void testMultipleTerms() {
+
+        def q = new HqlBuilder().query {
+            select 'book.name',
+                        'type.name'
+            from 'Book as book',
+                    'left join book.group as group',
+                    'left join group.type as type'
+            where "book.name like '%Ned%'",
+                        'group = :group'
+        }
+
+        def expectedQuery = /select book.name, type.name from Book as book left join book.group as group left join group.type as type/
+        expectedQuery +=  / where book.name like '%Ned%' and group = :group/
+        assert q.query == expectedQuery
+
+        def expectedPrintFormat = /select book.name, ${n}${t}type.name/
+        expectedPrintFormat += / ${n}from Book as book ${n}${t}left join book.group as group ${n}${t}left join group.type as type/
+        expectedPrintFormat += / ${n}where book.name like '%Ned%' ${n}${t}and group = :group/
+        assert q.printFormattedQuery == expectedPrintFormat
+
+    } // testMultipleTerms()
+
+    void testMultipleTermsAlternateForm() {
+
+        def q = new HqlBuilder().query {
+            select 'book.name' // Create clause and append arg to clause's term list.
+            select 'type.name' // Method arg is appended to existing clause's term list.
+            from 'Book as book'
+            left 'join book.group as group'
+            left 'left join group.type as type' // 'left join' has to be repeated since left is an existing clause.
+            where(/book.name like '%Ned%'/) // Slashy string literals have to be protected when calling a method.
+            where 'group = :group'
+        }
+
+        def expectedQuery = /select book.name, type.name from Book as book left join book.group as group left join group.type as type/
+        expectedQuery +=  / where book.name like '%Ned%' and group = :group/
+        assert q.query == expectedQuery
+
+        def expectedPrintFormat = /select book.name, ${n}${t}type.name/
+        expectedPrintFormat += / ${n}from Book as book ${n}left join book.group as group ${n}${t}left join group.type as type/
+        expectedPrintFormat += / ${n}where book.name like '%Ned%' ${n}${t}and group = :group/
+        assert q.printFormattedQuery == expectedPrintFormat
+
+    } // testMultipleTermsAlternateForm()
+
+    void testPlaceHolder() {
+
+        def q = new HqlBuilder().query {
+            select 'distinct book'
+            from 'Book as book'
+            where  // Place holder as propertyMissing call.
+            order 'by book.name asc'
+        }
+
+        // Assign to place holder which is in the middle of the query string.
+        q.where = /book.name like '%Ned%'/
+
+        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
+
+    } // testPlaceHolder()
+
+    void testPlaceHolderAlternateForm() {
+
+        def q = new HqlBuilder().query {
+            select 'distinct book'
+            from 'Book as book'
+            where '' // Place holder as method call, tests for nulls when also using append method call bellow.
+            order 'by book.name asc'
+        }
+
+        // Append to place holder which is in the middle of the query string.
+        q.where(/book.name like '%Ned%'/)
+
+        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
+
+    } // testPlaceHolderAlternateForm()
+
+    void testClauseRemoval() {
+
+        def q = new HqlBuilder().query {
+            select 'count(distinct book)'
+            from 'Book as book'
+            where = /book.name like '%Ned%'/  // Slashy string literals don't need protecting when assigning.
+            order 'by book.name asc'
+        }
+
+        q.order = null // Remove clause.
+        assert q.query == /select count(distinct book) from Book as book where book.name like '%Ned%'/
+
+    } // testClauseRemoval()
+
+    void testClauseRemovalAlternateForm() {
+
+        def q = new HqlBuilder().query {
+            select 'count(distinct book)'
+            from 'Book as book'
+            where = /book.name like '%Ned%'/  // Slashy string literals don't need protecting when assigning.
+            order 'by book.name asc'
+        }
+
+        q.removeClause('order') // Remove clause, alternate form.
+        assert q.query == /select count(distinct book) from Book as book where book.name like '%Ned%'/
+
+    } // testClauseRemovalAlternateForm()
+
+    void testLogicalBuilder() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where "book.name like '%Ned%'"
+                or "book.onSpecial = true"
+        }
+
+        assert q.query == /from Book as book where book.name like '%Ned%' or book.onSpecial = true/
+
+    } // testLogicalBuilder()
+
+    void testLogicalBuilderNesting() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where "book.name like '%Ned%'"
+                or {
+                    where "book.onSpecial = true"
+                    and 'book.inStock = true'
+                }
+        }
+
+        assert q.query == /from Book as book where book.name like '%Ned%' or ( book.onSpecial = true and book.inStock = true )/
+
+    } // testLogicalBuilderNesting()
+
+    void testLogicalBuilderNestingLoop() {
+        def range = 1..2
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where 'book.inStock = true'
+                and {
+                    range.each {
+                        or "book.id = $it"
+                    }
+                }
+        }
+
+        assert q.query == /from Book as book where book.inStock = true and ( book.id = 1 or book.id = 2 )/
+
+    } // testLogicalBuilderNestingLoop()
+
+    void testWhereClosure() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where {
+                and 'book.id = 1'
+            }
+        }
+
+        // Only 1 expression so no brackets.
+        assert q.query == /from Book as book where book.id = 1/
+
+    } // testWhereClosure()
+
+    void testWhereClosureAlternate() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+        }
+
+        q.where {
+            and 'book.id = 1',
+                    'book.inStock = true'
+        }
+
+        // More than 1 expression so brackets are included.
+        assert q.query == /from Book as book where ( book.id = 1 and book.inStock = true )/
+
+    } // testWhereClosureAlternate()
+
+// This is very likely to be a common usage error as it may seem like a natural way to write the where clause.
+// Is it possible to intercept the String & GString constructors just inside the closure and call where 'book.id = 1'?
+// Perhaps by internally creating a new Closure and using something like this:
+// http://groovy.codehaus.org/JN3515-Interception ???
+// Or is it possible to examine each statment of a closure?
+    void testWhereClosureWithNewString() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where {
+                'book.id = 1' // This statement is missing a method call and hence will simply be excluded.
+                and 'book.inStock = true'
+            }
+        }
+
+        // Would be nice if the first case was true.
+        assertFalse q.query == /from Book as book where ( book.id = 1 and book.inStock = true )/
+        assert q.query == /from Book as book where book.inStock = true/
+
+    } // testSelectWhereClosureWithNewString()
+
+    void testWithConditionals() {
+        def y = true
+        def n = false
+
+        def q = new HqlBuilder().query {
+            select 'distinct book'
+            from 'Book as book'
+            if(y)
+                where(/book.name like '%Ned%'/)
+            if(n)
+                order ''
+            else
+                order 'by book.name asc'
+        }
+
+        assert q.query == /select distinct book from Book as book where book.name like '%Ned%' order by book.name asc/
+
+    } // testWithConditionals()
+
+    void testSelectWithLooping() {
+        def selections = ['id', 'name', 'description']
+
+        def q = new HqlBuilder().query {
+            for(s in selections) {
+                select "book.${s}"
+            }
+            from 'Book as book'
+        }
+
+        assert q.query == /select book.id, book.name, book.description from Book as book/
+
+    } // testSelectWithLooping()
+
+    void testWhereWithLooping() {
+        def range = 1..3
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where 'book.inStock = true'
+                range.each {
+                    or "book.id = $it"
+                }
+        }
+
+        assert q.query == /from Book as book where book.inStock = true or book.id = 1 or book.id = 2 or book.id = 3/
+
+    } // testWhereWithLooping()
+
+    void testWhereDirectlyWithLoops() {
+        def range = 1..3
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where
+                range.each {
+                    or "book.id = $it"
+                }
+        }
+
+        assert q.query == /from Book as book where book.id = 1 or book.id = 2 or book.id = 3/
+
+    } // testWhereDirectlyWithLoops()
+
+    void testWhereNodeWithLoops() {
+        def range = 1..3
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where {
+                range.each {
+                    or "book.id = $it"
+                }
+            }
+        }
+
+        assert q.query == /from Book as book where ( book.id = 1 or book.id = 2 or book.id = 3 )/
+
+    } // testWhereNodeWithLoops()
+
+    void testOrderByMultipleTerms() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where 'book.id > 100'
+            order 'by book.name asc',
+                        'book.id desc'
+        }
+
+        assert q.query == 'from Book as book where book.id > 100 order by book.name asc, book.id desc'
+
+        assert q.printFormattedQuery == 'from Book as book \nwhere book.id > 100 \norder by book.name asc, \n\tbook.id desc'
+
+    } // testOrderByMultipleTerms()
+
+    void testGroupByMultipleTerms() {
+
+        def q = new HqlBuilder().query {
+            from 'Book as book'
+            where 'book.id > 100'
+            group 'by book.name asc',
+                        'book.id desc'
+        }
+
+        assert q.query == 'from Book as book where book.id > 100 group by book.name asc, book.id desc'
+
+        assert q.printFormattedQuery == 'from Book as book \nwhere book.id > 100 \ngroup by book.name asc, \n\tbook.id desc'
+
+    } // testGroupByMultipleTerms()
+
+    void testUpdate() {
+        def q = new HqlBuilder().query {
+            update 'Book b'
+            set 'b.name = :newName',
+                'b.inStock = true'
+            where 'b.name = :oldName'
+        }
+
+        assert q.query == 'update Book b set b.name = :newName, b.inStock = true where b.name = :oldName'
+
+        assert q.printFormattedQuery == 'update Book b \nset b.name = :newName, \n\tb.inStock = true \nwhere b.name = :oldName'
+
+    } // testUpdate()
+
+    void testDelete() {
+        def q = new HqlBuilder().query {
+            delete 'Book b'
+            where 'b.name = :oldName'
+        }
+
+        assert q.query == 'delete Book b where b.name = :oldName'
+
+        assert q.printFormattedQuery == 'delete Book b \nwhere b.name = :oldName'
+
+    } // testDelete()
+
+    void testInsertInto() {
+        def q = new HqlBuilder(debug:true).query {
+            insert 'into ArchiveBook (id, name)'
+            select 'b.id',
+                        'b.name'
+            from 'Book b'
+            where 'b.name = :oldName'
+        }
+
+        assert q.query == 'insert into ArchiveBook (id, name) select b.id, b.name from Book b where b.name = :oldName'
+
+        assert q.printFormattedQuery == 'insert into ArchiveBook (id, name) \nselect b.id, \n\tb.name \nfrom Book b \nwhere b.name = :oldName'
+
+    } // testInsertInto()
+
+} // end class
