Skip to content

Commit

Permalink
GORM should not check Entity's dirtiness on formula and transient typ…
Browse files Browse the repository at this point in the history
…e of properties (#1697)
  • Loading branch information
aulea committed Feb 27, 2023
1 parent ade4210 commit 26f98f9
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import grails.gorm.annotation.Entity
import grails.gorm.tests.GormDatastoreSpec

class DirtyCheckingSpec extends GormDatastoreSpec {

void "When marking whole class dirty, then derived and transient properties are still not dirty"() {
when:
TestBook book = new TestBook()
book.title = "Test"
and: "mark class as not dirty - to clear previous dirty tracking"
book.trackChanges()

then:
!book.hasChanged()

when: "Mark whole class as dirty"
book.markDirty()

then: "whole class is dirty"
book.hasChanged()

and: "The formula and transient properties are not dirty"
!book.hasChanged('formulaProperty')
!book.hasChanged('transientProperty')

and: "Other properties are"
book.hasChanged('id')
book.hasChanged('title')

}

void "Test that dirty tracking doesn't apply on Entity's transient properties"() {
when:
TestBook book = new TestBook()
book.title = "Test"
and: "mark class as not dirty, clear previous dirty tracking"
book.trackChanges()

then:
!book.hasChanged()

when: "update transient property"
book.transientProperty = "new transient value"

then: "class is not dirty"
!book.hasChanged()

and: "transient properties are not dirty"
!book.hasChanged('transientProperty')
}

@Override
List getDomainClasses() {
[TestBook]
}
}

@Entity
class TestBook implements Serializable {

Long id
String title

String formulaProperty

String transientProperty

static mapping = {
formulaProperty(formula: 'name || \' (formula)\'')
}

static transients = ['transientProperty']
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,24 @@ class DirtyCheckingTransformer implements CompilationUnitAware {
this.compilationUnit = compilationUnit
}

void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode, Class traitToInject = DirtyCheckable) {
// First add a local field that will store the change tracking state. The field is a simple list of property names that have changed
// the field is only added to root clauses that extend from java.lang.Object
final ClassNode changeTrackableClassNode = new ClassNode(DirtyCheckable).getPlainNodeReference()
final ClassNode changeTrackableClassNode = new ClassNode(traitToInject).getPlainNodeReference()
if (traitToInject != DirtyCheckable) {
changeTrackableClassNode.setSuperClass(new ClassNode(DirtyCheckable).getPlainNodeReference())
}
final MethodNode markDirtyMethodNode = changeTrackableClassNode.getMethod(METHOD_NAME_MARK_DIRTY, new Parameter(ClassHelper.STRING_TYPE, "propertyName"), new Parameter(ClassHelper.OBJECT_TYPE, "newValue"))


ClassNode superClass = classNode.getSuperClass()
boolean shouldWeave = superClass.equals(OBJECT_CLASS_NODE)

ClassNode dirtyCheckableTrait = ClassHelper.make(DirtyCheckable).getPlainNodeReference()

ClassNode dirtyCheckableTrait = ClassHelper.make(traitToInject).getPlainNodeReference()
if (traitToInject != DirtyCheckable) {
dirtyCheckableTrait.setSuperClass(new ClassNode(DirtyCheckable).getPlainNodeReference())
}

while(!shouldWeave) {
if(isDomainClass(superClass) || !superClass.getAnnotations(DIRTY_CHECK_CLASS_NODE).isEmpty()) {
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import org.codehaus.groovy.transform.AbstractASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.grails.datastore.gorm.GormEnhancer
import org.grails.datastore.gorm.GormEntity
import org.grails.datastore.gorm.GormEntityDirtyCheckable
import org.grails.datastore.gorm.query.GormQueryOperations
import org.grails.datastore.mapping.model.config.GormProperties
import org.grails.datastore.mapping.reflect.AstUtils
Expand Down Expand Up @@ -240,7 +241,7 @@ class GormEntityTransformation extends AbstractASTTransformation implements Comp

// now apply dirty checking behavior
def dirtyCheckTransformer = new DirtyCheckingTransformer()
dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode)
dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode, GormEntityDirtyCheckable)


// convert the methodMissing and propertyMissing implementations to $static_methodMissing and $static_propertyMissing for the static versions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2014 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:https://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 org.grails.datastore.gorm

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.grails.datastore.mapping.config.Property
import org.grails.datastore.mapping.dirty.checking.DirtyCheckable
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.PersistentProperty

import javax.annotation.Generated

/**
*
* Special trait meant only for GORM entities to override default behaviour of DirtyCheckable.
* Applied manually during GormEntity transformation
*
* @since 7.3
*/
@CompileStatic
trait GormEntityDirtyCheckable extends DirtyCheckable {

@Override
@Generated
boolean hasChanged(String propertyName) {
PersistentEntity entity = currentGormInstanceApi().persistentEntity

PersistentProperty persistentProperty = entity.getPropertyByName(propertyName)
if (!persistentProperty) {
// Not persistent property, transient. We don't track changes for transients
return false
}

Property propertyMapping = persistentProperty.getMapping().getMappedForm()
if (propertyMapping.derived) {
// Derived property cannot be changed, ex. sql formula
return false
}

return super.hasChanged(propertyName)
}

@Generated
private GormInstanceApi currentGormInstanceApi() {
(GormInstanceApi) GormEnhancer.findInstanceApi(getClass())
}
}

0 comments on commit 26f98f9

Please sign in to comment.