├── .gitignore ├── README.md ├── dev-tools └── tests.policy ├── pom.xml └── src ├── main ├── assemblies │ └── plugin.xml ├── java │ └── org │ │ └── elasticsearch │ │ ├── action │ │ ├── bulk │ │ │ ├── PublicBulkShardRequest.java │ │ │ └── PublicBulkShardResponse.java │ │ └── updatebyquery │ │ │ ├── BulkResponseOption.java │ │ │ ├── IndexUpdateByQueryRequest.java │ │ │ ├── IndexUpdateByQueryResponse.java │ │ │ ├── ShardUpdateByQueryRequest.java │ │ │ ├── ShardUpdateByQueryResponse.java │ │ │ ├── TransportShardUpdateByQueryAction.java │ │ │ ├── TransportUpdateByQueryAction.java │ │ │ ├── UpdateByQueryAction.java │ │ │ ├── UpdateByQueryRequest.java │ │ │ ├── UpdateByQueryRequestBuilder.java │ │ │ ├── UpdateByQueryResponse.java │ │ │ ├── UpdateByQuerySourceBuilder.java │ │ │ └── package-info.java │ │ ├── client │ │ ├── UpdateByQueryClient.java │ │ └── UpdateByQueryClientWrapper.java │ │ ├── common │ │ └── lucene │ │ │ └── TopLevelFixedBitSetCollector.java │ │ ├── plugin │ │ └── action │ │ │ └── updatebyquery │ │ │ └── ActionUpdateByQueryPlugin.java │ │ └── rest │ │ └── action │ │ └── updatebyquery │ │ └── RestUpdateByQueryAction.java └── resources │ └── es-plugin.properties └── test ├── java └── org │ └── elasticsearch │ └── test │ ├── integration │ └── updatebyquery │ │ └── UpdateByQueryTests.java │ └── stress │ └── updatebyquery │ └── UpdateByQueryStressTest.java └── resources ├── log4j.properties └── org └── elasticsearch └── test └── integration └── updatebyquery └── config └── scripts └── field1_incrementer.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | /work 3 | /logs 4 | /.idea 5 | /target 6 | .DS_Store 7 | *.iml 8 | /.project 9 | /.settings 10 | /.classpath 11 | .*.swp 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ElasticSearch Update By Query Plugin 2 | ==================================== 3 | 4 | The update by query API allows all documents that with the query to be updated with a script. 5 | This is experimental. 6 | 7 | This plugin is an adaptation of [elasticsearch/elasticsearch#2230][es#2230], see [@martijnvg's branch][martijnvg-ubq]. 8 | 9 | Upgrade notes 10 | ------------- 11 | 12 | * v2.0.0 dropped the support of the added context variables, see [the corresponding section](#context-variables). 13 | 14 | Installation 15 | ----------- 16 | 17 | Simply run at the root of your ElasticSearch v0.20.2+ installation: 18 | 19 | bin/plugin -install com.yakaz.elasticsearch.plugins/elasticsearch-action-updatebyquery/2.6.0 20 | 21 | This will download the plugin from the Central Maven Repository. 22 | 23 | For older versions of ElasticSearch, you can still use the longer: 24 | 25 | bin/plugin -url http://oss.sonatype.org/content/repositories/releases/com/yakaz/elasticsearch/plugins/elasticsearch-action-updatebyquery/1.0.0/elasticsearch-action-updatebyquery-1.0.0.zip install elasticsearch-action-updatebyquery 26 | 27 | In order to declare this plugin as a dependency, add the following to your `pom.xml`: 28 | 29 | ```xml 30 | 31 | com.yakaz.elasticsearch.plugins 32 | elasticsearch-action-updatebyquery 33 | 2.5.1 34 | 35 | ``` 36 | 37 | Version matrix: 38 | 39 | ┌───────────────────────────────┬────────────────────────┐ 40 | │ Update By Query Action Plugin │ ElasticSearch │ 41 | ├───────────────────────────────┼────────────────────────┤ 42 | │ │ 2.0.0-beta1 │ 43 | ├───────────────────────────────┼────────────────────────┤ 44 | │ 2.6.0 │ 1.6.0 ─► (1.7.4) │ 45 | ├───────────────────────────────┼────────────────────────┤ 46 | │ 2.5.x │ 1.5.0 ─► (1.5.2) │ 47 | ├───────────────────────────────┼────────────────────────┤ 48 | │ 2.4.0 │ 1.4.0 ─► (1.4.5) │ 49 | ├───────────────────────────────┼────────────────────────┤ 50 | │ 2.3.0 │ 1.4.0.Beta1 │ 51 | ├───────────────────────────────┼────────────────────────┤ 52 | │ 2.2.0 │ 1.3.0 ─► (1.3.8) │ 53 | ├───────────────────────────────┼────────────────────────┤ 54 | │ 2.1.1 │ 1.2.3 │ 55 | ├───────────────────────────────┼────────────────────────┤ 56 | │ 2.1.0 │ 1.2.0 ─► 1.2.2 │ 57 | ├───────────────────────────────┼────────────────────────┤ 58 | │ 2.0.x │ 1.1.0 ─► 1.1.2 │ 59 | ├───────────────────────────────┼────────────────────────┤ 60 | │ 1.6.x │ 1.0.0 ─► 1.0.2 │ 61 | ├───────────────────────────────┼────────────────────────┤ 62 | │ 1.5.x │ 1.0.0.Beta1 │ 63 | ├───────────────────────────────┼────────────────────────┤ 64 | │ 1.4.x │ 0.90.10 ─► (0.90.13) │ 65 | ├───────────────────────────────┼────────────────────────┤ 66 | │ 1.4.0 │ 0.90.6 ─► 0.90.9 │ 67 | ├───────────────────────────────┼────────────────────────┤ 68 | │ 1.3.x │ 0.90.4 ─► 0.90.5 │ 69 | ├───────────────────────────────┼────────────────────────┤ 70 | │ 1.2.x │ 0.90.3 │ 71 | ├───────────────────────────────┼────────────────────────┤ 72 | │ 1.1.x │ 0.90.0.beta1 ─► 0.90.2 │ 73 | ├───────────────────────────────┼────────────────────────┤ 74 | │ 1.0.x │ 0.20.0 ─► 0.20.4 │ 75 | └───────────────────────────────┴────────────────────────┘ 76 | 77 | Description 78 | ----------- 79 | 80 | The update by query API allows all documents that with the query to be updated with a script. 81 | This feature is experimental. 82 | 83 | The update by query works a bit different than the delete by query. 84 | The update by query api translates the documents that match into bulk index / delete requests. 85 | After the bulk limit has been reached, the bulk requests created thus far will be executed. 86 | After the bulk requests have been executed the next batch of requests will be prepared and executed. 87 | This behavior continues until all documents that matched the query have been processed. 88 | 89 | Example usage 90 | ------------- 91 | 92 | **Note:** The following example uses dynamic scripts, disabled by default since ElasticSearch 1.2.0. 93 | To enable them, add `script.disable_dynamic: false` to your elasticsearch configuration. 94 | 95 | Index an example document: 96 | 97 | ```sh 98 | curl -XPUT 'localhost:9200/twitter/tweet/1' -d ' 99 | { 100 | "text" : { 101 | "message" : "you know for search" 102 | }, 103 | "likes": 0 104 | }' 105 | ``` 106 | 107 | Execute the following update by query command: 108 | 109 | ```sh 110 | curl -XPOST 'localhost:9200/twitter/_update_by_query' -d ' 111 | { 112 | "query" : { 113 | "term" : { 114 | "message" : "you" 115 | } 116 | }, 117 | "script" : "ctx._source.likes += 1" 118 | }' 119 | ``` 120 | 121 | This will yield the following response: 122 | 123 | ```js 124 | { 125 | "ok" : true, 126 | "took" : 9, 127 | "total" : 1, 128 | "updated" : 1, 129 | "indices" : [ { 130 | "twitter" : { } 131 | } ] 132 | } 133 | ``` 134 | 135 | By default no bulk item responses are included in the response. 136 | If there are bulk item responses included in the response, the bulk response items are grouped by index and shard. 137 | This can be controlled by the `response` option. 138 | 139 | Options 140 | ------- 141 | 142 | ### Request body options: 143 | 144 | * `query`: The query that the documents must match to be updated. 145 | * `script`: The inline script source. 146 | * `script_file`: The file script name. 147 | * `script_id`: The indexed script id. 148 | * `lang`: The script language. 149 | * `params`: The script parameters. 150 | 151 | ### Query string options: 152 | 153 | * `consistency`: The write consistency of the index/delete operation. 154 | * `response`: What bulk response items to include into the update by query response. 155 | This can be set to the following: `none`, `failed` and `all`. 156 | Defaults to `none`. 157 | Warning: `all` can result in out of memory errors when the query results in many hits. 158 | * `routing`: Sets the routing that will be used to route the document to the relevant shard. 159 | * `timeout`: Timeout waiting for a shard to become available. 160 | 161 | ### Configuration options: 162 | 163 | * `action.updatebyquery.bulk_size`: The number of documents per update bulk. Defaults to `1000`. 164 | * `threadpool.bulk.queue_size`: This plugins files bulk requests to perform the actual updates. 165 | You may decide to increase this value over its default of 50 if you are experiencing the following errors: 166 | `EsRejectedExecutionException[rejected execution (queue capacity 50) on org.elasticsearch.action.updatebyquery.TransportShardUpdateByQueryAction$1]` 167 | 168 | Context variables 169 | ----------------- 170 | 171 | NOTE: v2.0.0 of this plugin dropped the support of additional context variables in favor of a unified code path with the Update API. 172 | Pull request [elasticsearch/elasticsearch#5724][es#5724] aims at restoring them (except `_uid`). 173 | As such, the context variables available through the update by query feature are the same as those available in the Update API scripts. 174 | 175 | ### Input variables 176 | 177 | Just like in the Update API, the script has access to the following variables (as of Elasticsearch 1.1.0): 178 | 179 | * `ctx` 180 | * `_source` 181 | 182 | And as of Elasticsearch 1.5.0: 183 | 184 | * `ctx` 185 | * `_source` 186 | * `_index` 187 | * `_type` 188 | * `_id` 189 | * `_version` 190 | * `_routing` 191 | * `_parent` 192 | * `_timestamp` 193 | * `_ttl` 194 | 195 | ### Output variables 196 | 197 | Just like in the Update API, you may update the following variables (as of Elasticsearch 1.1.0): 198 | 199 | * `ctx` 200 | * `_timestamp` 201 | * `_ttl` 202 | 203 | 204 | 205 | [es#2230]: https://github.com/elasticsearch/elasticsearch/issues/2230 206 | [martijnvg-ubq]: https://github.com/martijnvg/elasticsearch/tree/updatebyquery 207 | [es#5724]: https://github.com/elasticsearch/elasticsearch/pull/5724 208 | -------------------------------------------------------------------------------- /dev-tools/tests.policy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | // Policy file to prevent tests from writing outside the test sandbox directory 21 | // PLEASE NOTE: You may need to enable other permissions when new tests are added, 22 | // everything not allowed here is forbidden! 23 | 24 | grant { 25 | // permissions for file access, write access only to sandbox: 26 | permission java.io.FilePermission "<>", "read,execute"; 27 | permission java.io.FilePermission "${junit4.childvm.cwd}", "read,execute,write"; 28 | permission java.io.FilePermission "${junit4.childvm.cwd}${/}-", "read,execute,write,delete"; 29 | permission java.io.FilePermission "${junit4.tempDir}${/}*", "read,execute,write,delete"; 30 | permission groovy.security.GroovyCodeSourcePermission "/groovy/script"; 31 | 32 | // Allow connecting to the internet anywhere 33 | permission java.net.SocketPermission "*", "accept,listen,connect,resolve"; 34 | 35 | // Basic permissions needed for Lucene / Elasticsearch to work: 36 | permission java.util.PropertyPermission "*", "read,write"; 37 | permission java.lang.reflect.ReflectPermission "*"; 38 | permission java.lang.RuntimePermission "*"; 39 | 40 | // These two *have* to be spelled out a separate 41 | permission java.lang.management.ManagementPermission "control"; 42 | permission java.lang.management.ManagementPermission "monitor"; 43 | 44 | permission java.net.NetPermission "*"; 45 | permission java.util.logging.LoggingPermission "control"; 46 | permission javax.management.MBeanPermission "*", "*"; 47 | permission javax.management.MBeanServerPermission "*"; 48 | permission javax.management.MBeanTrustPermission "*"; 49 | 50 | // Needed for some things in DNS caching in the JVM 51 | permission java.security.SecurityPermission "getProperty.networkaddress.cache.ttl"; 52 | permission java.security.SecurityPermission "getProperty.networkaddress.cache.negative.ttl"; 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | elasticsearch-action-updatebyquery 6 | 7 | The Update By Query feature, as an action plugin for ElasticSearch. 8 | 9 | 2013 10 | 11 | com.yakaz.elasticsearch.plugins 12 | elasticsearch-action-updatebyquery 13 | 2.6.1-SNAPSHOT 14 | jar 15 | 16 | http://github.com/yakaz/elasticsearch-action-updatebyquery 17 | 18 | scm:git:git@github.com:yakaz/elasticsearch-action-updatebyquery.git 19 | scm:git:git@github.com:yakaz/elasticsearch-action-updatebyquery.git 20 | https://github.com/yakaz/elasticsearch-action-updatebyquery.git 21 | HEAD 22 | 23 | 24 | 25 | 26 | The Apache Software License, Version 2.0 27 | http://www.apache.org/licenses/LICENSE-2.0.txt 28 | repo 29 | 30 | 31 | 32 | 33 | 34 | ofavre 35 | Olivier Favre 36 | http://www.google.com/recaptcha/mailhide/d?k=01BFRL3yhSOUyxYhgio4AZrQ==&c=MG8swJyru2wFd6B6QWz--HIx_5O38r35CjjdTzn025Q= 37 | +1 38 | 39 | 40 | 41 | 42 | org.sonatype.oss 43 | oss-parent 44 | 7 45 | 46 | 47 | 48 | 1.6.0 49 | 4.10.4 50 | 2.1.11 51 | 2.4.0 52 | 1 53 | true 54 | onerror 55 | 56 | INFO 57 | 58 | 59 | 60 | 61 | com.carrotsearch.randomizedtesting 62 | randomizedtesting-runner 63 | ${randomizedtesting-runner.version} 64 | test 65 | 66 | 67 | 68 | org.apache.lucene 69 | lucene-test-framework 70 | ${lucene.version} 71 | test 72 | 73 | 74 | 75 | org.elasticsearch 76 | elasticsearch 77 | ${elasticsearch.version} 78 | test-jar 79 | test 80 | 81 | 82 | 83 | 84 | 85 | org.codehaus.groovy 86 | groovy-all 87 | ${groovy.version} 88 | test 89 | 90 | 91 | 92 | org.hamcrest 93 | hamcrest-all 94 | 1.3 95 | test 96 | 97 | 98 | 99 | log4j 100 | log4j 101 | 1.2.17 102 | test 103 | true 104 | 105 | 106 | 107 | org.slf4j 108 | slf4j-api 109 | 1.6.2 110 | test 111 | true 112 | 113 | 114 | 115 | org.elasticsearch 116 | elasticsearch 117 | ${elasticsearch.version} 118 | compile 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.codehaus.mojo 127 | cobertura-maven-plugin 128 | 2.5.1 129 | 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-compiler-plugin 137 | 2.3.2 138 | 139 | 1.7 140 | 1.7 141 | 142 | 143 | 144 | com.carrotsearch.randomizedtesting 145 | junit4-maven-plugin 146 | 2.1.2 147 | 148 | 149 | tests 150 | test 151 | 152 | junit4 153 | 154 | 155 | 20 156 | pipe,warn 157 | true 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | ${tests.jvms} 169 | 170 | 171 | 172 | 173 | 174 | 175 | **/*Tests.class 176 | **/*Test.class 177 | 178 | 179 | **/Abstract*.class 180 | **/*StressTest.class 181 | 182 | 183 | ${tests.jvm.argline} 184 | 185 | 186 | -Xmx512m 187 | -Xss256k 188 | -XX:MaxPermSize=128m 189 | -XX:MaxDirectMemorySize=512m 190 | -Des.logger.prefix= 191 | 192 | ${tests.shuffle} 193 | ${tests.verbose} 194 | ${tests.seed} 195 | ${tests.failfast} 196 | 197 | . 198 | 199 | ${tests.jvm.argline} 200 | ${tests.appendseed} 201 | ${tests.iters} 202 | ${tests.maxfailures} 203 | ${tests.failfast} 204 | ${tests.class} 205 | ${tests.method} 206 | ${tests.nightly} 207 | ${tests.badapples} 208 | ${tests.weekly} 209 | ${tests.slow} 210 | ${tests.awaitsfix} 211 | ${tests.slow} 212 | ${tests.timeoutSuite} 213 | ${tests.showSuccess} 214 | ${tests.integration} 215 | ${tests.cluster_seed} 216 | ${tests.client.ratio} 217 | ${tests.enable_mock_modules} 218 | ${tests.assertion.disabled} 219 | ${tests.rest} 220 | ${tests.rest.suite} 221 | ${tests.rest.spec} 222 | ${tests.network} 223 | ${env.ES_TEST_LOCAL} 224 | ${es.node.mode} 225 | ${es.logger.level} 226 | ${tests.security.manager} 227 | true 228 | 229 | ${project.build.directory} 230 | ${basedir}/dev-tools/tests.policy 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | org.apache.maven.plugins 239 | maven-surefire-plugin 240 | 2.16 241 | 242 | true 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-source-plugin 248 | 2.1.2 249 | 250 | 251 | attach-sources 252 | 253 | jar 254 | 255 | 256 | 257 | 258 | 259 | org.apache.maven.plugins 260 | maven-javadoc-plugin 261 | 2.9 262 | 263 | true 264 | 265 | 266 | 267 | attach-javadoc 268 | 269 | jar 270 | 271 | 272 | 273 | 274 | 275 | maven-assembly-plugin 276 | 2.3 277 | 278 | false 279 | ${project.build.directory}/releases/ 280 | 281 | ${basedir}/src/main/assemblies/plugin.xml 282 | 283 | 284 | 285 | 286 | package 287 | 288 | single 289 | 290 | 291 | 292 | 293 | 294 | org.apache.maven.plugins 295 | maven-release-plugin 296 | 2.4 297 | 298 | v@{project.version} 299 | 300 | 301 | 302 | 303 | org.codehaus.mojo 304 | cobertura-maven-plugin 305 | 2.5.1 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | org.codehaus.mojo 314 | cobertura-maven-plugin 315 | 2.5.1 316 | 317 | 318 | 319 | 320 | 321 | 322 | release-sign-artifacts 323 | 324 | 325 | performRelease 326 | true 327 | 328 | 329 | 330 | 331 | 332 | org.apache.maven.plugins 333 | maven-gpg-plugin 334 | 335 | 336 | sign-artifacts 337 | verify 338 | 339 | sign 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /src/main/assemblies/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugin 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | / 11 | true 12 | true 13 | 14 | org.elasticsearch:elasticsearch 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/bulk/PublicBulkShardRequest.java: -------------------------------------------------------------------------------- 1 | package org.elasticsearch.action.bulk; 2 | 3 | public class PublicBulkShardRequest extends BulkShardRequest { 4 | 5 | public PublicBulkShardRequest() { 6 | } 7 | 8 | public PublicBulkShardRequest(BulkRequest bulkRequest, String index, int shardId, boolean refresh, BulkItemRequest[] items) { 9 | super(bulkRequest, index, shardId, refresh, items); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/bulk/PublicBulkShardResponse.java: -------------------------------------------------------------------------------- 1 | package org.elasticsearch.action.bulk; 2 | 3 | import org.elasticsearch.index.shard.ShardId; 4 | 5 | public class PublicBulkShardResponse extends BulkShardResponse { 6 | 7 | public PublicBulkShardResponse() { 8 | } 9 | 10 | public PublicBulkShardResponse(ShardId shardId, BulkItemResponse[] responses) { 11 | super(shardId, responses); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/BulkResponseOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.ElasticsearchIllegalArgumentException; 23 | 24 | /** 25 | * Specifies how bulk responses that have been created based on the documents that have matched with the update query 26 | * are returned in the response. 27 | */ 28 | public enum BulkResponseOption { 29 | 30 | /** 31 | * Bulk responses aren't included in the response. 32 | */ 33 | NONE((byte) 0), 34 | 35 | /** 36 | * Only failed bulk responses are included in the response. 37 | */ 38 | FAILED((byte) 1), 39 | 40 | /** 41 | * All bulk responses are included in the response. 42 | */ 43 | ALL((byte) 2); 44 | 45 | private byte id; 46 | 47 | private BulkResponseOption(byte id) { 48 | this.id = id; 49 | } 50 | 51 | /** 52 | * The internal representation of the operation type. 53 | */ 54 | public byte id() { 55 | return id; 56 | } 57 | 58 | /** 59 | * Constructs the bulk response option from its internal representation. 60 | */ 61 | public static BulkResponseOption fromId(byte id) { 62 | if (id == 0) { 63 | return NONE; 64 | } else if (id == 1) { 65 | return FAILED; 66 | } else if (id == 2) { 67 | return ALL; 68 | } else { 69 | throw new ElasticsearchIllegalArgumentException("No type match for [" + id + "]"); 70 | } 71 | } 72 | 73 | /** 74 | * Parse the bulk response option from string. 75 | */ 76 | public static BulkResponseOption fromString(String type) { 77 | if ("none".equals(type)) { 78 | return NONE; 79 | } else if ("failed".equals(type)) { 80 | return FAILED; 81 | } else if ("all".equals(type)) { 82 | return ALL; 83 | } 84 | throw new ElasticsearchIllegalArgumentException("no response type match for [" + type + "], should be either `none`, `failed` or `all`"); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/IndexUpdateByQueryRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.common.collect.Sets; 23 | import org.elasticsearch.action.ActionRequestValidationException; 24 | import org.elasticsearch.action.support.replication.IndexReplicationOperationRequest; 25 | import org.elasticsearch.common.bytes.BytesReference; 26 | 27 | import java.util.Set; 28 | 29 | import static org.elasticsearch.action.ValidateActions.addValidationError; 30 | 31 | /** 32 | * Represents an update by query request targeted for a specific index. 33 | */ 34 | public class IndexUpdateByQueryRequest extends IndexReplicationOperationRequest { 35 | 36 | private String[] types = new String[0]; 37 | private BulkResponseOption bulkResponseOption; 38 | private String[] filteringAliases = new String[0]; 39 | private Set routing = Sets.newHashSet(); 40 | 41 | private BytesReference source; 42 | private long nowInMillis; 43 | 44 | IndexUpdateByQueryRequest(UpdateByQueryRequest request, String index, String[] filteringAliases, Set routing) { 45 | super(index, request.timeout(), request.replicationType(), request.consistencyLevel(), request.indices(), request.indicesOptions(), request); 46 | this.listenerThreaded(request.listenerThreaded()); 47 | this.types = request.types(); 48 | this.bulkResponseOption = request.bulkResponseOptions(); 49 | this.source = request.source(); 50 | this.nowInMillis = request.nowInMillis; 51 | if (filteringAliases != null) { 52 | this.filteringAliases = filteringAliases; 53 | } 54 | if (routing != null) { 55 | this.routing = routing; 56 | } 57 | } 58 | 59 | public String[] types() { 60 | return types; 61 | } 62 | 63 | public String[] filteringAliases() { 64 | return filteringAliases; 65 | } 66 | 67 | public BulkResponseOption bulkResponseOptions() { 68 | return bulkResponseOption; 69 | } 70 | 71 | public Set routing() { 72 | return routing; 73 | } 74 | 75 | public BytesReference source() { 76 | return source; 77 | } 78 | 79 | public long nowInMillis() { 80 | return nowInMillis; 81 | } 82 | 83 | @Override 84 | public ActionRequestValidationException validate() { 85 | ActionRequestValidationException validationException = super.validate(); 86 | if (source == null) { 87 | validationException = addValidationError("Source is missing", validationException); 88 | } 89 | return validationException; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/IndexUpdateByQueryResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.common.collect.Maps; 23 | import org.elasticsearch.action.ActionResponse; 24 | import org.elasticsearch.action.bulk.BulkItemResponse; 25 | import org.elasticsearch.common.io.stream.StreamInput; 26 | import org.elasticsearch.common.io.stream.StreamOutput; 27 | 28 | import java.io.IOException; 29 | import java.util.Map; 30 | 31 | /** 32 | * Encapsulates the result of an update by query request by bundling all bulk item responses. 33 | * Each bulk item response holds the result of an individual update. 34 | */ 35 | // TODO: Add Iterable for shard responses 36 | public class IndexUpdateByQueryResponse extends ActionResponse { 37 | 38 | private String index; 39 | private long totalHits; 40 | private long updated; 41 | private Map responsesByShard = Maps.newHashMap(); 42 | private Map failuresByShard = Maps.newHashMap(); 43 | 44 | IndexUpdateByQueryResponse() { 45 | } 46 | 47 | public IndexUpdateByQueryResponse(String index, ShardUpdateByQueryResponse[] shardResponses) { 48 | this.index = index; 49 | shardResponses(shardResponses); 50 | } 51 | 52 | public String index() { 53 | return index; 54 | } 55 | 56 | public String getIndex() { 57 | return index(); 58 | } 59 | 60 | public IndexUpdateByQueryResponse index(String index) { 61 | this.index = index; 62 | return this; 63 | } 64 | 65 | public IndexUpdateByQueryResponse shardResponses(ShardUpdateByQueryResponse[] responses) { 66 | for (ShardUpdateByQueryResponse response : responses) { 67 | totalHits += response.totalHits(); 68 | updated += response.updated(); 69 | if (response.failedShardExceptionMessage() != null) { 70 | failuresByShard.put(response.shardId(), response.failedShardExceptionMessage()); 71 | } 72 | if (response.bulkResponses().length != 0) { 73 | responsesByShard.put(response.shardId(), response.bulkResponses()); 74 | } 75 | } 76 | return this; 77 | } 78 | 79 | public Map responsesByShard() { 80 | return responsesByShard; 81 | } 82 | 83 | public Map failuresByShard() { 84 | return failuresByShard; 85 | } 86 | 87 | public long totalHits() { 88 | return totalHits; 89 | } 90 | 91 | public long updated() { 92 | return updated; 93 | } 94 | 95 | public long countShardResponses() { 96 | long count = 0; 97 | for (BulkItemResponse[] bulkItemResponses : responsesByShard.values()) { 98 | count += bulkItemResponses.length; 99 | } 100 | return count; 101 | } 102 | 103 | public boolean hasFailures() { 104 | return !failuresByShard.isEmpty(); 105 | } 106 | 107 | @Override 108 | public void readFrom(StreamInput in) throws IOException { 109 | super.readFrom(in); 110 | index = in.readString(); 111 | totalHits = in.readVLong(); 112 | updated = in.readVLong(); 113 | int size = in.readVInt(); 114 | for (int i = 0; i < size; i++) { 115 | int shardId = in.readVInt(); 116 | BulkItemResponse[] responses = new BulkItemResponse[in.readVInt()]; 117 | for (int j = 0; j < responses.length; j++) { 118 | responses[j] = BulkItemResponse.readBulkItem(in); 119 | } 120 | responsesByShard.put(shardId, responses); 121 | } 122 | 123 | size = in.readVInt(); 124 | for (int i = 0; i < size; i++) { 125 | int shardId = in.readVInt(); 126 | failuresByShard.put(shardId, in.readString()); 127 | } 128 | } 129 | 130 | @Override 131 | public void writeTo(StreamOutput out) throws IOException { 132 | super.writeTo(out); 133 | out.writeString(index); 134 | out.writeVLong(totalHits); 135 | out.writeVLong(updated); 136 | out.writeVInt(responsesByShard.size()); 137 | for (Map.Entry entry : responsesByShard.entrySet()) { 138 | out.writeVInt(entry.getKey()); 139 | out.writeVInt(entry.getValue().length); 140 | for (BulkItemResponse bulkItemResponse : entry.getValue()) { 141 | bulkItemResponse.writeTo(out); 142 | } 143 | } 144 | out.writeVInt(failuresByShard.size()); 145 | for (Map.Entry entry : failuresByShard.entrySet()) { 146 | out.writeVInt(entry.getKey()); 147 | out.writeString(entry.getValue()); 148 | } 149 | } 150 | 151 | public static IndexUpdateByQueryResponse readResponseItem(StreamInput in) throws IOException { 152 | IndexUpdateByQueryResponse response = new IndexUpdateByQueryResponse(); 153 | response.readFrom(in); 154 | return response; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/ShardUpdateByQueryRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ActionRequestValidationException; 23 | import org.elasticsearch.action.support.replication.ShardReplicationOperationRequest; 24 | import org.elasticsearch.common.Strings; 25 | import org.elasticsearch.common.bytes.BytesReference; 26 | import org.elasticsearch.common.io.stream.StreamInput; 27 | import org.elasticsearch.common.io.stream.StreamOutput; 28 | 29 | import java.io.IOException; 30 | 31 | import static org.elasticsearch.action.ValidateActions.addValidationError; 32 | 33 | /** 34 | * Represents a shard update by query request, that will be performed on the targeted shard. 35 | */ 36 | public class ShardUpdateByQueryRequest extends ShardReplicationOperationRequest { 37 | 38 | private String[] types; 39 | private BulkResponseOption bulkResponseOption; 40 | private String[] filteringAliases = Strings.EMPTY_ARRAY; 41 | 42 | private BytesReference source; 43 | 44 | private long nowInMillis; 45 | private int shardId = -1; 46 | private String targetNodeId; 47 | 48 | ShardUpdateByQueryRequest() { 49 | } 50 | 51 | ShardUpdateByQueryRequest(IndexUpdateByQueryRequest request, int shardId, String targetNodeId) { 52 | index(request.index()); 53 | consistencyLevel(request.consistencyLevel()); 54 | timeout = request.timeout(); 55 | listenerThreaded(request.listenerThreaded()); 56 | types = request.types(); 57 | source = request.source(); 58 | bulkResponseOption = request.bulkResponseOptions(); 59 | filteringAliases = request.filteringAliases(); 60 | this.nowInMillis = request.nowInMillis(); 61 | this.shardId = shardId; 62 | this.targetNodeId = targetNodeId; 63 | } 64 | 65 | public String[] types() { 66 | return types; 67 | } 68 | 69 | public BytesReference source() { 70 | return source; 71 | } 72 | 73 | public String[] filteringAliases() { 74 | return filteringAliases; 75 | } 76 | 77 | public long nowInMillis() { 78 | return nowInMillis; 79 | } 80 | 81 | public int shardId() { 82 | return shardId; 83 | } 84 | 85 | public String targetNodeId() { 86 | return targetNodeId; 87 | } 88 | 89 | public BulkResponseOption bulkResponseOptions() { 90 | return bulkResponseOption; 91 | } 92 | 93 | @Override 94 | public ActionRequestValidationException validate() { 95 | ActionRequestValidationException validationException = super.validate(); 96 | if (source == null) { 97 | validationException = addValidationError("Source is missing", validationException); 98 | } 99 | return validationException; 100 | } 101 | 102 | @Override 103 | public void readFrom(StreamInput in) throws IOException { 104 | super.readFrom(in); 105 | types = in.readStringArray(); 106 | bulkResponseOption = BulkResponseOption.fromId(in.readByte()); 107 | filteringAliases = in.readStringArray(); 108 | shardId = in.readVInt(); 109 | targetNodeId = in.readString(); 110 | source = in.readBytesReference(); 111 | } 112 | 113 | @Override 114 | public void writeTo(StreamOutput out) throws IOException { 115 | super.writeTo(out); 116 | out.writeStringArray(types); 117 | out.writeByte(bulkResponseOption.id()); 118 | out.writeStringArray(filteringAliases); 119 | out.writeVInt(shardId); 120 | out.writeString(targetNodeId); 121 | out.writeBytesReference(source); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/ShardUpdateByQueryResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ActionResponse; 23 | import org.elasticsearch.action.bulk.BulkItemResponse; 24 | import org.elasticsearch.common.io.stream.StreamInput; 25 | import org.elasticsearch.common.io.stream.StreamOutput; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * Update by query response from a single shard. 31 | */ 32 | // TODO: implement Iterabel for bulkResponses 33 | public class ShardUpdateByQueryResponse extends ActionResponse { 34 | 35 | private int shardId; 36 | private int totalHits; 37 | private int updated; 38 | private BulkItemResponse[] bulkResponses = new BulkItemResponse[0]; 39 | private String failedShardExceptionMessage; 40 | 41 | ShardUpdateByQueryResponse() { 42 | } 43 | 44 | public ShardUpdateByQueryResponse(int shardId) { 45 | this(shardId, 0, 0, new BulkItemResponse[0]); 46 | } 47 | 48 | public ShardUpdateByQueryResponse(int shardId, int totalHits, int updated, BulkItemResponse[] bulkResponses) { 49 | this.shardId = shardId; 50 | this.totalHits = totalHits; 51 | this.updated = updated; 52 | this.bulkResponses = bulkResponses; 53 | } 54 | 55 | public ShardUpdateByQueryResponse(int shardId, String failure) { 56 | this.shardId = shardId; 57 | this.failedShardExceptionMessage = failure; 58 | } 59 | 60 | public int shardId() { 61 | return shardId; 62 | } 63 | 64 | public void shardId(int shardId) { 65 | this.shardId = shardId; 66 | } 67 | 68 | public int updated() { 69 | return updated; 70 | } 71 | 72 | public String failedShardExceptionMessage() { 73 | return failedShardExceptionMessage; 74 | } 75 | 76 | public ShardUpdateByQueryResponse failedShardExceptionMessage(String failedShardExceptionMessage) { 77 | this.failedShardExceptionMessage = failedShardExceptionMessage; 78 | return this; 79 | } 80 | 81 | public BulkItemResponse[] bulkResponses() { 82 | return bulkResponses; 83 | } 84 | 85 | public ShardUpdateByQueryResponse responses(BulkItemResponse[] responses) { 86 | this.bulkResponses = responses; 87 | return this; 88 | } 89 | 90 | public int totalHits() { 91 | return totalHits; 92 | } 93 | 94 | @Override 95 | public void writeTo(StreamOutput out) throws IOException { 96 | super.writeTo(out); 97 | out.writeVInt(shardId); 98 | out.writeVInt(totalHits); 99 | out.writeVInt(updated); 100 | out.writeVInt(bulkResponses.length); 101 | for (BulkItemResponse response : bulkResponses) { 102 | response.writeTo(out); 103 | } 104 | out.writeOptionalString(failedShardExceptionMessage); 105 | } 106 | 107 | @Override 108 | public void readFrom(StreamInput in) throws IOException { 109 | super.readFrom(in); 110 | shardId = in.readVInt(); 111 | totalHits = in.readVInt(); 112 | updated = in.readVInt(); 113 | bulkResponses = new BulkItemResponse[in.readVInt()]; 114 | for (int i = 0; i < bulkResponses.length; i++) { 115 | bulkResponses[i] = BulkItemResponse.readBulkItem(in); 116 | } 117 | failedShardExceptionMessage = in.readOptionalString(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/TransportShardUpdateByQueryAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.support.ActionFilters; 23 | import org.elasticsearch.common.collect.Maps; 24 | import org.apache.lucene.index.IndexReader; 25 | import org.apache.lucene.search.DocIdSetIterator; 26 | import org.apache.lucene.util.FixedBitSet; 27 | import org.elasticsearch.ElasticsearchException; 28 | import org.elasticsearch.ExceptionsHelper; 29 | import org.elasticsearch.action.ActionListener; 30 | import org.elasticsearch.action.bulk.*; 31 | import org.elasticsearch.action.support.TransportAction; 32 | import org.elasticsearch.action.update.UpdateRequest; 33 | import org.elasticsearch.cache.recycler.CacheRecycler; 34 | import org.elasticsearch.cache.recycler.PageCacheRecycler; 35 | import org.elasticsearch.cluster.ClusterService; 36 | import org.elasticsearch.cluster.ClusterState; 37 | import org.elasticsearch.common.inject.Inject; 38 | import org.elasticsearch.common.lucene.TopLevelFixedBitSetCollector; 39 | import org.elasticsearch.common.settings.Settings; 40 | import org.elasticsearch.common.util.BigArrays; 41 | import org.elasticsearch.common.xcontent.XContentFactory; 42 | import org.elasticsearch.common.xcontent.XContentHelper; 43 | import org.elasticsearch.common.xcontent.XContentParser; 44 | import org.elasticsearch.index.fieldvisitor.JustUidFieldsVisitor; 45 | import org.elasticsearch.index.mapper.Uid; 46 | import org.elasticsearch.index.query.ParsedQuery; 47 | import org.elasticsearch.index.IndexService; 48 | import org.elasticsearch.index.shard.ShardId; 49 | import org.elasticsearch.index.shard.IndexShard; 50 | import org.elasticsearch.indices.IndicesService; 51 | import org.elasticsearch.script.ScriptParameterParser; 52 | import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; 53 | import org.elasticsearch.script.ScriptService; 54 | import org.elasticsearch.script.ScriptService.ScriptType; 55 | import org.elasticsearch.search.internal.DefaultSearchContext; 56 | import org.elasticsearch.search.internal.SearchContext; 57 | import org.elasticsearch.search.internal.ShardSearchLocalRequest; 58 | import org.elasticsearch.search.internal.ShardSearchRequest; 59 | import org.elasticsearch.threadpool.ThreadPool; 60 | import org.elasticsearch.transport.BaseTransportRequestHandler; 61 | import org.elasticsearch.transport.TransportChannel; 62 | import org.elasticsearch.transport.TransportService; 63 | 64 | import java.io.IOException; 65 | import java.util.ArrayList; 66 | import java.util.List; 67 | import java.util.Map; 68 | 69 | /** 70 | * Transport action that translates the shard update by query request into a bulk request. All actions are performed 71 | * locally and the bulk requests are then forwarded to the replica shards (this logic is done inside 72 | * {@link TransportShardBulkAction} which this transport action uses). 73 | */ 74 | public class TransportShardUpdateByQueryAction extends TransportAction { 75 | 76 | public final static String ACTION_NAME = UpdateByQueryAction.NAME + "/shard"; 77 | 78 | private final TransportShardBulkAction bulkAction; 79 | private final IndicesService indicesService; 80 | private final ClusterService clusterService; 81 | private final ScriptService scriptService; 82 | private final int batchSize; 83 | private final CacheRecycler cacheRecycler; 84 | private final PageCacheRecycler pageCacheRecycler; 85 | private final BigArrays bigArrays; 86 | 87 | @Inject 88 | public TransportShardUpdateByQueryAction(Settings settings, 89 | ThreadPool threadPool, 90 | TransportShardBulkAction bulkAction, 91 | ActionFilters actionFilters, 92 | TransportService transportService, 93 | CacheRecycler cacheRecycler, IndicesService indicesService, 94 | ClusterService clusterService, 95 | ScriptService scriptService, 96 | PageCacheRecycler pageCacheRecycler, 97 | BigArrays bigArrays) { 98 | super(settings, ACTION_NAME, threadPool, actionFilters); 99 | this.bulkAction = bulkAction; 100 | this.cacheRecycler = cacheRecycler; 101 | this.indicesService = indicesService; 102 | this.clusterService = clusterService; 103 | this.scriptService = scriptService; 104 | this.pageCacheRecycler = pageCacheRecycler; 105 | this.bigArrays = bigArrays; 106 | this.batchSize = componentSettings.getAsInt("bulk_size", 1000); 107 | transportService.registerHandler(ACTION_NAME, new TransportHandler()); 108 | } 109 | 110 | protected void doExecute(final ShardUpdateByQueryRequest request, final ActionListener listener) { 111 | String localNodeId = clusterService.state().nodes().localNodeId(); 112 | if (!localNodeId.equals(request.targetNodeId())) { 113 | throw new ElasticsearchException("Request arrived on the wrong node. This shouldn't happen!"); 114 | } 115 | 116 | if (request.operationThreaded()) { 117 | threadPool.executor(ThreadPool.Names.BULK).execute(new Runnable() { 118 | 119 | public void run() { 120 | doExecuteInternal(request, listener); 121 | } 122 | 123 | }); 124 | } else { 125 | doExecuteInternal(request, listener); 126 | } 127 | } 128 | 129 | private void doExecuteInternal(ShardUpdateByQueryRequest request, ActionListener listener) { 130 | IndexService indexService = indicesService.indexServiceSafe(request.index()); 131 | IndexShard indexShard = indexService.shardSafe(request.shardId()); 132 | ShardSearchRequest shardSearchRequest = new ShardSearchLocalRequest(request.types(), request.nowInMillis(), request.filteringAliases()); 133 | SearchContext searchContext = new DefaultSearchContext( 134 | 0, 135 | shardSearchRequest, 136 | null, indexShard.acquireSearcher("update_by_query"), indexService, indexShard, 137 | scriptService, cacheRecycler, pageCacheRecycler, bigArrays, threadPool.estimatedTimeInMillisCounter() 138 | ); 139 | SearchContext.setCurrent(searchContext); 140 | try { 141 | UpdateByQueryContext ubqContext = parseRequestSource(indexService, request, searchContext); 142 | searchContext.preProcess(); 143 | // TODO: Work per segment. The collector should collect docs per segment instead of one big set of top level ids 144 | TopLevelFixedBitSetCollector bitSetCollector = new TopLevelFixedBitSetCollector(searchContext.searcher().getIndexReader().maxDoc()); 145 | searchContext.searcher().search(searchContext.query(), searchContext.aliasFilter(), bitSetCollector); 146 | FixedBitSet docsToUpdate = bitSetCollector.getBitSet(); 147 | 148 | int docsToUpdateCount = docsToUpdate.cardinality(); 149 | logger.trace("[{}][{}] {} docs to update", request.index(), request.shardId(), docsToUpdateCount); 150 | 151 | if (docsToUpdateCount == 0) { 152 | ShardUpdateByQueryResponse response = new ShardUpdateByQueryResponse(request.shardId()); 153 | listener.onResponse(response); 154 | searchContext.close(); 155 | return; 156 | } 157 | BatchedShardUpdateByQueryExecutor bulkExecutor = new BatchedShardUpdateByQueryExecutor( 158 | listener, docsToUpdate, request, ubqContext 159 | ); 160 | bulkExecutor.executeBulkIndex(); 161 | } catch (Throwable t) { 162 | // If we end up here then BatchedShardUpdateByQueryExecutor#finalizeBulkActions isn't invoked 163 | // so we need to release the search context. 164 | searchContext.close(); 165 | listener.onFailure(t); 166 | } finally { 167 | SearchContext.removeCurrent(); 168 | } 169 | } 170 | 171 | private UpdateByQueryContext parseRequestSource(IndexService indexService, ShardUpdateByQueryRequest request, SearchContext context) { 172 | ScriptParameterParser scriptParameterParser = new ScriptParameterParser(); 173 | ParsedQuery parsedQuery = null; 174 | String script = null; 175 | ScriptType scriptType = null; 176 | String scriptLang = null; 177 | Map params = Maps.newHashMap(); 178 | try { 179 | XContentParser parser = XContentHelper.createParser(request.source()); 180 | for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { 181 | if (token == XContentParser.Token.FIELD_NAME) { 182 | String fieldName = parser.currentName(); 183 | if ("query".equals(fieldName)) { 184 | parsedQuery = indexService.queryParserService().parse(parser); 185 | } else if ("query_binary".equals(fieldName)) { 186 | parser.nextToken(); 187 | byte[] querySource = parser.binaryValue(); 188 | XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource); 189 | parsedQuery = indexService.queryParserService().parse(qSourceParser); 190 | } else if ("params".equals(fieldName)) { 191 | parser.nextToken(); 192 | params = parser.map(); 193 | } else { 194 | token = parser.nextToken(); 195 | scriptParameterParser.token(fieldName, token, parser); 196 | } 197 | } 198 | } 199 | } catch (Exception e) { 200 | throw new ElasticsearchException("Couldn't parse query from source.", e); 201 | } 202 | 203 | if (parsedQuery == null) { 204 | throw new ElasticsearchException("Query is required"); 205 | } 206 | 207 | ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue(); 208 | if (scriptValue != null) { 209 | script = scriptValue.script(); 210 | scriptType = scriptValue.scriptType(); 211 | } else { 212 | throw new ElasticsearchException("A script is required"); 213 | } 214 | scriptLang = scriptParameterParser.lang(); 215 | context.parsedQuery(parsedQuery); 216 | return new UpdateByQueryContext(context, batchSize, clusterService.state(), script, scriptType, scriptLang, params); 217 | } 218 | 219 | 220 | class BatchedShardUpdateByQueryExecutor implements ActionListener { 221 | 222 | private final ActionListener finalResponseListener; 223 | private final DocIdSetIterator iterator; 224 | private final int matches; 225 | private final ShardUpdateByQueryRequest request; 226 | private final List receivedBulkItemResponses; 227 | private final UpdateByQueryContext updateByQueryContext; 228 | 229 | // Counter for keeping tracker number of docs that have been updated. 230 | // No need for sync now since onResponse method in synchronized 231 | private int updated; 232 | 233 | BatchedShardUpdateByQueryExecutor(ActionListener finalResponseListener, 234 | FixedBitSet docsToUpdate, 235 | ShardUpdateByQueryRequest request, 236 | UpdateByQueryContext updateByQueryContext) { 237 | this.iterator = docsToUpdate.iterator(); 238 | this.matches = docsToUpdate.cardinality(); 239 | this.request = request; 240 | this.finalResponseListener = finalResponseListener; 241 | this.receivedBulkItemResponses = new ArrayList(); 242 | this.updateByQueryContext = updateByQueryContext; 243 | } 244 | 245 | // Call can be invoked with a Network thread. Replica isn't on the same node... Therefore when 246 | // need to continue with the bulk do it in a new thread. One thread will enter at the time. 247 | public synchronized void onResponse(BulkShardResponse bulkShardResponse) { 248 | try { 249 | for (BulkItemResponse itemResponse : bulkShardResponse.getResponses()) { 250 | if (!itemResponse.isFailed()) { 251 | updated++; 252 | } 253 | switch (request.bulkResponseOptions()) { 254 | case ALL: 255 | receivedBulkItemResponses.add(itemResponse); 256 | break; 257 | case FAILED: 258 | if (itemResponse.isFailed()) { 259 | receivedBulkItemResponses.add(itemResponse); 260 | } 261 | break; 262 | case NONE: 263 | break; 264 | } 265 | } 266 | if (iterator.docID() == DocIdSetIterator.NO_MORE_DOCS) { 267 | finalizeBulkActions(null); 268 | } else { 269 | threadPool.executor(ThreadPool.Names.BULK).execute(new Runnable() { 270 | public void run() { 271 | try { 272 | executeBulkIndex(); 273 | } catch (Throwable e) { 274 | onFailure(e); 275 | } 276 | } 277 | }); 278 | } 279 | } catch (Throwable t) { 280 | onFailure(t); 281 | } 282 | } 283 | 284 | public synchronized void onFailure(Throwable e) { 285 | try { 286 | logger.debug("error while executing bulk operations for an update by query action, sending partial response...", e); 287 | finalizeBulkActions(e); 288 | } catch (Throwable t) { 289 | finalResponseListener.onFailure(t); 290 | } 291 | } 292 | 293 | public void executeBulkIndex() throws IOException { 294 | fillBatch(iterator, updateByQueryContext.searchContext.searcher().getIndexReader(), request, updateByQueryContext.bulkItemRequestsBulkList); 295 | logger.trace("[{}][{}] executing bulk request with size {}", request.index(), request.shardId(), updateByQueryContext.bulkItemRequestsBulkList.size()); 296 | if (updateByQueryContext.bulkItemRequestsBulkList.isEmpty()) { 297 | onResponse(new PublicBulkShardResponse(new ShardId(request.index(), request.shardId()), new BulkItemResponse[0])); 298 | } else { 299 | // We are already on the primary shard. Only have network traffic for replica shards 300 | // Also no need for threadpool b/c TransUpdateAction uses it already for local requests. 301 | BulkItemRequest[] bulkItemRequests = 302 | updateByQueryContext.bulkItemRequestsBulkList.toArray(new BulkItemRequest[updateByQueryContext.bulkItemRequestsBulkList.size()]); 303 | // We clear the list, since the array is already created 304 | updateByQueryContext.bulkItemRequestsBulkList.clear(); 305 | BulkRequest fakeBulkRequest = new BulkRequest(); 306 | final BulkShardRequest bulkShardRequest = new PublicBulkShardRequest( 307 | fakeBulkRequest, request.index(), request.shardId(), false, bulkItemRequests 308 | ); 309 | // The batches are already threaded... No need for new thread 310 | bulkShardRequest.operationThreaded(false); 311 | bulkAction.execute(bulkShardRequest, this); 312 | } 313 | } 314 | 315 | private void finalizeBulkActions(Throwable e) { 316 | updateByQueryContext.searchContext.close(); 317 | BulkItemResponse[] bulkResponses = receivedBulkItemResponses.toArray(new BulkItemResponse[receivedBulkItemResponses.size()]); 318 | receivedBulkItemResponses.clear(); 319 | ShardUpdateByQueryResponse finalResponse = new ShardUpdateByQueryResponse( 320 | request.shardId(), matches, updated, bulkResponses 321 | ); 322 | 323 | if (e != null) { 324 | finalResponse.failedShardExceptionMessage(ExceptionsHelper.detailedMessage(e)); 325 | } 326 | finalResponseListener.onResponse(finalResponse); 327 | } 328 | 329 | // TODO: Work per segment. The collector should collect docs per segment instead of one big set of top level ids 330 | private void fillBatch(DocIdSetIterator iterator, IndexReader indexReader, ShardUpdateByQueryRequest request, 331 | List bulkItemRequests) throws IOException { 332 | int counter = 0; 333 | for (int docID = iterator.nextDoc(); docID != DocIdSetIterator.NO_MORE_DOCS; docID = iterator.nextDoc()) { 334 | JustUidFieldsVisitor fieldVisitor = new JustUidFieldsVisitor(); 335 | indexReader.document(docID, fieldVisitor); 336 | Uid uid = fieldVisitor.uid(); 337 | UpdateRequest updateRequest = new UpdateRequest(request.index(), uid.type(), uid.id()) 338 | .script(updateByQueryContext.scriptString, updateByQueryContext.scriptLang, 339 | updateByQueryContext.scriptType, updateByQueryContext.scriptParams); 340 | bulkItemRequests.add(new BulkItemRequest(counter, updateRequest)); 341 | 342 | if (++counter == batchSize) { 343 | break; 344 | } 345 | } 346 | } 347 | 348 | } 349 | 350 | class TransportHandler extends BaseTransportRequestHandler { 351 | 352 | public ShardUpdateByQueryRequest newInstance() { 353 | return new ShardUpdateByQueryRequest(); 354 | } 355 | 356 | public String executor() { 357 | return ThreadPool.Names.SAME; 358 | } 359 | 360 | public void messageReceived(final ShardUpdateByQueryRequest request, final TransportChannel channel) throws Exception { 361 | // no need to have a threaded listener since we just send back a response 362 | request.listenerThreaded(false); 363 | execute(request, new ActionListener() { 364 | 365 | public void onResponse(ShardUpdateByQueryResponse result) { 366 | try { 367 | channel.sendResponse(result); 368 | } catch (Exception e) { 369 | onFailure(e); 370 | } 371 | } 372 | 373 | public void onFailure(Throwable e) { 374 | try { 375 | channel.sendResponse(e); 376 | } catch (Exception e1) { 377 | logger.warn("Failed to send response for get", e1); 378 | } 379 | } 380 | 381 | }); 382 | } 383 | } 384 | 385 | } 386 | 387 | class UpdateByQueryContext { 388 | 389 | final SearchContext searchContext; 390 | final List bulkItemRequestsBulkList; 391 | final ClusterState clusterState; 392 | 393 | final String scriptString; 394 | final ScriptType scriptType; 395 | final String scriptLang; 396 | final Map scriptParams; 397 | 398 | UpdateByQueryContext(SearchContext searchContext, int batchSize, ClusterState clusterState, String scriptString, 399 | ScriptType scriptType, String scriptLang, Map scriptParams) { 400 | this.searchContext = searchContext; 401 | this.clusterState = clusterState; 402 | this.bulkItemRequestsBulkList = new ArrayList(batchSize); 403 | this.scriptString = scriptString; 404 | this.scriptType = scriptType; 405 | this.scriptLang = scriptLang; 406 | this.scriptParams = scriptParams; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/TransportUpdateByQueryAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.support.ActionFilters; 23 | import org.elasticsearch.common.collect.Lists; 24 | import org.elasticsearch.ExceptionsHelper; 25 | import org.elasticsearch.action.ActionListener; 26 | import org.elasticsearch.action.UnavailableShardsException; 27 | import org.elasticsearch.action.support.IndicesOptions; 28 | import org.elasticsearch.action.support.TransportAction; 29 | import org.elasticsearch.cluster.ClusterChangedEvent; 30 | import org.elasticsearch.cluster.ClusterService; 31 | import org.elasticsearch.cluster.ClusterState; 32 | import org.elasticsearch.cluster.TimeoutClusterStateListener; 33 | import org.elasticsearch.cluster.block.ClusterBlockException; 34 | import org.elasticsearch.cluster.block.ClusterBlockLevel; 35 | import org.elasticsearch.cluster.metadata.MetaData; 36 | import org.elasticsearch.cluster.node.DiscoveryNode; 37 | import org.elasticsearch.cluster.node.DiscoveryNodes; 38 | import org.elasticsearch.cluster.routing.GroupShardsIterator; 39 | import org.elasticsearch.cluster.routing.ShardIterator; 40 | import org.elasticsearch.cluster.routing.ShardRouting; 41 | import org.elasticsearch.common.Nullable; 42 | import org.elasticsearch.common.inject.Inject; 43 | import org.elasticsearch.common.settings.Settings; 44 | import org.elasticsearch.common.unit.TimeValue; 45 | import org.elasticsearch.node.NodeClosedException; 46 | import org.elasticsearch.threadpool.ThreadPool; 47 | import org.elasticsearch.transport.*; 48 | 49 | import java.util.List; 50 | import java.util.Map; 51 | import java.util.Set; 52 | import java.util.concurrent.atomic.AtomicInteger; 53 | import java.util.concurrent.atomic.AtomicReferenceArray; 54 | 55 | /** 56 | * Delegates a {@link IndexUpdateByQueryRequest} to the primary shards of the index this request is targeted to. 57 | * The requests is transformed into {@link ShardUpdateByQueryRequest} and send to each primary shard. Afterwards 58 | * the responses from the shards are merged into a final response which is send to the client. 59 | */ 60 | // Perhaps create a base transport update action that sends shard requests to primary shards only 61 | public class TransportUpdateByQueryAction extends TransportAction { 62 | 63 | private final TransportService transportService; 64 | 65 | private final ClusterService clusterService; 66 | 67 | private final TransportShardUpdateByQueryAction transportShardUpdateByQueryAction; 68 | 69 | @Inject 70 | public TransportUpdateByQueryAction(Settings settings, 71 | ThreadPool threadPool, 72 | ActionFilters actionFilters, 73 | TransportService transportService, 74 | ClusterService clusterService, 75 | TransportShardUpdateByQueryAction transportShardUpdateByQueryAction) { 76 | super(settings, UpdateByQueryAction.NAME, threadPool, actionFilters); 77 | this.transportService = transportService; 78 | this.clusterService = clusterService; 79 | this.transportShardUpdateByQueryAction = transportShardUpdateByQueryAction; 80 | transportService.registerHandler(UpdateByQueryAction.NAME, new TransportHandler()); 81 | } 82 | 83 | protected void doExecute(UpdateByQueryRequest request, ActionListener listener) { 84 | request.nowInMillis = System.currentTimeMillis(); 85 | MetaData metaData = clusterService.state().metaData(); 86 | String[] concreteIndices = metaData.concreteIndices(IndicesOptions.lenientExpandOpen(), request.indices()); 87 | Map> routingMap = metaData.resolveSearchRouting(request.routing(), request.indices()); 88 | if (concreteIndices.length == 1) { 89 | doExecuteIndexRequest(request, metaData, concreteIndices[0], routingMap, new SingleIndexUpdateByQueryActionListener(request.nowInMillis, listener)); 90 | } else { 91 | MultipleIndexUpdateByQueryActionListener indexActionListener = 92 | new MultipleIndexUpdateByQueryActionListener(request.nowInMillis, listener, concreteIndices.length); 93 | for (String concreteIndex : concreteIndices) { 94 | doExecuteIndexRequest(request, metaData, concreteIndex, routingMap, indexActionListener); 95 | } 96 | } 97 | 98 | } 99 | 100 | private static class MultipleIndexUpdateByQueryActionListener implements ActionListener { 101 | 102 | private final long nowInMillis; 103 | private final ActionListener listener; 104 | private final int expectedNumberOfResponses; 105 | private final AtomicReferenceArray successFullIndexResponses; 106 | private final AtomicReferenceArray failedIndexResponses; 107 | private final AtomicInteger indexCounter; 108 | private final AtomicInteger completionCounter; 109 | 110 | private MultipleIndexUpdateByQueryActionListener(long nowInMillis, ActionListener listener, int expectedNumberOfResponses) { 111 | this.nowInMillis = nowInMillis; 112 | this.listener = listener; 113 | successFullIndexResponses = new AtomicReferenceArray(expectedNumberOfResponses); 114 | failedIndexResponses = new AtomicReferenceArray(expectedNumberOfResponses); 115 | this.expectedNumberOfResponses = expectedNumberOfResponses; 116 | indexCounter = new AtomicInteger(); 117 | completionCounter = new AtomicInteger(expectedNumberOfResponses); 118 | } 119 | 120 | public void onResponse(IndexUpdateByQueryResponse indexUpdateByQueryResponse) { 121 | successFullIndexResponses.set(indexCounter.getAndIncrement(), indexUpdateByQueryResponse); 122 | if (completionCounter.decrementAndGet() == 0) { 123 | finishHim(); 124 | } 125 | } 126 | 127 | public void onFailure(Throwable e) { 128 | failedIndexResponses.set(indexCounter.getAndIncrement(), e); 129 | if (completionCounter.decrementAndGet() == 0) { 130 | finishHim(); 131 | } 132 | } 133 | 134 | private void finishHim() { 135 | long tookInMillis = System.currentTimeMillis() - nowInMillis; 136 | UpdateByQueryResponse response = new UpdateByQueryResponse(tookInMillis); 137 | List indexResponses = Lists.newArrayList(); 138 | List indexFailures = Lists.newArrayList(); 139 | for (int i = 0; i < expectedNumberOfResponses; i++) { 140 | IndexUpdateByQueryResponse indexResponse = successFullIndexResponses.get(i); 141 | if (indexResponse != null) { 142 | indexResponses.add(indexResponse); 143 | } else { 144 | indexFailures.add(ExceptionsHelper.detailedMessage(failedIndexResponses.get(i))); 145 | } 146 | } 147 | response.indexResponses(indexResponses.toArray(new IndexUpdateByQueryResponse[indexResponses.size()])); 148 | response.mainFailures(indexFailures.toArray(new String[indexFailures.size()])); 149 | listener.onResponse(response); 150 | } 151 | } 152 | 153 | private static class SingleIndexUpdateByQueryActionListener implements ActionListener { 154 | 155 | private final long nowInMillis; 156 | private final ActionListener listener; 157 | 158 | private SingleIndexUpdateByQueryActionListener(long nowInMillis, ActionListener listener) { 159 | this.listener = listener; 160 | this.nowInMillis = nowInMillis; 161 | } 162 | 163 | public void onResponse(IndexUpdateByQueryResponse indexUpdateByQueryResponse) { 164 | long tookInMillis = System.currentTimeMillis() - nowInMillis; 165 | UpdateByQueryResponse finalResponse = new UpdateByQueryResponse(tookInMillis, indexUpdateByQueryResponse); 166 | listener.onResponse(finalResponse); 167 | } 168 | 169 | public void onFailure(Throwable e) { 170 | long tookInMillis = System.currentTimeMillis() - nowInMillis; 171 | UpdateByQueryResponse finalResponse = new UpdateByQueryResponse(tookInMillis); 172 | finalResponse.mainFailures(new String[]{ExceptionsHelper.detailedMessage(e)}); 173 | listener.onResponse(finalResponse); 174 | } 175 | 176 | } 177 | 178 | 179 | // Index operations happen below here: 180 | 181 | protected void doExecuteIndexRequest(UpdateByQueryRequest request, MetaData metaData, String concreteIndex, 182 | @Nullable Map> routingMap, ActionListener listener) { 183 | String[] filteringAliases = metaData.filteringAliases(concreteIndex, request.indices()); 184 | Set routing = null; 185 | if (routingMap != null) { 186 | routing = routingMap.get(concreteIndex); 187 | } 188 | IndexUpdateByQueryRequest indexRequest = new IndexUpdateByQueryRequest(request, concreteIndex, filteringAliases, routing); 189 | new UpdateByQueryIndexOperationAction(indexRequest, listener).startExecution(); 190 | } 191 | 192 | private class UpdateByQueryIndexOperationAction { 193 | 194 | final IndexUpdateByQueryRequest request; 195 | final ActionListener indexActionListener; 196 | 197 | private UpdateByQueryIndexOperationAction(IndexUpdateByQueryRequest request, ActionListener listener) { 198 | this.request = request; 199 | this.indexActionListener = listener; 200 | } 201 | 202 | void startExecution() { 203 | startExecution(false); 204 | } 205 | 206 | boolean startExecution(boolean fromClusterEvent) { 207 | ClusterState state = clusterService.state(); 208 | ClusterBlockException blockException = state.blocks().globalBlockedException(ClusterBlockLevel.WRITE); 209 | if (blockException != null) { 210 | logger.trace("[{}] global block exception, retrying...", request.index()); 211 | overallRetry(fromClusterEvent, null, blockException); 212 | return false; 213 | } 214 | blockException = state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, request.index()); 215 | if (blockException != null) { 216 | logger.trace("[{}] index block exception, retrying...", request.index()); 217 | overallRetry(fromClusterEvent, null, blockException); 218 | return false; 219 | } 220 | 221 | List primaryShards = Lists.newArrayList(); 222 | GroupShardsIterator groupShardsIterator = 223 | clusterService.operationRouting().deleteByQueryShards(state, request.index(), request.routing()); 224 | for (ShardIterator shardIt : groupShardsIterator) { 225 | for (ShardRouting shardRouting = shardIt.nextOrNull(); shardRouting != null; shardRouting = shardIt.nextOrNull()) { 226 | if (shardRouting.primary()) { 227 | if (shardRouting.started()) { 228 | primaryShards.add(shardRouting); 229 | } else { 230 | logger.trace( 231 | "[{}][{}] required primary shard isn't available, retrying...", 232 | request.index(), shardRouting.id() 233 | ); 234 | overallRetry(fromClusterEvent, shardRouting, null); 235 | return false; 236 | } 237 | } 238 | } 239 | } 240 | 241 | if (primaryShards.size() != groupShardsIterator.size()) { 242 | logger.trace( 243 | "[{}] not all required primary shards[{}/{}] are available for index[{}], retrying...", 244 | request.index(), primaryShards.size(), groupShardsIterator.size() 245 | ); 246 | overallRetry(fromClusterEvent, null, null); 247 | return false; 248 | } 249 | 250 | logger.trace("[{}] executing IndexUpdateByQueryRequest", request.index()); 251 | DiscoveryNodes nodes = state.nodes(); 252 | final ShardResponseListener responseListener = new ShardResponseListener(primaryShards.size(), indexActionListener); 253 | for (ShardRouting shard : primaryShards) { 254 | logger.trace("[{}][{}] executing ShardUpdateByQueryRequest", request.index(), shard.id()); 255 | if (shard.currentNodeId().equals(nodes.localNodeId())) { 256 | executeLocally(shard, responseListener); 257 | } else { 258 | executeRemotely(shard, nodes, responseListener); 259 | } 260 | } 261 | 262 | return true; 263 | } 264 | 265 | void overallRetry(boolean fromClusterEvent, final ShardRouting shardRouting, @Nullable final Throwable failure) { 266 | if (!fromClusterEvent) { 267 | clusterService.add(request.timeout(), new TimeoutClusterStateListener() { 268 | 269 | public void postAdded() { 270 | logger.trace("[{}] post added, retrying update by query", request.index()); 271 | if (startExecution(true)) { 272 | // if we managed to start and perform the operation on the primary, we can remove this listener 273 | clusterService.remove(this); 274 | } 275 | } 276 | 277 | public void onClose() { 278 | logger.trace("[{}] update by query for, node closed", request.index()); 279 | clusterService.remove(this); 280 | indexActionListener.onFailure(new NodeClosedException(clusterService.localNode())); 281 | } 282 | 283 | public void clusterChanged(ClusterChangedEvent event) { 284 | logger.trace("[{}] cluster changed, retrying update by query", request.index()); 285 | if (startExecution(true)) { 286 | // if we managed to start and perform the operation on the primary, we can remove this listener 287 | clusterService.remove(this); 288 | } 289 | } 290 | 291 | public void onTimeout(TimeValue timeValue) { 292 | // just to be on the safe side, see if we can start it now? 293 | logger.trace("[{}] timeout, retrying update by query", request.index()); 294 | if (startExecution(true)) { 295 | clusterService.remove(this); 296 | return; 297 | } 298 | clusterService.remove(this); 299 | Throwable listenerFailure = failure; 300 | if (listenerFailure == null) { 301 | if (shardRouting == null) { 302 | listenerFailure = new UnavailableShardsException(null, "no available shards: Timeout waiting for [" + timeValue + "], request: " + request.toString()); 303 | } else { 304 | listenerFailure = new UnavailableShardsException(shardRouting.shardId(), "[" + shardRouting.shardId() + "] not started, Timeout waiting for [" + timeValue + "], request: " + request.toString()); 305 | } 306 | } 307 | indexActionListener.onFailure(listenerFailure); 308 | } 309 | }); 310 | } 311 | } 312 | 313 | void executeLocally(final ShardRouting shard, final ShardResponseListener responseListener) { 314 | final ShardUpdateByQueryRequest localShardRequest = new ShardUpdateByQueryRequest(request, shard.id(), shard.currentNodeId()); 315 | transportShardUpdateByQueryAction.execute(localShardRequest, new ActionListener() { 316 | 317 | public void onResponse(ShardUpdateByQueryResponse shardUpdateByQueryResponse) { 318 | responseListener.handleResponse(shardUpdateByQueryResponse); 319 | } 320 | 321 | public void onFailure(Throwable e) { 322 | responseListener.handleException(e, shard); 323 | } 324 | }); 325 | } 326 | 327 | void executeRemotely(final ShardRouting shard, DiscoveryNodes nodes, final ShardResponseListener responseListener) { 328 | final DiscoveryNode discoveryNode = nodes.get(shard.currentNodeId()); 329 | if (discoveryNode == null) { 330 | responseListener.handleException(new RuntimeException("No node for shard"), shard); 331 | return; 332 | } 333 | 334 | ShardUpdateByQueryRequest localShardRequest = new ShardUpdateByQueryRequest(request, shard.id(), shard.currentNodeId()); 335 | transportService.sendRequest(discoveryNode, TransportShardUpdateByQueryAction.ACTION_NAME, 336 | localShardRequest, new BaseTransportResponseHandler() { 337 | 338 | public ShardUpdateByQueryResponse newInstance() { 339 | return new ShardUpdateByQueryResponse(); 340 | } 341 | 342 | public void handleResponse(ShardUpdateByQueryResponse response) { 343 | responseListener.handleResponse(response); 344 | } 345 | 346 | public void handleException(TransportException e) { 347 | responseListener.handleException(e, shard); 348 | } 349 | 350 | public String executor() { 351 | return ThreadPool.Names.SAME; 352 | } 353 | }); 354 | } 355 | 356 | private class ShardResponseListener { 357 | 358 | final AtomicReferenceArray shardResponses; 359 | 360 | final AtomicInteger indexCounter; 361 | final AtomicInteger completionCounter; 362 | 363 | final ActionListener finalListener; 364 | 365 | final int numberOfExpectedShardResponses; 366 | 367 | private ShardResponseListener(int numberOfPrimaryShards, ActionListener finalListener) { 368 | shardResponses = new AtomicReferenceArray(numberOfPrimaryShards); 369 | numberOfExpectedShardResponses = numberOfPrimaryShards; 370 | indexCounter = new AtomicInteger(); 371 | completionCounter = new AtomicInteger(numberOfExpectedShardResponses); 372 | this.finalListener = finalListener; 373 | } 374 | 375 | void handleResponse(ShardUpdateByQueryResponse response) { 376 | shardResponses.set(indexCounter.getAndIncrement(), response); 377 | if (completionCounter.decrementAndGet() == 0) { 378 | finalizeAction(); 379 | } 380 | } 381 | 382 | void handleException(Throwable e, ShardRouting shard) { 383 | logger.error("[{}][{}] error while executing update by query shard request", e, request.index(), shard.id()); 384 | String failure = ExceptionsHelper.detailedMessage(e); 385 | shardResponses.set(indexCounter.getAndIncrement(), new ShardUpdateByQueryResponse(shard.id(), failure)); 386 | if (completionCounter.decrementAndGet() == 0) { 387 | finalizeAction(); 388 | } 389 | } 390 | 391 | void finalizeAction() { 392 | ShardUpdateByQueryResponse[] responses = new ShardUpdateByQueryResponse[shardResponses.length()]; 393 | for (int i = 0; i < shardResponses.length(); i++) { 394 | responses[i] = shardResponses.get(i); 395 | } 396 | IndexUpdateByQueryResponse finalResponse = new IndexUpdateByQueryResponse( 397 | request.index(), 398 | responses 399 | ); 400 | finalListener.onResponse(finalResponse); 401 | } 402 | 403 | } 404 | 405 | } 406 | 407 | private class TransportHandler extends BaseTransportRequestHandler { 408 | 409 | public UpdateByQueryRequest newInstance() { 410 | return new UpdateByQueryRequest(); 411 | } 412 | 413 | public String executor() { 414 | return ThreadPool.Names.SAME; 415 | } 416 | 417 | public void messageReceived(UpdateByQueryRequest request, final TransportChannel channel) throws Exception { 418 | // no need to have a threaded listener since we just send back a response 419 | request.listenerThreaded(false); 420 | doExecute(request, new ActionListener() { 421 | 422 | public void onResponse(UpdateByQueryResponse result) { 423 | try { 424 | channel.sendResponse(result); 425 | } catch (Exception e) { 426 | onFailure(e); 427 | } 428 | } 429 | 430 | public void onFailure(Throwable e) { 431 | try { 432 | channel.sendResponse(e); 433 | } catch (Exception e1) { 434 | logger.warn("Failed to send response for get", e1); 435 | } 436 | } 437 | }); 438 | } 439 | } 440 | 441 | } 442 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/UpdateByQueryAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ClientAction; 23 | import org.elasticsearch.client.Client; 24 | 25 | /** 26 | * Entry point from client to transport actions. 27 | */ 28 | public class UpdateByQueryAction extends ClientAction { 29 | 30 | public static final UpdateByQueryAction INSTANCE = new UpdateByQueryAction(); 31 | public static final String NAME = "updateByQuery"; 32 | 33 | private UpdateByQueryAction() { 34 | super(NAME); 35 | } 36 | 37 | public UpdateByQueryResponse newResponse() { 38 | return new UpdateByQueryResponse(); 39 | } 40 | 41 | public UpdateByQueryRequestBuilder newRequestBuilder(Client client) { 42 | return new UpdateByQueryRequestBuilder(client); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/UpdateByQueryRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ActionRequestValidationException; 23 | import org.elasticsearch.action.support.replication.IndicesReplicationOperationRequest; 24 | import org.elasticsearch.client.Requests; 25 | import org.elasticsearch.common.Strings; 26 | import org.elasticsearch.common.bytes.BytesReference; 27 | import org.elasticsearch.common.io.stream.StreamInput; 28 | import org.elasticsearch.common.io.stream.StreamOutput; 29 | import org.elasticsearch.common.xcontent.XContentType; 30 | 31 | import java.io.IOException; 32 | 33 | import static org.elasticsearch.action.ValidateActions.addValidationError; 34 | 35 | /** 36 | * Represents an update by query request. 37 | */ 38 | public class UpdateByQueryRequest extends IndicesReplicationOperationRequest { 39 | 40 | private static final XContentType contentType = Requests.CONTENT_TYPE; 41 | 42 | private String[] types = Strings.EMPTY_ARRAY; 43 | private BulkResponseOption bulkResponseOption = BulkResponseOption.NONE; 44 | private String routing; 45 | private BytesReference source; 46 | 47 | long nowInMillis; 48 | 49 | UpdateByQueryRequest() { 50 | 51 | } 52 | 53 | public UpdateByQueryRequest(String[] indices, String[] types) { 54 | this.indices = indices; 55 | this.types = types; 56 | } 57 | 58 | public String[] types() { 59 | return types; 60 | } 61 | 62 | public UpdateByQueryRequest types(String... types) { 63 | this.types = types; 64 | return this; 65 | } 66 | 67 | public UpdateByQueryRequest source(BytesReference source) { 68 | this.source = source; 69 | return this; 70 | } 71 | 72 | public UpdateByQueryRequest source(UpdateByQuerySourceBuilder sourceBuilder) { 73 | this.source = sourceBuilder.buildAsBytes(contentType); 74 | return this; 75 | } 76 | 77 | public BytesReference source() { 78 | return source; 79 | } 80 | 81 | public BulkResponseOption bulkResponseOptions() { 82 | return bulkResponseOption; 83 | } 84 | 85 | public UpdateByQueryRequest bulkResponseOptions(BulkResponseOption bulkResponseOption) { 86 | this.bulkResponseOption = bulkResponseOption; 87 | return this; 88 | } 89 | 90 | public String routing() { 91 | return routing; 92 | } 93 | 94 | public UpdateByQueryRequest routing(String routing) { 95 | this.routing = routing; 96 | return this; 97 | } 98 | 99 | @Override 100 | public ActionRequestValidationException validate() { 101 | ActionRequestValidationException validationException = super.validate(); 102 | if (source == null) { 103 | validationException = addValidationError("Source is missing", validationException); 104 | } 105 | return validationException; 106 | } 107 | 108 | @Override 109 | public void readFrom(StreamInput in) throws IOException { 110 | super.readFrom(in); 111 | types = in.readStringArray(); 112 | bulkResponseOption = BulkResponseOption.fromId(in.readByte()); 113 | routing = in.readOptionalString(); 114 | source = in.readBytesReference(); 115 | } 116 | 117 | @Override 118 | public void writeTo(StreamOutput out) throws IOException { 119 | super.writeTo(out); 120 | out.writeStringArray(types); 121 | out.writeByte(bulkResponseOption.id()); 122 | out.writeOptionalString(routing); 123 | out.writeBytesReference(source); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/UpdateByQueryRequestBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.updatebyquery; 20 | 21 | import org.elasticsearch.action.ActionListener; 22 | import org.elasticsearch.action.WriteConsistencyLevel; 23 | import org.elasticsearch.action.support.replication.IndicesReplicationOperationRequestBuilder; 24 | import org.elasticsearch.client.Client; 25 | import org.elasticsearch.client.UpdateByQueryClientWrapper; 26 | import org.elasticsearch.common.bytes.BytesReference; 27 | import org.elasticsearch.index.query.QueryBuilder; 28 | import org.elasticsearch.script.ScriptService; 29 | import org.elasticsearch.script.ScriptService.ScriptType; 30 | 31 | import java.util.Map; 32 | 33 | /** 34 | * A request builder that produces {@link IndexUpdateByQueryRequest} instances. 35 | */ 36 | public class UpdateByQueryRequestBuilder extends IndicesReplicationOperationRequestBuilder { 37 | 38 | private final UpdateByQueryClientWrapper updateByQueryClientWrapper; 39 | private UpdateByQuerySourceBuilder sourceBuilder; 40 | 41 | public UpdateByQueryRequestBuilder(Client client) { 42 | super(client, new UpdateByQueryRequest()); 43 | updateByQueryClientWrapper = new UpdateByQueryClientWrapper(client); 44 | } 45 | 46 | public UpdateByQueryRequestBuilder setTypes(String... types) { 47 | request().types(types); 48 | return this; 49 | } 50 | 51 | public UpdateByQueryRequestBuilder setIncludeBulkResponses(BulkResponseOption option) { 52 | request().bulkResponseOptions(option); 53 | return this; 54 | } 55 | 56 | public UpdateByQueryRequestBuilder setConsistencyLevel(WriteConsistencyLevel writeConsistencyLevel) { 57 | request().consistencyLevel(writeConsistencyLevel); 58 | return this; 59 | } 60 | 61 | /** 62 | * Constructs a new search source builder with a search query. 63 | * 64 | * @see org.elasticsearch.index.query.QueryBuilders 65 | */ 66 | public UpdateByQueryRequestBuilder setQuery(QueryBuilder queryBuilder) { 67 | sourceBuilder().query(queryBuilder); 68 | return this; 69 | } 70 | 71 | /** 72 | * Constructs a new search source builder with a search query. 73 | */ 74 | public UpdateByQueryRequestBuilder setQuery(BytesReference query) { 75 | sourceBuilder().query(query); 76 | return this; 77 | } 78 | 79 | /** 80 | * The language of the script to execute. 81 | * Valid options are: mvel, js, groovy, python, and native (Java)
82 | * Default: groovy 83 | *

84 | * Ref: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-scripting.html 85 | */ 86 | public UpdateByQueryRequestBuilder setScriptLang(String lang) { 87 | sourceBuilder().scriptLang(lang); 88 | return this; 89 | } 90 | 91 | /** 92 | * The inline script to execute. 93 | * @see #setScript(String, ScriptService.ScriptType) 94 | */ 95 | public UpdateByQueryRequestBuilder setScript(String script) { 96 | return setScript(script, ScriptType.INLINE); 97 | } 98 | 99 | /** 100 | * The script to execute. Note, make sure not to send different script each times and instead 101 | * use script params if possible with the same (automatically compiled) script. 102 | *

103 | * The script works with the variable ctx, which is bound to the entry, 104 | * e.g. ctx._source.mycounter += 1. 105 | * 106 | * @see #setScriptLang(String) 107 | * @see #setScriptParams(Map) 108 | */ 109 | public UpdateByQueryRequestBuilder setScript(String script, ScriptType scriptType) { 110 | sourceBuilder().script(script, scriptType); 111 | return this; 112 | } 113 | 114 | /** 115 | * Sets the script parameters to use with the script. 116 | */ 117 | public UpdateByQueryRequestBuilder setScriptParams(Map scriptParams) { 118 | if (scriptParams != null) { 119 | sourceBuilder().scriptParams(scriptParams); 120 | } 121 | return this; 122 | } 123 | 124 | /** 125 | * Add a script parameter. 126 | */ 127 | public UpdateByQueryRequestBuilder addScriptParam(String name, String value) { 128 | sourceBuilder().addScriptParam(name, value); 129 | return this; 130 | } 131 | 132 | protected void doExecute(ActionListener listener) { 133 | if (sourceBuilder != null) { 134 | request.source(sourceBuilder); 135 | } 136 | 137 | updateByQueryClientWrapper.updateByQuery(request, listener); 138 | } 139 | 140 | private UpdateByQuerySourceBuilder sourceBuilder() { 141 | if (sourceBuilder == null) { 142 | sourceBuilder = new UpdateByQuerySourceBuilder(); 143 | } 144 | return sourceBuilder; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/UpdateByQueryResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ActionResponse; 23 | import org.elasticsearch.common.Strings; 24 | import org.elasticsearch.common.io.stream.StreamInput; 25 | import org.elasticsearch.common.io.stream.StreamOutput; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * Encapsulates the result of an update by query request by bundling all bulk item responses. 31 | * Each bulk item response holds the result of an individual update. 32 | */ 33 | // TODO: implements Iterable for index responses 34 | public class UpdateByQueryResponse extends ActionResponse { 35 | 36 | private long tookInMillis; 37 | private long totalHits; 38 | private long updated; 39 | private IndexUpdateByQueryResponse[] indexResponses = new IndexUpdateByQueryResponse[0]; 40 | private String[] mainFailures = Strings.EMPTY_ARRAY; 41 | 42 | UpdateByQueryResponse() { 43 | } 44 | 45 | public UpdateByQueryResponse(long tookInMillis, IndexUpdateByQueryResponse... indexResponses) { 46 | this.tookInMillis = tookInMillis; 47 | indexResponses(indexResponses); 48 | } 49 | 50 | public long tookInMillis() { 51 | return tookInMillis; 52 | } 53 | 54 | public long getTookInMillis() { 55 | return tookInMillis(); 56 | } 57 | 58 | public IndexUpdateByQueryResponse[] indexResponses() { 59 | return indexResponses; 60 | } 61 | 62 | public IndexUpdateByQueryResponse[] getIndexResponses() { 63 | return indexResponses(); 64 | } 65 | 66 | public UpdateByQueryResponse indexResponses(IndexUpdateByQueryResponse[] responses) { 67 | for (IndexUpdateByQueryResponse response : responses) { 68 | totalHits += response.totalHits(); 69 | updated += response.updated(); 70 | } 71 | this.indexResponses = responses; 72 | return this; 73 | } 74 | 75 | /** 76 | * @return The main index level failures 77 | */ 78 | public String[] mainFailures() { 79 | return mainFailures; 80 | } 81 | 82 | public String[] getMainFailures() { 83 | return mainFailures(); 84 | } 85 | 86 | public UpdateByQueryResponse mainFailures(String[] mainFailures) { 87 | this.mainFailures = mainFailures; 88 | return this; 89 | } 90 | 91 | /** 92 | * @return the number of documents that have matched with the update query 93 | */ 94 | public long totalHits() { 95 | return totalHits; 96 | } 97 | 98 | public long getTotalHits() { 99 | return totalHits(); 100 | } 101 | 102 | /** 103 | * @return the number of documents that are actually updated 104 | */ 105 | public long updated() { 106 | return updated; 107 | } 108 | 109 | public long getUpdated() { 110 | return updated(); 111 | } 112 | 113 | public boolean hasFailures() { 114 | return mainFailures().length != 0; 115 | } 116 | 117 | @Override 118 | public void readFrom(StreamInput in) throws IOException { 119 | super.readFrom(in); 120 | tookInMillis = in.readVLong(); 121 | totalHits = in.readVLong(); 122 | updated = in.readVLong(); 123 | indexResponses = new IndexUpdateByQueryResponse[in.readVInt()]; 124 | for (int i = 0; i < indexResponses.length; i++) { 125 | indexResponses[i] = IndexUpdateByQueryResponse.readResponseItem(in); 126 | } 127 | mainFailures = in.readStringArray(); 128 | } 129 | 130 | @Override 131 | public void writeTo(StreamOutput out) throws IOException { 132 | super.writeTo(out); 133 | out.writeVLong(tookInMillis); 134 | out.writeVLong(totalHits); 135 | out.writeVLong(updated); 136 | out.writeVInt(indexResponses.length); 137 | for (IndexUpdateByQueryResponse response : indexResponses) { 138 | response.writeTo(out); 139 | } 140 | out.writeStringArray(mainFailures); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/UpdateByQuerySourceBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.action.updatebyquery; 21 | 22 | import org.elasticsearch.common.bytes.BytesReference; 23 | import org.elasticsearch.common.xcontent.ToXContent; 24 | import org.elasticsearch.common.xcontent.XContentBuilder; 25 | import org.elasticsearch.common.xcontent.XContentFactory; 26 | import org.elasticsearch.common.xcontent.XContentType; 27 | import org.elasticsearch.index.query.QueryBuilder; 28 | import org.elasticsearch.script.ScriptService; 29 | import org.elasticsearch.script.ScriptService.ScriptType; 30 | import org.elasticsearch.search.builder.SearchSourceBuilderException; 31 | 32 | import java.io.IOException; 33 | import java.util.Map; 34 | 35 | import static org.elasticsearch.common.collect.Maps.newHashMap; 36 | 37 | /** 38 | * Source builder of the script, lang, params and query for a update by query request. 39 | */ 40 | public class UpdateByQuerySourceBuilder implements ToXContent { 41 | 42 | private QueryBuilder queryBuilder; 43 | private BytesReference queryBinary; 44 | private String script; 45 | private ScriptType scriptType; 46 | private String scriptLang; 47 | private Map scriptParams = newHashMap(); 48 | 49 | public UpdateByQuerySourceBuilder query(QueryBuilder query) { 50 | this.queryBuilder = query; 51 | return this; 52 | } 53 | 54 | public UpdateByQuerySourceBuilder query(BytesReference queryBinary) { 55 | this.queryBinary = queryBinary; 56 | return this; 57 | } 58 | 59 | /** 60 | * The inline script to execute. 61 | * @see #script(String, ScriptService.ScriptType) 62 | */ 63 | public UpdateByQuerySourceBuilder script(String script) { 64 | return script(script, ScriptType.INLINE); 65 | } 66 | 67 | /** 68 | * The script to execute. Note, make sure not to send different script each times and instead 69 | * use script params if possible with the same (automatically compiled) script. 70 | *

71 | * The script works with the variable ctx, which is bound to the entry, 72 | * e.g. ctx._source.mycounter += 1. 73 | * 74 | * @see #scriptLang(String) 75 | * @see #scriptParams(Map) 76 | */ 77 | public UpdateByQuerySourceBuilder script(String script, ScriptType scriptType) { 78 | this.script = script; 79 | this.scriptType = scriptType; 80 | return this; 81 | } 82 | 83 | /** 84 | * The language of the script to execute. 85 | * Valid options are: mvel, js, groovy, python, and native (Java)
86 | * Default: groovy 87 | *

88 | * Ref: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-scripting.html 89 | */ 90 | public UpdateByQuerySourceBuilder scriptLang(String scriptLang) { 91 | this.scriptLang = scriptLang; 92 | return this; 93 | } 94 | 95 | /** 96 | * Sets the script parameters to use with the script. 97 | */ 98 | public UpdateByQuerySourceBuilder scriptParams(Map scriptParams) { 99 | this.scriptParams = scriptParams; 100 | return this; 101 | } 102 | 103 | /** 104 | * Add a script parameter. 105 | */ 106 | public UpdateByQuerySourceBuilder addScriptParam(String name, String value) { 107 | scriptParams.put(name, value); 108 | return this; 109 | } 110 | 111 | public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { 112 | builder.startObject(); 113 | if (queryBuilder != null) { 114 | builder.field("query"); 115 | queryBuilder.toXContent(builder, params); 116 | } 117 | 118 | if (queryBinary != null) { 119 | if (XContentFactory.xContentType(queryBinary) == builder.contentType()) { 120 | builder.rawField("query", queryBinary); 121 | } else { 122 | builder.field("query_binary", queryBinary); 123 | } 124 | } 125 | 126 | if (script != null && scriptType != null) { 127 | switch (scriptType) { 128 | case INLINE: 129 | builder.field(ScriptService.SCRIPT_INLINE.getPreferredName(), script); 130 | break; 131 | case FILE: 132 | builder.field(ScriptService.SCRIPT_FILE.getPreferredName(), script); 133 | break; 134 | case INDEXED: 135 | builder.field(ScriptService.SCRIPT_ID.getPreferredName(), script); 136 | break; 137 | } 138 | } 139 | 140 | if (scriptLang != null) { 141 | builder.field(ScriptService.SCRIPT_LANG.getPreferredName(), scriptLang); 142 | } 143 | 144 | if (!scriptParams.isEmpty()) { 145 | builder.field("params", scriptParams); 146 | } 147 | 148 | builder.endObject(); 149 | return builder; 150 | } 151 | 152 | public BytesReference buildAsBytes(XContentType contentType) throws SearchSourceBuilderException { 153 | try { 154 | XContentBuilder builder = XContentFactory.contentBuilder(contentType); 155 | toXContent(builder, ToXContent.EMPTY_PARAMS); 156 | return builder.bytes(); 157 | } catch (Exception e) { 158 | throw new SearchSourceBuilderException("Failed to build search source", e); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/updatebyquery/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /** 21 | * Update by query action. 22 | */ 23 | package org.elasticsearch.action.updatebyquery; -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/client/UpdateByQueryClient.java: -------------------------------------------------------------------------------- 1 | package org.elasticsearch.client; 2 | 3 | import org.elasticsearch.action.ActionFuture; 4 | import org.elasticsearch.action.ActionListener; 5 | import org.elasticsearch.action.updatebyquery.UpdateByQueryRequest; 6 | import org.elasticsearch.action.updatebyquery.UpdateByQueryRequestBuilder; 7 | import org.elasticsearch.action.updatebyquery.UpdateByQueryResponse; 8 | 9 | public interface UpdateByQueryClient { 10 | 11 | /** 12 | * Updates documents that match a query specified in the request. The update is based on a script. 13 | * 14 | * @param request The update by query request. 15 | * @param listener A listener that notifies the caller when the update by query operation has completed 16 | */ 17 | void updateByQuery(UpdateByQueryRequest request, ActionListener listener); 18 | 19 | /** 20 | * Performs the same action as in {@link #updateByQuery(org.elasticsearch.action.updatebyquery.UpdateByQueryRequest, 21 | * org.elasticsearch.action.ActionListener)}, but works with an {@link ActionFuture} instead of a {@link ActionListener}. 22 | * 23 | * @param request The update query request 24 | * @return The result future 25 | */ 26 | ActionFuture updateByQuery(UpdateByQueryRequest request); 27 | 28 | /** 29 | * Prepares a update for documents matching a query using a script. 30 | * 31 | * @return a builder instance 32 | */ 33 | UpdateByQueryRequestBuilder prepareUpdateByQuery(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/client/UpdateByQueryClientWrapper.java: -------------------------------------------------------------------------------- 1 | package org.elasticsearch.client; 2 | 3 | import org.elasticsearch.action.ActionFuture; 4 | import org.elasticsearch.action.ActionListener; 5 | import org.elasticsearch.action.updatebyquery.UpdateByQueryAction; 6 | import org.elasticsearch.action.updatebyquery.UpdateByQueryRequest; 7 | import org.elasticsearch.action.updatebyquery.UpdateByQueryRequestBuilder; 8 | import org.elasticsearch.action.updatebyquery.UpdateByQueryResponse; 9 | 10 | public class UpdateByQueryClientWrapper implements UpdateByQueryClient { 11 | 12 | protected final Client client; 13 | 14 | public UpdateByQueryClientWrapper(Client client) { 15 | this.client = client; 16 | } 17 | 18 | @Override 19 | public void updateByQuery(UpdateByQueryRequest request, ActionListener listener) { 20 | client.execute(UpdateByQueryAction.INSTANCE, request, listener); 21 | } 22 | 23 | @Override 24 | public ActionFuture updateByQuery(UpdateByQueryRequest request) { 25 | return client.execute(UpdateByQueryAction.INSTANCE, request); 26 | } 27 | 28 | @Override 29 | public UpdateByQueryRequestBuilder prepareUpdateByQuery() { 30 | return new UpdateByQueryRequestBuilder(client); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/common/lucene/TopLevelFixedBitSetCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.common.lucene; 21 | 22 | import org.apache.lucene.index.AtomicReaderContext; 23 | import org.apache.lucene.search.Collector; 24 | import org.apache.lucene.search.Scorer; 25 | import org.apache.lucene.util.FixedBitSet; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * Collector that collects the hits in a {@link FixedBitSet} instance. 31 | */ 32 | public class TopLevelFixedBitSetCollector extends Collector { 33 | 34 | private final FixedBitSet bitSet; 35 | private int docBase; 36 | 37 | /** 38 | * @param maxDoc The higest Lucene docid + 1 from a toplevel IndexReader / IndexSearcher. 39 | */ 40 | public TopLevelFixedBitSetCollector(int maxDoc) { 41 | this.bitSet = new FixedBitSet(maxDoc); 42 | } 43 | 44 | public void setScorer(Scorer scorer) throws IOException { 45 | } 46 | 47 | public void collect(int doc) throws IOException { 48 | bitSet.set(docBase + doc); 49 | } 50 | 51 | @Override 52 | public void setNextReader(AtomicReaderContext context) throws IOException { 53 | this.docBase = context.docBase; 54 | } 55 | 56 | public boolean acceptsDocsOutOfOrder() { 57 | return true; 58 | } 59 | 60 | /** 61 | * @return The matched Lucene doc ids as {@link FixedBitSet} 62 | */ 63 | public FixedBitSet getBitSet() { 64 | return bitSet; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/plugin/action/updatebyquery/ActionUpdateByQueryPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.plugin.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.ActionModule; 23 | import org.elasticsearch.action.updatebyquery.TransportShardUpdateByQueryAction; 24 | import org.elasticsearch.action.updatebyquery.TransportUpdateByQueryAction; 25 | import org.elasticsearch.action.updatebyquery.UpdateByQueryAction; 26 | import org.elasticsearch.common.inject.Module; 27 | import org.elasticsearch.plugins.AbstractPlugin; 28 | import org.elasticsearch.rest.RestModule; 29 | import org.elasticsearch.rest.action.updatebyquery.RestUpdateByQueryAction; 30 | 31 | /** 32 | * @author ofavre 33 | */ 34 | public class ActionUpdateByQueryPlugin extends AbstractPlugin { 35 | 36 | @Override public String name() { 37 | return "action-updatebyquery"; 38 | } 39 | 40 | @Override public String description() { 41 | return "Update By Query action plugin"; 42 | } 43 | 44 | @Override public void processModule(Module module) { 45 | if (module instanceof ActionModule) { 46 | ActionModule actionModule = (ActionModule) module; 47 | actionModule.registerAction(UpdateByQueryAction.INSTANCE, TransportUpdateByQueryAction.class, 48 | TransportShardUpdateByQueryAction.class); 49 | } else if (module instanceof RestModule) { 50 | RestModule restModule = (RestModule) module; 51 | restModule.addRestAction(RestUpdateByQueryAction.class); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/rest/action/updatebyquery/RestUpdateByQueryAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.rest.action.updatebyquery; 21 | 22 | import org.elasticsearch.action.WriteConsistencyLevel; 23 | import org.elasticsearch.action.bulk.BulkItemResponse; 24 | import org.elasticsearch.action.updatebyquery.*; 25 | import org.elasticsearch.client.Client; 26 | import org.elasticsearch.client.UpdateByQueryClient; 27 | import org.elasticsearch.client.UpdateByQueryClientWrapper; 28 | import org.elasticsearch.common.Strings; 29 | import org.elasticsearch.common.bytes.BytesArray; 30 | import org.elasticsearch.common.inject.Inject; 31 | import org.elasticsearch.common.settings.Settings; 32 | import org.elasticsearch.common.unit.TimeValue; 33 | import org.elasticsearch.common.xcontent.XContentBuilder; 34 | import org.elasticsearch.common.xcontent.XContentBuilderString; 35 | import org.elasticsearch.common.xcontent.XContentType; 36 | import org.elasticsearch.rest.*; 37 | import org.elasticsearch.rest.action.support.RestActions; 38 | import org.elasticsearch.rest.action.support.RestBuilderListener; 39 | import org.elasticsearch.script.ScriptParameterParser; 40 | import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue; 41 | 42 | import java.util.Map; 43 | 44 | import static org.elasticsearch.rest.RestRequest.Method.POST; 45 | import static org.elasticsearch.rest.RestStatus.OK; 46 | 47 | /** 48 | * Rest handler for update by query requests. 49 | */ 50 | public class RestUpdateByQueryAction extends BaseRestHandler { 51 | 52 | final protected UpdateByQueryClient updateByQueryClient; 53 | 54 | @Inject 55 | public RestUpdateByQueryAction(Settings settings, Client client, RestController controller) { 56 | super(settings, controller, client); 57 | updateByQueryClient = new UpdateByQueryClientWrapper(client); 58 | controller.registerHandler(POST, "/{index}/_update_by_query", this); 59 | controller.registerHandler(POST, "/{index}/{type}/_update_by_query", this); 60 | } 61 | 62 | @Override 63 | public void handleRequest(final RestRequest request, final RestChannel channel, Client client) { 64 | UpdateByQueryRequest udqRequest = new UpdateByQueryRequest( 65 | Strings.splitStringByCommaToArray(request.param("index")), 66 | Strings.splitStringByCommaToArray(request.param("type")) 67 | ); 68 | udqRequest.listenerThreaded(false); 69 | String consistencyLevel = request.param("consistency"); 70 | if (consistencyLevel != null) { 71 | udqRequest.consistencyLevel(WriteConsistencyLevel.fromString(consistencyLevel)); 72 | } 73 | String responseType = request.param("response"); 74 | if (responseType != null) { 75 | udqRequest.bulkResponseOptions(BulkResponseOption.fromString(responseType)); 76 | } 77 | udqRequest.routing(request.param("routing")); 78 | String timeout = request.param("timeout"); 79 | if (timeout != null) { 80 | udqRequest.timeout(TimeValue.parseTimeValue(timeout, null)); 81 | } 82 | 83 | // see if we have it in the body 84 | if (request.hasContent()) { 85 | udqRequest.source(request.content()); 86 | } else if (request.hasParam("source")) { 87 | udqRequest.source(new BytesArray(request.param("source"))); 88 | } else if (request.hasParam("q")) { 89 | UpdateByQuerySourceBuilder sourceBuilder = new UpdateByQuerySourceBuilder(); 90 | ScriptParameterParser scriptParameterParser = new ScriptParameterParser(); 91 | scriptParameterParser.parseParams(request); 92 | ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue(); 93 | if (scriptValue != null) { 94 | sourceBuilder.script(scriptValue.script(), scriptValue.scriptType()); 95 | } 96 | String scriptLang = scriptParameterParser.lang(); 97 | if (scriptLang != null) { 98 | sourceBuilder.scriptLang(scriptLang); 99 | } 100 | for (Map.Entry entry : request.params().entrySet()) { 101 | if (entry.getKey().startsWith("sp_")) { 102 | sourceBuilder.addScriptParam(entry.getKey().substring(3), entry.getValue()); 103 | } 104 | } 105 | 106 | sourceBuilder.query(RestActions.parseQuerySource(request).buildAsBytes(XContentType.JSON)); 107 | udqRequest.source(sourceBuilder); 108 | } 109 | 110 | updateByQueryClient.updateByQuery(udqRequest, new RestBuilderListener(channel) { 111 | 112 | @Override 113 | public RestResponse buildResponse(UpdateByQueryResponse response, XContentBuilder builder) throws Exception { 114 | builder.startObject(); 115 | builder.field(Fields.OK, !response.hasFailures()); 116 | builder.field(Fields.TOOK, response.tookInMillis()); 117 | builder.field(Fields.TOTAL, response.totalHits()); 118 | builder.field(Fields.UPDATED, response.updated()); 119 | 120 | if (response.hasFailures()) { 121 | builder.startArray(Fields.ERRORS); 122 | for (String failure : response.mainFailures()) { 123 | builder.field(Fields.ERROR, failure); 124 | } 125 | builder.endArray(); 126 | } 127 | 128 | if (response.indexResponses().length != 0) { 129 | builder.startArray(Fields.INDICES); 130 | for (IndexUpdateByQueryResponse indexResponse : response.indexResponses()) { 131 | builder.startObject(); 132 | builder.field(indexResponse.index()); 133 | builder.startObject(); 134 | for (Map.Entry shard : indexResponse.responsesByShard().entrySet()) { 135 | builder.startObject(shard.getKey().toString()); 136 | if (indexResponse.failuresByShard().containsKey(shard.getKey())) { 137 | builder.field(Fields.ERROR, indexResponse.failuresByShard().get(shard.getKey())); 138 | } 139 | builder.startArray(Fields.ITEMS); 140 | for (BulkItemResponse itemResponse : shard.getValue()) { 141 | builder.startObject(); 142 | builder.startObject(itemResponse.getOpType()); 143 | builder.field(Fields._INDEX, itemResponse.getIndex()); 144 | builder.field(Fields._TYPE, itemResponse.getType()); 145 | builder.field(Fields._ID, itemResponse.getId()); 146 | long version = itemResponse.getVersion(); 147 | if (version != -1) { 148 | builder.field(Fields._VERSION, itemResponse.getVersion()); 149 | } 150 | if (itemResponse.isFailed()) { 151 | builder.field(Fields.ERROR, itemResponse.getFailure().getMessage()); 152 | } else { 153 | builder.field(Fields.OK, true); 154 | } 155 | builder.endObject(); 156 | builder.endObject(); 157 | } 158 | builder.endArray(); 159 | builder.endObject(); 160 | } 161 | for (Map.Entry shard : indexResponse.failuresByShard().entrySet()) { 162 | builder.startObject(shard.getKey().toString()); 163 | builder.field(Fields.ERROR, shard.getValue()); 164 | builder.endObject(); 165 | } 166 | builder.endObject(); 167 | builder.endObject(); 168 | } 169 | builder.endArray(); 170 | } 171 | builder.endObject(); 172 | return new BytesRestResponse(OK, builder); 173 | } 174 | 175 | }); 176 | } 177 | 178 | static final class Fields { 179 | static final XContentBuilderString OK = new XContentBuilderString("ok"); 180 | static final XContentBuilderString ERRORS = new XContentBuilderString("errors"); 181 | static final XContentBuilderString ERROR = new XContentBuilderString("error"); 182 | static final XContentBuilderString TOOK = new XContentBuilderString("took"); 183 | static final XContentBuilderString _INDEX = new XContentBuilderString("_index"); 184 | static final XContentBuilderString _TYPE = new XContentBuilderString("_type"); 185 | static final XContentBuilderString _ID = new XContentBuilderString("_id"); 186 | static final XContentBuilderString _VERSION = new XContentBuilderString("_version"); 187 | static final XContentBuilderString TOTAL = new XContentBuilderString("total"); 188 | static final XContentBuilderString UPDATED = new XContentBuilderString("updated"); 189 | static final XContentBuilderString ITEMS = new XContentBuilderString("items"); 190 | static final XContentBuilderString INDICES = new XContentBuilderString("indices"); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/resources/es-plugin.properties: -------------------------------------------------------------------------------- 1 | plugin=org.elasticsearch.plugin.action.updatebyquery.ActionUpdateByQueryPlugin 2 | -------------------------------------------------------------------------------- /src/test/java/org/elasticsearch/test/integration/updatebyquery/UpdateByQueryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.test.integration.updatebyquery; 21 | 22 | import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; 23 | import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; 24 | import org.elasticsearch.action.bulk.BulkItemResponse; 25 | import org.elasticsearch.action.count.CountResponse; 26 | import org.elasticsearch.action.updatebyquery.BulkResponseOption; 27 | import org.elasticsearch.action.updatebyquery.IndexUpdateByQueryResponse; 28 | import org.elasticsearch.action.updatebyquery.UpdateByQueryRequestBuilder; 29 | import org.elasticsearch.action.updatebyquery.UpdateByQueryResponse; 30 | import org.elasticsearch.client.UpdateByQueryClientWrapper; 31 | import org.elasticsearch.common.settings.ImmutableSettings; 32 | import org.elasticsearch.common.settings.Settings; 33 | import org.elasticsearch.common.xcontent.XContentFactory; 34 | import org.elasticsearch.index.query.FilterBuilders; 35 | import org.elasticsearch.plugins.PluginsService; 36 | import org.elasticsearch.script.ScriptService; 37 | import org.elasticsearch.script.ScriptService.ScriptType; 38 | import org.elasticsearch.test.ElasticsearchIntegrationTest; 39 | import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; 40 | import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; 41 | import org.junit.Test; 42 | 43 | import java.io.IOException; 44 | import java.util.Arrays; 45 | import java.util.Comparator; 46 | import java.util.HashMap; 47 | import java.util.Map; 48 | 49 | import static org.elasticsearch.cluster.metadata.AliasAction.newAddAliasAction; 50 | import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; 51 | import static org.elasticsearch.index.query.QueryBuilders.termQuery; 52 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; 53 | import static org.hamcrest.Matchers.*; 54 | 55 | @ClusterScope( 56 | scope = Scope.SUITE, // needed to control node settings, in order to add "plugins.load_classpath_plugins=true" 57 | transportClientRatio = 0 // as we can't control the transport node settings to add "plugins.load_classpath_plugins=true", forbid TransportClients 58 | ) 59 | public class UpdateByQueryTests extends ElasticsearchIntegrationTest { 60 | 61 | public static UpdateByQueryClientWrapper updateByQueryClient() { 62 | return new UpdateByQueryClientWrapper(ElasticsearchIntegrationTest.client()); 63 | } 64 | 65 | protected void createIndex(String indexName) throws Exception { 66 | logger.info("--> creating index " + indexName); 67 | prepareCreate(indexName).addMapping("type1", XContentFactory.jsonBuilder() 68 | .startObject() 69 | .startObject("type1") 70 | .startObject("_timestamp").field("enabled", true).field("store", "yes").endObject() 71 | .startObject("_ttl").field("enabled", true).field("store", "yes").endObject() 72 | .endObject() 73 | .endObject()) 74 | .addMapping("subtype1", XContentFactory.jsonBuilder() 75 | .startObject() 76 | .startObject("subtype1") 77 | .startObject("_parent").field("type", "type1").endObject() 78 | .startObject("_timestamp").field("enabled", true).field("store", "yes").endObject() 79 | .startObject("_ttl").field("enabled", true).field("store", "yes").endObject() 80 | .endObject() 81 | .endObject()) 82 | .execute().actionGet(); 83 | } 84 | 85 | @Override 86 | protected Settings nodeSettings(int nodeOrdinal) { 87 | return ImmutableSettings.settingsBuilder() 88 | .put("path.conf", this.getResource("config").getPath()) 89 | .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true) 90 | .put("action.updatebyquery.bulk_size", 5) 91 | .put(super.nodeSettings(nodeOrdinal)) 92 | .build(); 93 | } 94 | 95 | protected interface ScriptSetter { 96 | UpdateByQueryRequestBuilder setScript(UpdateByQueryRequestBuilder ubqRequestBuilder); 97 | } 98 | public void doTestUpdateByQuery_scriptTypes_base(ScriptSetter scriptSetter) throws Exception { 99 | createIndex("test"); 100 | ClusterHealthResponse clusterHealth = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 101 | assertThat(clusterHealth.isTimedOut(), equalTo(false)); 102 | assertThat(clusterHealth.getStatus(), equalTo(ClusterHealthStatus.GREEN)); 103 | 104 | final long numDocs = 25; 105 | for (int i = 1; i <= numDocs; i++) { 106 | client().prepareIndex("test", "type1", Integer.toString(i)).setSource("field1", 1).execute().actionGet(); 107 | if (i % 10 == 0) { 108 | client().admin().indices().prepareFlush("test").execute().actionGet(); 109 | } 110 | } 111 | // Add one doc with a different type. 112 | client().prepareIndex("test", "type2", "1").setSource("field1", 1).execute().actionGet(); 113 | client().admin().indices().prepareRefresh("test").execute().actionGet(); 114 | 115 | CountResponse countResponse = client().prepareCount("test") 116 | .setQuery(termQuery("field1", 2)).get(); 117 | assertThat(countResponse.getCount(), equalTo(0L)); 118 | 119 | UpdateByQueryRequestBuilder ubqRequestBuilder = updateByQueryClient().prepareUpdateByQuery() 120 | .setIndices("test") 121 | .setTypes("type1") 122 | .setIncludeBulkResponses(BulkResponseOption.ALL) 123 | .setQuery(matchAllQuery()); 124 | ubqRequestBuilder = scriptSetter.setScript(ubqRequestBuilder); 125 | UpdateByQueryResponse response = ubqRequestBuilder.execute().actionGet(); 126 | 127 | assertThat(response, notNullValue()); 128 | assertThat(response.mainFailures().length, equalTo(0)); 129 | assertThat(response.totalHits(), equalTo(numDocs)); 130 | assertThat(response.updated(), equalTo(numDocs)); 131 | assertThat(response.indexResponses().length, equalTo(1)); 132 | assertThat(response.indexResponses()[0].countShardResponses(), equalTo(numDocs)); 133 | 134 | assertThat(response.indexResponses()[0].failuresByShard().isEmpty(), equalTo(true)); 135 | for (BulkItemResponse[] shardResponses : response.indexResponses()[0].responsesByShard().values()) { 136 | for (BulkItemResponse shardResponse : shardResponses) { 137 | assertThat(shardResponse.getVersion(), equalTo(2L)); 138 | assertThat(shardResponse.isFailed(), equalTo(false)); 139 | assertThat(shardResponse.getFailure(), nullValue()); 140 | assertThat(shardResponse.getFailureMessage(), nullValue()); 141 | } 142 | } 143 | 144 | client().admin().indices().prepareRefresh("test").execute().actionGet(); 145 | countResponse = client().prepareCount("test") 146 | .setQuery(termQuery("field1", 2)) 147 | .execute() 148 | .actionGet(); 149 | assertThat(countResponse.getCount(), equalTo(numDocs)); 150 | 151 | ubqRequestBuilder = updateByQueryClient().prepareUpdateByQuery() 152 | .setIndices("test") 153 | .setTypes("type1") 154 | .setQuery(matchAllQuery()); 155 | ubqRequestBuilder = scriptSetter.setScript(ubqRequestBuilder); 156 | response = ubqRequestBuilder.execute().actionGet(); 157 | 158 | assertThat(response, notNullValue()); 159 | assertThat(response.totalHits(), equalTo(numDocs)); 160 | assertThat(response.updated(), equalTo(numDocs)); 161 | assertThat(response.indexResponses().length, equalTo(1)); 162 | assertThat(response.indexResponses()[0].totalHits(), equalTo(numDocs)); 163 | assertThat(response.indexResponses()[0].updated(), equalTo(numDocs)); 164 | assertThat(response.indexResponses()[0].failuresByShard().size(), equalTo(0)); 165 | assertThat(response.indexResponses()[0].responsesByShard().size(), equalTo(0)); 166 | 167 | client().admin().indices().prepareRefresh("test").execute().actionGet(); 168 | countResponse = client().prepareCount("test") 169 | .setQuery(termQuery("field1", 3)) 170 | .execute() 171 | .actionGet(); 172 | assertThat(countResponse.getCount(), equalTo(numDocs)); 173 | } 174 | 175 | @Test 176 | public void testUpdateByQuery_scriptType_inlineImplicit() throws Exception { 177 | doTestUpdateByQuery_scriptTypes_base(new ScriptSetter() { 178 | @Override 179 | public UpdateByQueryRequestBuilder setScript(UpdateByQueryRequestBuilder ubqRequestBuilder) { 180 | return ubqRequestBuilder.setScript("ctx._source.field1 += 1"); 181 | } 182 | }); 183 | } 184 | 185 | @Test 186 | public void testUpdateByQuery_scriptType_inlineExplicit() throws Exception { 187 | doTestUpdateByQuery_scriptTypes_base(new ScriptSetter() { 188 | @Override 189 | public UpdateByQueryRequestBuilder setScript(UpdateByQueryRequestBuilder ubqRequestBuilder) { 190 | return ubqRequestBuilder.setScript("ctx._source.field1 += 1", ScriptType.INLINE); 191 | } 192 | }); 193 | } 194 | 195 | @Test 196 | public void testUpdateByQuery_scriptType_file() throws Exception { 197 | doTestUpdateByQuery_scriptTypes_base(new ScriptSetter() { 198 | @Override 199 | public UpdateByQueryRequestBuilder setScript(UpdateByQueryRequestBuilder ubqRequestBuilder) { 200 | return ubqRequestBuilder.setScript("field1_incrementer", ScriptType.FILE); 201 | } 202 | }); 203 | } 204 | 205 | @Test 206 | public void testUpdateByQuery_scriptType_indexed() throws Exception { 207 | indexRandom(true, client().prepareIndex(ScriptService.SCRIPT_INDEX, "groovy", "indexed_field1_incrementer") 208 | .setSource("{\"script\":\"ctx._source.field1 += 1\"}")); 209 | doTestUpdateByQuery_scriptTypes_base(new ScriptSetter() { 210 | @Override 211 | public UpdateByQueryRequestBuilder setScript(UpdateByQueryRequestBuilder ubqRequestBuilder) { 212 | return ubqRequestBuilder.setScript("indexed_field1_incrementer", ScriptType.INDEXED) 213 | .setScriptLang("groovy"); 214 | } 215 | }); 216 | } 217 | 218 | @Test 219 | public void testUpdateByQuery_multipleIndices() throws Exception { 220 | final int numIndices = 10; 221 | final long docsPerIndex = 10; 222 | final long numDocs = numIndices * docsPerIndex; 223 | 224 | // Create all indices beforehand 225 | for (int i = 0; i < numIndices; i++) { 226 | createIndex("test" + i); 227 | if (i % 5 == 0) { 228 | client().admin().indices().prepareFlush("test" + i).execute().actionGet(); 229 | } 230 | } 231 | ClusterHealthResponse clusterHealth = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 232 | assertThat(clusterHealth.isTimedOut(), equalTo(false)); 233 | assertThat(clusterHealth.getStatus(), equalTo(ClusterHealthStatus.GREEN)); 234 | 235 | // Index docs 236 | for (int i = 0; i < numIndices; i++) { 237 | String current = "test" + i; 238 | for (int id = 0; id < docsPerIndex; ++id) { 239 | client().prepareIndex(current, "type1", Integer.toString(id)).setSource("field1", 1).execute().actionGet(); 240 | } 241 | } 242 | // Add one doc with a different type. 243 | client().prepareIndex("test0", "type2", "-1").setSource("field1", 1).execute().actionGet(); 244 | client().admin().indices().prepareRefresh("*").execute().actionGet(); 245 | 246 | CountResponse countResponse = client().prepareCount("*") 247 | .setQuery(termQuery("field1", 2)) 248 | .execute() 249 | .actionGet(); 250 | assertThat(countResponse.getCount(), equalTo(0L)); 251 | 252 | Map scriptParams = new HashMap(); 253 | UpdateByQueryResponse response = updateByQueryClient().prepareUpdateByQuery() 254 | .setIndices("*") 255 | .setTypes("type1") 256 | .setIncludeBulkResponses(BulkResponseOption.ALL) 257 | .setScript("ctx._source.field1 += 1").setScriptParams(scriptParams) 258 | .setQuery(matchAllQuery()) 259 | .execute() 260 | .actionGet(); 261 | 262 | assertThat(response, notNullValue()); 263 | assertThat(response.totalHits(), equalTo(numDocs)); 264 | assertThat(response.updated(), equalTo(numDocs)); 265 | assertThat(response.indexResponses().length, equalTo(numIndices)); 266 | Arrays.sort(response.indexResponses(), new Comparator() { 267 | 268 | public int compare(IndexUpdateByQueryResponse res1, IndexUpdateByQueryResponse res2) { 269 | int index1 = Integer.parseInt(res1.index().substring(4)); 270 | int index2 = Integer.parseInt(res2.index().substring(4)); 271 | return index1 - index2; 272 | } 273 | 274 | }); 275 | 276 | for (int i = 0; i < response.indexResponses().length; i++) { 277 | String index = "test" + i; 278 | assertThat(response.indexResponses()[i].index(), equalTo(index)); 279 | assertThat(response.indexResponses()[i].countShardResponses(), equalTo(docsPerIndex)); 280 | 281 | assertThat(response.indexResponses()[i].failuresByShard().isEmpty(), equalTo(true)); 282 | for (BulkItemResponse[] shardResponses : response.indexResponses()[i].responsesByShard().values()) { 283 | for (BulkItemResponse shardResponse : shardResponses) { 284 | assertThat(shardResponse.getVersion(), equalTo(2L)); 285 | assertThat(shardResponse.isFailed(), equalTo(false)); 286 | assertThat(shardResponse.getFailure(), nullValue()); 287 | assertThat(shardResponse.getFailureMessage(), nullValue()); 288 | } 289 | } 290 | } 291 | 292 | assertThat(response.mainFailures().length, equalTo(0)); 293 | 294 | client().admin().indices().prepareRefresh("*").execute().actionGet(); 295 | countResponse = client().prepareCount("*") 296 | .setQuery(termQuery("field1", 2)) 297 | .execute() 298 | .actionGet(); 299 | assertThat(countResponse.getCount(), equalTo(numDocs)); 300 | } 301 | 302 | @Test 303 | public void testUpdateByQuery_usingAliases() throws IOException { 304 | client().admin().indices().prepareCreate("test").setSettings( 305 | ImmutableSettings.builder() 306 | .put(indexSettings()) 307 | .put("number_of_shards", Math.max(2, numberOfShards())) 308 | .build() 309 | ).addMapping("type1", XContentFactory.jsonBuilder() 310 | // Define the field prior to creating a filtered alias on it 311 | .startObject() 312 | .startObject("properties") 313 | .startObject("field") 314 | .field("type", "string") 315 | .endObject() 316 | .endObject() 317 | .endObject() 318 | ).execute().actionGet(); 319 | client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); 320 | 321 | client().admin().indices().prepareAliases().addAliasAction( 322 | newAddAliasAction("test", "alias0").routing("0") 323 | ).execute().actionGet(); 324 | 325 | client().admin().indices().prepareAliases().addAliasAction( 326 | newAddAliasAction("test", "alias1").filter(FilterBuilders.termFilter("field", "value2")).routing("1") 327 | ).execute().actionGet(); 328 | 329 | client().prepareIndex("alias0", "type1", "1").setSource("field", "value1").setRefresh(true).execute().actionGet(); 330 | client().prepareIndex("alias0", "type1", "2").setSource("field", "value2").setRefresh(true).execute().actionGet(); 331 | client().admin().indices().prepareFlush("test").execute().actionGet(); 332 | client().prepareIndex("alias1", "type1", "3").setSource("field", "value1").setRefresh(true).execute().actionGet(); 333 | client().prepareIndex("alias1", "type1", "4").setSource("field", "value2").setRefresh(true).execute().actionGet(); 334 | 335 | assertThat(client().prepareGet("alias0", "type1", "1").execute().actionGet().isExists(), equalTo(true)); 336 | assertThat(client().prepareGet("alias0", "type1", "2").execute().actionGet().isExists(), equalTo(true)); 337 | assertThat(client().prepareGet("alias1", "type1", "3").execute().actionGet().isExists(), equalTo(true)); 338 | assertThat(client().prepareGet("alias1", "type1", "4").execute().actionGet().isExists(), equalTo(true)); 339 | 340 | UpdateByQueryResponse response = updateByQueryClient().prepareUpdateByQuery() 341 | .setIndices("alias1") 342 | .setQuery(matchAllQuery()) 343 | .setScript("ctx.op = \"delete\"") 344 | .execute().actionGet(); 345 | assertThat(response.totalHits(), equalTo(1L)); 346 | assertThat(response.updated(), equalTo(1L)); 347 | 348 | response = updateByQueryClient().prepareUpdateByQuery() 349 | .setIndices("alias0") 350 | .setQuery(matchAllQuery()) 351 | .setScript("ctx.op = \"delete\"") 352 | .execute().actionGet(); 353 | assertThat(response.totalHits(), equalTo(2L)); 354 | assertThat(response.updated(), equalTo(2L)); 355 | 356 | assertThat(client().prepareGet("alias0", "type1", "1").execute().actionGet().isExists(), equalTo(false)); 357 | assertThat(client().prepareGet("alias0", "type1", "2").execute().actionGet().isExists(), equalTo(false)); 358 | assertThat(client().prepareGet("alias1", "type1", "3").execute().actionGet().isExists(), equalTo(true)); 359 | assertThat(client().prepareGet("alias1", "type1", "4").execute().actionGet().isExists(), equalTo(false)); 360 | } 361 | 362 | @Test 363 | public void testUpdateByQuery_noMatches() throws Exception { 364 | createIndex("test"); 365 | client().prepareIndex("test", "type1", "1").setSource("field1", 1).execute().actionGet(); 366 | client().admin().indices().prepareRefresh("test").execute().actionGet(); 367 | 368 | CountResponse countResponse = client().prepareCount("test") 369 | .setQuery(termQuery("field2", 1)).get(); 370 | assertHitCount(countResponse, 0); 371 | 372 | Map scriptParams = new HashMap(); 373 | UpdateByQueryResponse response = updateByQueryClient().prepareUpdateByQuery() 374 | .setIndices("test") 375 | .setTypes("type1") 376 | .setIncludeBulkResponses(BulkResponseOption.ALL) 377 | .setScript("ctx._source.field1 += 1").setScriptParams(scriptParams) 378 | .setQuery(termQuery("field2", 1)) 379 | .execute() 380 | .actionGet(); 381 | 382 | assertThat(response, notNullValue()); 383 | assertThat(response.mainFailures().length, equalTo(0)); 384 | assertThat(response.totalHits(), equalTo(0l)); 385 | assertThat(response.updated(), equalTo(0l)); 386 | assertThat(response.indexResponses(), arrayWithSize(1)); 387 | assertThat(response.indexResponses()[0].responsesByShard().isEmpty(), is(true)); 388 | } 389 | 390 | } 391 | -------------------------------------------------------------------------------- /src/test/java/org/elasticsearch/test/stress/updatebyquery/UpdateByQueryStressTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.elasticsearch.test.stress.updatebyquery; 21 | 22 | import org.elasticsearch.action.bulk.BulkItemResponse; 23 | import org.elasticsearch.action.bulk.BulkRequestBuilder; 24 | import org.elasticsearch.action.index.IndexResponse; 25 | import org.elasticsearch.action.updatebyquery.IndexUpdateByQueryResponse; 26 | import org.elasticsearch.action.updatebyquery.UpdateByQueryResponse; 27 | import org.elasticsearch.client.Client; 28 | import org.elasticsearch.client.Requests; 29 | import org.elasticsearch.client.UpdateByQueryClientWrapper; 30 | import org.elasticsearch.common.settings.ImmutableSettings; 31 | import org.elasticsearch.common.settings.Settings; 32 | import org.elasticsearch.index.query.QueryBuilders; 33 | import org.elasticsearch.node.Node; 34 | import org.elasticsearch.node.NodeBuilder; 35 | 36 | import java.util.Locale; 37 | import java.util.Map; 38 | 39 | /** 40 | */ 41 | public class UpdateByQueryStressTest { 42 | 43 | public static void main(String[] args) { 44 | final int NUMBER_OF_NODES = 4; 45 | final int NUMBER_OF_INDICES = 5; 46 | final int BATCH = 300000; 47 | 48 | final Settings nodeSettings = ImmutableSettings.settingsBuilder() 49 | .put("index.number_of_shards", 2) 50 | // .put("action.updatebyquery.bulk_size", 5) 51 | .put("index.number_of_replicas", 0).build(); 52 | final Node[] nodes = new Node[NUMBER_OF_NODES]; 53 | for (int i = 0; i < nodes.length; i++) { 54 | nodes[i] = NodeBuilder.nodeBuilder().settings(nodeSettings).node(); 55 | } 56 | 57 | Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 58 | public void run() { 59 | for (Node node : nodes) { 60 | node.close(); 61 | } 62 | } 63 | })); 64 | 65 | try { 66 | Client client = nodes.length == 1 ? nodes[0].client() : nodes[1].client(); 67 | UpdateByQueryClientWrapper updateByQueryClientWrapper = new UpdateByQueryClientWrapper(client); 68 | try { 69 | client.admin().indices().prepareDelete().execute().actionGet(); 70 | } catch (Exception e) { 71 | // ignore 72 | } 73 | 74 | client.admin().indices().prepareUpdateSettings("*").setSettings(ImmutableSettings.settingsBuilder().put("index.refresh_interval", -1)); 75 | for (int i = 0; i < NUMBER_OF_INDICES; i++) { 76 | BulkRequestBuilder bulkRequest = client.prepareBulk(); 77 | for (int j = 0; j < BATCH; j++) { 78 | bulkRequest.add(Requests.indexRequest("test" + i).id(Integer.toString(j)).type("type").source("field", "1")); 79 | if (bulkRequest.numberOfActions() % 10000 == 0) { 80 | bulkRequest.execute().actionGet(); 81 | bulkRequest = client.prepareBulk(); 82 | } 83 | } 84 | if (bulkRequest.numberOfActions() > 0) { 85 | bulkRequest.execute().actionGet(); 86 | } 87 | } 88 | client.admin().indices().prepareRefresh("*").execute().actionGet(); 89 | client.admin().indices().prepareUpdateSettings("*").setSettings(ImmutableSettings.settingsBuilder().put("index.refresh_interval", 1)); 90 | client.admin().cluster().prepareHealth("*").setWaitForGreenStatus().execute().actionGet(); 91 | 92 | UpdateByQueryResponse response = updateByQueryClientWrapper.prepareUpdateByQuery() 93 | .setIndices("*") 94 | .setQuery(QueryBuilders.matchAllQuery()) 95 | .setScript("ctx._source.field += 1") 96 | .execute() 97 | .actionGet(); 98 | 99 | System.out.printf(Locale.ENGLISH, "Update by query took: %d ms and matches with %d documents\n", response.tookInMillis(), response.totalHits()); 100 | System.out.printf(Locale.ENGLISH, "and %d documents have actually successfully been updated.\n", response.updated()); 101 | if (response.totalHits() != BATCH * NUMBER_OF_INDICES) { 102 | System.err.printf( 103 | Locale.ENGLISH, 104 | "Number of matches is incorrect! Expected %d but was %d\n", 105 | BATCH * NUMBER_OF_INDICES, 106 | response.totalHits() 107 | ); 108 | } 109 | 110 | if (response.indexResponses().length != NUMBER_OF_INDICES) { 111 | System.err.printf( 112 | Locale.ENGLISH, 113 | "Number of index sub responses is incorrect! Expected %d but was %d\n", 114 | BATCH, 115 | response.indexResponses().length 116 | ); 117 | } 118 | 119 | for (IndexUpdateByQueryResponse indexResponse : response.indexResponses()) { 120 | for (Map.Entry bulkItemResponses : indexResponse.responsesByShard().entrySet()) { 121 | for (BulkItemResponse bulkItemResponse : bulkItemResponses.getValue()) { 122 | IndexResponse indexRes = bulkItemResponse.getResponse(); 123 | if (indexRes.getVersion() != 2) { 124 | System.out.printf( 125 | Locale.ENGLISH, 126 | "Version doesn't match for id[%s] expected version 2, but was %d\n", 127 | indexRes.getId(), 128 | indexRes.getVersion() 129 | ); 130 | } 131 | } 132 | } 133 | } 134 | 135 | } finally { 136 | for (Node node : nodes) { 137 | node.close(); 138 | } 139 | } 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, out 2 | 3 | log4j.appender.out=org.apache.log4j.ConsoleAppender 4 | log4j.appender.out.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n 6 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/updatebyquery/config/scripts/field1_incrementer.groovy: -------------------------------------------------------------------------------- 1 | ctx._source.field1 += 1 2 | --------------------------------------------------------------------------------