Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow dimension fields to have multiple values in standard and logsdb index mode #112345

Merged
Merged
8 changes: 8 additions & 0 deletions docs/changelog/112345.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pr: 112345
summary: Allow dimension fields to have multiple values in standard and logsdb index
mode
area: Mapping
type: enhancement
issues:
- 112232
- 112239
4 changes: 2 additions & 2 deletions server/src/main/java/org/elasticsearch/index/IndexMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public IdFieldMapper buildIdFieldMapper(BooleanSupplier fieldDataEnabled) {

@Override
public DocumentDimensions buildDocumentDimensions(IndexSettings settings) {
return new DocumentDimensions.OnlySingleValueAllowed();
return DocumentDimensions.Noop.INSTANCE;
}

@Override
Expand Down Expand Up @@ -279,7 +279,7 @@ public MetadataFieldMapper timeSeriesRoutingHashFieldMapper() {

@Override
public DocumentDimensions buildDocumentDimensions(IndexSettings settings) {
return new DocumentDimensions.OnlySingleValueAllowed();
return DocumentDimensions.Noop.INSTANCE;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,47 @@ private void add(String fieldName) {
}
}
};

/**
* Noop implementation that doesn't perform validations on dimension fields
*/
enum Noop implements DocumentDimensions {
felixbarny marked this conversation as resolved.
Show resolved Hide resolved

INSTANCE;

@Override
public DocumentDimensions addString(String fieldName, BytesRef utf8Value) {
return this;
}

@Override
public DocumentDimensions addString(String fieldName, String value) {
return this;
}

@Override
public DocumentDimensions addIp(String fieldName, InetAddress value) {
return this;
}

@Override
public DocumentDimensions addLong(String fieldName, long value) {
return this;
}

@Override
public DocumentDimensions addUnsignedLong(String fieldName, long value) {
return this;
}

@Override
public DocumentDimensions addBoolean(String fieldName, boolean value) {
return this;
}

@Override
public DocumentDimensions validate(IndexSettings settings) {
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.script.BooleanFieldScript;
Expand All @@ -25,6 +26,7 @@
import org.elasticsearch.xcontent.XContentFactory;

import java.io.IOException;
import java.time.Instant;
import java.util.List;
import java.util.function.Function;

Expand Down Expand Up @@ -257,15 +259,24 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimension", true);
});
DocumentMapper mapper = randomBoolean() ? createDocumentMapper(mapping) : createTimeSeriesModeDocumentMapper(mapping);
IndexMode indexMode = randomFrom(IndexMode.values());
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

Exception e = expectThrows(DocumentParsingException.class, () -> mapper.parse(source(b -> b.array("field", true, false))));
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field", true, false);
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

public void testDimensionInRoutingPath() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.IndexVersions;
import org.elasticsearch.script.IpFieldScript;
Expand All @@ -26,6 +27,7 @@

import java.io.IOException;
import java.net.InetAddress;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
Expand Down Expand Up @@ -255,17 +257,25 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimension", true);
}));
});

Exception e = expectThrows(
DocumentParsingException.class,
() -> mapper.parse(source(b -> b.array("field", "192.168.1.1", "192.168.1.1")))
);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
IndexMode indexMode = randomFrom(IndexMode.values());
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field", "192.168.1.1", "192.168.1.1");
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.analysis.AnalyzerScope;
Expand All @@ -44,6 +45,7 @@
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -373,15 +375,24 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimension", true);
});
DocumentMapper mapper = randomBoolean() ? createDocumentMapper(mapping) : createTimeSeriesModeDocumentMapper(mapping);
IndexMode indexMode = randomFrom(IndexMode.values());
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

Exception e = expectThrows(DocumentParsingException.class, () -> mapper.parse(source(b -> b.array("field", "1234", "45678"))));
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field", "1234", "45678");
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

public void testDimensionExtraLongKeyword() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.mapper.DocumentMapper;
Expand All @@ -34,6 +35,7 @@
import org.junit.AssumptionViolatedException;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -189,18 +191,25 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimensions", List.of("key1", "key2", "field3.key3"));
});
DocumentMapper mapper = randomBoolean() ? createDocumentMapper(mapping) : createTimeSeriesModeDocumentMapper(mapping);

Exception e = expectThrows(
DocumentParsingException.class,
() -> mapper.parse(source(b -> b.array("field.key1", "value1", "value2")))
);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field.key1] cannot be a multi-valued field"));
IndexMode indexMode = randomFrom(IndexMode.values());
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field.key1", "value1", "value2");
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field.key1] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

public void testDisableIndex() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ protected static String randomIndexOptions() {
return randomFrom("docs", "freqs", "positions", "offsets");
}

protected final DocumentMapper createDocumentMapper(XContentBuilder mappings, IndexMode indexMode) throws IOException {
return switch (indexMode) {
case STANDARD -> createDocumentMapper(mappings);
case TIME_SERIES -> createTimeSeriesModeDocumentMapper(mappings);
case LOGSDB -> createLogsModeDocumentMapper(mappings);
};
}

protected final DocumentMapper createDocumentMapper(XContentBuilder mappings) throws IOException {
return createMapperService(mappings).documentMapper();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
package org.elasticsearch.index.mapper;

import org.apache.lucene.index.IndexableField;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.time.Instant;
import java.util.List;

import static org.hamcrest.Matchers.containsString;
Expand Down Expand Up @@ -69,17 +72,25 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimension", true);
}));
});

Exception e = expectThrows(
DocumentParsingException.class,
() -> mapper.parse(source(b -> b.array("field", randomNumber(), randomNumber(), randomNumber())))
);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
IndexMode indexMode = randomFrom(IndexMode.values());
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field", randomNumber(), randomNumber(), randomNumber());
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

public void testMetricAndDimension() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import java.io.IOException;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -260,17 +261,25 @@ public void testDimensionIndexedAndDocvalues() {
}
}

public void testDimensionMultiValuedField() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
public void testDimensionMultiValuedField() throws Throwable {
XContentBuilder mapping = fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_dimension", true);
}));
});

Exception e = expectThrows(
DocumentParsingException.class,
() -> mapper.parse(source(b -> b.array("field", randomNonNegativeLong(), randomNonNegativeLong(), randomNonNegativeLong())))
);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
IndexMode indexMode = randomFrom(IndexMode.values());
DocumentMapper mapper = createDocumentMapper(mapping, indexMode);

ThrowingRunnable parseArray = () -> mapper.parse(source(b -> {
b.array("field", randomNonNegativeLong(), randomNonNegativeLong(), randomNonNegativeLong());
b.field("@timestamp", Instant.now());
}));
if (indexMode == IndexMode.TIME_SERIES) {
Exception e = expectThrows(DocumentParsingException.class, parseArray);
assertThat(e.getCause().getMessage(), containsString("Dimension field [field] cannot be a multi-valued field"));
} else {
parseArray.run();
}
}

public void testMetricType() throws IOException {
Expand Down

This file was deleted.

Loading