| /* |
| * 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.calcite.sql; |
| |
| import org.apache.calcite.rel.type.DynamicRecordType; |
| import org.apache.calcite.sql.parser.SqlParserPos; |
| import org.apache.calcite.sql.util.SqlVisitor; |
| import org.apache.calcite.sql.validate.SqlMonotonicity; |
| import org.apache.calcite.sql.validate.SqlQualified; |
| import org.apache.calcite.sql.validate.SqlValidator; |
| import org.apache.calcite.sql.validate.SqlValidatorScope; |
| import org.apache.calcite.util.Litmus; |
| import org.apache.calcite.util.Util; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.dataflow.qual.Pure; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| |
| /** |
| * A <code>SqlIdentifier</code> is an identifier, possibly compound. |
| */ |
| public class SqlIdentifier extends SqlNode { |
| /** An identifier for star, "*". |
| * |
| * @see SqlNodeList#SINGLETON_STAR */ |
| public static final SqlIdentifier STAR = star(SqlParserPos.ZERO); |
| |
| //~ Instance fields -------------------------------------------------------- |
| |
| /** |
| * Array of the components of this compound identifier. |
| * |
| * <p>The empty string represents the wildcard "*", |
| * to distinguish it from a real "*" (presumably specified using quotes). |
| * |
| * <p>It's convenient to have this member public, and it's convenient to |
| * have this member not-final, but it's a shame it's public and not-final. |
| * If you assign to this member, please use |
| * {@link #setNames(java.util.List, java.util.List)}. |
| * And yes, we'd like to make identifiers immutable one day. |
| */ |
| public ImmutableList<String> names; |
| |
| /** |
| * This identifier's collation (if any). |
| */ |
| final @Nullable SqlCollation collation; |
| |
| /** |
| * A list of the positions of the components of compound identifiers. |
| */ |
| protected @Nullable ImmutableList<SqlParserPos> componentPositions; |
| |
| //~ Constructors ----------------------------------------------------------- |
| |
| /** |
| * Creates a compound identifier, for example <code>foo.bar</code>. |
| * |
| * @param names Parts of the identifier, length ≥ 1 |
| */ |
| public SqlIdentifier( |
| List<String> names, |
| @Nullable SqlCollation collation, |
| SqlParserPos pos, |
| @Nullable List<SqlParserPos> componentPositions) { |
| super(pos); |
| this.names = ImmutableList.copyOf(names); |
| this.collation = collation; |
| this.componentPositions = |
| componentPositions == null ? null |
| : ImmutableList.copyOf(componentPositions); |
| for (String name : names) { |
| assert name != null; |
| } |
| } |
| |
| public SqlIdentifier(List<String> names, SqlParserPos pos) { |
| this(names, null, pos, null); |
| } |
| |
| /** |
| * Creates a simple identifier, for example <code>foo</code>, with a |
| * collation. |
| */ |
| public SqlIdentifier( |
| String name, |
| @Nullable SqlCollation collation, |
| SqlParserPos pos) { |
| this(ImmutableList.of(name), collation, pos, null); |
| } |
| |
| /** |
| * Creates a simple identifier, for example <code>foo</code>. |
| */ |
| public SqlIdentifier( |
| String name, |
| SqlParserPos pos) { |
| this(ImmutableList.of(name), null, pos, null); |
| } |
| |
| /** Creates an identifier that is a singleton wildcard star. */ |
| public static SqlIdentifier star(SqlParserPos pos) { |
| return star(ImmutableList.of(""), pos, ImmutableList.of(pos)); |
| } |
| |
| /** Creates an identifier that ends in a wildcard star. */ |
| public static SqlIdentifier star(List<String> names, SqlParserPos pos, |
| List<SqlParserPos> componentPositions) { |
| return new SqlIdentifier( |
| Util.transform(names, s -> s.equals("*") ? "" : s), null, pos, |
| componentPositions); |
| } |
| |
| //~ Methods ---------------------------------------------------------------- |
| |
| @Override public SqlKind getKind() { |
| return SqlKind.IDENTIFIER; |
| } |
| |
| @Override public SqlNode clone(SqlParserPos pos) { |
| return new SqlIdentifier(names, collation, pos, componentPositions); |
| } |
| |
| @Override public String toString() { |
| return getString(names); |
| } |
| |
| /** Converts a list of strings to a qualified identifier. */ |
| public static String getString(List<String> names) { |
| return Util.sepList(toStar(names), "."); |
| } |
| |
| /** Converts empty strings in a list of names to stars. */ |
| public static List<String> toStar(List<String> names) { |
| return Util.transform(names, s -> s.equals("") ? "*" : s); |
| } |
| |
| /** |
| * Modifies the components of this identifier and their positions. |
| * |
| * @param names Names of components |
| * @param poses Positions of components |
| */ |
| public void setNames(List<String> names, @Nullable List<SqlParserPos> poses) { |
| this.names = ImmutableList.copyOf(names); |
| this.componentPositions = |
| poses == null ? null |
| : ImmutableList.copyOf(poses); |
| } |
| |
| /** Returns an identifier that is the same as this except one modified name. |
| * Does not modify this identifier. */ |
| public SqlIdentifier setName(int i, String name) { |
| if (!names.get(i).equals(name)) { |
| String[] nameArray = names.toArray(new String[0]); |
| nameArray[i] = name; |
| return new SqlIdentifier(ImmutableList.copyOf(nameArray), collation, pos, |
| componentPositions); |
| } else { |
| return this; |
| } |
| } |
| |
| /** Returns an identifier that is the same as this except with a component |
| * added at a given position. Does not modify this identifier. */ |
| public SqlIdentifier add(int i, String name, SqlParserPos pos) { |
| final List<String> names2 = new ArrayList<>(names); |
| names2.add(i, name); |
| final List<SqlParserPos> pos2; |
| if (componentPositions == null) { |
| pos2 = null; |
| } else { |
| pos2 = new ArrayList<>(componentPositions); |
| pos2.add(i, pos); |
| } |
| return new SqlIdentifier(names2, collation, pos, pos2); |
| } |
| |
| /** |
| * Returns the position of the <code>i</code>th component of a compound |
| * identifier, or the position of the whole identifier if that information |
| * is not present. |
| * |
| * @param i Ordinal of component. |
| * @return Position of i'th component |
| */ |
| public SqlParserPos getComponentParserPosition(int i) { |
| assert (i >= 0) && (i < names.size()); |
| return (componentPositions == null) ? getParserPosition() |
| : componentPositions.get(i); |
| } |
| |
| /** |
| * Copies names and components from another identifier. Does not modify the |
| * cross-component parser position. |
| * |
| * @param other identifier from which to copy |
| */ |
| public void assignNamesFrom(SqlIdentifier other) { |
| setNames(other.names, other.componentPositions); |
| } |
| |
| /** |
| * Creates an identifier which contains only the <code>ordinal</code>th |
| * component of this compound identifier. It will have the correct |
| * {@link SqlParserPos}, provided that detailed position information is |
| * available. |
| */ |
| public SqlIdentifier getComponent(int ordinal) { |
| return getComponent(ordinal, ordinal + 1); |
| } |
| |
| public SqlIdentifier getComponent(int from, int to) { |
| final SqlParserPos pos; |
| final ImmutableList<SqlParserPos> pos2; |
| if (componentPositions == null) { |
| pos2 = null; |
| pos = this.pos; |
| } else { |
| pos2 = componentPositions.subList(from, to); |
| pos = SqlParserPos.sum(pos2); |
| } |
| return new SqlIdentifier(names.subList(from, to), collation, pos, pos2); |
| } |
| |
| /** |
| * Creates an identifier that consists of this identifier plus a name segment. |
| * Does not modify this identifier. |
| */ |
| public SqlIdentifier plus(String name, SqlParserPos pos) { |
| final ImmutableList<String> names = |
| ImmutableList.<String>builder().addAll(this.names).add(name).build(); |
| final ImmutableList<SqlParserPos> componentPositions; |
| final SqlParserPos pos2; |
| ImmutableList<SqlParserPos> thisComponentPositions = this.componentPositions; |
| if (thisComponentPositions != null) { |
| final ImmutableList.Builder<SqlParserPos> builder = |
| ImmutableList.builder(); |
| componentPositions = |
| builder.addAll(thisComponentPositions).add(pos).build(); |
| pos2 = SqlParserPos.sum(builder.add(this.pos).build()); |
| } else { |
| componentPositions = null; |
| pos2 = pos; |
| } |
| return new SqlIdentifier(names, collation, pos2, componentPositions); |
| } |
| |
| /** |
| * Creates an identifier that consists of this identifier plus a wildcard star. |
| * Does not modify this identifier. |
| */ |
| public SqlIdentifier plusStar() { |
| final SqlIdentifier id = this.plus("*", SqlParserPos.ZERO); |
| return new SqlIdentifier( |
| id.names.stream().map(s -> s.equals("*") ? "" : s) |
| .collect(toImmutableList()), |
| null, id.pos, id.componentPositions); |
| } |
| |
| /** Creates an identifier that consists of all but the last {@code n} |
| * name segments of this one. */ |
| public SqlIdentifier skipLast(int n) { |
| return getComponent(0, names.size() - n); |
| } |
| |
| @Override public void unparse( |
| SqlWriter writer, |
| int leftPrec, |
| int rightPrec) { |
| SqlUtil.unparseSqlIdentifierSyntax(writer, this, false); |
| } |
| |
| @Override public void validate(SqlValidator validator, SqlValidatorScope scope) { |
| validator.validateIdentifier(this, scope); |
| } |
| |
| @Override public void validateExpr(SqlValidator validator, SqlValidatorScope scope) { |
| // First check for builtin functions which don't have parentheses, |
| // like "LOCALTIME". |
| final SqlCall call = validator.makeNullaryCall(this); |
| if (call != null) { |
| validator.validateCall(call, scope); |
| return; |
| } |
| |
| validator.validateIdentifier(this, scope); |
| } |
| |
| @Override public boolean equalsDeep(@Nullable SqlNode node, Litmus litmus) { |
| if (!(node instanceof SqlIdentifier)) { |
| return litmus.fail("{} != {}", this, node); |
| } |
| SqlIdentifier that = (SqlIdentifier) node; |
| if (this.names.size() != that.names.size()) { |
| return litmus.fail("{} != {}", this, node); |
| } |
| for (int i = 0; i < names.size(); i++) { |
| if (!this.names.get(i).equals(that.names.get(i))) { |
| return litmus.fail("{} != {}", this, node); |
| } |
| } |
| return litmus.succeed(); |
| } |
| |
| @Override public <R> R accept(SqlVisitor<R> visitor) { |
| return visitor.visit(this); |
| } |
| |
| @Pure |
| public @Nullable SqlCollation getCollation() { |
| return collation; |
| } |
| |
| public String getSimple() { |
| assert names.size() == 1; |
| return names.get(0); |
| } |
| |
| /** Returns the simple names in a list of identifiers. |
| * Assumes that the list consists of are not-null, simple identifiers. */ |
| public static List<String> simpleNames(List<? extends SqlNode> list) { |
| return Util.transform(list, n -> ((SqlIdentifier) n).getSimple()); |
| } |
| |
| /** Returns the simple names in a iterable of identifiers. |
| * Assumes that the iterable consists of not-null, simple identifiers. */ |
| public static Iterable<String> simpleNames(Iterable<? extends SqlNode> list) { |
| return Util.transform(list, n -> ((SqlIdentifier) n).getSimple()); |
| } |
| |
| /** |
| * Returns whether this identifier is a star, such as "*" or "foo.bar.*". |
| */ |
| public boolean isStar() { |
| return Util.last(names).equals(""); |
| } |
| |
| /** |
| * Returns whether this is a simple identifier. "FOO" is simple; "*", |
| * "FOO.*" and "FOO.BAR" are not. |
| */ |
| public boolean isSimple() { |
| return names.size() == 1 && !isStar(); |
| } |
| |
| /** |
| * Returns whether the {@code i}th component of a compound identifier is |
| * quoted. |
| * |
| * @param i Ordinal of component |
| * @return Whether i'th component is quoted |
| */ |
| public boolean isComponentQuoted(int i) { |
| return componentPositions != null |
| && componentPositions.get(i).isQuoted(); |
| } |
| |
| @Override public SqlMonotonicity getMonotonicity(SqlValidatorScope scope) { |
| // for "star" column, whether it's static or dynamic return not_monotonic directly. |
| if (Util.last(names).equals("") || DynamicRecordType.isDynamicStarColName(Util.last(names))) { |
| return SqlMonotonicity.NOT_MONOTONIC; |
| } |
| |
| // First check for builtin functions which don't have parentheses, |
| // like "LOCALTIME". |
| final SqlValidator validator = scope.getValidator(); |
| final SqlCall call = validator.makeNullaryCall(this); |
| if (call != null) { |
| return call.getMonotonicity(scope); |
| } |
| final SqlQualified qualified = scope.fullyQualify(this); |
| assert qualified.namespace != null : "namespace must not be null in " + qualified; |
| final SqlIdentifier fqId = qualified.identifier; |
| return qualified.namespace.resolve().getMonotonicity(Util.last(fqId.names)); |
| } |
| } |