com.hazelcast.org.apache.calcite.sql.validate.AggChecker Maven / Gradle / Ivy
/*
* 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 com.hazelcast.org.apache.calcite.sql.validate;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlIdentifier;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlNodeList;
import com.hazelcast.org.apache.calcite.sql.SqlSelect;
import com.hazelcast.org.apache.calcite.sql.SqlWindow;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.sql.util.SqlBasicVisitor;
import com.hazelcast.org.apache.calcite.util.Litmus;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import static com.hazelcast.org.apache.calcite.sql.validate.SqlNonNullableAccessors.getSelectList;
import static com.hazelcast.org.apache.calcite.util.Static.RESOURCE;
import static java.util.Objects.requireNonNull;
/**
* Visitor which throws an exception if any component of the expression is not a
* group expression.
*/
class AggChecker extends SqlBasicVisitor {
//~ Instance fields --------------------------------------------------------
private final Deque scopes = new ArrayDeque<>();
private final List extraExprs;
private final List groupExprs;
private boolean distinct;
private SqlValidatorImpl validator;
//~ Constructors -----------------------------------------------------------
/**
* Creates an AggChecker.
*
* @param validator Validator
* @param scope Scope
* @param groupExprs Expressions in GROUP BY (or SELECT DISTINCT) clause,
* that are therefore available
* @param distinct Whether aggregation checking is because of a SELECT
* DISTINCT clause
*/
AggChecker(
SqlValidatorImpl validator,
AggregatingScope scope,
List extraExprs,
List groupExprs,
boolean distinct) {
this.validator = validator;
this.extraExprs = extraExprs;
this.groupExprs = groupExprs;
this.distinct = distinct;
this.scopes.push(scope);
}
//~ Methods ----------------------------------------------------------------
boolean isGroupExpr(SqlNode expr) {
for (SqlNode groupExpr : groupExprs) {
if (groupExpr.equalsDeep(expr, Litmus.IGNORE)) {
return true;
}
}
for (SqlNode extraExpr : extraExprs) {
if (extraExpr.equalsDeep(expr, Litmus.IGNORE)) {
return true;
}
}
return false;
}
@Override public Void visit(SqlIdentifier id) {
if (isGroupExpr(id) || id.isStar()) {
// Star may validly occur in "SELECT COUNT(*) OVER w"
return null;
}
// Is it a call to a parentheses-free function?
final SqlCall call = validator.makeNullaryCall(id);
if (call != null) {
return call.accept(this);
}
// Didn't find the identifier in the group-by list as is, now find
// it fully-qualified.
// TODO: It would be better if we always compared fully-qualified
// to fully-qualified.
final SqlQualified fqId = scopes.getFirst().fullyQualify(id);
if (isGroupExpr(fqId.identifier)) {
return null;
}
SqlNode originalExpr = validator.getOriginal(id);
final String exprString = originalExpr.toString();
throw validator.newValidationError(originalExpr,
distinct
? RESOURCE.notSelectDistinctExpr(exprString)
: RESOURCE.notGroupExpr(exprString));
}
@Override public Void visit(SqlCall call) {
final SqlValidatorScope scope = scopes.peek();
if (call.getOperator().isAggregator()) {
if (distinct) {
if (scope instanceof AggregatingSelectScope) {
SqlNodeList selectList =
getSelectList((SqlSelect) scope.getNode());
// Check if this aggregation function is just an element in the select
for (SqlNode sqlNode : selectList) {
if (sqlNode.getKind() == SqlKind.AS) {
sqlNode = ((SqlCall) sqlNode).operand(0);
}
if (validator.expand(sqlNode, scope)
.equalsDeep(call, Litmus.IGNORE)) {
return null;
}
}
}
// Cannot use agg fun in ORDER BY clause if have SELECT DISTINCT.
SqlNode originalExpr = validator.getOriginal(call);
final String exprString = originalExpr.toString();
throw validator.newValidationError(call,
RESOURCE.notSelectDistinctExpr(exprString));
}
// For example, 'sum(sal)' in 'SELECT sum(sal) FROM emp GROUP
// BY deptno'
return null;
}
switch (call.getKind()) {
case FILTER:
case WITHIN_GROUP:
case RESPECT_NULLS:
case IGNORE_NULLS:
case WITHIN_DISTINCT:
call.operand(0).accept(this);
return null;
default:
break;
}
// Visit the operand in window function
if (call.getKind() == SqlKind.OVER) {
for (SqlNode operand : call.operand(0).getOperandList()) {
operand.accept(this);
}
// Check the OVER clause
final SqlNode over = call.operand(1);
if (over instanceof SqlCall) {
over.accept(this);
} else if (over instanceof SqlIdentifier) {
// Check the corresponding SqlWindow in WINDOW clause
final SqlWindow window =
requireNonNull(scope, () -> "scope for " + call)
.lookupWindow(((SqlIdentifier) over).getSimple());
requireNonNull(window, () -> "window for " + call);
window.getPartitionList().accept(this);
window.getOrderList().accept(this);
}
}
if (isGroupExpr(call)) {
// This call matches an expression in the GROUP BY clause.
return null;
}
final SqlCall groupCall =
SqlStdOperatorTable.convertAuxiliaryToGroupCall(call);
if (groupCall != null) {
if (isGroupExpr(groupCall)) {
// This call is an auxiliary function that matches a group call in the
// GROUP BY clause.
//
// For example TUMBLE_START is an auxiliary of the TUMBLE
// group function, and
// TUMBLE_START(rowtime, INTERVAL '1' HOUR)
// matches
// TUMBLE(rowtime, INTERVAL '1' HOUR')
return null;
}
throw validator.newValidationError(groupCall,
RESOURCE.auxiliaryWithoutMatchingGroupCall(
call.getOperator().getName(), groupCall.getOperator().getName()));
}
if (call.isA(SqlKind.QUERY)) {
// Allow queries for now, even though they may contain
// references to forbidden columns.
return null;
}
// Switch to new scope.
SqlValidatorScope newScope = requireNonNull(scope, () -> "scope for " + call)
.getOperandScope(call);
scopes.push(newScope);
// Visit the operands (only expressions).
call.getOperator()
.acceptCall(this, call, true, ArgHandlerImpl.instance());
// Restore scope.
scopes.pop();
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy