Permalink
Browse files

Merge branch 'master' of github.com:grails/grails-core

  • Loading branch information...
2 parents a2ff2d2 + a745961 commit e4c02b6ac9a0df82b5c9cd8f6dd94d50e6f68826 @graemerocher graemerocher committed Sep 13, 2012
@@ -16,6 +16,7 @@
package grails.orm;
import grails.gorm.DetachedCriteria;
+import grails.util.CollectionUtils;
import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovySystem;
@@ -42,6 +43,7 @@
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
+import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
@@ -231,6 +233,40 @@ protected void addProjectionToList(Projection propertyProjection, String alias)
}
}
+ /**
+ * Adds a sql projection to the criteria
+ *
+ * @param sql SQL projecting a single value
+ * @param columnAlias column alias for the projected value
+ * @param type the type of the projected value
+ */
+ protected void sqlProjection(String sql, String columnAlias, Type type) {
+ sqlProjection(sql, CollectionUtils.newList(columnAlias), CollectionUtils.newList(type));
+ }
+
+ /**
+ * Adds a sql projection to the criteria
+ *
+ * @param sql SQL projecting
+ * @param columnAliases List of column aliases for the projected values
+ * @param type List of types for the projected values
+ */
+ protected void sqlProjection(String sql, List<String> columnAliases, List<Type> types) {
+ projectionList.add(Projections.sqlProjection(sql, columnAliases.toArray(new String[columnAliases.size()]), types.toArray(new Type[types.size()])));
+ }
+
+ /**
+ * Adds a sql projection to the criteria
+ *
+ * @param sql SQL projecting
+ * @param groupBy group by clause
+ * @param columnAliases List of column aliases for the projected values
+ * @param type List of types for the projected values
+ */
+ protected void sqlGroupProjection(String sql, String groupBy, List<String> columnAliases, List<Type> types) {
+ projectionList.add(Projections.sqlGroupProjection(sql, groupBy, columnAliases.toArray(new String[columnAliases.size()]), types.toArray(new Type[types.size()])));
+ }
+
/**
* A projection that selects a distince property name
* @param propertyName The property name
@@ -268,10 +268,12 @@ class FormTagLib {
void outputAttributes(attrs, writer, boolean useNameAsIdIfIdDoesNotExist = false) {
attrs.remove('tagName') // Just in case one is left
attrs.each { k, v ->
- writer << k
- writer << '="'
- writer << v.encodeAsHTML()
- writer << '" '
+ if(v != null) {
+ writer << k
+ writer << '="'
+ writer << v.encodeAsHTML()
+ writer << '" '
+ }
}
if (useNameAsIdIfIdDoesNotExist) {
outputNameAsIdIfIdDoesNotExist(attrs, writer)
@@ -391,10 +391,12 @@ abstract class AbstractGrailsTagTests extends GroovyTestCase {
try {
request.setAttribute(RequestConstants.PAGE, page)
+ request.setAttribute(GrailsPageFilter.GSP_SITEMESH_PAGE, new GSPSitemeshPage())
return applyTemplate(layout, params,null, "/layouts/test_"+System.currentTimeMillis())
}
finally {
request.removeAttribute(RequestConstants.PAGE)
+ request.removeAttribute(GrailsPageFilter.GSP_SITEMESH_PAGE)
}
}
/**
@@ -5,6 +5,10 @@ import grails.persistence.Entity
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler
import org.codehaus.groovy.grails.commons.GrailsDomainClass
+import org.hibernate.Hibernate
+import org.hibernate.criterion.Projections
+import org.hibernate.type.StandardBasicTypes
+import org.hibernate.type.Type
/**
* @author Graeme Rocher
@@ -13,7 +17,7 @@ import org.codehaus.groovy.grails.commons.GrailsDomainClass
class HibernateCriteriaBuilderTests extends AbstractGrailsHibernateTests {
protected getDomainClasses() {
- [CriteriaBuilderTestClass, CriteriaBuilderTestClass2, OneAuthorPublisher, OneBookAuthor, Book]
+ [CriteriaBuilderTestClass, CriteriaBuilderTestClass2, OneAuthorPublisher, OneBookAuthor, Book, Box]
}
List retrieveListOfNames() { ['bart'] }
@@ -61,6 +65,63 @@ class HibernateCriteriaBuilderTests extends AbstractGrailsHibernateTests {
}
}
+ void testSqlProjection() {
+ def domainClass = ga.getDomainClass(Box.name).clazz
+
+ assertNotNull(domainClass)
+
+ def obj = domainClass.newInstance()
+ obj.setProperty("width", 2)
+ obj.setProperty("height", 7)
+ obj.save()
+
+ obj = domainClass.newInstance()
+ obj.setProperty("width", 2)
+ obj.setProperty("height", 8)
+ obj.save()
+
+ obj = domainClass.newInstance()
+ obj.setProperty("width", 2)
+ obj.setProperty("height", 9)
+ obj.save()
+
+ obj = domainClass.newInstance()
+ obj.setProperty("width", 4)
+ obj.setProperty("height", 9)
+ obj.save()
+
+ def results = Box.withCriteria {
+ projections {
+ sqlProjection 'sum(width * height) as totalArea', 'totalArea', StandardBasicTypes.INTEGER
+ }
+ }
+
+ assert 1 == results?.size()
+ assert 84 == results[0]
+
+ results = Box.withCriteria {
+ projections {
+ sqlProjection '(width + height) as perimeter, (width * height) as area', ['perimeter', 'area'], [StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER]
+ }
+ }
+
+ assert 4 == results?.size()
+ assert [9, 14] == results[0]
+ assert [10, 16] == results[1]
+ assert [11, 18] == results[2]
+ assert [13, 36] == results[3]
+
+ results = Box.withCriteria {
+ projections {
+ sqlGroupProjection 'width, sum(height) as combinedHeightsForThisWidth', 'width', ['width', 'combinedHeightsForThisWidth'], [StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER]
+ }
+ }
+
+ assert 2 == results?.size()
+ assert [2, 24] == results[0]
+ assert [4, 9] == results[1]
+ }
+
void testSqlRestriction() {
createDomainData()
@@ -1726,3 +1787,9 @@ class CriteriaBuilderTestClass2 {
String firstName
Date dateCreated
}
+
+@Entity
+class Box {
+ int width
+ int height
+}
@@ -2,6 +2,12 @@ package org.codehaus.groovy.grails.web.sitemesh
import org.codehaus.groovy.grails.support.MockStringResourceLoader
import org.codehaus.groovy.grails.web.taglib.AbstractGrailsTagTests
+import org.springframework.mock.web.MockServletConfig
+
+import com.opensymphony.module.sitemesh.Config
+import com.opensymphony.module.sitemesh.Decorator
+import com.opensymphony.module.sitemesh.DecoratorMapper
+import com.opensymphony.module.sitemesh.factory.BaseFactory
/**
* Tests the sitemesh capturing and rendering tags end-to-end
@@ -77,6 +83,71 @@ class FullSitemeshLifeCycleTests extends AbstractGrailsTagTests {
''', result
}
+ static class DummySiteMeshFactory extends BaseFactory {
+ public DummySiteMeshFactory(Config config) {
+ super(config)
+ }
+
+ @Override
+ public void refresh() {
+ }
+ }
+
+ def configureSitemesh() {
+ def mockServletConfig = new MockServletConfig()
+ def siteMeshConfig = new Config(mockServletConfig)
+ def siteMeshFactory = new DummySiteMeshFactory(siteMeshConfig)
+ def decorator = {name -> [getPage: {-> "/layout/${name}.gsp".toString()}] as Decorator }
+ siteMeshFactory.decoratorMapper = [getNamedDecorator: {request, name -> decorator(name)}] as DecoratorMapper
+ FactoryHolder.factory = siteMeshFactory
+ }
+
+ void testMultipleLevelsOfLayouts() {
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layout/dialog.gsp', '''<html>
+ <head><g:layoutHead /><title>Dialog - <g:layoutTitle /></title></head>
+ <body onload="${g.pageProperty(name:'body.onload')}"><div id="dialog"><g:layoutBody /></div></body>
+</html>''')
+ resourceLoader.registerMockResource('/layout/base.gsp', '''<html>
+ <head><g:layoutHead /><title>Base - <g:layoutTitle /></title></head>
+ <body onload="${g.pageProperty(name:'body.onload')}"><div id="base"><g:layoutBody /></div></body>
+</html>''')
+ appCtx.groovyPageLocator.addResourceLoader resourceLoader
+
+ configureSitemesh()
+
+ def template = '''
+<g:applyLayout name="base"><g:applyLayout name="dialog">
+<html>
+ <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>This is the title</title></head>
+ <body onload="test();">body text</body>
+</html>
+</g:applyLayout></g:applyLayout>
+'''
+ request.setAttribute(GrailsPageFilter.GSP_SITEMESH_PAGE, new GSPSitemeshPage())
+ assertOutputEquals '''
+<html>
+ <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Base - Dialog - This is the title</title></head>
+ <body onload="test();"><div id="base"><div id="dialog">body text</div></div></body>
+</html>
+''', template
+
+ def layout = '''
+<html>
+ <head><title>Decorated <g:layoutTitle default="defaultTitle"/></title><g:layoutHead /></head>
+ <body><h1>Hello</h1><g:layoutBody /></body>
+</html>
+'''
+ def result = applyLayout(layout, template)
+
+ assertEquals '''
+<html>
+ <head><title>Decorated Base - Dialog - This is the title</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
+ <body><h1>Hello</h1><div id="base"><div id="dialog">body text</div></div></body>
+</html>
+''', result
+ }
+
void testParameters() {
def template = '''
<html>
@@ -327,6 +327,20 @@ class FormTagLib3Tests extends AbstractGrailsTagTests {
assertEquals 'Tag [select] is missing required attribute [name]', message
}
}
+
+ void testSelectTagWithNullAttribute() {
+ final StringWriter sw = new StringWriter()
+ final PrintWriter pw = new PrintWriter(sw)
+
+ withTag("select", pw) { tag ->
+ assertNotNull tag
+ tag([name: 'mySelectTag', from: [], errors: null])
+ }
+
+ println sw.toString()
+
+ assertTrue sw.toString().startsWith('<select name="mySelectTag" id="mySelectTag" >')
+ }
void testDatePickerWithYearsAndRelativeYearsAttribute() {
final StringWriter sw = new StringWriter()

0 comments on commit e4c02b6

Please sign in to comment.