Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ Operator genOPTree(ASTNode ast, PlannerContext plannerCtx) throws SemanticExcept
msg = "Plan not optimized by CBO.";
}
this.ctx.setCboInfo(msg);
// hive.cbo.enable is still true, so analyzeInternal skipped ORDER BY ordinal
// substitution. Resolve them before the legacy planner compiles the ordinal
// as a constant (and then drops the sort entirely). See HIVE-30037.
processPositionAlias(ast, false, true);
sinkOp = super.genOPTree(ast, plannerCtx);
}
}
Expand Down
18 changes: 13 additions & 5 deletions ql/src/java/org/apache/hadoop/hive/ql/parse/SemanticAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -14239,6 +14239,15 @@ private void validateCreateView()

// Process the position alias in GROUPBY and ORDERBY
void processPositionAlias(ASTNode ast) throws SemanticException {
// When CBO is enabled, ORDER BY ordinals are resolved in CalcitePlanner.genSortByKey.
// If CBO later declines the statement, CalcitePlanner.genOPTree calls this again with
// only ORDER BY enabled; GROUP BY expressions have already been resolved and
// must not be interpreted as ordinals a second time (HIVE-30037).
processPositionAlias(ast, true, !HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_CBO_ENABLED));
}

void processPositionAlias(ASTNode ast, boolean processGroupByPositionAlias,

Check failure on line 14249 in ql/src/java/org/apache/hadoop/hive/ql/parse/SemanticAnalyzer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 106 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=apache_hive&issues=AaC0qUJbrFF_D3zHXyda&open=AaC0qUJbrFF_D3zHXyda&pullRequest=6798
boolean processOrderByPositionAlias) throws SemanticException {
boolean isBothByPos = HiveConf.getBoolVar(conf, ConfVars.HIVE_GROUPBY_ORDERBY_POSITION_ALIAS);
boolean isGbyByPos = isBothByPos
|| HiveConf.getBoolVar(conf, ConfVars.HIVE_GROUPBY_POSITION_ALIAS);
Expand Down Expand Up @@ -14278,7 +14287,7 @@ void processPositionAlias(ASTNode ast) throws SemanticException {
int selectExpCnt = selectNode.getChildCount();

// replace each of the position alias in GROUPBY with the actual column name
if (groupbyNode != null) {
if (processGroupByPositionAlias && groupbyNode != null) {
for (int child_pos = 0; child_pos < groupbyNode.getChildCount(); ++child_pos) {
ASTNode node = (ASTNode) groupbyNode.getChild(child_pos);
if (node.getToken().getType() == HiveParser.Number) {
Expand All @@ -14301,10 +14310,9 @@ void processPositionAlias(ASTNode ast) throws SemanticException {
}
}

// replace each of the position alias in ORDERBY with the actual column name,
// if cbo is enabled, orderby position will be processed in genPlan
if (!HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_CBO_ENABLED)
&& orderbyNode != null) {
// replace each of the position alias in ORDERBY with the actual column name.
// When CBO actually plans the statement, ordinals are resolved in genSortByKey.
if (processOrderByPositionAlias && orderbyNode != null) {
isAllCol = false;
for (int child_pos = 0; child_pos < selectNode.getChildCount(); ++child_pos) {
ASTNode node = (ASTNode) selectNode.getChild(child_pos).getChild(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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.hadoop.hive.ql.parse;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.ql.DriverFactory;
import org.apache.hadoop.hive.ql.IDriver;
import org.apache.hadoop.hive.ql.session.SessionState;
import org.apache.hive.testutils.HiveTestEnvSetup;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;

/**
* HIVE-30037: executed row order when CBO declines the statement.
*
* {@link TestSemanticAnalyzer} already pins AST substitution on this path.
* This class runs the query (Tez mini cluster via {@link HiveTestEnvSetup})
* and asserts the result is 3, 2, 1 rather than the pre-fix unsorted order.
*/
public class TestOrderByOrdinalCboDeclineRows {

private static final String TABLE = "hive30037_ob_t";
private static final String QUERY =
"select d from " + TABLE + " tablesample (5 rows) s order by 1 desc";

@ClassRule
public static HiveTestEnvSetup envSetup = new HiveTestEnvSetup();

@Rule
public TestRule methodRule = envSetup.getMethodRule();

@BeforeClass
public static void beforeClass() throws Exception {
IDriver driver = createDriver();
dropTables(driver);
driver.run("create table " + TABLE + " (d int)");
driver.run("insert into " + TABLE + " values (1), (2), (3)");
}

@AfterClass
public static void afterClass() throws Exception {
IDriver driver = createDriver();
dropTables(driver);
}

public static void dropTables(IDriver driver) throws Exception {
driver.run("drop table if exists " + TABLE);
}

@Test
public void testTablesampleOrderByOrdinalDescReturns321() throws Exception {
IDriver driver = createDriver();
driver.run(QUERY);
List<String> rows = fetchRows(driver);
assertEquals("TABLESAMPLE + ORDER BY 1 DESC should return 3,2,1; fetchTask="
+ driver.getFetchTask(), Arrays.asList("3", "2", "1"), rows);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private static List<String> fetchRows(IDriver driver) throws Exception {
driver.setMaxRows(100);
List rows = new ArrayList();
if (driver.getFetchTask() != null) {
driver.getFetchTask().setMaxRows(100);
driver.getFetchTask().fetch(rows);
} else {
List batch = new ArrayList();
while (driver.getResults(batch)) {
rows.addAll(batch);
batch.clear();
}
}
List<String> out = new ArrayList<>();
for (Object row : rows) {
out.add(String.valueOf(row));
}
return out;
}

private static IDriver createDriver() {
HiveConf conf = envSetup.getTestCtx().hiveConf;
conf.setVar(HiveConf.ConfVars.HIVE_AUTHORIZATION_MANAGER,
"org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdHiveAuthorizerFactory");
HiveConf.setBoolVar(conf, HiveConf.ConfVars.HIVE_SUPPORT_CONCURRENCY, false);
HiveConf.setBoolVar(conf, HiveConf.ConfVars.HIVE_CBO_ENABLED, true);
HiveConf.setVar(conf, HiveConf.ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
SessionState.start(conf);
return DriverFactory.newDriver(conf);
}
}
128 changes: 128 additions & 0 deletions ql/src/test/org/apache/hadoop/hive/ql/parse/TestSemanticAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,134 @@ public void testCboAmbiguityChecksAreReturnPathInvariant() throws Exception {
}
}

// ==== HIVE-30037: ORDER BY ordinals when CBO declines the statement ====

@Test
public void testOrderByOrdinalResolvedWhenCboDeclinesTablesample() throws Exception {
assertOrderByOrdinalResolvedOnCboDecline(
"select key from table1 tablesample (2 rows) order by 1 desc");
}

@Test
public void testOrderByOrdinalResolvedWhenCboDeclinesSortByLimitSubquery() throws Exception {
assertOrderByOrdinalResolvedOnCboDecline(
"select key from (select key from table1 sort by key limit 5) s order by 1 desc");
}

@Test
public void testOrderByOrdinalStillSortedWhenCboHandlesStatement() throws Exception {
AnalyzedQuery analyzed = analyzeQueryWithCbo(
"select key from table1 order by 1 desc");
assertTrue(analyzed.analyzer.getCboInfo(),
analyzed.analyzer.getCboInfo() != null
&& analyzed.analyzer.getCboInfo().contains("Plan optimized by CBO"));
}

@Test
public void testCboDeclinePreservesOutOfRangeGroupByConstant() throws Exception {
assertGroupByConstantSurvivesCboDecline("100");
}

@Test
public void testCboDeclinePreservesInRangeGroupByConstant() throws Exception {
assertGroupByConstantSurvivesCboDecline("2");
}

private void assertGroupByConstantSurvivesCboDecline(String constant) throws Exception {
boolean original = conf.getBoolVar(HiveConf.ConfVars.HIVE_GROUPBY_POSITION_ALIAS);
conf.setBoolVar(HiveConf.ConfVars.HIVE_GROUPBY_POSITION_ALIAS, true);
try {
AnalyzedQuery analyzed = analyzeQueryWithCbo("select " + constant
+ " as k, count(*) as n from table1 tablesample (2 rows) group by 1 order by 1");
assertTrue(analyzed.analyzer.getCboInfo(),
analyzed.analyzer.getCboInfo() != null
&& analyzed.analyzer.getCboInfo().contains("not optimized by CBO"));
ASTNode groupBy = findFirstNodeOfType(analyzed.ast, HiveParser.TOK_GROUPBY);
assertNotNull(groupBy);
assertEquals(HiveParser.Number, ((ASTNode) groupBy.getChild(0)).getType());
assertEquals(constant, groupBy.getChild(0).getText());
ASTNode orderByRef = findFirstOrderByRef(analyzed.ast);
assertNotNull(orderByRef);
assertEquals(HiveParser.Number, orderByRef.getType());
assertEquals(constant, orderByRef.getText());
} finally {
conf.setBoolVar(HiveConf.ConfVars.HIVE_GROUPBY_POSITION_ALIAS, original);
}
}

private static ASTNode findFirstNodeOfType(ASTNode node, int type) {
if (node.getType() == type) {
return node;
}
for (int i = 0; i < node.getChildCount(); i++) {
ASTNode found = findFirstNodeOfType((ASTNode) node.getChild(i), type);
if (found != null) {
return found;
}
}
return null;
}

private void assertOrderByOrdinalResolvedOnCboDecline(String query) throws Exception {
AnalyzedQuery analyzed = analyzeQueryWithCbo(query);
assertTrue(analyzed.analyzer.getCboInfo(),
analyzed.analyzer.getCboInfo() != null
&& analyzed.analyzer.getCboInfo().contains("not optimized by CBO"));
ASTNode orderByRef = findFirstOrderByRef(analyzed.ast);
assertNotNull("expected an ORDER BY expression in " + query, orderByRef);
assertEquals("ORDER BY ordinal should be substituted with the select expression, got text="
+ orderByRef.getText() + " cboInfo=" + analyzed.analyzer.getCboInfo(),
HiveParser.TOK_TABLE_OR_COL, orderByRef.getType());
}

private AnalyzedQuery analyzeQueryWithCbo(String query) throws Exception {
HiveConf cboConf = new HiveConf(conf);
cboConf.setBoolVar(HiveConf.ConfVars.HIVE_CBO_ENABLED, true);
cboConf.setVar(HiveConf.ConfVars.HIVE_FETCH_TASK_CONVERSION, "none");
SessionState.start(cboConf);
Context ctx = new Context(cboConf);
ASTNode astNode = ParseUtils.parse(query, ctx);
QueryState queryState = new QueryState.Builder().withHiveConf(cboConf).build();
BaseSemanticAnalyzer analyzer = SemanticAnalyzerFactory.get(queryState, astNode);
analyzer.initCtx(ctx);
try {
analyzer.analyze(astNode, ctx);
} finally {
analyzer.endAnalysis(astNode);
}
return new AnalyzedQuery(analyzer, astNode);
}

private static ASTNode findFirstOrderByRef(ASTNode node) {
if (node.getType() == HiveParser.TOK_ORDERBY && node.getChildCount() > 0
&& node.getChild(0).getChildCount() > 0) {
ASTNode colNode = (ASTNode) node.getChild(0).getChild(0);
if (colNode != null && colNode.getChildCount() > 0) {
return (ASTNode) colNode.getChild(0);
}
}
if (node.getChildren() == null) {
return null;
}
for (int i = 0; i < node.getChildCount(); i++) {
ASTNode found = findFirstOrderByRef((ASTNode) node.getChild(i));
if (found != null) {
return found;
}
}
return null;
}

private static final class AnalyzedQuery {
final BaseSemanticAnalyzer analyzer;
final ASTNode ast;

AnalyzedQuery(BaseSemanticAnalyzer analyzer, ASTNode ast) {
this.analyzer = analyzer;
this.ast = ast;
}
}

private static ColumnInfo stringCol(String internalName, String tab, String alias, boolean markedAmbiguous) {
ColumnInfo colInfo = new ColumnInfo(internalName, TypeInfoFactory.stringTypeInfo, tab, false);
colInfo.setAlias(alias);
Expand Down
12 changes: 12 additions & 0 deletions ql/src/test/queries/clientpositive/order_by_pos_cbo_decline.q
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- HIVE-30037: CBO declines TABLESAMPLE; ORDER BY 1 DESC must return 3, 2, 1.
-- MiniLlapLocal: mvn test -pl itests/qtest -Dtest=TestMiniLlapLocalCliDriver -Dqfile=order_by_pos_cbo_decline.q
-- Overwrite golden: add -Dtest.output.overwrite=true

set hive.fetch.task.conversion=none;
set hive.cbo.enable=true;

create table hive30037_ob_t (d int);

insert into hive30037_ob_t values (1), (2), (3);

select d from hive30037_ob_t tablesample (5 rows) s order by 1 desc;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
PREHOOK: query: create table hive30037_ob_t (d int)
PREHOOK: type: CREATETABLE
PREHOOK: Output: database:default
PREHOOK: Output: default@hive30037_ob_t
POSTHOOK: query: create table hive30037_ob_t (d int)
POSTHOOK: type: CREATETABLE
POSTHOOK: Output: database:default
POSTHOOK: Output: default@hive30037_ob_t
PREHOOK: query: insert into hive30037_ob_t values (1), (2), (3)
PREHOOK: type: QUERY
PREHOOK: Input: _dummy_database@_dummy_table
PREHOOK: Output: default@hive30037_ob_t
POSTHOOK: query: insert into hive30037_ob_t values (1), (2), (3)
POSTHOOK: type: QUERY
POSTHOOK: Input: _dummy_database@_dummy_table
POSTHOOK: Output: default@hive30037_ob_t
POSTHOOK: Lineage: hive30037_ob_t.d SCRIPT []
PREHOOK: query: select d from hive30037_ob_t tablesample (5 rows) s order by 1 desc
PREHOOK: type: QUERY
PREHOOK: Input: default@hive30037_ob_t
#### A masked pattern was here ####
POSTHOOK: query: select d from hive30037_ob_t tablesample (5 rows) s order by 1 desc
POSTHOOK: type: QUERY
POSTHOOK: Input: default@hive30037_ob_t
#### A masked pattern was here ####
3
2
1