mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-22 17:40:34 +00:00
This PR finally removes the `term` class and replaces it with `expression`.
* There was some trouble with `lwt_cache_id` in `expr::function_call`.
The current code works the following way:
* for each `function_call` inside a `term` that describes a pk restriction, `prepare_context::add_pk_function_call` is called.
* `add_pk_function_call` takes a `::shared_ptr<cql3::functions::function_call>`, sets its `cache_id` and pushes this shared pointer onto a vector of all collected function calls
* Later when some condiition is met we want to clear cache ids of all those collected function calls. To do this we iterate through shared pointers collected in `prepare_context` and clear cache id for each of them.
This doesn't work with `expr::function_call` because it isn't kept inside a shared pointer.
To solve this I put the `lwt_cache_id` inside a shared pointer and then `prepare_context` collects these shared pointers to cache ids.
I also experimented with doing this without any shared pointers, maybe we could just walk through the expression and clear the cache ids ourselves. But the problem is that expressions are copied all the time, we could clear the cache in one place, but forget about a copy. Doing it using shared pointers more closely matches the original behaviour.
The experiment is on the [term2-pr3-backup-altcache](https://github.com/cvybhu/scylla/tree/term2-pr3-backup-altcache) branch
* `shared_ptr<term>` being `nullptr` could mean:
* It represents a cql value `null`
* That there is no value, like `std::nullopt` (for example in `attributes.hh`)
* That it's a mistake, it shouldn't be possible
A good way to distinguish between optional and mistake is to look for `my_term->bind_and_get()`, we then know that it's not an optional value.
* On the other hand `raw_value` cased to bool means:
* `false` - null or unset
* `true` - some value, maybe empty
I ran a simple benchmark on my laptop to see how performance is affected:
```
build/release/test/perf/perf_simple_query --smp 1 -m 1G --operations-per-shard 1000000 --task-quota-ms 10
```
* On master (a21b1fbb2f) I get:
```
176506.60 tps ( 77.0 allocs/op, 12.0 tasks/op, 45831 insns/op)
median 176506.60 tps ( 77.0 allocs/op, 12.0 tasks/op, 45831 insns/op)
median absolute deviation: 0.00
maximum: 176506.60
minimum: 176506.60
```
* On this branch I get:
```
172225.30 tps ( 75.1 allocs/op, 12.1 tasks/op, 46106 insns/op)
median 172225.30 tps ( 75.1 allocs/op, 12.1 tasks/op, 46106 insns/op)
median absolute deviation: 0.00
maximum: 172225.30
minimum: 172225.30
```
Closes #9481
* github.com:scylladb/scylla:
cql3: Remove remaining mentions of term
cql3: Remove term
cql3: Rename prepare_term to prepare_expression
cql3: Make prepare_term return an expression instead of term
cql3: expr: Add size check to evaluate_set
cql3: expr: Add expr::contains_bind_marker
cql3: expr: Rename find_atom to find_binop
cql3: expr: Add find_in_expression
cql3: Remove term in operations
cql3: Remove term in relations
cql3: Remove term in multi_column_restrictions
cql3: Remove term in term_slice, rename to bounds_slice
cql3: expr: Remove term in expression
cql3: expr: Add evaluate_IN_list(expression, options)
cql3: Remove term in column_condition
cql3: Remove term in select_statement
cql3: Remove term in update_statement
cql3: Use internal cql format in insert_prepared_json_statement cache
types: Add map_type_impl::serialize(range of <bytes, bytes>)
cql3: Remove term in cql3/attributes
cql3: expr: Add constant::view() method
cql3: expr: Implement fill_prepare_context(expression)
cql3: expr: add expr::visit that takes a mutable expression
cql3: expr: Add receiver to expr::bind_variable
191 lines
8.4 KiB
C++
191 lines
8.4 KiB
C++
/*
|
|
* 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) 2015-present 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "cql3/single_column_relation.hh"
|
|
#include "cql3/restrictions/single_column_restriction.hh"
|
|
#include "cql3/statements/request_validations.hh"
|
|
#include "cql3/cql3_type.hh"
|
|
#include "cql3/lists.hh"
|
|
#include "unimplemented.hh"
|
|
#include "types/map.hh"
|
|
#include "types/list.hh"
|
|
|
|
#include <seastar/util/defer.hh>
|
|
|
|
using namespace cql3::expr;
|
|
|
|
namespace cql3 {
|
|
|
|
expression
|
|
single_column_relation::to_expression(const std::vector<lw_shared_ptr<column_specification>>& receivers,
|
|
const expr::expression& raw,
|
|
database& db,
|
|
const sstring& keyspace,
|
|
prepare_context& ctx) const {
|
|
// TODO: optimize vector away, accept single column_specification
|
|
assert(receivers.size() == 1);
|
|
auto expr = prepare_expression(raw, db, keyspace, receivers[0]);
|
|
expr::fill_prepare_context(expr, ctx);
|
|
return expr;
|
|
}
|
|
|
|
::shared_ptr<restrictions::restriction>
|
|
single_column_relation::new_EQ_restriction(database& db, schema_ptr schema, prepare_context& ctx) {
|
|
const column_definition& column_def = to_column_definition(*schema, *_entity);
|
|
auto reset_processing_pk_column = defer([&ctx] () noexcept { ctx.set_processing_pk_restrictions(false); });
|
|
if (column_def.is_partition_key()) {
|
|
ctx.set_processing_pk_restrictions(true);
|
|
}
|
|
if (!_map_key) {
|
|
auto r = ::make_shared<restrictions::single_column_restriction>(column_def);
|
|
auto e = to_expression(to_receivers(*schema, column_def), *_value, db, schema->ks_name(), ctx);
|
|
r->expression = binary_operator{column_value{&column_def}, expr::oper_t::EQ, std::move(e)};
|
|
return r;
|
|
}
|
|
auto&& receivers = to_receivers(*schema, column_def);
|
|
auto&& entry_key = to_expression({receivers[0]}, *_map_key, db, schema->ks_name(), ctx);
|
|
auto&& entry_value = to_expression({receivers[1]}, *_value, db, schema->ks_name(), ctx);
|
|
auto r = make_shared<restrictions::single_column_restriction>(column_def);
|
|
r->expression = binary_operator{
|
|
column_value(&column_def, std::move(entry_key)), oper_t::EQ, std::move(entry_value)};
|
|
return r;
|
|
}
|
|
|
|
::shared_ptr<restrictions::restriction>
|
|
single_column_relation::new_IN_restriction(database& db, schema_ptr schema, prepare_context& ctx) {
|
|
using namespace restrictions;
|
|
const column_definition& column_def = to_column_definition(*schema, *_entity);
|
|
auto reset_processing_pk_column = defer([&ctx] () noexcept { ctx.set_processing_pk_restrictions(false); });
|
|
if (column_def.is_partition_key()) {
|
|
ctx.set_processing_pk_restrictions(true);
|
|
}
|
|
auto receivers = to_receivers(*schema, column_def);
|
|
assert(_in_values.empty() || !_value);
|
|
if (_value) {
|
|
auto e = to_expression(receivers, *_value, db, schema->ks_name(), ctx);
|
|
auto r = ::make_shared<single_column_restriction>(column_def);
|
|
r->expression = binary_operator{column_value{&column_def}, expr::oper_t::IN, std::move(e)};
|
|
return r;
|
|
}
|
|
auto expressions = to_expressions(receivers, _in_values, db, schema->ks_name(), ctx);
|
|
// Convert a single-item IN restriction to an EQ restriction
|
|
if (expressions.size() == 1) {
|
|
auto r = ::make_shared<single_column_restriction>(column_def);
|
|
r->expression = binary_operator{column_value{&column_def}, expr::oper_t::EQ, std::move(expressions[0])};
|
|
return r;
|
|
}
|
|
auto r = ::make_shared<single_column_restriction>(column_def);
|
|
collection_constructor list_value {
|
|
.style = collection_constructor::style_type::list,
|
|
.elements = std::move(expressions),
|
|
.type = list_type_impl::get_instance(column_def.type, false),
|
|
};
|
|
r->expression = binary_operator{
|
|
column_value{&column_def},
|
|
expr::oper_t::IN,
|
|
std::move(list_value)};
|
|
return r;
|
|
}
|
|
|
|
::shared_ptr<restrictions::restriction>
|
|
single_column_relation::new_LIKE_restriction(
|
|
database& db, schema_ptr schema, prepare_context& ctx) {
|
|
const column_definition& column_def = to_column_definition(*schema, *_entity);
|
|
if (!column_def.type->is_string()) {
|
|
throw exceptions::invalid_request_exception(
|
|
format("LIKE is allowed only on string types, which {} is not", column_def.name_as_text()));
|
|
}
|
|
auto e = to_expression(to_receivers(*schema, column_def), *_value, db, schema->ks_name(), ctx);
|
|
auto r = ::make_shared<restrictions::single_column_restriction>(column_def);
|
|
r->expression = binary_operator{column_value{&column_def}, expr::oper_t::LIKE, std::move(e)};
|
|
return r;
|
|
}
|
|
|
|
std::vector<lw_shared_ptr<column_specification>>
|
|
single_column_relation::to_receivers(const schema& schema, const column_definition& column_def) const
|
|
{
|
|
using namespace statements::request_validations;
|
|
auto receiver = column_def.column_specification;
|
|
|
|
if (schema.is_dense() && column_def.is_regular()) {
|
|
throw exceptions::invalid_request_exception(format("Predicates on the non-primary-key column ({}) of a COMPACT table are not yet supported", column_def.name_as_text()));
|
|
}
|
|
|
|
if (is_contains() && !receiver->type->is_collection()) {
|
|
throw exceptions::invalid_request_exception(format("Cannot use CONTAINS on non-collection column \"{}\"", receiver->name));
|
|
}
|
|
|
|
if (is_contains_key()) {
|
|
if (!dynamic_cast<const map_type_impl*>(receiver->type.get())) {
|
|
throw exceptions::invalid_request_exception(format("Cannot use CONTAINS KEY on non-map column {}", receiver->name));
|
|
}
|
|
}
|
|
|
|
if (_map_key) {
|
|
check_false(dynamic_cast<const list_type_impl*>(receiver->type.get()), "Indexes on list entries ({}[index] = value) are not currently supported.", receiver->name);
|
|
check_true(dynamic_cast<const map_type_impl*>(receiver->type.get()), "Column {} cannot be used as a map", receiver->name);
|
|
check_true(receiver->type->is_multi_cell(), "Map-entry equality predicates on frozen map column {} are not supported", receiver->name);
|
|
check_true(is_EQ(), "Only EQ relations are supported on map entries");
|
|
}
|
|
|
|
if (receiver->type->is_collection()) {
|
|
// We don't support relations against entire collections (unless they're frozen), like "numbers = {1, 2, 3}"
|
|
check_false(receiver->type->is_multi_cell() && !is_legal_relation_for_non_frozen_collection(),
|
|
"Collection column '{}' ({}) cannot be restricted by a '{}' relation",
|
|
receiver->name,
|
|
receiver->type->as_cql3_type(),
|
|
get_operator());
|
|
|
|
if (is_contains_key() || is_contains()) {
|
|
receiver = make_collection_receiver(receiver, is_contains_key());
|
|
} else if (receiver->type->is_multi_cell() && _map_key && is_EQ()) {
|
|
return {
|
|
make_collection_receiver(receiver, true),
|
|
make_collection_receiver(receiver, false),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {std::move(receiver)};
|
|
}
|
|
|
|
}
|