| /* |
| * 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.util.HashSet; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| import javax.annotation.Nullable; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Objects; |
| import com.google.common.collect.Iterables; |
| |
| import org.apache.cassandra.config.DatabaseDescriptor; |
| import org.apache.cassandra.cql3.CqlBuilder; |
| import org.apache.cassandra.cql3.SchemaElement; |
| import org.apache.cassandra.cql3.functions.UDAggregate; |
| import org.apache.cassandra.cql3.functions.UDFunction; |
| import org.apache.cassandra.db.marshal.UserType; |
| import org.apache.cassandra.exceptions.ConfigurationException; |
| import org.apache.cassandra.locator.AbstractReplicationStrategy; |
| import org.apache.cassandra.schema.Functions.FunctionsDiff; |
| import org.apache.cassandra.schema.Tables.TablesDiff; |
| import org.apache.cassandra.schema.Types.TypesDiff; |
| import org.apache.cassandra.schema.Views.ViewsDiff; |
| import org.apache.cassandra.service.StorageService; |
| |
| import static java.lang.String.format; |
| |
| import static com.google.common.collect.Iterables.any; |
| |
| /** |
| * An immutable representation of keyspace metadata (name, params, tables, types, and functions). |
| */ |
| public final class KeyspaceMetadata implements SchemaElement |
| { |
| public enum Kind |
| { |
| REGULAR, VIRTUAL |
| } |
| |
| public final String name; |
| public final Kind kind; |
| public final KeyspaceParams params; |
| public final Tables tables; |
| public final Views views; |
| public final Types types; |
| public final Functions functions; |
| |
| private KeyspaceMetadata(String name, Kind kind, KeyspaceParams params, Tables tables, Views views, Types types, Functions functions) |
| { |
| this.name = name; |
| this.kind = kind; |
| this.params = params; |
| this.tables = tables; |
| this.views = views; |
| this.types = types; |
| this.functions = functions; |
| } |
| |
| public static KeyspaceMetadata create(String name, KeyspaceParams params) |
| { |
| return new KeyspaceMetadata(name, Kind.REGULAR, params, Tables.none(), Views.none(), Types.none(), Functions.none()); |
| } |
| |
| public static KeyspaceMetadata create(String name, KeyspaceParams params, Tables tables) |
| { |
| return new KeyspaceMetadata(name, Kind.REGULAR, params, tables, Views.none(), Types.none(), Functions.none()); |
| } |
| |
| public static KeyspaceMetadata create(String name, KeyspaceParams params, Tables tables, Views views, Types types, Functions functions) |
| { |
| return new KeyspaceMetadata(name, Kind.REGULAR, params, tables, views, types, functions); |
| } |
| |
| public static KeyspaceMetadata virtual(String name, Tables tables) |
| { |
| return new KeyspaceMetadata(name, Kind.VIRTUAL, KeyspaceParams.local(), tables, Views.none(), Types.none(), Functions.none()); |
| } |
| |
| public KeyspaceMetadata withSwapped(KeyspaceParams params) |
| { |
| return new KeyspaceMetadata(name, kind, params, tables, views, types, functions); |
| } |
| |
| public KeyspaceMetadata withSwapped(Tables regular) |
| { |
| return new KeyspaceMetadata(name, kind, params, regular, views, types, functions); |
| } |
| |
| public KeyspaceMetadata withSwapped(Views views) |
| { |
| return new KeyspaceMetadata(name, kind, params, tables, views, types, functions); |
| } |
| |
| public KeyspaceMetadata withSwapped(Types types) |
| { |
| return new KeyspaceMetadata(name, kind, params, tables, views, types, functions); |
| } |
| |
| public KeyspaceMetadata withSwapped(Functions functions) |
| { |
| return new KeyspaceMetadata(name, kind, params, tables, views, types, functions); |
| } |
| |
| public boolean isVirtual() |
| { |
| return kind == Kind.VIRTUAL; |
| } |
| |
| /** |
| * Returns a new KeyspaceMetadata with all instances of old UDT replaced with the updated version. |
| * Replaces all instances in tables, views, types, and functions. |
| */ |
| public KeyspaceMetadata withUpdatedUserType(UserType udt) |
| { |
| return new KeyspaceMetadata(name, |
| kind, |
| params, |
| tables.withUpdatedUserType(udt), |
| views.withUpdatedUserTypes(udt), |
| types.withUpdatedUserType(udt), |
| functions.withUpdatedUserType(udt)); |
| } |
| |
| public Iterable<TableMetadata> tablesAndViews() |
| { |
| return Iterables.concat(tables, views.allTableMetadata()); |
| } |
| |
| @Nullable |
| public TableMetadata getTableOrViewNullable(String tableOrViewName) |
| { |
| ViewMetadata view = views.getNullable(tableOrViewName); |
| return view == null |
| ? tables.getNullable(tableOrViewName) |
| : view.metadata; |
| } |
| |
| public boolean hasTable(String tableName) |
| { |
| return tables.get(tableName).isPresent(); |
| } |
| |
| public boolean hasView(String viewName) |
| { |
| return views.get(viewName).isPresent(); |
| } |
| |
| public boolean hasIndex(String indexName) |
| { |
| return any(tables, t -> t.indexes.has(indexName)); |
| } |
| |
| public String findAvailableIndexName(String baseName) |
| { |
| if (!hasIndex(baseName)) |
| return baseName; |
| |
| int i = 1; |
| do |
| { |
| String name = baseName + '_' + i++; |
| if (!hasIndex(name)) |
| return name; |
| } |
| while (true); |
| } |
| |
| public Optional<TableMetadata> findIndexedTable(String indexName) |
| { |
| for (TableMetadata table : tablesAndViews()) |
| if (table.indexes.has(indexName)) |
| return Optional.of(table); |
| |
| return Optional.empty(); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return Objects.hashCode(name, kind, params, tables, views, functions, types); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) |
| return true; |
| |
| if (!(o instanceof KeyspaceMetadata)) |
| return false; |
| |
| KeyspaceMetadata other = (KeyspaceMetadata) o; |
| |
| return name.equals(other.name) |
| && kind == other.kind |
| && params.equals(other.params) |
| && tables.equals(other.tables) |
| && views.equals(other.views) |
| && functions.equals(other.functions) |
| && types.equals(other.types); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MoreObjects.toStringHelper(this) |
| .add("name", name) |
| .add("kind", kind) |
| .add("params", params) |
| .add("tables", tables) |
| .add("views", views) |
| .add("functions", functions) |
| .add("types", types) |
| .toString(); |
| } |
| |
| @Override |
| public SchemaElementType elementType() |
| { |
| return SchemaElementType.KEYSPACE; |
| } |
| |
| @Override |
| public String elementKeyspace() |
| { |
| return name; |
| } |
| |
| @Override |
| public String elementName() |
| { |
| return name; |
| } |
| |
| @Override |
| public String toCqlString(boolean withInternals, boolean ifNotExists) |
| { |
| CqlBuilder builder = new CqlBuilder(); |
| if (isVirtual()) |
| { |
| builder.append("/*") |
| .newLine() |
| .append("Warning: Keyspace ") |
| .appendQuotingIfNeeded(name) |
| .append(" is a virtual keyspace and cannot be recreated with CQL.") |
| .newLine() |
| .append("Structure, for reference:") |
| .newLine() |
| .append("VIRTUAL KEYSPACE ") |
| .appendQuotingIfNeeded(name) |
| .append(';') |
| .newLine() |
| .append("*/") |
| .toString(); |
| } |
| else |
| { |
| builder.append("CREATE KEYSPACE "); |
| |
| if (ifNotExists) |
| { |
| builder.append("IF NOT EXISTS "); |
| } |
| |
| builder.appendQuotingIfNeeded(name) |
| .append(" WITH replication = "); |
| |
| params.replication.appendCqlTo(builder); |
| |
| builder.append(" AND durable_writes = ") |
| .append(params.durableWrites) |
| .append(';') |
| .toString(); |
| } |
| return builder.toString(); |
| } |
| |
| public void validate() |
| { |
| if (!SchemaConstants.isValidName(name)) |
| { |
| throw new ConfigurationException(format("Keyspace name must not be empty, more than %s characters long, " |
| + "or contain non-alphanumeric-underscore characters (got \"%s\")", |
| SchemaConstants.NAME_LENGTH, |
| name)); |
| } |
| |
| params.validate(name); |
| |
| tablesAndViews().forEach(TableMetadata::validate); |
| |
| Set<String> indexNames = new HashSet<>(); |
| for (TableMetadata table : tables) |
| { |
| for (IndexMetadata index : table.indexes) |
| { |
| if (indexNames.contains(index.name)) |
| throw new ConfigurationException(format("Duplicate index name %s in keyspace %s", index.name, name)); |
| |
| indexNames.add(index.name); |
| } |
| } |
| } |
| |
| public AbstractReplicationStrategy createReplicationStrategy() |
| { |
| return AbstractReplicationStrategy.createReplicationStrategy(name, |
| params.replication.klass, |
| StorageService.instance.getTokenMetadata(), |
| DatabaseDescriptor.getEndpointSnitch(), |
| params.replication.options); |
| } |
| |
| static Optional<KeyspaceDiff> diff(KeyspaceMetadata before, KeyspaceMetadata after) |
| { |
| return KeyspaceDiff.diff(before, after); |
| } |
| |
| public static final class KeyspaceDiff |
| { |
| public final KeyspaceMetadata before; |
| public final KeyspaceMetadata after; |
| |
| public final TablesDiff tables; |
| public final ViewsDiff views; |
| public final TypesDiff types; |
| |
| public final FunctionsDiff<UDFunction> udfs; |
| public final FunctionsDiff<UDAggregate> udas; |
| |
| private KeyspaceDiff(KeyspaceMetadata before, |
| KeyspaceMetadata after, |
| TablesDiff tables, |
| ViewsDiff views, |
| TypesDiff types, |
| FunctionsDiff<UDFunction> udfs, |
| FunctionsDiff<UDAggregate> udas) |
| { |
| this.before = before; |
| this.after = after; |
| this.tables = tables; |
| this.views = views; |
| this.types = types; |
| this.udfs = udfs; |
| this.udas = udas; |
| } |
| |
| private static Optional<KeyspaceDiff> diff(KeyspaceMetadata before, KeyspaceMetadata after) |
| { |
| if (before == after) |
| return Optional.empty(); |
| |
| if (!before.name.equals(after.name)) |
| { |
| String msg = String.format("Attempting to diff two keyspaces with different names ('%s' and '%s')", before.name, after.name); |
| throw new IllegalArgumentException(msg); |
| } |
| |
| TablesDiff tables = Tables.diff(before.tables, after.tables); |
| ViewsDiff views = Views.diff(before.views, after.views); |
| TypesDiff types = Types.diff(before.types, after.types); |
| |
| @SuppressWarnings("unchecked") FunctionsDiff<UDFunction> udfs = FunctionsDiff.NONE; |
| @SuppressWarnings("unchecked") FunctionsDiff<UDAggregate> udas = FunctionsDiff.NONE; |
| if (before.functions != after.functions) |
| { |
| udfs = Functions.udfsDiff(before.functions, after.functions); |
| udas = Functions.udasDiff(before.functions, after.functions); |
| } |
| |
| if (before.params.equals(after.params) && tables.isEmpty() && views.isEmpty() && types.isEmpty() && udfs.isEmpty() && udas.isEmpty()) |
| return Optional.empty(); |
| |
| return Optional.of(new KeyspaceDiff(before, after, tables, views, types, udfs, udas)); |
| } |
| } |
| } |