/*
* 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.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.antlr.runtime.*;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.view.View;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
public final class ViewMetadata
{
public final String keyspace;
public final String name;
public final TableId baseTableId;
public final String baseTableName;
public final boolean includeAllColumns;
public final TableMetadata metadata;
public final SelectStatement.RawStatement select;
public final String whereClause;
/**
* @param name Name of the view
* @param baseTableId Internal ID of the table which this view is based off of
* @param includeAllColumns Whether to include all columns or not
*/
public ViewMetadata(String keyspace,
String name,
TableId baseTableId,
String baseTableName,
boolean includeAllColumns,
SelectStatement.RawStatement select,
String whereClause,
TableMetadata metadata)
{
this.keyspace = keyspace;
this.name = name;
this.baseTableId = baseTableId;
this.baseTableName = baseTableName;
this.includeAllColumns = includeAllColumns;
this.select = select;
this.whereClause = whereClause;
this.metadata = metadata;
}
/**
* @return true if the view specified by this definition will include the column, false otherwise
*/
public boolean includes(ColumnIdentifier column)
{
return metadata.getColumn(column) != null;
}
public ViewMetadata copy(TableMetadata newMetadata)
{
return new ViewMetadata(keyspace, name, baseTableId, baseTableName, includeAllColumns, select, whereClause, newMetadata);
}
public TableMetadata baseTableMetadata()
{
return Schema.instance.getTableMetadata(baseTableId);
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof ViewMetadata))
return false;
ViewMetadata other = (ViewMetadata) o;
return Objects.equals(keyspace, other.keyspace)
&& Objects.equals(name, other.name)
&& Objects.equals(baseTableId, other.baseTableId)
&& Objects.equals(includeAllColumns, other.includeAllColumns)
&& Objects.equals(whereClause, other.whereClause)
&& Objects.equals(metadata, other.metadata);
}
@Override
public int hashCode()
{
return new HashCodeBuilder(29, 1597)
.append(keyspace)
.append(name)
.append(baseTableId)
.append(includeAllColumns)
.append(whereClause)
.append(metadata)
.toHashCode();
}
@Override
public String toString()
{
return new ToStringBuilder(this)
.append("keyspace", keyspace)
.append("name", name)
.append("baseTableId", baseTableId)
.append("baseTableName", baseTableName)
.append("includeAllColumns", includeAllColumns)
.append("whereClause", whereClause)
.append("metadata", metadata)
.toString();
}
/**
* Replace the column 'from' with 'to' in this materialized view definition's partition,
* clustering, or included columns.
* @param from the existing column
* @param to the new column
*/
public ViewMetadata renamePrimaryKeyColumn(ColumnIdentifier from, ColumnIdentifier to)
{
// convert whereClause to Relations, rename ids in Relations, then convert back to whereClause
List<Relation> relations = whereClauseToRelations(whereClause);
ColumnMetadata.Raw fromRaw = ColumnMetadata.Raw.forQuoted(from.toString());
ColumnMetadata.Raw toRaw = ColumnMetadata.Raw.forQuoted(to.toString());
List<Relation> newRelations =
relations.stream()
.map(r -> r.renameIdentifier(fromRaw, toRaw))
.collect(Collectors.toList());
String rawSelect = View.buildSelectStatement(baseTableName, metadata.columns(), whereClause);
return new ViewMetadata(keyspace,
name,
baseTableId,
baseTableName,
includeAllColumns,
(SelectStatement.RawStatement) QueryProcessor.parseStatement(rawSelect),
View.relationsToWhereClause(newRelations),
metadata.unbuild().renamePrimaryKeyColumn(from, to).build());
}
public ViewMetadata withAddedRegularColumn(ColumnMetadata column)
{
return new ViewMetadata(keyspace,
name,
baseTableId,
baseTableName,
includeAllColumns,
select,
whereClause,
metadata.unbuild().addColumn(column).build());
}
public ViewMetadata withAlteredColumnType(ColumnIdentifier name, AbstractType<?> type)
{
return new ViewMetadata(keyspace,
this.name,
baseTableId,
baseTableName,
includeAllColumns,
select,
whereClause,
metadata.unbuild().alterColumnType(name, type).build());
}
private static List<Relation> whereClauseToRelations(String whereClause)
{
try
{
return CQLFragmentParser.parseAnyUnhandled(CqlParser::whereClause, whereClause).build().relations;
}
catch (RecognitionException | SyntaxException exc)
{
throw new RuntimeException("Unexpected error parsing materialized view's where clause while handling column rename: ", exc);
}
}
}