Package org.apache.cassandra.cql3.operations

Source Code of org.apache.cassandra.cql3.operations.ListOperation$PrecisionTime

/**
* 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.operations;

import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.cassandra.cql3.ColumnNameBuilder;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.cql3.UpdateParameters;
import org.apache.cassandra.db.ColumnFamily;
import org.apache.cassandra.db.IColumn;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.UUIDGen;

public class ListOperation implements Operation
{
    // Our reference time (1 jan 2010, 00:00:00) in milliseconds.
    private static final long REFERENCE_TIME = 1262304000000L;

    /*
     * For prepend, we need to be able to generate unique but decreasing time
     * UUID, which is a bit challenging. To do that, given a time in milliseconds,
     * we adds a number representing the 100-nanoseconds precision and make sure
     * that within the same millisecond, that number is always increasing. We
     * do rely on the fact that the user will only provide decreasing
     * milliseconds timestamp for that purpose.
     */
    private static class PrecisionTime
    {
        public final long millis;
        public final int nanos;

        public PrecisionTime(long millis, int nanos)
        {
            this.millis = millis;
            this.nanos = nanos;
        }
    }

    private static final AtomicReference<PrecisionTime> last = new AtomicReference<PrecisionTime>(new PrecisionTime(Long.MAX_VALUE, 0));

    private static PrecisionTime getNextTime(long millis)
    {
        while (true)
        {
            PrecisionTime current = last.get();

            assert millis <= current.millis;
            PrecisionTime next = millis < current.millis
                    ? new PrecisionTime(millis, 0)
                    : new PrecisionTime(millis, current.nanos + 1);

            if (last.compareAndSet(current, next))
                return next;
        }
    }

    enum Kind { SET, SET_IDX, APPEND, PREPEND, DISCARD, DISCARD_IDX }

    private final List<Term> values;
    private final Kind kind;

    private ListOperation(List<Term> values, Kind kind)
    {
        this.values = Collections.unmodifiableList(values);
        this.kind = kind;
    }

    public void execute(ColumnFamily cf,
                        ColumnNameBuilder builder,
                        CollectionType validator,
                        UpdateParameters params,
                        List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
    {
        if (validator.kind != CollectionType.Kind.LIST)
            throw new InvalidRequestException("List operations are only supported on List typed columns, but " + validator + " given.");

        switch (kind)
        {
            case SET:
                cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
                doAppend(cf, builder, validator, params);
                break;
            case SET_IDX:
                doSet(cf, builder, params, validator, list);
                break;
            case APPEND:
                doAppend(cf, builder, validator, params);
                break;
            case PREPEND:
                doPrepend(cf, builder, validator, params);
                break;
            case DISCARD:
                doDiscard(cf, validator, params, list);
                break;
            case DISCARD_IDX:
                doDiscardIdx(cf, params, list);
                break;
            default:
                throw new AssertionError("Unsupported List operation: " + kind);
        }
    }

    public void execute(ColumnFamily cf, ColumnNameBuilder builder, AbstractType<?> validator, UpdateParameters params) throws InvalidRequestException
    {
        throw new InvalidRequestException("List operations are only supported on List typed columns, but " + validator + " given.");
    }

    private void doSet(ColumnFamily cf, ColumnNameBuilder builder, UpdateParameters params, CollectionType validator, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
    {
        int idx = validateListIdx(values.get(0), list);
        Term value = values.get(1);

        ByteBuffer name = list.get(idx).right.name();
        cf.addColumn(params.makeColumn(name, value.getByteBuffer(validator.valueComparator(), params.variables)));
    }

    private void doAppend(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
    {
        for (int i = 0; i < values.size(); i++)
        {
            ColumnNameBuilder b = i == values.size() - 1 ? builder : builder.copy();
            ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
            ByteBuffer name = b.add(uuid).build();
            cf.addColumn(params.makeColumn(name, values.get(i).getByteBuffer(validator.valueComparator(), params.variables)));
        }
    }

    private void doPrepend(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
    {
        long time = REFERENCE_TIME - (System.currentTimeMillis() - REFERENCE_TIME);

        // We do the loop in reverse order because getNext() will create increasing time but we want the last
        // value in the prepended list to have the lower time
        for (int i = values.size() - 1; i >= 0; i--)
        {
            ColumnNameBuilder b = i == 0 ? builder : builder.copy();
            PrecisionTime pt = getNextTime(time);
            ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(pt.millis, pt.nanos));
            ByteBuffer name = b.add(uuid).build();
            cf.addColumn(params.makeColumn(name, values.get(i).getByteBuffer(validator.valueComparator(), params.variables)));
        }
    }

    private void doDiscard(ColumnFamily cf, CollectionType validator, UpdateParameters params, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
    {
        if (list == null)
            return;

        Set<ByteBuffer> toDiscard = new HashSet<ByteBuffer>();

        for (Term value : values)
            toDiscard.add(value.getByteBuffer(validator.valueComparator(), params.variables));

        for (Pair<ByteBuffer, IColumn> p : list)
        {
            IColumn c = p.right;
            if (toDiscard.contains(c.value()))
                cf.addColumn(params.makeTombstone(c.name()));
        }
    }

    private void doDiscardIdx(ColumnFamily cf, UpdateParameters params, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
    {
        int idx = validateListIdx(values.get(0), list);
        cf.addColumn(params.makeTombstone(list.get(idx).right.name()));
    }

    public List<Term> getValues()
    {
        return values;
    }

    public boolean requiresRead()
    {
        return kind == Kind.DISCARD || kind == Kind.DISCARD_IDX || kind == Kind.SET || kind == Kind.SET_IDX;
    }

    public Type getType()
    {
        return Type.LIST;
    }

    /* Utility methods */

    public static Operation Set(List<Term> values)
    {
        return new ListOperation(values, Kind.SET);
    }

    public static Operation SetIndex(List<Term> values)
    {
        return new ListOperation(values, Kind.SET_IDX);
    }

    public static Operation Append(List<Term> values)
    {
        return new ListOperation(values, Kind.APPEND);
    }

    public static Operation Prepend(List<Term> values)
    {
        return new ListOperation(values, Kind.PREPEND);
    }

    public static Operation Discard(List<Term> values)
    {
        return new ListOperation(values, Kind.DISCARD);
    }

    public static Operation DiscardKey(List<Term> values)
    {
        return new ListOperation(values, Kind.DISCARD_IDX);
    }

    private int validateListIdx(Term value, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
    {
        try
        {
            if (value.getType() != Term.Type.INTEGER)
                throw new InvalidRequestException(String.format("Invalid argument %s for %s, must be an integer.", value.getText(), getType()));

            int idx = Integer.parseInt(value.getText());
            if (list == null || list.size() <= idx)
                throw new InvalidRequestException(String.format("Invalid index %d, list has size %d", idx, list == null ? 0 : list.size()));

            return idx;
        }
        catch (NumberFormatException e)
        {
            // This should not happen, unless we screwed up the parser
            throw new RuntimeException();
        }
    }
}
TOP

Related Classes of org.apache.cassandra.cql3.operations.ListOperation$PrecisionTime

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.