| /* |
| * 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.filter; |
| |
| import java.io.IOException; |
| import java.util.*; |
| |
| import javax.annotation.Nullable; |
| |
| import com.google.common.collect.SortedSetMultimap; |
| import com.google.common.collect.TreeMultimap; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.cassandra.cql3.ColumnIdentifier; |
| import org.apache.cassandra.db.*; |
| import org.apache.cassandra.db.rows.CellPath; |
| import org.apache.cassandra.gms.Gossiper; |
| import org.apache.cassandra.io.util.DataInputPlus; |
| import org.apache.cassandra.io.util.DataOutputPlus; |
| import org.apache.cassandra.net.MessagingService; |
| import org.apache.cassandra.schema.ColumnMetadata; |
| import org.apache.cassandra.schema.TableMetadata; |
| import org.apache.cassandra.utils.CassandraVersion; |
| |
| /** |
| * Represents which (non-PK) columns (and optionally which sub-part of a column for complex columns) are selected |
| * by a query. |
| * |
| * We distinguish 2 sets of columns in practice: the _fetched_ columns, which are the columns that we (may, see |
| * below) need to fetch internally, and the _queried_ columns, which are the columns that the user has selected |
| * in its request. |
| * |
| * The reason for distinguishing those 2 sets is that due to the CQL semantic (see #6588 for more details), we |
| * often need to internally fetch all regular columns or all columns for the queried table, but can still do some |
| * optimizations for those columns that are not directly queried by the user (see #10657 for more details). |
| * |
| * Note that in practice: |
| * - the _queried_ columns set is always included in the _fetched_ one. |
| * - whenever those sets are different, the _fetched_ columns can contain either all the regular columns and |
| * the static columns queried by the user or all the regular and static columns. If the query is a partition level |
| * query (no restrictions on clustering or regular columns) all the static columns will need to be fetched as |
| * some data will need to be returned to the user if the partition has no row but some static data. For all the |
| * other scenarios only the regular columns are required. |
| * - in the special case of a {@code SELECT *} query, we want to query all columns, and _fetched_ == _queried. |
| * As this is a common case, we special case it by using a specific subclass for it. |
| * |
| * For complex columns, this class optionally allows to specify a subset of the cells to query for each column. |
| * We can either select individual cells by path name, or a slice of them. Note that this is a sub-selection of |
| * _queried_ cells, so if _fetched_ != _queried_, then the cell selected by this sub-selection are considered |
| * queried and the other ones are considered fetched (and if a column has some sub-selection, it must be a queried |
| * column, which is actually enforced by the Builder below). |
| */ |
| public abstract class ColumnFilter |
| { |
| private final static Logger logger = LoggerFactory.getLogger(ColumnFilter.class); |
| |
| public static final ColumnFilter NONE = selection(RegularAndStaticColumns.NONE); |
| |
| public static final Serializer serializer = new Serializer(); |
| |
| /** |
| * The fetching strategy for the different queries. |
| */ |
| private enum FetchingStrategy |
| { |
| /** |
| * This strategy will fetch all the regular and static columns. |
| * |
| * <p>According to the CQL semantic a partition exists if it has at least one row or one of its static columns is not null. |
| * For queries that have no restrictions on the clustering or regular columns, C* will return some data for |
| * the partition even if it does not contains any row as long as one of the static columns contains data. |
| * To be able to ensure those queries all columns need to be fetched.</p> |
| * |
| * <p>This strategy is also used, instead of the ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS one, in mixed version clusters |
| * where some nodes have a version lower than 4.0. To ensure backward compatibility with those version that interpret the |
| * _fetchAll_ serialization flag as a true fetch all request.</p> |
| */ |
| ALL_COLUMNS |
| { |
| @Override |
| boolean fetchesAllColumns(boolean isStatic) |
| { |
| return true; |
| } |
| |
| @Override |
| RegularAndStaticColumns getFetchedColumns(TableMetadata metadata, RegularAndStaticColumns queried) |
| { |
| return metadata.regularAndStaticColumns(); |
| } |
| }, |
| |
| /** |
| * This strategy will fetch all the regular and selected static columns. |
| * |
| * <p>According to the CQL semantic a row exists if at least one of its columns is not null. |
| * To ensure that we need to fetch all regular columns.</p> |
| */ |
| ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS |
| { |
| @Override |
| boolean fetchesAllColumns(boolean isStatic) |
| { |
| return !isStatic; |
| } |
| |
| @Override |
| RegularAndStaticColumns getFetchedColumns(TableMetadata metadata, RegularAndStaticColumns queried) |
| { |
| return new RegularAndStaticColumns(queried.statics, metadata.regularColumns()); |
| } |
| }, |
| |
| /** |
| * Fetch only the columns that have been selected. |
| * |
| * <p>With this strategy _queried_ == _fetched_. This strategy is only used for internal queries.</p> |
| */ |
| ONLY_QUERIED_COLUMNS |
| { |
| @Override |
| boolean fetchesAllColumns(boolean isStatic) |
| { |
| return false; |
| } |
| |
| @Override |
| boolean areAllFetchedColumnsQueried() |
| { |
| return true; |
| } |
| |
| @Override |
| RegularAndStaticColumns getFetchedColumns(TableMetadata metadata, RegularAndStaticColumns queried) |
| { |
| return queried; |
| } |
| }; |
| |
| /** |
| * Checks if the strategy fetch all the specified columns |
| * |
| * @param isStatic {@code true} is the check is for static columns, {@code false} otherwise |
| * @return {@code true} if the strategy fetch all the static columns, {@code false} otherwise. |
| */ |
| abstract boolean fetchesAllColumns(boolean isStatic); |
| |
| /** |
| * Checks if all the fetched columns are guaranteed to be queried |
| * |
| * @return {@code true} if all the fetched columns are guaranteed to be queried, {@code false} otherwise. |
| */ |
| boolean areAllFetchedColumnsQueried() |
| { |
| return false; |
| } |
| |
| /** |
| * Returns the columns that must be fetched to answer the query. |
| * |
| * @param metadata the table metadata |
| * @param queried the queried columns |
| * @return the columns that must be fetched |
| */ |
| abstract RegularAndStaticColumns getFetchedColumns(TableMetadata metadata, RegularAndStaticColumns queried); |
| } |
| |
| /** |
| * Returns {@code true} if there are pre-4.0-rc2 nodes in the cluster, {@code false} otherwise. |
| * |
| * <p>ColumnFilters from 4.0 releases before RC2 wrongly assumed that fetching all regular columns and not |
| * the static columns was enough. That was not the case for queries that needed to return rows for empty partitions. |
| * See CASSANDRA-16686 for more details.</p> |
| */ |
| private static boolean isUpgradingFromVersionLowerThan40RC2() |
| { |
| if (Gossiper.instance.isUpgradingFromVersionLowerThan(CassandraVersion.CASSANDRA_4_0_RC2)) |
| { |
| logger.trace("ColumnFilter conversion has been applied so that static columns will not be fetched because there are pre 4.0-rc2 nodes in the cluster"); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if there are pre-4.0 nodes in the cluster, {@code false} otherwise. |
| * |
| * <p>If there pre-4.0 nodes in the cluster all static columns should be fetched along with all regular columns. |
| * This is due to the fact that this nodes have a different understanding of the fetchAll serialization flag. |
| * Pre-4.0 the fetchAll flag meant that all the columns regular AND STATIC should be fetched whereas for 4.0 |
| * nodes it meant that only the regular columns and the queried static columns should be fetched.</p> |
| */ |
| private static boolean isUpgradingFromVersionLowerThan40() |
| { |
| if (Gossiper.instance.isUpgradingFromVersionLowerThan(CassandraVersion.CASSANDRA_4_0)) |
| { |
| logger.trace("ColumnFilter conversion has been applied so that all static columns will be fetched because there are pre 4.0 nodes in the cluster"); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if there are pre-3.4 nodes in the cluster, {@code false} otherwise. |
| * |
| * When fetchAll is enabled on pre CASSANDRA-10657 (3.4-), queried columns are not considered at all, and it |
| * is assumed that all columns are queried. CASSANDRA-10657 (3.4+) brings back skipping values of columns |
| * which are not in queried set when fetchAll is enabled. That makes exactly the same filter being |
| * interpreted in a different way on 3.4- and 3.4+. |
| * |
| * Moreover, there is no way to convert the filter with fetchAll and queried != null so that it is |
| * interpreted the same way on 3.4- because that Cassandra version does not support such filtering. |
| * |
| * In order to avoid inconsistencies in data read by 3.4- and 3.4+ we need to avoid creation of incompatible |
| * filters when the cluster contains 3.4- nodes. We need to do that by using a wildcard query. |
| * |
| * see CASSANDRA-10657, CASSANDRA-15833, CASSANDRA-16415 |
| */ |
| private static boolean isUpgradingFromVersionLowerThan34() |
| { |
| if (Gossiper.instance.isUpgradingFromVersionLowerThan(CassandraVersion.CASSANDRA_3_4)) |
| { |
| logger.trace("ColumnFilter conversion has been applied so that all columns will be queried because there are pre 3.4 nodes in the cluster"); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * A filter that includes all columns for the provided table. |
| */ |
| public static ColumnFilter all(TableMetadata metadata) |
| { |
| return new WildCardColumnFilter(metadata.regularAndStaticColumns()); |
| } |
| |
| /** |
| * A filter that only fetches/queries the provided columns. |
| * <p> |
| * Note that this shouldn't be used for CQL queries in general as all columns should be queried to |
| * preserve CQL semantic (see class javadoc). This is ok for some internal queries however (and |
| * for #6588 if/when we implement it). |
| */ |
| public static ColumnFilter selection(RegularAndStaticColumns columns) |
| { |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ONLY_QUERIED_COLUMNS, null, columns, null); |
| } |
| |
| /** |
| * A filter that fetches all columns for the provided table, but returns |
| * only the queried ones. |
| */ |
| public static ColumnFilter selection(TableMetadata metadata, |
| RegularAndStaticColumns queried, |
| boolean returnStaticContentOnPartitionWithNoRows) |
| { |
| // pre CASSANDRA-10657 (3.4-), when fetchAll is enabled, queried columns are not considered at all, and it |
| // is assumed that all columns are queried. |
| if (isUpgradingFromVersionLowerThan34()) |
| { |
| return new WildCardColumnFilter(metadata.regularAndStaticColumns()); |
| } |
| |
| // pre CASSANDRA-12768 (4.0-) all static columns should be fetched along with all regular columns. |
| if (isUpgradingFromVersionLowerThan40()) |
| { |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_COLUMNS, metadata, queried, null); |
| } |
| |
| // pre CASSANDRA-16686 (4.0-RC2-) static columns were not fetched unless queried which led to some wrong |
| // results for some queries |
| if (!returnStaticContentOnPartitionWithNoRows || isUpgradingFromVersionLowerThan40RC2()) |
| { |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS, metadata, queried, null); |
| } |
| |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_COLUMNS, metadata, queried, null); |
| } |
| |
| /** |
| * The columns that needs to be fetched internally for this filter. |
| * |
| * @return the columns to fetch for this filter. |
| */ |
| public abstract RegularAndStaticColumns fetchedColumns(); |
| |
| /** |
| * The columns actually queried by the user. |
| * <p> |
| * Note that this is in general not all the columns that are fetched internally (see {@link #fetchedColumns}). |
| */ |
| public abstract RegularAndStaticColumns queriedColumns(); |
| |
| /** |
| * Whether all the (regular or static) columns are fetched by this filter. |
| * <p> |
| * Note that this method is meant as an optimization but a negative return |
| * shouldn't be relied upon strongly: this can return {@code false} but |
| * still have all the columns fetches if those were manually selected by the |
| * user. The goal here is to cheaply avoid filtering things on wildcard |
| * queries, as those are common. |
| * |
| * @param isStatic whether to check for static columns or not. If {@code true}, |
| * the method returns if all static columns are fetched, otherwise it checks |
| * regular columns. |
| */ |
| public abstract boolean fetchesAllColumns(boolean isStatic); |
| |
| /** |
| * Whether _fetched_ == _queried_ for this filter, and so if the {@code isQueried()} methods |
| * can return {@code false} for some column/cell. |
| */ |
| public abstract boolean allFetchedColumnsAreQueried(); |
| |
| /** |
| * Whether the provided column is fetched by this filter. |
| */ |
| public abstract boolean fetches(ColumnMetadata column); |
| |
| /** |
| * Whether the provided column, which is assumed to be _fetched_ by this filter (so the caller must guarantee |
| * that {@code fetches(column) == true}, is also _queried_ by the user. |
| * |
| * !WARNING! please be sure to understand the difference between _fetched_ and _queried_ |
| * columns that this class made before using this method. If unsure, you probably want |
| * to use the {@link #fetches} method. |
| */ |
| public abstract boolean fetchedColumnIsQueried(ColumnMetadata column); |
| |
| /** |
| * Whether the provided complex cell (identified by its column and path), which is assumed to be _fetched_ by |
| * this filter, is also _queried_ by the user. |
| * |
| * !WARNING! please be sure to understand the difference between _fetched_ and _queried_ |
| * columns that this class made before using this method. If unsure, you probably want |
| * to use the {@link #fetches} method. |
| */ |
| public abstract boolean fetchedCellIsQueried(ColumnMetadata column, CellPath path); |
| |
| /** |
| * Creates a new {@code Tester} to efficiently test the inclusion of cells of complex column |
| * {@code column}. |
| * |
| * @param column for complex column for which to create a tester. |
| * @return the created tester or {@code null} if all the cells from the provided column |
| * are queried. |
| */ |
| @Nullable |
| public abstract Tester newTester(ColumnMetadata column); |
| |
| /** |
| * Checks if this {@code ColumnFilter} is for a wildcard query. |
| * |
| * @return {@code true} if this {@code ColumnFilter} is for a wildcard query, {@code false} otherwise. |
| */ |
| public boolean isWildcard() |
| { |
| return false; |
| } |
| |
| /** |
| * Returns the CQL string corresponding to this {@code ColumnFilter}. |
| * |
| * @return the CQL string corresponding to this {@code ColumnFilter}. |
| */ |
| public abstract String toCQLString(); |
| |
| /** |
| * Returns the sub-selections or {@code null} if there are none. |
| * |
| * @return the sub-selections or {@code null} if there are none |
| */ |
| protected abstract SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections(); |
| |
| /** |
| * Returns a {@code ColumnFilter} builder that fetches all regular columns or all columns (and queries the columns |
| * added to the builder, or everything if no column is added). |
| * |
| * @param metadata the table metadata |
| * @param returnStaticContentOnPartitionWithNoRows {@code true} if the query must return static contents if the partition has no row, |
| * {@code false} otherwise. |
| */ |
| public static Builder allRegularColumnsBuilder(TableMetadata metadata, boolean returnStaticContentOnPartitionWithNoRows) |
| { |
| return new Builder(metadata, returnStaticContentOnPartitionWithNoRows); |
| } |
| |
| /** |
| * Returns a {@code ColumnFilter} builder that only fetches the columns/cells added to the builder. |
| */ |
| public static Builder selectionBuilder() |
| { |
| return new Builder(null, false); |
| } |
| |
| public static class Tester |
| { |
| private final boolean isFetched; |
| private ColumnSubselection current; |
| private final Iterator<ColumnSubselection> iterator; |
| |
| private Tester(boolean isFetched, Iterator<ColumnSubselection> iterator) |
| { |
| this.isFetched = isFetched; |
| this.iterator = iterator; |
| } |
| |
| public boolean fetches(CellPath path) |
| { |
| return isFetched || hasSubselection(path); |
| } |
| |
| /** |
| * Must only be called if {@code fetches(path) == true}. |
| */ |
| public boolean fetchedCellIsQueried(CellPath path) |
| { |
| return !isFetched || hasSubselection(path); |
| } |
| |
| private boolean hasSubselection(CellPath path) |
| { |
| while (current != null || iterator.hasNext()) |
| { |
| if (current == null) |
| current = iterator.next(); |
| |
| int cmp = current.compareInclusionOf(path); |
| if (cmp == 0) // The path is included |
| return true; |
| else if (cmp < 0) // The path is before this sub-selection, it's not included by any |
| return false; |
| |
| // the path is after this sub-selection, we need to check the next one. |
| current = null; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * A builder for a {@code ColumnFilter} object. |
| * |
| * Note that the columns added to this build are the _queried_ column. Whether or not all columns |
| * are _fetched_ depends on which constructor you've used to obtained this builder, allRegularColumnsBuilder (all |
| * columns are fetched) or selectionBuilder (only the queried columns are fetched). |
| * |
| * Note that for a allRegularColumnsBuilder, if no queried columns are added, this is interpreted as querying |
| * all columns, not querying none (but if you know you want to query all columns, prefer |
| * {@link ColumnFilter#all(TableMetadata)}. For selectionBuilder, adding no queried columns means no column will be |
| * fetched (so the builder will return {@code PartitionColumns.NONE}). |
| * |
| * Also, if only a sub-selection of a complex column should be queried, then only the corresponding |
| * sub-selection method of the builder ({@link #slice} or {@link #select}) should be called for the |
| * column, but {@link #add} shouldn't. if {@link #add} is also called, the whole column will be |
| * queried and the sub-selection(s) will be ignored. This is done for correctness of CQL where |
| * if you do "SELECT m, m[2..5]", you are really querying the whole collection. |
| */ |
| public static class Builder |
| { |
| private final TableMetadata metadata; // null if we don't fetch all columns |
| |
| /** |
| * {@code true} if the query must return static contents if the partition has no row, {@code false} otherwise. |
| */ |
| private final boolean returnStaticContentOnPartitionWithNoRows; |
| |
| private RegularAndStaticColumns.Builder queriedBuilder; |
| |
| private List<ColumnSubselection> subSelections; |
| |
| private Set<ColumnMetadata> fullySelectedComplexColumns; |
| |
| private Builder(TableMetadata metadata, boolean returnStaticContentOnPartitionWithNoRows) |
| { |
| this.metadata = metadata; |
| this.returnStaticContentOnPartitionWithNoRows = returnStaticContentOnPartitionWithNoRows; |
| } |
| |
| public Builder add(ColumnMetadata c) |
| { |
| if (c.isComplex() && c.type.isMultiCell()) |
| { |
| if (fullySelectedComplexColumns == null) |
| fullySelectedComplexColumns = new HashSet<>(); |
| fullySelectedComplexColumns.add(c); |
| } |
| return addInternal(c); |
| } |
| |
| public Builder addAll(Iterable<ColumnMetadata> columns) |
| { |
| for (ColumnMetadata column : columns) |
| add(column); |
| return this; |
| } |
| |
| private Builder addInternal(ColumnMetadata c) |
| { |
| if (c.isPrimaryKeyColumn()) |
| return this; |
| |
| if (queriedBuilder == null) |
| queriedBuilder = RegularAndStaticColumns.builder(); |
| queriedBuilder.add(c); |
| return this; |
| } |
| |
| private Builder addSubSelection(ColumnSubselection subSelection) |
| { |
| ColumnMetadata column = subSelection.column(); |
| assert column.isComplex() && column.type.isMultiCell(); |
| addInternal(column); |
| if (subSelections == null) |
| subSelections = new ArrayList<>(); |
| subSelections.add(subSelection); |
| return this; |
| } |
| |
| public Builder slice(ColumnMetadata c, CellPath from, CellPath to) |
| { |
| return addSubSelection(ColumnSubselection.slice(c, from, to)); |
| } |
| |
| public Builder select(ColumnMetadata c, CellPath elt) |
| { |
| return addSubSelection(ColumnSubselection.element(c, elt)); |
| } |
| |
| public ColumnFilter build() |
| { |
| boolean isFetchAll = metadata != null; |
| |
| RegularAndStaticColumns queried = queriedBuilder == null ? null : queriedBuilder.build(); |
| |
| // It's only ok to have queried == null in ColumnFilter if isFetchAll. So deal with the case of a selectionBuilder |
| // with nothing selected (we can at least happen on some backward compatible queries - CASSANDRA-10471). |
| if (!isFetchAll && queried == null) |
| queried = RegularAndStaticColumns.NONE; |
| |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> s = buildSubSelections(); |
| |
| if (isFetchAll) |
| { |
| // When fetchAll is enabled on pre CASSANDRA-10657 (3.4-), queried columns are not considered at all, and it |
| // is assumed that all columns are queried. CASSANDRA-10657 (3.4+) brings back skipping values of columns |
| // which are not in queried set when fetchAll is enabled. That makes exactly the same filter being |
| // interpreted in a different way on 3.4- and 3.4+. |
| // |
| // Moreover, there is no way to convert the filter with fetchAll and queried != null so that it is |
| // interpreted the same way on 3.4- because that Cassandra version does not support such filtering. |
| // |
| // In order to avoid inconsitencies in data read by 3.4- and 3.4+ we need to avoid creation of incompatible |
| // filters when the cluster contains 3.4- nodes. We do that by forcibly setting queried to null. |
| // |
| // see CASSANDRA-10657, CASSANDRA-15833, CASSANDRA-16415 |
| if (queried == null || isUpgradingFromVersionLowerThan34()) |
| { |
| return new WildCardColumnFilter(metadata.regularAndStaticColumns()); |
| } |
| |
| // pre CASSANDRA-12768 (4.0-) all static columns should be fetched along with all regular columns. |
| if (isUpgradingFromVersionLowerThan40()) |
| { |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_COLUMNS, metadata, queried, s); |
| } |
| |
| // pre CASSANDRA-16686 (4.0-RC2-) static columns where not fetched unless queried witch lead to some wrong results |
| // for some queries |
| if (!returnStaticContentOnPartitionWithNoRows || isUpgradingFromVersionLowerThan40RC2()) |
| { |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS, metadata, queried, s); |
| } |
| |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ALL_COLUMNS, metadata, queried, s); |
| } |
| |
| return SelectionColumnFilter.newInstance(FetchingStrategy.ONLY_QUERIED_COLUMNS, (TableMetadata) null, queried, s); |
| } |
| |
| private SortedSetMultimap<ColumnIdentifier, ColumnSubselection> buildSubSelections() |
| { |
| if (subSelections == null) |
| return null; |
| |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> s = TreeMultimap.create(Comparator.naturalOrder(), Comparator.naturalOrder()); |
| for (ColumnSubselection subSelection : subSelections) |
| { |
| if (fullySelectedComplexColumns == null || !fullySelectedComplexColumns.contains(subSelection.column())) |
| s.put(subSelection.column().name, subSelection); |
| } |
| |
| return s; |
| } |
| } |
| |
| /** |
| * {@code ColumnFilter} sub-class for wildcard queries. |
| * |
| * <p>The class does not rely on TableMetadata and expects a fix set of columns to prevent issues |
| * with Schema race propagation. See CASSANDRA-15899.</p> |
| */ |
| public static class WildCardColumnFilter extends ColumnFilter |
| { |
| /** |
| * The queried and fetched columns. |
| */ |
| private final RegularAndStaticColumns fetchedAndQueried; |
| |
| /** |
| * Creates a {@code ColumnFilter} for wildcard queries. |
| * |
| * <p>The class does not rely on TableMetadata and expects a fix set of columns to prevent issues |
| * with Schema race propagation. See CASSANDRA-15899.</p> |
| * |
| * @param fetchedAndQueried the fetched and queried columns |
| */ |
| private WildCardColumnFilter(RegularAndStaticColumns fetchedAndQueried) |
| { |
| this.fetchedAndQueried = fetchedAndQueried; |
| } |
| |
| @Override |
| public RegularAndStaticColumns fetchedColumns() |
| { |
| return fetchedAndQueried; |
| } |
| |
| @Override |
| public RegularAndStaticColumns queriedColumns() |
| { |
| return fetchedAndQueried; |
| } |
| |
| @Override |
| public boolean fetchesAllColumns(boolean isStatic) |
| { |
| return true; |
| } |
| |
| @Override |
| public boolean allFetchedColumnsAreQueried() |
| { |
| return true; |
| } |
| |
| @Override |
| public boolean fetches(ColumnMetadata column) |
| { |
| return true; |
| } |
| |
| @Override |
| public boolean fetchedColumnIsQueried(ColumnMetadata column) |
| { |
| return true; |
| } |
| |
| @Override |
| public boolean fetchedCellIsQueried(ColumnMetadata column, CellPath path) |
| { |
| return true; |
| } |
| |
| @Override |
| public Tester newTester(ColumnMetadata column) |
| { |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object other) |
| { |
| if (other == this) |
| return true; |
| |
| if (!(other instanceof WildCardColumnFilter)) |
| return false; |
| |
| WildCardColumnFilter w = (WildCardColumnFilter) other; |
| |
| return fetchedAndQueried.equals(w.fetchedAndQueried); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return Objects.hash(fetchedAndQueried); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "*/*"; |
| } |
| |
| public String toCQLString() |
| { |
| return "*"; |
| } |
| |
| @Override |
| public boolean isWildcard() |
| { |
| return true; |
| } |
| |
| @Override |
| protected SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections() |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * {@code ColumnFilter} sub-class for queries with selected columns. |
| * |
| * <p>The class does not rely on TableMetadata and expect a fix set of fetched columns to prevent issues |
| * with Schema race propagation. See CASSANDRA-15899.</p> |
| */ |
| public static class SelectionColumnFilter extends ColumnFilter |
| { |
| public final FetchingStrategy fetchingStrategy; |
| |
| /** |
| * The selected columns |
| */ |
| private final RegularAndStaticColumns queried; |
| |
| /** |
| * The columns that need to be fetched to be able |
| */ |
| private final RegularAndStaticColumns fetched; |
| |
| private final SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections; // can be null |
| |
| public static SelectionColumnFilter newInstance(FetchingStrategy fetchingStrategy, |
| TableMetadata metadata, |
| RegularAndStaticColumns queried, |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections) |
| { |
| assert fetchingStrategy != FetchingStrategy.ONLY_QUERIED_COLUMNS || metadata == null; |
| assert queried != null; |
| |
| return new SelectionColumnFilter(fetchingStrategy, |
| queried, |
| fetchingStrategy.getFetchedColumns(metadata, queried), |
| subSelections); |
| } |
| |
| /** |
| * Creates a {@code ColumnFilter} for queries with selected columns. |
| * |
| * <p>The class does not rely on TableMetadata and expect a fix set of columns to prevent issues |
| * with Schema race propagation. See CASSANDRA-15899.</p> |
| * |
| * @param fetchingStrategy the strategy used to select the fetched columns |
| * @param fetched the columns that must be fetched |
| * @param queried the queried columns |
| * @param subSelections the columns sub-selections |
| */ |
| public SelectionColumnFilter(FetchingStrategy fetchingStrategy, |
| RegularAndStaticColumns queried, |
| RegularAndStaticColumns fetched, |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections) |
| { |
| assert queried != null; |
| assert fetched.includes(queried); |
| |
| this.fetchingStrategy = fetchingStrategy; |
| this.queried = queried; |
| this.fetched = fetched; |
| this.subSelections = subSelections; |
| } |
| |
| @Override |
| public RegularAndStaticColumns fetchedColumns() |
| { |
| return fetched; |
| } |
| |
| @Override |
| public RegularAndStaticColumns queriedColumns() |
| { |
| return queried; |
| } |
| |
| @Override |
| public boolean fetchesAllColumns(boolean isStatic) |
| { |
| return fetchingStrategy.fetchesAllColumns(isStatic); |
| } |
| |
| @Override |
| public boolean allFetchedColumnsAreQueried() |
| { |
| return fetchingStrategy.areAllFetchedColumnsQueried(); |
| } |
| |
| @Override |
| public boolean fetches(ColumnMetadata column) |
| { |
| return fetchingStrategy.fetchesAllColumns(column.isStatic()) || fetched.contains(column); |
| } |
| |
| /** |
| * Whether the provided complex cell (identified by its column and path), which is assumed to be _fetched_ by |
| * this filter, is also _queried_ by the user. |
| * |
| * !WARNING! please be sure to understand the difference between _fetched_ and _queried_ |
| * columns that this class made before using this method. If unsure, you probably want |
| * to use the {@link #fetches} method. |
| */ |
| @Override |
| public boolean fetchedColumnIsQueried(ColumnMetadata column) |
| { |
| return fetchingStrategy.areAllFetchedColumnsQueried() || queried.contains(column); |
| } |
| |
| @Override |
| public boolean fetchedCellIsQueried(ColumnMetadata column, CellPath path) |
| { |
| assert path != null; |
| |
| // first verify that the column to which the cell belongs is queried |
| if (!fetchedColumnIsQueried(column)) |
| return false; |
| |
| if (subSelections == null) |
| return true; |
| |
| SortedSet<ColumnSubselection> s = subSelections.get(column.name); |
| // No subsection for this column means everything is queried |
| if (s.isEmpty()) |
| return true; |
| |
| for (ColumnSubselection subSel : s) |
| if (subSel.compareInclusionOf(path) == 0) |
| return true; |
| |
| return false; |
| } |
| |
| @Override |
| public Tester newTester(ColumnMetadata column) |
| { |
| if (subSelections == null || !column.isComplex()) |
| return null; |
| |
| SortedSet<ColumnSubselection> s = subSelections.get(column.name); |
| if (s.isEmpty()) |
| return null; |
| |
| return new Tester(fetchingStrategy.fetchesAllColumns(column.isStatic()), s.iterator()); |
| } |
| |
| @Override |
| protected SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections() |
| { |
| return subSelections; |
| } |
| |
| @Override |
| public boolean equals(Object other) |
| { |
| if (other == this) |
| return true; |
| |
| if (!(other instanceof SelectionColumnFilter)) |
| return false; |
| |
| SelectionColumnFilter otherCf = (SelectionColumnFilter) other; |
| |
| return otherCf.fetchingStrategy == this.fetchingStrategy && |
| Objects.equals(otherCf.queried, this.queried) && |
| Objects.equals(otherCf.fetched, this.fetched) && |
| Objects.equals(otherCf.subSelections, this.subSelections); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return Objects.hash(fetchingStrategy, queried, fetched, subSelections); |
| } |
| |
| @Override |
| public String toString() |
| { |
| String prefix = ""; |
| |
| if (fetchingStrategy.fetchesAllColumns(true)) |
| prefix = "*/"; |
| |
| if (fetchingStrategy == FetchingStrategy.ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS) |
| { |
| prefix = queried.statics.isEmpty() |
| ? "<all regulars>/" |
| : String.format("<all regulars>+%s/", toString(queried.statics.selectOrderIterator(), false)); |
| } |
| |
| return prefix + toString(queried.selectOrderIterator(), false); |
| } |
| |
| @Override |
| public String toCQLString() |
| { |
| return queried.isEmpty() ? "*" : toString(queried.selectOrderIterator(), true); |
| } |
| |
| private String toString(Iterator<ColumnMetadata> columns, boolean cql) |
| { |
| StringJoiner joiner = cql ? new StringJoiner(", ") : new StringJoiner(", ", "[", "]"); |
| |
| while (columns.hasNext()) |
| { |
| ColumnMetadata column = columns.next(); |
| String columnName = cql ? column.name.toCQLString() : String.valueOf(column.name); |
| |
| SortedSet<ColumnSubselection> s = subSelections != null |
| ? subSelections.get(column.name) |
| : Collections.emptySortedSet(); |
| |
| if (s.isEmpty()) |
| joiner.add(columnName); |
| else |
| s.forEach(subSel -> joiner.add(String.format("%s%s", columnName, subSel.toString(cql)))); |
| } |
| return joiner.toString(); |
| } |
| } |
| |
| public static class Serializer |
| { |
| // Prior to 4.0 the FETCH_ALL flag meant fetch all regular and static columns. From 4.0 onward it meant |
| // fetch all regular columns and queried static columns |
| private static final int FETCH_ALL_MASK = 0x01; |
| private static final int HAS_QUERIED_MASK = 0x02; |
| private static final int HAS_SUB_SELECTIONS_MASK = 0x04; |
| // The FETCH_ALL_STATICS flag was added in CASSANDRA-16686 to allow 4.0 to handle queries that required |
| // to return static data for empty partitions |
| private static final int FETCH_ALL_STATICS_MASK = 0x08; |
| |
| private static int makeHeaderByte(ColumnFilter selection) |
| { |
| return (selection.fetchesAllColumns(false) ? FETCH_ALL_MASK : 0) |
| | (!selection.isWildcard() ? HAS_QUERIED_MASK : 0) |
| | (selection.subSelections() != null ? HAS_SUB_SELECTIONS_MASK : 0) |
| | (selection.fetchesAllColumns(true) ? FETCH_ALL_STATICS_MASK : 0); |
| } |
| |
| public void serialize(ColumnFilter selection, DataOutputPlus out, int version) throws IOException |
| { |
| out.writeByte(makeHeaderByte(selection)); |
| |
| if (version >= MessagingService.VERSION_3014 && selection.fetchesAllColumns(false)) |
| { |
| serializeRegularAndStaticColumns(selection.fetchedColumns(), out); |
| } |
| |
| if (!selection.isWildcard()) |
| { |
| serializeRegularAndStaticColumns(selection.queriedColumns(), out); |
| } |
| |
| serializeSubSelections(selection.subSelections(), out, version); |
| } |
| |
| private void serializeSubSelections(SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections, |
| DataOutputPlus out, |
| int version) throws IOException |
| { |
| if (subSelections != null) |
| { |
| out.writeUnsignedVInt(subSelections.size()); |
| for (ColumnSubselection subSel : subSelections.values()) |
| ColumnSubselection.serializer.serialize(subSel, out, version); |
| } |
| } |
| |
| private void serializeRegularAndStaticColumns(RegularAndStaticColumns regularAndStaticColumns, |
| DataOutputPlus out) throws IOException |
| { |
| Columns.serializer.serialize(regularAndStaticColumns.statics, out); |
| Columns.serializer.serialize(regularAndStaticColumns.regulars, out); |
| } |
| |
| public ColumnFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException |
| { |
| int header = in.readUnsignedByte(); |
| // The meaning of isFetchAll is actually different for pre-4.0 versions and for 4.0+ versions |
| // In 4.0+ it meant is fetch all regulars |
| boolean isFetchAll = (header & FETCH_ALL_MASK) != 0; |
| boolean hasQueried = (header & HAS_QUERIED_MASK) != 0; |
| boolean hasSubSelections = (header & HAS_SUB_SELECTIONS_MASK) != 0; |
| boolean isFetchAllStatics = (header & FETCH_ALL_STATICS_MASK) != 0; |
| |
| RegularAndStaticColumns fetched = null; |
| RegularAndStaticColumns queried = null; |
| |
| if (isFetchAll) |
| { |
| if (version >= MessagingService.VERSION_3014) |
| { |
| fetched = deserializeRegularAndStaticColumns(in, metadata); |
| } |
| else |
| { |
| fetched = metadata.regularAndStaticColumns(); |
| } |
| } |
| |
| if (hasQueried) |
| { |
| queried = deserializeRegularAndStaticColumns(in, metadata); |
| } |
| |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections = null; |
| if (hasSubSelections) |
| { |
| subSelections = deserializeSubSelection(in, version, metadata); |
| } |
| |
| if (isFetchAll) |
| { |
| // pre CASSANDRA-10657 (3.4-), when fetchAll is enabled, queried columns are not considered at all, and it |
| // is assumed that all columns are queried. |
| if (!hasQueried || isUpgradingFromVersionLowerThan34()) |
| { |
| return new WildCardColumnFilter(fetched); |
| } |
| |
| // pre CASSANDRA-12768 (4.0-) all static columns should be fetched along with all regular columns. |
| if (isUpgradingFromVersionLowerThan40()) |
| { |
| return new SelectionColumnFilter(FetchingStrategy.ALL_COLUMNS, queried, fetched, subSelections); |
| } |
| |
| // pre CASSANDRA-16686 (4.0-RC2-) static columns where not fetched unless queried witch lead to some wrong results |
| // for some queries |
| if (!isFetchAllStatics || isUpgradingFromVersionLowerThan40RC2()) |
| { |
| return new SelectionColumnFilter(FetchingStrategy.ALL_REGULARS_AND_QUERIED_STATICS_COLUMNS, queried, fetched, subSelections); |
| } |
| |
| return new SelectionColumnFilter(FetchingStrategy.ALL_COLUMNS, queried, fetched, subSelections); |
| } |
| |
| return new SelectionColumnFilter(FetchingStrategy.ONLY_QUERIED_COLUMNS, queried, queried, subSelections); |
| } |
| |
| private RegularAndStaticColumns deserializeRegularAndStaticColumns(DataInputPlus in, |
| TableMetadata metadata) throws IOException |
| { |
| Columns statics = Columns.serializer.deserialize(in, metadata); |
| Columns regulars = Columns.serializer.deserialize(in, metadata); |
| return new RegularAndStaticColumns(statics, regulars); |
| } |
| |
| private SortedSetMultimap<ColumnIdentifier, ColumnSubselection> deserializeSubSelection(DataInputPlus in, |
| int version, |
| TableMetadata metadata) throws IOException |
| { |
| SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections = TreeMultimap.create(Comparator.naturalOrder(), Comparator.naturalOrder()); |
| int size = (int) in.readUnsignedVInt(); |
| for (int i = 0; i < size; i++) |
| { |
| ColumnSubselection subSel = ColumnSubselection.serializer.deserialize(in, version, metadata); |
| subSelections.put(subSel.column().name, subSel); |
| } |
| return subSelections; |
| } |
| |
| public long serializedSize(ColumnFilter selection, int version) |
| { |
| long size = 1; // header byte |
| |
| if (version >= MessagingService.VERSION_3014 && selection.fetchesAllColumns(false)) |
| { |
| size += regularAndStaticColumnsSerializedSize(selection.fetchedColumns()); |
| } |
| |
| if (!selection.isWildcard()) |
| { |
| size += regularAndStaticColumnsSerializedSize(selection.queriedColumns()); |
| } |
| |
| size += subSelectionsSerializedSize(selection.subSelections(), version); |
| |
| return size; |
| } |
| |
| private long regularAndStaticColumnsSerializedSize(RegularAndStaticColumns columns) |
| { |
| return Columns.serializer.serializedSize(columns.statics) |
| + Columns.serializer.serializedSize(columns.regulars); |
| } |
| |
| private long subSelectionsSerializedSize(SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections, |
| int version) |
| { |
| if (subSelections == null) |
| return 0; |
| |
| int size = TypeSizes.sizeofUnsignedVInt(subSelections.size()); |
| for (ColumnSubselection subSel : subSelections.values()) |
| size += ColumnSubselection.serializer.serializedSize(subSel, version); |
| |
| return size; |
| } |
| } |
| } |