|  | @@ -0,0 +1,1456 @@
 | 
											
												
													
														|  | 
 |  | +/*
 | 
											
												
													
														|  | 
 |  | + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 | 
											
												
													
														|  | 
 |  | + * or more contributor license agreements. Licensed under the Elastic License
 | 
											
												
													
														|  | 
 |  | + * 2.0; you may not use this file except in compliance with the Elastic License
 | 
											
												
													
														|  | 
 |  | + * 2.0.
 | 
											
												
													
														|  | 
 |  | + */
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +package org.elasticsearch.xpack.esql.qa.rest;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +import org.apache.http.util.EntityUtils;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.Version;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.client.Request;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.client.Response;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.client.ResponseException;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.common.Strings;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.common.network.NetworkAddress;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.core.CheckedConsumer;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.geo.GeometryTestUtils;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.index.mapper.BlockLoader;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.index.mapper.SourceFieldMapper;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.logging.LogManager;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.logging.Logger;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.test.ESTestCase;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.test.ListMatcher;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.test.rest.ESRestTestCase;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.xcontent.XContentBuilder;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.xcontent.XContentType;
 | 
											
												
													
														|  | 
 |  | +import org.elasticsearch.xcontent.json.JsonXContent;
 | 
											
												
													
														|  | 
 |  | +import org.hamcrest.Matcher;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +import java.io.IOException;
 | 
											
												
													
														|  | 
 |  | +import java.math.BigDecimal;
 | 
											
												
													
														|  | 
 |  | +import java.math.BigInteger;
 | 
											
												
													
														|  | 
 |  | +import java.util.ArrayList;
 | 
											
												
													
														|  | 
 |  | +import java.util.Collections;
 | 
											
												
													
														|  | 
 |  | +import java.util.Comparator;
 | 
											
												
													
														|  | 
 |  | +import java.util.List;
 | 
											
												
													
														|  | 
 |  | +import java.util.Locale;
 | 
											
												
													
														|  | 
 |  | +import java.util.Map;
 | 
											
												
													
														|  | 
 |  | +import java.util.TreeMap;
 | 
											
												
													
														|  | 
 |  | +import java.util.function.Function;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +import static org.elasticsearch.test.ListMatcher.matchesList;
 | 
											
												
													
														|  | 
 |  | +import static org.elasticsearch.test.MapMatcher.assertMap;
 | 
											
												
													
														|  | 
 |  | +import static org.elasticsearch.test.MapMatcher.matchesMap;
 | 
											
												
													
														|  | 
 |  | +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.entityToMap;
 | 
											
												
													
														|  | 
 |  | +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.runEsqlSync;
 | 
											
												
													
														|  | 
 |  | +import static org.hamcrest.Matchers.closeTo;
 | 
											
												
													
														|  | 
 |  | +import static org.hamcrest.Matchers.containsString;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +/**
 | 
											
												
													
														|  | 
 |  | + * Creates indices with many different mappings and fetches values from them to make sure
 | 
											
												
													
														|  | 
 |  | + * we can do it. Think of this as an integration test for {@link BlockLoader}
 | 
											
												
													
														|  | 
 |  | + * implementations <strong>and</strong> an integration test for field resolution.
 | 
											
												
													
														|  | 
 |  | + * This is a port of a test with the same name on the SQL side.
 | 
											
												
													
														|  | 
 |  | + */
 | 
											
												
													
														|  | 
 |  | +public abstract class FieldExtractorTestCase extends ESRestTestCase {
 | 
											
												
													
														|  | 
 |  | +    private static final Logger logger = LogManager.getLogger(FieldExtractorTestCase.class);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testTextField() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        textTest().test(randomAlphaOfLength(20));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test textTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("text").randomStoreUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testKeywordField() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        Integer ignoreAbove = randomBoolean() ? null : between(10, 50);
 | 
											
												
													
														|  | 
 |  | +        int length = between(10, 50);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(length);
 | 
											
												
													
														|  | 
 |  | +        keywordTest().ignoreAbove(ignoreAbove).test(value, ignoredByIgnoreAbove(ignoreAbove, length) ? null : value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test keywordTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("keyword").randomDocValuesAndStoreUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testConstantKeywordField() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        boolean specifyInMapping = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        boolean specifyInDocument = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(20);
 | 
											
												
													
														|  | 
 |  | +        new Test("constant_keyword").expectedType("keyword")
 | 
											
												
													
														|  | 
 |  | +            .value(specifyInMapping ? value : null)
 | 
											
												
													
														|  | 
 |  | +            .test(specifyInDocument ? value : null, specifyInMapping || specifyInDocument ? value : null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testWildcardField() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        Integer ignoreAbove = randomBoolean() ? null : between(10, 50);
 | 
											
												
													
														|  | 
 |  | +        int length = between(10, 50);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(length);
 | 
											
												
													
														|  | 
 |  | +        new Test("wildcard").expectedType("keyword")
 | 
											
												
													
														|  | 
 |  | +            .ignoreAbove(ignoreAbove)
 | 
											
												
													
														|  | 
 |  | +            .test(value, ignoredByIgnoreAbove(ignoreAbove, length) ? null : value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testLong() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        long value = randomLong();
 | 
											
												
													
														|  | 
 |  | +        longTest().test(randomBoolean() ? Long.toString(value) : value, value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testLongWithDecimalParts() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        long value = randomLong();
 | 
											
												
													
														|  | 
 |  | +        int decimalPart = between(1, 99);
 | 
											
												
													
														|  | 
 |  | +        BigDecimal withDecimals = new BigDecimal(value + "." + decimalPart);
 | 
											
												
													
														|  | 
 |  | +        /*
 | 
											
												
													
														|  | 
 |  | +         * It's possible to pass the BigDecimal here without converting to a string
 | 
											
												
													
														|  | 
 |  | +         * but that rounds in a different way, and I'm not quite able to reproduce it
 | 
											
												
													
														|  | 
 |  | +         * at the time.
 | 
											
												
													
														|  | 
 |  | +         */
 | 
											
												
													
														|  | 
 |  | +        longTest().test(withDecimals.toString(), value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testLongMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        longTest().forceIgnoreMalformed().test(randomAlphaOfLength(5), null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test longTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("long").randomIgnoreMalformedUnlessSynthetic().randomDocValuesUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testInt() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        int value = randomInt();
 | 
											
												
													
														|  | 
 |  | +        intTest().test(randomBoolean() ? Integer.toString(value) : value, value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testIntWithDecimalParts() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        double value = randomDoubleBetween(Integer.MIN_VALUE, Integer.MAX_VALUE, true);
 | 
											
												
													
														|  | 
 |  | +        intTest().test(randomBoolean() ? Double.toString(value) : value, (int) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testIntMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        intTest().forceIgnoreMalformed().test(randomAlphaOfLength(5), null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test intTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("integer").randomIgnoreMalformedUnlessSynthetic().randomDocValuesUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testShort() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        short value = randomShort();
 | 
											
												
													
														|  | 
 |  | +        shortTest().test(randomBoolean() ? Short.toString(value) : value, (int) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testShortWithDecimalParts() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        double value = randomDoubleBetween(Short.MIN_VALUE, Short.MAX_VALUE, true);
 | 
											
												
													
														|  | 
 |  | +        shortTest().test(randomBoolean() ? Double.toString(value) : value, (int) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testShortMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        shortTest().forceIgnoreMalformed().test(randomAlphaOfLength(5), null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test shortTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("short").expectedType("integer").randomIgnoreMalformedUnlessSynthetic().randomDocValuesUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testByte() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        byte value = randomByte();
 | 
											
												
													
														|  | 
 |  | +        byteTest().test(Byte.toString(value), (int) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testByteWithDecimalParts() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        double value = randomDoubleBetween(Byte.MIN_VALUE, Byte.MAX_VALUE, true);
 | 
											
												
													
														|  | 
 |  | +        byteTest().test(randomBoolean() ? Double.toString(value) : value, (int) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testByteMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        byteTest().forceIgnoreMalformed().test(randomAlphaOfLength(5), null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test byteTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("byte").expectedType("integer").randomIgnoreMalformedUnlessSynthetic().randomDocValuesUnlessSynthetic();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testUnsignedLong() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        BigInteger value = randomUnsignedLong();
 | 
											
												
													
														|  | 
 |  | +        new Test("unsigned_long").randomIgnoreMalformedUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .randomDocValuesUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .test(
 | 
											
												
													
														|  | 
 |  | +                randomBoolean() ? value.toString() : value,
 | 
											
												
													
														|  | 
 |  | +                value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0 ? value.longValue() : value
 | 
											
												
													
														|  | 
 |  | +            );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testUnsignedLongMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        new Test("unsigned_long").forceIgnoreMalformed().randomDocValuesUnlessSynthetic().test(randomAlphaOfLength(5), null);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testDouble() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        double value = randomDouble();
 | 
											
												
													
														|  | 
 |  | +        new Test("double").randomIgnoreMalformedUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .randomDocValuesUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .test(randomBoolean() ? Double.toString(value) : value, value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testFloat() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        float value = randomFloat();
 | 
											
												
													
														|  | 
 |  | +        new Test("float").expectedType("double")
 | 
											
												
													
														|  | 
 |  | +            .randomIgnoreMalformedUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .randomDocValuesUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .test(randomBoolean() ? Float.toString(value) : value, (double) value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testScaledFloat() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        double value = randomBoolean() ? randomDoubleBetween(-Double.MAX_VALUE, Double.MAX_VALUE, true) : randomFloat();
 | 
											
												
													
														|  | 
 |  | +        double scalingFactor = randomDoubleBetween(0, Double.MAX_VALUE, false);
 | 
											
												
													
														|  | 
 |  | +        new Test("scaled_float").expectedType("double")
 | 
											
												
													
														|  | 
 |  | +            .randomIgnoreMalformedUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .randomDocValuesUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .scalingFactor(scalingFactor)
 | 
											
												
													
														|  | 
 |  | +            .test(randomBoolean() ? Double.toString(value) : value, scaledFloatMatcher(scalingFactor, value));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Matcher<Double> scaledFloatMatcher(double scalingFactor, double d) {
 | 
											
												
													
														|  | 
 |  | +        long encoded = Math.round(d * scalingFactor);
 | 
											
												
													
														|  | 
 |  | +        double decoded = encoded / scalingFactor;
 | 
											
												
													
														|  | 
 |  | +        return closeTo(decoded, Math.ulp(decoded));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testBoolean() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        boolean value = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        new Test("boolean").ignoreMalformed(randomBoolean())
 | 
											
												
													
														|  | 
 |  | +            .randomDocValuesUnlessSynthetic()
 | 
											
												
													
														|  | 
 |  | +            .test(randomBoolean() ? Boolean.toString(value) : value, value);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testIp() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        ipTest().test(NetworkAddress.format(randomIp(randomBoolean())));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private Test ipTest() {
 | 
											
												
													
														|  | 
 |  | +        return new Test("ip").ignoreMalformed(randomBoolean());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testVersionField() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        new Test("version").test(randomVersionString());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testGeoPoint() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "not supported until 8.13",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_13_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        new Test("geo_point")
 | 
											
												
													
														|  | 
 |  | +            // TODO we should support loading geo_point from doc values if source isn't enabled
 | 
											
												
													
														|  | 
 |  | +            .sourceMode(randomValueOtherThanMany(s -> s.stored() == false, () -> randomFrom(SourceMode.values())))
 | 
											
												
													
														|  | 
 |  | +            .ignoreMalformed(randomBoolean())
 | 
											
												
													
														|  | 
 |  | +            .storeAndDocValues(randomBoolean(), randomBoolean())
 | 
											
												
													
														|  | 
 |  | +            .test(GeometryTestUtils.randomPoint(false).toString());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testGeoShape() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "not supported until 8.13",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_13_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        new Test("geo_shape")
 | 
											
												
													
														|  | 
 |  | +            // TODO if source isn't enabled how can we load *something*? It's just triangles, right?
 | 
											
												
													
														|  | 
 |  | +            .sourceMode(randomValueOtherThanMany(s -> s.stored() == false, () -> randomFrom(SourceMode.values())))
 | 
											
												
													
														|  | 
 |  | +            .ignoreMalformed(randomBoolean())
 | 
											
												
													
														|  | 
 |  | +            .storeAndDocValues(randomBoolean(), randomBoolean())
 | 
											
												
													
														|  | 
 |  | +            // TODO pick supported random shapes
 | 
											
												
													
														|  | 
 |  | +            .test(GeometryTestUtils.randomPoint(false).toString());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testAliasToKeyword() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        keywordTest().createAlias().test(randomAlphaOfLength(20));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testAliasToText() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        textTest().createAlias().test(randomAlphaOfLength(20));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testAliasToInt() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        intTest().createAlias().test(randomInt());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testFlattenedUnsupported() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        new Test("flattened").createIndex("test", "flattened");
 | 
											
												
													
														|  | 
 |  | +        index("test", """
 | 
											
												
													
														|  | 
 |  | +            {"flattened": {"a": "foo"}}""");
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("flattened", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public void testEmptyMapping() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        createIndex("test", index -> {});
 | 
											
												
													
														|  | 
 |  | +        index("test", """
 | 
											
												
													
														|  | 
 |  | +            {}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT missing | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(err, containsString("Unknown column [missing]"));
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // TODO this is broken in main too
 | 
											
												
													
														|  | 
 |  | +        // Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        // assertMap(
 | 
											
												
													
														|  | 
 |  | +        // result,
 | 
											
												
													
														|  | 
 |  | +        // matchesMap().entry("columns", List.of(columnInfo("f", "unsupported"), columnInfo("f.raw", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +        // .entry("values", List.of(matchesList().item(null).item(null)))
 | 
											
												
													
														|  | 
 |  | +        // );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "text_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "text",
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "raw": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "keyword",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_above": 10
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTextFieldWithKeywordSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(20);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = new Test("text").storeAndDocValues(randomBoolean(), null).sub("raw", keywordTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("text_field", "text"), columnInfo("text_field.raw", "keyword")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "text_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "text",
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "int": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true/false
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTextFieldWithIntegerSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        int value = randomInt();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = textTest().sub("int", intTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("text_field", "text"), columnInfo("text_field.int", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(Integer.toString(value)).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "text_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "text",
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "int": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTextFieldWithIntegerSubfieldMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(5);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = textTest().sourceMode(SourceMode.DEFAULT).sub("int", intTest().ignoreMalformed(true)).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("text_field", "text"), columnInfo("text_field.int", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "text_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "text",
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "ip": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "ip",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true/false
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTextFieldWithIpSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = NetworkAddress.format(randomIp(randomBoolean()));
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = textTest().sub("ip", ipTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("text_field", "text"), columnInfo("text_field.ip", "ip")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "text_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "text",
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "ip": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "ip",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTextFieldWithIpSubfieldMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(10);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = textTest().sourceMode(SourceMode.DEFAULT).sub("ip", ipTest().ignoreMalformed(true)).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("text_field", "text"), columnInfo("text_field.ip", "ip")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "integer_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true/false,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "str": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "text/keyword"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntFieldWithTextOrKeywordSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        int value = randomInt();
 | 
											
												
													
														|  | 
 |  | +        boolean text = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = intTest().sub("str", text ? textTest() : keywordTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry(
 | 
											
												
													
														|  | 
 |  | +                "columns",
 | 
											
												
													
														|  | 
 |  | +                List.of(columnInfo("integer_field", "integer"), columnInfo("integer_field.str", text ? "text" : "keyword"))
 | 
											
												
													
														|  | 
 |  | +            ).entry("values", List.of(matchesList().item(value).item(Integer.toString(value))))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "integer_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "str": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "text/keyword"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntFieldWithTextOrKeywordSubfieldMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(5);
 | 
											
												
													
														|  | 
 |  | +        boolean text = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = intTest().forceIgnoreMalformed().sub("str", text ? textTest() : keywordTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry(
 | 
											
												
													
														|  | 
 |  | +                "columns",
 | 
											
												
													
														|  | 
 |  | +                List.of(columnInfo("integer_field", "integer"), columnInfo("integer_field.str", text ? "text" : "keyword"))
 | 
											
												
													
														|  | 
 |  | +            ).entry("values", List.of(matchesList().item(null).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "ip_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "ip",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true/false,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "str": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "text/keyword"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIpFieldWithTextOrKeywordSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = NetworkAddress.format(randomIp(randomBoolean()));
 | 
											
												
													
														|  | 
 |  | +        boolean text = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = ipTest().sub("str", text ? textTest() : keywordTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("ip_field", "ip"), columnInfo("ip_field.str", text ? "text" : "keyword")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "ip_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "ip",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "str": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "text/keyword"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIpFieldWithTextOrKeywordSubfieldMalformed() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        String value = randomAlphaOfLength(5);
 | 
											
												
													
														|  | 
 |  | +        boolean text = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = ipTest().forceIgnoreMalformed().sub("str", text ? textTest() : keywordTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("ip_field", "ip"), columnInfo("ip_field.str", text ? "text" : "keyword")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "integer_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "ip",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true/false,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "byte": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "byte",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true/false
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntFieldWithByteSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        byte value = randomByte();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = intTest().sub("byte", byteTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("integer_field", "integer"), columnInfo("integer_field.byte", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item((int) value).item((int) value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "integer_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true/false,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "byte": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "byte",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntFieldWithByteSubfieldTooBig() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        int value = randomValueOtherThanMany((Integer v) -> (Byte.MIN_VALUE <= v) && (v <= Byte.MAX_VALUE), ESTestCase::randomInt);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = intTest().sourceMode(SourceMode.DEFAULT)
 | 
											
												
													
														|  | 
 |  | +            .sub("byte", byteTest().ignoreMalformed(true))
 | 
											
												
													
														|  | 
 |  | +            .roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("integer_field", "integer"), columnInfo("integer_field.byte", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(value).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "byte_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "byte",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true/false,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "int": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "int",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true/false
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testByteFieldWithIntSubfield() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        byte value = randomByte();
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = byteTest().sub("int", intTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("byte_field", "integer"), columnInfo("byte_field.int", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item((int) value).item((int) value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "byte_field": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "byte",
 | 
											
												
													
														|  | 
 |  | +     *   "ignore_malformed": true,
 | 
											
												
													
														|  | 
 |  | +     *   "fields": {
 | 
											
												
													
														|  | 
 |  | +     *     "int": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "int",
 | 
											
												
													
														|  | 
 |  | +     *       "ignore_malformed": true/false
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     *   }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testByteFieldWithIntSubfieldTooBig() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        int value = randomValueOtherThanMany((Integer v) -> (Byte.MIN_VALUE <= v) && (v <= Byte.MAX_VALUE), ESTestCase::randomInt);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = byteTest().forceIgnoreMalformed().sub("int", intTest()).roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("byte_field", "integer"), columnInfo("byte_field.int", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null).item(value)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "f": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "f": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "long"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIncompatibleTypes() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        keywordTest().createIndex("test1", "f");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"f": "f1"}""");
 | 
											
												
													
														|  | 
 |  | +        longTest().createIndex("test2", "f");
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"f": 1}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test*"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("f", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null), matchesList().item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT f | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(
 | 
											
												
													
														|  | 
 |  | +            deyaml(err),
 | 
											
												
													
														|  | 
 |  | +            containsString(
 | 
											
												
													
														|  | 
 |  | +                "Cannot use field [f] due to ambiguities being mapped as [2] incompatible types: [keyword] in [test1], [long] in [test2]"
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "file": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "other_file": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testDistinctInEachIndex() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        keywordTest().createIndex("test1", "file");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"file": "f1"}""");
 | 
											
												
													
														|  | 
 |  | +        keywordTest().createIndex("test2", "other");
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"other": "o2"}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT file, other"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("file", "keyword"), columnInfo("other", "keyword")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item("f1").item(null), matchesList().item(null).item("o2")))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "file": {
 | 
											
												
													
														|  | 
 |  | +     *    "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "file": {
 | 
											
												
													
														|  | 
 |  | +     *    "type": "object",
 | 
											
												
													
														|  | 
 |  | +     *    "properties": {
 | 
											
												
													
														|  | 
 |  | +     *       "raw": {
 | 
											
												
													
														|  | 
 |  | +     *          "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     *       }
 | 
											
												
													
														|  | 
 |  | +     *    }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testMergeKeywordAndObject() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        keywordTest().createIndex("test1", "file");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"file": "f1"}""");
 | 
											
												
													
														|  | 
 |  | +        createIndex("test2", index -> {
 | 
											
												
													
														|  | 
 |  | +            index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +            {
 | 
											
												
													
														|  | 
 |  | +                index.startObject("file");
 | 
											
												
													
														|  | 
 |  | +                {
 | 
											
												
													
														|  | 
 |  | +                    index.field("type", "object");
 | 
											
												
													
														|  | 
 |  | +                    index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +                    {
 | 
											
												
													
														|  | 
 |  | +                        index.startObject("raw").field("type", "keyword").endObject();
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                    index.endObject();
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                index.endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +        });
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"file": {"raw": "o2"}}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT file, file.raw | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(
 | 
											
												
													
														|  | 
 |  | +            deyaml(err),
 | 
											
												
													
														|  | 
 |  | +            containsString(
 | 
											
												
													
														|  | 
 |  | +                "Cannot use field [file] due to ambiguities"
 | 
											
												
													
														|  | 
 |  | +                    + " being mapped as [2] incompatible types: [keyword] in [test1], [object] in [test2]"
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT file.raw | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("file", "unsupported"), columnInfo("file.raw", "keyword")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null).item("o2"), matchesList().item(null).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * One index with an unsupported field and a supported sub-field. The supported sub-field
 | 
											
												
													
														|  | 
 |  | +     * is marked as unsupported <strong>because</strong> the parent is unsupported. Mapping:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "f": {
 | 
											
												
													
														|  | 
 |  | +     *    "type": "ip_range"  ----- The type here doesn't matter, but it has to be one we don't support
 | 
											
												
													
														|  | 
 |  | +     *    "fields": {
 | 
											
												
													
														|  | 
 |  | +     *       "raw": {
 | 
											
												
													
														|  | 
 |  | +     *          "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     *       }
 | 
											
												
													
														|  | 
 |  | +     *    }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testPropagateUnsupportedToSubFields() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        createIndex("test", index -> {
 | 
											
												
													
														|  | 
 |  | +            index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +            index.startObject("f");
 | 
											
												
													
														|  | 
 |  | +            {
 | 
											
												
													
														|  | 
 |  | +                index.field("type", "ip_range");
 | 
											
												
													
														|  | 
 |  | +                index.startObject("fields");
 | 
											
												
													
														|  | 
 |  | +                {
 | 
											
												
													
														|  | 
 |  | +                    index.startObject("raw").field("type", "keyword").endObject();
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                index.endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +        });
 | 
											
												
													
														|  | 
 |  | +        index("test", """
 | 
											
												
													
														|  | 
 |  | +            {"f": "192.168.0.1/24"}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT f, f.raw | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(err, containsString("Cannot use field [f] with unsupported type [ip_range]"));
 | 
											
												
													
														|  | 
 |  | +        assertThat(err, containsString("Cannot use field [f.raw] with unsupported type [ip_range]"));
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("f", "unsupported"), columnInfo("f.raw", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "f": {
 | 
											
												
													
														|  | 
 |  | +     *    "type": "ip_range"  ----- The type here doesn't matter, but it has to be one we don't support
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "f": {
 | 
											
												
													
														|  | 
 |  | +     *    "type": "object",
 | 
											
												
													
														|  | 
 |  | +     *    "properties": {
 | 
											
												
													
														|  | 
 |  | +     *       "raw": {
 | 
											
												
													
														|  | 
 |  | +     *          "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     *       }
 | 
											
												
													
														|  | 
 |  | +     *    }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testMergeUnsupportedAndObject() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        createIndex("test1", index -> {
 | 
											
												
													
														|  | 
 |  | +            index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +            index.startObject("f").field("type", "ip_range").endObject();
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +        });
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"f": "192.168.0.1/24"}""");
 | 
											
												
													
														|  | 
 |  | +        createIndex("test2", index -> {
 | 
											
												
													
														|  | 
 |  | +            index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +            {
 | 
											
												
													
														|  | 
 |  | +                index.startObject("f");
 | 
											
												
													
														|  | 
 |  | +                {
 | 
											
												
													
														|  | 
 |  | +                    index.field("type", "object");
 | 
											
												
													
														|  | 
 |  | +                    index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +                    {
 | 
											
												
													
														|  | 
 |  | +                        index.startObject("raw").field("type", "keyword").endObject();
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                    index.endObject();
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                index.endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +        });
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"f": {"raw": "o2"}}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT f, f.raw | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(err, containsString("Cannot use field [f] with unsupported type [ip_range]"));
 | 
											
												
													
														|  | 
 |  | +        assertThat(err, containsString("Cannot use field [f.raw] with unsupported type [ip_range]"));
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("f", "unsupported"), columnInfo("f.raw", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null).item(null), matchesList().item(null).item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "integer"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "integer",
 | 
											
												
													
														|  | 
 |  | +     *     "doc_values": false
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntegerDocValuesConflict() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        intTest().sourceMode(SourceMode.DEFAULT).storeAndDocValues(null, true).createIndex("test1", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 1}""");
 | 
											
												
													
														|  | 
 |  | +        intTest().sourceMode(SourceMode.DEFAULT).storeAndDocValues(null, false).createIndex("test2", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 2}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT emp_no | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("emp_no", "integer")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(1), matchesList().item(2)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "long"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "integer"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     *
 | 
											
												
													
														|  | 
 |  | +     * In an ideal world we'd promote the {@code integer} to an {@code long} and just go.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testLongIntegerConflict() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        longTest().sourceMode(SourceMode.DEFAULT).createIndex("test1", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 1}""");
 | 
											
												
													
														|  | 
 |  | +        intTest().sourceMode(SourceMode.DEFAULT).createIndex("test2", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 2}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT emp_no | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(
 | 
											
												
													
														|  | 
 |  | +            deyaml(err),
 | 
											
												
													
														|  | 
 |  | +            containsString(
 | 
											
												
													
														|  | 
 |  | +                "Cannot use field [emp_no] due to ambiguities being "
 | 
											
												
													
														|  | 
 |  | +                    + "mapped as [2] incompatible types: [integer] in [test2], [long] in [test1]"
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("emp_no", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null), matchesList().item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "integer"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *     "type": "short"
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     *
 | 
											
												
													
														|  | 
 |  | +     * In an ideal world we'd promote the {@code short} to an {@code integer} and just go.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testIntegerShortConflict() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        intTest().sourceMode(SourceMode.DEFAULT).createIndex("test1", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 1}""");
 | 
											
												
													
														|  | 
 |  | +        shortTest().sourceMode(SourceMode.DEFAULT).createIndex("test2", "emp_no");
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"emp_no": 2}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT emp_no | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(
 | 
											
												
													
														|  | 
 |  | +            deyaml(err),
 | 
											
												
													
														|  | 
 |  | +            containsString(
 | 
											
												
													
														|  | 
 |  | +                "Cannot use field [emp_no] due to ambiguities being "
 | 
											
												
													
														|  | 
 |  | +                    + "mapped as [2] incompatible types: [integer] in [test1], [short] in [test2]"
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 2"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(
 | 
											
												
													
														|  | 
 |  | +            result,
 | 
											
												
													
														|  | 
 |  | +            matchesMap().entry("columns", List.of(columnInfo("emp_no", "unsupported")))
 | 
											
												
													
														|  | 
 |  | +                .entry("values", List.of(matchesList().item(null), matchesList().item(null)))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Two indices, one with:
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "foo": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "object",
 | 
											
												
													
														|  | 
 |  | +     *   "properties": {
 | 
											
												
													
														|  | 
 |  | +     *     "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "integer"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>
 | 
											
												
													
														|  | 
 |  | +     * and the other with
 | 
											
												
													
														|  | 
 |  | +     * <pre>
 | 
											
												
													
														|  | 
 |  | +     * "foo": {
 | 
											
												
													
														|  | 
 |  | +     *   "type": "object",
 | 
											
												
													
														|  | 
 |  | +     *   "properties": {
 | 
											
												
													
														|  | 
 |  | +     *     "emp_no": {
 | 
											
												
													
														|  | 
 |  | +     *       "type": "keyword"
 | 
											
												
													
														|  | 
 |  | +     *     }
 | 
											
												
													
														|  | 
 |  | +     * }
 | 
											
												
													
														|  | 
 |  | +     * </pre>.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    public void testTypeConflictInObject() throws IOException {
 | 
											
												
													
														|  | 
 |  | +        assumeTrue(
 | 
											
												
													
														|  | 
 |  | +            "order of fields in error message inconsistent before 8.14",
 | 
											
												
													
														|  | 
 |  | +            getCachedNodesVersions().stream().allMatch(v -> Version.fromString(v).onOrAfter(Version.V_8_14_0))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        createIndex("test1", empNoInObject("integer"));
 | 
											
												
													
														|  | 
 |  | +        index("test1", """
 | 
											
												
													
														|  | 
 |  | +            {"foo": {"emp_no": 1}}""");
 | 
											
												
													
														|  | 
 |  | +        createIndex("test2", empNoInObject("keyword"));
 | 
											
												
													
														|  | 
 |  | +        index("test2", """
 | 
											
												
													
														|  | 
 |  | +            {"foo": {"emp_no": "cat"}}""");
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 3"));
 | 
											
												
													
														|  | 
 |  | +        assertMap(result, matchesMap().entry("columns", List.of(columnInfo("foo.emp_no", "unsupported"))).extraOk());
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        ResponseException e = expectThrows(
 | 
											
												
													
														|  | 
 |  | +            ResponseException.class,
 | 
											
												
													
														|  | 
 |  | +            () -> runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | SORT foo.emp_no | LIMIT 3"))
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        String err = EntityUtils.toString(e.getResponse().getEntity());
 | 
											
												
													
														|  | 
 |  | +        assertThat(
 | 
											
												
													
														|  | 
 |  | +            deyaml(err),
 | 
											
												
													
														|  | 
 |  | +            containsString(
 | 
											
												
													
														|  | 
 |  | +                "Cannot use field [foo.emp_no] due to ambiguities being "
 | 
											
												
													
														|  | 
 |  | +                    + "mapped as [2] incompatible types: [integer] in [test1], [keyword] in [test2]"
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private CheckedConsumer<XContentBuilder, IOException> empNoInObject(String empNoType) {
 | 
											
												
													
														|  | 
 |  | +        return index -> {
 | 
											
												
													
														|  | 
 |  | +            index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +            {
 | 
											
												
													
														|  | 
 |  | +                index.startObject("foo");
 | 
											
												
													
														|  | 
 |  | +                {
 | 
											
												
													
														|  | 
 |  | +                    index.field("type", "object");
 | 
											
												
													
														|  | 
 |  | +                    index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +                    {
 | 
											
												
													
														|  | 
 |  | +                        index.startObject("emp_no").field("type", empNoType).endObject();
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                    index.endObject();
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                index.endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            index.endObject();
 | 
											
												
													
														|  | 
 |  | +        };
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private enum SourceMode {
 | 
											
												
													
														|  | 
 |  | +        DEFAULT {
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            void sourceMapping(XContentBuilder builder) {}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            boolean stored() {
 | 
											
												
													
														|  | 
 |  | +                return true;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        },
 | 
											
												
													
														|  | 
 |  | +        STORED {
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            void sourceMapping(XContentBuilder builder) throws IOException {
 | 
											
												
													
														|  | 
 |  | +                builder.startObject(SourceFieldMapper.NAME).field("mode", "stored").endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            boolean stored() {
 | 
											
												
													
														|  | 
 |  | +                return true;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        },
 | 
											
												
													
														|  | 
 |  | +        /* TODO add support to this test for disabling _source
 | 
											
												
													
														|  | 
 |  | +        DISABLED {
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            void sourceMapping(XContentBuilder builder) throws IOException {
 | 
											
												
													
														|  | 
 |  | +                builder.startObject(SourceFieldMapper.NAME).field("mode", "disabled").endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            boolean stored() {
 | 
											
												
													
														|  | 
 |  | +                return false;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        },
 | 
											
												
													
														|  | 
 |  | +         */
 | 
											
												
													
														|  | 
 |  | +        SYNTHETIC {
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            void sourceMapping(XContentBuilder builder) throws IOException {
 | 
											
												
													
														|  | 
 |  | +                builder.startObject(SourceFieldMapper.NAME).field("mode", "synthetic").endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            @Override
 | 
											
												
													
														|  | 
 |  | +            boolean stored() {
 | 
											
												
													
														|  | 
 |  | +                return false;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        };
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        abstract void sourceMapping(XContentBuilder builder) throws IOException;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        abstract boolean stored();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private boolean ignoredByIgnoreAbove(Integer ignoreAbove, int length) {
 | 
											
												
													
														|  | 
 |  | +        return ignoreAbove != null && length > ignoreAbove;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private BigInteger randomUnsignedLong() {
 | 
											
												
													
														|  | 
 |  | +        BigInteger big = BigInteger.valueOf(randomNonNegativeLong()).shiftLeft(1);
 | 
											
												
													
														|  | 
 |  | +        return big.add(randomBoolean() ? BigInteger.ONE : BigInteger.ZERO);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static String randomVersionString() {
 | 
											
												
													
														|  | 
 |  | +        return randomVersionNumber() + (randomBoolean() ? "" : randomPrerelease());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static String randomVersionNumber() {
 | 
											
												
													
														|  | 
 |  | +        int numbers = between(1, 3);
 | 
											
												
													
														|  | 
 |  | +        String v = Integer.toString(between(0, 100));
 | 
											
												
													
														|  | 
 |  | +        for (int i = 1; i < numbers; i++) {
 | 
											
												
													
														|  | 
 |  | +            v += "." + between(0, 100);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        return v;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static String randomPrerelease() {
 | 
											
												
													
														|  | 
 |  | +        if (rarely()) {
 | 
											
												
													
														|  | 
 |  | +            return randomFrom("alpha", "beta", "prerelease", "whatever");
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        return randomFrom("alpha", "beta", "") + randomVersionNumber();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private record StoreAndDocValues(Boolean store, Boolean docValues) {}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static class Test {
 | 
											
												
													
														|  | 
 |  | +        private final String type;
 | 
											
												
													
														|  | 
 |  | +        private final Map<String, Test> subFields = new TreeMap<>();
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        private SourceMode sourceMode;
 | 
											
												
													
														|  | 
 |  | +        private String expectedType;
 | 
											
												
													
														|  | 
 |  | +        private Function<SourceMode, Boolean> ignoreMalformed;
 | 
											
												
													
														|  | 
 |  | +        private Function<SourceMode, StoreAndDocValues> storeAndDocValues = s -> new StoreAndDocValues(null, null);
 | 
											
												
													
														|  | 
 |  | +        private Double scalingFactor;
 | 
											
												
													
														|  | 
 |  | +        private Integer ignoreAbove;
 | 
											
												
													
														|  | 
 |  | +        private Object value;
 | 
											
												
													
														|  | 
 |  | +        private boolean createAlias;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test(String type) {
 | 
											
												
													
														|  | 
 |  | +            this.type = type;
 | 
											
												
													
														|  | 
 |  | +            // Default the expected return type to the field type.
 | 
											
												
													
														|  | 
 |  | +            this.expectedType = type;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test sourceMode(SourceMode sourceMode) {
 | 
											
												
													
														|  | 
 |  | +            this.sourceMode = sourceMode;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test expectedType(String expectedType) {
 | 
											
												
													
														|  | 
 |  | +            this.expectedType = expectedType;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test ignoreMalformed(boolean ignoreMalformed) {
 | 
											
												
													
														|  | 
 |  | +            this.ignoreMalformed = s -> ignoreMalformed;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        /**
 | 
											
												
													
														|  | 
 |  | +         * Enable {@code ignore_malformed} and disable synthetic _source because
 | 
											
												
													
														|  | 
 |  | +         * most fields don't support ignore_malformed and synthetic _source.
 | 
											
												
													
														|  | 
 |  | +         */
 | 
											
												
													
														|  | 
 |  | +        Test forceIgnoreMalformed() {
 | 
											
												
													
														|  | 
 |  | +            return this.sourceMode(randomValueOtherThan(SourceMode.SYNTHETIC, () -> randomFrom(SourceMode.values()))).ignoreMalformed(true);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test randomIgnoreMalformedUnlessSynthetic() {
 | 
											
												
													
														|  | 
 |  | +            this.ignoreMalformed = s -> s == SourceMode.SYNTHETIC ? false : randomBoolean();
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test storeAndDocValues(Boolean store, Boolean docValues) {
 | 
											
												
													
														|  | 
 |  | +            this.storeAndDocValues = s -> new StoreAndDocValues(store, docValues);
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test randomStoreUnlessSynthetic() {
 | 
											
												
													
														|  | 
 |  | +            this.storeAndDocValues = s -> new StoreAndDocValues(s == SourceMode.SYNTHETIC ? true : randomBoolean(), null);
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test randomDocValuesAndStoreUnlessSynthetic() {
 | 
											
												
													
														|  | 
 |  | +            this.storeAndDocValues = s -> {
 | 
											
												
													
														|  | 
 |  | +                if (s == SourceMode.SYNTHETIC) {
 | 
											
												
													
														|  | 
 |  | +                    boolean store = randomBoolean();
 | 
											
												
													
														|  | 
 |  | +                    return new StoreAndDocValues(store, store == false || randomBoolean());
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                return new StoreAndDocValues(randomBoolean(), randomBoolean());
 | 
											
												
													
														|  | 
 |  | +            };
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test randomDocValuesUnlessSynthetic() {
 | 
											
												
													
														|  | 
 |  | +            this.storeAndDocValues = s -> new StoreAndDocValues(null, s == SourceMode.SYNTHETIC || randomBoolean());
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test scalingFactor(double scalingFactor) {
 | 
											
												
													
														|  | 
 |  | +            this.scalingFactor = scalingFactor;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test ignoreAbove(Integer ignoreAbove) {
 | 
											
												
													
														|  | 
 |  | +            this.ignoreAbove = ignoreAbove;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test value(Object value) {
 | 
											
												
													
														|  | 
 |  | +            this.value = value;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test createAlias() {
 | 
											
												
													
														|  | 
 |  | +            this.createAlias = true;
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Test sub(String name, Test sub) {
 | 
											
												
													
														|  | 
 |  | +            this.subFields.put(name, sub);
 | 
											
												
													
														|  | 
 |  | +            return this;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> roundTrip(Object value) throws IOException {
 | 
											
												
													
														|  | 
 |  | +            String fieldName = type + "_field";
 | 
											
												
													
														|  | 
 |  | +            createIndex("test", fieldName);
 | 
											
												
													
														|  | 
 |  | +            if (randomBoolean()) {
 | 
											
												
													
														|  | 
 |  | +                createIndex("test2", fieldName);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            if (value == null) {
 | 
											
												
													
														|  | 
 |  | +                logger.info("indexing empty doc");
 | 
											
												
													
														|  | 
 |  | +                index("test", "{}");
 | 
											
												
													
														|  | 
 |  | +            } else {
 | 
											
												
													
														|  | 
 |  | +                logger.info("indexing {}::{}", value, value.getClass().getName());
 | 
											
												
													
														|  | 
 |  | +                index("test", Strings.toString(JsonXContent.contentBuilder().startObject().field(fieldName, value).endObject()));
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            return fetchAll();
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        void test(Object value) throws IOException {
 | 
											
												
													
														|  | 
 |  | +            test(value, value);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        /**
 | 
											
												
													
														|  | 
 |  | +         * Round trip the value through and index configured by the parameters
 | 
											
												
													
														|  | 
 |  | +         * of this test and assert that it matches the {@code expectedValues}
 | 
											
												
													
														|  | 
 |  | +         * which can be either the expected value or a subclass of {@link Matcher}.
 | 
											
												
													
														|  | 
 |  | +         */
 | 
											
												
													
														|  | 
 |  | +        void test(Object value, Object expectedValue) throws IOException {
 | 
											
												
													
														|  | 
 |  | +            Map<String, Object> result = roundTrip(value);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            logger.info("expecting {}", expectedValue == null ? null : expectedValue + "::" + expectedValue.getClass().getName());
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            List<Map<String, Object>> columns = new ArrayList<>();
 | 
											
												
													
														|  | 
 |  | +            columns.add(columnInfo(type + "_field", expectedType));
 | 
											
												
													
														|  | 
 |  | +            if (createAlias) {
 | 
											
												
													
														|  | 
 |  | +                columns.add(columnInfo("a.b.c." + type + "_field_alias", expectedType));
 | 
											
												
													
														|  | 
 |  | +                columns.add(columnInfo(type + "_field_alias", expectedType));
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            Collections.sort(columns, Comparator.comparing(m -> (String) m.get("name")));
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            ListMatcher values = matchesList();
 | 
											
												
													
														|  | 
 |  | +            values = values.item(expectedValue);
 | 
											
												
													
														|  | 
 |  | +            if (createAlias) {
 | 
											
												
													
														|  | 
 |  | +                values = values.item(expectedValue);
 | 
											
												
													
														|  | 
 |  | +                values = values.item(expectedValue);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            assertMap(result, matchesMap().entry("columns", columns).entry("values", List.of(values)));
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        void createIndex(String name, String fieldName) throws IOException {
 | 
											
												
													
														|  | 
 |  | +            if (sourceMode == null) {
 | 
											
												
													
														|  | 
 |  | +                sourceMode(randomFrom(SourceMode.values()));
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            logger.info("source_mode: {}", sourceMode);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            FieldExtractorTestCase.createIndex(name, index -> {
 | 
											
												
													
														|  | 
 |  | +                sourceMode.sourceMapping(index);
 | 
											
												
													
														|  | 
 |  | +                index.startObject("properties");
 | 
											
												
													
														|  | 
 |  | +                {
 | 
											
												
													
														|  | 
 |  | +                    index.startObject(fieldName);
 | 
											
												
													
														|  | 
 |  | +                    fieldMapping(index);
 | 
											
												
													
														|  | 
 |  | +                    index.endObject();
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +                    if (createAlias) {
 | 
											
												
													
														|  | 
 |  | +                        // create two aliases - one within a hierarchy, the other just a simple field w/o hierarchy
 | 
											
												
													
														|  | 
 |  | +                        index.startObject(fieldName + "_alias");
 | 
											
												
													
														|  | 
 |  | +                        {
 | 
											
												
													
														|  | 
 |  | +                            index.field("type", "alias");
 | 
											
												
													
														|  | 
 |  | +                            index.field("path", fieldName);
 | 
											
												
													
														|  | 
 |  | +                        }
 | 
											
												
													
														|  | 
 |  | +                        index.endObject();
 | 
											
												
													
														|  | 
 |  | +                        index.startObject("a.b.c." + fieldName + "_alias");
 | 
											
												
													
														|  | 
 |  | +                        {
 | 
											
												
													
														|  | 
 |  | +                            index.field("type", "alias");
 | 
											
												
													
														|  | 
 |  | +                            index.field("path", fieldName);
 | 
											
												
													
														|  | 
 |  | +                        }
 | 
											
												
													
														|  | 
 |  | +                        index.endObject();
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                index.endObject();
 | 
											
												
													
														|  | 
 |  | +            });
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        private void fieldMapping(XContentBuilder builder) throws IOException {
 | 
											
												
													
														|  | 
 |  | +            builder.field("type", type);
 | 
											
												
													
														|  | 
 |  | +            if (ignoreMalformed != null) {
 | 
											
												
													
														|  | 
 |  | +                boolean v = ignoreMalformed.apply(sourceMode);
 | 
											
												
													
														|  | 
 |  | +                builder.field("ignore_malformed", v);
 | 
											
												
													
														|  | 
 |  | +                ignoreMalformed = m -> v;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            StoreAndDocValues sd = storeAndDocValues.apply(sourceMode);
 | 
											
												
													
														|  | 
 |  | +            storeAndDocValues = m -> sd;
 | 
											
												
													
														|  | 
 |  | +            if (sd.docValues != null) {
 | 
											
												
													
														|  | 
 |  | +                builder.field("doc_values", sd.docValues);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            if (sd.store != null) {
 | 
											
												
													
														|  | 
 |  | +                builder.field("store", sd.store);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            if (scalingFactor != null) {
 | 
											
												
													
														|  | 
 |  | +                builder.field("scaling_factor", scalingFactor);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            if (ignoreAbove != null) {
 | 
											
												
													
														|  | 
 |  | +                builder.field("ignore_above", ignoreAbove);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            if (value != null) {
 | 
											
												
													
														|  | 
 |  | +                builder.field("value", value);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            if (subFields.isEmpty() == false) {
 | 
											
												
													
														|  | 
 |  | +                builder.startObject("fields");
 | 
											
												
													
														|  | 
 |  | +                for (Map.Entry<String, Test> sub : subFields.entrySet()) {
 | 
											
												
													
														|  | 
 |  | +                    builder.startObject(sub.getKey());
 | 
											
												
													
														|  | 
 |  | +                    if (sub.getValue().sourceMode == null) {
 | 
											
												
													
														|  | 
 |  | +                        sub.getValue().sourceMode = sourceMode;
 | 
											
												
													
														|  | 
 |  | +                    } else if (sub.getValue().sourceMode != sourceMode) {
 | 
											
												
													
														|  | 
 |  | +                        throw new IllegalStateException("source_mode can't be configured on sub-fields");
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                    sub.getValue().fieldMapping(builder);
 | 
											
												
													
														|  | 
 |  | +                    builder.endObject();
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                builder.endObject();
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        private Map<String, Object> fetchAll() throws IOException {
 | 
											
												
													
														|  | 
 |  | +            return runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query("FROM test* | LIMIT 10"));
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static Map<String, Object> columnInfo(String name, String type) {
 | 
											
												
													
														|  | 
 |  | +        return Map.of("name", name, "type", type);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static void index(String name, String... docs) throws IOException {
 | 
											
												
													
														|  | 
 |  | +        Request request = new Request("POST", "/" + name + "/_bulk");
 | 
											
												
													
														|  | 
 |  | +        request.addParameter("refresh", "true");
 | 
											
												
													
														|  | 
 |  | +        StringBuilder bulk = new StringBuilder();
 | 
											
												
													
														|  | 
 |  | +        for (String doc : docs) {
 | 
											
												
													
														|  | 
 |  | +            bulk.append(String.format(Locale.ROOT, """
 | 
											
												
													
														|  | 
 |  | +                {"index":{}}
 | 
											
												
													
														|  | 
 |  | +                %s
 | 
											
												
													
														|  | 
 |  | +                """, doc));
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        request.setJsonEntity(bulk.toString());
 | 
											
												
													
														|  | 
 |  | +        Response response = client().performRequest(request);
 | 
											
												
													
														|  | 
 |  | +        Map<String, Object> result = entityToMap(response.getEntity(), XContentType.JSON);
 | 
											
												
													
														|  | 
 |  | +        assertMap(result, matchesMap().extraOk().entry("errors", false));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    private static void createIndex(String name, CheckedConsumer<XContentBuilder, IOException> mapping) throws IOException {
 | 
											
												
													
														|  | 
 |  | +        Request request = new Request("PUT", "/" + name);
 | 
											
												
													
														|  | 
 |  | +        XContentBuilder index = JsonXContent.contentBuilder().prettyPrint().startObject();
 | 
											
												
													
														|  | 
 |  | +        index.startObject("settings");
 | 
											
												
													
														|  | 
 |  | +        {
 | 
											
												
													
														|  | 
 |  | +            index.field("index.number_of_replicas", 0);
 | 
											
												
													
														|  | 
 |  | +            index.field("index.number_of_shards", 1);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        index.endObject();
 | 
											
												
													
														|  | 
 |  | +        index.startObject("mappings");
 | 
											
												
													
														|  | 
 |  | +        mapping.accept(index);
 | 
											
												
													
														|  | 
 |  | +        index.endObject();
 | 
											
												
													
														|  | 
 |  | +        index.endObject();
 | 
											
												
													
														|  | 
 |  | +        String configStr = Strings.toString(index);
 | 
											
												
													
														|  | 
 |  | +        logger.info("index: {} {}", name, configStr);
 | 
											
												
													
														|  | 
 |  | +        request.setJsonEntity(configStr);
 | 
											
												
													
														|  | 
 |  | +        client().performRequest(request);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    /**
 | 
											
												
													
														|  | 
 |  | +     * Yaml adds newlines and some indentation which we don't want to match.
 | 
											
												
													
														|  | 
 |  | +     */
 | 
											
												
													
														|  | 
 |  | +    private String deyaml(String err) {
 | 
											
												
													
														|  | 
 |  | +        return err.replaceAll("\\\\\n\s+\\\\", "");
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +}
 |