/*
 * 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.cassandra.schema;

import java.nio.ByteBuffer;
import java.util.*;

import javax.annotation.Nullable;

import com.google.common.collect.*;

import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.ConfigurationException;

import static java.lang.String.format;
import static com.google.common.collect.Iterables.filter;
import static java.util.stream.Collectors.toList;
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;

An immutable container for a keyspace's UDTs.
/** * An immutable container for a keyspace's UDTs. */
public final class Types implements Iterable<UserType> { private static final Types NONE = new Types(ImmutableMap.of()); private final Map<ByteBuffer, UserType> types; private Types(Builder builder) { types = builder.types.build(); } /* * For use in RawBuilder::build only. */ private Types(Map<ByteBuffer, UserType> types) { this.types = types; } public static Builder builder() { return new Builder(); } public static RawBuilder rawBuilder(String keyspace) { return new RawBuilder(keyspace); } public static Types none() { return NONE; } public static Types of(UserType... types) { return builder().add(types).build(); } public Iterator<UserType> iterator() { return types.values().iterator(); }
Get the type with the specified name
Params:
  • name – a non-qualified type name
Returns:an empty Optional if the type name is not found; a non-empty optional of UserType otherwise
/** * Get the type with the specified name * * @param name a non-qualified type name * @return an empty {@link Optional} if the type name is not found; a non-empty optional of {@link UserType} otherwise */
public Optional<UserType> get(ByteBuffer name) { return Optional.ofNullable(types.get(name)); }
Get the type with the specified name
Params:
  • name – a non-qualified type name
Returns:null if the type name is not found; the found UserType otherwise
/** * Get the type with the specified name * * @param name a non-qualified type name * @return null if the type name is not found; the found {@link UserType} otherwise */
@Nullable public UserType getNullable(ByteBuffer name) { return types.get(name); }
Create a Types instance with the provided type added
/** * Create a Types instance with the provided type added */
public Types with(UserType type) { if (get(type.name).isPresent()) throw new IllegalStateException(format("Type %s already exists", type.name)); return builder().add(this).add(type).build(); }
Creates a Types instance with the type with the provided name removed
/** * Creates a Types instance with the type with the provided name removed */
public Types without(ByteBuffer name) { UserType type = get(name).orElseThrow(() -> new IllegalStateException(format("Type %s doesn't exists", name))); return builder().add(filter(this, t -> t != type)).build(); } MapDifference<ByteBuffer, UserType> diff(Types other) { return Maps.difference(types, other.types); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Types)) return false; Types other = (Types) o; if (types.size() != other.types.size()) return false; Iterator<Map.Entry<ByteBuffer, UserType>> thisIter = this.types.entrySet().iterator(); Iterator<Map.Entry<ByteBuffer, UserType>> otherIter = other.types.entrySet().iterator(); while (thisIter.hasNext()) { Map.Entry<ByteBuffer, UserType> thisNext = thisIter.next(); Map.Entry<ByteBuffer, UserType> otherNext = otherIter.next(); if (!thisNext.getKey().equals(otherNext.getKey())) return false; if (!thisNext.getValue().equals(otherNext.getValue(), true)) // ignore freezing return false; } return true; } @Override public int hashCode() { return types.hashCode(); } @Override public String toString() { return types.values().toString(); } public static final class Builder { final ImmutableSortedMap.Builder<ByteBuffer, UserType> types = ImmutableSortedMap.naturalOrder(); private Builder() { } public Types build() { return new Types(this); } public Builder add(UserType type) { assert type.isMultiCell(); types.put(type.name, type); return this; } public Builder add(UserType... types) { for (UserType type : types) add(type); return this; } public Builder add(Iterable<UserType> types) { types.forEach(this::add); return this; } } public static final class RawBuilder { final String keyspace; final List<RawUDT> definitions; private RawBuilder(String keyspace) { this.keyspace = keyspace; this.definitions = new ArrayList<>(); }
Build a Types instance from Raw definitions. Constructs a DAG of graph dependencies and resolves them 1 by 1 in topological order.
/** * Build a Types instance from Raw definitions. * * Constructs a DAG of graph dependencies and resolves them 1 by 1 in topological order. */
public Types build() { if (definitions.isEmpty()) return Types.none(); /* * build a DAG of UDT dependencies */ Map<RawUDT, Integer> vertices = new HashMap<>(); // map values are numbers of referenced types for (RawUDT udt : definitions) vertices.put(udt, 0); Multimap<RawUDT, RawUDT> adjacencyList = HashMultimap.create(); for (RawUDT udt1 : definitions) for (RawUDT udt2 : definitions) if (udt1 != udt2 && udt1.referencesUserType(udt2)) adjacencyList.put(udt2, udt1); /* * resolve dependencies in topological order, using Kahn's algorithm */ adjacencyList.values().forEach(vertex -> vertices.put(vertex, vertices.get(vertex) + 1)); Queue<RawUDT> resolvableTypes = new LinkedList<>(); // UDTs with 0 dependencies for (Map.Entry<RawUDT, Integer> entry : vertices.entrySet()) if (entry.getValue() == 0) resolvableTypes.add(entry.getKey()); Types types = new Types(new HashMap<>()); while (!resolvableTypes.isEmpty()) { RawUDT vertex = resolvableTypes.remove(); for (RawUDT dependentType : adjacencyList.get(vertex)) if (vertices.replace(dependentType, vertices.get(dependentType) - 1) == 1) resolvableTypes.add(dependentType); UserType udt = vertex.prepare(keyspace, types); types.types.put(udt.name, udt); } if (types.types.size() != definitions.size()) throw new ConfigurationException(format("Cannot resolve UDTs for keyspace %s: some types are missing", keyspace)); /* * return an immutable copy */ return Types.builder().add(types).build(); } public void add(String name, List<String> fieldNames, List<String> fieldTypes) { List<CQL3Type.Raw> rawFieldTypes = fieldTypes.stream() .map(CQLTypeParser::parseRaw) .collect(toList()); definitions.add(new RawUDT(name, fieldNames, rawFieldTypes)); } private static final class RawUDT { final String name; final List<String> fieldNames; final List<CQL3Type.Raw> fieldTypes; RawUDT(String name, List<String> fieldNames, List<CQL3Type.Raw> fieldTypes) { this.name = name; this.fieldNames = fieldNames; this.fieldTypes = fieldTypes; } boolean referencesUserType(RawUDT other) { return fieldTypes.stream().anyMatch(t -> t.referencesUserType(other.name)); } UserType prepare(String keyspace, Types types) { List<FieldIdentifier> preparedFieldNames = fieldNames.stream() .map(t -> FieldIdentifier.forInternalString(t)) .collect(toList()); List<AbstractType<?>> preparedFieldTypes = fieldTypes.stream() .map(t -> t.prepareInternal(keyspace, types).getType()) .collect(toList()); return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes, true); } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(Object other) { return name.equals(((RawUDT) other).name); } } } }