Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GORM should not check Entity's dirtiness on formula and transient type of properties #1697

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
GORM should not check Entity's dirtiness on formula and transient typ…
…e of properties
  • Loading branch information
aulea committed Nov 24, 2022
commit 022ae028fb4939e40c324abc58af1a5da11f1de6
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())
}
}