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)