Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions docs/changelog/105062.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 105062
summary: Nest pass-through objects within objects
area: TSDB
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -381,12 +381,12 @@ dynamic templates - conflicting aliases:
- match: { aggregations.filterA.tsids.buckets.0.doc_count: 2 }

---
dynamic templates - subobject in passthrough object error:
dynamic templates with nesting:
- skip:
version: " - 8.12.99"
features: "default_shards"
reason: "Support for dynamic fields was added in 8.13"
- do:
catch: /Tried to add subobject \[subcategory\] to object \[attributes\] which does not support subobjects/
indices.put_index_template:
name: my-dynamic-template
body:
Expand All @@ -395,21 +395,116 @@ dynamic templates - subobject in passthrough object error:
template:
settings:
index:
number_of_shards: 1
mode: time_series
time_series:
start_time: 2023-08-31T13:03:08.138Z

mappings:
properties:
attributes:
type: passthrough
dynamic: true
time_series_dimension: true
resource:
type: object
properties:
subcategory:
type: object
properties:
dim:
type: keyword
attributes:
type: passthrough
dynamic: true
time_series_dimension: true
dynamic_templates:
- counter_metric:
mapping:
type: integer
time_series_metric: counter

- do:
catch: /Mapping definition for \[attributes\] has unsupported parameters:\ \[subobjects \:\ true\]/
bulk:
index: k9s
refresh: true
body:
- '{ "create": { "dynamic_templates": { "data": "counter_metric" } } }'
- '{ "@timestamp": "2023-09-01T13:03:08.138Z","data": "10", "resource.attributes.dim": "A", "resource.attributes.another.dim": "C", "attributes.more.dim": "E" }'
- '{ "create": { "dynamic_templates": { "data": "counter_metric" } } }'
- '{ "@timestamp": "2023-09-01T13:03:09.138Z","data": "20", "resource.attributes.dim": "A", "resource.attributes.another.dim": "C", "attributes.more.dim": "E" }'
- '{ "create": { "dynamic_templates": { "data": "counter_metric" } } }'
- '{ "@timestamp": "2023-09-01T13:03:10.138Z","data": "30", "resource.attributes.dim": "B", "resource.attributes.another.dim": "D", "attributes.more.dim": "F" }'
- '{ "create": { "dynamic_templates": { "data": "counter_metric" } } }'
- '{ "@timestamp": "2023-09-01T13:03:10.238Z","data": "40", "resource.attributes.dim": "B", "resource.attributes.another.dim": "D", "attributes.more.dim": "F" }'

- do:
search:
index: k9s
body:
size: 0

- match: { hits.total.value: 4 }

- do:
search:
index: k9s
body:
size: 0
aggs:
filterA:
filter:
term:
dim: A
aggs:
tsids:
terms:
field: _tsid

- length: { aggregations.filterA.tsids.buckets: 1 }
- match: { aggregations.filterA.tsids.buckets.0.key: "LEjiJ4ATCXWlzeFvhGQ9lYlnP-nRIGKYihfZ18WoJ94t9a8OpbsCdwZALomb" }
- match: { aggregations.filterA.tsids.buckets.0.doc_count: 2 }

- do:
search:
index: k9s
body:
size: 0
aggs:
filterA:
filter:
term:
another.dim: C
aggs:
tsids:
terms:
field: _tsid

- length: { aggregations.filterA.tsids.buckets: 1 }
- match: { aggregations.filterA.tsids.buckets.0.key: "LEjiJ4ATCXWlzeFvhGQ9lYlnP-nRIGKYihfZ18WoJ94t9a8OpbsCdwZALomb" }
- match: { aggregations.filterA.tsids.buckets.0.doc_count: 2 }

- do:
search:
index: k9s
body:
size: 0
aggs:
filterA:
filter:
term:
more.dim: E
aggs:
tsids:
terms:
field: _tsid

- length: { aggregations.filterA.tsids.buckets: 1 }
- match: { aggregations.filterA.tsids.buckets.0.key: "LEjiJ4ATCXWlzeFvhGQ9lYlnP-nRIGKYihfZ18WoJ94t9a8OpbsCdwZALomb" }
- match: { aggregations.filterA.tsids.buckets.0.doc_count: 2 }

---
dynamic templates - subobject in passthrough object error:
- skip:
version: " - 8.12.99"
reason: "Support for dynamic fields was added in 8.13"
- do:
catch: /Tried to add subobject \[subcategory\] to object \[attributes\] which does not support subobjects/
indices.put_index_template:
name: my-dynamic-template
body:
Expand All @@ -418,24 +513,21 @@ dynamic templates - subobject in passthrough object error:
template:
settings:
index:
number_of_shards: 1
mode: time_series
time_series:
start_time: 2023-08-31T13:03:08.138Z

mappings:
properties:
attributes:
type: passthrough
subobjects: true
properties:
subcategory:
type: object
properties:
dim:
type: keyword

---
dynamic templates - passthrough not under root error:
- skip:
version: " - 8.12.99"
reason: "Support for dynamic fields was added in 8.13"
- do:
catch: /Tried to add passthrough subobject \[attributes\] to object \[resource\], passthrough is not supported as a subobject/
catch: /Mapping definition for \[attributes\] has unsupported parameters:\ \[subobjects \:\ true\]/
indices.put_index_template:
name: my-dynamic-template
body:
Expand All @@ -444,11 +536,13 @@ dynamic templates - passthrough not under root error:
template:
settings:
index:
number_of_shards: 1
mode: time_series
time_series:
start_time: 2023-08-31T13:03:08.138Z

mappings:
properties:
"resource.attributes":
attributes:
type: passthrough
dynamic: true
time_series_dimension: true
subobjects: true
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ protected static void parseProperties(
}
}

if (objBuilder.subobjects.value() == false && type.equals(ObjectMapper.CONTENT_TYPE)) {
if (objBuilder.subobjects.value() == false
&& (type.equals(ObjectMapper.CONTENT_TYPE)
|| type.equals(NestedObjectMapper.CONTENT_TYPE)
|| type.equals(PassThroughObjectMapper.CONTENT_TYPE))) {
throw new MapperParsingException(
"Tried to add subobject ["
+ fieldName
Expand All @@ -304,24 +307,6 @@ protected static void parseProperties(
+ "] which does not support subobjects"
);
}
if (objBuilder.subobjects.value() == false && type.equals(NestedObjectMapper.CONTENT_TYPE)) {
throw new MapperParsingException(
"Tried to add nested object ["
+ fieldName
+ "] to object ["
+ objBuilder.name()
+ "] which does not support subobjects"
);
}
if (type.equals(PassThroughObjectMapper.CONTENT_TYPE) && objBuilder instanceof RootObjectMapper.Builder == false) {
throw new MapperParsingException(
"Tried to add passthrough subobject ["
+ fieldName
+ "] to object ["
+ objBuilder.name()
+ "], passthrough is not supported as a subobject"
);
}
Mapper.TypeParser typeParser = parserContext.typeParser(type);
if (typeParser == null) {
throw new MapperParsingException("No handler for type [" + type + "] declared on field [" + fieldName + "]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public PassThroughObjectMapper.Builder setContainsDimensions() {
public PassThroughObjectMapper build(MapperBuilderContext context) {
return new PassThroughObjectMapper(
name,
context.buildFullName(name),
enabled,
dynamic,
buildMappers(context.createChildContext(name)),
Expand All @@ -70,19 +71,20 @@ public PassThroughObjectMapper build(MapperBuilderContext context) {

PassThroughObjectMapper(
String name,
String fullPath,
Explicit<Boolean> enabled,
Dynamic dynamic,
Map<String, Mapper> mappers,
Explicit<Boolean> timeSeriesDimensionSubFields
) {
// Subobjects are not currently supported.
super(name, name, enabled, Explicit.IMPLICIT_FALSE, dynamic, mappers);
super(name, fullPath, enabled, Explicit.IMPLICIT_FALSE, dynamic, mappers);
this.timeSeriesDimensionSubFields = timeSeriesDimensionSubFields;
}

@Override
PassThroughObjectMapper withoutMappers() {
return new PassThroughObjectMapper(simpleName(), enabled, dynamic, Map.of(), timeSeriesDimensionSubFields);
return new PassThroughObjectMapper(simpleName(), fullPath(), enabled, dynamic, Map.of(), timeSeriesDimensionSubFields);
}

public boolean containsDimensions() {
Expand All @@ -91,7 +93,7 @@ public boolean containsDimensions() {

@Override
public PassThroughObjectMapper.Builder newBuilder(IndexVersion indexVersionCreated) {
PassThroughObjectMapper.Builder builder = new PassThroughObjectMapper.Builder(name());
PassThroughObjectMapper.Builder builder = new PassThroughObjectMapper.Builder(simpleName());
builder.enabled = enabled;
builder.dynamic = dynamic;
builder.timeSeriesDimensionSubFields = timeSeriesDimensionSubFields;
Expand All @@ -108,6 +110,7 @@ public PassThroughObjectMapper merge(ObjectMapper mergeWith, MergeReason reason,

return new PassThroughObjectMapper(
simpleName(),
fullPath(),
mergeResult.enabled(),
mergeResult.dynamic(),
mergeResult.mappers(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

public class RootObjectMapper extends ObjectMapper {
private static final DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(RootObjectMapper.class);
private static final int MAX_NESTING_LEVEL_FOR_PASS_THROUGH_OBJECTS = 20;

/**
* Parameter used when serializing {@link RootObjectMapper} and request that the runtime section is skipped.
Expand Down Expand Up @@ -111,7 +112,11 @@ public RootObjectMapper.Builder addRuntimeFields(Map<String, RuntimeField> runti
@Override
public RootObjectMapper build(MapperBuilderContext context) {
Map<String, Mapper> mappers = buildMappers(context);
mappers.putAll(getAliasMappers(mappers, context));

Map<String, Mapper> aliasMappers = new HashMap<>();
getAliasMappers(mappers, aliasMappers, context, 0);
mappers.putAll(aliasMappers);

return new RootObjectMapper(
name,
enabled,
Expand All @@ -126,8 +131,11 @@ public RootObjectMapper build(MapperBuilderContext context) {
);
}

Map<String, Mapper> getAliasMappers(Map<String, Mapper> mappers, MapperBuilderContext context) {
Map<String, Mapper> aliasMappers = new HashMap<>();
void getAliasMappers(Map<String, Mapper> mappers, Map<String, Mapper> aliasMappers, MapperBuilderContext context, int level) {
if (level >= MAX_NESTING_LEVEL_FOR_PASS_THROUGH_OBJECTS) {
logger.warn("Exceeded maximum nesting level for searching for pass-through object fields within object fields.");
return;
}
for (Mapper mapper : mappers.values()) {
// Create aliases for all fields in child passthrough mappers and place them under the root object.
if (mapper instanceof PassThroughObjectMapper passthroughMapper) {
Expand All @@ -154,9 +162,11 @@ Map<String, Mapper> getAliasMappers(Map<String, Mapper> mappers, MapperBuilderCo
}
}
}
} else if (mapper instanceof ObjectMapper objectMapper) {
// Call recursively to check child fields. The level guards against long recursive call sequences.
getAliasMappers(objectMapper.mappers, aliasMappers, context, level + 1);
}
}
return aliasMappers;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,7 @@ public void testSubobjectsFalseWithInnerNestedFromDynamicTemplate() {
);
assertThat(exception.getRootCause(), instanceOf(MapperParsingException.class));
assertEquals(
"Tried to add nested object [time] to object [__dynamic__test] which does not support subobjects",
"Tried to add subobject [time] to object [__dynamic__test] which does not support subobjects",
exception.getRootCause().getMessage()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ public void testSubobjectsFalseWithInnerNested() {
b.endObject();
})));
assertEquals(
"Failed to parse mapping: Tried to add nested object [time] to object [service] which does not support subobjects",
"Failed to parse mapping: Tried to add subobject [time] to object [service] which does not support subobjects",
exception.getMessage()
);
}
Expand Down Expand Up @@ -457,7 +457,7 @@ public void testSubobjectsFalseRootWithInnerNested() {
b.endObject();
})));
assertEquals(
"Failed to parse mapping: Tried to add nested object [metrics.service] to object [_doc] which does not support subobjects",
"Failed to parse mapping: Tried to add subobject [metrics.service] to object [_doc] which does not support subobjects",
exception.getMessage()
);
}
Expand Down
Loading