Skip to content

Commit 88c5d52

Browse files
authored
[7.x] Verify that the field is aggregatable before attempting cardinality aggregation (#53874) (#54004)
1 parent ce31997 commit 88c5d52

File tree

2 files changed

+25
-2
lines changed

2 files changed

+25
-2
lines changed

x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/ClassificationIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class ClassificationIT extends MlNativeDataFrameAnalyticsIntegTestCase {
5757
private static final String BOOLEAN_FIELD = "boolean-field";
5858
private static final String NUMERICAL_FIELD = "numerical-field";
5959
private static final String DISCRETE_NUMERICAL_FIELD = "discrete-numerical-field";
60+
private static final String TEXT_FIELD = "text-field";
6061
private static final String KEYWORD_FIELD = "keyword-field";
6162
private static final String NESTED_FIELD = "outer-field.inner-field";
6263
private static final String ALIAS_TO_KEYWORD_FIELD = "alias-to-keyword-field";
@@ -261,6 +262,14 @@ public void testWithOnlyTrainingRowsAndTrainingPercentIsFifty_DependentVariableI
261262
assertThat(e.getMessage(), startsWith("invalid types [double] for required field [numerical-field];"));
262263
}
263264

265+
public void testWithOnlyTrainingRowsAndTrainingPercentIsFifty_DependentVariableIsText() throws Exception {
266+
ElasticsearchStatusException e = expectThrows(
267+
ElasticsearchStatusException.class,
268+
() -> testWithOnlyTrainingRowsAndTrainingPercentIsFifty(
269+
"classification_training_percent_is_50_text", TEXT_FIELD, KEYWORD_FIELD_VALUES, null));
270+
assertThat(e.getMessage(), startsWith("field [text-field] of type [text] is non-aggregatable"));
271+
}
272+
264273
public void testWithOnlyTrainingRowsAndTrainingPercentIsFifty_DependentVariableIsBoolean() throws Exception {
265274
testWithOnlyTrainingRowsAndTrainingPercentIsFifty(
266275
"classification_training_percent_is_50_boolean", BOOLEAN_FIELD, BOOLEAN_FIELD_VALUES, "boolean");
@@ -517,6 +526,7 @@ private static void createIndex(String index) {
517526
BOOLEAN_FIELD, "type=boolean",
518527
NUMERICAL_FIELD, "type=double",
519528
DISCRETE_NUMERICAL_FIELD, "type=integer",
529+
TEXT_FIELD, "type=text",
520530
KEYWORD_FIELD, "type=keyword",
521531
NESTED_FIELD, "type=keyword",
522532
ALIAS_TO_KEYWORD_FIELD, "type=alias,path=" + KEYWORD_FIELD,
@@ -532,6 +542,7 @@ private static void indexData(String sourceIndex, int numTrainingRows, int numNo
532542
BOOLEAN_FIELD, BOOLEAN_FIELD_VALUES.get(i % BOOLEAN_FIELD_VALUES.size()),
533543
NUMERICAL_FIELD, NUMERICAL_FIELD_VALUES.get(i % NUMERICAL_FIELD_VALUES.size()),
534544
DISCRETE_NUMERICAL_FIELD, DISCRETE_NUMERICAL_FIELD_VALUES.get(i % DISCRETE_NUMERICAL_FIELD_VALUES.size()),
545+
TEXT_FIELD, KEYWORD_FIELD_VALUES.get(i % KEYWORD_FIELD_VALUES.size()),
535546
KEYWORD_FIELD, KEYWORD_FIELD_VALUES.get(i % KEYWORD_FIELD_VALUES.size()),
536547
NESTED_FIELD, KEYWORD_FIELD_VALUES.get(i % KEYWORD_FIELD_VALUES.size()));
537548
IndexRequest indexRequest = new IndexRequest(sourceIndex).source(source.toArray());
@@ -550,6 +561,9 @@ private static void indexData(String sourceIndex, int numTrainingRows, int numNo
550561
Arrays.asList(
551562
DISCRETE_NUMERICAL_FIELD, DISCRETE_NUMERICAL_FIELD_VALUES.get(i % DISCRETE_NUMERICAL_FIELD_VALUES.size())));
552563
}
564+
if (TEXT_FIELD.equals(dependentVariable) == false) {
565+
source.addAll(Arrays.asList(TEXT_FIELD, KEYWORD_FIELD_VALUES.get(i % KEYWORD_FIELD_VALUES.size())));
566+
}
553567
if (KEYWORD_FIELD.equals(dependentVariable) == false) {
554568
source.addAll(Arrays.asList(KEYWORD_FIELD, KEYWORD_FIELD_VALUES.get(i % KEYWORD_FIELD_VALUES.size())));
555569
}

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/dataframe/extractor/ExtractedFieldsDetectorFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.action.ActionListener;
1111
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
1212
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
13+
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
1314
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesAction;
1415
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
1516
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
@@ -78,7 +79,7 @@ private void create(String[] index, DataFrameAnalyticsConfig config, ActionListe
7879
ActionListener<FieldCapabilitiesResponse> fieldCapabilitiesHandler = ActionListener.wrap(
7980
fieldCapabilitiesResponse -> {
8081
fieldCapsResponseHolder.set(fieldCapabilitiesResponse);
81-
getCardinalitiesForFieldsWithConstraints(index, config, fieldCardinalitiesHandler);
82+
getCardinalitiesForFieldsWithConstraints(index, config, fieldCapabilitiesResponse, fieldCardinalitiesHandler);
8283
},
8384
listener::onFailure
8485
);
@@ -96,7 +97,9 @@ private void create(String[] index, DataFrameAnalyticsConfig config, ActionListe
9697
getDocValueFieldsLimit(index, docValueFieldsLimitListener);
9798
}
9899

99-
private void getCardinalitiesForFieldsWithConstraints(String[] index, DataFrameAnalyticsConfig config,
100+
private void getCardinalitiesForFieldsWithConstraints(String[] index,
101+
DataFrameAnalyticsConfig config,
102+
FieldCapabilitiesResponse fieldCapabilitiesResponse,
100103
ActionListener<Map<String, Long>> listener) {
101104
List<FieldCardinalityConstraint> fieldCardinalityConstraints = config.getAnalysis().getFieldCardinalityConstraints();
102105
if (fieldCardinalityConstraints.isEmpty()) {
@@ -111,6 +114,12 @@ private void getCardinalitiesForFieldsWithConstraints(String[] index, DataFrameA
111114

112115
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().size(0).query(config.getSource().getParsedQuery());
113116
for (FieldCardinalityConstraint constraint : fieldCardinalityConstraints) {
117+
for (FieldCapabilities fieldCaps : fieldCapabilitiesResponse.getField(constraint.getField()).values()) {
118+
if (fieldCaps.isAggregatable() == false) {
119+
throw ExceptionsHelper.badRequestException("field [{}] of type [{}] is non-aggregatable",
120+
fieldCaps.getName(), fieldCaps.getType());
121+
}
122+
}
114123
searchSourceBuilder.aggregation(
115124
AggregationBuilders.cardinality(constraint.getField())
116125
.field(constraint.getField())

0 commit comments

Comments
 (0)