/**
* 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.planner.sql.handlers;
import java.math.BigDecimal;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.tools.ValidationException;
import org.apache.calcite.util.NlsString;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.ops.QueryContext;
import org.apache.drill.exec.physical.PhysicalPlan;
import org.apache.drill.exec.planner.sql.DirectPlan;
import org.apache.drill.exec.server.options.OptionManager;
import org.apache.drill.exec.server.options.OptionValue;
import org.apache.drill.exec.server.options.OptionValue.OptionType;
import org.apache.drill.exec.util.ImpersonationUtil;
import org.apache.drill.exec.work.foreman.ForemanSetupException;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlSetOption;
/**
* Converts a {@link SqlNode} representing "ALTER .. SET option = value" and "ALTER ... RESET ..." statements to a
* {@link PhysicalPlan}. See {@link SqlSetOption}. These statements have side effects i.e. the options within the
* system context or the session context are modified. The resulting {@link DirectPlan} returns to the client a string
* that is the name of the option that was updated.
*/
public class SetOptionHandler extends AbstractSqlHandler {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(SetOptionHandler.class);
private final QueryContext context;
public SetOptionHandler(QueryContext context) {
this.context = context;
}
@Override
public PhysicalPlan getPlan(SqlNode sqlNode) throws ValidationException, ForemanSetupException {
final SqlSetOption option = unwrap(sqlNode, SqlSetOption.class);
final SqlNode value = option.getValue();
if (value != null && !(value instanceof SqlLiteral)) {
throw UserException.validationError()
.message("Drill does not support assigning non-literal values in SET statements.")
.build(logger);
}
final String scope = option.getScope();
final OptionValue.OptionType type;
if (scope == null) { // No scope mentioned assumed SESSION
type = OptionType.SESSION;
} else {
switch (scope.toLowerCase()) {
case "session":
type = OptionType.SESSION;
break;
case "system":
type = OptionType.SYSTEM;
break;
default:
throw UserException.validationError()
.message("Invalid OPTION scope %s. Scope must be SESSION or SYSTEM.", scope)
.build(logger);
}
}
final OptionManager options = context.getOptions();
if (type == OptionType.SYSTEM) {
// If the user authentication is enabled, make sure the user who is trying to change the system option has
// administrative privileges.
if (context.isUserAuthenticationEnabled() &&
!ImpersonationUtil.hasAdminPrivileges(
context.getQueryUserName(),
options.getOption(ExecConstants.ADMIN_USERS_VALIDATOR),
options.getOption(ExecConstants.ADMIN_USER_GROUPS_VALIDATOR))) {
throw UserException.permissionError()
.message("Not authorized to change SYSTEM options.")
.build(logger);
}
}
// Currently, we convert multi-part identifier to a string.
final String name = option.getName().toString();
if (value != null) { // SET option
final OptionValue optionValue = createOptionValue(name, type, (SqlLiteral) value);
options.setOption(optionValue);
} else { // RESET option
if ("ALL".equalsIgnoreCase(name)) {
options.deleteAllOptions(type);
} else {
options.deleteOption(name, type);
}
}
return DirectPlan.createDirectPlan(context, true, String.format("%s updated.", name));
}
private static OptionValue createOptionValue(final String name, final OptionValue.OptionType type,
final SqlLiteral literal) {
final Object object = literal.getValue();
final SqlTypeName typeName = literal.getTypeName();
switch (typeName) {
case DECIMAL: {
final BigDecimal bigDecimal = (BigDecimal) object;
if (bigDecimal.scale() == 0) {
return OptionValue.createLong(type, name, bigDecimal.longValue());
} else {
return OptionValue.createDouble(type, name, bigDecimal.doubleValue());
}
}
case DOUBLE:
case FLOAT:
return OptionValue.createDouble(type, name, ((BigDecimal) object).doubleValue());
case SMALLINT:
case TINYINT:
case BIGINT:
case INTEGER:
return OptionValue.createLong(type, name, ((BigDecimal) object).longValue());
case VARBINARY:
case VARCHAR:
case CHAR:
return OptionValue.createString(type, name, ((NlsString) object).getValue());
case BOOLEAN:
return OptionValue.createBoolean(type, name, (Boolean) object);
default:
throw UserException.validationError()
.message("Drill doesn't support assigning literals of type %s in SET statements.", typeName)
.build(logger);
}
}
}