Browse Source

Add the `include_type_name` option to the search and document APIs. (#29506)

This commit add the `include_type_name` option to the `index`, `update`,
`delete`, `get`, `bulk` and `search` APIs. When set to `false`, the response
will omit the `_type` in the response. This option doesn't work if the endpoint
contains a type. For instance, the following call would succeed:

```
GET index/_doc/1?include_type_name=false
```

But the following one would fail:

```
GET index/some_type/1?include_type_name=false
```

Relates #15613
Adrien Grand 7 years ago
parent
commit
d223bcf7ab

+ 4 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/bulk.json

@@ -16,6 +16,10 @@
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "wait_for_active_shards": {
           "type" : "string",
           "description" : "Sets the number of shard copies that must be active before proceeding with the bulk operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)" 

+ 5 - 2
rest-api-spec/src/main/resources/rest-api-spec/api/delete.json

@@ -4,7 +4,7 @@
     "methods": ["DELETE"],
     "url": {
       "path": "/{index}/{type}/{id}",
-      "paths": ["/{index}/{type}/{id}"],
+      "paths": ["/{index}/{type}/{id}", "/{index}/_doc/{id}"],
       "parts": {
         "id": {
           "type" : "string",
@@ -18,11 +18,14 @@
         },
         "type": {
           "type" : "string",
-          "required" : true,
           "description" : "The type of the document"
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "wait_for_active_shards": {
           "type" : "string",
           "description" : "Sets the number of shard copies that must be active before proceeding with the delete operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)"

+ 5 - 2
rest-api-spec/src/main/resources/rest-api-spec/api/get.json

@@ -4,7 +4,7 @@
     "methods": ["GET"],
     "url": {
       "path": "/{index}/{type}/{id}",
-      "paths": ["/{index}/{type}/{id}"],
+      "paths": ["/{index}/{type}/{id}", "/{index}/_doc/{id}"],
       "parts": {
         "id": {
           "type" : "string",
@@ -18,11 +18,14 @@
         },
         "type": {
           "type" : "string",
-          "required" : true,
           "description" : "The type of the document (use `_all` to fetch the first document matching the ID across all types)"
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "stored_fields": {
           "type": "list",
           "description" : "A comma-separated list of stored fields to return in the response"

+ 4 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/index.json

@@ -21,6 +21,10 @@
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "wait_for_active_shards": {
           "type" : "string",
           "description" : "Sets the number of shard copies that must be active before proceeding with the index operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)"

+ 4 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/search.json

@@ -16,6 +16,10 @@
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "analyzer": {
           "type" : "string",
           "description" : "The analyzer to use for the query string"

+ 5 - 2
rest-api-spec/src/main/resources/rest-api-spec/api/update.json

@@ -4,7 +4,7 @@
     "methods": ["POST"],
     "url": {
       "path": "/{index}/{type}/{id}/_update",
-      "paths": ["/{index}/{type}/{id}/_update"],
+      "paths": ["/{index}/{type}/{id}/_update", "/{index}/_doc/{id}/_update"],
       "parts": {
         "id": {
           "type": "string",
@@ -18,11 +18,14 @@
         },
         "type": {
           "type": "string",
-          "required": true,
           "description": "The type of the document"
         }
       },
       "params": {
+        "include_type_name": {
+          "type" : "string",
+          "description" : "Whether to add the type name to the response"
+        },
         "wait_for_active_shards": {
           "type": "string",
           "description": "Sets the number of shard copies that must be active before proceeding with the update operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)"

+ 226 - 12
rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_mapping/20_no_types.yml

@@ -39,44 +39,257 @@
   - match: { index.mappings.properties.foo.type: "keyword" }
   - match: { index.mappings.properties.bar.type: "float" }
 
-# Explicit id
+---
+"Index explicit IDs without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
   - do:
-      index:
-          index:   index
-          id:      1
-          body:    { foo: bar }
+      indices.create:
+        index: index
+        include_type_name: false
 
-# Implicit id
   - do:
       index:
+          include_type_name: false
           index:   index
+          id:      1
           body:    { foo: bar }
 
-# Bulk with explicit id
+  - match:    { "_index": "index" }
+  - is_false: _type
+
   - do:
       bulk:
           index:   index
+          include_type_name: false
           body: |
             { "index": { "_id": "2" } }
             { "doc": { "foo": "baz" } }
 
-# Bulk with implicit id
+  - match:    { "items.0.index._index": "index" }
+  - is_false: items.0.index._type
+
+---
+"Index implicit IDs without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      index:
+          index:   index
+          include_type_name: false
+          body:    { foo: bar }
+
+  - match:    { "_index": "index" }
+  - is_false: _type
+
   - do:
       bulk:
           index:   index
+          include_type_name: false
           body: |
             { "index": { } }
             { "doc": { "foo": "baz" } }
 
+  - match:    { "items.0.index._index": "index" }
+  - is_false: items.0.index._type
+
+---
+"Mixing include_type_name=false with explicit types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      catch: /illegal_argument_exception/
+      index:
+          index:   index
+          type:    type
+          id:      1
+          include_type_name: false
+          body:    { foo: bar }
+
+  - do:
+      catch: /illegal_argument_exception/
+      index:
+          index:   index
+          type:    type
+          include_type_name: false
+          body:    { foo: bar }
+
+  - do:
+      catch: /illegal_argument_exception/
+      get:
+          index:   index
+          type:    type
+          id:      1
+          include_type_name: false
+
+  - do:
+      catch: /illegal_argument_exception/
+      update:
+          index:   index
+          type:    type
+          id:      1
+          include_type_name: false
+          body:
+              doc:    { foo: baz }
+
+  - do:
+      catch: /illegal_argument_exception/
+      delete:
+          index:   index
+          type:    type
+          id:      1
+          include_type_name: false
+
+  - do:
+      catch: /illegal_argument_exception/
+      search:
+          index:   index
+          type:    type
+          include_type_name: false
+
+  - do:
+      catch: /illegal_argument_exception/
+      search:
+          index:   index
+          type:    _doc
+          include_type_name: false
+
+---
+"Update API without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      index:
+          index:   index
+          id:      1
+          include_type_name: false
+          body:    { "foo": "bar" }
+
+  - do:
+      update:
+          index:   index
+          id:      1
+          include_type_name: false
+          body:
+              doc: { "foo": "baz" }
+
+  - match:    { "_index": "index" }
+  - is_false: _type
+
+---
+"GET API without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      index:
+          index:   index
+          id:      1
+          include_type_name: false
+          body:    { "foo": "bar" }
+
+  - do:
+      get:
+          index:   index
+          id:      1
+          include_type_name: false
+
+  - match:    { "_index": "index" }
+  - is_false: _type
+
+---
+"Delete API without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      index:
+          index:   index
+          id:      1
+          include_type_name: false
+          body:    { "foo": "bar" }
+
+  - do:
+      delete:
+          index:   index
+          id:      1
+          include_type_name: false
+
+  - match:    { "_index": "index" }
+  - is_false: _type
+
+---
+"Search without types":
+
+  - skip:
+      version: " - 6.99.99"
+      reason:  include_type_name was introduced in 7.0.0
+
+  - do:
+      indices.create:
+        index: index
+        include_type_name: false
+
+  - do:
+      index:
+          index:   index
+          id:      1
+          include_type_name: false
+          body:    { "foo": "bar" }
+
   - do:
       indices.refresh:
-          index:  index
+          index: index
 
   - do:
-      count:
+      search:
           index: index
+          include_type_name: false
 
-  - match: { count: 4 }
+  - match:    { "hits.total": 1 }
+  - match:    { "hits.hits.0._index": "index" }
+  - is_false: hits.hits.0._type
 
 ---
 "PUT mapping with a type and include_type_name: false":
@@ -88,6 +301,7 @@
   - do:
       indices.create:
         index: index
+        include_type_name: false
 
   - do:
       catch: /illegal_argument_exception/
@@ -101,7 +315,7 @@
               type: float
 
 ---
-"Empty index with the include_type_name=false option":
+"GET mappings on empty index with the include_type_name=false option":
 
   - skip:
       version: " - 6.99.99"

+ 5 - 3
server/src/main/java/org/elasticsearch/action/DocWriteResponse.java

@@ -295,9 +295,11 @@ public abstract class DocWriteResponse extends ReplicationResponse implements Wr
 
     public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
         ReplicationResponse.ShardInfo shardInfo = getShardInfo();
-        builder.field(_INDEX, shardId.getIndexName())
-                .field(_TYPE, type)
-                .field(_ID, id)
+        builder.field(_INDEX, shardId.getIndexName());
+        if (params.paramAsBoolean("include_type_name", true)) {
+            builder.field(_TYPE, type);
+        }
+        builder.field(_ID, id)
                 .field(_VERSION, version)
                 .field(RESULT, getResult().getLowercase());
         if (forcedRefresh) {

+ 3 - 1
server/src/main/java/org/elasticsearch/index/get/GetResult.java

@@ -252,7 +252,9 @@ public class GetResult implements Streamable, Iterable<DocumentField>, ToXConten
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
         builder.field(_INDEX, index);
-        builder.field(_TYPE, type);
+        if (params.paramAsBoolean("include_type_name", true)) {
+            builder.field(_TYPE, type);
+        }
         builder.field(_ID, id);
         if (isExists()) {
             if (version != -1) {

+ 9 - 1
server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java

@@ -72,7 +72,15 @@ public class RestBulkAction extends BaseRestHandler {
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
         BulkRequest bulkRequest = Requests.bulkRequest();
         String defaultIndex = request.param("index");
-        String defaultType = request.param("type", MapperService.SINGLE_MAPPING_NAME);
+        String defaultType = request.param("type");
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
+        if (includeTypeName == false && defaultType != null) {
+            throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the bulx APIs with the " +
+                    "[_bulk] and [{index}/_bulk] endpoints.");
+        }
+        if (defaultType == null) {
+            defaultType = MapperService.SINGLE_MAPPING_NAME;
+        }
         String defaultRouting = request.param("routing");
         FetchSourceContext defaultFetchSourceContext = FetchSourceContext.parseFromRestRequest(request);
         String defaultPipeline = request.param("pipeline");

+ 8 - 1
server/src/main/java/org/elasticsearch/rest/action/document/RestDeleteAction.java

@@ -24,6 +24,7 @@ import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.VersionType;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
@@ -47,7 +48,13 @@ public class RestDeleteAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        DeleteRequest deleteRequest = new DeleteRequest(request.param("index"), request.param("type"), request.param("id"));
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
+        final String type = request.param("type");
+        if (includeTypeName == false && MapperService.SINGLE_MAPPING_NAME.equals(type) == false) {
+            throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the delete API with the " +
+                    "[{index}/_doc/{id}] endpoints.");
+        }
+        DeleteRequest deleteRequest = new DeleteRequest(request.param("index"), type, request.param("id"));
         deleteRequest.routing(request.param("routing"));
         deleteRequest.timeout(request.paramAsTime("timeout", DeleteRequest.DEFAULT_TIMEOUT));
         deleteRequest.setRefreshPolicy(request.param("refresh"));

+ 8 - 1
server/src/main/java/org/elasticsearch/rest/action/document/RestGetAction.java

@@ -25,6 +25,7 @@ import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.VersionType;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
@@ -55,7 +56,13 @@ public class RestGetAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        final GetRequest getRequest = new GetRequest(request.param("index"), request.param("type"), request.param("id"));
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
+        final String type = request.param("type");
+        if (includeTypeName == false && MapperService.SINGLE_MAPPING_NAME.equals(type) == false) {
+            throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the get APIs with the " +
+                    "[{index}/_doc/{id}] endpoint.");
+        }
+        final GetRequest getRequest = new GetRequest(request.param("index"), type, request.param("id"));
         getRequest.refresh(request.paramAsBoolean("refresh", getRequest.refresh()));
         getRequest.routing(request.param("routing"));
         getRequest.preference(request.param("preference"));

+ 8 - 1
server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java

@@ -24,6 +24,7 @@ import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.VersionType;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
@@ -78,7 +79,13 @@ public class RestIndexAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        IndexRequest indexRequest = new IndexRequest(request.param("index"), request.param("type"), request.param("id"));
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
+        final String type = request.param("type");
+        if (includeTypeName == false && MapperService.SINGLE_MAPPING_NAME.equals(type) == false) {
+            throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the index APIs with the " +
+                    "[{index}/_doc/{id}] and [{index}/_doc] endpoints.");
+        }
+        IndexRequest indexRequest = new IndexRequest(request.param("index"), type, request.param("id"));
         indexRequest.routing(request.param("routing"));
         indexRequest.setPipeline(request.param("pipeline"));
         indexRequest.source(request.requiredContent(), request.getXContentType());

+ 8 - 1
server/src/main/java/org/elasticsearch/rest/action/document/RestUpdateAction.java

@@ -25,6 +25,7 @@ import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.VersionType;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
@@ -50,7 +51,13 @@ public class RestUpdateAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        UpdateRequest updateRequest = new UpdateRequest(request.param("index"), request.param("type"), request.param("id"));
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
+        final String type = request.param("type");
+        if (includeTypeName == false && MapperService.SINGLE_MAPPING_NAME.equals(type) == false) {
+            throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the update API with the " +
+                    "[{index}/_doc/{id}/_update] endpoint.");
+        }
+        UpdateRequest updateRequest = new UpdateRequest(request.param("index"), type, request.param("id"));
         updateRequest.routing(request.param("routing"));
         updateRequest.timeout(request.paramAsTime("timeout", updateRequest.timeout()));
         updateRequest.setRefreshPolicy(request.param("refresh"));

+ 6 - 0
server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java

@@ -27,6 +27,7 @@ import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
@@ -150,8 +151,13 @@ public class RestSearchAction extends BaseRestHandler {
             searchRequest.scroll(new Scroll(parseTimeValue(scroll, null, "scroll")));
         }
 
+        final boolean includeTypeName = request.paramAsBoolean("include_type_name", true);
         String types = request.param("type");
         if (types != null) {
+            if (includeTypeName == false) {
+                throw new IllegalArgumentException("You may only use the [include_type_name=false] option with the search API with the " +
+                        "[{index}/_search] endpoint.");
+            }
             DEPRECATION_LOGGER.deprecated("The {index}/{type}/_search endpoint is deprecated, use {index}/_search instead");
         }
         searchRequest.types(Strings.splitStringByCommaToArray(types));

+ 1 - 1
server/src/main/java/org/elasticsearch/search/SearchHit.java

@@ -426,7 +426,7 @@ public final class SearchHit implements Streamable, ToXContentObject, Iterable<D
         if (index != null) {
             builder.field(Fields._INDEX, RemoteClusterAware.buildRemoteIndexName(clusterAlias, index));
         }
-        if (type != null) {
+        if (type != null && params.paramAsBoolean("include_type_name", true)) {
             builder.field(Fields._TYPE, type);
         }
         if (id != null) {