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

org.apache.cassandra.cql3.SuperColumnCompatibility Maven / Gradle / Ivy

There is a newer version: 4.3.1.0
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.cql3;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction;
import org.apache.cassandra.cql3.restrictions.SingleRestriction;
import org.apache.cassandra.cql3.restrictions.TermSlice;
import org.apache.cassandra.cql3.selection.Selection;
import org.apache.cassandra.cql3.statements.Bound;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.Columns;
import org.apache.cassandra.db.CompactTables;
import org.apache.cassandra.db.PartitionColumns;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;

import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
import static org.apache.cassandra.cql3.statements.SelectStatement.getComponents;

/**
 * Class incapsulating the helper logic to handle SELECT / UPDATE / INSERT special-cases related
 * to SuperColumn tables in applicable scenarios.
 *
 * SuperColumn families have a special layout and are represented as a Map internally. These tables
 * have two special columns (called `column2` and `value` by default):
 *
 *   * `column2`, {@link CFMetaData#superCfValueColumn}, a key of the SuperColumn map, exposed as a
 *   REGULAR column, but stored in schema tables as a CLUSTERING column to make a distinction from
 *   the SC value column in case of renames.
 *   * `value`, {@link CFMetaData#compactValueColumn()}, a value of the SuperColumn map, exposed and
 *   stored as a REGULAR column
 *
 * These columns have to be translated to this internal representation as key and value, correspondingly.
 *
 * In CQL terms, the SuperColumn families is encoded with:
 *
 *   CREATE TABLE super (
 *      key [key_validation_class],
 *      super_column_name [comparator],
 *      [column_metadata_1] [type1],
 *      ...,
 *      [column_metadata_n] [type1],
 *      "" map<[sub_comparator], [default_validation_class]>
 *      PRIMARY KEY (key, super_column_name)
 *   )
 *
 * In other words, every super column is encoded by a row. That row has one column for each defined
 * "column_metadata", but it also has a special map column (whose name is the empty string as this is
 * guaranteed to never conflict with a user-defined "column_metadata") which stores the super column
 * "dynamic" sub-columns.
 *
 * On write path, `column2` and `value` columns are translated to the key and value of the
 * underlying map. During the read, the inverse conversion is done. Deletes are converted into
 * discards by the key in the underlying map. Counters are handled by translating an update to a
 * counter update with a cell path. See {@link SuperColumnRestrictions} for the details.
 *
 * Since non-dense SuperColumn families do not modify the contents of the internal map through in CQL
 * and do not expose this via CQL either, reads, writes and deletes are handled normally.
 *
 * Sidenote: a _dense_ SuperColumn Familiy is the one that has no added REGULAR columns.
 */
public class SuperColumnCompatibility
{
    // We use an empty value for the 1) this can't conflict with a user-defined column and 2) this actually
    // validate with any comparator which makes it convenient for columnDefinitionComparator().
    public static final ByteBuffer SUPER_COLUMN_MAP_COLUMN = ByteBufferUtil.EMPTY_BYTE_BUFFER;
    public static final String SUPER_COLUMN_MAP_COLUMN_STR = UTF8Type.instance.compose(SUPER_COLUMN_MAP_COLUMN);

    /**
     * Dense flag might have been incorrectly set if the node was upgraded from 2.x before CASSANDRA-12373.
     *
     * For 3.x created tables, the flag is set correctly in ThriftConversion code.
     */
    public static boolean recalculateIsDense(Columns columns)
    {
        return columns.size() == 1 && columns.getComplex(0).name.toString().isEmpty();
    }

    /**
     * For _dense_ SuperColumn Families, the supercolumn key column has to be translated to the collection subselection
     * query in order to avoid reading an entire collection and then filtering out the results.
     */
    public static ColumnFilter getColumnFilter(CFMetaData cfm, QueryOptions queryOptions, SuperColumnRestrictions restrictions)
    {
        assert cfm.isSuper() && cfm.isDense();

        ColumnFilter.Builder builder = ColumnFilter.selectionBuilder();
        builder.add(cfm.compactValueColumn());

        if (restrictions.keySliceRestriction != null)
        {
            SingleColumnRestriction.SuperColumnKeySliceRestriction restriction = restrictions.keySliceRestriction;
            TermSlice slice = restriction.slice;

            ByteBuffer start = slice.hasBound(Bound.START) ? slice.bound(Bound.START).bindAndGet(queryOptions) : null;
            ByteBuffer end = slice.hasBound(Bound.END) ? slice.bound(Bound.END).bindAndGet(queryOptions) : null;

            builder.slice(cfm.compactValueColumn(),
                          start == null ? CellPath.BOTTOM : CellPath.create(start),
                          end == null ? CellPath.TOP : CellPath.create(end));
        }
        else if (restrictions.keyEQRestriction != null)
        {
            SingleColumnRestriction.SuperColumnKeyEQRestriction restriction = restrictions.keyEQRestriction;
            ByteBuffer value = restriction.bindValue(queryOptions);
            builder.select(cfm.compactValueColumn(), CellPath.create(value));
        }
        else if (restrictions.keyINRestriction != null)
        {
            SingleColumnRestriction.SuperColumnKeyINRestriction cast = restrictions.keyINRestriction;
            Set keyINRestrictionValues = new TreeSet(((MapType) cfm.compactValueColumn().type).getKeysType());
            keyINRestrictionValues.addAll(cast.getValues(queryOptions));

            for (ByteBuffer value : keyINRestrictionValues)
                builder.select(cfm.compactValueColumn(), CellPath.create(value));
        }
        else if (restrictions.multiEQRestriction != null)
        {
            SingleColumnRestriction.SuperColumnMultiEQRestriction restriction = restrictions.multiEQRestriction;
            ByteBuffer value = restriction.secondValue;
            builder.select(cfm.compactValueColumn(), CellPath.create(value));
        }

        return builder.build();
    }

    /**
     * For _dense_ SuperColumn Families.
     *
     * On read path, instead of writing row per map, we have to write a row per key/value pair in map.
     *
     * For example:
     *
     *   | partition-key | clustering-key | { key1: value1, key2: value2 } |
     *
     * Will be translated to:
     *
     *   | partition-key | clustering-key | key1 | value1 |
     *   | partition-key | clustering-key | key2 | value2 |
     *
     */
    public static void processPartition(CFMetaData cfm, Selection selection, RowIterator partition, Selection.ResultSetBuilder result, ProtocolVersion protocolVersion,
                                        SuperColumnRestrictions restrictions, QueryOptions queryOptions)
    {
        assert cfm.isDense();
        ByteBuffer[] keyComponents = getComponents(cfm, partition.partitionKey());

        int nowInSeconds = FBUtilities.nowInSeconds();
        while (partition.hasNext())
        {
            Row row = partition.next();

            ComplexColumnData ccd = row.getComplexColumnData(cfm.compactValueColumn());

            if (ccd == null)
                continue;

            Iterator cellIter = ccd.iterator();

            outer:
            while (cellIter.hasNext())
            {
                Cell cell = cellIter.next();
                ByteBuffer superColumnKey = cell.path().get(0);

                if (restrictions != null)
                {
                    // Slice on SuperColumn key
                    if (restrictions.keySliceRestriction != null)
                    {
                        for (Bound bound : Bound.values())
                        {
                            if (restrictions.keySliceRestriction.hasBound(bound) &&
                                !restrictions.keySliceRestriction.isInclusive(bound))
                            {
                                ByteBuffer excludedValue = restrictions.keySliceRestriction.bindValue(queryOptions);
                                if (excludedValue.equals(superColumnKey))
                                    continue outer;
                            }
                        }
                    }

                    // Multi-column restriction on clustering+SuperColumn key
                    if (restrictions.multiSliceRestriction != null &&
                        cfm.comparator.compare(row.clustering(), Clustering.make(restrictions.multiSliceRestriction.firstValue)) == 0)
                    {
                        AbstractType t = ((MapType) cfm.compactValueColumn().type).getKeysType();
                        int cmp = t.compare(superColumnKey, restrictions.multiSliceRestriction.secondValue);

                        if ((cmp == 0 && !restrictions.multiSliceRestriction.trueInclusive) ||     // EQ
                            (restrictions.multiSliceRestriction.hasBound(Bound.END) && cmp > 0) || // LT
                            (restrictions.multiSliceRestriction.hasBound(Bound.START) && cmp < 0)) // GT
                            continue outer;
                    }
                }

                Row staticRow = partition.staticRow();
                result.newRow(partition.partitionKey(), staticRow.clustering());

                for (ColumnDefinition def : selection.getColumns())
                {
                    if (cfm.isSuperColumnKeyColumn(def))
                    {
                        result.add(superColumnKey);
                    }
                    else if (cfm.isSuperColumnValueColumn(def))
                    {
                        result.add(cell, nowInSeconds);
                    }
                    else
                    {
                        switch (def.kind)
                        {
                            case PARTITION_KEY:
                                result.add(keyComponents[def.position()]);
                                break;
                            case CLUSTERING:
                                result.add(row.clustering().get(def.position()));
                                break;
                            case REGULAR:
                            case STATIC:
                                throw new AssertionError(String.format("Invalid column '%s' found in SuperColumn table", def.name.toString()));
                        }
                    }
                }
            }
        }
    }

    /**
     * For _dense_ SuperColumn Families.
     *
     * On the write path, we have to do combine the columns into a key/value pair:
     *
     * So inserting a row:
     *
     *     | partition-key | clustering-key | key1 | value1 |
     *
     * Would result into:
     *
     *     | partition-key | clustering-key | {key1: value1} |
     *
     * or adding / overwriting the value for `key1`.
     */
    public static void prepareInsertOperations(CFMetaData cfm,
                                               List columnNames,
                                               WhereClause.Builder whereClause,
                                               List columnValues,
                                               VariableSpecifications boundNames,
                                               Operations operations)
    {
        List defs = new ArrayList<>(columnNames.size());
        for (int i = 0; i < columnNames.size(); i++)
        {
            ColumnDefinition id = columnNames.get(i).prepare(cfm);
            defs.add(id);
        }

        prepareInsertOperations(cfm, defs, boundNames, columnValues, whereClause, operations);
    }

    /**
     * For _dense_ SuperColumn Families.
     *
     * {@link #prepareInsertOperations(CFMetaData, List, VariableSpecifications, List, WhereClause.Builder, Operations)},
     * but for INSERT JSON queries
     */
    public static void prepareInsertJSONOperations(CFMetaData cfm,
                                                   List defs,
                                                   VariableSpecifications boundNames,
                                                   Json.Prepared prepared,
                                                   WhereClause.Builder whereClause,
                                                   Operations operations)
    {
        List columnValues = new ArrayList<>(defs.size());
        for (ColumnDefinition def : defs)
            columnValues.add(prepared.getRawTermForColumn(def, true));

        prepareInsertOperations(cfm, defs, boundNames, columnValues, whereClause, operations);
    }

    private static void prepareInsertOperations(CFMetaData cfm,
                                                List defs,
                                                VariableSpecifications boundNames,
                                                List columnValues,
                                                WhereClause.Builder whereClause,
                                                Operations operations)
    {
        assert cfm.isDense();
        assert defs.size() == columnValues.size();

        Term.Raw superColumnKey = null;
        Term.Raw superColumnValue = null;

        for (int i = 0, size = defs.size(); i < size; i++)
        {
            ColumnDefinition def = defs.get(i);
            Term.Raw raw = columnValues.get(i);

            if (cfm.isSuperColumnKeyColumn(def))
            {
                superColumnKey = raw;
                collectMarkerSpecifications(raw, boundNames, def);
            }
            else if (cfm.isSuperColumnValueColumn(def))
            {
                superColumnValue = raw;
                collectMarkerSpecifications(raw, boundNames, def);
            }
            else if (def.isPrimaryKeyColumn())
            {
                whereClause.add(new SingleColumnRelation(ColumnDefinition.Raw.forColumn(def), Operator.EQ, raw));
            }
            else
            {
                throw invalidRequest("Invalid column {} in where clause");
            }
        }

        checkTrue(superColumnValue != null,
                  "Column value is mandatory for SuperColumn tables");
        checkTrue(superColumnKey != null,
                  "Column key is mandatory for SuperColumn tables");

        Operation operation = new Operation.SetElement(superColumnKey, superColumnValue).prepare(cfm, cfm.compactValueColumn());
        operations.add(operation);
    }

    /**
     * Collect the marker specifications for the bound columns manually, since the operations on a column are
     * converted to the operations on the collection element.
     */
    private static void collectMarkerSpecifications(Term.Raw raw, VariableSpecifications boundNames, ColumnDefinition def)
    {
        if (raw instanceof AbstractMarker.Raw)
            boundNames.add(((AbstractMarker.Raw) raw).bindIndex(), def);
    }

    /**
     * For _dense_ SuperColumn Families.
     *
     * During UPDATE operation, the update by clustering (with correponding relation in WHERE clause)
     * has to be substituted with an update to the map that backs the given SuperColumn.
     *
     * For example, an update such as:
     *
     *     UPDATE ... SET value = 'value1' WHERE key = 'pk' AND column1 = 'ck' AND column2 = 'mk'
     *
     * Will update the value under key 'mk' in the map, backing the SuperColumn, located in the row
     * with clustering 'ck' in the partition with key 'pk'.
     */
    public static WhereClause prepareUpdateOperations(CFMetaData cfm,
                                                      WhereClause whereClause,
                                                      List> updates,
                                                      VariableSpecifications boundNames,
                                                      Operations operations)
    {
        assert cfm.isDense();
        Term.Raw superColumnKey = null;
        Term.Raw superColumnValue = null;

        List newRelations = new ArrayList<>(whereClause.relations.size());
        for (int i = 0; i < whereClause.relations.size(); i++)
        {
            SingleColumnRelation relation = (SingleColumnRelation) whereClause.relations.get(i);
            ColumnDefinition def = relation.getEntity().prepare(cfm);

            if (cfm.isSuperColumnKeyColumn(def))
            {
                superColumnKey = relation.getValue();
                collectMarkerSpecifications(superColumnKey, boundNames, def);
            }
            else
            {
                newRelations.add(relation);
            }
        }

        checkTrue(superColumnKey != null,
                  "Column key is mandatory for SuperColumn tables");

        for (Pair entry : updates)
        {
            ColumnDefinition def = entry.left.prepare(cfm);

            if (!cfm.isSuperColumnValueColumn(def))
                throw invalidRequest("Column `%s` of type `%s` found in SET part", def.name, def.type.asCQL3Type());

            Operation operation;

            if (entry.right instanceof Operation.Addition)
            {
                Operation.Addition op = (Operation.Addition) entry.right;
                superColumnValue = op.value();

                operation = new Operation.ElementAddition(superColumnKey, superColumnValue).prepare(cfm, cfm.compactValueColumn());
            }
            else if (entry.right instanceof Operation.Substraction)
            {
                Operation.Substraction op = (Operation.Substraction) entry.right;
                superColumnValue = op.value();

                operation = new Operation.ElementSubtraction(superColumnKey, superColumnValue).prepare(cfm, cfm.compactValueColumn());
            }
            else if (entry.right instanceof Operation.SetValue)
            {
                Operation.SetValue op = (Operation.SetValue) entry.right;
                superColumnValue = op.value();

                operation = new Operation.SetElement(superColumnKey, superColumnValue).prepare(cfm, cfm.compactValueColumn());
            }
            else
            {
                throw invalidRequest("Invalid operation `%s` on column `%s` of type `%s` found in SET part", entry.right, def.name, def.type.asCQL3Type());
            }

            collectMarkerSpecifications(superColumnValue, boundNames, def);
            operations.add(operation);
        }

        checkTrue(superColumnValue != null,
                  "Column value is mandatory for SuperColumn tables");

        return newRelations.size() != whereClause.relations.size() ? whereClause.copy(newRelations) : whereClause;
    }

    /**
     * Rebuilds LWT conditions on SuperColumn _value_ column.
     *
     * Conditions have to be changed to correspond the internal representation of SuperColumn value, since it's not
     * a separate column, but a value in a hidden compact value column.
     */
    public static Conditions rebuildLWTColumnConditions(Conditions conditions, CFMetaData cfm, WhereClause whereClause)
    {
        if (conditions.isEmpty() || conditions.isIfExists() || conditions.isIfNotExists())
            return conditions;

        ColumnConditions.Builder builder = ColumnConditions.newBuilder();
        Collection columnConditions = ((ColumnConditions) conditions).columnConditions();

        Pair superColumnKeyRelation = SuperColumnCompatibility.getSuperColumnKeyRelation(whereClause.relations, cfm);

        checkNotNull(superColumnKeyRelation,
                     "Lightweight transactions on SuperColumn tables are only supported with supplied SuperColumn key");

        for (ColumnCondition columnCondition : columnConditions)
        {
            checkTrue(cfm.isSuperColumnValueColumn(columnCondition.column),
                      "Lightweight transactions are only supported on the value column of SuperColumn tables");

            Term.Raw value = superColumnKeyRelation.right.getValue();
            Term collectionElemnt = value instanceof AbstractMarker.Raw ?
                                    new Constants.Marker(((AbstractMarker.Raw) value).bindIndex(),
                                                         superColumnKeyRelation.left) :
                                    value.prepare(cfm.ksName, superColumnKeyRelation.left);
            builder.add(ColumnCondition.condition(cfm.compactValueColumn(),
                                                  collectionElemnt,
                                                  columnCondition.value(), columnCondition.operator));
        }

        return builder.build();
    }

    /**
     * Returns a relation on the SuperColumn key
     */
    private static Pair getSuperColumnKeyRelation(List relations, CFMetaData cfm)
    {
        for (int i = 0; i < relations.size(); i++)
        {
            SingleColumnRelation relation = (SingleColumnRelation) relations.get(i);
            ColumnDefinition def = relation.getEntity().prepare(cfm);

            if (cfm.isSuperColumnKeyColumn(def))
                return Pair.create(def, relation);
        }
        return null;
    }

    /**
     * For _dense_ SuperColumn Families.
     *
     * Delete, when the "regular" columns are present, have to be translated into
     * deletion of value in the internal map by key.
     *
     * For example, delete such as:
     *
     *     DELETE FROM ... WHERE key = 'pk' AND column1 = 'ck' AND column2 = 'mk'
     *
     * Will delete a value under 'mk' from the map, located in the row with clustering key 'ck' in the partition
     * with key 'pk'.
     */
    public static WhereClause prepareDeleteOperations(CFMetaData cfm,
                                                      WhereClause whereClause,
                                                      VariableSpecifications boundNames,
                                                      Operations operations)
    {
        assert cfm.isDense();
        List newRelations = new ArrayList<>(whereClause.relations.size());

        for (int i = 0; i < whereClause.relations.size(); i++)
        {
            Relation orig = whereClause.relations.get(i);

            checkFalse(orig.isMultiColumn(),
                       "Multi-column relations cannot be used in WHERE clauses for UPDATE and DELETE statements: %s", orig);
            checkFalse(orig.onToken(),
                       "Token relations cannot be used in WHERE clauses for UPDATE and DELETE statements: %s", orig);

            SingleColumnRelation relation = (SingleColumnRelation) orig;
            ColumnDefinition def = relation.getEntity().prepare(cfm);

            if (cfm.isSuperColumnKeyColumn(def))
            {
                Term.Raw value = relation.getValue();

                if (value instanceof AbstractMarker.Raw)
                    boundNames.add(((AbstractMarker.Raw) value).bindIndex(), def);

                Operation operation = new Maps.DiscarderByKey(cfm.compactValueColumn(), value.prepare(cfm.ksName, def));
                operations.add(operation);
            }
            else
            {
                newRelations.add(relation);
            }
        }

        return newRelations.size() != whereClause.relations.size() ? whereClause.copy(newRelations) : whereClause;
    }

    /**
     * Create a column name generator for SuperColumns
     */
    public static CompactTables.DefaultNames columnNameGenerator(List partitionKeyColumns,
                                                                 List clusteringColumns,
                                                                 PartitionColumns partitionColumns)
    {
        Set names = new HashSet<>();
        // If the clustering column was renamed, the supercolumn key's default nname still can't be `column1` (SuperColumn
        // key renames are handled separately by looking up an existing column).
        names.add("column1");
        for (ColumnDefinition columnDefinition: partitionKeyColumns)
            names.add(columnDefinition.name.toString());
        for (ColumnDefinition columnDefinition: clusteringColumns)
            names.add(columnDefinition.name.toString());
        for (ColumnDefinition columnDefinition: partitionColumns)
            names.add(columnDefinition.name.toString());

        return CompactTables.defaultNameGenerator(names);
    }

    /**
     * Find a SuperColumn key column if it's available (for example, when it was renamed) or create one with a default name.
     */
    public static ColumnDefinition getSuperCfKeyColumn(CFMetaData cfm, List clusteringColumns, CompactTables.DefaultNames defaultNames)
    {
        assert cfm.isDense();

        MapType mapType = (MapType) cfm.compactValueColumn().type;
        // Pre CASSANDRA-12373 3.x-created supercolumn family
        if (clusteringColumns.size() == 1)
        {
            // create a new one with a default name
            ColumnIdentifier identifier = ColumnIdentifier.getInterned(defaultNames.defaultClusteringName(), true);
            return new ColumnDefinition(cfm.ksName, cfm.cfName, identifier, mapType.getKeysType(), ColumnDefinition.NO_POSITION, ColumnDefinition.Kind.REGULAR);
        }

        // Upgrade path: table created in 2.x, handle pre-created columns and/or renames.
        assert clusteringColumns.size() == 2 : clusteringColumns;
        ColumnDefinition cd = clusteringColumns.get(1);

        assert cd.type.equals(mapType.getKeysType()) : cd.type + " != " + mapType.getKeysType();
        return new ColumnDefinition(cfm.ksName, cfm.cfName, cd.name, mapType.getKeysType(), ColumnDefinition.NO_POSITION, ColumnDefinition.Kind.REGULAR);
    }

    /**
     * Find a SuperColumn value column if it's available (for example, when it was renamed) or create one with a default name.
     */
    public static ColumnDefinition getSuperCfValueColumn(CFMetaData cfm, PartitionColumns partitionColumns, ColumnDefinition superCfKeyColumn, CompactTables.DefaultNames defaultNames)
    {
        assert cfm.isDense();

        MapType mapType = (MapType) cfm.compactValueColumn().type;
        for (ColumnDefinition def: partitionColumns.regulars)
        {
            if (!def.name.bytes.equals(SUPER_COLUMN_MAP_COLUMN) && def.type.equals(mapType.getValuesType()) && !def.equals(superCfKeyColumn))
                return def;
        }

        ColumnIdentifier identifier = ColumnIdentifier.getInterned(defaultNames.defaultCompactValueName(), true);
        return new ColumnDefinition(cfm.ksName, cfm.cfName, identifier, mapType.getValuesType(), ColumnDefinition.NO_POSITION, ColumnDefinition.Kind.REGULAR);
    }

    /**
     * SuperColumn key is stored in {@link CFMetaData#columnMetadata} as a clustering column (to make sure we can make
     * a distinction between the SuperColumn key and SuperColumn value columns, especially when they have the same type
     * and were renamed), but exposed as {@link CFMetaData#superCfKeyColumn} as a regular column to be compatible with
     * the storage engine.
     *
     * This remapping is necessary to facilitate the column metadata part.
     */
    public static ColumnDefinition getSuperCfSschemaRepresentation(ColumnDefinition superCfKeyColumn)
    {
        return new ColumnDefinition(superCfKeyColumn.ksName, superCfKeyColumn.cfName, superCfKeyColumn.name, superCfKeyColumn.type, 1, ColumnDefinition.Kind.CLUSTERING);
    }

    public static boolean isSuperColumnMapColumn(ColumnDefinition column)
    {
        return column.isRegular() && column.name.bytes.equals(SuperColumnCompatibility.SUPER_COLUMN_MAP_COLUMN);
    }

    public static ColumnDefinition getCompactValueColumn(PartitionColumns columns)
    {
        for (ColumnDefinition column : columns.regulars)
        {
            if (isSuperColumnMapColumn(column))
                return column;
        }
        throw new AssertionError("Invalid super column table definition, no 'dynamic' map column");
    }

    /**
     * Restrictions are the trickiest part of the SuperColumn integration.
     * See specific docs on each field. For the purpose of this doc, the "default" column names are used,
     * `column2` and `value`. Detailed description and semantics of these fields can be found in this class'
     * header comment.
     */
    public static class SuperColumnRestrictions
    {
        /**
         * Restrictions in the form of:
         *   ... AND (column1, column2) > ('value1', 1)
         * Multi-column restrictions. `column1` will be handled normally by the clustering bounds,
         * and `column2` value has to be "saved" and filtered out in `processPartition`, as there's no
         * direct mapping of multi-column restrictions to clustering + cell path. The first row
         * is special-cased to make sure the semantics of multi-column restrictions are preserved.
         */
        private final SingleColumnRestriction.SuperColumnMultiSliceRestriction multiSliceRestriction;

        /**
         * Restrictions in the form of:
         *   ... AND (column1, column2) = ('value1', 1)
         * Multi-column restriction with EQ does have a direct mapping: `column1` will be handled
         * normally by the clustering bounds, and the `column2` will be special-cased by the
         * {@link #getColumnFilter(CFMetaData, QueryOptions, SuperColumnRestrictions)} as a collection path lookup.
         */
        private final SingleColumnRestriction.SuperColumnMultiEQRestriction multiEQRestriction;

        /**
         * Restrictions in the form of:
         *   ... AND column2 >= 5
         * For non-filtering cases (when the preceding clustering column and a partition key are
         * restricted), will be handled in {@link #getColumnFilter(CFMetaData, QueryOptions, SuperColumnRestrictions)}
         * like an inclusive bounds lookup.
         *
         * For the restrictions taking a form of
         *   ... AND column2 > 5
         * (non-inclusive ones), the items that match `=` will be filtered out
         * by {@link #processPartition(CFMetaData, Selection, RowIterator, Selection.ResultSetBuilder, ProtocolVersion, SuperColumnRestrictions, QueryOptions)}
         *
         * Unfortunately, there are no good ways to do it other than here:
         * {@link RowFilter} can't be used in this case, since the complex collection cells are not yet rows by that
         * point.
         * {@link ColumnFilter} (which is used for inclusive slices) can't be changed to support exclusive slices as it would
         * require a protocol change in order to add a Kind. So exclusive slices are a combination of inclusive plus
         * an ad-hoc filter.
         */
        private final SingleColumnRestriction.SuperColumnKeySliceRestriction keySliceRestriction;

        /**
         * Restrictions in the form of:
         *   ... AND column2 IN (1, 2, 3)
         * For non-filtering cases (when the preceeding clustering column and a partition key are
         * restricted), are handled in {@link #getColumnFilter(CFMetaData, QueryOptions, SuperColumnRestrictions)} by
         * adding multiple collection paths to the {@link ColumnFilter}
         */
        private final SingleColumnRestriction.SuperColumnKeyINRestriction keyINRestriction;

        /**
         * Restrictions in the form of:
         *   ... AND column2 = 1
         * For non-filtering cases (when the preceeding clustering column and a partition key are
         * restricted), will be handled by converting the restriction to the column filter on
         * the collection key in {@link #getColumnFilter(CFMetaData, QueryOptions, SuperColumnRestrictions)}
         */
        private final SingleColumnRestriction.SuperColumnKeyEQRestriction keyEQRestriction;

        public SuperColumnRestrictions(Iterator restrictions)
        {
            // In order to keep the fields final, assignments have to be done outside the loop
            SingleColumnRestriction.SuperColumnMultiSliceRestriction multiSliceRestriction = null;
            SingleColumnRestriction.SuperColumnKeySliceRestriction keySliceRestriction = null;
            SingleColumnRestriction.SuperColumnKeyINRestriction keyINRestriction = null;
            SingleColumnRestriction.SuperColumnMultiEQRestriction multiEQRestriction = null;
            SingleColumnRestriction.SuperColumnKeyEQRestriction keyEQRestriction = null;

            while (restrictions.hasNext())
            {
                SingleRestriction restriction = restrictions.next();

                if (restriction instanceof SingleColumnRestriction.SuperColumnMultiSliceRestriction)
                    multiSliceRestriction = (SingleColumnRestriction.SuperColumnMultiSliceRestriction) restriction;
                else if (restriction instanceof SingleColumnRestriction.SuperColumnKeySliceRestriction)
                    keySliceRestriction = (SingleColumnRestriction.SuperColumnKeySliceRestriction) restriction;
                else if (restriction instanceof SingleColumnRestriction.SuperColumnKeyINRestriction)
                    keyINRestriction = (SingleColumnRestriction.SuperColumnKeyINRestriction) restriction;
                else if (restriction instanceof SingleColumnRestriction.SuperColumnMultiEQRestriction)
                    multiEQRestriction = (SingleColumnRestriction.SuperColumnMultiEQRestriction) restriction;
                else if (restriction instanceof SingleColumnRestriction.SuperColumnKeyEQRestriction)
                    keyEQRestriction = (SingleColumnRestriction.SuperColumnKeyEQRestriction) restriction;
            }

            this.multiSliceRestriction = multiSliceRestriction;
            this.keySliceRestriction = keySliceRestriction;
            this.keyINRestriction = keyINRestriction;
            this.multiEQRestriction = multiEQRestriction;
            this.keyEQRestriction = keyEQRestriction;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy