diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalog.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalog.java index a407f5c6186..460cfcb8030 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalog.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalog.java @@ -5,15 +5,20 @@ */ package org.elasticsearch.xpack.sql.analysis.catalog; +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; + import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Supplier; @@ -43,10 +48,7 @@ public class EsCatalog implements Catalog { if (idx == null) { return null; } - if (false == indexHasOnlyOneType(idx)) { - throw new SqlIllegalArgumentException("index has more than one type"); - } - return EsIndex.build(idx); + return EsIndex.build(idx, singleType(idx, false)); } @Override @@ -69,16 +71,48 @@ public class EsCatalog implements Catalog { // filter unsupported (indices with more than one type) indices while (indexMetadata.hasNext()) { IndexMetaData imd = indexMetadata.next(); - if (indexHasOnlyOneType(imd)) { - list.add(EsIndex.build(imd)); + MappingMetaData type = singleType(imd, true); + if (type != null) { + list.add(EsIndex.build(imd, type)); } } return list; } - private boolean indexHasOnlyOneType(IndexMetaData index) { - return index.getMappings().size() <= 1; + /** + * Return the single type in the index of {@code null} if there + * are no types in the index. + * @param badIndicesAreNull if true then return null for indices with + * more than one type, if false throw an exception for such indices + */ + @Nullable + private MappingMetaData singleType(IndexMetaData index, boolean badIndicesAreNull) { + /* We actually ignore the _default_ mapping because it is still + * allowed but deprecated. */ + MappingMetaData result = null; + List typeNames = null; + for (ObjectObjectCursor type : index.getMappings()) { + if ("_default_".equals(type.key)) { + continue; + } + if (result != null) { + if (badIndicesAreNull) { + return null; + } + if (typeNames == null) { + typeNames = new ArrayList<>(); + typeNames.add(result.type()); + } + typeNames.add(type.key); + } + result = type.value; + } + if (typeNames == null) { + return result; + } + Collections.sort(typeNames); + throw new IllegalArgumentException("[" + index.getIndex().getName() + "] has more than one type " + typeNames); } private String[] resolveIndex(String pattern) { diff --git a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsIndex.java b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsIndex.java index 523de00e759..c15975696e0 100644 --- a/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsIndex.java +++ b/sql/server/src/main/java/org/elasticsearch/xpack/sql/analysis/catalog/EsIndex.java @@ -7,16 +7,13 @@ package org.elasticsearch.xpack.sql.analysis.catalog; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; -import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.Types; import org.elasticsearch.xpack.sql.util.StringUtils; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -56,24 +53,8 @@ public class EsIndex { return settings; } - static List build(Iterator metadata) { - if (metadata == null || !metadata.hasNext()) { - return emptyList(); - } - List list = new ArrayList<>(); - while (metadata.hasNext()) { - build(metadata.next()); - } - return list; - } - - static EsIndex build(IndexMetaData metadata) { - ImmutableOpenMap mappings = metadata.getMappings(); - if (mappings.size() > 1) { - throw new SqlIllegalArgumentException("Cannot use index [%s] as it contains multiple types %s", metadata.getIndex().getName(), Arrays.toString(mappings.keys().toArray())); - } - MappingMetaData mm = mappings.isEmpty() ? null : mappings.valuesIt().next(); - Map mapping = mm != null ? Types.fromEs(mm.sourceAsMap()) : emptyMap(); + static EsIndex build(IndexMetaData metadata, @Nullable MappingMetaData type) { + Map mapping = type != null ? Types.fromEs(type.sourceAsMap()) : emptyMap(); List aliases = Arrays.asList(metadata.getAliases().keys().toArray(String.class)); return new EsIndex(metadata.getIndex().getName(), mapping, aliases, metadata.getSettings()); diff --git a/sql/server/src/test/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalogTests.java b/sql/server/src/test/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalogTests.java new file mode 100644 index 00000000000..ce64cd24597 --- /dev/null +++ b/sql/server/src/test/java/org/elasticsearch/xpack/sql/analysis/catalog/EsCatalogTests.java @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.sql.analysis.catalog; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.hasSize; + +public class EsCatalogTests extends ESTestCase { + public void testEmpty() { + EsCatalog catalog = catalog(ClusterState.builder(ClusterName.DEFAULT).build()); + assertEquals(emptyList(), catalog.listIndices("*")); + assertNull(catalog.getIndex("test")); + } + + public void testIndexExists() throws IOException { + EsCatalog catalog = catalog(ClusterState.builder(ClusterName.DEFAULT) + .metaData(MetaData.builder() + .put(index("test") + .putMapping("test", "{}")) + .build()) + .build()); + + List indices = catalog.listIndices("*"); + assertThat(indices, hasSize(1)); + assertEquals("test", indices.get(0).name()); + assertEquals(emptyMap(), catalog.getIndex("test").mapping()); + } + + public void testIndexWithDefaultType() throws IOException { + EsCatalog catalog = catalog(ClusterState.builder(ClusterName.DEFAULT) + .metaData(MetaData.builder() + .put(index("test") + .putMapping("test", "{}") + .putMapping("_default_", "{}")) + .build()) + .build()); + + List indices = catalog.listIndices("*"); + assertThat(indices, hasSize(1)); + assertEquals("test", indices.get(0).name()); + assertEquals(emptyMap(), catalog.getIndex("test").mapping()); + } + + public void testIndexWithTwoTypes() throws IOException { + EsCatalog catalog = catalog(ClusterState.builder(ClusterName.DEFAULT) + .metaData(MetaData.builder() + .put(index("test") + .putMapping("first_type", "{}") + .putMapping("second_type", "{}")) + .build()) + .build()); + + assertEquals(emptyList(), catalog.listIndices("*")); + Exception e = expectThrows(IllegalArgumentException.class, () -> catalog.getIndex("test")); + assertEquals(e.getMessage(), "[test] has more than one type [first_type, second_type]"); + } + + private EsCatalog catalog(ClusterState state) { + EsCatalog catalog = new EsCatalog(() -> state); + catalog.setIndexNameExpressionResolver(new IndexNameExpressionResolver(Settings.EMPTY)); + return catalog; + } + + private IndexMetaData.Builder index(String name) throws IOException { + return IndexMetaData.builder("test") + .settings(Settings.builder() + .put("index.version.created", Version.CURRENT) + .put("index.number_of_shards", 1) + .put("index.number_of_replicas", 1)); + } +}