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:
parent
c6cfa08a61
commit
73eb4cbbbe
|
@ -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
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue