Skip to content
Open
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
@@ -0,0 +1,41 @@
/**
* Copyright 2022-2026 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.hive.hive2rel;

import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;


public class CoralRexBuilder extends RexBuilder {
/**
* Creates a RexBuilder.
*
* @param typeFactory Type factory
*/
public CoralRexBuilder(RelDataTypeFactory typeFactory) {
super(typeFactory);
}

/**
* CoralRexBuilder overrides this method to make field access case-insensitively,
* because in Hive 1.1, if the base table `t` contains non-lowercase struct field like `s struct(A:string)`,
* the schema of the view `v` based on the base table would become `s struct(a:string)`,
* translation for SQL `SELECT * FROM v WHERE v.s.A='xxx'` will fail with the following exception
* if caseSensitive=true, given Calcite would convert `v.s.A` to `v.s.a` to be aligned with the
* schema of view `v` during the validation phase:
*
* java.lang.AssertionError: Type 'RecordType(VARCHAR(2147483647) A)' has no field 'a'
*
* Setting caseSensitive=false would not cause regression because Calcite doesn't allow
* two struct fields which only differ in casing like `struct(a:string,A:string)`, check
* org.apache.calcite.sql.validate.DelegatingScope.fullyQualify for more info
*/
@Override
public RexNode makeFieldAccess(RexNode expr, String fieldName, boolean caseSensitive) {
return super.makeFieldAccess(expr, fieldName, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright 2021-2026 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.hive.hive2rel;

import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlDelegatingConformance;


public class CoralSqlConformance extends SqlDelegatingConformance {

public static final SqlConformance CORAL_SQL = new CoralSqlConformance();

/**
* @deprecated Use {@link #CORAL_SQL} instead.
*/
@Deprecated
public static final SqlConformance HIVE_SQL = CORAL_SQL;

protected CoralSqlConformance() {
super(SqlConformanceEnum.PRAGMATIC_2003);
}

@Override
public boolean allowNiladicParentheses() {
return true;
}

@Override
public boolean isSortByAlias() {
return true;
}

@Override
public boolean isHavingAlias() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright 2017-2026 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.hive.hive2rel;

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

import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.plan.Convention;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.prepare.Prepare;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.core.Uncollect;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlUnnestOperator;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql2rel.SqlRexConvertletTable;
import org.apache.calcite.sql2rel.SqlToRelConverter;

import com.linkedin.coral.common.HiveUncollect;
import com.linkedin.coral.common.functions.CoralSqlUnnestOperator;


/**
* Class to convert SQL to Calcite RelNode. This class
* specializes the functionality provided by {@link SqlToRelConverter}.
*/
class CoralSqlToRelConverter extends SqlToRelConverter {

CoralSqlToRelConverter(RelOptTable.ViewExpander viewExpander, SqlValidator validator,
Prepare.CatalogReader catalogReader, RelOptCluster cluster, SqlRexConvertletTable convertletTable,
Config config) {
super(viewExpander, validator, catalogReader, cluster, convertletTable, config);
}

// This differs from base class in two ways:
// 1. This does not validate the type of converted rel rowType with that of validated node. This is because
// hive is lax in enforcing view schemas.
// 2. This skips calling some methods because (1) those are private, and (2) not required for our usecase
public RelRoot convertQuery(SqlNode query, final boolean needsValidation, final boolean top) {
if (needsValidation) {
query = validator.validate(query);
}

RelMetadataQuery.THREAD_PROVIDERS.set(JaninoRelMetadataProvider.of(cluster.getMetadataProvider()));
RelNode result = convertQueryRecursive(query, top, null).rel;
RelCollation collation = RelCollations.EMPTY;

if (SQL2REL_LOGGER.isDebugEnabled()) {
SQL2REL_LOGGER.debug(RelOptUtil.dumpPlan("Plan after converting SqlNode to RelNode", result,
SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES));
}

final RelDataType validatedRowType = validator.getValidatedNodeType(query);
return RelRoot.of(result, validatedRowType, query.getKind()).withCollation(collation);
}

@Override
protected void convertFrom(Blackboard bb, SqlNode from) {
if (from == null) {
super.convertFrom(bb, from);
return;
}
switch (from.getKind()) {
case UNNEST:
convertUnnestFrom(bb, from);
break;
default:
super.convertFrom(bb, from);
break;
}
}

private void convertUnnestFrom(Blackboard bb, SqlNode from) {
final SqlCall call;
call = (SqlCall) from;
final List<SqlNode> nodes = call.getOperandList();
final SqlUnnestOperator operator = (SqlUnnestOperator) call.getOperator();
// FIXME: base class calls 'replaceSubqueries for operands here but that's a private
// method. This is not an issue for our usecases with hive but we may need handling in future
final List<RexNode> exprs = new ArrayList<>();
final List<String> fieldNames = new ArrayList<>();
for (Ord<SqlNode> node : Ord.zip(nodes)) {
exprs.add(bb.convertExpression(node.e));
// In Hive, "LATERAL VIEW EXPLODE(arr) t" is equivalent to "LATERAL VIEW EXPLODE(arr) t AS col".
// Use the default column name "col" if not specified.
fieldNames.add(node.e.getKind() == SqlKind.AS ? validator.deriveAlias(node.e, node.i)
: CoralSqlUnnestOperator.ARRAY_ELEMENT_COLUMN_NAME);
}
final RelNode input = RelOptUtil.createProject((null != bb.root) ? bb.root : LogicalValues.createOneRow(cluster),
exprs, fieldNames, true);
Uncollect uncollect =
new HiveUncollect(cluster, cluster.traitSetOf(Convention.NONE), input, operator.withOrdinality);
bb.setRoot(uncollect, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2017-2026 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.hive.hive2rel;

import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.config.NullCollation;
import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlInsert;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql.validate.SqlValidatorScope;

import com.linkedin.coral.common.functions.FunctionFieldReferenceOperator;


public class CoralSqlValidator extends SqlValidatorImpl {

public CoralSqlValidator(SqlOperatorTable opTab, CalciteCatalogReader catalogReader, JavaTypeFactory typeFactory,
SqlConformance conformance) {
super(opTab, catalogReader, typeFactory, conformance);
setDefaultNullCollation(NullCollation.LOW);
}

@Override
protected RelDataType getLogicalSourceRowType(RelDataType sourceRowType, SqlInsert insert) {
final RelDataType superType = super.getLogicalSourceRowType(sourceRowType, insert);
return ((JavaTypeFactory) typeFactory).toSql(superType);
}

@Override
protected RelDataType getLogicalTargetRowType(RelDataType targetRowType, SqlInsert insert) {
final RelDataType superType = super.getLogicalTargetRowType(targetRowType, insert);
return ((JavaTypeFactory) typeFactory).toSql(superType);
}

@Override
protected void inferUnknownTypes(RelDataType inferredType, SqlValidatorScope scope, SqlNode node) {
if (SqlUtil.isNullLiteral(node, false)) {
setValidatedNodeType(node, typeFactory.createSqlType(SqlTypeName.NULL));
return;
}
super.inferUnknownTypes(inferredType, scope, node);
}

@Override
public SqlNode expand(SqlNode expr, SqlValidatorScope scope) {
if (expr instanceof SqlBasicCall
&& ((SqlBasicCall) expr).getOperator().equals(FunctionFieldReferenceOperator.DOT)) {
SqlBasicCall dotCall = (SqlBasicCall) expr;
if (dotCall.operand(0) instanceof SqlBasicCall) {
return expr;
}
}
return super.expand(expr, scope);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2017-2026 LinkedIn Corporation. All rights reserved.
* Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.coral.hive.hive2rel;

import java.util.List;

import javax.annotation.Nonnull;

import com.google.common.base.Preconditions;

import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.util.Util;

import com.linkedin.coral.common.FuzzyUnionSqlRewriter;


/**
* Class that implements {@link org.apache.calcite.plan.RelOptTable.ViewExpander}
* interface to support expansion of views to relational algebra.
*/
public class CoralViewExpander implements RelOptTable.ViewExpander {

private final HiveToRelConverter hiveToRelConverter;
/**
* Instantiates a new view expander.
*
* @param hiveToRelConverter Hive to Rel converter
*/
public CoralViewExpander(@Nonnull HiveToRelConverter hiveToRelConverter) {
this.hiveToRelConverter = hiveToRelConverter;
}

@Override
public RelRoot expandView(RelDataType rowType, String queryString, List<String> schemaPath, List<String> viewPath) {
Preconditions.checkNotNull(viewPath);
Preconditions.checkState(!viewPath.isEmpty());

String dbName = Util.last(schemaPath);
String tableName = viewPath.get(0);

SqlNode sqlNode = hiveToRelConverter.processView(dbName, tableName)
.accept(new FuzzyUnionSqlRewriter(tableName, hiveToRelConverter));
return hiveToRelConverter.getSqlToRelConverter().convertQuery(sqlNode, true, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
package com.linkedin.coral.hive.hive2rel;

import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;


public class HiveRexBuilder extends RexBuilder {
/**
* @deprecated Use {@link CoralRexBuilder} instead.
*/
@Deprecated
public class HiveRexBuilder extends CoralRexBuilder {
/**
* Creates a RexBuilder.
*
Expand All @@ -19,23 +21,4 @@ public class HiveRexBuilder extends RexBuilder {
public HiveRexBuilder(RelDataTypeFactory typeFactory) {
super(typeFactory);
}

/**
* HiveRexBuilder overrides this method to make field access case-insensitively,
* because in Hive 1.1, if the base table `t` contains non-lowercase struct field like `s struct(A:string)`,
* the schema of the view `v` based on the base table would become `s struct(a:string)`,
* translation for SQL `SELECT * FROM v WHERE v.s.A='xxx'` will fail with the following exception
* if caseSensitive=true, given Calcite would convert `v.s.A` to `v.s.a` to be aligned with the
* schema of view `v` during the validation phase:
*
* java.lang.AssertionError: Type 'RecordType(VARCHAR(2147483647) A)' has no field 'a'
*
* Setting caseSensitive=false would not cause regression because Calcite doesn't allow
* two struct fields which only differ in casing like `struct(a:string,A:string)`, check
* org.apache.calcite.sql.validate.DelegatingScope.fullyQualify for more info
*/
@Override
public RexNode makeFieldAccess(RexNode expr, String fieldName, boolean caseSensitive) {
return super.makeFieldAccess(expr, fieldName, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,17 @@
package com.linkedin.coral.hive.hive2rel;

import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlDelegatingConformance;


public class HiveSqlConformance extends SqlDelegatingConformance {
/**
* @deprecated Use {@link CoralSqlConformance} instead.
*/
@Deprecated
public class HiveSqlConformance extends CoralSqlConformance {

public static final SqlConformance HIVE_SQL = new HiveSqlConformance();
public static final SqlConformance HIVE_SQL = CoralSqlConformance.CORAL_SQL;

private HiveSqlConformance() {
super(SqlConformanceEnum.PRAGMATIC_2003);
}

@Override
public boolean allowNiladicParentheses() {
return true;
}

@Override
public boolean isSortByAlias() {
return true;
}

@Override
public boolean isHavingAlias() {
return true;
super();
}
}
Loading
Loading