/* 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 statement 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().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