|
@@ -30,6 +30,7 @@ import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
|
|
import org.elasticsearch.xpack.sql.type.DataType;
|
|
|
import org.elasticsearch.xpack.sql.type.DateEsField;
|
|
|
import org.elasticsearch.xpack.sql.type.EsField;
|
|
|
+import org.elasticsearch.xpack.sql.type.InvalidMappedField;
|
|
|
import org.elasticsearch.xpack.sql.type.KeywordEsField;
|
|
|
import org.elasticsearch.xpack.sql.type.TextEsField;
|
|
|
import org.elasticsearch.xpack.sql.type.Types;
|
|
@@ -51,6 +52,7 @@ import java.util.Objects;
|
|
|
import java.util.Set;
|
|
|
import java.util.TreeMap;
|
|
|
import java.util.TreeSet;
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
import static java.util.Collections.emptyMap;
|
|
@@ -263,13 +265,21 @@ public class IndexResolver {
|
|
|
// without sorting, they can still be detected however without the emptyMap optimization
|
|
|
// (fields without multi-fields have no children)
|
|
|
for (Entry<String, Map<String, FieldCapabilities>> entry : sortedFields) {
|
|
|
+
|
|
|
+ InvalidMappedField invalidField = null;
|
|
|
+ FieldCapabilities fieldCap = null;
|
|
|
+ errorMessage.setLength(0);
|
|
|
+
|
|
|
String name = entry.getKey();
|
|
|
+
|
|
|
// skip internal fields
|
|
|
if (!name.startsWith("_")) {
|
|
|
Map<String, FieldCapabilities> types = entry.getValue();
|
|
|
// field is mapped differently across indices
|
|
|
if (types.size() > 1) {
|
|
|
- // build error message
|
|
|
+ // build the error message
|
|
|
+ // and create a MultiTypeField
|
|
|
+
|
|
|
for (Entry<String, FieldCapabilities> type : types.entrySet()) {
|
|
|
if (errorMessage.length() > 0) {
|
|
|
errorMessage.append(", ");
|
|
@@ -280,37 +290,39 @@ public class IndexResolver {
|
|
|
errorMessage.append(Arrays.toString(type.getValue().indices()));
|
|
|
}
|
|
|
|
|
|
- errorMessage.insert(0,
|
|
|
- "[" + indexPattern + "] points to indices with incompatible mappings; " +
|
|
|
- "field [" + name + "] is mapped in [" + types.size() + "] different ways: ");
|
|
|
+ errorMessage.insert(0, "mapped as [" + types.size() + "] incompatible types: ");
|
|
|
+
|
|
|
+ invalidField = new InvalidMappedField(name, errorMessage.toString());
|
|
|
}
|
|
|
- if (errorMessage.length() > 0) {
|
|
|
- return IndexResolution.invalid(errorMessage.toString());
|
|
|
- }
|
|
|
-
|
|
|
- FieldCapabilities fieldCap = types.values().iterator().next();
|
|
|
- // validate search/agg-able
|
|
|
- if (fieldCap.isAggregatable() && fieldCap.nonAggregatableIndices() != null) {
|
|
|
- errorMessage.append("[" + indexPattern + "] points to indices with incompatible mappings: ");
|
|
|
- errorMessage.append("field [" + name + "] is aggregateable except in ");
|
|
|
- errorMessage.append(Arrays.toString(fieldCap.nonAggregatableIndices()));
|
|
|
- }
|
|
|
- if (fieldCap.isSearchable() && fieldCap.nonSearchableIndices() != null) {
|
|
|
+ // type is okay, check aggregation
|
|
|
+ else {
|
|
|
+ fieldCap = types.values().iterator().next();
|
|
|
+ // validate search/agg-able
|
|
|
+ if (fieldCap.isAggregatable() && fieldCap.nonAggregatableIndices() != null) {
|
|
|
+ errorMessage.append("mapped as aggregatable except in ");
|
|
|
+ errorMessage.append(Arrays.toString(fieldCap.nonAggregatableIndices()));
|
|
|
+ }
|
|
|
+ if (fieldCap.isSearchable() && fieldCap.nonSearchableIndices() != null) {
|
|
|
+ if (errorMessage.length() > 0) {
|
|
|
+ errorMessage.append(",");
|
|
|
+ }
|
|
|
+ errorMessage.append("mapped as searchable except in ");
|
|
|
+ errorMessage.append(Arrays.toString(fieldCap.nonSearchableIndices()));
|
|
|
+ }
|
|
|
+
|
|
|
if (errorMessage.length() > 0) {
|
|
|
- errorMessage.append(",");
|
|
|
+ invalidField = new InvalidMappedField(name, errorMessage.toString());
|
|
|
}
|
|
|
- errorMessage.append("[" + indexPattern + "] points to indices with incompatible mappings: ");
|
|
|
- errorMessage.append("field [" + name + "] is searchable except in ");
|
|
|
- errorMessage.append(Arrays.toString(fieldCap.nonSearchableIndices()));
|
|
|
- }
|
|
|
- if (errorMessage.length() > 0) {
|
|
|
- return IndexResolution.invalid(errorMessage.toString());
|
|
|
}
|
|
|
|
|
|
// validation passes - create the field
|
|
|
- // and name wasn't added before
|
|
|
+ // if the name wasn't added before
|
|
|
+ final InvalidMappedField invalidF = invalidField;
|
|
|
+ final FieldCapabilities fieldCapab = fieldCap;
|
|
|
if (!flattedMapping.containsKey(name)) {
|
|
|
- createField(name, fieldCap, fieldCaps, hierarchicalMapping, flattedMapping, false);
|
|
|
+ createField(name, fieldCaps, hierarchicalMapping, flattedMapping, s -> {
|
|
|
+ return invalidF != null ? invalidF : createField(s, fieldCapab.getType(), emptyMap(), fieldCapab.isAggregatable());
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -318,8 +330,9 @@ public class IndexResolver {
|
|
|
return IndexResolution.valid(new EsIndex(indexPattern, hierarchicalMapping));
|
|
|
}
|
|
|
|
|
|
- private static EsField createField(String fieldName, FieldCapabilities caps, Map<String, Map<String, FieldCapabilities>> globalCaps,
|
|
|
- Map<String, EsField> hierarchicalMapping, Map<String, EsField> flattedMapping, boolean hasChildren) {
|
|
|
+ private static EsField createField(String fieldName, Map<String, Map<String, FieldCapabilities>> globalCaps,
|
|
|
+ Map<String, EsField> hierarchicalMapping, Map<String, EsField> flattedMapping,
|
|
|
+ Function<String, EsField> field) {
|
|
|
|
|
|
Map<String, EsField> parentProps = hierarchicalMapping;
|
|
|
|
|
@@ -336,39 +349,37 @@ public class IndexResolver {
|
|
|
throw new SqlIllegalArgumentException("Cannot find field {}; this is likely a bug", parentName);
|
|
|
}
|
|
|
FieldCapabilities parentCap = map.values().iterator().next();
|
|
|
- parent = createField(parentName, parentCap, globalCaps, hierarchicalMapping, flattedMapping, true);
|
|
|
+ parent = createField(parentName, globalCaps, hierarchicalMapping, flattedMapping,
|
|
|
+ s -> createField(s, parentCap.getType(), new TreeMap<>(), parentCap.isAggregatable()));
|
|
|
}
|
|
|
parentProps = parent.getProperties();
|
|
|
}
|
|
|
|
|
|
- EsField field = null;
|
|
|
- Map<String, EsField> props = hasChildren ? new TreeMap<>() : emptyMap();
|
|
|
+ EsField esField = field.apply(fieldName);
|
|
|
+
|
|
|
+ parentProps.put(fieldName, esField);
|
|
|
+ flattedMapping.put(fullFieldName, esField);
|
|
|
|
|
|
- DataType esType = DataType.fromTypeName(caps.getType());
|
|
|
+ return esField;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static EsField createField(String fieldName, String typeName, Map<String, EsField> props, boolean isAggregateable) {
|
|
|
+ DataType esType = DataType.fromTypeName(typeName);
|
|
|
switch (esType) {
|
|
|
case TEXT:
|
|
|
- field = new TextEsField(fieldName, props, false);
|
|
|
- break;
|
|
|
+ return new TextEsField(fieldName, props, false);
|
|
|
case KEYWORD:
|
|
|
int length = DataType.KEYWORD.defaultPrecision;
|
|
|
// TODO: to check whether isSearchable/isAggregateable takes into account the presence of the normalizer
|
|
|
boolean normalized = false;
|
|
|
- field = new KeywordEsField(fieldName, props, caps.isAggregatable(), length, normalized);
|
|
|
- break;
|
|
|
+ return new KeywordEsField(fieldName, props, isAggregateable, length, normalized);
|
|
|
case DATE:
|
|
|
- field = new DateEsField(fieldName, props, caps.isAggregatable());
|
|
|
- break;
|
|
|
+ return new DateEsField(fieldName, props, isAggregateable);
|
|
|
case UNSUPPORTED:
|
|
|
- field = new UnsupportedEsField(fieldName, caps.getType());
|
|
|
- break;
|
|
|
+ return new UnsupportedEsField(fieldName, typeName);
|
|
|
default:
|
|
|
- field = new EsField(fieldName, esType, props, caps.isAggregatable());
|
|
|
+ return new EsField(fieldName, esType, props, isAggregateable);
|
|
|
}
|
|
|
-
|
|
|
- parentProps.put(fieldName, field);
|
|
|
- flattedMapping.put(fullFieldName, field);
|
|
|
-
|
|
|
- return field;
|
|
|
}
|
|
|
|
|
|
private static FieldCapabilitiesRequest createFieldCapsRequest(String index) {
|