SQL: Polish behavior of SYS TABLES command (#40535)

SYS TABLES meta command has been improved to better adhere to the ODBC
spec in particular with regards to the handling of enumerations (and
the differences between '%', null and ''(empty string))

Fix #40348

(cherry picked from commit e3070615000228c283d17ce8d182b44f1450a5d5)
This commit is contained in:
Costin Leau 2019-03-28 10:06:38 +02:00 committed by Costin Leau
parent de5f924daa
commit 5485efa2af
4 changed files with 104 additions and 62 deletions

View File

@ -257,8 +257,7 @@ class JdbcDatabaseMetaData implements DatabaseMetaData, JdbcWrapper {
@Override
public boolean supportsConvert() throws SQLException {
//TODO: add Convert
return false;
return true;
}
@Override
@ -774,14 +773,14 @@ class JdbcDatabaseMetaData implements DatabaseMetaData, JdbcWrapper {
@Override
public ResultSet getCatalogs() throws SQLException {
// TABLE_CAT is the first column
Object[][] data = queryColumn(con, "SYS TABLES CATALOG LIKE '%'", 1);
Object[][] data = queryColumn(con, "SYS TABLES CATALOG LIKE '%' LIKE ''", 1);
return memorySet(con.cfg, columnInfo("", "TABLE_CAT"), data);
}
@Override
public ResultSet getTableTypes() throws SQLException {
// TABLE_TYPE (4)
Object[][] data = queryColumn(con, "SYS TABLES TYPE '%'", 4);
Object[][] data = queryColumn(con, "SYS TABLES CATALOG LIKE '' LIKE '' TYPE '%'", 4);
return memorySet(con.cfg, columnInfo("", "TABLE_TYPE"), data);
}

View File

@ -149,9 +149,8 @@ abstract class CommandBuilder extends LogicalPlanBuilder {
if (value != null) {
// check special ODBC wildcard case
if (value.equals(StringUtils.SQL_WILDCARD) && ctx.string().size() == 1) {
// convert % to enumeration
// https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/value-list-arguments?view=ssdt-18vs2017
types.addAll(IndexType.VALID);
// treat % as null
// https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/value-list-arguments
}
// special case for legacy apps (like msquery) that always asks for 'TABLE'
// which we manually map to all concrete tables supported

View File

@ -14,9 +14,8 @@ import org.elasticsearch.xpack.sql.plan.logical.command.Command;
import org.elasticsearch.xpack.sql.session.Rows;
import org.elasticsearch.xpack.sql.session.SchemaRowSet;
import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.util.CollectionUtils;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.ArrayList;
import java.util.Comparator;
@ -77,8 +76,11 @@ public class SysTables extends Command {
// 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)) {
// catalog enumeration
if (clusterPattern == null || clusterPattern.pattern().equals(SQL_WILDCARD)) {
// enumerate only if pattern is "" and no types are specified (types is null)
if (pattern != null && pattern.pattern().isEmpty() && index == null
&& types == null) {
Object[] enumeration = new Object[10];
// send only the cluster, everything else null
enumeration[0] = cluster;
@ -87,12 +89,15 @@ public class SysTables extends Command {
}
}
// if no types were specified (the parser takes care of the % case)
if (IndexType.VALID.equals(types)) {
if ((clusterPattern == null || clusterPattern.pattern().isEmpty())
&& (pattern == null || pattern.pattern().isEmpty())) {
// enumerate types
// if no types are specified (the parser takes care of the % case)
if (types == null) {
// empty string for catalog
if (clusterPattern != null && clusterPattern.pattern().isEmpty()
// empty string for table like and no index specified
&& pattern != null && pattern.pattern().isEmpty() && index == null) {
List<List<?>> values = new ArrayList<>();
// send only the types, everything else null
// send only the types, everything else is made of empty strings
for (IndexType type : IndexType.VALID) {
Object[] enumeration = new Object[10];
enumeration[3] = type.toSql();
@ -105,7 +110,7 @@ public class SysTables extends Command {
}
}
// no enumeration pattern found, list actual tables
String cRegex = clusterPattern != null ? clusterPattern.asJavaRegex() : null;
// if the catalog doesn't match, don't return any results

View File

@ -51,20 +51,60 @@ public class SysTablesTests extends ESTestCase {
private final IndexInfo index = new IndexInfo("test", IndexType.INDEX);
private final IndexInfo alias = new IndexInfo("alias", IndexType.ALIAS);
public void testSysTablesEnumerateCatalog() throws Exception {
executeCommand("SYS TABLES CATALOG LIKE '%'", r -> {
//
// catalog enumeration
//
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));
}
}, index);
}
public void testSysTablesEnumerateTypes() throws Exception {
executeCommand("SYS TABLES TYPE '%'", r -> {
//
// table types enumeration
//
public void testSysTablesTypesEnumerationWoString() throws Exception {
executeCommand("SYS TABLES CATALOG LIKE '' LIKE '' ", r -> {
assertEquals(2, r.size());
assertEquals("BASE TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("VIEW", r.column(3));
});
}, new IndexInfo[0]);
}
public void testSysTablesEnumerateTypes() throws Exception {
executeCommand("SYS TABLES CATALOG LIKE '' LIKE '' TYPE '%'", r -> {
assertEquals(2, r.size());
assertEquals("BASE TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("VIEW", r.column(3));
}, alias, index);
}
public void testSysTablesTypesEnumeration() throws Exception {
executeCommand("SYS TABLES CATALOG LIKE '' LIKE '' TYPE '%'", r -> {
assertEquals(2, r.size());
Iterator<IndexType> it = IndexType.VALID.stream().sorted(Comparator.comparing(IndexType::toSql)).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 testSysTablesDifferentCatalog() throws Exception {
@ -77,17 +117,42 @@ public class SysTablesTests extends ESTestCase {
public void testSysTablesNoTypes() throws Exception {
executeCommand("SYS TABLES", r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertEquals("BASE TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
assertEquals("VIEW", r.column(3));
}, index, alias);
}
public void testSysTablesWithLegacyTypes() throws Exception {
executeCommand("SYS TABLES TYPE 'TABLE', 'ALIAS'", r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertEquals("TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
assertEquals("VIEW", r.column(3));
}, index, alias);
}
public void testSysTablesWithProperTypes() throws Exception {
executeCommand("SYS TABLES TYPE 'BASE TABLE', 'ALIAS'", r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertEquals("BASE TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
assertEquals("VIEW", r.column(3));
}, index, alias);
}
public void testSysTablesPattern() throws Exception {
executeCommand("SYS TABLES LIKE '%'", r -> {
assertEquals("test", r.column(2));
assertTrue(r.advanceRow());
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertEquals("BASE TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
}, index, alias);
}
@ -130,7 +195,18 @@ public class SysTablesTests extends ESTestCase {
assertEquals("test", r.column(2));
assertEquals("TABLE", r.column(3));
}, index);
}
public void testSysTablesNoPatternWithTypesSpecifiedInLegacyMode() throws Exception {
executeCommand("SYS TABLES TYPE 'TABLE','VIEW'", r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertEquals("TABLE", r.column(3));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
assertEquals("VIEW", r.column(3));
}, index, alias);
}
public void testSysTablesOnlyIndicesLegacyModeParameterized() throws Exception {
@ -192,43 +268,6 @@ public class SysTablesTests extends ESTestCase {
}, 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.stream().sorted(Comparator.comparing(IndexType::toSql)).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(0, r.size());
}, new IndexInfo[0]);
}
private SqlTypedParamValue param(Object value) {
return new SqlTypedParamValue(DataTypes.fromJava(value).typeName, value);