diff --git a/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcErrorsTestCase.java b/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcErrorsTestCase.java index 8929ff3d8933d..535b5447ff355 100644 --- a/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcErrorsTestCase.java +++ b/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcErrorsTestCase.java @@ -40,18 +40,6 @@ public void testSelectColumnFromMissingIndex() throws SQLException { } } - public void testSelectFromEmptyIndex() throws IOException, SQLException { - // Create an index without any types - Request request = new Request("PUT", "/test"); - request.setJsonEntity("{}"); - client().performRequest(request); - - try (Connection c = esJdbc()) { - SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT * FROM test").executeQuery()); - assertEquals("Found 1 problem\nline 1:8: Cannot determine columns for [*]", e.getMessage()); - } - } - public void testSelectColumnFromEmptyIndex() throws IOException, SQLException { Request request = new Request("PUT", "/test"); request.setJsonEntity("{}"); diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/ErrorsTestCase.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/ErrorsTestCase.java index 700a03072feda..455ec4ecf7b7f 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/ErrorsTestCase.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/ErrorsTestCase.java @@ -18,8 +18,6 @@ public interface ErrorsTestCase { void testSelectColumnFromMissingIndex() throws Exception; - void testSelectFromEmptyIndex() throws Exception; - void testSelectColumnFromEmptyIndex() throws Exception; void testSelectMissingField() throws Exception; diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ErrorsTestCase.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ErrorsTestCase.java index 443355f64665a..c2c3ef671555e 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ErrorsTestCase.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ErrorsTestCase.java @@ -43,17 +43,6 @@ public void testSelectColumnFromMissingIndex() throws Exception { assertEquals("line 1:17: Unknown index [test]" + END, readLine()); } - @Override - public void testSelectFromEmptyIndex() throws Exception { - // Create an index without any types - Request request = new Request("PUT", "/test"); - request.setJsonEntity("{}"); - client().performRequest(request); - - assertFoundOneProblem(command("SELECT * FROM test")); - assertEquals("line 1:8: Cannot determine columns for [*]" + END, readLine()); - } - @Override public void testSelectColumnFromEmptyIndex() throws Exception { Request request = new Request("PUT", "/test"); diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/DataLoader.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/DataLoader.java index db4677b185537..115d858aabb11 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/DataLoader.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/DataLoader.java @@ -72,6 +72,15 @@ protected static void loadEmpDatasetIntoEs(RestClient client) throws Exception { // frozen index loadEmpDatasetIntoEs(client, "frozen_emp", "employees"); freeze(client, "frozen_emp"); + loadNoColsDatasetIntoEs(client, "empty_mapping"); + } + + private static void loadNoColsDatasetIntoEs(RestClient client, String index) throws Exception { + createEmptyIndex(client, index); + Request request = new Request("POST", "/" + index + "/_bulk"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"index\":{}\n{}\n" + "{\"index\":{}\n{}\n"); + client.performRequest(request); } public static void loadDocsDatasetIntoEs(RestClient client) throws Exception { diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/SqlSpecTestCase.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/SqlSpecTestCase.java index 40bcaeaa1a214..0e3fbe3ed7884 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/SqlSpecTestCase.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/SqlSpecTestCase.java @@ -31,7 +31,10 @@ public abstract class SqlSpecTestCase extends SpecBaseIntegrationTestCase { private String query; @ClassRule - public static LocalH2 H2 = new LocalH2(c -> c.createStatement().execute("RUNSCRIPT FROM 'classpath:/setup_test_emp.sql'")); + public static LocalH2 H2 = new LocalH2((c) -> { + c.createStatement().execute("RUNSCRIPT FROM 'classpath:/setup_test_emp.sql'"); + c.createStatement().execute("RUNSCRIPT FROM 'classpath:/setup_empty_mapping.sql'"); + }); @ParametersFactory(argumentFormatting = PARAM_FORMATTING) public static List readScriptSpec() throws Exception { diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/rest/RestSqlTestCase.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/rest/RestSqlTestCase.java index 928c756d665af..83a6eafa72c79 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/rest/RestSqlTestCase.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/rest/RestSqlTestCase.java @@ -344,16 +344,6 @@ public void testSelectColumnFromMissingIndex() throws Exception { expectBadRequest(() -> runSql(mode, "SELECT abc FROM missing"), containsString("1:17: Unknown index [missing]")); } - @Override - public void testSelectFromEmptyIndex() throws Exception { - // Create an index without any types - Request request = new Request("PUT", "/test"); - request.setJsonEntity("{}"); - client().performRequest(request); - String mode = randomFrom("jdbc", "plain"); - expectBadRequest(() -> runSql(mode, "SELECT * FROM test"), containsString("1:8: Cannot determine columns for [*]")); - } - @Override public void testSelectColumnFromEmptyIndex() throws Exception { Request request = new Request("PUT", "/test"); diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/command.csv-spec b/x-pack/plugin/sql/qa/server/src/main/resources/command.csv-spec index 8905284af1792..c59be149efba4 100644 --- a/x-pack/plugin/sql/qa/server/src/main/resources/command.csv-spec +++ b/x-pack/plugin/sql/qa/server/src/main/resources/command.csv-spec @@ -223,13 +223,14 @@ TODAY |SCALAR showTables SHOW TABLES; - name | type | kind + name | type | kind +empty_mapping |TABLE |INDEX logs |TABLE |INDEX logs_nanos |TABLE |INDEX test_alias |VIEW |ALIAS -test_alias_emp |VIEW |ALIAS -test_emp |TABLE |INDEX -test_emp_copy |TABLE |INDEX +test_alias_emp |VIEW |ALIAS +test_emp |TABLE |INDEX +test_emp_copy |TABLE |INDEX ; showTablesSimpleLike @@ -382,3 +383,19 @@ last_name |VARCHAR |text last_name.keyword |VARCHAR |keyword salary |INTEGER |integer ; + + +describeNoCols +DESCRIBE "empty_mapping"; + + column:s | type:s | mapping:s +----------------------+---------------+--------------- +; + + +showColumnsInNoCols +SHOW COLUMNS IN "empty_mapping"; + + column:s | type:s | mapping:s +----------------------+---------------+--------------- +; diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.csv-spec b/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.csv-spec new file mode 100644 index 0000000000000..ad76bcaad5a08 --- /dev/null +++ b/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.csv-spec @@ -0,0 +1,28 @@ + +// the following queries all return no rows in H2 + +selectConstAggregationWithGroupBy +SELECT 1 a, COUNT(*) b, MAX(1) c FROM empty_mapping GROUP BY a; + + a:i | b:l | c:i +---------------+---------------+--------------- +1 |2 |1 +; + +subselectWithConst +SELECT 1, * FROM (SELECT * FROM empty_mapping) s; + + 1:i +--------------- +1 +1 +; + +subselectWithInnerConst +SELECT * FROM (SELECT 1, * FROM empty_mapping) s; + + 1:i +--------------- +1 +1 +; diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.sql-spec b/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.sql-spec new file mode 100644 index 0000000000000..9d4a77ae38c19 --- /dev/null +++ b/x-pack/plugin/sql/qa/server/src/main/resources/empty-mapping.sql-spec @@ -0,0 +1,22 @@ +selectStar +SELECT * FROM empty_mapping; +selectStarWithFilter +SELECT * FROM empty_mapping WHERE 2 > 1; +selectStarWithFilterAndLimit +SELECT * FROM empty_mapping WHERE 1 > 2 LIMIT 10; + +selectCount +SELECT COUNT(*) FROM empty_mapping; +// awaits fix: https://github.com/elastic/elasticsearch/issues/74311 +// selectCountWithWhere +// SELECT COUNT(*) FROM empty_mapping WHERE 1 + 1 = 3; + +selectConst +SELECT 1, 2, 3 FROM empty_mapping; +selectConstAggregation +SELECT MAX(1), SUM(2) FROM empty_mapping; + +// fails in H2 with a syntax error but cannot be tested in CSV spec because datasets without columns cannot be parsed +// awaits fix: https://github.com/elastic/elasticsearch/issues/39895 (latest H2 version can run the query) +// subselect +// SELECT * FROM (SELECT * FROM empty_mapping) s; diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/setup_empty_mapping.sql b/x-pack/plugin/sql/qa/server/src/main/resources/setup_empty_mapping.sql new file mode 100644 index 0000000000000..e16078d4ece89 --- /dev/null +++ b/x-pack/plugin/sql/qa/server/src/main/resources/setup_empty_mapping.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS "empty_mapping"; +CREATE TABLE "empty_mapping" (); + +INSERT INTO "empty_mapping" DEFAULT VALUES; +INSERT INTO "empty_mapping" DEFAULT VALUES; diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/setup_test_emp.sql b/x-pack/plugin/sql/qa/server/src/main/resources/setup_test_emp.sql index 804c2bbe34a77..829a739895531 100644 --- a/x-pack/plugin/sql/qa/server/src/main/resources/setup_test_emp.sql +++ b/x-pack/plugin/sql/qa/server/src/main/resources/setup_test_emp.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS "test_emp"; CREATE TABLE "test_emp" ( "birth_date" TIMESTAMP WITH TIME ZONE, - "emp_no" INT, + "emp_no" INT, "first_name" VARCHAR(50), "gender" VARCHAR(1), "hire_date" TIMESTAMP WITH TIME ZONE, @@ -10,4 +10,5 @@ CREATE TABLE "test_emp" ( "name" VARCHAR(50), "salary" INT ) - AS SELECT birth_date, emp_no, first_name, gender, hire_date, languages, last_name, CONCAT(first_name, ' ', last_name) AS name, salary FROM CSVREAD('classpath:/employees.csv'); \ No newline at end of file + AS SELECT birth_date, emp_no, first_name, gender, hire_date, languages, last_name, CONCAT(first_name, ' ', last_name) AS name, salary FROM CSVREAD('classpath:/employees.csv'); + diff --git a/x-pack/plugin/sql/qa/server/src/main/resources/slow/frozen.csv-spec b/x-pack/plugin/sql/qa/server/src/main/resources/slow/frozen.csv-spec index 1fd722d70b04b..650c76089d272 100644 --- a/x-pack/plugin/sql/qa/server/src/main/resources/slow/frozen.csv-spec +++ b/x-pack/plugin/sql/qa/server/src/main/resources/slow/frozen.csv-spec @@ -7,14 +7,15 @@ showTables SHOW TABLES INCLUDE FROZEN; - name | type | kind + name | type | kind +empty_mapping |TABLE |INDEX frozen_emp |TABLE |FROZEN INDEX logs |TABLE |INDEX logs_nanos |TABLE |INDEX test_alias |VIEW |ALIAS -test_alias_emp |VIEW |ALIAS -test_emp |TABLE |INDEX -test_emp_copy |TABLE |INDEX +test_alias_emp |VIEW |ALIAS +test_emp |TABLE |INDEX +test_emp_copy |TABLE |INDEX ; columnFromFrozen @@ -31,18 +32,18 @@ F percentileFrozen SELECT gender, PERCENTILE(emp_no, 92.45) p1 FROM FROZEN frozen_emp GROUP BY gender; -gender:s | p1:d -null |10018.745 +gender:s | p1:d +null |10018.745 F |10098.0085 -M |10091.393 +M |10091.393 ; countFromFrozen SELECT gender, COUNT(*) AS c FROM FROZEN frozen_emp GROUP BY gender; gender:s | c:l -null |10 -F |33 +null |10 +F |33 M |57 ; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java index f7d9a803c4601..cf19e679e54ff 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Analyzer.java @@ -417,12 +417,9 @@ private List expandProjections(List for (NamedExpression ne : projections) { if (ne instanceof UnresolvedStar) { List expanded = expandStar((UnresolvedStar) ne, output); + // the field exists, but cannot be expanded (no sub-fields) - if (expanded.isEmpty()) { - result.add(ne); - } else { - result.addAll(expanded); - } + result.addAll(expanded); } else if (ne instanceof UnresolvedAlias) { UnresolvedAlias ua = (UnresolvedAlias) ne; if (ua.child() instanceof UnresolvedStar) { @@ -442,7 +439,7 @@ private List expandStar(UnresolvedStar us, List outp // a qualifier is specified - since this is a star, it should be a CompoundDataType if (us.qualifier() != null) { // resolve the so-called qualifier first - // since this is an unresolved start we don't know whether it's a path or an actual qualifier + // since this is an unresolved star we don't know whether it's a path or an actual qualifier Attribute q = resolveAgainstList(us.qualifier(), output, true); // the wildcard couldn't be expanded because the field doesn't exist at all @@ -455,6 +452,10 @@ private List expandStar(UnresolvedStar us, List outp else if (q.resolved() == false) { return singletonList(q); } + // qualifier resolves to a non-struct field and cannot be expanded + else if (DataTypes.isPrimitive(q.dataType())) { + return singletonList(us); + } // now use the resolved 'qualifier' to match for (Attribute attr : output) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/container/QueryContainer.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/container/QueryContainer.java index 9907d0ed688ca..471a75edabd24 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/container/QueryContainer.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/container/QueryContainer.java @@ -182,7 +182,9 @@ public List> sortingColumns() { */ public BitSet columnMask(List columns) { BitSet mask = new BitSet(fields.size()); - aliasName(columns.get(0)); + if (columns.size() > 0) { + aliasName(columns.get(0)); + } for (Attribute column : columns) { Expression expression = aliases.resolve(column, column); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/FieldAttributeTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/FieldAttributeTests.java index 9fa54bc107e7b..1f101b8f93d08 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/FieldAttributeTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/FieldAttributeTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.xpack.sql.parser.SqlParser; import org.elasticsearch.xpack.sql.stats.Metrics; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -301,4 +302,15 @@ public void testFunctionWithExpressionOverNonExistingFieldAsArgumentAndSameAlias assertEquals("Found 1 problem\nline 1:22: Unknown column [missing]", ex.getMessage()); } + public void testExpandStarOnIndexWithoutColumns() { + EsIndex test = new EsIndex("test", Collections.emptyMap()); + getIndexResult = IndexResolution.valid(test); + analyzer = new Analyzer(SqlTestUtils.TEST_CFG, functionRegistry, getIndexResult, verifier); + + LogicalPlan plan = plan("SELECT * FROM test"); + + assertThat(plan, instanceOf(Project.class)); + assertTrue(((Project) plan).projections().isEmpty()); + } + }