SQL: Expand SYS TABLES behavior to support ODBC spec (elastic/x-pack-elasticsearch#4375)
Add behavior for enumerating catalogs, table types, etc when the main param is SQL_ALL_* while all the rest are empty strings Fix elastic/x-pack-elasticsearch#4334 Original commit: elastic/x-pack-elasticsearch@28e7b15904
This commit is contained in:
parent
bf5f7e1847
commit
e69f820423
|
@ -44,7 +44,11 @@ public class IndexResolver {
|
|||
public enum IndexType {
|
||||
|
||||
INDEX("BASE TABLE"),
|
||||
ALIAS("ALIAS");
|
||||
ALIAS("ALIAS"),
|
||||
// value for user types unrecognized
|
||||
UNKNOWN("UKNOWN");
|
||||
|
||||
public static final EnumSet<IndexType> VALID = EnumSet.of(INDEX, ALIAS);
|
||||
|
||||
private final String toSql;
|
||||
|
||||
|
@ -59,13 +63,13 @@ public class IndexResolver {
|
|||
public static IndexType from(String name) {
|
||||
if (name != null) {
|
||||
name = name.toUpperCase(Locale.ROOT);
|
||||
for (IndexType type : IndexType.values()) {
|
||||
for (IndexType type : IndexType.VALID) {
|
||||
if (type.toSql.equals(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return IndexType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.elasticsearch.xpack.sql.plan.logical.command.sys.SysTables;
|
|||
import org.elasticsearch.xpack.sql.plan.logical.command.sys.SysTypes;
|
||||
import org.elasticsearch.xpack.sql.plugin.SqlTypedParamValue;
|
||||
import org.elasticsearch.xpack.sql.tree.Location;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
@ -150,13 +151,19 @@ abstract class CommandBuilder extends LogicalPlanBuilder {
|
|||
for (StringContext string : ctx.string()) {
|
||||
String value = string(string);
|
||||
if (value != null) {
|
||||
IndexType type = IndexType.from(value);
|
||||
if (type == null) {
|
||||
throw new ParsingException(source(ctx), "Invalid table type [{}]", value);
|
||||
// check special ODBC wildcard case
|
||||
if (value.equals(StringUtils.SQL_WILDCARD) && ctx.string().size() == 1) {
|
||||
// since % is the same as not specifying a value, choose
|
||||
// https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/value-list-arguments?view=ssdt-18vs2017
|
||||
// that is skip the value
|
||||
} else {
|
||||
IndexType type = IndexType.from(value);
|
||||
types.add(type);
|
||||
}
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// if the ODBC enumeration is specified, skip validation
|
||||
EnumSet<IndexType> set = types.isEmpty() ? null : EnumSet.copyOf(types);
|
||||
return new SysTables(source(ctx), visitPattern(ctx.clusterPattern), visitPattern(ctx.tablePattern), set);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.elasticsearch.xpack.sql.tree.Location;
|
|||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
@ -43,7 +42,7 @@ public class SysTableTypes extends Command {
|
|||
|
||||
@Override
|
||||
public final void execute(SqlSession session, ActionListener<SchemaRowSet> listener) {
|
||||
listener.onResponse(Rows.of(output(), Stream.of(IndexType.values())
|
||||
listener.onResponse(Rows.of(output(), IndexType.VALID.stream()
|
||||
.map(t -> singletonList(t.toSql()))
|
||||
.collect(toList())));
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import org.elasticsearch.xpack.sql.session.SchemaRowSet;
|
|||
import org.elasticsearch.xpack.sql.session.SqlSession;
|
||||
import org.elasticsearch.xpack.sql.tree.Location;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -24,6 +26,7 @@ import java.util.regex.Pattern;
|
|||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.elasticsearch.xpack.sql.util.StringUtils.EMPTY;
|
||||
import static org.elasticsearch.xpack.sql.util.StringUtils.SQL_WILDCARD;
|
||||
|
||||
public class SysTables extends Command {
|
||||
|
||||
|
@ -62,6 +65,36 @@ public class SysTables extends Command {
|
|||
public final void execute(SqlSession session, ActionListener<SchemaRowSet> listener) {
|
||||
String cluster = session.indexResolver().clusterName();
|
||||
|
||||
// first check if where dealing with ODBC enumeration
|
||||
// namely one param specified with '%', everything else empty string
|
||||
// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqltables-function?view=ssdt-18vs2017#comments
|
||||
|
||||
if (clusterPattern != null && clusterPattern.pattern().equals(SQL_WILDCARD)) {
|
||||
if (pattern != null && pattern.pattern().isEmpty() && CollectionUtils.isEmpty(types)) {
|
||||
Object[] enumeration = new Object[10];
|
||||
// send only the cluster, everything else null
|
||||
enumeration[0] = cluster;
|
||||
listener.onResponse(Rows.singleton(output(), enumeration));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no types were specified (the parser takes care of the % case)
|
||||
if (CollectionUtils.isEmpty(types)) {
|
||||
if (clusterPattern != null && clusterPattern.pattern().isEmpty()) {
|
||||
List<List<?>> values = new ArrayList<>();
|
||||
// send only the types, everything else null
|
||||
for (IndexType type : IndexType.VALID) {
|
||||
Object[] enumeration = new Object[10];
|
||||
enumeration[3] = type.toSql();
|
||||
values.add(asList(enumeration));
|
||||
}
|
||||
listener.onResponse(Rows.of(output(), values));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String cRegex = clusterPattern != null ? clusterPattern.asJavaRegex() : null;
|
||||
|
||||
// if the catalog doesn't match, don't return any results
|
||||
|
|
|
@ -26,6 +26,7 @@ public abstract class StringUtils {
|
|||
|
||||
public static final String EMPTY = "";
|
||||
public static final String NEW_LINE = "\n";
|
||||
public static final String SQL_WILDCARD = "%";
|
||||
|
||||
//CamelCase to camel_case
|
||||
public static String camelCaseToUnderscore(String string) {
|
||||
|
@ -82,7 +83,7 @@ public abstract class StringUtils {
|
|||
else {
|
||||
switch (curr) {
|
||||
case '%':
|
||||
regex.append(escaped ? "%" : ".*");
|
||||
regex.append(escaped ? SQL_WILDCARD : ".*");
|
||||
break;
|
||||
case '_':
|
||||
regex.append(escaped ? "_" : ".");
|
||||
|
@ -145,7 +146,7 @@ public abstract class StringUtils {
|
|||
} else {
|
||||
switch (curr) {
|
||||
case '%':
|
||||
wildcard.append(escaped ? "%" : "*");
|
||||
wildcard.append(escaped ? SQL_WILDCARD : "*");
|
||||
break;
|
||||
case '_':
|
||||
wildcard.append(escaped ? "_" : "?");
|
||||
|
@ -191,7 +192,7 @@ public abstract class StringUtils {
|
|||
} else {
|
||||
switch (curr) {
|
||||
case '%':
|
||||
wildcard.append(escaped ? "%" : "*");
|
||||
wildcard.append(escaped ? SQL_WILDCARD : "*");
|
||||
break;
|
||||
case '_':
|
||||
wildcard.append(escaped ? "_" : "*");
|
||||
|
|
|
@ -15,7 +15,6 @@ import org.elasticsearch.xpack.sql.analysis.index.IndexResolver;
|
|||
import org.elasticsearch.xpack.sql.analysis.index.IndexResolver.IndexInfo;
|
||||
import org.elasticsearch.xpack.sql.analysis.index.IndexResolver.IndexType;
|
||||
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
|
||||
import org.elasticsearch.xpack.sql.parser.ParsingException;
|
||||
import org.elasticsearch.xpack.sql.parser.SqlParser;
|
||||
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
|
||||
import org.elasticsearch.xpack.sql.plugin.SqlTypedParamValue;
|
||||
|
@ -26,6 +25,7 @@ import org.elasticsearch.xpack.sql.type.EsField;
|
|||
import org.elasticsearch.xpack.sql.type.TypesTests;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -41,6 +41,8 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
public class SysTablesTests extends ESTestCase {
|
||||
|
||||
private static final String CLUSTER_NAME = "cluster";
|
||||
|
||||
private final SqlParser parser = new SqlParser();
|
||||
private final Map<String, EsField> mapping = TypesTests.loadMapping("mapping-multi-field-with-nested.json", true);
|
||||
private final IndexInfo index = new IndexInfo("test", IndexType.INDEX);
|
||||
|
@ -137,25 +139,75 @@ public class SysTablesTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testSysTablesWithInvalidType() throws Exception {
|
||||
ParsingException pe = expectThrows(ParsingException.class, () -> sql("SYS TABLES LIKE 'test' TYPE 'QUE HORA ES'"));
|
||||
assertEquals("line 1:2: Invalid table type [QUE HORA ES]", pe.getMessage());
|
||||
executeCommand("SYS TABLES LIKE 'test' TYPE 'QUE HORA ES'", r -> {
|
||||
assertEquals(0, r.size());
|
||||
}, new IndexInfo[0]);
|
||||
}
|
||||
|
||||
public void testSysTablesCatalogEnumeration() throws Exception {
|
||||
executeCommand("SYS TABLES CATALOG LIKE '%' LIKE ''", r -> {
|
||||
assertEquals(1, r.size());
|
||||
assertEquals(CLUSTER_NAME, r.column(0));
|
||||
// everything else should be null
|
||||
for (int i = 1; i < 10; i++) {
|
||||
assertNull(r.column(i));
|
||||
}
|
||||
}, new IndexInfo[0]);
|
||||
}
|
||||
|
||||
public void testSysTablesTypesEnumeration() throws Exception {
|
||||
executeCommand("SYS TABLES CATALOG LIKE '' LIKE '' TYPE '%'", r -> {
|
||||
assertEquals(2, r.size());
|
||||
|
||||
Iterator<IndexType> it = IndexType.VALID.iterator();
|
||||
|
||||
for (int t = 0; t < r.size(); t++) {
|
||||
assertEquals(it.next().toSql(), r.column(3));
|
||||
|
||||
// everything else should be null
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (i != 3) {
|
||||
assertNull(r.column(i));
|
||||
}
|
||||
}
|
||||
|
||||
r.advanceRow();
|
||||
}
|
||||
}, new IndexInfo[0]);
|
||||
}
|
||||
|
||||
public void testSysTablesTypesEnumerationWoString() throws Exception {
|
||||
executeCommand("SYS TABLES CATALOG LIKE '' LIKE '' ", r -> {
|
||||
assertEquals(2, r.size());
|
||||
|
||||
Iterator<IndexType> it = IndexType.VALID.iterator();
|
||||
|
||||
for (int t = 0; t < r.size(); t++) {
|
||||
assertEquals(it.next().toSql(), r.column(3));
|
||||
|
||||
// everything else should be null
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (i != 3) {
|
||||
assertNull(r.column(i));
|
||||
}
|
||||
}
|
||||
|
||||
r.advanceRow();
|
||||
}
|
||||
}, new IndexInfo[0]);
|
||||
}
|
||||
|
||||
private SqlTypedParamValue param(Object value) {
|
||||
return new SqlTypedParamValue(value, DataTypes.fromJava(value));
|
||||
}
|
||||
|
||||
private Tuple<Command, SqlSession> sql(String sql) {
|
||||
return sql(sql, emptyList());
|
||||
}
|
||||
|
||||
private Tuple<Command, SqlSession> sql(String sql, List<SqlTypedParamValue> params) {
|
||||
EsIndex test = new EsIndex("test", mapping);
|
||||
Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC);
|
||||
Command cmd = (Command) analyzer.analyze(parser.createStatement(sql, params), true);
|
||||
|
||||
IndexResolver resolver = mock(IndexResolver.class);
|
||||
when(resolver.clusterName()).thenReturn("cluster");
|
||||
when(resolver.clusterName()).thenReturn(CLUSTER_NAME);
|
||||
|
||||
SqlSession session = new SqlSession(null, null, null, resolver, null, null, null);
|
||||
return new Tuple<>(cmd, session);
|
||||
|
|
Loading…
Reference in New Issue