blob: d67d7e66080426711ab5f869695129b785dcdb73 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.drill.exec.physical.impl.scan.v3.schema;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.drill.categories.EvfTest;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.exec.physical.impl.scan.v3.schema.ScanProjectionParser.ProjectionParseResult;
import org.apache.drill.exec.physical.rowSet.RowSetTestUtils;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.apache.drill.test.BaseTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Test the projection list parser: parses a list of SchemaPath
* items into a detailed structure, handling duplicate or overlapping
* items. Special cases the select-all (SELECT *) and select none
* (SELECT COUNT(*)) cases.
* <p>
* These tests should verify everything about (runtime) projection
* parsing; the only bits not tested here is that which is
* inherently specific to some use case.
*/
@Category(EvfTest.class)
public class TestProjectionParser extends BaseTest {
/**
* Null map means everything is projected
*/
@Test
public void testProjectionAll() {
ProjectionParseResult result = ScanProjectionParser.parse(null);
assertEquals(0, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
assertTrue(SchemaUtils.isProjectAll(projSet));
assertFalse(SchemaUtils.isProjectNone(projSet));
assertTrue(projSet.isEmpty());
}
/**
* SELECT * means everything is projected
*/
@Test
public void testWildcard() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectAll());
assertEquals(0, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
assertTrue(SchemaUtils.isProjectAll(projSet));
assertFalse(SchemaUtils.isProjectNone(projSet));
assertTrue(projSet.isEmpty());
}
/**
* Test an empty projection which occurs in a
* SELECT COUNT(*) query.
* Empty list means nothing is projected.
*/
@Test
public void testProjectionNone() {
ProjectionParseResult result = ScanProjectionParser.parse(
Collections.emptyList());
assertEquals(-1, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
assertFalse(SchemaUtils.isProjectAll(projSet));
assertTrue(SchemaUtils.isProjectNone(projSet));
assertTrue(projSet.isEmpty());
}
/**
* Simple non-map columns
*/
@Test
public void testProjectionSimple() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a", "b", "c"));
assertEquals(-1, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
assertFalse(SchemaUtils.isProjectAll(projSet));
assertFalse(SchemaUtils.isProjectNone(projSet));
assertNotNull(projSet.metadata("a"));
assertNotNull(projSet.metadata("b"));
assertNotNull(projSet.metadata("c"));
assertNull(projSet.metadata("d"));
assertEquals(3, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertTrue(a.isSimple());
assertFalse(a.isArray());
assertFalse(a.isMap());
assertNull(a.tupleSchema());
}
/**
* The projection set does not enforce uniqueness.
*/
@Test
public void testSimpleDups() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a", "b", "a"));
assertEquals(-1, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
assertEquals(2, projSet.size());
assertEquals(2, ((ProjectedColumn) projSet.metadata("a")).refCount());
}
/**
* Selected map projection, multiple levels, full projection
* at leaf level.
*/
@Test
public void testProjectionMapSubset() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("map.a", "map.b", "map.map2.x"));
TupleMetadata projSet = result.dynamicSchema;
// Map itself is projected and has a map qualifier
ProjectedColumn map = (ProjectedColumn) projSet.metadata("map");
assertNotNull(map);
// Map: an explicit map-like at top level
assertTrue(map.isMap());
TupleMetadata mapProj = map.tupleSchema();
assertNotNull(mapProj);
assertFalse(SchemaUtils.isProjectAll(mapProj));
assertFalse(SchemaUtils.isProjectNone(mapProj));
assertNotNull(mapProj.metadata("a"));
assertNotNull(mapProj.metadata("b"));
assertNotNull(mapProj.metadata("map2"));
assertNull(mapProj.metadata("bogus"));
// Map b: an implied nested map
ProjectedColumn b = (ProjectedColumn) mapProj.metadata("b");
assertTrue(b.isSimple());
// Map2, an nested map, has an explicit projection
ProjectedColumn map2 = (ProjectedColumn) mapProj.metadata("map2");
TupleMetadata map2Proj = map2.tupleSchema();
assertNotNull(map2Proj);
assertFalse(SchemaUtils.isProjectAll(map2Proj));
assertNotNull(map2Proj.metadata("x"));
assertNull(map2Proj.metadata("bogus"));
}
/**
* Project both a map member and the entire map.
*/
@Test
public void testProjectionMapAndSimple() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("map.a", "map"));
doTestMapAndSimple(result.dynamicSchema);
}
/**
* Project both an entire map and a map member.
*/
@Test
public void testProjectionSimpleAndMap() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("map", "map.a"));
doTestMapAndSimple(result.dynamicSchema);
}
private void doTestMapAndSimple(TupleMetadata projSet) {
ProjectedColumn map = (ProjectedColumn) projSet.metadata("map");
assertNotNull(map);
assertTrue(map.isMap());
TupleMetadata mapProj = map.tupleSchema();
assertNotNull(mapProj);
assertTrue(SchemaUtils.isProjectAll(mapProj));
assertNotNull(mapProj.metadata("a"));
assertNull(mapProj.metadata("b"));
}
/**
* Project both a map member and the entire map.
*/
@Test
public void testProjectionMapAndWildcard() {
// Built up by hand because "map.*" is not valid Drill
// expression syntax.
List<SchemaPath> projCols = new ArrayList<>();
projCols.add(SchemaPath.getCompoundPath("map", "a"));
projCols.add(SchemaPath.getCompoundPath("map", SchemaPath.DYNAMIC_STAR));
ProjectionParseResult result = ScanProjectionParser.parse(projCols);
assertEquals(-1, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
doTestMapAndSimple(projSet);
}
/**
* Project both an entire map and a map member.
*/
@Test
public void testProjectionWildcardAndMap() {
List<SchemaPath> projCols = new ArrayList<>();
projCols.add(SchemaPath.getCompoundPath("map", SchemaPath.DYNAMIC_STAR));
projCols.add(SchemaPath.getCompoundPath("map", "a"));
ProjectionParseResult result = ScanProjectionParser.parse(projCols);
assertEquals(-1, result.wildcardPosn);
TupleMetadata projSet = result.dynamicSchema;
doTestMapAndSimple(projSet);
}
@Test
public void testMapDetails() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a.b.c", "a.c", "d"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(2, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertFalse(a.isSimple());
assertFalse(a.isArray());
assertTrue(a.isMap());
// a{}
TupleMetadata aMembers = a.tupleSchema();
assertNotNull(aMembers);
assertEquals(2, aMembers.size());
// a.b
ProjectedColumn a_b = (ProjectedColumn) aMembers.metadata(0);
assertEquals("b", a_b.name());
assertTrue(a_b.isMap());
// a.b{}
TupleMetadata a_bMembers = a_b.tupleSchema();
assertNotNull(a_bMembers);
assertEquals(1, a_bMembers.size());
// a.b.c
ProjectedColumn a_b_c = (ProjectedColumn) a_bMembers.metadata(0);
assertNotNull(a_b_c);
assertEquals("c", a_b_c.name());
assertTrue(a_b_c.isSimple());
// a.c
ProjectedColumn a_c = (ProjectedColumn) aMembers.metadata(1);
assertNotNull(a_c);
assertEquals("c", a_c.name());
assertTrue(a_c.isSimple());
// d
ProjectedColumn d = (ProjectedColumn) projSet.metadata(1);
assertNotNull(d);
assertEquals("d", d.name());
assertTrue(d.isSimple());
}
/**
* Duplicate column names are merged for projection.
*/
@Test
public void testMapDups() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a.b", "a.c", "a.b"));
TupleMetadata projSet = result.dynamicSchema;
ProjectedColumn a = (ProjectedColumn) projSet.metadata("a");
TupleMetadata aMap = a.tupleSchema();
assertEquals(2, aMap.size());
assertEquals(2, ((ProjectedColumn) aMap.metadata("b")).refCount());
}
@Test
public void testArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[1]", "a[3]"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertTrue(a.isArray());
assertEquals(1, a.arrayDims());
assertFalse(a.isSimple());
assertFalse(a.isMap());
assertTrue(a.hasIndexes());
boolean indexes[] = a.indexes();
assertNotNull(indexes);
assertEquals(4, indexes.length);
assertFalse(indexes[0]);
assertTrue(indexes[1]);
assertFalse(indexes[2]);
assertTrue(indexes[3]);
}
@Test
public void testMultiDimArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[0][1][2]", "a[2][3]"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertTrue(a.isArray());
// Dimension count is the maximum seen.
assertEquals(3, a.arrayDims());
assertFalse(a.isSimple());
assertFalse(a.isMap());
// Indexes only at the first dimension
boolean[] indexes = a.indexes();
assertNotNull(indexes);
assertEquals(3, indexes.length);
assertTrue(indexes[0]);
assertFalse(indexes[1]);
assertTrue(indexes[2]);
}
/**
* Duplicate array entries are allowed to handle the
* use case of a[1], a[1].z. Each element is reported once;
* the project operator will create copies as needed.
*/
@Test
public void testArrayDupsIgnored() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[1]", "a[3]", "a[1]", "a[3].z"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertTrue(a.isArray());
boolean indexes[] = a.indexes();
assertNotNull(indexes);
assertEquals(4, indexes.length);
assertFalse(indexes[0]);
assertTrue(indexes[1]);
assertFalse(indexes[2]);
assertTrue(indexes[3]);
}
@Test
public void testArrayAndSimple() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[1]", "a"));
doTestArrayAndSimple(result.dynamicSchema);
}
@Test
public void testSimpleAndArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a", "a[1]"));
doTestArrayAndSimple(result.dynamicSchema);
}
private void doTestArrayAndSimple(TupleMetadata projSet) {
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
assertEquals("a", a.name());
assertTrue(a.isArray());
assertNull(a.indexes());
}
@Test
// Drill syntax does not support map arrays
public void testMapArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[1].x"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
// Column acts like an array
assertTrue(a.isArray());
assertTrue(a.hasIndexes());
assertEquals(1, a.arrayDims());
// And the column acts like a map
assertTrue(a.isMap());
TupleMetadata aProj = a.tupleSchema();
assertFalse(SchemaUtils.isProjectAll(aProj));
assertFalse(SchemaUtils.isProjectNone(aProj));
assertNotNull(aProj.metadata("x"));
assertNull(aProj.metadata("y"));
}
@Test
// Drill syntax does not support map arrays
public void testMap2DArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("a[1][2].x"));
TupleMetadata projSet = result.dynamicSchema;
assertEquals(1, projSet.size());
ProjectedColumn a = (ProjectedColumn) projSet.metadata(0);
// Column acts like an array
assertTrue(a.isArray());
assertTrue(a.hasIndexes());
// Note that the multiple dimensions are inferred only through
// the multiple levels of qualifiers.
// And the column acts like a map
assertTrue(a.isMap());
TupleMetadata aProj = a.tupleSchema();
assertNotNull(aProj.metadata("x"));
assertNull(aProj.metadata("y"));
}
/**
* Projection does not enforce semantics; it just report what it
* sees. This allows cases such as m.a and m[0], which might mean
* that m is a map array, m.a wants an array of a-member values, and m[0]
* wants the first map in the array. Not clear Drill actually supports
* these cases, however.
*/
@Test
public void testArrayAndMap() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("m.a", "m[0]"));
doTestArrayAndMap(result.dynamicSchema);
}
@Test
public void testMapAndArray() {
ProjectionParseResult result = ScanProjectionParser.parse(
RowSetTestUtils.projectList("m[0]", "m.a"));
doTestArrayAndMap(result.dynamicSchema);
}
private void doTestArrayAndMap(TupleMetadata projSet) {
assertEquals(1, projSet.size());
ProjectedColumn m = (ProjectedColumn) projSet.metadata("m");
assertTrue(m.isArray());
assertEquals(1, m.arrayDims());
assertTrue(m.isMap());
TupleMetadata mProj = m.tupleSchema();
assertNotNull(mProj.metadata("a"));
assertNull(mProj.metadata("b"));
}
}