SQL: Support multi-index format as table identifier (#33278)

Extend tableIdentifier to support multi-index format; not just * but
also enumeration and exclusion

Fix #33162
This commit is contained in:
Costin Leau 2018-08-31 10:45:25 +03:00 committed by GitHub
parent c6cfa08a61
commit 73eb4cbbbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 54 deletions

View File

@ -19,6 +19,7 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.xpack.sql.type.EsField; import org.elasticsearch.xpack.sql.type.EsField;
@ -300,7 +301,7 @@ public class IndexResolver {
private static GetIndexRequest createGetIndexRequest(String index) { private static GetIndexRequest createGetIndexRequest(String index) {
return new GetIndexRequest() return new GetIndexRequest()
.local(true) .local(true)
.indices(index) .indices(Strings.commaDelimitedListToStringArray(index))
.features(Feature.MAPPINGS) .features(Feature.MAPPINGS)
//lenient because we throw our own errors looking at the response e.g. if something was not resolved //lenient because we throw our own errors looking at the response e.g. if something was not resolved
//also because this way security doesn't throw authorization exceptions but rather honours ignore_unavailable //also because this way security doesn't throw authorization exceptions but rather honours ignore_unavailable

View File

@ -12,6 +12,7 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.util.CollectionUtils;
@ -92,7 +93,7 @@ public class Querier {
log.trace("About to execute query {} on {}", StringUtils.toString(sourceBuilder), index); log.trace("About to execute query {} on {}", StringUtils.toString(sourceBuilder), index);
} }
SearchRequest search = prepareRequest(client, sourceBuilder, timeout, index); SearchRequest search = prepareRequest(client, sourceBuilder, timeout, Strings.commaDelimitedListToStringArray(index));
ActionListener<SearchResponse> l; ActionListener<SearchResponse> l;
if (query.isAggsOnly()) { if (query.isAggsOnly()) {

View File

@ -21,23 +21,9 @@ abstract class IdentifierBuilder extends AbstractBuilder {
ParseTree tree = ctx.name != null ? ctx.name : ctx.TABLE_IDENTIFIER(); ParseTree tree = ctx.name != null ? ctx.name : ctx.TABLE_IDENTIFIER();
String index = tree.getText(); String index = tree.getText();
validateIndex(index, source);
return new TableIdentifier(source, visitIdentifier(ctx.catalog), index); return new TableIdentifier(source, visitIdentifier(ctx.catalog), index);
} }
// see https://github.com/elastic/elasticsearch/issues/6736
static void validateIndex(String index, Location source) {
for (int i = 0; i < index.length(); i++) {
char c = index.charAt(i);
if (Character.isUpperCase(c)) {
throw new ParsingException(source, "Invalid index name (needs to be lowercase) {}", index);
}
if (c == '\\' || c == '/' || c == '<' || c == '>' || c == '|' || c == ',' || c == ' ') {
throw new ParsingException(source, "Invalid index name (illegal character {}) {}", c, index);
}
}
}
@Override @Override
public String visitIdentifier(IdentifierContext ctx) { public String visitIdentifier(IdentifierContext ctx) {
return ctx == null ? null : ctx.getText(); return ctx == null ? null : ctx.getText();

View File

@ -1,38 +0,0 @@
/*
* 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.parser;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import static org.hamcrest.Matchers.is;
public class IdentifierBuilderTests extends ESTestCase {
private static Location L = new Location(1, 10);
public void testTypicalIndex() throws Exception {
IdentifierBuilder.validateIndex("some-index", L);
}
public void testInternalIndex() throws Exception {
IdentifierBuilder.validateIndex(".some-internal-index-2020-02-02", L);
}
public void testIndexPattern() throws Exception {
IdentifierBuilder.validateIndex(".some-*", L);
}
public void testInvalidIndex() throws Exception {
ParsingException pe = expectThrows(ParsingException.class, () -> IdentifierBuilder.validateIndex("some,index", L));
assertThat(pe.getMessage(), is("line 1:12: Invalid index name (illegal character ,) some,index"));
}
public void testUpperCasedIndex() throws Exception {
ParsingException pe = expectThrows(ParsingException.class, () -> IdentifierBuilder.validateIndex("thisIsAnIndex", L));
assertThat(pe.getMessage(), is("line 1:12: Invalid index name (needs to be lowercase) thisIsAnIndex"));
}
}

View File

@ -162,3 +162,27 @@ last_name | VARCHAR
last_name.keyword | VARCHAR last_name.keyword | VARCHAR
salary | INTEGER salary | INTEGER
; ;
describeIncludeExclude
DESCRIBE "test_emp*,-test_alias*";
column:s | type:s
birth_date | TIMESTAMP
dep | STRUCT
dep.dep_id | VARCHAR
dep.dep_name | VARCHAR
dep.dep_name.keyword | VARCHAR
dep.from_date | TIMESTAMP
dep.to_date | TIMESTAMP
emp_no | INTEGER
first_name | VARCHAR
first_name.keyword | VARCHAR
gender | VARCHAR
hire_date | TIMESTAMP
languages | TINYINT
last_name | VARCHAR
last_name.keyword | VARCHAR
salary | INTEGER
;