/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.metadata; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import io.crate.core.StringUtils; import io.crate.sql.Identifiers; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; public class ColumnIdent implements Path, Comparable<ColumnIdent> { public static final Predicate<CharSequence> INVALID_COLUMN_NAME_PREDICATE = Predicates.contains(Pattern.compile("[\\[\\'\\]\\.]")); private static final Ordering<Iterable<String>> ordering = Ordering.<String>natural().lexicographical(); private final String name; private final List<String> path; public ColumnIdent(StreamInput in) throws IOException { name = in.readString(); int numParts = in.readVInt(); if (numParts > 0) { path = new ArrayList<>(numParts); for (int i = 0; i < numParts; i++) { path.add(in.readString()); } } else { path = ImmutableList.of(); } } public ColumnIdent(String name) { this.name = name; this.path = ImmutableList.of(); } public ColumnIdent(String name, String childName) { this(name, ImmutableList.of(childName)); } public ColumnIdent(String name, @Nullable List<String> path) { this.name = name; this.path = MoreObjects.firstNonNull(path, ImmutableList.<String>of()); } public static ColumnIdent fromPath(@Nullable String path) { if (path == null) { return null; } List<String> parts = StringUtils.PATH_SPLITTER.splitToList(path); if (parts.size() > 1) { return new ColumnIdent(parts.get(0), parts.subList(1, parts.size())); } else { return new ColumnIdent(parts.get(0)); } } public static ColumnIdent getChild(ColumnIdent parent, String name) { if (parent.isColumn()) { return new ColumnIdent(parent.name, name); } List<String> childPath = ImmutableList.<String>builder().addAll(parent.path).add(name).build(); return new ColumnIdent(parent.name, childPath); } /** * checks whether this ColumnIdent is a child of <code>parentIdent</code> * * @param parentIdent the ident to check for parenthood * @return true if <code>parentIdent</code> is parentIdent of this, false otherwise. */ public boolean isChildOf(ColumnIdent parentIdent) { if (!name.equals(parentIdent.name)) return false; if (path.size() > parentIdent.path.size()) { Iterator<String> parentIt = parentIdent.path.iterator(); Iterator<String> it = path.iterator(); while (parentIt.hasNext()) { if (!parentIt.next().equals(it.next())) { return false; } } return true; } return false; } /** * person['addresses']['street'] --> person['addresses'] * <p> * person --> null */ public ColumnIdent getParent() { if (isColumn()) { return null; } if (path.size() > 1) { return new ColumnIdent(name(), path.subList(0, path.size() - 1)); } return new ColumnIdent(name()); } /** * creates a new columnIdent which just consists of the path of the given columnIdent * e.g. * <pre>foo['x']['y']</pre> * becomes * <pre> x['y']</pre> * * If the columnIdent doesn't have a path the return value is null */ @Nullable public ColumnIdent shiftRight() { if (path.isEmpty()) { return null; } ColumnIdent newCi; if (path.size() > 1) { newCi = new ColumnIdent(path.get(0), path.subList(1, path.size())); } else { newCi = new ColumnIdent(path.get(0)); } return newCi; } /** * returns true if this is a system column */ public boolean isSystemColumn() { return name.startsWith("_"); } /** * person['addresses']['street'] --> person * <p> * person --> person */ public ColumnIdent getRoot() { if (isColumn()) { return this; } return new ColumnIdent(name()); } public String name() { return name; } public String fqn() { if (isColumn()) { return name; } return StringUtils.PATH_JOINER.join(name, StringUtils.PATH_JOINER.join(path)); } @Override public String outputName() { return sqlFqn(); } public String quotedOutputName() { return sqlFqn(Identifiers.quoteIfNeeded(name)); } public String sqlFqn() { return sqlFqn(name); } private String sqlFqn(String name) { StringBuilder sb = new StringBuilder(name); for (String s : path) { sb.append("['"); sb.append(s); sb.append("']"); } return sb.toString(); } public List<String> path() { return path; } /** * @return true if this is a top level column, otherwise false */ public boolean isColumn() { return path.isEmpty(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ColumnIdent that = (ColumnIdent) o; if (!name.equals(that.name)) return false; return path.equals(that.path); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + path.hashCode(); return result; } @Override public String toString() { return sqlFqn(); } @Override public int compareTo(ColumnIdent o) { return ComparisonChain.start() .compare(name, o.name) .compare(path, o.path, ordering) .result(); } public void writeTo(StreamOutput out) throws IOException { out.writeString(name); out.writeVInt(path.size()); for (String s : path) { out.writeString(s); } } /** * Create a new ColumnIdent with the name inserted at the start * <p> * E.g. ColumnIdent y['z'].prepend('x') becomes ColumnIdent x['y']['z'] */ public ColumnIdent prepend(String name) { if (path.isEmpty()) { return new ColumnIdent(name, this.name); } List<String> newPath = new ArrayList<>(path); newPath.add(0, this.name); return new ColumnIdent(name, newPath); } }