| // 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.impala.analysis; |
| |
| import java.util.List; |
| |
| import org.apache.impala.catalog.BuiltinsDb; |
| import org.apache.impala.catalog.Db; |
| import org.apache.impala.common.AnalysisException; |
| import org.apache.impala.thrift.TFunctionName; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| |
| /** |
| * Class to represent a function name. Function names are specified as |
| * db.function_name. |
| */ |
| public class FunctionName { |
| // Only set for parsed function names. |
| private final List<String> fnNamePath_; |
| |
| // Set/validated during analysis. |
| private String db_; |
| private String fn_; |
| private boolean isBuiltin_ = false; |
| private boolean isAnalyzed_ = false; |
| |
| /** |
| * C'tor for parsed function names. The function names could be invalid. The validity |
| * is checked during analysis. |
| */ |
| public FunctionName(List<String> fnNamePath) { |
| fnNamePath_ = fnNamePath; |
| } |
| |
| public FunctionName(String dbName, String fn) { |
| db_ = (dbName != null) ? dbName.toLowerCase() : null; |
| fn_ = fn.toLowerCase(); |
| fnNamePath_ = null; |
| } |
| |
| public FunctionName(String fn) { |
| this(null, fn); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof FunctionName)) return false; |
| FunctionName o = (FunctionName)obj; |
| if ((db_ == null || o.db_ == null) && (db_ != o.db_)) { |
| if (db_ == null && o.db_ != null) return false; |
| if (db_ != null && o.db_ == null) return false; |
| if (!db_.equalsIgnoreCase(o.db_)) return false; |
| } |
| return fn_.equalsIgnoreCase(o.fn_); |
| } |
| |
| public String getDb() { return db_; } |
| public String getFunction() { return fn_; } |
| public boolean isFullyQualified() { return db_ != null; } |
| public boolean isBuiltin() { return isBuiltin_; } |
| public List<String> getFnNamePath() { return fnNamePath_; } |
| |
| @Override |
| public String toString() { |
| // The fnNamePath_ is not always set. |
| if (!isAnalyzed_ && fnNamePath_ != null) return Joiner.on(".").join(fnNamePath_); |
| if (db_ == null || isBuiltin_) return fn_; |
| return db_ + "." + fn_; |
| } |
| |
| public void analyze(Analyzer analyzer) throws AnalysisException { |
| analyze(analyzer, true); |
| } |
| |
| /** |
| * Path resolution happens as follows. |
| * |
| * Fully-qualified function name: |
| * - Set the database name to the database name specified. |
| * |
| * Non-fully-qualified function name: |
| * - When preferBuiltinsDb is true: |
| * - If the function name specified has the same name as a built-in function, |
| * set the database name to _impala_builtins. |
| * - Else, set the database name to the current session DB name. |
| * - When preferBuiltinsDb is false: set the database name to current session DB name. |
| */ |
| public void analyze(Analyzer analyzer, boolean preferBuiltinsDb) |
| throws AnalysisException { |
| if (isAnalyzed_) return; |
| analyzeFnNamePath(); |
| if (fn_.isEmpty()) throw new AnalysisException("Function name cannot be empty."); |
| for (int i = 0; i < fn_.length(); ++i) { |
| if (!isValidCharacter(fn_.charAt(i))) { |
| throw new AnalysisException( |
| "Function names must be all alphanumeric or underscore. " + |
| "Invalid name: " + fn_); |
| } |
| } |
| if (Character.isDigit(fn_.charAt(0))) { |
| throw new AnalysisException("Function cannot start with a digit: " + fn_); |
| } |
| |
| // Resolve the database for this function. |
| Db builtinDb = BuiltinsDb.getInstance(); |
| if (!isFullyQualified()) { |
| db_ = analyzer.getDefaultDb(); |
| if (preferBuiltinsDb && builtinDb.containsFunction(fn_)) { |
| db_ = BuiltinsDb.NAME; |
| } |
| } |
| Preconditions.checkNotNull(db_); |
| isBuiltin_ = db_.equals(BuiltinsDb.NAME) && |
| builtinDb.containsFunction(fn_); |
| isAnalyzed_ = true; |
| } |
| |
| private void analyzeFnNamePath() throws AnalysisException { |
| if (fnNamePath_ == null) return; |
| if (fnNamePath_.size() > 2 || fnNamePath_.isEmpty()) { |
| throw new AnalysisException( |
| String.format("Invalid function name: '%s'. Expected [dbname].funcname.", |
| Joiner.on(".").join(fnNamePath_))); |
| } else if (fnNamePath_.size() > 1) { |
| db_ = fnNamePath_.get(0).toLowerCase(); |
| fn_ = fnNamePath_.get(1).toLowerCase(); |
| } else { |
| Preconditions.checkState(fnNamePath_.size() == 1); |
| fn_ = fnNamePath_.get(0).toLowerCase(); |
| } |
| } |
| |
| private boolean isValidCharacter(char c) { |
| return Character.isLetterOrDigit(c) || c == '_'; |
| } |
| |
| public TFunctionName toThrift() { |
| TFunctionName name = new TFunctionName(fn_); |
| name.setDb_name(db_); |
| return name; |
| } |
| |
| public static FunctionName fromThrift(TFunctionName fnName) { |
| return new FunctionName(fnName.getDb_name(), fnName.getFunction_name()); |
| } |
| } |