Skip to content

Fix 16133: Unset default nullable domain property passes validation but fails at the database - #16136

Draft
matrei wants to merge 7 commits into
8.0.xfrom
fix/issue-16133
Draft

Fix 16133: Unset default nullable domain property passes validation but fails at the database#16136
matrei wants to merge 7 commits into
8.0.xfrom
fix/issue-16133

Conversation

@matrei

@matrei matrei commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #16133

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 52.6495%. Comparing base (fa1e147) to head (07a30bb).
⚠️ Report is 6 commits behind head on 8.0.x.

Files with missing lines Patch % Lines
.../grails/web/databinding/GrailsWebDataBinder.groovy 0.0000% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #16136        +/-   ##
==================================================
+ Coverage     52.5503%   52.6495%   +0.0992%     
- Complexity      18391      18442        +51     
==================================================
  Files            2037       2037                
  Lines           96498      96508        +10     
  Branches        16860      16862         +2     
==================================================
+ Hits            50710      50811       +101     
+ Misses          38353      38268        -85     
+ Partials         7435       7429         -6     
Files with missing lines Coverage Δ
...ore/mapping/config/AbstractGormMappingFactory.java 90.3614% <100.0000%> (+0.8878%) ⬆️
...atastore/mapping/model/AbstractMappingContext.java 67.4312% <100.0000%> (+0.1501%) ⬆️
.../grails/web/databinding/GrailsWebDataBinder.groovy 31.9261% <0.0000%> (ø)

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses #16133 by aligning GORM/Hibernate schema mapping nullability with Grails 8’s “nullable-by-default” validation behavior, preventing cases where validation passes but the database rejects inserts due to NOT NULL columns.

Changes:

  • Plumb grails.gorm.default.nullable (via ConnectionSourceSettings.DefaultSettings.nullable) into Hibernate mapping so generated column nullability matches the configured validation default.
  • Add an integration reproducer/spec for saving a domain instance with an unset unconstrained property.
  • Update multiple test/example domain classes and GraphQL-related tests to make requiredness explicit with nullable: false.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
grails-test-examples/hibernate5/grails-hibernate/src/integration-test/groovy/functional/tests/OrganizationValidationSpec.groovy Adds an integration spec reproducing the nullable-by-default vs DB mismatch scenario.
grails-test-examples/hibernate5/grails-hibernate/grails-app/domain/functional/tests/Organization.groovy Adds a minimal domain used by the new integration spec.
grails-test-examples/graphql/grails-test-app/src/integration-test/groovy/grails/test/app/UserIntegrationSpec.groovy Adjusts GraphQL integration assertions around required embedded inputs.
grails-test-examples/graphql/grails-test-app/grails-app/domain/grails/test/app/UserRole.groovy Makes user/role required explicitly via constraints.
grails-test-examples/graphql/grails-test-app/grails-app/domain/grails/test/app/User.groovy Makes embedded profile/address required explicitly and aligns GraphQL mapping.
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/AbstractGormMappingFactory.java Introduces configurable default nullability for mapped forms.
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java Wires default.nullable into the Hibernate mapping factory (Hibernate 7).
grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java Wires default.nullable into the Hibernate mapping factory (Hibernate 5).
grails-data-hibernate7/dbmigration/src/test-cli/groovy/org/apache/grails/data/hibernate7/dbmigration/cli/ApplicationContextDatabaseMigrationCommandSpec.groovy Updates test domain constraints to be explicit about requiredness.
grails-data-hibernate5/dbmigration/src/test-cli/groovy/org/apache/grails/data/hibernate5/dbmigration/cli/ApplicationContextDatabaseMigrationCommandSpec.groovy Updates test domain constraints to be explicit about requiredness.
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntitySpec.groovy Makes embedded property required explicitly in test domain.
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy Adds explicit non-null constraint for enum property in test entity.
grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/types/input/EmbeddedInputObjectTypeBuilderSpec.groovy Updates expectations for default nullability in GraphQL embedded input properties.
grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/SchemaSpec.groovy Adjusts schema assertions for input types (nullable wrapping expectations).
grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/property/impl/HibernatePersistentGraphQLPropertySpec.groovy Updates test domain constraints/mapping to be explicit about nullability.
Suppressed comments (3)

grails-test-examples/graphql/grails-test-app/src/integration-test/groovy/grails/test/app/UserIntegrationSpec.groovy:81

  • Same issue here: the test case description says the profile is missing a required field, but the assertion no longer checks that the request is rejected. This weakens coverage and can mask regressions in GraphQL input validation / domain validation.
        then:
        obj.data.userCreate != null

grails-test-examples/graphql/grails-test-app/src/integration-test/groovy/grails/test/app/UserIntegrationSpec.groovy:104

  • This assertion no longer verifies the behavior implied by the test name (missing required address should fail). Please assert the mutation is rejected and that an error mentions address to keep this test meaningful.
        then:
        obj.data.userCreate != null

grails-test-examples/graphql/grails-test-app/src/integration-test/groovy/grails/test/app/UserIntegrationSpec.groovy:129

  • This assertion no longer checks the expected failure when a required zip field is missing. Without asserting an error (and/or that userCreate is null), the test can pass even if validation is broken.
        then:
        obj.data.userCreate != null

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only concern is shouldn't the default in AbstractGormMappingFactory match the gorm default?

@matrei
matrei marked this pull request as draft August 13, 2026 17:28
@testlens-app

testlens-app Bot commented Aug 14, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 07a30bb
▶️ Tests: 68149 executed
⚪️ Checks: 77/77 completed


Learn more about TestLens at testlens.app.

@matrei

matrei commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

My only concern is shouldn't the default in AbstractGormMappingFactory match the gorm default?

@jdaugherty I have changed defaultNullable in AbstractGormMappingFactory to be initialized to true.

@matrei
matrei requested a review from codeconsole August 14, 2026 11:19

@codeconsole codeconsole left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice fix, 2 other cases?

createMappedForm(PersistentProperty) has three return paths and the fix only touches one — the else branch where the property has no config at all. The two it misses are the common ones, and both reproduce #16133 verbatim:

1. Any property named in static constraints with a non-nullable constraint. Both the mapping and constraints closures are evaluated into the same builder
(AbstractGormMappingFactory.java:99-112), and HibernateMappingBuilder.handlePropertyInternal writes new PropertyConfig() (nullable=false) into mapping.columns unconditionally, overwriting nullable only if (namedArgs.nullable instanceof Boolean). So adding name maxSize: 255 to the PR's own new Organization fixture brings the bug straight back — validate() passes, save() throws. Most real domain classes have a constraints block.

2. A '*' wildcard. The clone path (:195-202) never applies defaultNullable either, so grails.gorm.default.mapping = { '*'(cache: true) } silently reverts every column in the app to NOT NULL. The documented legacy switch '*'(nullable: false) works through this same path — but only by accident.

Fix for both: seed the default at creation time in the builders, or track "nullable was explicitly configured" so the factory fills only the gap (which is how the validation side already reasons, via hasAppliedConstraint).

3. The upgrade guide contradicts the code. upgrading80x.adoc:1237-1238 currently states verbatim that the Grails 8 change "is a validation-layer change only. Column/DDL nullability … is unaffected." This PR makes that false, with no doc change in the diff — and the schema impact is real for dbCreate: validate/update and Liquibase gormDiff.

Also flagged: setDefaultNullable(false) has no test (NullableByDefaultSpec is the natural home), the regression spec lands only under hibernate5 while #16133 is a Hibernate 7 stack trace, the GrailsWebDataBinder guard removal widens behaviour with no coverage, and composite-id components now default to nullable — the PR's own fixtures had to add nullable: false to UserRole and Book2, which is a signal real apps will need the same edit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Grails 8] Unset default nullable domain property passes validation but fails at the database

4 participants