Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a8751b8
Add properties support to flattened field type
parkertimmins Mar 16, 2026
a53c321
Improve flattened properties test assertions
parkertimmins Mar 17, 2026
228cc5e
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 17, 2026
ae4afd9
Add integration tests for flattened mapped properties
parkertimmins Mar 18, 2026
5632999
Support synthetic source for flattened mapped properties
parkertimmins Mar 18, 2026
4064d04
Add YAML REST tests for flattened mapped properties
parkertimmins Mar 18, 2026
365b373
Fix mapped properties bugs in synthetic source and null handling
parkertimmins Mar 18, 2026
6e72afe
Add tests and disallow copy_to/multi_fields on mapped properties
parkertimmins Mar 18, 2026
1d496cb
Change XContent builders to json string
parkertimmins Mar 18, 2026
bcfbb9c
Dont' change existing tests to keep diff smaller
parkertimmins Mar 18, 2026
60c598d
Fix block loader to include mapped property values
parkertimmins Mar 18, 2026
a1ac0a8
Update docs/changelog/144451.yaml
parkertimmins Mar 18, 2026
c248681
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 18, 2026
27bbd55
Use TreeMap for mapped properties to maintain sort order
parkertimmins Mar 18, 2026
7a3dec6
Extract toPropertyLoaders helper for mapped properties
parkertimmins Mar 18, 2026
b29b0e8
[CI] Auto commit changes from spotless
Mar 18, 2026
4707a99
Address review feedback for mapped sub-fields
parkertimmins Mar 19, 2026
aab68c6
Use bulk indexing in flattened YAML REST tests
parkertimmins Mar 19, 2026
07d2413
Convert unreachable instanceof check to assertion
parkertimmins Mar 19, 2026
1c40e6e
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 19, 2026
e880435
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 20, 2026
03a10fe
rename node feature
parkertimmins Mar 20, 2026
ee57871
[CI] Auto commit changes from spotless
Mar 20, 2026
7eb9d25
rename node feature
parkertimmins Mar 20, 2026
18c516f
Fix flaky index sort test in CCS suite
parkertimmins Mar 20, 2026
497bab3
remove forbidden api
parkertimmins Mar 20, 2026
9712db5
[CI] Auto commit changes from spotless
Mar 20, 2026
eabfa99
Fix flattened subfield YAML test mapping
parkertimmins Mar 20, 2026
ad58602
Add test for flattened as multi-field rejection
parkertimmins Mar 20, 2026
706406e
Move multivalued test to FlattenedMapperTests
parkertimmins Mar 20, 2026
d2313f1
Allow flattened type as multi-field of another field
parkertimmins Mar 23, 2026
bb2e4d7
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 24, 2026
f5df0f2
update docs to mention that copy_to and fields are disallowed
parkertimmins Mar 24, 2026
24074eb
Revert "Allow flattened type as multi-field of another field"
parkertimmins Mar 24, 2026
f9c335c
Use FieldMapper.TypeParser for FlattenedFieldMapper
parkertimmins Mar 24, 2026
8ece334
simplify property merge logic
parkertimmins Mar 24, 2026
545512a
Add KeyedFlattenedDocValuesBlockLoader tests
parkertimmins Mar 25, 2026
24608c9
Fix incorrect assertion in yaml
parkertimmins Mar 25, 2026
f11b7bd
Remove settings disallowed in serverless from yaml test
parkertimmins Mar 25, 2026
b7c3f00
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 25, 2026
7cac114
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 25, 2026
1412da9
Fix flaky index sort yaml test for flattened fields
parkertimmins Mar 26, 2026
e3f7e98
refresh after force merge in sort yaml test
parkertimmins Mar 26, 2026
d6bc406
Merge branch 'main' into parker/flattened-sub-fields
parkertimmins Mar 26, 2026
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
5 changes: 5 additions & 0 deletions docs/changelog/144451.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
area: Mapping
issues: []
pr: 144451
summary: Add properties support to flattened field type
type: enhancement
52 changes: 51 additions & 1 deletion docs/reference/elasticsearch/mapping-reference/flattened.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,53 @@ Because `labels` is a `flattened` field type, the entire object is mapped as a s
```


## Mapped sub-fields [flattened-properties]

By default, all keys in a flattened field are indexed as untyped keyword values. The `properties` parameter allows specific keys to be mapped as their own typed fields, such as `keyword`, `ip`, `long`, `date`, or any other leaf field type. Mapped keys are indexed exclusively through their sub-field and are excluded from the flattened field's representation.

This is useful when certain keys within the flattened object need functionality that plain flattened indexing does not support, such as index sorting, field aliases, or typed queries (for example, IP range queries on an `ip` field).

```console
PUT events
{
"mappings": {
"properties": {
"attributes": {
"type": "flattened",
"properties": {
"host.name": { "type": "keyword" },
"host.ip": { "type": "ip" }
}
}
}
}
}

POST events/_doc/1
{
"attributes": {
"host.name": "web-1",
"host.ip": "192.168.1.10",
"region": "us-east-1"
}
}
```

In this example, `attributes.host.name` is a keyword field and `attributes.host.ip` is an IP field, both with their full typed capabilities. The key `region` is not mapped, so it is indexed through the normal flattened mechanism. Searching on `attributes.host.ip` uses IP-aware queries:

```console
POST events/_search
{
"query": {
"term": { "attributes.host.ip": "192.168.1.10" }
}
}
```

Only leaf field types are allowed as sub-field types.
Object, nested, and flattened types cannot be used as properties of a flattened field.
Sub-fields may not use `copy_to` or `fields` (multi-fields) parameters.

## Parameters for flattened object fields [flattened-params]

The following mapping parameters are accepted:
Expand All @@ -232,6 +279,9 @@ The following mapping parameters are accepted:
[`null_value`](/reference/elasticsearch/mapping-reference/null-value.md)
: A string value which is substituted for any explicit `null` values within the flattened object field. Defaults to `null`, which means null fields are treated as if they were missing.

`properties`
: (Optional, object) A map of key names to field mappings. Allows specific keys within the flattened object to be mapped as typed sub-fields. Each entry maps a key (using dot notation for nested keys) to a leaf field type definition. See [Mapped sub-fields](#flattened-properties).

[`similarity`](/reference/elasticsearch/mapping-reference/similarity.md)
: Which scoring algorithm or *similarity* should be used. Defaults to `BM25`.

Expand Down Expand Up @@ -412,4 +462,4 @@ For example, if the field is defined in an index configured with synthetic sourc
}
}
```
% TEST[skip:backporting-from-new-docs]
% TEST[skip:backporting-from-new-docs]
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,273 @@ setup:
fields: [ { "field" : "flattened.non_existing_field" } ]

- is_false: hits.hits.0.fields

---
"Test flattened field with mapped properties":
- requires:
cluster_features: ["mapper.flattened.mapped_subfields"]
reason: "mapped properties on flattened fields"
- do:
indices.create:
index: test
body:
mappings:
properties:
labels:
type: flattened
properties:
host.name:
type: keyword
status_code:
type: long

- do:
bulk:
index: test
refresh: true
body:
- '{ "index": { "_id": "1" } }'
- '{ "labels": { "host": { "name": "server-a" }, "status_code": 200, "region": "us-east-1" } }'
- '{ "index": { "_id": "2" } }'
- '{ "labels": { "host": { "name": "server-b" }, "status_code": 503, "region": "eu-west-1" } }'

- match: { errors: false }

# Term query on mapped keyword property
- do:
search:
index: test
body:
query:
term:
labels.host.name: server-a

- match: { hits.total.value: 1 }
- match: { hits.hits.0._id: "1" }

# Range query on mapped long property
- do:
search:
index: test
body:
query:
range:
labels.status_code:
gte: 500

- match: { hits.total.value: 1 }
- match: { hits.hits.0._id: "2" }

# Term query on unmapped key still works as flattened
- do:
search:
index: test
body:
query:
term:
labels.region: us-east-1

- match: { hits.total.value: 1 }
- match: { hits.hits.0._id: "1" }

# Sort on mapped keyword property
- do:
search:
index: test
body:
sort: [ { labels.host.name: asc } ]

- match: { hits.hits.0._id: "1" }
- match: { hits.hits.1._id: "2" }

# Sort on mapped long property
- do:
search:
index: test
body:
sort: [ { labels.status_code: desc } ]

- match: { hits.hits.0._id: "2" }
- match: { hits.hits.1._id: "1" }

---
"Test flattened mapped subfields with aggregations":
- requires:
cluster_features: ["mapper.flattened.mapped_subfields"]
reason: "mapped subfields on flattened fields"
- do:
indices.create:
index: test
body:
mappings:
properties:
labels:
type: flattened
properties:
env:
type: keyword
priority:
type: long

- do:
bulk:
index: test
refresh: true
body:
- '{ "index": { "_id": "1" } }'
- '{ "labels": { "env": "prod", "priority": 1 } }'
- '{ "index": { "_id": "2" } }'
- '{ "labels": { "env": "prod", "priority": 3 } }'
- '{ "index": { "_id": "3" } }'
- '{ "labels": { "env": "staging", "priority": 2 } }'

- match: { errors: false }

# Terms aggregation on mapped keyword
- do:
search:
index: test
body:
size: 0
aggs:
envs:
terms:
field: labels.env

- length: { aggregations.envs.buckets: 2 }
- match: { aggregations.envs.buckets.0.key: prod }
- match: { aggregations.envs.buckets.0.doc_count: 2 }
- match: { aggregations.envs.buckets.1.key: staging }
- match: { aggregations.envs.buckets.1.doc_count: 1 }

# Stats aggregation on mapped long
- do:
search:
index: test
body:
size: 0
aggs:
priority_stats:
stats:
field: labels.priority

- match: { aggregations.priority_stats.count: 3 }
- match: { aggregations.priority_stats.min: 1.0 }
- match: { aggregations.priority_stats.max: 3.0 }

---
"Test flattened mapped subfields with synthetic source":
- requires:
cluster_features: ["mapper.flattened.mapped_subfields"]
reason: "mapped subfields on flattened fields"
- do:
indices.create:
index: test
body:
settings:
index:
mapping.source.mode: synthetic
mappings:
properties:
labels:
type: flattened
properties:
host.name:
type: keyword
status_code:
type: long

- do:
index:
index: test
id: "1"
body:
labels:
host:
name: server-a
status_code: 200
region: us-east-1
refresh: true

- do:
search:
index: test

- match: { hits.hits.0._source.labels.region: us-east-1 }
- match: { hits.hits.0._source.labels.host\.name: server-a }
- match: { hits.hits.0._source.labels.status_code: 200 }

---
"Test flattened mapped subfields serialization in mapping":
- requires:
cluster_features: ["mapper.flattened.mapped_subfields"]
reason: "mapped subfields on flattened fields"
- do:
indices.create:
index: test
body:
mappings:
properties:
labels:
type: flattened
properties:
host.name:
type: keyword
count:
type: long

- do:
indices.get_mapping:
index: test

- match: { test.mappings.properties.labels.type: flattened }
- match: { test.mappings.properties.labels.properties.host\.name.type: keyword }
- match: { test.mappings.properties.labels.properties.count.type: long }

---
"Test flattened mapped sub-field as index sort field":
- requires:
cluster_features: ["mapper.flattened.mapped_subfields"]
reason: "mapped subfields on flattened fields"
- do:
indices.create:
index: test
body:
settings:
index:
sort.field: labels.priority
sort.order: asc
mappings:
properties:
labels:
type: flattened
properties:
priority:
type: long

- do:
bulk:
index: test
refresh: true
body:
- '{ "index": { "_id": "1" } }'
- '{ "labels": { "priority": 3, "name": "low" } }'
- '{ "index": { "_id": "2" } }'
- '{ "labels": { "priority": 1, "name": "high" } }'
- '{ "index": { "_id": "3" } }'
- '{ "labels": { "priority": 2, "name": "medium" } }'
- match: { errors: false }

# force merge to guarantee sort order
- do:
indices.forcemerge:
index: test
max_num_segments: 1

- do:
search:
index: test

- match: { hits.hits.0._id: "2" }
- match: { hits.hits.1._id: "3" }
- match: { hits.hits.2._id: "1" }
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Set;

import static org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper.FLATTENED_MAPPED_SUBFIELDS_FEATURE;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.RESCORE_VECTOR_QUANTIZED_VECTOR_MAPPING;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING;
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.USE_DEFAULT_OVERSAMPLE_VALUE_FOR_BBQ;
Expand Down Expand Up @@ -139,7 +140,8 @@ public Set<NodeFeature> getTestFeatures() {
TEXT_FIELD_DOC_VALUES,
DENSE_VECTOR_DYNAMIC_TEMPLATE_DOTTED_FIELD_FIX,
DOC_VALUES_MULTI_VALUE,
DENSE_VECTOR_DYNAMIC_TEMPLATE_NESTED_OBJECT_FIX
DENSE_VECTOR_DYNAMIC_TEMPLATE_NESTED_OBJECT_FIX,
FLATTENED_MAPPED_SUBFIELDS_FEATURE
);
}
}
Loading
Loading