diff --git a/configure.py b/configure.py index a6e6d4a7a5..cc11663028 100755 --- a/configure.py +++ b/configure.py @@ -418,6 +418,7 @@ scylla_core = (['database.cc', 'db/index/secondary_index.cc', 'db/marshal/type_parser.cc', 'db/batchlog_manager.cc', + 'db/view/view.cc', 'io/io.cc', 'utils/utils.cc', 'utils/UUID_gen.cc', diff --git a/db/view/view.cc b/db/view/view.cc new file mode 100644 index 0000000000..426debcc4c --- /dev/null +++ b/db/view/view.cc @@ -0,0 +1,129 @@ +/* + * 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 (C) 2017 ScyllaDB + * + * Modified by ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include + +#include "clustering_bounds_comparator.hh" +#include "cql3/statements/select_statement.hh" +#include "cql3/util.hh" +#include "db/view/view.hh" + +namespace db { + +namespace view { + +cql3::statements::select_statement& view::select_statement() const { + if (!_select_statement) { + std::vector included; + if (!_schema->view_info()->include_all_columns()) { + included.reserve(_schema->all_columns_in_select_order().size()); + boost::transform(_schema->all_columns_in_select_order(), std::back_inserter(included), std::mem_fn(&column_definition::name_as_text)); + } + auto raw = cql3::util::build_select_statement(_schema->view_info()->base_name(), _schema->view_info()->where_clause(), std::move(included)); + raw->prepare_keyspace(_schema->ks_name()); + raw->set_bound_variables({}); + cql3::cql_stats ignored; + auto prepared = raw->prepare(service::get_local_storage_proxy().get_db().local(), ignored, true); + _select_statement = static_pointer_cast(prepared->statement); + } + return *_select_statement; +} + +const query::partition_slice& view::partition_slice() const { + if (!_partition_slice) { + _partition_slice = select_statement().make_partition_slice(cql3::query_options({ })); + } + return *_partition_slice; +} + +const dht::partition_range_vector& view::partition_ranges() const { + if (!_partition_ranges) { + _partition_ranges = select_statement().get_restrictions()->get_partition_key_ranges(cql3::query_options({ })); + } + return *_partition_ranges; +} + +bool view::partition_key_matches(const ::schema& base, const dht::decorated_key& key) const { + dht::ring_position rp(key); + auto& ranges = partition_ranges(); + return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) { + return range.contains(rp, dht::ring_position_comparator(base)); + }); +} + +bool view::clustering_prefix_matches(const ::schema& base, const partition_key& key, const clustering_key_prefix& ck) const { + bound_view::compare less(base); + auto& ranges = partition_slice().row_ranges(base, key); + return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) { + auto bounds = bound_view::from_range(range); + return !less(ck, bounds.first) && !less(bounds.second, ck); + }); +} + +bool view::may_be_affected_by(const ::schema& base, const dht::decorated_key& key, const rows_entry& update) const { + // We can guarantee that the view won't be affected if: + // - the primary key is excluded by the view filter (note that this isn't true of the filter on regular columns: + // even if an update don't match a view condition on a regular column, that update can still invalidate a + // pre-existing entry); + // - the update doesn't modify any of the columns impacting the view (where "impacting" the view means that column + // is neither included in the view, nor used by the view filter). + if (!partition_key_matches(base, key) && !clustering_prefix_matches(base, key.key(), update.key())) { + return false; + } + + // We want to check if the update modifies any of the columns that are part of the view (in which case the view is + // affected). But iff the view includes all the base table columns, or the update has either a row deletion or a + // row marker, we know the view is affected right away. + if (_schema->view_info()->include_all_columns() || update.row().deleted_at() || update.row().marker().is_live()) { + return true; + } + + bool affected = false; + update.row().cells().for_each_cell_until([&] (column_id id, const atomic_cell_or_collection& cell) { + affected = _schema->get_column_definition(base.column_at(column_kind::regular_column, id).name()); + return stop_iteration(affected); + }); + return affected; +} + +} // namespace view +} // namespace db + diff --git a/db/view/view.hh b/db/view/view.hh index 460c6409ad..0a9ab3f26c 100644 --- a/db/view/view.hh +++ b/db/view/view.hh @@ -21,7 +21,16 @@ #pragma once +#include "dht/i_partitioner.hh" +#include "query-request.hh" #include "schema.hh" +#include "stdx.hh" + +namespace cql3 { +namespace statements { +class select_statement; +} +} namespace db { @@ -29,10 +38,13 @@ namespace view { class view final { view_ptr _schema; + mutable shared_ptr _select_statement; + mutable stdx::optional _partition_slice; + mutable stdx::optional _partition_ranges; public: - view(view_ptr schema) - : _schema(schema) - { } + explicit view(view_ptr schema) + : _schema(std::move(schema)) { + } view_ptr schema() const { return _schema; @@ -40,7 +52,40 @@ public: void update(view_ptr new_schema) { _schema = new_schema; + _select_statement = nullptr; + _partition_slice = { }; + _partition_ranges = { }; } + + /** + * Whether the view filter considers the specified partition key. + * + * @param base the base table schema. + * @param key the partition key that is updated. + * @return false if we can guarantee that inserting an update for specified key + * won't affect the view in any way, true otherwise. + */ + bool partition_key_matches(const ::schema& base, const dht::decorated_key& key) const; + + /** + * Whether the view might be affected by the provided update. + * + * Note that having this method return true is not an absolute guarantee that the view will be + * updated, just that it most likely will, but a false return guarantees it won't be affected. + * + * @param base the base table schema. + * @param key the partition key that is updated. + * @param update the base table update being applied. + * @return false if we can guarantee that inserting update for key + * won't affect the view in any way, true otherwise. + */ + bool may_be_affected_by(const ::schema& base, const dht::decorated_key& key, const rows_entry& update) const; + +private: + cql3::statements::select_statement& select_statement() const; + const query::partition_slice& partition_slice() const; + const dht::partition_range_vector& partition_ranges() const; + bool clustering_prefix_matches(const ::schema& base, const partition_key& key, const clustering_key_prefix& ck) const; }; } diff --git a/query-request.hh b/query-request.hh index 559d1f2311..a89594f7d8 100644 --- a/query-request.hh +++ b/query-request.hh @@ -124,6 +124,8 @@ public: partition_slice(partition_slice&&); ~partition_slice(); + partition_slice& operator=(partition_slice&& other) noexcept; + const clustering_row_ranges& row_ranges(const schema&, const partition_key&) const; void set_range(const schema&, const partition_key&, clustering_row_ranges); void clear_range(const schema&, const partition_key&); diff --git a/query.cc b/query.cc index 90c4d85aca..f20c22dc20 100644 --- a/query.cc +++ b/query.cc @@ -90,6 +90,8 @@ partition_slice::partition_slice(clustering_row_ranges row_ranges, partition_slice::partition_slice(partition_slice&&) = default; +partition_slice& partition_slice::operator=(partition_slice&& other) noexcept = default; + // Only needed because selection_statement::execute does copies of its read_command // in the map-reduce op. partition_slice::partition_slice(const partition_slice& s)