| /* |
| * 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. |
| */ |
| 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 |
| * |
| * @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 |
| * |
| * @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 |
| */ |
| 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 |
| */ |
| 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. |
| */ |
| 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); |
| } |
| } |
| } |
| } |