All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.db.view.View Maven / Gradle / Ivy

Go to download

The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.

There is a newer version: 5.0.2
Show newest version
/*
 * 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.db.view;

import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import com.google.common.collect.Iterables;

import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.selection.RawSelector;
import org.apache.cassandra.cql3.selection.Selectable;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A View copies data from a base table into a view table which can be queried independently from the
 * base. Every update which targets the base table must be fed through the {@link ViewManager} to ensure
 * that if a view needs to be updated, the updates are properly created and fed into the view.
 */
public class View
{
    public final static String USAGE_WARNING = "Materialized views are experimental and are not recommended for production use.";

    private static final Logger logger = LoggerFactory.getLogger(View.class);

    public final String name;
    private volatile ViewMetadata definition;

    private final ColumnFamilyStore baseCfs;

    public volatile List baseNonPKColumnsInViewPK;
    private ViewBuilder builder;

    private SelectStatement select;
    private ReadQuery query;

    public View(ViewMetadata definition, ColumnFamilyStore baseCfs)
    {
        this.baseCfs = baseCfs;
        this.name = definition.name();

        updateDefinition(definition);
    }

    public ViewMetadata getDefinition()
    {
        return definition;
    }

    /**
     * This updates the columns stored which are dependent on the base TableMetadata.
     */
    public void updateDefinition(ViewMetadata definition)
    {
        this.definition = definition;
        List nonPKDefPartOfViewPK = new ArrayList<>();
        for (ColumnMetadata baseColumn : baseCfs.metadata.get().columns())
        {
            ColumnMetadata viewColumn = getViewColumn(baseColumn);
            if (viewColumn != null && !baseColumn.isPrimaryKeyColumn() && viewColumn.isPrimaryKeyColumn())
                nonPKDefPartOfViewPK.add(baseColumn);
        }
        this.baseNonPKColumnsInViewPK = nonPKDefPartOfViewPK;
    }

    /**
     * The view column corresponding to the provided base column. This can
     * return {@code null} if the column is denormalized in the view.
     */
    public ColumnMetadata getViewColumn(ColumnMetadata baseColumn)
    {
        return definition.metadata.getColumn(baseColumn.name);
    }

    /**
     * The base column corresponding to the provided view column. This should
     * never return {@code null} since a view can't have its "own" columns.
     */
    public ColumnMetadata getBaseColumn(ColumnMetadata viewColumn)
    {
        ColumnMetadata baseColumn = baseCfs.metadata().getColumn(viewColumn.name);
        assert baseColumn != null;
        return baseColumn;
    }

    /**
     * Whether the view might be affected by the provided update.
     * 

* Note that having this method return {@code true} is not an absolute guarantee that the view will be * updated, just that it most likely will, but a {@code false} return guarantees it won't be affected). * * @param partitionKey the partition key that is updated. * @param update the update being applied. * @return {@code false} if we can guarantee that inserting {@code update} for key {@code partitionKey} * won't affect the view in any way, {@code true} otherwise. */ public boolean mayBeAffectedBy(DecoratedKey partitionKey, Row update) { // We can guarantee that the view won't be affected if: // - the clustering is excluded by the view filter (note that this isn't true of the filter on regular columns: // even if an update don't match a view condition on a regular column, that update can still invalidate an pre-existing // entry). // - or the update don't modify any of the columns impacting the view (where "impacting" the view means that column is // neither included in the view, nor used by the view filter). if (!getReadQuery().selectsClustering(partitionKey, update.clustering())) return false; return true; } /** * Whether a given base row matches the view filter (and thus if is should have a corresponding entry). *

* Note that this differs from {@link #mayBeAffectedBy} in that the provide row must be the current * state of the base row, not just some updates to it. This method also has no false positive: a base * row either do or don't match the view filter. * * @param partitionKey the partition key that is updated. * @param baseRow the current state of a particular base row. * @param nowInSec the current time in seconds (to decide what is live and what isn't). * @return {@code true} if {@code baseRow} matches the view filters, {@code false} otherwise. */ public boolean matchesViewFilter(DecoratedKey partitionKey, Row baseRow, long nowInSec) { return getReadQuery().selectsClustering(partitionKey, baseRow.clustering()) && getSelectStatement().rowFilterForInternalCalls().isSatisfiedBy(baseCfs.metadata(), partitionKey, baseRow, nowInSec); } /** * Returns the SelectStatement used to populate and filter this view. Internal users should access the select * statement this way to ensure it has been prepared. */ SelectStatement getSelectStatement() { if (null == select) { SelectStatement.Parameters parameters = new SelectStatement.Parameters(Collections.emptyMap(), Collections.emptyList(), false, true, false); SelectStatement.RawStatement rawSelect = new SelectStatement.RawStatement(new QualifiedName(baseCfs.getKeyspaceName(), baseCfs.name), parameters, selectClause(), definition.whereClause, null, null); rawSelect.setBindVariables(Collections.emptyList()); select = rawSelect.prepare(ClientState.forInternalCalls(), true); } return select; } private List selectClause() { return definition.metadata .columns() .stream() .map(c -> c.name.toString()) .map(Selectable.RawIdentifier::forQuoted) .map(c -> new RawSelector(c, null)) .collect(Collectors.toList()); } /** * Returns the ReadQuery used to filter this view. Internal users should access the query this way to ensure it * has been prepared. */ ReadQuery getReadQuery() { if (query == null) query = getSelectStatement().getQuery(QueryOptions.forInternalCalls(Collections.emptyList()), FBUtilities.nowInSeconds()); return query; } public synchronized void build() { stopBuild(); builder = new ViewBuilder(baseCfs, this); builder.start(); } /** * Stops the building of this view, no-op if it isn't building. */ synchronized void stopBuild() { if (builder != null) { logger.debug("Stopping current view builder due to schema change or truncate"); builder.stop(); builder = null; } } @Nullable public static TableMetadataRef findBaseTable(String keyspace, String viewName) { ViewMetadata view = Schema.instance.getView(keyspace, viewName); return (view == null) ? null : Schema.instance.getTableMetadataRef(view.baseTableId); } // TODO: REMOVE public static Iterable findAll(String keyspace, String baseTable) { KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspace); return Iterables.filter(ksm.views, view -> view.baseTableName.equals(baseTable)); } public boolean hasSamePrimaryKeyColumnsAsBaseTable() { return baseNonPKColumnsInViewPK.isEmpty(); } /** * When views contains a primary key column that is not part * of the base table primary key, we use that column liveness * info as the view PK, to ensure that whenever that column * is not live in the base, the row is not live in the view. * * This is done to prevent cells other than the view PK from * making the view row alive when the view PK column is not * live in the base. So in this case we tie the row liveness, * to the primary key liveness. * * See CASSANDRA-11500 for context. */ public boolean enforceStrictLiveness() { return !baseNonPKColumnsInViewPK.isEmpty(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy