blob: a02916878995d0dc70628088c95a91bf30b472f1 [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.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));
}
}
}