Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give precedence to index creation when mixing typed templates with typeless index creation and vice-versa. #37871

Merged
74 changes: 74 additions & 0 deletions docs/reference/mapping/removal_of_types.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,77 @@ PUT index/_doc/1
The <<docs-index_,GET>>, <<docs-delete,`DELETE`>>, <<docs-update,`_update`>> and <<search,`_search`>> APIs
will continue to return a `_type` key in the response in 7.0, but it is considered deprecated and will be
removed in 8.0.

[float]
=== Index templates

It is recommended to make index templates typeless before upgrading to 7.0 by
re-adding them with `include_type_name` set to `false`.

In case typeless templates are used with typed index creation calls or typed
templates are used with typeless index creation calls, the template will still
be applied but the index creation call decides whether there should be a type
or not. For instance in the below example, `index-1-01` will have a type in
spite of the fact that it matches a template that is typeless, and `index-2-01`
will be typeless in spite of the fact that it matches a template that defines
a type. Both `index-1-01` and `index-2-01` will inherit the `foo` field from
the template that they match.

[source,js]
--------------------------------------------------
PUT _template/template1
{
"index_patterns":[ "index-1-*" ],
"mappings": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}

PUT _template/template2?include_type_name=true
{
"index_patterns":[ "index-2-*" ],
"mappings": {
"type": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}
}

PUT index-1-01?include_type_name=true
{
"mappings": {
"type": {
"properties": {
"bar": {
"type": "long"
}
}
}
}
}

PUT index-2-01
{
"mappings": {
"properties": {
"bar": {
"type": "long"
}
}
}
}
--------------------------------------------------
// CONSOLE

In case of implicit index creation, because of documents that get indexed in
an index that doesn't exist yet, the template is always honored. This is
usually not a problem due to the fact that typless index calls work on typed
Copy link
Contributor

Choose a reason for hiding this comment

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

Small typo (typless) but it's so hard to get a green build, I can just fix this when we do another pass over this documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will open a follow-up PR.

indices.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
"Create a typeless index while there is a typed template":
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the change is a bit subtle, I wonder if it'd be worth adding some unit tests in IndexCreationTaskTests? In particular, reading the code I had to think a bit about what would happen if no mappings were provided in the create index requests, or if the template or create request had more than one type.


- skip:
version: " - 6.99.99"
reason: needs change to be backported to 6.7

- do:
indices.put_template:
include_type_name: true
name: test_template
body:
index_patterns: test-*
mappings:
my_type:
properties:
foo:
type: keyword

- do:
indices.create:
include_type_name: false
index: test-1
body:
mappings:
properties:
bar:
type: "long"

- do:
indices.get_mapping:
include_type_name: true
index: test-1

- is_true: test-1.mappings._doc # the index creation call won
- is_false: test-1.mappings.my_type
- is_true: test-1.mappings._doc.properties.foo
- is_true: test-1.mappings._doc.properties.bar

---
"Create a typed index while there is a typeless template":

- skip:
version: " - 6.99.99"
reason: needs change to be backported to 6.7

- do:
indices.put_template:
include_type_name: false
name: test_template
body:
index_patterns: test-*
mappings:
properties:
foo:
type: keyword

- do:
indices.create:
include_type_name: true
index: test-1
body:
mappings:
my_type:
properties:
bar:
type: "long"

- do:
indices.get_mapping:
include_type_name: true
index: test-1

- is_true: test-1.mappings.my_type # the index creation call won
- is_false: test-1.mappings._doc
- is_true: test-1.mappings.my_type.properties.foo
- is_true: test-1.mappings.my_type.properties.bar

---
"Implicitly create a typed index while there is a typeless template":

- skip:
version: " - 6.99.99"
reason: needs change to be backported to 6.7

- do:
indices.put_template:
include_type_name: false
name: test_template
body:
index_patterns: test-*
mappings:
properties:
foo:
type: keyword

- do:
catch: /the final mapping would have more than 1 type/
index:
index: test-1
type: my_type
body: { bar: 42 }

---
"Implicitly create a typeless index while there is a typed template":

- skip:
version: " - 6.99.99"
reason: needs typeless index operations to work on typed indices

- do:
indices.put_template:
include_type_name: true
name: test_template
body:
index_patterns: test-*
mappings:
my_type:
properties:
foo:
type: keyword

- do:
index:
index: test-1
type: my_type
body: { bar: 42 }

- do:
indices.get_mapping:
include_type_name: true
index: test-1

- is_true: test-1.mappings.my_type # the template is honored
- is_false: test-1.mappings._doc
- is_true: test-1.mappings.my_type.properties.foo
- is_true: test-1.mappings.my_type.properties.bar
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,28 @@ public ClusterState execute(ClusterState currentState) throws Exception {
if (mappings.containsKey(cursor.key)) {
XContentHelper.mergeDefaults(mappings.get(cursor.key),
MapperService.parseMapping(xContentRegistry, mappingString));
} else if (mappings.size() == 1 && cursor.key.equals(MapperService.SINGLE_MAPPING_NAME)) {
// Typeless template with typed mapping
Map<String, Object> templateMapping = MapperService.parseMapping(xContentRegistry, mappingString);
assert templateMapping.size() == 1 : templateMapping;
assert cursor.key.equals(templateMapping.keySet().iterator().next()) :
cursor.key + " != " + templateMapping;
Map.Entry<String, Map<String, Object>> mappingEntry = mappings.entrySet().iterator().next();
templateMapping = Collections.singletonMap(
mappingEntry.getKey(), // reuse type name from the mapping
templateMapping.values().iterator().next()); // but actual mappings from the template
XContentHelper.mergeDefaults(mappingEntry.getValue(), templateMapping);
} else if (template.mappings().size() == 1 && mappings.containsKey(MapperService.SINGLE_MAPPING_NAME)) {
// Typed template with typeless mapping
Map<String, Object> templateMapping = MapperService.parseMapping(xContentRegistry, mappingString);
assert templateMapping.size() == 1 : templateMapping;
assert cursor.key.equals(templateMapping.keySet().iterator().next()) :
cursor.key + " != " + templateMapping;
Map<String, Object> mapping = mappings.get(MapperService.SINGLE_MAPPING_NAME);
templateMapping = Collections.singletonMap(
MapperService.SINGLE_MAPPING_NAME, // make template mapping typeless
templateMapping.values().iterator().next());
XContentHelper.mergeDefaults(mapping, templateMapping);
} else {
mappings.put(cursor.key,
MapperService.parseMapping(xContentRegistry, mappingString));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,32 @@ public void testWriteIndexValidationException() throws Exception {
assertThat(exception.getMessage(), startsWith("alias [alias1] has more than one write index ["));
}

public void testTypelessTemplateWithTypedIndexCreation() throws Exception {
addMatchingTemplate(builder -> builder.putMapping("type", "{\"type\": {}}"));
setupRequestMapping(MapperService.SINGLE_MAPPING_NAME, new CompressedXContent("{\"_doc\":{}}"));
executeTask();
assertThat(getMappingsFromResponse(), Matchers.hasKey(MapperService.SINGLE_MAPPING_NAME));
}

public void testTypedTemplateWithTypelessIndexCreation() throws Exception {
addMatchingTemplate(builder -> builder.putMapping(MapperService.SINGLE_MAPPING_NAME, "{\"_doc\": {}}"));
setupRequestMapping("type", new CompressedXContent("{\"type\":{}}"));
executeTask();
assertThat(getMappingsFromResponse(), Matchers.hasKey("type"));
}

public void testTypedTemplate() throws Exception {
addMatchingTemplate(builder -> builder.putMapping("type", "{\"type\": {}}"));
executeTask();
assertThat(getMappingsFromResponse(), Matchers.hasKey("type"));
}

public void testTypelessTemplate() throws Exception {
addMatchingTemplate(builder -> builder.putMapping(MapperService.SINGLE_MAPPING_NAME, "{\"_doc\": {}}"));
executeTask();
assertThat(getMappingsFromResponse(), Matchers.hasKey(MapperService.SINGLE_MAPPING_NAME));
}

private IndexRoutingTable createIndexRoutingTableWithStartedShards(Index index) {
final IndexRoutingTable idxRoutingTable = mock(IndexRoutingTable.class);

Expand Down