/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.update; import org.opensearch.action.update.UpdateRequestBuilder; import org.opensearch.action.update.UpdateResponse; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.test.OpenSearchIntegTestCase; import org.junit.Before; import java.io.IOException; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; /** * Tests for noop updates. */ public class UpdateNoopIT extends OpenSearchIntegTestCase { public void testSingleField() throws Exception { updateAndCheckSource(0, 1, fields("bar", "baz")); updateAndCheckSource(0, 1, fields("bar", "baz")); updateAndCheckSource(1, 2, fields("bar", "bir")); updateAndCheckSource(1, 2, fields("bar", "bir")); updateAndCheckSource(2, 3, fields("bar", "foo")); updateAndCheckSource(3, 4, fields("bar", null)); updateAndCheckSource(3, 4, fields("bar", null)); updateAndCheckSource(4, 5, fields("bar", "foo")); // detect_noop defaults to true updateAndCheckSource(4, 5, null, fields("bar", "foo")); assertEquals(4, totalNoopUpdates()); } public void testTwoFields() throws Exception { // Use random keys so we get random iteration order. String key1 = 1 + randomAlphaOfLength(3); String key2 = 2 + randomAlphaOfLength(3); String key3 = 3 + randomAlphaOfLength(3); updateAndCheckSource(0, 1, fields(key1, "foo", key2, "baz")); updateAndCheckSource(0, 1, fields(key1, "foo", key2, "baz")); updateAndCheckSource(1, 2, fields(key1, "foo", key2, "bir")); updateAndCheckSource(1, 2, fields(key1, "foo", key2, "bir")); updateAndCheckSource(2, 3, fields(key1, "foo", key2, "foo")); updateAndCheckSource(3, 4, fields(key1, "foo", key2, null)); updateAndCheckSource(3, 4, fields(key1, "foo", key2, null)); updateAndCheckSource(4, 5, fields(key1, "foo", key2, "foo")); updateAndCheckSource(5, 6, fields(key1, null, key2, "foo")); updateAndCheckSource(5, 6, fields(key1, null, key2, "foo")); updateAndCheckSource(6, 7, fields(key1, null, key2, null)); updateAndCheckSource(6, 7, fields(key1, null, key2, null)); updateAndCheckSource(7, 8, fields(key1, null, key2, null, key3, null)); assertEquals(5, totalNoopUpdates()); } public void testArrayField() throws Exception { updateAndCheckSource(0, 1, fields("bar", "baz")); updateAndCheckSource(1, 2, fields("bar", new String[] { "baz", "bort" })); updateAndCheckSource(1, 2, fields("bar", new String[] { "baz", "bort" })); updateAndCheckSource(2, 3, fields("bar", "bir")); updateAndCheckSource(2, 3, fields("bar", "bir")); updateAndCheckSource(3, 4, fields("bar", new String[] { "baz", "bort" })); updateAndCheckSource(3, 4, fields("bar", new String[] { "baz", "bort" })); updateAndCheckSource(4, 5, fields("bar", new String[] { "bir", "bort" })); updateAndCheckSource(4, 5, fields("bar", new String[] { "bir", "bort" })); updateAndCheckSource(5, 6, fields("bar", new String[] { "bir", "for" })); updateAndCheckSource(5, 6, fields("bar", new String[] { "bir", "for" })); updateAndCheckSource(6, 7, fields("bar", new String[] { "bir", "for", "far" })); assertEquals(5, totalNoopUpdates()); } public void testMap() throws Exception { // Use random keys so we get variable iteration order. String key1 = 1 + randomAlphaOfLength(3); String key2 = 2 + randomAlphaOfLength(3); String key3 = 3 + randomAlphaOfLength(3); updateAndCheckSource( 0, 1, XContentFactory.jsonBuilder().startObject().startObject("test").field(key1, "foo").field(key2, "baz").endObject().endObject() ); updateAndCheckSource( 0, 1, XContentFactory.jsonBuilder().startObject().startObject("test").field(key1, "foo").field(key2, "baz").endObject().endObject() ); updateAndCheckSource( 1, 2, XContentFactory.jsonBuilder().startObject().startObject("test").field(key1, "foo").field(key2, "bir").endObject().endObject() ); updateAndCheckSource( 1, 2, XContentFactory.jsonBuilder().startObject().startObject("test").field(key1, "foo").field(key2, "bir").endObject().endObject() ); updateAndCheckSource( 2, 3, XContentFactory.jsonBuilder().startObject().startObject("test").field(key1, "foo").field(key2, "foo").endObject().endObject() ); updateAndCheckSource( 3, 4, XContentFactory.jsonBuilder() .startObject() .startObject("test") .field(key1, "foo") .field(key2, (Object) null) .endObject() .endObject() ); updateAndCheckSource( 3, 4, XContentFactory.jsonBuilder() .startObject() .startObject("test") .field(key1, "foo") .field(key2, (Object) null) .endObject() .endObject() ); updateAndCheckSource( 4, 5, XContentFactory.jsonBuilder() .startObject() .startObject("test") .field(key1, "foo") .field(key2, (Object) null) .field(key3, (Object) null) .endObject() .endObject() ); assertEquals(3, totalNoopUpdates()); } public void testMapAndField() throws Exception { updateAndCheckSource( 0, 1, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "baz") .endObject() .endObject() ); updateAndCheckSource( 0, 1, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "baz") .endObject() .endObject() ); updateAndCheckSource( 1, 2, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "bir") .endObject() .endObject() ); updateAndCheckSource( 1, 2, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "bir") .endObject() .endObject() ); updateAndCheckSource( 2, 3, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "foo") .endObject() .endObject() ); updateAndCheckSource( 3, 4, XContentFactory.jsonBuilder() .startObject() .field("f", "bar") .startObject("m") .field("mf1", "foo") .field("mf2", "foo") .endObject() .endObject() ); updateAndCheckSource( 3, 4, XContentFactory.jsonBuilder() .startObject() .field("f", "bar") .startObject("m") .field("mf1", "foo") .field("mf2", "foo") .endObject() .endObject() ); updateAndCheckSource( 4, 5, XContentFactory.jsonBuilder() .startObject() .field("f", "baz") .startObject("m") .field("mf1", "foo") .field("mf2", "foo") .endObject() .endObject() ); updateAndCheckSource( 5, 6, XContentFactory.jsonBuilder() .startObject() .field("f", "bop") .startObject("m") .field("mf1", "foo") .field("mf2", "foo") .endObject() .endObject() ); assertEquals(3, totalNoopUpdates()); } /** * Totally empty requests are noop if and only if detect noops is true and * its true by default. */ public void testTotallyEmpty() throws Exception { updateAndCheckSource( 0, 1, XContentFactory.jsonBuilder() .startObject() .field("f", "foo") .startObject("m") .field("mf1", "foo") .field("mf2", "baz") .endObject() .endObject() ); update(true, 0, 1, XContentFactory.jsonBuilder().startObject().endObject()); update(false, 1, 2, XContentFactory.jsonBuilder().startObject().endObject()); update(null, 1, 2, XContentFactory.jsonBuilder().startObject().endObject()); } private XContentBuilder fields(Object... fields) throws IOException { assertEquals("Fields must field1, value1, field2, value2, etc", 0, fields.length % 2); XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (int i = 0; i < fields.length; i += 2) { builder.field((String) fields[i], fields[i + 1]); } builder.endObject(); return builder; } private void updateAndCheckSource(long expectedSeqNo, long expectedVersion, XContentBuilder xContentBuilder) { updateAndCheckSource(expectedSeqNo, expectedVersion, true, xContentBuilder); } private void updateAndCheckSource(long expectedSeqNo, long expectedVersion, Boolean detectNoop, XContentBuilder xContentBuilder) { UpdateResponse updateResponse = update(detectNoop, expectedSeqNo, expectedVersion, xContentBuilder); assertEquals(updateResponse.getGetResult().sourceRef().utf8ToString(), BytesReference.bytes(xContentBuilder).utf8ToString()); } private UpdateResponse update(Boolean detectNoop, long expectedSeqNo, long expectedVersion, XContentBuilder xContentBuilder) { UpdateRequestBuilder updateRequest = client().prepareUpdate("test", "1") .setDoc(xContentBuilder) .setDocAsUpsert(true) .setFetchSource(true); if (detectNoop != null) { updateRequest.setDetectNoop(detectNoop); } UpdateResponse updateResponse = updateRequest.get(); assertThat(updateResponse.getGetResult(), notNullValue()); assertThat(updateResponse.getSeqNo(), equalTo(expectedSeqNo)); assertThat(updateResponse.getVersion(), equalTo(expectedVersion)); return updateResponse; } private long totalNoopUpdates() { return client().admin() .indices() .prepareStats("test") .setIndexing(true) .get() .getIndex("test") .getTotal() .getIndexing() .getTotal() .getNoopUpdateCount(); } @Before public void setup() { createIndex("test"); ensureGreen(); } }