blob: 6742057bfb7a13551f2f1bb0917762a3e9d84ced [file] [log] [blame]
/*
* 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 &ge; 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));
}
}