Repository: phoenix Updated Branches: refs/heads/4.x-HBase-1.0 95cff2d49 -> b35cb98ca
PHOENIX-1312 Do not always project the empty column family (Ram) Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/b35cb98c Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/b35cb98c Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/b35cb98c Branch: refs/heads/4.x-HBase-1.0 Commit: b35cb98caece0183ce4dc29fcae4bfc80dbdc504 Parents: 95cff2d Author: ramkrishna <ramkrishna.s.vasude...@gmail.com> Authored: Mon Dec 21 13:35:40 2015 +0530 Committer: ramkrishna <ramkrishna.s.vasude...@gmail.com> Committed: Mon Dec 21 13:40:05 2015 +0530 ---------------------------------------------------------------------- .../phoenix/end2end/MultiCfQueryExecIT.java | 69 +++++++++++++++- .../phoenix/compile/ProjectionCompiler.java | 16 ++-- .../apache/phoenix/compile/QueryCompiler.java | 2 +- .../apache/phoenix/compile/RowProjector.java | 18 +++-- .../phoenix/compile/StatementContext.java | 2 +- .../phoenix/iterate/BaseResultIterators.java | 84 ++++++++++++-------- .../phoenix/compile/QueryCompilerTest.java | 33 +++++++- .../java/org/apache/phoenix/util/TestUtil.java | 12 +-- 8 files changed, 179 insertions(+), 57 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java index 2edf189..f5566ce 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java @@ -172,17 +172,80 @@ public class MultiCfQueryExecIT extends BaseOwnClusterClientManagedTimeIT { assertFalse(rs.next()); // Use E column family. Since the column family with the empty key value (the first one, A) // is always added to the scan, we never really use other guideposts (but this may change). - List<KeyRange> splits = getAllSplits(conn, "MULTI_CF", "e.cpu_utilization IS NOT NULL"); + List<KeyRange> splits = getAllSplits(conn, "MULTI_CF", "e.cpu_utilization IS NOT NULL", "COUNT(*)"); // Since the E column family is not populated, it won't have as many splits assertEquals(3, splits.size()); // Same as above for G column family. - splits = getAllSplits(conn, "MULTI_CF", "g.response_time IS NOT NULL"); + splits = getAllSplits(conn, "MULTI_CF", "g.response_time IS NOT NULL", "COUNT(*)"); assertEquals(3, splits.size()); } finally { conn.close(); } } - + + @Test + public void testGuidePostsRetrievedForMultiCF() throws Exception { + Connection conn; + PreparedStatement stmt; + ResultSet rs; + + long ts = nextTimestamp(); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts + 10)); + conn = DriverManager.getConnection(getUrl(), props); + conn.createStatement() + .execute( + "CREATE TABLE T ( k INTEGER PRIMARY KEY, A.V1 VARCHAR, B.V2 VARCHAR, C.V3 VARCHAR)"); + conn.close(); + + props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts + 30)); + conn = DriverManager.getConnection(getUrl(), props); + stmt = conn.prepareStatement("UPSERT INTO T VALUES(?,?,?,?)"); + stmt.setInt(1, 1); + stmt.setString(2, "A"); + stmt.setString(3, "B"); + stmt.setString(4, "C"); + stmt.execute(); + conn.commit(); + + stmt = conn.prepareStatement("UPSERT INTO T VALUES(?,?,?,?)"); + stmt.setInt(1, 2); + stmt.setString(2, "D"); + stmt.setString(3, "E"); + stmt.setString(4, "F"); + stmt.execute(); + conn.commit(); + + stmt = conn.prepareStatement("UPSERT INTO T(k, A.V1, C.V3) VALUES(?,?,?)"); + stmt.setInt(1, 3); + stmt.setString(2, "E"); + stmt.setString(3, "X"); + stmt.execute(); + conn.commit(); + + stmt = conn.prepareStatement("UPSERT INTO T(k, A.V1, C.V3) VALUES(?,?,?)"); + stmt.setInt(1, 4); + stmt.setString(2, "F"); + stmt.setString(3, "F"); + stmt.execute(); + conn.commit(); + + conn.close(); + + analyzeTable(getUrl(), ts + 50, "T"); + + props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts + 60)); + conn = DriverManager.getConnection(getUrl(), props); + rs = conn.createStatement().executeQuery("SELECT B.V2 FROM T WHERE B.V2 = 'B'"); + assertTrue(rs.next()); + assertEquals("B",rs.getString(1)); + List<KeyRange> splits = getAllSplits(conn, "T", "C.V3 = 'X'", "A.V1"); + assertEquals(5, splits.size()); + splits = getAllSplits(conn, "T", "B.V2 = 'B'", "B.V2"); + assertEquals(3, splits.size()); + conn.close(); + } + @Test public void testCFToDisambiguate2() throws Exception { long ts = nextTimestamp(); http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java index 7cc2e66..3cf3934 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java @@ -79,7 +79,6 @@ import org.apache.phoenix.schema.PDatum; import org.apache.phoenix.schema.PName; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTable.IndexType; -import org.apache.phoenix.schema.PTable.ViewType; import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.schema.RowKeySchema; @@ -107,6 +106,7 @@ import com.google.common.collect.Sets; * @since 0.1 */ public class ProjectionCompiler { + private static final Expression NULL_EXPRESSION = LiteralExpression.newConstant(null); private ProjectionCompiler() { } @@ -116,7 +116,9 @@ public class ProjectionCompiler { } public static RowProjector compile(StatementContext context, SelectStatement statement, GroupBy groupBy) throws SQLException { - return compile(context, statement, groupBy, Collections.<PColumn>emptyList()); + return compile(context, statement, groupBy, Collections.<PColumn>emptyList(), + NULL_EXPRESSION// Pass null expression because we don't want empty key value to be projected + ); } private static int getMinPKOffset(PTable table, PName tenantId) { @@ -338,7 +340,7 @@ public class ProjectionCompiler { * @return projector used to access row values during scan * @throws SQLException */ - public static RowProjector compile(StatementContext context, SelectStatement statement, GroupBy groupBy, List<? extends PDatum> targetColumns) throws SQLException { + public static RowProjector compile(StatementContext context, SelectStatement statement, GroupBy groupBy, List<? extends PDatum> targetColumns, Expression where) throws SQLException { List<KeyValueColumnExpression> arrayKVRefs = new ArrayList<KeyValueColumnExpression>(); List<ProjectedColumnExpression> arrayProjectedColumnRefs = new ArrayList<ProjectedColumnExpression>(); List<Expression> arrayKVFuncs = new ArrayList<Expression>(); @@ -384,7 +386,7 @@ public class ProjectionCompiler { } else { projectAllTableColumns(context, tRef, true, projectedExpressions, projectedColumns, targetColumns); } - } else if (node instanceof FamilyWildcardParseNode){ + } else if (node instanceof FamilyWildcardParseNode) { if (tableRef == TableRef.EMPTY_TABLE_REF) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.NO_TABLE_SPECIFIED_FOR_WILDCARD_SELECT).build().buildException(); } @@ -483,16 +485,16 @@ public class ProjectionCompiler { } selectVisitor.compile(); - boolean isProjectEmptyKeyValue = (table.getType() != PTableType.VIEW || table.getViewType() != ViewType.MAPPED) - && !isWildcard; + boolean isProjectEmptyKeyValue = false; if (isWildcard) { projectAllColumnFamilies(table, scan); } else { + isProjectEmptyKeyValue = where == null || LiteralExpression.isTrue(where) || where.requiresFinalEvaluation(); for (byte[] family : projectedFamilies) { projectColumnFamily(table, scan, family); } } - return new RowProjector(projectedColumns, estimatedByteSize, isProjectEmptyKeyValue, resolver.hasUDFs()); + return new RowProjector(projectedColumns, estimatedByteSize, isProjectEmptyKeyValue, resolver.hasUDFs(), isWildcard); } private static void projectAllColumnFamilies(PTable table, Scan scan) { http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java index ad65c1c..70bb815 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java @@ -542,7 +542,7 @@ public class QueryCompiler { Set<SubqueryParseNode> subqueries = Sets.<SubqueryParseNode> newHashSet(); Expression where = WhereCompiler.compile(context, select, viewWhere, subqueries); context.setResolver(resolver); // recover resolver - RowProjector projector = ProjectionCompiler.compile(context, select, groupBy, asSubquery ? Collections.<PDatum>emptyList() : targetColumns); + RowProjector projector = ProjectionCompiler.compile(context, select, groupBy, asSubquery ? Collections.<PDatum>emptyList() : targetColumns, where); OrderBy orderBy = OrderByCompiler.compile(context, select, groupBy, limit, projector, groupBy == GroupBy.EMPTY_GROUP_BY ? innerPlanTupleProjector : null, isInRowKeyOrder); // Final step is to build the query plan if (!asSubquery) { http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/main/java/org/apache/phoenix/compile/RowProjector.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/RowProjector.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/RowProjector.java index 99ab5d4..2123788 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/RowProjector.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/RowProjector.java @@ -25,7 +25,6 @@ import java.util.List; import org.apache.phoenix.expression.Determinism; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.expression.visitor.CloneNonDeterministicExpressionVisitor; -import org.apache.phoenix.schema.AmbiguousColumnException; import org.apache.phoenix.schema.ColumnNotFoundException; import org.apache.phoenix.util.SchemaUtil; @@ -50,12 +49,13 @@ public class RowProjector { private final boolean allCaseSensitive; private final boolean someCaseSensitive; private final int estimatedSize; + private final boolean isProjectAll; private final boolean isProjectEmptyKeyValue; private final boolean cloneRequired; private final boolean hasUDFs; public RowProjector(RowProjector projector, boolean isProjectEmptyKeyValue) { - this(projector.getColumnProjectors(), projector.getEstimatedRowByteSize(), isProjectEmptyKeyValue, projector.hasUDFs); + this(projector.getColumnProjectors(), projector.getEstimatedRowByteSize(), isProjectEmptyKeyValue, projector.hasUDFs, projector.isProjectAll); } /** * Construct RowProjector based on a list of ColumnProjectors. @@ -65,7 +65,7 @@ public class RowProjector { * @param estimatedRowSize */ public RowProjector(List<? extends ColumnProjector> columnProjectors, int estimatedRowSize, boolean isProjectEmptyKeyValue) { - this(columnProjectors, estimatedRowSize, isProjectEmptyKeyValue, false); + this(columnProjectors, estimatedRowSize, isProjectEmptyKeyValue, false, false); } /** * Construct RowProjector based on a list of ColumnProjectors. @@ -76,7 +76,7 @@ public class RowProjector { * @param isProjectEmptyKeyValue * @param hasUDFs */ - public RowProjector(List<? extends ColumnProjector> columnProjectors, int estimatedRowSize, boolean isProjectEmptyKeyValue, boolean hasUDFs) { + public RowProjector(List<? extends ColumnProjector> columnProjectors, int estimatedRowSize, boolean isProjectEmptyKeyValue, boolean hasUDFs, boolean isProjectAll) { this.columnProjectors = Collections.unmodifiableList(columnProjectors); int position = columnProjectors.size(); reverseIndex = ArrayListMultimap.<String, Integer>create(); @@ -95,6 +95,7 @@ public class RowProjector { this.someCaseSensitive = someCaseSensitive; this.estimatedSize = estimatedRowSize; this.isProjectEmptyKeyValue = isProjectEmptyKeyValue; + this.isProjectAll = isProjectAll; this.hasUDFs = hasUDFs; boolean hasPerInvocationExpression = false; if (!hasUDFs) { @@ -129,14 +130,17 @@ public class RowProjector { } } return new RowProjector(clonedColProjectors, - this.getEstimatedRowByteSize(), - this.isProjectEmptyKeyValue(), this.hasUDFs); + this.estimatedSize, this.isProjectEmptyKeyValue, this.hasUDFs, this.isProjectAll); } - public boolean isProjectEmptyKeyValue() { + public boolean projectEveryRow() { return isProjectEmptyKeyValue; } + public boolean projectEverything() { + return isProjectAll; + } + public List<? extends ColumnProjector> getColumnProjectors() { return columnProjectors; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java index 80c4b89..9f4562a 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java @@ -280,7 +280,7 @@ public class StatementContext { whereConditionColumns.add(new Pair<byte[], byte[]>(cf, q)); } - public List<Pair<byte[], byte[]>> getWhereCoditionColumns() { + public List<Pair<byte[], byte[]>> getWhereConditionColumns() { return whereConditionColumns; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java index 19c60bf..21f082f 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java @@ -25,6 +25,7 @@ import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -158,32 +159,38 @@ public abstract class BaseResultIterators extends ExplainTable implements Result } else { FilterableStatement statement = plan.getStatement(); RowProjector projector = plan.getProjector(); - boolean keyOnlyFilter = familyMap.isEmpty() && context.getWhereCoditionColumns().isEmpty(); - if (projector.isProjectEmptyKeyValue()) { + boolean keyOnlyFilter = familyMap.isEmpty() && context.getWhereConditionColumns().isEmpty(); + if (!projector.projectEverything()) { // If nothing projected into scan and we only have one column family, just allow everything // to be projected and use a FirstKeyOnlyFilter to skip from row to row. This turns out to // be quite a bit faster. // Where condition columns also will get added into familyMap // When where conditions are present, we can not add FirstKeyOnlyFilter at beginning. - if (familyMap.isEmpty() && context.getWhereCoditionColumns().isEmpty() + if (familyMap.isEmpty() && context.getWhereConditionColumns().isEmpty() && table.getColumnFamilies().size() == 1) { // Project the one column family. We must project a column family since it's possible // that there are other non declared column families that we need to ignore. scan.addFamily(table.getColumnFamilies().get(0).getName().getBytes()); } else { - byte[] ecf = SchemaUtil.getEmptyColumnFamily(table); - // Project empty key value unless the column family containing it has - // been projected in its entirety. - if (!familyMap.containsKey(ecf) || familyMap.get(ecf) != null) { - scan.addColumn(ecf, QueryConstants.EMPTY_COLUMN_BYTES); + if (projector.projectEveryRow()) { + byte[] ecf = SchemaUtil.getEmptyColumnFamily(table); + // Project empty key value unless the column family containing it has + // been projected in its entirety. + if (!familyMap.containsKey(ecf) || familyMap.get(ecf) != null) { + scan.addColumn(ecf, QueryConstants.EMPTY_COLUMN_BYTES); + } } } - } else if (table.getViewType() == ViewType.MAPPED) { - // Since we don't have the empty key value in MAPPED tables, we must select all CFs in HRS. But only the - // selected column values are returned back to client - for (PColumnFamily family : table.getColumnFamilies()) { - scan.addFamily(family.getName().getBytes()); - } + if (table.getViewType() == ViewType.MAPPED) { + if (projector.projectEveryRow()) { + // Since we don't have the empty key value in MAPPED tables, + // we must select all CFs in HRS. However, only the + // selected column values are returned back to client. + for (PColumnFamily family : table.getColumnFamilies()) { + scan.addFamily(family.getName().getBytes()); + } + } + } } // Add FirstKeyOnlyFilter if there are no references to key value columns if (keyOnlyFilter) { @@ -236,7 +243,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result new TreeMap<ImmutableBytesPtr, NavigableSet<ImmutableBytesPtr>>(); Set<byte[]> conditionOnlyCfs = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR); int referencedCfCount = familyMap.size(); - for (Pair<byte[], byte[]> whereCol : context.getWhereCoditionColumns()) { + for (Pair<byte[], byte[]> whereCol : context.getWhereConditionColumns()) { if (!(familyMap.containsKey(whereCol.getFirst()))) { referencedCfCount++; } @@ -267,7 +274,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result } } // Making sure that where condition CFs are getting scanned at HRS. - for (Pair<byte[], byte[]> whereCol : context.getWhereCoditionColumns()) { + for (Pair<byte[], byte[]> whereCol : context.getWhereConditionColumns()) { if (useOptimization) { if (!(familyMap.containsKey(whereCol.getFirst()))) { scan.addFamily(whereCol.getFirst()); @@ -350,7 +357,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result return guideIndex; } - private List<byte[]> getGuidePosts() { + private List<byte[]> getGuidePosts(Set<byte[]> whereConditions) { /* * Don't use guide posts if: * 1) We're doing a point lookup, as HBase is fast enough at those @@ -368,22 +375,26 @@ public abstract class BaseResultIterators extends ExplainTable implements Result byte[] defaultCF = SchemaUtil.getEmptyColumnFamily(getTable()); if (table.getColumnFamilies().isEmpty()) { // For sure we can get the defaultCF from the table - if (guidePostMap.get(defaultCF) != null) { - gps = guidePostMap.get(defaultCF).getGuidePosts(); - } + gps = getDefaultFamilyGuidePosts(guidePostMap, defaultCF); } else { - Scan scan = context.getScan(); - if (scan.getFamilyMap().size() > 0 && !scan.getFamilyMap().containsKey(defaultCF)) { - // If default CF is not used in scan, use first CF referenced in scan - GuidePostsInfo guidePostsInfo = guidePostMap.get(scan.getFamilyMap().keySet().iterator().next()); - if (guidePostsInfo != null) { - gps = guidePostsInfo.getGuidePosts(); + byte[] familyInWhere = null; + if (!whereConditions.isEmpty()) { + if (whereConditions.contains(defaultCF)) { + gps = getDefaultFamilyGuidePosts(guidePostMap, defaultCF); + } else { + familyInWhere = whereConditions.iterator().next(); + if(familyInWhere != null) { + GuidePostsInfo guidePostsInfo = guidePostMap.get(familyInWhere); + if (guidePostsInfo != null) { + gps = guidePostsInfo.getGuidePosts(); + } else { + // As there are no guideposts collected for the where family we go with the default CF + gps = getDefaultFamilyGuidePosts(guidePostMap, defaultCF); + } } + } } else { - // Otherwise, favor use of default CF. - if (guidePostMap.get(defaultCF) != null) { - gps = guidePostMap.get(defaultCF).getGuidePosts(); - } + gps = getDefaultFamilyGuidePosts(guidePostMap, defaultCF); } } if (gps == null) { @@ -391,6 +402,13 @@ public abstract class BaseResultIterators extends ExplainTable implements Result } return gps; } + + private List<byte[]> getDefaultFamilyGuidePosts(Map<byte[], GuidePostsInfo> guidePostMap, byte[] defaultCF) { + if (guidePostMap.get(defaultCF) != null) { + return guidePostMap.get(defaultCF).getGuidePosts(); + } + return null; + } private static String toString(List<byte[]> gps) { StringBuilder buf = new StringBuilder(gps.size() * 100); @@ -439,7 +457,11 @@ public abstract class BaseResultIterators extends ExplainTable implements Result PTable table = getTable(); boolean isSalted = table.getBucketNum() != null; boolean isLocalIndex = table.getIndexType() == IndexType.LOCAL; - List<byte[]> gps = getGuidePosts(); + HashSet<byte[]> whereConditions = new HashSet<byte[]>(context.getWhereConditionColumns().size()); + for(Pair<byte[], byte[]> where : context.getWhereConditionColumns()) { + whereConditions.add(where.getFirst()); + } + List<byte[]> gps = getGuidePosts(whereConditions); if (logger.isDebugEnabled()) { logger.debug("Guideposts: " + toString(gps)); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java index bea222b..9411549 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java @@ -441,6 +441,12 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest { return plan.getContext().getScan(); } + private Scan projectQuery(String query) throws SQLException { + QueryPlan plan = getQueryPlan(query, Collections.emptyList()); + plan.iterator(); // Forces projection + return plan.getContext().getScan(); + } + private QueryPlan getQueryPlan(String query, List<Object> binds) throws SQLException { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); @@ -449,7 +455,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest { for (Object bind : binds) { statement.setObject(1, bind); } - return statement.compileQuery(query); + QueryPlan plan = statement.compileQuery(query); + return plan; } finally { conn.close(); } @@ -2146,5 +2153,29 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest { } } + private static void assertFamilies(Scan s, String... families) { + assertEquals(families.length, s.getFamilyMap().size()); + for (String fam : families) { + byte[] cf = Bytes.toBytes(fam); + assertTrue("Expected to contain " + fam, s.getFamilyMap().containsKey(cf)); + } + } + + @Test + public void testProjection() throws SQLException { + Connection conn = DriverManager.getConnection(getUrl()); + try { + conn.createStatement().execute("CREATE TABLE t(k INTEGER PRIMARY KEY, a.v1 VARCHAR, b.v2 VARCHAR, c.v3 VARCHAR)"); + assertFamilies(projectQuery("SELECT k FROM t"), "A"); + assertFamilies(projectQuery("SELECT k FROM t WHERE k = 5"), "A"); + assertFamilies(projectQuery("SELECT v2 FROM t WHERE k = 5"), "A", "B"); + assertFamilies(projectQuery("SELECT v2 FROM t WHERE v2 = 'a'"), "B"); + assertFamilies(projectQuery("SELECT v3 FROM t WHERE v2 = 'a'"), "B", "C"); + assertFamilies(projectQuery("SELECT v3 FROM t WHERE v2 = 'a' AND v3 is null"), "A", "B", "C"); + } finally { + conn.close(); + } + } + } http://git-wip-us.apache.org/repos/asf/phoenix/blob/b35cb98c/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java index 451ce62..e66f8ca 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java @@ -490,20 +490,20 @@ public class TestUtil { } public static List<KeyRange> getAllSplits(Connection conn, String tableName) throws SQLException { - return getSplits(conn, tableName, null, null, null, null); + return getSplits(conn, tableName, null, null, null, null, null); } - public static List<KeyRange> getAllSplits(Connection conn, String tableName, String where) throws SQLException { - return getSplits(conn, tableName, null, null, null, where); + public static List<KeyRange> getAllSplits(Connection conn, String tableName, String where, String selectClause) throws SQLException { + return getSplits(conn, tableName, null, null, null, where, selectClause); } - public static List<KeyRange> getSplits(Connection conn, String tableName, String pkCol, byte[] lowerRange, byte[] upperRange, String whereClauseSuffix) throws SQLException { + public static List<KeyRange> getSplits(Connection conn, String tableName, String pkCol, byte[] lowerRange, byte[] upperRange, String whereClauseSuffix, String selectClause) throws SQLException { String whereClauseStart = (lowerRange == null && upperRange == null ? "" : " WHERE " + ((lowerRange != null ? (pkCol + " >= ? " + (upperRange != null ? " AND " : "")) : "") + (upperRange != null ? (pkCol + " < ?") : "" ))); String whereClause = whereClauseSuffix == null ? whereClauseStart : whereClauseStart.length() == 0 ? (" WHERE " + whereClauseSuffix) : (" AND " + whereClauseSuffix); - String query = "SELECT /*+ NO_INDEX */ COUNT(*) FROM " + tableName + whereClause; + String query = "SELECT /*+ NO_INDEX */ "+selectClause+" FROM " + tableName + whereClause; PhoenixPreparedStatement pstmt = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); if (lowerRange != null) { pstmt.setBytes(1, lowerRange); @@ -548,7 +548,7 @@ public class TestUtil { } public static List<KeyRange> getSplits(Connection conn, byte[] lowerRange, byte[] upperRange) throws SQLException { - return getSplits(conn, STABLE_NAME, STABLE_PK_NAME, lowerRange, upperRange, null); + return getSplits(conn, STABLE_NAME, STABLE_PK_NAME, lowerRange, upperRange, null, "COUNT(*)"); } public static List<KeyRange> getAllSplits(Connection conn) throws SQLException {