From e202a13fffed89ff6b319ed754e2b1016c7cd67c Mon Sep 17 00:00:00 2001 From: Tomasz Grabiec Date: Wed, 4 Mar 2015 21:26:24 +0100 Subject: [PATCH] cql3: Convert SingleColumnPrimaryKeyRestrictions --- .../SingleColumnPrimaryKeyRestrictions.java | 312 ------------------ .../single_column_primary_key_restrictions.hh | 272 +++++++++++++++ unimplemented.cc | 1 + unimplemented.hh | 3 +- 4 files changed, 275 insertions(+), 313 deletions(-) delete mode 100644 cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java create mode 100644 cql3/restrictions/single_column_primary_key_restrictions.hh diff --git a/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java b/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java deleted file mode 100644 index 5c8386e444..0000000000 --- a/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * 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.restrictions; - -import java.nio.ByteBuffer; -import java.util.*; - -import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.statements.Bound; -import org.apache.cassandra.db.IndexExpression; -import org.apache.cassandra.db.composites.CBuilder; -import org.apache.cassandra.db.composites.CType; -import org.apache.cassandra.db.composites.Composite; -import org.apache.cassandra.db.composites.Composite.EOC; -import org.apache.cassandra.db.composites.Composites; -import org.apache.cassandra.db.composites.CompositesBuilder; -import org.apache.cassandra.db.index.SecondaryIndexManager; -import org.apache.cassandra.exceptions.InvalidRequestException; - -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; - -/** - * A set of single column restrictions on a primary key part (partition key or clustering key). - */ -final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestrictions -{ - /** - * The composite type. - */ - private final CType ctype; - - /** - * The restrictions. - */ - private final SingleColumnRestrictions restrictions; - - /** - * true if the restrictions are corresponding to an EQ, false otherwise. - */ - private boolean eq; - - /** - * true if the restrictions are corresponding to an IN, false otherwise. - */ - private boolean in; - - /** - * true if the restrictions are corresponding to a Slice, false otherwise. - */ - private boolean slice; - - /** - * true if the restrictions are corresponding to a Contains, false otherwise. - */ - private boolean contains; - - public SingleColumnPrimaryKeyRestrictions(CType ctype) - { - this.ctype = ctype; - this.restrictions = new SingleColumnRestrictions(); - this.eq = true; - } - - private SingleColumnPrimaryKeyRestrictions(SingleColumnPrimaryKeyRestrictions primaryKeyRestrictions, - SingleColumnRestriction restriction) throws InvalidRequestException - { - this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction); - this.ctype = primaryKeyRestrictions.ctype; - - if (!primaryKeyRestrictions.isEmpty()) - { - ColumnDefinition lastColumn = primaryKeyRestrictions.restrictions.lastColumn(); - ColumnDefinition newColumn = restriction.getColumnDef(); - - checkFalse(primaryKeyRestrictions.isSlice() && newColumn.position() > lastColumn.position(), - "Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", - newColumn.name, - lastColumn.name); - - if (newColumn.position() < lastColumn.position()) - checkFalse(restriction.isSlice(), - "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", - restrictions.nextColumn(newColumn).name, - newColumn.name); - } - - if (restriction.isSlice() || primaryKeyRestrictions.isSlice()) - this.slice = true; - else if (restriction.isContains() || primaryKeyRestrictions.isContains()) - this.contains = true; - else if (restriction.isIN()) - this.in = true; - else - this.eq = true; - } - - @Override - public boolean isSlice() - { - return slice; - } - - @Override - public boolean isEQ() - { - return eq; - } - - @Override - public boolean isIN() - { - return in; - } - - @Override - public boolean isOnToken() - { - return false; - } - - @Override - public boolean isContains() - { - return contains; - } - - @Override - public boolean isMultiColumn() - { - return false; - } - - @Override - public boolean usesFunction(String ksName, String functionName) - { - return restrictions.usesFunction(ksName, functionName); - } - - @Override - public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException - { - if (restriction.isMultiColumn()) - { - checkTrue(isEmpty(), - "Mixing single column relations and multi column relations on clustering columns is not allowed"); - return (PrimaryKeyRestrictions) restriction; - } - - if (restriction.isOnToken()) - { - checkTrue(isEmpty(), "Columns \"%s\" cannot be restricted by both a normal relation and a token relation", - ((TokenRestriction) restriction).getColumnNamesAsString()); - return (PrimaryKeyRestrictions) restriction; - } - - return new SingleColumnPrimaryKeyRestrictions(this, (SingleColumnRestriction) restriction); - } - - @Override - public List valuesAsComposites(QueryOptions options) throws InvalidRequestException - { - CompositesBuilder builder = new CompositesBuilder(ctype.builder(), ctype); - for (ColumnDefinition def : restrictions.getColumnDefs()) - { - Restriction r = restrictions.getRestriction(def); - assert !r.isSlice(); - - List values = r.values(options); - - if (values.isEmpty()) - return Collections.emptyList(); - - builder.addEachElementToAll(values); - checkFalse(builder.containsNull(), "Invalid null value for column %s", def.name); - } - - return builder.build(); - } - - @Override - public List boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException - { - CBuilder builder = ctype.builder(); - List defs = new ArrayList<>(restrictions.getColumnDefs()); - - CompositesBuilder compositeBuilder = new CompositesBuilder(builder, ctype); - // The end-of-component of composite doesn't depend on whether the - // component type is reversed or not (i.e. the ReversedType is applied - // to the component comparator but not to the end-of-component itself), - // it only depends on whether the slice is reversed - int keyPosition = 0; - for (ColumnDefinition def : defs) - { - // In a restriction, we always have Bound.START < Bound.END for the "base" comparator. - // So if we're doing a reverse slice, we must inverse the bounds when giving them as start and end of the slice filter. - // But if the actual comparator itself is reversed, we must inversed the bounds too. - Bound b = !def.isReversedType() ? bound : bound.reverse(); - Restriction r = restrictions.getRestriction(def); - if (keyPosition != def.position() || r.isContains()) - { - EOC eoc = !compositeBuilder.isEmpty() && bound.isEnd() ? EOC.END : EOC.NONE; - return compositeBuilder.buildWithEOC(eoc); - } - if (r.isSlice()) - { - if (!r.hasBound(b)) - { - // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix. - // For composites, if there was preceding component and we're computing the end, we must change the last component - // End-Of-Component, otherwise we would be selecting only one record. - EOC eoc = !compositeBuilder.isEmpty() && bound.isEnd() ? EOC.END : EOC.NONE; - return compositeBuilder.buildWithEOC(eoc); - } - - ByteBuffer value = checkNotNull(r.bounds(b, options).get(0), "Invalid null clustering key part %s", r); - compositeBuilder.addElementToAll(value); - Composite.EOC eoc = eocFor(r, bound, b); - return compositeBuilder.buildWithEOC(eoc); - } - - List values = r.values(options); - - if (values.isEmpty()) - return Collections.emptyList(); - - compositeBuilder.addEachElementToAll(values); - - checkFalse(compositeBuilder.containsNull(), "Invalid null clustering key part %s", def.name); - keyPosition++; - } - // Means no relation at all or everything was an equal - // Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection, - // it would be harmless to do it. However, we use this method got the partition key too. And when a query - // with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that - // case using the eoc would be bad, since for the random partitioner we have no guarantee that - // prefix.end() will sort after prefix (see #5240). - EOC eoc = bound.isEnd() && compositeBuilder.hasRemaining() ? EOC.END : EOC.NONE; - return compositeBuilder.buildWithEOC(eoc); - } - - @Override - public List values(QueryOptions options) throws InvalidRequestException - { - return Composites.toByteBuffers(valuesAsComposites(options)); - } - - @Override - public List bounds(Bound b, QueryOptions options) throws InvalidRequestException - { - return Composites.toByteBuffers(boundsAsComposites(b, options)); - } - - private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound) - { - if (eocBound.isStart()) - return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END; - - return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START; - } - - @Override - public boolean hasBound(Bound b) - { - if (isEmpty()) - return false; - return restrictions.lastRestriction().hasBound(b); - } - - @Override - public boolean isInclusive(Bound b) - { - if (isEmpty()) - return false; - return restrictions.lastRestriction().isInclusive(b); - } - - @Override - public boolean hasSupportingIndex(SecondaryIndexManager indexManager) - { - return restrictions.hasSupportingIndex(indexManager); - } - - @Override - public void addIndexExpressionTo(List expressions, QueryOptions options) throws InvalidRequestException - { - restrictions.addIndexExpressionTo(expressions, options); - } - - @Override - public Collection getColumnDefs() - { - return restrictions.getColumnDefs(); - } -} \ No newline at end of file diff --git a/cql3/restrictions/single_column_primary_key_restrictions.hh b/cql3/restrictions/single_column_primary_key_restrictions.hh new file mode 100644 index 0000000000..2125d8b49f --- /dev/null +++ b/cql3/restrictions/single_column_primary_key_restrictions.hh @@ -0,0 +1,272 @@ +/* + * 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. + */ + +/* + * Copyright 2015 Cloudius Systems + * + * Modified by Cloudius Systems + */ + +#pragma once + +#include +#include "schema.hh" +#include "cartesian_product.hh" +#include "cql3/restrictions/primary_key_restrictions.hh" +#include "cql3/restrictions/single_column_restrictions.hh" + +namespace cql3 { + +namespace restrictions { + +/** + * A set of single column restrictions on a primary key part (partition key or clustering key). + */ +class single_column_primary_key_restrictions : public primary_key_restrictions { +private: + schema_ptr _schema; + ::shared_ptr _restrictions; + ::shared_ptr> _tuple; + bool _slice; + bool _contains; + bool _in; +public: + single_column_primary_key_restrictions(schema_ptr schema, ::shared_ptr> tuple) + : _schema(schema) + , _restrictions(::make_shared(schema)) + , _tuple(std::move(tuple)) + , _slice(false) + , _contains(false) + , _in(false) + { } + + virtual bool is_on_token() override { + return false; + } + + virtual bool is_multi_column() override { + return false; + } + + virtual bool is_slice() override { + return _slice; + } + + virtual bool is_contains() override { + return _contains; + } + + virtual bool is_IN() override { + return _in; + } + + virtual bool uses_function(const sstring& ks_name, const sstring& function_name) override { + return _restrictions->uses_function(ks_name, function_name); + } + + void do_merge_with(::shared_ptr restriction) { + if (!_restrictions->empty()) { + auto last_column = *_restrictions->last_column(); + auto new_column = restriction->get_column_def(); + + if (_slice && _schema->position(new_column) > _schema->position(last_column)) { + throw exceptions::invalid_request_exception(sprint( + "Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", + new_column.name_as_text(), last_column.name_as_text())); + } + + if (_schema->position(new_column) < _schema->position(last_column)) { + if (restriction->is_slice()) { + throw exceptions::invalid_request_exception(sprint( + "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", + _restrictions->next_column(new_column)->name_as_text(), new_column.name_as_text())); + } + } + } + + _slice |= restriction->is_slice(); + _in |= restriction->is_IN(); + _contains |= restriction->is_contains(); + _restrictions->add_restriction(restriction); + } + + virtual void merge_with(::shared_ptr restriction) override { + if (restriction->is_multi_column()) { + throw exceptions::invalid_request_exception( + "Mixing single column relations and multi column relations on clustering columns is not allowed"); + } + + if (restriction->is_on_token()) { + fail(unimplemented::cause::TOKEN_RESTRICTION); +#if 0 + throw exceptions::invalid_request_exception("Columns \"%s\" cannot be restricted by both a normal relation and a token relation", + ((TokenRestriction) restriction).getColumnNamesAsString()); +#endif + } + do_merge_with(::static_pointer_cast(restriction)); + } + + virtual std::vector values_as_serialized_tuples(const query_options& options) override { + std::vector> value_vector; + value_vector.reserve(_restrictions->size()); + for (auto def : _restrictions->get_column_defs()) { + auto r = _restrictions->get_restriction(*def); + assert(!r->is_slice()); + + std::vector values = r->values(options); + for (auto&& val : values) { + if (!val) { + throw exceptions::invalid_request_exception(sprint("Invalid null value for column %s", def->name_as_text())); + } + } + if (values.empty()) { + return {}; + } + value_vector.emplace_back(std::move(values)); + } + + std::vector result; + result.reserve(cartesian_product_size(value_vector)); + for (auto&& v : make_cartesian_product(value_vector)) { + result.emplace_back(_tuple->serialize_value(v)); + } + return result; + } + + virtual std::vector bounds(const query_options& options) override { + std::vector ranges; + std::vector> vec_of_values; + + // TODO: optimize for all EQ case + + for (auto def : _restrictions->get_column_defs()) { + auto r = _restrictions->get_restriction(*def); + + if (vec_of_values.size() != _schema->position(*def) || r->is_contains()) { + // The prefixes built so far are the longest we can build, + // the rest of the constraints will have to be applied using filtering. + break; + } + + if (r->is_slice()) { + // TODO: make restriction::bounds() return query::range to simplify all this + if (cartesian_product_is_empty(vec_of_values)) { + auto read_value = [r, &options] (statements::bound b) { + auto value = r->bounds(b, options)[0]; + if (!value) { + throw exceptions::invalid_request_exception(sprint("Invalid null clustering key part %s", r->to_string())); + } + return *value; + }; + if (r->has_bound(statements::bound::START) && r->has_bound(statements::bound::END)) { + ranges.emplace_back(query::range(read_value(statements::bound::START), read_value(statements::bound::END), + r->is_inclusive(statements::bound::START), r->is_inclusive(statements::bound::END))); + } else if (r->has_bound(statements::bound::START)) { + ranges.emplace_back(query::range::make_starting_with(read_value(statements::bound::START), + r->is_inclusive(statements::bound::START))); + } else { + assert(r->has_bound(statements::bound::END)); + ranges.emplace_back(query::range::make_ending_with(read_value(statements::bound::END), + r->is_inclusive(statements::bound::END))); + } + if (def->type->is_reversed()) { + ranges.back().reverse(); + } + return std::move(ranges); + } + + ranges.reserve(cartesian_product_size(vec_of_values)); + for (auto&& prefix : make_cartesian_product(vec_of_values)) { + auto read_bounds = [r, &prefix, &options, this](bytes& value_holder, bool& inclusive_holder, statements::bound bound) { + if (r->has_bound(bound)) { + auto value = std::move(r->bounds(bound, options)[0]); + if (!value) { + throw exceptions::invalid_request_exception(sprint("Invalid null clustering key part %s", r->to_string())); + } + prefix.emplace_back(std::move(value)); + value_holder = _tuple->serialize_value(prefix); + prefix.pop_back(); + inclusive_holder = r->is_inclusive(bound); + } else { + value_holder = _tuple->serialize_value(prefix); + inclusive_holder = true; + } + }; + + bytes start_tuple; + bytes end_tuple; + bool start_inclusive; + bool end_inclusive; + + read_bounds(start_tuple, start_inclusive, statements::bound::START); + read_bounds(end_tuple, end_inclusive, statements::bound::END); + ranges.emplace_back(query::range(std::move(start_tuple), std::move(end_tuple), + start_inclusive, end_inclusive)); + if (def->type->is_reversed()) { + ranges.back().reverse(); + } + } + + return std::move(ranges); + } + + auto values = r->values(options); + for (auto&& val : values) { + if (!val) { + throw exceptions::invalid_request_exception(sprint("Invalid null clustering key part %s", def->name_as_text())); + } + } + if (values.empty()) { + return {}; + } + vec_of_values.emplace_back(std::move(values)); + } + + ranges.reserve(cartesian_product_size(vec_of_values)); + for (auto&& prefix : make_cartesian_product(vec_of_values)) { + ranges.emplace_back(query::range::make_singular(_tuple->serialize_value(prefix))); + } + + return std::move(ranges); + } + +#if 0 + virtual bool hasSupportingIndex(SecondaryIndexManager indexManager) override { + return restrictions.hasSupportingIndex(indexManager); + } + + virtual void addIndexExpressionTo(List expressions, QueryOptions options) override { + restrictions.addIndexExpressionTo(expressions, options); + } +#endif + + virtual std::vector get_column_defs() override { + return _restrictions->get_column_defs(); + } + + virtual bool empty() override { + return _restrictions->empty(); + } + + virtual uint32_t size() override { + return _restrictions->size(); + } +}; + +} +} diff --git a/unimplemented.cc b/unimplemented.cc index 413108e200..8a42994a37 100644 --- a/unimplemented.cc +++ b/unimplemented.cc @@ -24,6 +24,7 @@ std::ostream& operator<<(std::ostream& out, cause c) { case cause::METRICS: return out << "METRICS"; case cause::COMPACT_TABLES: return out << "COMPACT_TABLES"; case cause::GOSSIP: return out << "GOSSIP"; + case cause::TOKEN_RESTRICTION: return out << "TOKEN_RESTRICTION"; } assert(0); } diff --git a/unimplemented.hh b/unimplemented.hh index 6acd0d0902..7e2d9da19a 100644 --- a/unimplemented.hh +++ b/unimplemented.hh @@ -22,7 +22,8 @@ enum class cause { COUNTERS, METRICS, COMPACT_TABLES, - GOSSIP + GOSSIP, + TOKEN_RESTRICTION, }; void fail(cause what) __attribute__((noreturn));