diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97278b58bb1..45f34fc5195 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,4 +7,7 @@ updates: open-pull-requests-limit: 10 target-branch: 7.0.x labels: - - "type: dependency upgrade" + - "type: dependency upgrade" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch", "version-update:semver-minor"] diff --git a/.github/renovate.json b/.github/renovate.json index eb49a4f21f1..12c20a61a2f 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,6 +4,10 @@ ], "labels": ["type: dependency upgrade"], "packageRules": [ + { + "matchUpdateTypes": ["major"], + "enabled": false + }, { "matchPackagePatterns": ["*"], "allowedVersions": "!/SNAPSHOT$/" diff --git a/README.md b/README.md index 07a37674798..2873b1dcf6e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ +[Grails]: http://grails.org/ +[Groovy]: http://groovy-lang.org/ +[Apache License, Version 2.0]: http://www.apache.org/licenses/LICENSE-2.0.html + [![Java CI](https://github.com/grails/grails-data-mapping/actions/workflows/gradle.yml/badge.svg)](https://github.com/grails/grails-data-mapping/actions/workflows/gradle.yml) [![Release](https://github.com/grails/grails-data-mapping/actions/workflows/release.yml/badge.svg)](https://github.com/grails/grails-data-mapping/actions/workflows/release.yml) GORM (Grails Object Mapping) === -[Grails][Grails] is a framework used to build web applications with the [Groovy][Groovy] programming language. This project provides the plumbings for the GORM API both for Hibernate and for new implementations of GORM ontop of NoSQL datastores. -[Grails]: http://grails.org/ -[Groovy]: http://groovy-lang.org/ +[Grails][Grails] is a framework used to build web applications with the [Groovy][Groovy] programming language. This project provides the plumbings for the GORM API both for Hibernate and for new implementations of GORM on top of NoSQL datastores. Getting Started @@ -14,11 +16,10 @@ Getting Started For further information see the dedicated websites: -* [Stable Version](http://gorm.grails.org/). -* [Development Version](http://gorm.grails.org/snapshot/). +* [Stable Version](http://gorm.grails.org/) +* [Development Version](http://gorm.grails.org/snapshot/) License --- Grails and Groovy are licensed under the terms of the [Apache License, Version 2.0][Apache License, Version 2.0]. -[Apache License, Version 2.0]: http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/build.gradle b/build.gradle index 89b93c8da13..2c38404e81d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,6 @@ buildscript { dependencies { classpath "io.github.gradle-nexus:publish-plugin:1.3.0" classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' - classpath "org.gradle:test-retry-gradle-plugin:1.4.1" } } @@ -88,7 +87,6 @@ subprojects { apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'java-library' - apply plugin: "org.gradle.test-retry" sourceCompatibility = 1.11 targetCompatibility = 1.11 diff --git a/gradle.properties b/gradle.properties index 6fd5b334893..62f94b573c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ title=Grails GORM projectDesc=GORM - Grails Data Access Framework projectUrl=https://gorm.grails.org/ -projectVersion=8.0.0-M1 +projectVersion=8.0.0-M2 githubSlug=grails/grails-data-mapping developers=Graeme Rocher,Jeff Brown,Burt Beckwith,James Kleeh,Puneet Behl hibernateValidatorVersion=6.2.5.Final @@ -13,7 +13,7 @@ commonsValidatorVersion=1.7 hibernateVersion=5.6.15.Final servletApiVersion=4.0.1 spockVersion=2.1-groovy-3.0 -springVersion=5.3.26 +springVersion=5.3.27 caffeineVersion=2.9.3 grailsVersion=6.0.0-M2 grailsAsyncVersion=4.0.0 diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/core/Session.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/core/Session.java index d1ff143f87f..345c9dc8bbd 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/core/Session.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/core/Session.java @@ -161,7 +161,7 @@ public interface Session extends QueryCreator { boolean contains(Object o); /** - * The flush mode, defaults to FlushModeType.AUTO + * The flush mode, defaults to FlushModeType.COMMIT * * @param flushMode The FlushModeType */ diff --git a/grails-datastore-gorm-test/src/test/groovy/org/grails/datastore/gorm/DirtyCheckingSpec.groovy b/grails-datastore-gorm-test/src/test/groovy/org/grails/datastore/gorm/DirtyCheckingSpec.groovy index e69de29bb2d..aea388f1c69 100644 --- a/grails-datastore-gorm-test/src/test/groovy/org/grails/datastore/gorm/DirtyCheckingSpec.groovy +++ b/grails-datastore-gorm-test/src/test/groovy/org/grails/datastore/gorm/DirtyCheckingSpec.groovy @@ -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'] +} \ No newline at end of file diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/DirtyCheckingTransformer.groovy b/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/DirtyCheckingTransformer.groovy index 8c4ebd6c6df..372b08ea583 100644 --- a/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/DirtyCheckingTransformer.groovy +++ b/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/DirtyCheckingTransformer.groovy @@ -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 diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy b/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy index cc7bd4e38a1..593aa35880a 100644 --- a/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy +++ b/grails-datastore-gorm/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy @@ -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 @@ -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 diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/GormEntityDirtyCheckable.groovy b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/GormEntityDirtyCheckable.groovy new file mode 100644 index 00000000000..5c34a964482 --- /dev/null +++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/GormEntityDirtyCheckable.groovy @@ -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://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()) + } +} diff --git a/settings.gradle b/settings.gradle index 65cf48a3d23..8bb133b5589 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ plugins { - id "com.gradle.enterprise" version "3.11.1" + id "com.gradle.enterprise" version "3.13" id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.10' }