codeconsole opened a new pull request, #16732:
URL: https://github.com/apache/grails-core/pull/16732
## What
The two configuration lookup changes that #16130 left out of 8.1.x because
they change the answer for existing keys, as asked for in its review, plus the
paths that have to agree with them. Builds on #16130: this branch merges it, so
until #16130 is merged and 8.1.x is merged up the diff shows its commits too;
the commits of this PR start at the cherry-pick.
**A dotted key that runs past a value that is not a map is absent.** In
Grails 8, `getProperty('server.url.port')` with `server.url: http://localhost`
returned the URL, `getProperty('grails.gorm.default.constraints.nullable')`
with a closure at `grails.gorm.default.constraints` returned the closure, and
`getProperty('a.b.0')` with a list at `a.b` returned the list;
`containsProperty` of each was `true`. Now each is `null`, the default is
returned when one is given, and `containsProperty` is `false`. `navigate()` on
a config and `CodeGenConfig.navigate` answer the same way, so the lookup paths
no longer disagree.
```groovy
// server.url: http://localhost
config.getProperty('server.url.port') // was
'http://localhost', now null
config.getProperty('server.url.port', String, 'none') // 'none'
config.containsProperty('server.url.port') // false
config.navigate('server', 'url', 'port') // null
```
**The flat key is read before the nested maps are walked.** `merge` and
dot-notation assignment write every nested value twice, as a flat dotted key on
the root and as an entry of the nested map, so the flat key answers nearly
every lookup with one map read and the walk only runs for a key that has no
flat form. Where the two representations have been made to disagree, the flat
key wins:
```groovy
config.merge([a: [b: 'merged']])
config.put('a.b', 'flat') // a raw root put: now read by
getProperty, as it always was by config['a.b']
config.getProperty('a.b') // 'flat' (was 'merged')
config.a.put('b', 'nested') // a raw sub-map put: no longer read by
getProperty
config.getProperty('a.b') // 'flat' (was 'nested'); config.a.b is
'nested'
config.a.b = 'assigned' // dot-notation assignment writes both,
so every read agrees
config.getProperty('a.b') // 'assigned'
```
Reading the flat key first also exposed that the flat key never went through
the placeholder hook: a YAML value `evaluatedName: ${name}` was resolved in the
nested entry but kept the raw `${name}` in the flat key, which is what
`config['hello.evaluatedName']` returned on every released version. The flat
write now goes through the same `mergeMapEntry` hook as the nested one, so both
representations hold the resolved value and every read agrees.
The stale flat key that `merge` leaves when a higher-precedence scalar
replaces a lower-precedence block, which is what property-source layering
produces at startup, is read too: `merge(['server.url.port': 'y', 'server.url':
'x'])` answers `server.url.port` with `y` (was `x`), and `put('a', 'scalar');
put('a.b', 1)` answers `a.b` with `1` (was `scalar`). The absent-past-non-map
change alone already gives both of those the flat answer, so flat-first adds
only which raw single-representation write wins.
## Measurements
The harness from #16130 (two million `getProperty(key, String)` calls over
four keys, three configured and one absent, best of five, 60 environment
variables), run on this branch before and after the flat-first commit:
| version | ns per call |
|---|---|
| this branch with #16130 and the absent-past-non-map walk | 129 |
| plus reading the flat key first | 77 |
## Behaviour changes
Both are described in section 17 of the 9.0 upgrade guide, with the required
actions: read the parent instead of a child of a scalar, list or closure, and
assign through dot notation instead of writing directly into a nested map when
the value is read back through `getProperty`. The what's new entry for the
faster lookups gained a sentence pointing there.
## Commits
- The cherry-pick of the absent-past-scalar commit that #16130 reverted,
extended to List and closure values and `containsProperty`.
- `navigate` stops at a value that is not a map (`NavigableMap.navigateMap`,
which `CodeGenConfig.navigate` uses).
- The flat key read before the walk, with the four disagreement cases pinned.
- Docs.
- Placeholders resolved in the flat key as well, which the full grails-core
suite asked for through `NavigableMapSpringProfilesSpec`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]